Skip to content

Fix generators not being added or removed correctly#65038

Merged
arunchndr merged 2 commits intodotnet:release/dev17.4from
jasonmalinowski:fix-generators-not-being-added-or-removed-correctly
Nov 9, 2022
Merged

Fix generators not being added or removed correctly#65038
arunchndr merged 2 commits intodotnet:release/dev17.4from
jasonmalinowski:fix-generators-not-being-added-or-removed-correctly

Conversation

@jasonmalinowski
Copy link
Member

When the list of analyzer references change for a project, we produce a new list of AnalyzerFileReferences and pass that into Project.WithAnalyzerReferences. The implementation of that method figures out which references are added or removed, and also uses those changes to update the list of generators being held by the generator.

This seems innocent enough, except that the AnalyzerFileReferences here implement value equality: two references are equal if they have the same file path. These references however will not return the same generator instances, because each reference does it's own loading and caching of that list. Thus, when we computed the list of analyzers that are added or removed, we won't see analyzer references that are equal, and won't update the generator driver. Later code that expects those to be in sync will then start throwing exceptions.

Fixes https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1655835

While then writing tests for that, I found a second issue:

If the last generator was removed, and it was generating trees, we would have potentially left that tree in the Compilation that was returned. I believe this would have been a transient issue -- any later change to the project would have created a new CompilationTracker with an InProgress state; the code that processes an InProgress state into the final state would have correctly seen we no longer had a generator and would have dropped the old compilation at that point.

When the list of analyzer references change for a project, we produce
a new list of AnalyzerFileReferences and pass that into
Project.WithAnalyzerReferences. The implementation of that method
figures out which references are added or removed, and also uses
those changes to update the list of generators being held by the
generator.

This seems innocent enough, except that the AnalyzerFileReferences
here implement value equality: two references are equal if they have
the same file path. These references however will not return the same
generator instances, because each reference does it's own loading
and caching of that list. Thus, when we computed the list of analyzers
that are added or removed, we won't see analyzer references that are
equal, and won't update the generator driver. Later code that expects
those to be in sync will then start throwing exceptions.

Fixes https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1655835
@ghost ghost added the Area-IDE label Oct 27, 2022
@jasonmalinowski jasonmalinowski marked this pull request as ready for review October 27, 2022 04:21
@jasonmalinowski jasonmalinowski requested a review from a team as a code owner October 27, 2022 04:21
@jasonmalinowski jasonmalinowski changed the base branch from main to release/dev17.4 October 27, 2022 04:21
Comment on lines +1039 to +1040
// An alternative approach would be to call oldProject.WithAnalyzerReferences keeping all the references in there that are value equal the same,
// but this avoids any surprises where other components calling WithAnalyzerReferences might not expect that.
Copy link
Member Author

Choose a reason for hiding this comment

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

Now that I'm aware of this gotcha, I'm questioning the generally goodness of having AnalyzerFileReferences be equal comparable in the first place, but since that'd be an API breaking change I'm doing this for 17.4 and 17.5 Preview 1; we can take as a follow up item questioning the wisdom of all of this.

Copy link
Contributor

Choose a reason for hiding this comment

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

oh yes. we're on the same page. i thin kwe should break this.


private class TestGeneratorReferenceWithFilePathEquality : TestGeneratorReference, IEquatable<AnalyzerReference>
{
public TestGeneratorReferenceWithFilePathEquality(ISourceGenerator generator, string analyzerFilePath) : base(generator)
Copy link
Member Author

Choose a reason for hiding this comment

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

In 17.5 TestGeneratorReference also has a FullPath property, which this isn't passing to; I'll mop that up in main -- right now this PR cleanly applies to both branches this way.

//
// When we're comparing AnalyzerReferences, we'll compare with reference equality; AnalyzerReferences like AnalyzerFileReference
// may implement their own equality, but that can result in things getting out of sync: two references that are value equal can still
// have their own generator instances; it's important that as we're adding and removing references that are value equal that we
Copy link
Contributor

Choose a reason for hiding this comment

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

ick ick ick. perhaps they should not implement equality?

Copy link
Contributor

@CyrusNajmabadi CyrusNajmabadi left a comment

Choose a reason for hiding this comment

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

lgtm. but please invert the if.

If the last generator was removed, and it was generating trees, we would
have potentially left that tree in the Compilation that was returned.
I believe this would have been a transient issue -- any later change
to the project would have created a new CompilationTracker with an
InProgress state; the code that processes an InProgress state into
the final state would have correctly seen we no longer had a generator
and would have dropped the old compilation at that point.
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.

4 participants