From 1371a66c487d6714cfbf606ea16e08dd07da25b3 Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Tue, 24 Aug 2021 17:10:38 -0500 Subject: [PATCH 1/3] Migrate LoggerMessageGenerator to IIncrementalGenerator This reduces the time spent in the background in VS running the source generator, since we only need to respond to methods that have the LoggerMessageAttribute on them. Contributes to #56702 --- eng/Versions.props | 6 +-- .../tests/SourceGenerators/RoslynTestUtils.cs | 7 +-- .../gen/LoggerMessageGenerator.Parser.cs | 39 +++++++++++++-- .../gen/LoggerMessageGenerator.cs | 49 ++++++++----------- .../LoggerMessageGeneratorParserTests.cs | 5 +- 5 files changed, 66 insertions(+), 40 deletions(-) diff --git a/eng/Versions.props b/eng/Versions.props index 42d6f811752efa..ee035defb6b9c4 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -40,9 +40,9 @@ - - 3.9.0 - 3.9.0 + + 4.0.0-2.final + 4.0.0-2.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..9577ca0b519567 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,42 @@ 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 (var 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 +473,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..cda31c43951b98 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((s, _) => Parser.IsSyntaxTargetForGeneration(s), (ctx, _) => Parser.GetSemanticTargetForGeneration(ctx)) + .Where(m => m is not null); + + IncrementalValueProvider<(Compilation, ImmutableArray)> compilationAndClasses = + context.CompilationProvider.Combine(classDeclarations.Collect()); + + context.RegisterSourceOutput(compilationAndClasses, (spc, source) => Execute(source.Item1, source.Item2, spc)); } - [ExcludeFromCodeCoverage] - public void Execute(GeneratorExecutionContext context) + private 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); From 103e8839cc5ba0a96187b6abf8d47312fd224023 Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Tue, 24 Aug 2021 18:18:17 -0500 Subject: [PATCH 2/3] PR feedback --- eng/Versions.props | 4 ++-- .../gen/LoggerMessageGenerator.cs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/eng/Versions.props b/eng/Versions.props index ee035defb6b9c4..a86e7cc942f824 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -41,8 +41,8 @@ - 4.0.0-2.final - 4.0.0-2.final + 4.0.0-3.final + 4.0.0-3.final diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.cs b/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.cs index cda31c43951b98..7aaca68e758a28 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.cs @@ -23,16 +23,16 @@ public partial class LoggerMessageGenerator : IIncrementalGenerator public void Initialize(IncrementalGeneratorInitializationContext context) { IncrementalValuesProvider classDeclarations = context.SyntaxProvider - .CreateSyntaxProvider((s, _) => Parser.IsSyntaxTargetForGeneration(s), (ctx, _) => Parser.GetSemanticTargetForGeneration(ctx)) - .Where(m => m is not null); + .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, (spc, source) => Execute(source.Item1, source.Item2, spc)); + context.RegisterSourceOutput(compilationAndClasses, static (spc, source) => Execute(source.Item1, source.Item2, spc)); } - private void Execute(Compilation compilation, ImmutableArray classes, SourceProductionContext context) + private static void Execute(Compilation compilation, ImmutableArray classes, SourceProductionContext context) { if (classes.IsDefaultOrEmpty) { From 630ed4447136495adb799c554202e2d931335e68 Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Wed, 25 Aug 2021 12:02:24 -0500 Subject: [PATCH 3/3] PR feedback --- .../gen/LoggerMessageGenerator.Parser.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 9577ca0b519567..b448bc79081e8d 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Parser.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Parser.cs @@ -39,7 +39,7 @@ internal static bool IsSyntaxTargetForGeneration(SyntaxNode node) => foreach (AttributeListSyntax attributeListSyntax in methodDeclarationSyntax.AttributeLists) { - foreach (var attributeSyntax in attributeListSyntax.Attributes) + foreach (AttributeSyntax attributeSyntax in attributeListSyntax.Attributes) { IMethodSymbol attributeSymbol = context.SemanticModel.GetSymbolInfo(attributeSyntax).Symbol as IMethodSymbol; if (attributeSymbol == null) @@ -55,7 +55,6 @@ internal static bool IsSyntaxTargetForGeneration(SyntaxNode node) => return methodDeclarationSyntax.Parent as ClassDeclarationSyntax; } } - } return null;