diff --git a/eng/Versions.props b/eng/Versions.props index 42d6f811752efa..a86e7cc942f824 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -40,9 +40,9 @@ - - 3.9.0 - 3.9.0 + + 4.0.0-3.final + 4.0.0-3.final diff --git a/src/libraries/Common/tests/SourceGenerators/RoslynTestUtils.cs b/src/libraries/Common/tests/SourceGenerators/RoslynTestUtils.cs index 4928270b06f170..1c57260b8601de 100644 --- a/src/libraries/Common/tests/SourceGenerators/RoslynTestUtils.cs +++ b/src/libraries/Common/tests/SourceGenerators/RoslynTestUtils.cs @@ -141,10 +141,9 @@ public static TextSpan MakeSpan(string text, int spanNum) /// Runs a Roslyn generator over a set of source files. /// public static async Task<(ImmutableArray, ImmutableArray)> RunGenerator( - ISourceGenerator generator, + IIncrementalGenerator generator, IEnumerable? references, IEnumerable sources, - AnalyzerConfigOptionsProvider? optionsProvider = null, bool includeBaseReferences = true, CancellationToken cancellationToken = default) { @@ -156,7 +155,9 @@ public static TextSpan MakeSpan(string text, int spanNum) Compilation? comp = await proj!.GetCompilationAsync(CancellationToken.None).ConfigureAwait(false); - CSharpGeneratorDriver cgd = CSharpGeneratorDriver.Create(new[] { generator }, optionsProvider: optionsProvider); + // workaround https://github.com/dotnet/roslyn/pull/55866. We can remove "LangVersion=Preview" when we get a Roslyn build with that change. + CSharpParseOptions options = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Preview); + CSharpGeneratorDriver cgd = CSharpGeneratorDriver.Create(new[] { generator.AsSourceGenerator() }, parseOptions: options); GeneratorDriver gd = cgd.RunGenerators(comp!, cancellationToken); GeneratorDriverRunResult r = gd.GetRunResult(); 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 4a995039104b9d..b448bc79081e8d 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Parser.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Parser.cs @@ -17,6 +17,8 @@ public partial class LoggerMessageGenerator { internal class Parser { + private const string LoggerMessageAttribute = "Microsoft.Extensions.Logging.LoggerMessageAttribute"; + private readonly CancellationToken _cancellationToken; private readonly Compilation _compilation; private readonly Action _reportDiagnostic; @@ -28,13 +30,41 @@ public Parser(Compilation compilation, Action reportDiagnostic, Canc _reportDiagnostic = reportDiagnostic; } + internal static bool IsSyntaxTargetForGeneration(SyntaxNode node) => + node is MethodDeclarationSyntax m && m.AttributeLists.Count > 0; + + internal static ClassDeclarationSyntax? GetSemanticTargetForGeneration(GeneratorSyntaxContext context) + { + var methodDeclarationSyntax = (MethodDeclarationSyntax)context.Node; + + foreach (AttributeListSyntax attributeListSyntax in methodDeclarationSyntax.AttributeLists) + { + foreach (AttributeSyntax attributeSyntax in attributeListSyntax.Attributes) + { + IMethodSymbol attributeSymbol = context.SemanticModel.GetSymbolInfo(attributeSyntax).Symbol as IMethodSymbol; + if (attributeSymbol == null) + { + continue; + } + + INamedTypeSymbol attributeContainingTypeSymbol = attributeSymbol.ContainingType; + string fullName = attributeContainingTypeSymbol.ToDisplayString(); + + if (fullName == LoggerMessageAttribute) + { + return methodDeclarationSyntax.Parent as ClassDeclarationSyntax; + } + } + } + + return null; + } + /// /// Gets the set of logging classes containing methods to output. /// public IReadOnlyList GetLogClasses(IEnumerable classes) { - const string LoggerMessageAttribute = "Microsoft.Extensions.Logging.LoggerMessageAttribute"; - INamedTypeSymbol loggerMessageAttribute = _compilation.GetTypeByMetadataName(LoggerMessageAttribute); if (loggerMessageAttribute == null) { @@ -442,11 +472,11 @@ public IReadOnlyList GetLogClasses(IEnumerable + bool IsAllowedKind(SyntaxKind kind) => kind == SyntaxKind.ClassDeclaration || kind == SyntaxKind.StructDeclaration || kind == SyntaxKind.RecordDeclaration; - + while (parentLoggerClass != null && IsAllowedKind(parentLoggerClass.Kind())) { currentLoggerClass.ParentClass = new LoggerClass diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.cs b/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.cs index 92105d515e328c..7aaca68e758a28 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.cs @@ -3,7 +3,10 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Tracing; +using System.Linq; using System.Runtime.CompilerServices; using System.Text; using Microsoft.CodeAnalysis; @@ -15,50 +18,38 @@ namespace Microsoft.Extensions.Logging.Generators { [Generator] - public partial class LoggerMessageGenerator : ISourceGenerator + public partial class LoggerMessageGenerator : IIncrementalGenerator { - [ExcludeFromCodeCoverage] - public void Initialize(GeneratorInitializationContext context) + public void Initialize(IncrementalGeneratorInitializationContext context) { - context.RegisterForSyntaxNotifications(SyntaxReceiver.Create); + IncrementalValuesProvider classDeclarations = context.SyntaxProvider + .CreateSyntaxProvider(static (s, _) => Parser.IsSyntaxTargetForGeneration(s), static (ctx, _) => Parser.GetSemanticTargetForGeneration(ctx)) + .Where(static m => m is not null); + + IncrementalValueProvider<(Compilation, ImmutableArray)> compilationAndClasses = + context.CompilationProvider.Combine(classDeclarations.Collect()); + + context.RegisterSourceOutput(compilationAndClasses, static (spc, source) => Execute(source.Item1, source.Item2, spc)); } - [ExcludeFromCodeCoverage] - public void Execute(GeneratorExecutionContext context) + private static void Execute(Compilation compilation, ImmutableArray classes, SourceProductionContext context) { - if (context.SyntaxReceiver is not SyntaxReceiver receiver || receiver.ClassDeclarations.Count == 0) + if (classes.IsDefaultOrEmpty) { // nothing to do yet return; } - var p = new Parser(context.Compilation, context.ReportDiagnostic, context.CancellationToken); - IReadOnlyList logClasses = p.GetLogClasses(receiver.ClassDeclarations); + IEnumerable distinctClasses = classes.Distinct(); + + var p = new Parser(compilation, context.ReportDiagnostic, context.CancellationToken); + IReadOnlyList logClasses = p.GetLogClasses(distinctClasses); if (logClasses.Count > 0) { var e = new Emitter(); string result = e.Emit(logClasses, context.CancellationToken); - - context.AddSource("LoggerMessage.g.cs", SourceText.From(result, Encoding.UTF8)); - } - } - [ExcludeFromCodeCoverage] - private sealed class SyntaxReceiver : ISyntaxReceiver - { - internal static ISyntaxReceiver Create() - { - return new SyntaxReceiver(); - } - - public List ClassDeclarations { get; } = new (); - - public void OnVisitSyntaxNode(SyntaxNode syntaxNode) - { - if (syntaxNode is ClassDeclarationSyntax classSyntax) - { - ClassDeclarations.Add(classSyntax); - } + context.AddSource("LoggerMessage.g.cs", SourceText.From(result, Encoding.UTF8)); } } } 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 b2fe61084c52ea..a1ec4253469ae7 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 @@ -380,6 +380,7 @@ public class Object {} public class Void {} public class String {} public struct DateTime {} + public abstract class Attribute {} } namespace System.Collections { @@ -392,10 +393,12 @@ public interface ILogger {} } namespace Microsoft.Extensions.Logging { - public class LoggerMessageAttribute {} + public class LoggerMessageAttribute : System.Attribute {} } partial class C { + [Microsoft.Extensions.Logging.LoggerMessage] + public static partial void Log(ILogger logger); } ", false, includeBaseReferences: false, includeLoggingReferences: false);