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

Add helper for source generators to filter to nodes with attribute matching an attribute name #61293

Merged
merged 157 commits into from
Jun 14, 2022

Conversation

CyrusNajmabadi
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi commented May 12, 2022

Fixes #54725

// Create an aggregated view of all global aliases across all files. This should only update when an individual
// file changes its global aliases.
var allUpGlobalAliasesProvider = individualFileGlobalAliasesProvider
.Collect().Select((arrays, _) => GlobalAliases.Create(arrays.SelectMany(a => a.AliasAndSymbolNames).ToImmutableArray()));
Copy link
Member Author

Choose a reason for hiding this comment

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

note: it's unclear to me how Collect works. My hope is that it doesn't rerun later parts if it sees that all its inputs have retained value-equality.

Copy link
Member

Choose a reason for hiding this comment

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

Collect is implemented using the BatchNode<T> which does have caching semantics. At the same time we should be able to verify this with testing correct?

Copy link
Member Author

Choose a reason for hiding this comment

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

yeah, i believe this is workign properly (and all the tests validate each stage of hte pipeline. wouldn't mind additional confirmation though. I was askign this prior to getting all the tests and learning a lot that way.


// Combine the two providers so that we reanalyze every file if the global aliases change, or we reanalyze a
// particular file when it's compilation unit changes.
var compilationUnitAndGlobalAliasesProvider = compilationUnitProvider.Combine(allUpGlobalAliasesProvider);
Copy link
Member Author

Choose a reason for hiding this comment

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

similarly, if a change happens and the aggregrated global aliases list is unchanged, i'm hoping this provider will not rerun its transform for all compilation units.

Copy link
Member

@AraHaan AraHaan Jun 10, 2022

Choose a reason for hiding this comment

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

In this case would it change when e.g DesignTime vs normal compilation unit type changes (when no code has changed between the DesignTime and the normal compilation unit?

Copy link
Member Author

Choose a reason for hiding this comment

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

If no code changes, then it won't rerun.

@jaredpar
Copy link
Member

jaredpar commented Jun 8, 2022

Took some time this afternoon to see how this new API would work in existing attribute based generators. It's a really smooth transition to the new API:

@CyrusNajmabadi
Copy link
Member Author

@stephentoub Do you have other generators that look for attributes to determine what to run on?

@stephentoub
Copy link
Member

All of our generators. In addition to the two Jared mentions, also the LibraryImport generator, the LoggingMessage generator, and the EventSource generator.

@CyrusNajmabadi
Copy link
Member Author

Thanks!

@AraHaan
Copy link
Member

AraHaan commented Jun 10, 2022

I also have an attribute based generator too, as for migration to the new apis it would need to be tested with it which is located here.

internal IncrementalValuesProvider<T> ForAttributeWithMetadataName<T>(
string fullyQualifiedMetadataName,
Func<SyntaxNode, CancellationToken, bool> predicate,
Func<GeneratorAttributeSyntaxContext, CancellationToken, T> transform)
Copy link
Contributor

Choose a reason for hiding this comment

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

We should move this into SyntaxValueProvider. You can pass through any of the inputs you need at the .ctor.

// Group all the nodes by syntax tree, so we can process a whole syntax tree at a time. This will let us make
// the required semantic model for it once, instead of potentially many times (in the rare, but possible case of
// a single file with a ton of matching nodes in it).
var groupedNodes = collectedNodes.SelectMany(
Copy link
Contributor

Choose a reason for hiding this comment

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

It's possible to re-write this code so that it isn't based on the public API and instead operates as an ISyntaxSelectionStrategy<T> that gets executed when the node is asked to update. That will allow it to share semantic model and syntax tree walks with the other syntax inputs. That should improve perf and reduce the memory usage, but it's something we can do afterwards without changing the public API


public IncrementalValueProvider<Compilation> CompilationProvider => new IncrementalValueProvider<Compilation>(SharedInputNodes.Compilation.WithRegisterOutput(RegisterOutput).WithTrackingName(WellKnownGeneratorInputs.Compilation));

internal IncrementalValueProvider<CompilationOptions> CompilationOptionsProvider => new(SharedInputNodes.CompilationOptions.WithRegisterOutput(RegisterOutput).WithTrackingName(WellKnownGeneratorInputs.CompilationOptions));
Copy link
Contributor

Choose a reason for hiding this comment

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

This isn't used anywhere other than inside ForAttributeWithSimpleName, so we can just use it directly in that method instead.

var syntaxHelper = this.SyntaxHelper;

// Create a provider that provides (and updates) the global aliases for any particular file when it is edited.
var individualFileGlobalAliasesProvider = this.SyntaxProvider.CreateSyntaxProvider(
Copy link
Contributor

Choose a reason for hiding this comment

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

Basically what @jaredpar said, its possible to make this more efficient if we re-write it as an ISyntaxSelectionStrategy<T> but it won't affect the public API and we should not block on it. Lets do it in a follow up PR at some point in the future.

// Create a provider that provides (and updates) the global aliases for any particular file when it is edited.
var individualFileGlobalAliasesProvider = this.SyntaxProvider.CreateSyntaxProvider(
static (n, _) => n is ICompilationUnitSyntax,
static (context, _) => getGlobalAliasesInCompilationUnit(context.SyntaxHelper, context.Node)).WithTrackingName("individualFileGlobalAliases_ForAttribute");
Copy link
Contributor

Choose a reason for hiding this comment

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

We need the tracking names for our internal testing, but they 'leak' into public as they'll now show up in users tracking results if they use this API. I think thats fine, but I think we should get consensus that we're a) ok with that and b) agree that this is not a public API contract and the nodes can be removed / renamed without it being considered a breaking change.

Copy link
Member Author

Choose a reason for hiding this comment

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

but I think we should get consensus that we're a) ok with that and b) agree that this is not a public API contract and the nodes can be removed / renamed without it being considered a breaking change.

Totally agreed. We coudl bring that to API meeting and then doc this explicitly.

@CyrusNajmabadi
Copy link
Member Author

We should move this into SyntaxValueProvider. You can pass through any of the inputs you need at the .ctor.

Done.

@CyrusNajmabadi
Copy link
Member Author

This isn't used anywhere other than inside ForAttributeWithSimpleName, so we can just use it directly in that method instead.

I tried ot move this. HOwever, it depends on RegisterOutput, which i couldn't reference from another type. So i wans't sure how best to move this. What do you recommend?

@CyrusNajmabadi
Copy link
Member Author

@chsienki anything blocking on this? or are you ok for this goign in and doing any improvements as followup work to come?

@chsienki
Copy link
Contributor

This isn't used anywhere other than inside ForAttributeWithSimpleName, so we can just use it directly in that method instead.

I tried ot move this. HOwever, it depends on RegisterOutput, which i couldn't reference from another type. So i wans't sure how best to move this. What do you recommend?

Ok, no worries, this is internal so fine for now.

Copy link
Contributor

@chsienki chsienki left a comment

Choose a reason for hiding this comment

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

LGTM. Thanks!

@CyrusNajmabadi CyrusNajmabadi merged commit 44c573d into dotnet:main Jun 14, 2022
@CyrusNajmabadi CyrusNajmabadi deleted the attributeLookup branch June 14, 2022 21:01
@ghost ghost added this to the Next milestone Jun 14, 2022
@RikkiGibson RikkiGibson modified the milestones: Next, 17.3 P3 Jun 28, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Higher Level SyntaxProvider APIs for incremental generators
7 participants