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

Do not report conflicting types errors for source-generated APIs #71546

Closed
wants to merge 3 commits into from

Conversation

333fred
Copy link
Member

@333fred 333fred commented Jan 9, 2024

It is a common scenario for source generators to make internal attributes in an assembly via our PostInit callbacks. This is mostly fine, but can result in annoying CS0436 "this type conflicts" warnings. To better support this scenario, we suppress this warning for this specific case. In order to plumb this knowledge through, I did have to update a bit larger tail than I would have otherwise preferred, but we now have the building block for implementing #56810 as well.

@dotnet-issue-labeler dotnet-issue-labeler bot added Area-Compilers untriaged Issues and PRs which have not yet been triaged by a lead labels Jan 9, 2024
@333fred
Copy link
Member Author

333fred commented Jan 9, 2024

@jaredpar @chsienki for your thoughts before I go and publish this. Do we want to proceed with suppressing this?

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. As an alternative to having two sets of trees, did you consider having a flag on the syntax tree itself instead?

@333fred
Copy link
Member Author

333fred commented Jan 9, 2024

did you consider having a flag on the syntax tree itself instead

Yes, and threw out that idea immediately as I don't want Dave or Cyrus to come murder me in my sleep.

@sharwell
Copy link
Member

sharwell commented Jan 9, 2024

I am not a fan of this change. I would very much prefer for source generators to have an API with access to metadata references prior to the creation of a Compilation.

@333fred
Copy link
Member Author

333fred commented Jan 9, 2024

Could you please elaborate @sharwell?

@sharwell
Copy link
Member

sharwell commented Jan 9, 2024

I went into it a bit here:
#71059 (comment)

The summary is CS0436 is only getting reported because of a limitation in the RegisterPostInitializationOutput API, and we should address that limitation directly. A correctly written source generator will not produce the duplicate type which results in CS0436, and therefore there is no need to make any special exception for this diagnostic.

@chsienki
Copy link
Contributor

chsienki commented Jan 9, 2024

did you consider having a flag on the syntax tree itself instead

Yes, and threw out that idea immediately as I don't want Dave or Cyrus to come murder me in my sleep.

Sarcasm aside, why is that a problem? Just because it would increase the memory usage?

@chsienki
Copy link
Contributor

chsienki commented Jan 9, 2024

I went into it a bit here: #71059 (comment)

The summary is CS0436 is only getting reported because of a limitation in the RegisterPostInitializationOutput API, and we should address that limitation directly. A correctly written source generator will not produce the duplicate type which results in CS0436, and therefore there is no need to make any special exception for this diagnostic.

I think there is definitely scope to have a post-init context that would allow some form of metadata lookup. Things like parse options would be useful too: (see #71299). We do already re-parse post-init sources in certain circumstances, so it's unlikely to be a huge perf hit.

I also wonder if we want to consider a different direction though and make attribute addition more declarative: rather than force users to lookup an attribute and add it if it's missing, we could design an API that effectively says 'add this attribute if it's not there'. Some of the early designs for FAWMN had the same idea, which we could always resurrect too, where you not only say 'find this attribute' but 'add and find this attribute'

@sharwell
Copy link
Member

sharwell commented Jan 9, 2024

I also wonder if we want to consider a different direction though and make attribute addition more declarative: rather than force users to lookup an attribute and add it if it's missing, we could design an API that effectively says 'add this attribute if it's not there'. Some of the early designs for FAWMN had the same idea, which we could always resurrect too, where you not only say 'find this attribute' but 'add and find this attribute'

If we did go that direction, it seems like we'd want to do it as a helper API on top of a lower-level API that works for both that and other scenarios. Attributes aren't the only types users may want to add. Another example would be adding IsExternalInit.

@333fred
Copy link
Member Author

333fred commented Jan 9, 2024

Just because it would increase the memory usage?

Yes. If we couldn't fit a bit on SyntaxTree to indicate if it contains any attributes, we can't fit a bit on it to indicate if it's source generated.

// IVT assemblies that may also run the same source generator. We don't want to warn in these cases, so we suppress the warning
// if the source symbol is from a source-generated file. We only do this for types with a single location, since that is the very
// specific scenario we are targeting.
if (srcSymbol.Locations is not [{ SourceTree: { } srcTree }] || !Compilation.IsSourceGeneratedTree(srcTree))
Copy link
Member

Choose a reason for hiding this comment

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

It feels wrong to me to tie this to generated code because it doesn't satisfy all the scenarios. Consider:

  1. A key scenario for source generators is that you can walk away from them. Essentially at any point you can set <EmitGeneratedCompilerFiles>true, check in the files, remove the generators and move on with life. Tieing the diagnostic to this heuristic means that scenario breaks
  2. This problem comes up frequently in non-generated code. Consider that there are many NuPkg out there which distribute source not DLLs. They have this exact problem and it's not generated code, it's plain old code.

My instinct is to lean towards a solution where a type is marked as

I know there may be multiple definitions of me, so what, pick the source.

@jaredpar
Copy link
Member

. I would very much prefer for source generators to have an API with access to metadata references prior to the creation of a Compilation.

That does not help. Consider that there are essentially the following scenarios when a generator is trying to use an attribute (let's call it GenerateAttribute):

a. There are zero GeneralAttribute definitions in metadata references
b. There is one GeneralAttribute definition in metadata references
c. There is more than one GeneralAttribute definition in metadata references

Knowing the set of metadata references ahead of time only helps with scenarios (a) and (b). For (c) there is no solution other than issuing a diagnostic to tell the user to pick one and attach and extern alias. That's not really a solution IMHO.

@sharwell
Copy link
Member

@jaredpar What would scenario C look like after this change?

@sharwell
Copy link
Member

It sounds like Jared is suggesting that the way to resolve 2 or more types in metadata references would be to define a third type in the current assembly, at which point the compilation prefers that type over all of the types of the same name coming from metadata references. If that is truly the goal, it seems like it could be better resolved by the following:

  1. In the event a type is defined in the current assembly and also defined in a metadata reference, binding always prefers the type in the current assembly.
  2. In the event a type is defined in the current assembly and also defined in a metadata reference, a warning is issued for the type in the current assembly saying that the type is hiding one or more types defined in metadata references.

Source generators that intentionally inject code that hides other code from metadata references can suppress the warning for (2) at the location of that source generated declaration. Users would not be required to have a source generator in order to use this feature.

@jaredpar
Copy link
Member

What would scenario C look like after this change?

This change is being done rationale of providing a simple story to source generator authors. Rather than dig through metadata just emit the attribute every time. The problem with that approach is you end up with the ambiguity warnings. From that perspective B and C are the same scenario cause the compiler will prefer the source definition over the metadata one. All this change does is remove the warning in that case.

It sounds like Jared is suggesting that the way to resolve 2 or more types in metadata references would be to define a third type in the current assembly, at which point the compilation prefers that type over all of the types of the same name coming from metadata references.

No. There is a discussion on going about best practices for generators. The recommended best practice of always generate your attribute falls into the warning trap I've been describing. This PR is an attempt to get sane behavior from the compiler in the face of our desired best practice for source generators.

In the event a type is defined in the current assembly and also defined in a metadata reference, binding always prefers the type in the current assembly.

That is the current behavior, the compiler just emits a warning which is undesirable for the generator case.

In the event a type is defined in the current assembly and also defined in a metadata reference, a warning is issued for the type in the current assembly saying that the type is hiding one or more types defined in metadata references.

The compiler does issue a warning. The problem is the warning is on the consumption of the type, not the definition. That means source generator consumers have to know about this mess which is a bad user experience. We're exploring ways to either remove the warning or put it in a place where generator authors can suppress it so customers don't know about it.

@sharwell
Copy link
Member

In the event a type is defined in the current assembly and also defined in a metadata reference, binding always prefers the type in the current assembly.

That is the current behavior, the compiler just emits a warning which is undesirable for the generator case.

...

We're exploring ways to either remove the warning or put it in a place where generator authors can suppress it so customers don't know about it.

Yes, this is what I was suggesting as a preferred behavior (relocating the warning to declaration).

@333fred
Copy link
Member Author

333fred commented Dec 19, 2024

Closing in favor of #76523.

@333fred 333fred closed this Dec 19, 2024
@333fred 333fred deleted the source-gen-warning branch December 19, 2024 22:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Compilers untriaged Issues and PRs which have not yet been triaged by a lead VSCode
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants