Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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 @@ -4199,6 +4199,186 @@ public static void Interceptor(this Program program, int param)
}
}

[Fact]
public void SourceGenerator_As_IncrementalGenerator_As_SourceGenerator()
{
ISourceGenerator generator = new TestSourceGenerator();

var incrementalGenerator = generator.AsIncrementalGenerator();
var sourceGenerator = incrementalGenerator.AsSourceGenerator();

Assert.Same(generator, sourceGenerator);
}

[Fact]
public void SourceGenerator_As_IncrementalGenerator_GetGeneratorType()
{
ISourceGenerator generator = new TestSourceGenerator();

var incrementalGenerator = generator.AsIncrementalGenerator();
var type = incrementalGenerator.GetGeneratorType();

Assert.Same(generator.GetType(), type);
}

[Fact]
public void IncrementalGenerator_As_SourceGenerator_As_IncrementalGenerator()
{
IIncrementalGenerator generator = new PipelineCallbackGenerator(ctx => { });

var sourceGenerator = generator.AsSourceGenerator();
var incrementalGenerator = sourceGenerator.AsIncrementalGenerator();

Assert.Same(generator, incrementalGenerator);
}

[Fact]
public void IncrementalGenerator_As_SourceGenerator_GetGeneratorType()
{
IIncrementalGenerator generator = new PipelineCallbackGenerator(ctx => { });

var sourceGenerator = generator.AsSourceGenerator();
var type = sourceGenerator.GetGeneratorType();

Assert.Same(generator.GetType(), type);
}

[Fact]
public void GeneratorDriver_CreateWith_Wrapped_ISourceGenerator()
{
bool executeCalled = false;
ISourceGenerator generator = new TestSourceGenerator() { ExecuteImpl = (context) => { executeCalled = true; } };

var incrementalGenerator = generator.AsIncrementalGenerator();

var generatorDriver = CSharpGeneratorDriver.Create(incrementalGenerator);
generatorDriver.RunGenerators(CreateCompilation("class C { }"));

Assert.True(executeCalled);
}

[Fact]
public void GeneratorDriver_CanFilter_GeneratorsToRun()
{
var generator1 = new PipelineCallbackGenerator((ctx) => { ctx.RegisterSourceOutput(ctx.CompilationProvider, (spc, c) => { spc.AddSource("gen1Source.cs", c.SyntaxTrees.First().GetRoot().ToFullString() + " //generator1"); }); }).AsSourceGenerator();
var generator2 = new PipelineCallbackGenerator2((ctx) => { ctx.RegisterSourceOutput(ctx.CompilationProvider, (spc, c) => { spc.AddSource("gen2Source.cs", c.SyntaxTrees.First().GetRoot().ToFullString() + " //generator2"); }); }).AsSourceGenerator();

var compilation = CreateCompilation("class Compilation1{}");
GeneratorDriver driver = CSharpGeneratorDriver.Create(generator1, generator2);

driver = driver.RunGenerators(compilation);
var result = driver.GetRunResult();

Assert.Equal(2, result.GeneratedTrees.Length);
Assert.Equal("class Compilation1{} //generator1", result.GeneratedTrees[0].ToString());
Assert.Equal("class Compilation1{} //generator2", result.GeneratedTrees[1].ToString());

// change the generated source, but only run generator 1
compilation = CreateCompilation("class Compilation2{}");
driver = driver.RunGenerators(compilation, ctx => ctx.Generator == generator1);
result = driver.GetRunResult();

// only the first generator should have run, generator 2 hasn't been updated
Assert.Equal(2, result.GeneratedTrees.Length);
Assert.Equal("class Compilation2{} //generator1", result.GeneratedTrees[0].ToString());
Assert.Equal("class Compilation1{} //generator2", result.GeneratedTrees[1].ToString());

// now only run generator 2
compilation = CreateCompilation("class Compilation3{}");
driver = driver.RunGenerators(compilation, ctx => ctx.Generator == generator2);
result = driver.GetRunResult();

Assert.Equal(2, result.GeneratedTrees.Length);
Assert.Equal("class Compilation2{} //generator1", result.GeneratedTrees[0].ToString());
Assert.Equal("class Compilation3{} //generator2", result.GeneratedTrees[1].ToString());

// run everything
compilation = CreateCompilation("class Compilation4{}");
driver = driver.RunGenerators(compilation);
result = driver.GetRunResult();

Assert.Equal(2, result.GeneratedTrees.Length);
Assert.Equal("class Compilation4{} //generator1", result.GeneratedTrees[0].ToString());
Assert.Equal("class Compilation4{} //generator2", result.GeneratedTrees[1].ToString());
}

[Fact]
public void GeneratorDriver_CanFilter_GeneratorsToRun_AndUpdateCompilation()
{
var generator1 = new PipelineCallbackGenerator((ctx) => { ctx.RegisterSourceOutput(ctx.CompilationProvider, (spc, c) => { spc.AddSource("gen1Source.cs", "//" + c.SyntaxTrees.First().GetRoot().ToFullString() + " generator1"); }); }).AsSourceGenerator();
var generator2 = new PipelineCallbackGenerator2((ctx) => { ctx.RegisterSourceOutput(ctx.CompilationProvider, (spc, c) => { spc.AddSource("gen2Source.cs", "//" + c.SyntaxTrees.First().GetRoot().ToFullString() + " generator2"); }); }).AsSourceGenerator();

var parseOptions = CSharpParseOptions.Default;
var compilation = CreateCompilation("class Compilation1{}", parseOptions: parseOptions);
GeneratorDriver driver = CSharpGeneratorDriver.Create([generator1, generator2], parseOptions: parseOptions);

driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var outputDiagnostics);
Assert.Empty(outputDiagnostics);

// change the generated source, but only run generator 1
compilation = CreateCompilation("class Compilation2{}", parseOptions: parseOptions);
driver = driver.RunGenerators(compilation, ctx => ctx.Generator == generator1);

// now run all the generators and update the compilation
driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out outputCompilation, out outputDiagnostics);
Assert.Empty(outputDiagnostics);
}

[Fact]
public void GeneratorDriver_ReturnsEmptyRunResult_IfFiltered_BeforeRunning()
{
bool initWasCalled = false;

var generator1 = new PipelineCallbackGenerator((ctx) => { ctx.RegisterSourceOutput(ctx.CompilationProvider, (spc, c) => { spc.AddSource("gen1Source.cs", c.SyntaxTrees.First().GetRoot().ToFullString() + " //generator1"); }); }).AsSourceGenerator();
var generator2 = new PipelineCallbackGenerator2((ctx) => { initWasCalled = true; ctx.RegisterSourceOutput(ctx.CompilationProvider, (spc, c) => { spc.AddSource("gen2Source.cs", c.SyntaxTrees.First().GetRoot().ToFullString() + " //generator2"); }); }).AsSourceGenerator();

var compilation = CreateCompilation("class Compilation1{}");
GeneratorDriver driver = CSharpGeneratorDriver.Create(generator1, generator2);

driver = driver.RunGenerators(compilation, ctx => ctx.Generator == generator1);
var result = driver.GetRunResult();

// only a single tree is generated
Assert.Equal(1, result.GeneratedTrees.Length);
Assert.Equal("class Compilation1{} //generator1", result.GeneratedTrees[0].ToString());

// but both generators are represented in the run results
Assert.Equal(2, result.Results.Length);

Assert.Equal(generator1, result.Results[0].Generator);
Assert.Equal(1, result.Results[0].GeneratedSources.Length);

// the second generator was never initialized and produced no results
Assert.False(initWasCalled);
Assert.Equal(generator2, result.Results[1].Generator);
Assert.True(result.Results[1].GeneratedSources.IsDefault);
}

[Fact]
public void GeneratorDriver_GeneratorIsNotRun_IfAlreadyUpToDate_DueToFiltering()
{
bool stepRan = false;
var generator1 = new PipelineCallbackGenerator((ctx) => { ctx.RegisterSourceOutput(ctx.CompilationProvider, (spc, c) => { stepRan = true; }); }).AsSourceGenerator();

var compilation = CreateCompilation("class Compilation1{}");
GeneratorDriver driver = CSharpGeneratorDriver.Create(generator1);

// don't run
driver = driver.RunGenerators(compilation, ctx => false);
Assert.False(stepRan);

// run
driver = driver.RunGenerators(compilation, ctx => true);
Assert.True(stepRan);

// re-run everything
stepRan = false;
driver = driver.RunGenerators(compilation);

// step didn't run because the generator was already up to date
Assert.False(stepRan);
}

[Fact]
public void ReplaceGenerators_Initializes_New_Generators()
{
Expand Down Expand Up @@ -4228,5 +4408,62 @@ public void ReplaceGenerators_Initializes_New_Generators()
Assert.True(initWasCalled);
Assert.Single(results.GeneratedTrees);
}

// check post init trees get re-parsed if we change parse options while suppressed

[Fact]
public void GeneratorDriver_DoesNotIncludePostInitTrees_WhenFiltered()
{
var generator = new PipelineCallbackGenerator((ctx) => { ctx.RegisterPostInitializationOutput(postInitCtx => { postInitCtx.AddSource("staticSource.cs", "//static"); }); }).AsSourceGenerator();

var parseOptions = CSharpParseOptions.Default;
var compilation = CreateCompilation("class Compilation1{}", parseOptions: parseOptions);
GeneratorDriver driver = CSharpGeneratorDriver.Create([generator], parseOptions: parseOptions);

driver = driver.RunGenerators(compilation, (ctx) => false);
var result = driver.GetRunResult();

Assert.Empty(result.GeneratedTrees);

driver = driver.RunGenerators(compilation);
result = driver.GetRunResult();

Assert.Single(result.GeneratedTrees);
}

[Fact]
public void GeneratorDriver_ReparsesPostInitTrees_IfParseOptionsChange_WhileSuppressed()
{
var generator1 = new PipelineCallbackGenerator((ctx) => { ctx.RegisterPostInitializationOutput(postInitCtx => { postInitCtx.AddSource("staticSource.cs", "//static"); }); }).AsSourceGenerator();
var generator2 = new PipelineCallbackGenerator2((ctx) => { ctx.RegisterPostInitializationOutput(postInitCtx => { postInitCtx.AddSource("staticSource2.cs", "//static 2"); }); }).AsSourceGenerator();

var parseOptions = CSharpParseOptions.Default;
var compilation = CreateCompilation("class Compilation1{}", parseOptions: parseOptions);
GeneratorDriver driver = CSharpGeneratorDriver.Create([generator1, generator2], parseOptions: parseOptions);

driver = driver.RunGenerators(compilation);
var result = driver.GetRunResult();
Assert.Equal(2, result.GeneratedTrees.Length);

// change the parse options, but only run one of the generators
var newParseOptions = parseOptions.WithLanguageVersion(LanguageVersion.CSharp9);
compilation = CreateCompilation("class Compilation2{}", parseOptions: newParseOptions);
driver = driver.WithUpdatedParseOptions(newParseOptions);
driver = driver.RunGenerators(compilation, (ctx) => ctx.Generator == generator1);
result = driver.GetRunResult();

// the post init tree from generator2 still has the old parse options, as it didn't run
Assert.Equal(2, result.GeneratedTrees.Length);
Assert.Same(newParseOptions, result.GeneratedTrees[0].Options);
Assert.Same(parseOptions, result.GeneratedTrees[1].Options);

// now run everything and ensure it brings the init trees up to date
driver = driver.RunGenerators(compilation);
result = driver.GetRunResult();

Assert.Equal(2, result.GeneratedTrees.Length);
Assert.Same(newParseOptions, result.GeneratedTrees[0].Options);
Assert.Same(newParseOptions, result.GeneratedTrees[1].Options);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1353,8 +1353,7 @@ private DriverStateTable.Builder GetBuilder(DriverStateTable previous, bool trac
previous,
SyntaxStore.Empty,
driverOptions: new GeneratorDriverOptions(IncrementalGeneratorOutputKind.None, trackIncrementalGeneratorSteps),
runtime: TimeSpan.Zero,
parseOptionsChanged: false);
runtime: TimeSpan.Zero);

return new DriverStateTable.Builder(c, state, SyntaxStore.Empty.ToBuilder(c, ImmutableArray<SyntaxInputNode>.Empty, trackIncrementalGeneratorSteps, cancellationToken: default));
}
Expand Down
10 changes: 10 additions & 0 deletions src/Compilers/Core/Portable/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
Microsoft.CodeAnalysis.GeneratorDriver.RunGenerators(Microsoft.CodeAnalysis.Compilation! compilation) -> Microsoft.CodeAnalysis.GeneratorDriver!
Microsoft.CodeAnalysis.GeneratorDriver.RunGenerators(Microsoft.CodeAnalysis.Compilation! compilation, System.Func<Microsoft.CodeAnalysis.GeneratorFilterContext, bool>? generatorFilter, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.GeneratorDriver!
Microsoft.CodeAnalysis.GeneratorDriver.RunGenerators(Microsoft.CodeAnalysis.Compilation! compilation, System.Threading.CancellationToken cancellationToken) -> Microsoft.CodeAnalysis.GeneratorDriver!
*REMOVED*Microsoft.CodeAnalysis.GeneratorDriver.RunGenerators(Microsoft.CodeAnalysis.Compilation! compilation, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.GeneratorDriver!
Microsoft.CodeAnalysis.GeneratorFilterContext
Microsoft.CodeAnalysis.GeneratorFilterContext.CancellationToken.get -> System.Threading.CancellationToken
Microsoft.CodeAnalysis.GeneratorFilterContext.Generator.get -> Microsoft.CodeAnalysis.ISourceGenerator!
Microsoft.CodeAnalysis.GeneratorFilterContext.GeneratorFilterContext() -> void
Microsoft.CodeAnalysis.IPropertySymbol.PartialDefinitionPart.get -> Microsoft.CodeAnalysis.IPropertySymbol?
Microsoft.CodeAnalysis.IPropertySymbol.PartialImplementationPart.get -> Microsoft.CodeAnalysis.IPropertySymbol?
Microsoft.CodeAnalysis.IPropertySymbol.IsPartialDefinition.get -> bool
Microsoft.CodeAnalysis.ITypeParameterSymbol.AllowsRefLikeType.get -> bool
Microsoft.CodeAnalysis.RuntimeCapability.ByRefLikeGenerics = 8 -> Microsoft.CodeAnalysis.RuntimeCapability
static Microsoft.CodeAnalysis.GeneratorExtensions.AsIncrementalGenerator(this Microsoft.CodeAnalysis.ISourceGenerator! sourceGenerator) -> Microsoft.CodeAnalysis.IIncrementalGenerator!
static Microsoft.CodeAnalysis.GeneratorExtensions.GetGeneratorType(this Microsoft.CodeAnalysis.IIncrementalGenerator! generator) -> System.Type!
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ namespace Microsoft.CodeAnalysis
/// </summary>
internal sealed class SourceGeneratorAdaptor : IIncrementalGenerator
{
/// <summary>
/// A dummy extension that is used to indicate this adaptor was created outside of the driver.
/// </summary>
public const string DummySourceExtension = ".dummy";

private readonly string _sourceExtension;
Copy link
Member

Choose a reason for hiding this comment

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

Is this a value that is controllable by customers? If so have reservations about using a magic string. Had trouble tracking this through all the APIs to see where it originates.

Copy link
Member Author

Choose a reason for hiding this comment

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

No, it's only created internally. We track the file extension because we actively parse the tree, so we need to pass it in from the outer driver when we wrap an ISourceGenerator.

The generators on this path are never actually used directly, so we can just set it to anything. I chose a magic string so we can assert later on that it isn't used and dings us if we ever do allow it, so we know to fix it.

At some point we should probably refactor these so the string isn't needed at all, but this just seemed simpler for now.


internal ISourceGenerator SourceGenerator { get; }
Expand All @@ -27,6 +32,11 @@ public SourceGeneratorAdaptor(ISourceGenerator generator, string sourceExtension

public void Initialize(IncrementalGeneratorInitializationContext context)
{
// We don't currently have any APIs that accept IIncrementalGenerator directly (even in construction we wrap and unwrap them)
// so it should be impossible to get here with a wrapper that was created via ISourceGenerator.AsIncrementalGenerator.
// If we ever do have such an API, we will need to make sure that the source extension is updated as part of adding it to the driver.
Debug.Assert(_sourceExtension != DummySourceExtension);

GeneratorInitializationContext generatorInitContext = new GeneratorInitializationContext(CancellationToken.None);
SourceGenerator.Initialize(generatorInitContext);

Expand Down
Loading