diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Emitter.cs b/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Emitter.cs index 4afff41866d349..623a55becda34c 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Emitter.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Emitter.cs @@ -341,10 +341,18 @@ private void GenParameters(LoggerMethod lm) _builder.Append(", "); } + if (p.IsScoped) + { + _builder.Append("scoped "); + } if (p.Qualifier != null) { _builder.Append($"{p.Qualifier} "); } + if (p.IsParams) + { + _builder.Append("params "); + } _builder.Append($"{p.Type} {p.CodeName}"); } } diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Parser.cs b/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Parser.cs index 6af2a7856ed41e..0d397324038636 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Parser.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Parser.cs @@ -366,6 +366,10 @@ public IReadOnlyList GetLogClasses(IEnumerable GetLogClasses(IEnumerable !IsLogger && !IsException && !IsLogLevel; @@ -1034,7 +1046,9 @@ internal sealed class LoggerParameter IsLogger = IsLogger, IsException = IsException, IsLogLevel = IsLogLevel, - IsEnumerable = IsEnumerable + IsEnumerable = IsEnumerable, + IsParams = IsParams, + IsScoped = IsScoped }; } @@ -1051,6 +1065,8 @@ internal sealed record LoggerParameterSpec : IEquatable public required bool IsException { get; init; } public required bool IsLogLevel { get; init; } public required bool IsEnumerable { get; init; } + public required bool IsParams { get; init; } + public required bool IsScoped { get; init; } // A parameter flagged as IsTemplateParameter is not going to be taken care of specially as an argument to ILogger.Log // but instead is supposed to be taken as a parameter for the template. @@ -1067,7 +1083,9 @@ public bool Equals(LoggerParameterSpec? other) IsLogger == other.IsLogger && IsException == other.IsException && IsLogLevel == other.IsLogLevel && - IsEnumerable == other.IsEnumerable; + IsEnumerable == other.IsEnumerable && + IsParams == other.IsParams && + IsScoped == other.IsScoped; } public override int GetHashCode() @@ -1080,6 +1098,8 @@ public override int GetHashCode() hash = HashHelpers.Combine(hash, IsException.GetHashCode()); hash = HashHelpers.Combine(hash, IsLogLevel.GetHashCode()); hash = HashHelpers.Combine(hash, IsEnumerable.GetHashCode()); + hash = HashHelpers.Combine(hash, IsParams.GetHashCode()); + hash = HashHelpers.Combine(hash, IsScoped.GetHashCode()); return hash; } } diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Roslyn4.0.cs b/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Roslyn4.0.cs index 40b7f73b6bf98e..480fc324f3121c 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Roslyn4.0.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Roslyn4.0.cs @@ -230,7 +230,9 @@ private static LoggerClass FromSpec(LoggerClassSpec spec) IsLogger = paramSpec.IsLogger, IsException = paramSpec.IsException, IsLogLevel = paramSpec.IsLogLevel, - IsEnumerable = paramSpec.IsEnumerable + IsEnumerable = paramSpec.IsEnumerable, + IsParams = paramSpec.IsParams, + IsScoped = paramSpec.IsScoped }); } @@ -245,7 +247,9 @@ private static LoggerClass FromSpec(LoggerClassSpec spec) IsLogger = paramSpec.IsLogger, IsException = paramSpec.IsException, IsLogLevel = paramSpec.IsLogLevel, - IsEnumerable = paramSpec.IsEnumerable + IsEnumerable = paramSpec.IsEnumerable, + IsParams = paramSpec.IsParams, + IsScoped = paramSpec.IsScoped }); } diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/Baselines/TestWithParamsArray.generated.txt b/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/Baselines/TestWithParamsArray.generated.txt new file mode 100644 index 00000000000000..8f168928b61ff2 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/Baselines/TestWithParamsArray.generated.txt @@ -0,0 +1,25 @@ +// +#nullable enable + +namespace Microsoft.Extensions.Logging.Generators.Tests.TestClasses +{ + partial class TestWithParamsArray + { + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Logging.Generators", "%VERSION%")] + private static readonly global::System.Action __M0Callback = + global::Microsoft.Extensions.Logging.LoggerMessage.Define(global::Microsoft.Extensions.Logging.LogLevel.Information, new global::Microsoft.Extensions.Logging.EventId(0, nameof(M0)), "M0 {p1} {args}", new global::Microsoft.Extensions.Logging.LogDefineOptions() { SkipEnabledCheck = true }); + + /// + /// Message: M0 {p1} {args} + /// Level: Information + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Logging.Generators", "%VERSION%")] + public static partial void M0(global::Microsoft.Extensions.Logging.ILogger logger, global::System.String p1, params global::System.Object?[] args) + { + if (logger.IsEnabled(global::Microsoft.Extensions.Logging.LogLevel.Information)) + { + __M0Callback(logger, p1, args, null); + } + } + } +} \ No newline at end of file diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/Baselines/TestWithParamsCollection.generated.txt b/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/Baselines/TestWithParamsCollection.generated.txt new file mode 100644 index 00000000000000..5410d472641f67 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/Baselines/TestWithParamsCollection.generated.txt @@ -0,0 +1,25 @@ +// +#nullable enable + +namespace Microsoft.Extensions.Logging.Generators.Tests.TestClasses +{ + partial class TestWithParamsCollection + { + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Logging.Generators", "%VERSION%")] + private static readonly global::System.Action, global::System.Exception?> __M0Callback = + global::Microsoft.Extensions.Logging.LoggerMessage.Define>(global::Microsoft.Extensions.Logging.LogLevel.Information, new global::Microsoft.Extensions.Logging.EventId(0, nameof(M0)), "M0 {p1} {args}", new global::Microsoft.Extensions.Logging.LogDefineOptions() { SkipEnabledCheck = true }); + + /// + /// Message: M0 {p1} {args} + /// Level: Information + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Logging.Generators", "%VERSION%")] + public static partial void M0(global::Microsoft.Extensions.Logging.ILogger logger, global::System.String p1, params global::System.Collections.Generic.IEnumerable args) + { + if (logger.IsEnabled(global::Microsoft.Extensions.Logging.LogLevel.Information)) + { + __M0Callback(logger, p1, args, null); + } + } + } +} \ No newline at end of file diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/LoggerMessageGeneratorEmitterTests.cs b/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/LoggerMessageGeneratorEmitterTests.cs index 95ea729d1a1d5d..b7599585fa5e3d 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/LoggerMessageGeneratorEmitterTests.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/LoggerMessageGeneratorEmitterTests.cs @@ -98,6 +98,21 @@ internal static partial class TestWithMoreThan6Params await VerifyAgainstBaselineUsingFile("TestWithMoreThan6Params.generated.txt", testSourceCode); } + [Fact] + public async Task TestBaseline_TestWithParamsArray_Success() + { + string testSourceCode = @" +namespace Microsoft.Extensions.Logging.Generators.Tests.TestClasses +{ + internal static partial class TestWithParamsArray + { + [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = ""M0 {p1} {args}"")] + public static partial void M0(ILogger logger, string p1, params object?[] args); + } +}"; + await VerifyAgainstBaselineUsingFile("TestWithParamsArray.generated.txt", testSourceCode); + } + [Fact] public async Task TestBaseline_TestWithDynamicLogLevel_Success() { @@ -214,6 +229,22 @@ public sealed class BarAttribute : Attribute { } } #if ROSLYN4_8_OR_GREATER + [Fact] + public async Task TestBaseline_TestWithParamsCollection_Success() + { + string testSourceCode = @" +using System.Collections.Generic; +namespace Microsoft.Extensions.Logging.Generators.Tests.TestClasses +{ + internal static partial class TestWithParamsCollection + { + [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = ""M0 {p1} {args}"")] + public static partial void M0(ILogger logger, string p1, params IEnumerable args); + } +}"; + await VerifyAgainstBaselineUsingFile("TestWithParamsCollection.generated.txt", testSourceCode); + } + [Fact] public async Task TestBaseline_TestWithLoggerFromPrimaryConstructor_Success() { diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/LoggerMessageGeneratorParserTests.cs b/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/LoggerMessageGeneratorParserTests.cs index 83262d8451e028..dcde763bc85684 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/LoggerMessageGeneratorParserTests.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/LoggerMessageGeneratorParserTests.cs @@ -859,6 +859,100 @@ partial class C Assert.Contains("p1", diagnostics[0].GetMessage(), StringComparison.InvariantCulture); } + [Fact] + public async Task ParamsParameterOK() + { + IReadOnlyList diagnostics = await RunGenerator(@" + partial class C + { + [LoggerMessage(EventId = 0, Level = LogLevel.Debug, Message = ""Parameter {args}"")] + static partial void M(ILogger logger, params object?[] args); + }"); + + Assert.Empty(diagnostics); + } + +#if ROSLYN4_8_OR_GREATER + [Fact] + public async Task ParamsCollectionParameterOK() + { + IReadOnlyList diagnostics = await RunGenerator(@" + using System.Collections.Generic; + partial class C + { + [LoggerMessage(EventId = 0, Level = LogLevel.Debug, Message = ""Parameter {args}"")] + static partial void M(ILogger logger, params IEnumerable args); + }"); + + Assert.Empty(diagnostics); + } + + [Fact] + public async Task RefReadOnlyParameterOK() + { + IReadOnlyList diagnostics = await RunGenerator(@" + partial class C + { + [LoggerMessage(EventId = 0, Level = LogLevel.Debug, Message = ""Parameter {p1}"")] + static partial void M(ILogger logger, ref readonly int p1); + }"); + + Assert.Empty(diagnostics); + } + + [Fact] + public async Task ScopedRefParameterOK() + { + IReadOnlyList diagnostics = await RunGenerator(@" + partial class C + { + [LoggerMessage(EventId = 0, Level = LogLevel.Debug, Message = ""Parameter {p1}"")] + static partial void M(ILogger logger, scoped ref int p1); + }"); + + Assert.Empty(diagnostics); + } + + [Fact] + public async Task ScopedRefReadOnlyParameterOK() + { + IReadOnlyList diagnostics = await RunGenerator(@" + partial class C + { + [LoggerMessage(EventId = 0, Level = LogLevel.Debug, Message = ""Parameter {p1}"")] + static partial void M(ILogger logger, scoped ref readonly int p1); + }"); + + Assert.Empty(diagnostics); + } + + [Fact] + public async Task RefReadOnlyParameterWithMoreThan6ParamsOK() + { + IReadOnlyList diagnostics = await RunGenerator(@" + partial class C + { + [LoggerMessage(EventId = 0, Level = LogLevel.Debug, Message = ""{p1} {p2} {p3} {p4} {p5} {p6} {p7}"")] + static partial void M(ILogger logger, int p1, int p2, int p3, int p4, int p5, int p6, ref readonly int p7); + }"); + + Assert.Empty(diagnostics); + } + + [Fact] + public async Task ScopedRefParameterWithMoreThan6ParamsOK() + { + IReadOnlyList diagnostics = await RunGenerator(@" + partial class C + { + [LoggerMessage(EventId = 0, Level = LogLevel.Debug, Message = ""{p1} {p2} {p3} {p4} {p5} {p6} {p7}"")] + static partial void M(ILogger logger, int p1, int p2, int p3, int p4, int p5, int p6, scoped ref int p7); + }"); + + Assert.Empty(diagnostics); + } +#endif + [Fact] public async Task MalformedFormatString() { diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/ParameterTestExtensions.cs b/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/ParameterTestExtensions.cs index f51fc2e62f6a96..4b550446307b82 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/ParameterTestExtensions.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/ParameterTestExtensions.cs @@ -15,5 +15,19 @@ internal struct S [LoggerMessage(EventId = 1, Level = LogLevel.Information, Message = "UseRefParameter {s}")] internal static partial void UseRefParameter(ILogger logger, ref S s); + + [LoggerMessage(EventId = 2, Level = LogLevel.Information, Message = "UseParamsParameter {p1} {args}")] + internal static partial void UseParamsParameter(ILogger logger, string p1, params object?[] args); + +#if ROSLYN4_8_OR_GREATER + [LoggerMessage(EventId = 3, Level = LogLevel.Information, Message = "UseRefReadOnlyParameter {s}")] + internal static partial void UseRefReadOnlyParameter(ILogger logger, ref readonly S s); + + [LoggerMessage(EventId = 4, Level = LogLevel.Information, Message = "UseScopedRefParameter {s}")] + internal static partial void UseScopedRefParameter(ILogger logger, scoped ref S s); + + [LoggerMessage(EventId = 5, Level = LogLevel.Information, Message = "UseParamsCollectionParameter {p1} {args}")] + internal static partial void UseParamsCollectionParameter(ILogger logger, string p1, params System.Collections.Generic.IEnumerable args); +#endif } }