Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
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
  • Loading branch information
eerhardt authored and github-actions committed Aug 27, 2021
commit 1371a66c487d6714cfbf606ea16e08dd07da25b3
6 changes: 3 additions & 3 deletions eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@
<ProjectServicingConfiguration Include="Microsoft.NETCore.App.Ref" PatchVersion="0" />
</ItemGroup>
<PropertyGroup>
<!-- For source generator support we need to target a pinned version in order to be able to run on older versions of Roslyn -->
<MicrosoftCodeAnalysisCSharpWorkspacesVersion>3.9.0</MicrosoftCodeAnalysisCSharpWorkspacesVersion>
<MicrosoftCodeAnalysisVersion>3.9.0</MicrosoftCodeAnalysisVersion>
<!-- For source generator support we are targeting the latest version of Roslyn for now, until we can support multi-targeting -->
<MicrosoftCodeAnalysisCSharpWorkspacesVersion>4.0.0-2.final</MicrosoftCodeAnalysisCSharpWorkspacesVersion>
<MicrosoftCodeAnalysisVersion>4.0.0-2.final</MicrosoftCodeAnalysisVersion>
</PropertyGroup>
<PropertyGroup>
<!-- Code analysis dependencies -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,10 +141,9 @@ public static TextSpan MakeSpan(string text, int spanNum)
/// Runs a Roslyn generator over a set of source files.
/// </summary>
public static async Task<(ImmutableArray<Diagnostic>, ImmutableArray<GeneratedSourceResult>)> RunGenerator(
ISourceGenerator generator,
IIncrementalGenerator generator,
IEnumerable<Assembly>? references,
IEnumerable<string> sources,
AnalyzerConfigOptionsProvider? optionsProvider = null,
bool includeBaseReferences = true,
CancellationToken cancellationToken = default)
{
Expand All @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Diagnostic> _reportDiagnostic;
Expand All @@ -28,13 +30,42 @@ public Parser(Compilation compilation, Action<Diagnostic> 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;
}

/// <summary>
/// Gets the set of logging classes containing methods to output.
/// </summary>
public IReadOnlyList<LoggerClass> GetLogClasses(IEnumerable<ClassDeclarationSyntax> classes)
{
const string LoggerMessageAttribute = "Microsoft.Extensions.Logging.LoggerMessageAttribute";

INamedTypeSymbol loggerMessageAttribute = _compilation.GetTypeByMetadataName(LoggerMessageAttribute);
if (loggerMessageAttribute == null)
{
Expand Down Expand Up @@ -442,11 +473,11 @@ public IReadOnlyList<LoggerClass> GetLogClasses(IEnumerable<ClassDeclarationSynt
LoggerClass currentLoggerClass = lc;
var parentLoggerClass = (classDec.Parent as TypeDeclarationSyntax);

bool IsAllowedKind(SyntaxKind kind) =>
bool IsAllowedKind(SyntaxKind kind) =>
kind == SyntaxKind.ClassDeclaration ||
kind == SyntaxKind.StructDeclaration ||
kind == SyntaxKind.RecordDeclaration;

while (parentLoggerClass != null && IsAllowedKind(parentLoggerClass.Kind()))
{
currentLoggerClass.ParentClass = new LoggerClass
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<ClassDeclarationSyntax> classDeclarations = context.SyntaxProvider
.CreateSyntaxProvider((s, _) => Parser.IsSyntaxTargetForGeneration(s), (ctx, _) => Parser.GetSemanticTargetForGeneration(ctx))
.Where(m => m is not null);

IncrementalValueProvider<(Compilation, ImmutableArray<ClassDeclarationSyntax>)> 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<ClassDeclarationSyntax> 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<LoggerClass> logClasses = p.GetLogClasses(receiver.ClassDeclarations);
IEnumerable<ClassDeclarationSyntax> distinctClasses = classes.Distinct();

var p = new Parser(compilation, context.ReportDiagnostic, context.CancellationToken);
IReadOnlyList<LoggerClass> 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<ClassDeclarationSyntax> 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));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@ public class Object {}
public class Void {}
public class String {}
public struct DateTime {}
public abstract class Attribute {}
}
namespace System.Collections
{
Expand All @@ -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);

Expand Down