Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate LoggerMessageGenerator to IIncrementalGenerator #58068

Merged
merged 3 commits into from
Aug 27, 2021

Conversation

eerhardt
Copy link
Member

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

Note: The current plan is to enable multi-targeting in a separate PR, once the design for how to multi-target Roslyn Components is solidified. We are considering using the IIncrementalGenerator for RC1 in order to get feedback before doing the multi-targeting work.

cc @jaredpar

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 dotnet#56702
@ghost
Copy link

ghost commented Aug 24, 2021

Tagging subscribers to this area: @maryamariyan
See info in area-owners.md if you want to be subscribed.

Issue Details

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

Note: The current plan is to enable multi-targeting in a separate PR, once the design for how to multi-target Roslyn Components is solidified. We are considering using the IIncrementalGenerator for RC1 in order to get feedback before doing the multi-targeting work.

cc @jaredpar

Author: eerhardt
Assignees: -
Labels:

area-Extensions-Logging

Milestone: -

eng/Versions.props Outdated Show resolved Hide resolved
@@ -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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
node is MethodDeclarationSyntax m && m.AttributeLists.Count > 0;
node is MethodDeclarationSyntax { AttributeLists.Count: > 0 };

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO this falls into a "preference / style" category, and I don't think the suggested change reads well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stephentoub do you have a preference on this sugar?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with either style here (as long as they produce the same IL, which I believe they do). The pattern matching syntax is newer and takes some getting used to, and in a case like this, it doesn't buy you a whole lot; its benefit becomes more apparent when there are more things being checked, deeper levels of nesting being examined, etc.

{
if (context.SyntaxReceiver is not SyntaxReceiver receiver || receiver.ClassDeclarations.Count == 0)
if (classes.IsDefaultOrEmpty)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (classes.IsDefaultOrEmpty)
if (classes.IsEmpty)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why NullRef here when someone passes in an uninitialized ImmutableArray? Is this guaranteed to never have a null backing array?

{
// 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();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😕 Is this necessary?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. The Parser is currently written such that it expects distinct ClassDeclarations passed into it. I didn't want to re-write it completely since we are planning on multi-targeting with Roslyn 3.9 as well.

IncrementalValueProvider<(Compilation, ImmutableArray<ClassDeclarationSyntax>)> compilationAndClasses =
context.CompilationProvider.Combine(classDeclarations.Collect());

context.RegisterSourceOutput(compilationAndClasses, (spc, source) => Execute(source.Item1, source.Item2, spc));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chsienki Is this a case where you would recommend non-semantic source output?

Copy link
Member Author

@eerhardt eerhardt Aug 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does "non-semantic source output" mean?

If it means using RegisterImplementationSourceOutput, that doesn't work for this generator because this generator fills out partial methods. If we use RegisterImplementationSourceOutput, then the IDE doesn't see the implementation being filled out and you get red squiggles like the following:

image

Even though the "full build" succeeds.

{
// 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();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just curious as I haven't written against this API, why are there duplicates here? Unfortunate we have to new up a HashSet etc.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the Parser.IsSyntaxTargetForGeneration is keying off of MethodDeclarations that have LoggerMessageAttributes applied to them. So we are filtering down to the methods we care about. But the Parser is currently written in terms of ClassDeclarations, and I didn't want to completely re-write the Parser since the plan is to multi-target against Roslyn 3.9. If the Parser was re-written, we wouldn't be able to share as much code between the two targets.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if I did re-write the Parser, we would still need to map MethodDeclaration => ClassDeclaration => LoggerClass, which would probably involve a Dictionary. So we'd still need to have a hash of them.

@eerhardt eerhardt merged commit 230dd1d into dotnet:main Aug 27, 2021
@eerhardt eerhardt deleted the LoggingGeneratorV2 branch August 27, 2021 15:54
@eerhardt
Copy link
Member Author

/backport to release/6.0-rc1

@github-actions
Copy link
Contributor

Started backporting to release/6.0-rc1: https://github.com/dotnet/runtime/actions/runs/1174845336

@ghost ghost locked as resolved and limited conversation to collaborators Sep 26, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants