-
Notifications
You must be signed in to change notification settings - Fork 4k
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
New generator apis #74127
base: main
Are you sure you want to change the base?
New generator apis #74127
Conversation
/// <param name="generatorFilter">An optional filter that specifies which generators to run. If <c>null</c> all generators will run.</param> | ||
/// <param name="cancellationToken">Used to cancel an in progress operation.</param> | ||
/// <returns>An updated driver that contains the results of the generators running.</returns> | ||
public GeneratorDriver RunGenerators(Compilation compilation, Func<GeneratorFilterContext, bool>? generatorFilter = null, CancellationToken cancellationToken = default) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I couldn't figure out a way to make generatorFilter
non-optional (as discussed in the API review) without breaking our back compat guidelines.
Another option would be to just make this a differently named method, like RunGeneratorsWithFilter
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see the problem - what guidelines would be broken?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Our analyzer requires that we don't have multiple overloads with optional parameters: https://github.com/dotnet/roslyn/blob/main/docs/Adding%20Optional%20Parameters%20in%20Public%20API.md
That means that the signatures when we make generatorFilter
required are:
RunGenerators(Compilation, CancellationToken)
RunGenerators(Compilation, Func<> filter, CancellationToken = default)
Anyone who was calling RunGenerators(Compilation)
will now be source broken, because we no longer have an overload with only a single required parameter.
Note: It also says that we shouldn't add new optional parameters in the middle of a signature for source-compat. Imagine if you had RunGenerator(compilation, default)
that might bind to a different overload now. So we're in a bit of tricky situation.
We can either: make the filter optional, and bend the 'don't put optional in the middle rule', or we can make it non-optional, and introduce a third overload of RunGenerators
that only takes a Compilation
parameter.
Tagging @333fred for any API shape thoughts on this one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can make it non-optional, and introduce a third overload of
RunGenerators
that only takes aCompilation
parameter.
This sounds like the best option to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the problem with the signature in the current form? Yes it's optional but it just gets filled in as essentially "run everything". That behavior is compatible with the existing overload so that all seems to track for me. What am I missing?
@dotnet/roslyn-compiler for review please. |
@jjonescz PTAL |
src/Compilers/Core/Portable/SourceGeneration/GeneratorDriver.cs
Outdated
Show resolved
Hide resolved
/// <param name="generatorFilter">An optional filter that specifies which generators to run. If <c>null</c> all generators will run.</param> | ||
/// <param name="cancellationToken">Used to cancel an in progress operation.</param> | ||
/// <returns>An updated driver that contains the results of the generators running.</returns> | ||
public GeneratorDriver RunGenerators(Compilation compilation, Func<GeneratorFilterContext, bool>? generatorFilter = null, CancellationToken cancellationToken = default) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see the problem - what guidelines would be broken?
src/Compilers/Core/Portable/SourceGeneration/GeneratorExtensions.cs
Outdated
Show resolved
Hide resolved
|
||
// step didn't run because the generator was already up to date | ||
Assert.False(stepRan); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RegisterPostInitializationOutput should run even if the generator is filtered out, right? Consider testing that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm, this is tricky. It seems logical to say that we shouldn't even init a generator if it's filtered out, which would skip the post init stage.
However, we also have a step there where we re-parse the post init docs if the parse options change. Right now that's a single flag as it's assumed all the generators run. If we skip a generator on a parse options change then it'll not get them reparsed and cause errors the next time we run.
We would need to track per-generator if they have had their parse options updated or not, so that we can re-parse them as needed. The alternative is to just always do the init + postinit steps for a generator even if its requested to be skipped.
Let me play around a little and see how hard the per-generator flag would be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, the per generator flag is actually trivial, as we can just compare the produced trees with the latest parse options.
I've updated it so that the generator doesn't init, and doesn't take part in the compilation at all if it's filtered out + added tests to cover those cases.
Add tests to ensure we re-parse init only trees if the parse options changed between filtered runs
1a24208
to
28299c6
Compare
@@ -47,7 +47,7 @@ public static Type GetGeneratorType(this IIncrementalGenerator generator) | |||
/// Converts an <see cref="IIncrementalGenerator"/> into an <see cref="ISourceGenerator"/> object that can be used when constructing a <see cref="GeneratorDriver"/> | |||
/// </summary> | |||
/// <param name="incrementalGenerator">The incremental generator to wrap</param> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// <param name="incrementalGenerator">The incremental generator to wrap</param> | |
/// <param name="incrementalGenerator">The incremental generator to convert</param> |
@@ -124,5 +125,7 @@ public GeneratorState WithError(Exception exception, Diagnostic error, TimeSpan | |||
internal ImmutableDictionary<string, ImmutableArray<IncrementalGeneratorRunStep>> OutputSteps { get; } | |||
|
|||
internal ImmutableArray<(string Key, string Value)> HostOutputs { get; } | |||
|
|||
internal bool RequiresPostInitReparse(ParseOptions parseOptions) => PostInitTrees.Any(t => t.Tree.Options != parseOptions); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider using static
lambda if possible:
internal bool RequiresPostInitReparse(ParseOptions parseOptions) => PostInitTrees.Any(t => t.Tree.Options != parseOptions); | |
internal bool RequiresPostInitReparse(ParseOptions parseOptions) => PostInitTrees.Any(static (t, parseOptions) => t.Tree.Options != parseOptions, parseOptions); |
@@ -20,8 +20,7 @@ namespace Microsoft.CodeAnalysis | |||
DriverStateTable stateTable, | |||
SyntaxStore syntaxStore, | |||
GeneratorDriverOptions driverOptions, | |||
TimeSpan runtime, | |||
bool parseOptionsChanged) | |||
TimeSpan runtime) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we remove the internal readonly bool ParseOptionsChanged;
field as well?
/// A dummy extension that is used to indicate this adaptor was created outside of the driver. | ||
/// </summary> | ||
public const string DummySourceExtension = ".dummy"; | ||
|
||
private readonly string _sourceExtension; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a value that is controllable by customers? If so have reservations about using a magic string. Had trouble tracking this through all the APIs to see where it originates.
/// <param name="generatorFilter">An optional filter that specifies which generators to run. If <c>null</c> all generators will run.</param> | ||
/// <param name="cancellationToken">Used to cancel an in progress operation.</param> | ||
/// <returns>An updated driver that contains the results of the generators running.</returns> | ||
public GeneratorDriver RunGenerators(Compilation compilation, Func<GeneratorFilterContext, bool>? generatorFilter = null, CancellationToken cancellationToken = default) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the problem with the signature in the current form? Yes it's optional but it just gets filled in as essentially "run everything". That behavior is compatible with the existing overload so that all seems to track for me. What am I missing?
@@ -195,7 +205,7 @@ public GeneratorDriverTimingInfo GetTimingInfo() | |||
return new GeneratorDriverTimingInfo(_state.RunTime, generatorTimings); | |||
} | |||
|
|||
internal GeneratorDriverState RunGeneratorsCore(Compilation compilation, DiagnosticBag? diagnosticsBag, CancellationToken cancellationToken = default) | |||
internal GeneratorDriverState RunGeneratorsCore(Compilation compilation, DiagnosticBag? diagnosticsBag, Func<GeneratorFilterContext, bool>? generatorFilter = null, CancellationToken cancellationToken = default) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than having the filter be optional here why not normalize it in the constructor to be non-null? Essentially somethnig like
generatorFilter ??= static _ => true;
Believe that should be amortized to a single alloc, if not could cache as a static readonly
on the type
Adds APIs to convert
ISourceGenerators
toIIncrementalGenerators
and allows the generator driver to only update a sub-set of generators via a new filter parameter.Fixes #74039
Fixes #74086