Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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

Merged
captainsafia merged 4 commits intomainfromsafia/public-program-gen
Oct 9, 2024

Conversation

captainsafia
Copy link
Member

With the introduction of top-level statements in .NET 6, there is not longer an explicitProgram 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:

  • Add anIVT from their application code to their test code
  • Add apublic partial class Program {} declaration

The 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 thepublic partial class Program {} declaration to applications that:

  • Use top-level statements
  • Don't declare theProgram class as public in some other fashion
  • Don't use the old-styleProgram.Main declarations

martincostello and etkr reacted with thumbs up emoji
@captainsafiacaptainsafia requested review fromwtgodbe anda team ascode ownersOctober 2, 2024 04:20
@ghostghost added the area-infrastructureIncludes: MSBuild projects/targets, build scripts, CI, Installers and shared framework labelOct 2, 2024
@captainsafiacaptainsafia added the area-minimalIncludes minimal APIs, endpoint filters, parameter binding, request delegate generator etc labelOct 2, 2024
@@ -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"
Copy link
MemberAuthor

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");
Copy link
MemberAuthor

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

@martincostello
Copy link
Member

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 😅

@gfoidl
Copy link
Member

A different idea / approach:

Update the compiler so that it can emit apublic class Program that is configurable via MsBuild-property.
So the default remains the same, when the property is set thepublic class is emitted.
Thus the change here boils down to updating the template csproj.

Or is the process via compiler, sdk, etc. too tedious that this approach is cheaper?

@captainsafia
Copy link
MemberAuthor

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?

Oh, good idea! We can definitely author an analyzer + codefix that discovers thepublic partial class Program { } and recommends removing them if you're targeting net10 and above.

Update the compiler so that it can emit a public class Program that is configurable via MsBuild-property.

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:

  • At the moment, this ASP.NET Core integration testing scenarioseems to be the only one.
  • More importantly, there were questions about what the visibility/accessibility of types defined in the generatedProgram class should be.

With those in mind, it was a bit more straightforward for ASP.NET to control its own destiny here and author a source generator.

martincostello and gfoidl reacted with thumbs up emoji

@amcasey
Copy link
Member

(Haven't read the code yet)

We also don't want to do this if they'veexplicitly declared it internal, right?

internalpartialclassProgram{}

@amcasey
Copy link
Member

amcasey commentedOct 2, 2024
edited
Loading

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
Copy link
MemberAuthor

captainsafia commentedOct 3, 2024
edited
Loading

We also don't want to do this if they've explicitly declared it internal, right?

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 theProgram class is alreadyinternal.I suppose it's also possible for somebody to declareprivate partial class Program { }. It's not you can't declareprivate orprotected access in top-level classes.

Edit:this might be a useful reference.

Good reference! I'll incorporate some of the checks here into our code.

@Tornhoof
Copy link
Contributor

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.

@captainsafia
Copy link
MemberAuthor

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 theProgram class that is already defined looks like. We'd end up generating thepublic partial class Program { } declaration in more situations that is acceptable which might result in surprising behavior.

Also,

some switch somewhere during

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.

Copy link
Member

@amcaseyamcasey left a 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)
Copy link
Member

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?

Copy link
MemberAuthor

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.

@Tornhoof
Copy link
Contributor

Tornhoof commentedOct 3, 2024
edited
Loading

I'm assuming you're referring to a switch that the user would set.

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.

Copy link
Member

@amcaseyamcasey left a 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.

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

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.

Copy link
MemberAuthor

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.

Copy link
Member

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.

Copy link
MemberAuthor

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.

@captainsafia
Copy link
MemberAuthor

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.

martincostello reacted with thumbs up emoji

@captainsafiacaptainsafia merged commit15f0a89 intomainOct 9, 2024
27 checks passed
@captainsafiacaptainsafia deleted the safia/public-program-gen branchOctober 9, 2024 00:48
@dotnet-policy-servicedotnet-policy-servicebot added this to the10.0-preview1 milestoneOct 9, 2024
captainsafia added a commit that referenced this pull requestDec 31, 2024
* 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
captainsafia added a commit that referenced this pull requestFeb 11, 2025
* 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
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers

@amcaseyamcaseyamcasey approved these changes

@wtgodbewtgodbeAwaiting requested review from wtgodbewtgodbe is a code owner

Assignees
No one assigned
Labels
area-infrastructureIncludes: MSBuild projects/targets, build scripts, CI, Installers and shared frameworkarea-minimalIncludes minimal APIs, endpoint filters, parameter binding, request delegate generator etc
Projects
None yet
Milestone
10.0-preview1
Development

Successfully merging this pull request may close these issues.

5 participants
@captainsafia@martincostello@gfoidl@amcasey@Tornhoof

[8]ページ先頭

©2009-2025 Movatter.jp