- Notifications
You must be signed in to change notification settings - Fork10.4k
Add source generator to emit public Program class definition#58199
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Conversation
@@ -73,6 +73,10 @@ This package is an internal implementation of the .NET Core SDK and is not meant | |||
Private="false" | |||
ReferenceOutputAssembly="false" /> | |||
<ProjectReference Include="..\..\AspNetCoreAnalyzers\src\SourceGenerators\Microsoft.AspNetCore.App.SourceGenerators.csproj" |
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.
@wtgodbe Can you sanity check my packaging changes here? I think it's correct given what we had to do in .NEt 8 for theRequestDelegateGenerator
but let me know if I missed something.
{ | ||
var internalGeneratedProgramClass = context.CompilationProvider.Select((compilation, cancellationToken) => | ||
{ | ||
var program = compilation.GetTypeByMetadataName("Program"); |
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.
cc:@amcasey for Roslyn review
Just curious, will there be any analyzer or anything that could point out this was added and that any manually added Program declarations can be removed? I have atonne of these I could delete with this built-in (example), so anything that could point it out rather than me needing to go find them all would be useful 😅 |
A different idea / approach: Update the compiler so that it can emit a Or is the process via compiler, sdk, etc. too tedious that this approach is cheaper? |
Oh, good idea! We can definitely author an analyzer + codefix that discovers the
There was a proposal to change the compiler's behavior to emit a public Program class by default for top-level statements but it was rejected because:
With those in mind, it was a bit more straightforward for ASP.NET to control its own destiny here and author a source generator. |
(Haven't read the code yet) We also don't want to do this if they'veexplicitly declared it internal, right? internalpartialclassProgram{} |
src/Framework/AspNetCoreAnalyzers/src/SourceGenerators/PublicTopLevelProgramGenerator.cs OutdatedShow resolvedHide resolved
Uh oh!
There was an error while loading.Please reload this page.
src/Framework/AspNetCoreAnalyzers/src/SourceGenerators/PublicTopLevelProgramGenerator.cs OutdatedShow resolvedHide resolved
Uh oh!
There was an error while loading.Please reload this page.
src/Framework/AspNetCoreAnalyzers/src/SourceGenerators/PublicTopLevelProgramGenerator.cs OutdatedShow resolvedHide resolved
Uh oh!
There was an error while loading.Please reload this page.
amcasey commentedOct 2, 2024 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
I'm pretty sure the compilation knows its ownentrypoint, but I'm not sure that's exposed anywhere. Edit:this might be a useful reference. |
captainsafia commentedOct 3, 2024 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Yep, this is another good case to cover in tests. Practically, I don't know how frequently users are doing this since the visibility of the
Good reference! I'll incorporate some of the checks here into our code. |
I kinda second@gfoidl's question, isn't there a better way to do this, which technically should just be some switch somewhere during project generation or similar, run only once. Instead a source generator which is evaluated more often. |
I started off with this strategy but reached a limitation because there's not a ton of ability to introspect what the Also,
I'm assuming you're referring to a switch that the user would set. I'm keen to have this behavior be on-by-default since that moves the needle for this experience a lot more than users having to switch from a declaration in their source code to another switch in their app code. |
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.
The checks that Program is the generated one seem sufficient. I'm ignorant about whether this is an efficient way to write a source generator.
} | ||
// If the discovered `Program` type is an interface, struct or generic type then its not | ||
// generated and has been defined in source, so we can skip it | ||
if (program.TypeKind == TypeKind.Struct || program.TypeKind == TypeKind.Interface || program.IsGenericType) |
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.
Out of curiosity, why notprogram.TypeKind != TypeKind.Class
?
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.
That's....a good idea 😅 I'll update as needed.
src/Framework/AspNetCoreAnalyzers/src/SourceGenerators/PublicTopLevelProgramGenerator.cs OutdatedShow resolvedHide resolved
Uh oh!
There was an error while loading.Please reload this page.
Tornhoof commentedOct 3, 2024 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Yeah, something like the implicit usings setting or something similar, could then even be enabled automatically during project creation, but I understand that's a lot more effort than a src gen. Using MSBuild properties to decide if a srcgen should be executed is also not as straight forward as one might think. I admit, that I don't really see any quick win here. |
1e09f50
to3bc89cd
CompareThere 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.
The selection logic looks correct to me.
...rk/AspNetCoreAnalyzers/src/SourceGenerators/Microsoft.AspNetCore.App.SourceGenerators.csprojShow resolvedHide resolved
Uh oh!
There was an error while loading.Please reload this page.
{ | ||
var internalGeneratedProgramClass = context.CompilationProvider | ||
// Get the entry point associated with the compilation, this maps to the Main method definition | ||
.Select((compilation, cancellationToken) => compilation.GetEntryPoint(cancellationToken)) |
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.
Iterated selection is very tidy, but it seems like it might be expensive. If this is run repeatedly, I wonder if it would be more efficient to have a single Select with a bigger lambda.
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.
According tothe guidance on writing incremental generators it's better to split things out to multipleSelects
so that there's an opportunity to cache more frequently in between steps.
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.
Surely that's only true for things that change independently? Retrieving and casting the containing type in the first select doesn't seem like something that could affect caching.
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.
Yes, I suppose the first two selects could be collapsed into one given the things they evaluate are closely related but I don't think there's anythingactively harmful about splitting up the transformation.
src/Framework/AspNetCoreAnalyzers/src/SourceGenerators/PublicTopLevelProgramGenerator.cs OutdatedShow resolvedHide resolved
Uh oh!
There was an error while loading.Please reload this page.
Merging this now to validate the E2E both perf and functionality wise once it is inserted into the alpha SDK. I'll follow up with building the analyzer after that. |
15f0a89
intomainUh oh!
There was an error while loading.Please reload this page.
* Add source generator to emit public Program class definition* Add more checks and test cases* Use GetEntrypoint API and transformations for better caching* Address feedback
* Add source generator to emit public Program class definition* Add more checks and test cases* Use GetEntrypoint API and transformations for better caching* Address feedback
With the introduction of top-level statements in .NET 6, there is not longer an explicit
Program
class defined in user's ASP.NET Core applications. Instead, we rely on theProgram
class generated by the compiler, which hasa default visibility ofinternal
.This introduces an annoying hurdle for users who want to write integration tests on top of ourWebApplicationFactory which requires a public entrypoint as a generic type argument.
To work around this, we document that users can either:
IVT
from their application code to their test codepublic partial class Program {}
declarationThe first approach runs the risk of the user having to expose truly internal types to their test code. The second approach is hard to discover.
To resolve this issue, we introduce a new source generator to the shared framework that will emit the
public partial class Program {}
declaration to applications that:Program
class as public in some other fashionProgram.Main
declarations