Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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}");
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,10 @@ public IReadOnlyList<LoggerClass> GetLogClasses(IEnumerable<ClassDeclarationSynt
keepMethod = false;
break;
}
else if (paramSymbol.RefKind == (RefKind)4) // RefKind.RefReadOnlyParameter, added in Roslyn 4.8
{
qualifier = "ref readonly";
}

string typeName = paramTypeSymbol.ToDisplayString(
SymbolDisplayFormat.FullyQualifiedFormat.WithMiscellaneousOptions(
Expand All @@ -381,7 +385,11 @@ public IReadOnlyList<LoggerClass> GetLogClasses(IEnumerable<ClassDeclarationSynt
IsException = !foundException && IsBaseOrIdentity(paramTypeSymbol, _exceptionSymbol, sm.Compilation),
IsLogLevel = !foundLogLevel && IsBaseOrIdentity(paramTypeSymbol, _logLevelSymbol, sm.Compilation),
IsEnumerable = IsBaseOrIdentity(paramTypeSymbol, _enumerableSymbol, sm.Compilation) && !IsBaseOrIdentity(paramTypeSymbol, _stringSymbol, sm.Compilation),
IsParams = paramSymbol.IsParams,
};
#if ROSLYN4_4_OR_GREATER
lp.IsScoped = paramSymbol.ScopedKind != ScopedKind.None;
#endif

foundLogger |= lp.IsLogger;
foundException |= lp.IsException;
Expand Down Expand Up @@ -1021,6 +1029,10 @@ internal sealed class LoggerParameter
public bool IsException;
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;
Expand All @@ -1034,7 +1046,9 @@ internal sealed class LoggerParameter
IsLogger = IsLogger,
IsException = IsException,
IsLogLevel = IsLogLevel,
IsEnumerable = IsEnumerable
IsEnumerable = IsEnumerable,
IsParams = IsParams,
IsScoped = IsScoped
};
}

Expand All @@ -1051,6 +1065,8 @@ internal sealed record LoggerParameterSpec : IEquatable<LoggerParameterSpec>
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.
Expand All @@ -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()
Expand All @@ -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;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
});
}

Expand All @@ -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
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// <auto-generated/>
#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<global::Microsoft.Extensions.Logging.ILogger, global::System.String, global::System.Object?[], global::System.Exception?> __M0Callback =
global::Microsoft.Extensions.Logging.LoggerMessage.Define<global::System.String, global::System.Object?[]>(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 });

/// <summary>
/// <para><b>Message:</b> M0 {p1} {args}</para>
/// <para><b>Level:</b> Information</para>
/// </summary>
[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);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// <auto-generated/>
#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::Microsoft.Extensions.Logging.ILogger, global::System.String, global::System.Collections.Generic.IEnumerable<global::System.String>, global::System.Exception?> __M0Callback =
global::Microsoft.Extensions.Logging.LoggerMessage.Define<global::System.String, global::System.Collections.Generic.IEnumerable<global::System.String>>(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 });

/// <summary>
/// <para><b>Message:</b> M0 {p1} {args}</para>
/// <para><b>Level:</b> Information</para>
/// </summary>
[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<global::System.String> args)
{
if (logger.IsEnabled(global::Microsoft.Extensions.Logging.LogLevel.Information))
{
__M0Callback(logger, p1, args, null);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down Expand Up @@ -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<string> args);
}
}";
await VerifyAgainstBaselineUsingFile("TestWithParamsCollection.generated.txt", testSourceCode);
}

[Fact]
public async Task TestBaseline_TestWithLoggerFromPrimaryConstructor_Success()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -859,6 +859,100 @@ partial class C
Assert.Contains("p1", diagnostics[0].GetMessage(), StringComparison.InvariantCulture);
}

[Fact]
public async Task ParamsParameterOK()
{
IReadOnlyList<Diagnostic> 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<Diagnostic> 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<string> args);
}");

Assert.Empty(diagnostics);
}

[Fact]
public async Task RefReadOnlyParameterOK()
{
IReadOnlyList<Diagnostic> 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<Diagnostic> 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<Diagnostic> 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<Diagnostic> 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<Diagnostic> 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()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> args);
#endif
}
}
Loading