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

Ensure an empty run result doesn't throw when generators are present. #74034

Merged
merged 4 commits into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,28 @@ public void RunResults_Are_Empty_Before_Generation()
Assert.Empty(results.Results);
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/74033")]
public void RunResults_Are_Empty_Before_Generation_With_Generators()
{
var generator = new SingleFileTestGenerator("public class D {}", "source.cs");

GeneratorDriver driver = CSharpGeneratorDriver.Create([generator], parseOptions: TestOptions.Regular);
var results = driver.GetRunResult();

Assert.Empty(results.GeneratedTrees);
Assert.Empty(results.Diagnostics);

var result = Assert.Single(results.Results);

Assert.Null(result.Exception);
Assert.Empty(result.Diagnostics);
Assert.Empty(result.GeneratedSources);
Assert.Empty(result.TrackedSteps);
Assert.Empty(result.TrackedOutputSteps);
Assert.Equal(TimeSpan.Zero, result.ElapsedTime);
Assert.Equal(generator, result.Generator);
}

[Fact]
public void RunResults_Are_Available_After_Generation()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ internal GeneratorDriver(GeneratorDriverState state)
internal GeneratorDriver(ParseOptions parseOptions, ImmutableArray<ISourceGenerator> generators, AnalyzerConfigOptionsProvider optionsProvider, ImmutableArray<AdditionalText> additionalTexts, GeneratorDriverOptions driverOptions)
{
var incrementalGenerators = GetIncrementalGenerators(generators, SourceExtension);
_state = new GeneratorDriverState(parseOptions, optionsProvider, generators, incrementalGenerators, additionalTexts, ImmutableArray.Create(new GeneratorState[generators.Length]), DriverStateTable.Empty, SyntaxStore.Empty, driverOptions, runtime: TimeSpan.Zero, parseOptionsChanged: true);
_state = new GeneratorDriverState(parseOptions, optionsProvider, generators, incrementalGenerators, additionalTexts, generators.SelectAsArray(g => GeneratorState.Empty), DriverStateTable.Empty, SyntaxStore.Empty, driverOptions, runtime: TimeSpan.Zero, parseOptionsChanged: true);
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you please explain how this change prevents an NRE which was occurring in the original code?

Copy link
Member

Choose a reason for hiding this comment

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

I'm guessing it's in getGeneratorSources access to generatorState.PostInitTrees.Length. The value for PostInitTrees is a default immutable array for default(GeneratorState), as opposed to an empty immutable array for GeneratorState.Empty.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When calling .GetRunResult() we call into a local function getGeneratorSources that creates an array to handle the generated sources of the given generator

ArrayBuilder<GeneratedSourceResult> sources = ArrayBuilder<GeneratedSourceResult>.GetInstance(generatorState.PostInitTrees.Length + generatorState.GeneratedTrees.Length);

That calls into the collections of the individual states to get their sizes. Prior to the change, we created default GeneratorStates, so the PostInitTrees and GeneratedTrees were uninitialized immutable arrays which throw when attempting to get their length.

Instead we now store GeneratorState.Empty instances, which in turn have empty result arrays rather than uninitialized ones.

We had a test that checked you could call GetRunResult before calling RunGenerators but that test didn't add any generators, so the generatorStates array was empty. Adding a generator ensures that array has content, and we see the issue in the added test.

}

public GeneratorDriver RunGenerators(Compilation compilation, CancellationToken cancellationToken = default)
Expand Down
Loading