From 6d93721406b9659fd89f20434f4fe2a295742d1f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 11:49:32 +0000 Subject: [PATCH 1/6] Initial plan From cba47620a77770e7b5afd17ebca96cf9574543c6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 12:28:51 +0000 Subject: [PATCH 2/6] Initial plan: params, ref readonly, scoped support in LoggerMessage generator Co-authored-by: svick <287848+svick@users.noreply.github.com> --- .../gen/LoggerMessageGenerator.Emitter.cs | 4 ++++ .../gen/LoggerMessageGenerator.Parser.cs | 10 ++++++++-- .../gen/LoggerMessageGenerator.Roslyn4.0.cs | 6 ++++-- .../TestClasses/ParameterTestExtensions.cs | 3 +++ 4 files changed, 19 insertions(+), 4 deletions(-) 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..1410071b9f2d51 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Emitter.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Emitter.cs @@ -345,6 +345,10 @@ private void GenParameters(LoggerMethod lm) { _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..a24f68948c38b0 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Parser.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Parser.cs @@ -381,6 +381,7 @@ public IReadOnlyList GetLogClasses(IEnumerable !IsLogger && !IsException && !IsLogLevel; @@ -1034,7 +1036,8 @@ internal sealed class LoggerParameter IsLogger = IsLogger, IsException = IsException, IsLogLevel = IsLogLevel, - IsEnumerable = IsEnumerable + IsEnumerable = IsEnumerable, + IsParams = IsParams }; } @@ -1051,6 +1054,7 @@ 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; } // 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 +1071,8 @@ public bool Equals(LoggerParameterSpec? other) IsLogger == other.IsLogger && IsException == other.IsException && IsLogLevel == other.IsLogLevel && - IsEnumerable == other.IsEnumerable; + IsEnumerable == other.IsEnumerable && + IsParams == other.IsParams; } public override int GetHashCode() @@ -1080,6 +1085,7 @@ 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()); 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..c3d9aac56a424a 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,8 @@ private static LoggerClass FromSpec(LoggerClassSpec spec) IsLogger = paramSpec.IsLogger, IsException = paramSpec.IsException, IsLogLevel = paramSpec.IsLogLevel, - IsEnumerable = paramSpec.IsEnumerable + IsEnumerable = paramSpec.IsEnumerable, + IsParams = paramSpec.IsParams }); } @@ -245,7 +246,8 @@ private static LoggerClass FromSpec(LoggerClassSpec spec) IsLogger = paramSpec.IsLogger, IsException = paramSpec.IsException, IsLogLevel = paramSpec.IsLogLevel, - IsEnumerable = paramSpec.IsEnumerable + IsEnumerable = paramSpec.IsEnumerable, + IsParams = paramSpec.IsParams }); } 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..65c8b59de9ce6e 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,8 @@ 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); } } From 212efcb9bfd5b7e19c1505d7e1fc915c93aeb531 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 12:43:24 +0000 Subject: [PATCH 3/6] Add params, ref readonly, and scoped support to LoggerMessage source generator Co-authored-by: svick <287848+svick@users.noreply.github.com> --- .../gen/LoggerMessageGenerator.Emitter.cs | 4 ++ .../gen/LoggerMessageGenerator.Parser.cs | 16 +++++- .../gen/LoggerMessageGenerator.Roslyn4.0.cs | 6 ++- .../TestWithParamsArray.generated.txt | 25 +++++++++ .../LoggerMessageGeneratorEmitterTests.cs | 15 ++++++ .../LoggerMessageGeneratorParserTests.cs | 54 +++++++++++++++++++ .../TestClasses/ParameterTestExtensions.cs | 8 +++ 7 files changed, 124 insertions(+), 4 deletions(-) create mode 100644 src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/Baselines/TestWithParamsArray.generated.txt 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 1410071b9f2d51..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,6 +341,10 @@ private void GenParameters(LoggerMethod lm) _builder.Append(", "); } + if (p.IsScoped) + { + _builder.Append("scoped "); + } if (p.Qualifier != null) { _builder.Append($"{p.Qualifier} "); 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 a24f68948c38b0..3a2449ba369b9b 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; @@ -1037,7 +1045,8 @@ internal sealed class LoggerParameter IsException = IsException, IsLogLevel = IsLogLevel, IsEnumerable = IsEnumerable, - IsParams = IsParams + IsParams = IsParams, + IsScoped = IsScoped }; } @@ -1055,6 +1064,7 @@ internal sealed record LoggerParameterSpec : IEquatable 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. @@ -1072,7 +1082,8 @@ public bool Equals(LoggerParameterSpec? other) IsException == other.IsException && IsLogLevel == other.IsLogLevel && IsEnumerable == other.IsEnumerable && - IsParams == other.IsParams; + IsParams == other.IsParams && + IsScoped == other.IsScoped; } public override int GetHashCode() @@ -1086,6 +1097,7 @@ public override int 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 c3d9aac56a424a..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 @@ -231,7 +231,8 @@ private static LoggerClass FromSpec(LoggerClassSpec spec) IsException = paramSpec.IsException, IsLogLevel = paramSpec.IsLogLevel, IsEnumerable = paramSpec.IsEnumerable, - IsParams = paramSpec.IsParams + IsParams = paramSpec.IsParams, + IsScoped = paramSpec.IsScoped }); } @@ -247,7 +248,8 @@ private static LoggerClass FromSpec(LoggerClassSpec spec) IsException = paramSpec.IsException, IsLogLevel = paramSpec.IsLogLevel, IsEnumerable = paramSpec.IsEnumerable, - IsParams = paramSpec.IsParams + 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/LoggerMessageGeneratorEmitterTests.cs b/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/LoggerMessageGeneratorEmitterTests.cs index 95ea729d1a1d5d..65a192ff7a53f8 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() { 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..1d9ca3b90ae0ec 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,60 @@ 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 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); + } +#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 65c8b59de9ce6e..6f23f4c9b7f70a 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 @@ -18,5 +18,13 @@ internal struct 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); +#endif } } From eb2a98f064d5661d6c5ae813520e816e91572057 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 13:20:11 +0000 Subject: [PATCH 4/6] Fix CS0649 build error for IsScoped field in Roslyn 3.11 generator build Co-authored-by: svick <287848+svick@users.noreply.github.com> --- .../gen/LoggerMessageGenerator.Parser.cs | 2 ++ 1 file changed, 2 insertions(+) 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 3a2449ba369b9b..0d397324038636 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Parser.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Parser.cs @@ -1030,7 +1030,9 @@ internal sealed class LoggerParameter public bool IsLogLevel; public bool IsEnumerable; public bool IsParams; +#pragma warning disable CS0649 // Field is never assigned to in builds without ROSLYN4_4_OR_GREATER public bool IsScoped; +#pragma warning restore CS0649 // 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. public bool IsTemplateParameter => !IsLogger && !IsException && !IsLogLevel; From c39b81e7611ebce6a3d50995d5b08f7b329e77c1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 14:05:28 +0000 Subject: [PATCH 5/6] Add tests for C# 13 params collections in LoggerMessage source generator Co-authored-by: svick <287848+svick@users.noreply.github.com> --- .../TestWithParamsCollection.generated.txt | 25 +++++++++++++++++++ .../LoggerMessageGeneratorEmitterTests.cs | 16 ++++++++++++ .../LoggerMessageGeneratorParserTests.cs | 14 +++++++++++ .../TestClasses/ParameterTestExtensions.cs | 3 +++ 4 files changed, 58 insertions(+) create mode 100644 src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/Baselines/TestWithParamsCollection.generated.txt 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 65a192ff7a53f8..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 @@ -229,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 1d9ca3b90ae0ec..c67ddf0f6403d9 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 @@ -873,6 +873,20 @@ partial class C } #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() { 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 6f23f4c9b7f70a..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 @@ -25,6 +25,9 @@ internal struct 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 } } From bfad00a74fcdc47cb70780a17672b77e0b446b64 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Feb 2026 17:45:03 +0000 Subject: [PATCH 6/6] Add tests for ref readonly and scoped with more than 6 template parameters Co-authored-by: tarekgh <10833894+tarekgh@users.noreply.github.com> --- .../LoggerMessageGeneratorParserTests.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) 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 c67ddf0f6403d9..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 @@ -925,6 +925,32 @@ partial class C 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]