diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx
index 6e35ba05afac9beff341fc44b6a33efaec64f5a4..98a607631d2103d87b66aed44636c59442b0107d 100644
--- a/src/Compilers/CSharp/Portable/CSharpResources.resx
+++ b/src/Compilers/CSharp/Portable/CSharpResources.resx
@@ -6436,4 +6436,4 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
extension GetEnumerator
-
+
\ No newline at end of file
diff --git a/src/Compilers/CSharp/Test/Semantic/SourceGeneration/GeneratorDriverTests.cs b/src/Compilers/CSharp/Test/Semantic/SourceGeneration/GeneratorDriverTests.cs
index ce8b9a5fcfa261941f4e8fcc98d98470b23fd698..42a45ec2c6314499d2d28a582b2fab9f7ec7cbda 100644
--- a/src/Compilers/CSharp/Test/Semantic/SourceGeneration/GeneratorDriverTests.cs
+++ b/src/Compilers/CSharp/Test/Semantic/SourceGeneration/GeneratorDriverTests.cs
@@ -16,6 +16,7 @@
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
+using Roslyn.Test.Utilities;
using Roslyn.Test.Utilities.TestGenerators;
using Xunit;
@@ -698,6 +699,209 @@ class C { }
$"{generator2.GetType().Module.ModuleVersionId}_{generator2.GetType().FullName}_source.cs"
}, filePaths);
}
+
+ [Fact]
+ public void RunResults_Are_Empty_Before_Generation()
+ {
+ GeneratorDriver driver = new CSharpGeneratorDriver(TestOptions.Regular, ImmutableArray.Empty, CompilerAnalyzerConfigOptionsProvider.Empty, ImmutableArray.Empty);
+ var results = driver.GetRunResult();
+
+ Assert.Empty(results.GeneratedTrees);
+ Assert.Empty(results.Diagnostics);
+ Assert.Empty(results.Results);
+ }
+
+ [Fact]
+ public void RunResults_Are_Available_After_Generation()
+ {
+ var source = @"
+class C { }
+";
+ var parseOptions = TestOptions.Regular;
+ Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions);
+ compilation.VerifyDiagnostics();
+ Assert.Single(compilation.SyntaxTrees);
+
+ var generator = new CallbackGenerator((ic) => { }, (sgc) => { sgc.AddSource("test", SourceText.From("public class D {}", Encoding.UTF8)); });
+
+ GeneratorDriver driver = new CSharpGeneratorDriver(parseOptions, ImmutableArray.Create(generator), CompilerAnalyzerConfigOptionsProvider.Empty, ImmutableArray.Empty);
+ driver = driver.RunGenerators(compilation);
+
+ var results = driver.GetRunResult();
+
+ Assert.Single(results.GeneratedTrees);
+ Assert.Single(results.Results);
+ Assert.Empty(results.Diagnostics);
+
+ var result = results.Results.Single();
+
+ Assert.Null(result.Exception);
+ Assert.Empty(result.Diagnostics);
+ Assert.Single(result.GeneratedSources);
+ Assert.Equal(results.GeneratedTrees.Single(), result.GeneratedSources.Single().SyntaxTree);
+ }
+
+ [Fact]
+ public void RunResults_Combine_SyntaxTrees()
+ {
+ var source = @"
+class C { }
+";
+ var parseOptions = TestOptions.Regular;
+ Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions);
+ compilation.VerifyDiagnostics();
+ Assert.Single(compilation.SyntaxTrees);
+
+ var generator = new CallbackGenerator((ic) => { }, (sgc) => { sgc.AddSource("test", SourceText.From("public class D {}", Encoding.UTF8)); sgc.AddSource("test2", SourceText.From("public class E {}", Encoding.UTF8)); });
+ var generator2 = new SingleFileTestGenerator("public class F{}");
+ var generator3 = new SingleFileTestGenerator2("public class G{}");
+
+ GeneratorDriver driver = new CSharpGeneratorDriver(parseOptions, ImmutableArray.Create(generator, generator2, generator3), CompilerAnalyzerConfigOptionsProvider.Empty, ImmutableArray.Empty);
+ driver = driver.RunGenerators(compilation);
+
+ var results = driver.GetRunResult();
+
+ Assert.Equal(4, results.GeneratedTrees.Length);
+ Assert.Equal(3, results.Results.Length);
+ Assert.Empty(results.Diagnostics);
+
+ var result1 = results.Results[0];
+ var result2 = results.Results[1];
+ var result3 = results.Results[2];
+
+ Assert.Null(result1.Exception);
+ Assert.Empty(result1.Diagnostics);
+ Assert.Equal(2, result1.GeneratedSources.Length);
+ Assert.Equal(results.GeneratedTrees[0], result1.GeneratedSources[0].SyntaxTree);
+ Assert.Equal(results.GeneratedTrees[1], result1.GeneratedSources[1].SyntaxTree);
+
+ Assert.Null(result2.Exception);
+ Assert.Empty(result2.Diagnostics);
+ Assert.Single(result2.GeneratedSources);
+ Assert.Equal(results.GeneratedTrees[2], result2.GeneratedSources[0].SyntaxTree);
+
+ Assert.Null(result3.Exception);
+ Assert.Empty(result3.Diagnostics);
+ Assert.Single(result3.GeneratedSources);
+ Assert.Equal(results.GeneratedTrees[3], result3.GeneratedSources[0].SyntaxTree);
+ }
+
+ [Fact]
+ public void RunResults_Combine_Diagnostics()
+ {
+ var source = @"
+class C { }
+";
+ var parseOptions = TestOptions.Regular;
+ Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions);
+ compilation.VerifyDiagnostics();
+ Assert.Single(compilation.SyntaxTrees);
+
+ string description = "This is a test diagnostic";
+ DiagnosticDescriptor generatorDiagnostic1 = new DiagnosticDescriptor("TG001", "Test Diagnostic", description, "Generators", DiagnosticSeverity.Warning, isEnabledByDefault: true, description: description);
+ DiagnosticDescriptor generatorDiagnostic2 = new DiagnosticDescriptor("TG002", "Test Diagnostic", description, "Generators", DiagnosticSeverity.Warning, isEnabledByDefault: true, description: description);
+ DiagnosticDescriptor generatorDiagnostic3 = new DiagnosticDescriptor("TG003", "Test Diagnostic", description, "Generators", DiagnosticSeverity.Warning, isEnabledByDefault: true, description: description);
+
+ var diagnostic1 = Microsoft.CodeAnalysis.Diagnostic.Create(generatorDiagnostic1, Location.None);
+ var diagnostic2 = Microsoft.CodeAnalysis.Diagnostic.Create(generatorDiagnostic2, Location.None);
+ var diagnostic3 = Microsoft.CodeAnalysis.Diagnostic.Create(generatorDiagnostic3, Location.None);
+
+ var generator = new CallbackGenerator((ic) => { }, (sgc) => { sgc.ReportDiagnostic(diagnostic1); sgc.ReportDiagnostic(diagnostic2); });
+ var generator2 = new CallbackGenerator2((ic) => { }, (sgc) => { sgc.ReportDiagnostic(diagnostic3); });
+
+ GeneratorDriver driver = new CSharpGeneratorDriver(parseOptions, ImmutableArray.Create(generator, generator2), CompilerAnalyzerConfigOptionsProvider.Empty, ImmutableArray.Empty);
+ driver = driver.RunGenerators(compilation);
+
+ var results = driver.GetRunResult();
+
+ Assert.Equal(2, results.Results.Length);
+ Assert.Equal(3, results.Diagnostics.Length);
+ Assert.Empty(results.GeneratedTrees);
+
+ var result1 = results.Results[0];
+ var result2 = results.Results[1];
+
+ Assert.Null(result1.Exception);
+ Assert.Equal(2, result1.Diagnostics.Length);
+ Assert.Empty(result1.GeneratedSources);
+ Assert.Equal(results.Diagnostics[0], result1.Diagnostics[0]);
+ Assert.Equal(results.Diagnostics[1], result1.Diagnostics[1]);
+
+ Assert.Null(result2.Exception);
+ Assert.Single(result2.Diagnostics);
+ Assert.Empty(result2.GeneratedSources);
+ Assert.Equal(results.Diagnostics[2], result2.Diagnostics[0]);
+ }
+
+ [Fact]
+ public void FullGeneration_Diagnostics_AreSame_As_RunResults()
+ {
+ var source = @"
+class C { }
+";
+ var parseOptions = TestOptions.Regular;
+ Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions);
+ compilation.VerifyDiagnostics();
+ Assert.Single(compilation.SyntaxTrees);
+
+ string description = "This is a test diagnostic";
+ DiagnosticDescriptor generatorDiagnostic1 = new DiagnosticDescriptor("TG001", "Test Diagnostic", description, "Generators", DiagnosticSeverity.Warning, isEnabledByDefault: true, description: description);
+ DiagnosticDescriptor generatorDiagnostic2 = new DiagnosticDescriptor("TG002", "Test Diagnostic", description, "Generators", DiagnosticSeverity.Warning, isEnabledByDefault: true, description: description);
+ DiagnosticDescriptor generatorDiagnostic3 = new DiagnosticDescriptor("TG003", "Test Diagnostic", description, "Generators", DiagnosticSeverity.Warning, isEnabledByDefault: true, description: description);
+
+ var diagnostic1 = Microsoft.CodeAnalysis.Diagnostic.Create(generatorDiagnostic1, Location.None);
+ var diagnostic2 = Microsoft.CodeAnalysis.Diagnostic.Create(generatorDiagnostic2, Location.None);
+ var diagnostic3 = Microsoft.CodeAnalysis.Diagnostic.Create(generatorDiagnostic3, Location.None);
+
+ var generator = new CallbackGenerator((ic) => { }, (sgc) => { sgc.ReportDiagnostic(diagnostic1); sgc.ReportDiagnostic(diagnostic2); });
+ var generator2 = new CallbackGenerator2((ic) => { }, (sgc) => { sgc.ReportDiagnostic(diagnostic3); });
+
+ GeneratorDriver driver = new CSharpGeneratorDriver(parseOptions, ImmutableArray.Create(generator, generator2), CompilerAnalyzerConfigOptionsProvider.Empty, ImmutableArray.Empty);
+ driver = driver.RunFullGeneration(compilation, out _, out var fullDiagnostics);
+
+ var results = driver.GetRunResult();
+
+ Assert.Equal(3, results.Diagnostics.Length);
+ Assert.Equal(3, fullDiagnostics.Length);
+ AssertEx.Equal(results.Diagnostics, fullDiagnostics);
+ }
+
+ [Fact]
+ public void Cancellation_During_Execution_Doesnt_Report_As_Generator_Error()
+ {
+ var source = @"
+class C
+{
+}
+";
+ var parseOptions = TestOptions.Regular;
+ Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions);
+ compilation.VerifyDiagnostics();
+
+ Assert.Single(compilation.SyntaxTrees);
+
+ CancellationTokenSource cts = new CancellationTokenSource();
+
+ var testGenerator = new CallbackGenerator(
+ onInit: (i) => { },
+ onExecute: (e) => { cts.Cancel(); }
+ );
+
+ // test generator cancels the token. Check that the call to this generator doesn't make it look like it errored.
+ var testGenerator2 = new CallbackGenerator2(
+ onInit: (i) => { },
+ onExecute: (e) => { e.AddSource("a", SourceText.From("public class E {}", Encoding.UTF8)); }
+ );
+
+
+ GeneratorDriver driver = new CSharpGeneratorDriver(parseOptions, ImmutableArray.Create(testGenerator, testGenerator2), CompilerAnalyzerConfigOptionsProvider.Empty, ImmutableArray.Empty);
+ var oldDriver = driver;
+
+ Assert.Throws(() =>
+ driver = driver.RunFullGeneration(compilation, out var outputCompilation, out var outputDiagnostics, cts.Token)
+ );
+ Assert.Same(oldDriver, driver);
+ }
}
}
diff --git a/src/Compilers/CSharp/Test/Semantic/SourceGeneration/SyntaxAwareGeneratorTests.cs b/src/Compilers/CSharp/Test/Semantic/SourceGeneration/SyntaxAwareGeneratorTests.cs
index 6c77bd9acf0d6127a4d6d89dfa8e2938fd99a248..de043660d28a81e16bbc170947ea3b15f26427f5 100644
--- a/src/Compilers/CSharp/Test/Semantic/SourceGeneration/SyntaxAwareGeneratorTests.cs
+++ b/src/Compilers/CSharp/Test/Semantic/SourceGeneration/SyntaxAwareGeneratorTests.cs
@@ -5,11 +5,13 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
+using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Text;
using Roslyn.Test.Utilities.TestGenerators;
using Xunit;
@@ -84,14 +86,14 @@ class C { }
Assert.Single(compilation.SyntaxTrees);
var testGenerator = new CallbackGenerator(
- onInit: Initialize,
+ onInit: initialize,
onExecute: (e) => { }
);
GeneratorDriver driver = new CSharpGeneratorDriver(parseOptions, ImmutableArray.Create(testGenerator), CompilerAnalyzerConfigOptionsProvider.Empty, ImmutableArray.Empty);
driver.RunFullGeneration(compilation, out _, out _);
- void Initialize(InitializationContext initContext)
+ void initialize(InitializationContext initContext)
{
initContext.RegisterForSyntaxNotifications(() => new TestSyntaxReceiver());
Assert.Throws(() =>
@@ -192,21 +194,248 @@ void Function()
Assert.IsType(testReceiver.VisitedNodes[0]);
}
+ [Fact]
+ public void Syntax_Receiver_Exception_During_Creation()
+ {
+ var source = @"
+class C
+{
+ int Property { get; set; }
+
+ void Function()
+ {
+ var x = 5;
+ x += 4;
+ }
+}
+";
+ var parseOptions = TestOptions.Regular;
+ Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions);
+ compilation.VerifyDiagnostics();
+
+ Assert.Single(compilation.SyntaxTrees);
+
+ var testGenerator = new CallbackGenerator(
+ onInit: (i) => i.RegisterForSyntaxNotifications(() => throw new Exception("Test Exception")),
+ onExecute: (e) => { Assert.True(false); }
+ );
+
+ GeneratorDriver driver = new CSharpGeneratorDriver(parseOptions, ImmutableArray.Create(testGenerator), CompilerAnalyzerConfigOptionsProvider.Empty, ImmutableArray.Empty);
+ driver = driver.RunFullGeneration(compilation, out var outputCompilation, out var outputDiagnostics);
+ var results = driver.GetRunResult();
+
+ Assert.Empty(results.GeneratedTrees);
+ Assert.Single(results.Diagnostics);
+ Assert.Single(results.Results);
+ Assert.Single(results.Results[0].Diagnostics);
+
+ Assert.NotNull(results.Results[0].Exception);
+ Assert.Equal("Test Exception", results.Results[0].Exception?.Message);
+
+ outputDiagnostics.Verify(
+ Diagnostic(ErrorCode.WRN_GeneratorFailedDuringGeneration).WithArguments("CallbackGenerator").WithLocation(1, 1)
+ );
+ }
+
+ [Fact]
+ public void Syntax_Receiver_Exception_During_Visit()
+ {
+ var source = @"
+class C
+{
+ int Property { get; set; }
+
+ void Function()
+ {
+ var x = 5;
+ x += 4;
+ }
+}
+";
+ var parseOptions = TestOptions.Regular;
+ Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions);
+ compilation.VerifyDiagnostics();
+
+ Assert.Single(compilation.SyntaxTrees);
+
+ var testGenerator = new CallbackGenerator(
+ onInit: (i) => i.RegisterForSyntaxNotifications(() => new TestSyntaxReceiver(tag: 0, callback: (a) => { if (a is AssignmentExpressionSyntax) throw new Exception("Test Exception"); })),
+ onExecute: (e) => { e.AddSource("test", SourceText.From("public class D{}", Encoding.UTF8)); }
+ );
+
+ GeneratorDriver driver = new CSharpGeneratorDriver(parseOptions, ImmutableArray.Create(testGenerator), CompilerAnalyzerConfigOptionsProvider.Empty, ImmutableArray.Empty);
+ driver = driver.RunFullGeneration(compilation, out var outputCompilation, out var outputDiagnostics);
+ var results = driver.GetRunResult();
+
+ Assert.Empty(results.GeneratedTrees);
+ Assert.Single(results.Diagnostics);
+ Assert.Single(results.Results);
+ Assert.Single(results.Results[0].Diagnostics);
+
+ Assert.NotNull(results.Results[0].Exception);
+ Assert.Equal("Test Exception", results.Results[0].Exception?.Message);
+
+ outputDiagnostics.Verify(
+ Diagnostic(ErrorCode.WRN_GeneratorFailedDuringGeneration).WithArguments("CallbackGenerator").WithLocation(1, 1)
+ );
+ }
+
+ [Fact]
+ public void Syntax_Receiver_Exception_During_Visit_Stops_Visits_On_Other_Trees()
+ {
+ var source1 = @"
+class C
+{
+ int Property { get; set; }
+}
+";
+ var source2 = @"
+class D
+{
+ public void Method() { }
+}
+";
+ var parseOptions = TestOptions.Regular;
+ Compilation compilation = CreateCompilation(new[] { source1, source2 }, options: TestOptions.DebugDll, parseOptions: parseOptions);
+ compilation.VerifyDiagnostics();
+
+ Assert.Equal(2, compilation.SyntaxTrees.Count());
+
+ TestSyntaxReceiver receiver1 = new TestSyntaxReceiver(tag: 0, callback: (a) => { if (a is PropertyDeclarationSyntax) throw new Exception("Test Exception"); });
+ var testGenerator1 = new CallbackGenerator(
+ onInit: (i) => i.RegisterForSyntaxNotifications(() => receiver1),
+ onExecute: (e) => { }
+ );
+
+ TestSyntaxReceiver receiver2 = new TestSyntaxReceiver(tag: 1);
+ var testGenerator2 = new CallbackGenerator2(
+ onInit: (i) => i.RegisterForSyntaxNotifications(() => receiver2),
+ onExecute: (e) => { }
+ );
+
+ GeneratorDriver driver = new CSharpGeneratorDriver(parseOptions, ImmutableArray.Create(testGenerator1, testGenerator2), CompilerAnalyzerConfigOptionsProvider.Empty, ImmutableArray.Empty);
+ driver = driver.RunFullGeneration(compilation, out var outputCompilation, out var outputDiagnostics);
+ var results = driver.GetRunResult();
+
+ Assert.DoesNotContain(receiver1.VisitedNodes, n => n is MethodDeclarationSyntax);
+ Assert.Contains(receiver2.VisitedNodes, n => n is MethodDeclarationSyntax);
+
+ }
+
+ [Fact]
+ public void Syntax_Receiver_Exception_During_Visit_Doesnt_Stop_Other_Receivers()
+ {
+ var source = @"
+class C
+{
+ int Property { get; set; }
+
+ void Function()
+ {
+ var x = 5;
+ x += 4;
+ }
+}
+";
+ var parseOptions = TestOptions.Regular;
+ Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions);
+ compilation.VerifyDiagnostics();
+
+ Assert.Single(compilation.SyntaxTrees);
+
+ var testGenerator = new CallbackGenerator(
+ onInit: (i) => i.RegisterForSyntaxNotifications(() => new TestSyntaxReceiver(tag: 0, callback: (a) => { if (a is AssignmentExpressionSyntax) throw new Exception("Test Exception"); })),
+ onExecute: (e) => { }
+ );
+
+ ISyntaxReceiver? receiver = null;
+ var testGenerator2 = new CallbackGenerator2(
+ onInit: (i) => i.RegisterForSyntaxNotifications(() => new TestSyntaxReceiver(tag: 1)),
+ onExecute: (e) => { receiver = e.SyntaxReceiver; e.AddSource("test", SourceText.From("public class D{}", Encoding.UTF8)); }
+ );
+
+ GeneratorDriver driver = new CSharpGeneratorDriver(parseOptions, ImmutableArray.Create(testGenerator, testGenerator2), CompilerAnalyzerConfigOptionsProvider.Empty, ImmutableArray.Empty);
+ driver = driver.RunFullGeneration(compilation, out var outputCompilation, out var outputDiagnostics);
+ var results = driver.GetRunResult();
+
+ Assert.Single(results.GeneratedTrees);
+ Assert.Single(results.Diagnostics);
+ Assert.Equal(2, results.Results.Length);
+
+ Assert.Single(results.Results[0].Diagnostics);
+ Assert.NotNull(results.Results[0].Exception);
+ Assert.Equal("Test Exception", results.Results[0].Exception?.Message);
+
+ Assert.Empty(results.Results[1].Diagnostics);
+
+ var testReceiver = (TestSyntaxReceiver)receiver!;
+ Assert.Equal(1, testReceiver.Tag);
+ Assert.Equal(21, testReceiver.VisitedNodes.Count);
+
+ outputDiagnostics.Verify(
+ Diagnostic(ErrorCode.WRN_GeneratorFailedDuringGeneration).WithArguments("CallbackGenerator").WithLocation(1, 1)
+ );
+ }
+
+ [Fact]
+ public void Syntax_Receiver_Is_Not_Created_If_Exception_During_Initialize()
+ {
+ var source = @"
+class C
+{
+ int Property { get; set; }
+
+ void Function()
+ {
+ var x = 5;
+ x += 4;
+ }
+}
+";
+ var parseOptions = TestOptions.Regular;
+ Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions);
+ compilation.VerifyDiagnostics();
+
+ Assert.Single(compilation.SyntaxTrees);
+
+ TestSyntaxReceiver? receiver = null;
+ var testGenerator = new CallbackGenerator(
+ onInit: (i) => { i.RegisterForSyntaxNotifications(() => receiver = new TestSyntaxReceiver()); throw new Exception("test exception"); },
+ onExecute: (e) => { Assert.True(false); }
+ );
+
+ GeneratorDriver driver = new CSharpGeneratorDriver(parseOptions, ImmutableArray.Create(testGenerator), CompilerAnalyzerConfigOptionsProvider.Empty, ImmutableArray.Empty);
+ driver = driver.RunFullGeneration(compilation, out var outputCompilation, out var outputDiagnostics);
+ var results = driver.GetRunResult();
+
+ Assert.Null(receiver);
+
+ outputDiagnostics.Verify(
+ Diagnostic(ErrorCode.WRN_GeneratorFailedDuringInitialization).WithArguments("CallbackGenerator").WithLocation(1, 1)
+ );
+ }
class TestSyntaxReceiver : ISyntaxReceiver
{
+ private readonly Action? _callback;
+
public List VisitedNodes { get; } = new List();
public int Tag { get; }
- public TestSyntaxReceiver(int tag = 0)
+ public TestSyntaxReceiver(int tag = 0, Action? callback = null)
{
Tag = tag;
+ _callback = callback;
}
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
{
VisitedNodes.Add(syntaxNode);
+ if (_callback is object)
+ {
+ _callback(syntaxNode);
+ }
}
}
}
diff --git a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt
index ba677b3ca322d0217a7bb7a73d06a1a796ed392e..5de1fb54c9822aaa70b49dd798499f77fb314890 100644
--- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt
+++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt
@@ -9,10 +9,14 @@ Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzers.GetAnalysisResultAsy
Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzers.GetAnalysisResultAsync(Microsoft.CodeAnalysis.AdditionalText file, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task
Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.AdditionalFileActionsCount.get -> int
Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.AdditionalFileActionsCount.set -> void
+Microsoft.CodeAnalysis.GeneratorDriverRunResult.Diagnostics.get -> System.Collections.Immutable.ImmutableArray
+Microsoft.CodeAnalysis.GeneratorDriverRunResult.GeneratedTrees.get -> System.Collections.Immutable.ImmutableArray
+Microsoft.CodeAnalysis.GeneratorDriverRunResult.Results.get -> System.Collections.Immutable.ImmutableArray
Microsoft.CodeAnalysis.SyntaxTreeOptionsProvider
Microsoft.CodeAnalysis.SyntaxTreeOptionsProvider.SyntaxTreeOptionsProvider() -> void
abstract Microsoft.CodeAnalysis.SyntaxTreeOptionsProvider.IsGenerated(Microsoft.CodeAnalysis.SyntaxTree tree) -> bool?
abstract Microsoft.CodeAnalysis.SyntaxTreeOptionsProvider.TryGetDiagnosticValue(Microsoft.CodeAnalysis.SyntaxTree tree, string diagnosticId, out Microsoft.CodeAnalysis.ReportDiagnostic severity) -> bool
+Microsoft.CodeAnalysis.GeneratorRunResult.GeneratedSources.get -> System.Collections.Immutable.ImmutableArray
const Microsoft.CodeAnalysis.WellKnownMemberNames.TopLevelStatementsEntryPointMethodName = "$" -> string
const Microsoft.CodeAnalysis.WellKnownMemberNames.TopLevelStatementsEntryPointTypeName = "$" -> string
Microsoft.CodeAnalysis.CompilationOptions.SyntaxTreeOptionsProvider.get -> Microsoft.CodeAnalysis.SyntaxTreeOptionsProvider
@@ -20,3 +24,14 @@ Microsoft.CodeAnalysis.CompilationOptions.WithSyntaxTreeOptionsProvider(Microsof
Microsoft.CodeAnalysis.Operations.IPatternOperation.NarrowedType.get -> Microsoft.CodeAnalysis.ITypeSymbol
virtual Microsoft.CodeAnalysis.Diagnostics.AnalysisContext.RegisterAdditionalFileAction(System.Action action) -> void
virtual Microsoft.CodeAnalysis.Diagnostics.CompilationStartAnalysisContext.RegisterAdditionalFileAction(System.Action action) -> void
+Microsoft.CodeAnalysis.GeneratedSourceResult
+Microsoft.CodeAnalysis.GeneratedSourceResult.HintName.get -> string
+Microsoft.CodeAnalysis.GeneratedSourceResult.SourceText.get -> Microsoft.CodeAnalysis.Text.SourceText
+Microsoft.CodeAnalysis.GeneratedSourceResult.SyntaxTree.get -> Microsoft.CodeAnalysis.SyntaxTree
+Microsoft.CodeAnalysis.GeneratorDriver.GetRunResult() -> Microsoft.CodeAnalysis.GeneratorDriverRunResult
+Microsoft.CodeAnalysis.GeneratorDriver.RunGenerators(Microsoft.CodeAnalysis.Compilation compilation, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.GeneratorDriver
+Microsoft.CodeAnalysis.GeneratorDriverRunResult
+Microsoft.CodeAnalysis.GeneratorRunResult
+Microsoft.CodeAnalysis.GeneratorRunResult.Diagnostics.get -> System.Collections.Immutable.ImmutableArray
+Microsoft.CodeAnalysis.GeneratorRunResult.Exception.get -> System.Exception
+Microsoft.CodeAnalysis.GeneratorRunResult.Generator.get -> Microsoft.CodeAnalysis.ISourceGenerator
\ No newline at end of file
diff --git a/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriver.cs b/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriver.cs
index f778253566c8da98833705b2f97528840f95351e..bb9913c70a229e7b054753fdf465bd87846f0ea3 100644
--- a/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriver.cs
+++ b/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriver.cs
@@ -9,7 +9,6 @@
using System.Threading;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.PooledObjects;
-using Roslyn.Utilities;
#nullable enable
namespace Microsoft.CodeAnalysis
@@ -46,80 +45,19 @@ internal GeneratorDriver(ParseOptions parseOptions, ImmutableArray.Empty, editsFailed: true);
}
- public GeneratorDriver RunFullGeneration(Compilation compilation, out Compilation outputCompilation, out ImmutableArray diagnostics, CancellationToken cancellationToken = default)
+ public GeneratorDriver RunGenerators(Compilation compilation, CancellationToken cancellationToken = default)
{
- // with no generators, there is no work to do
- if (_state.Generators.Length == 0)
- {
- outputCompilation = compilation;
- diagnostics = ImmutableArray.Empty;
- return this;
- }
-
- // run the actual generation
- var state = StateWithPendingEditsApplied(_state);
- var stateBuilder = ArrayBuilder.GetInstance();
- var receivers = PooledDictionary.GetInstance();
- var diagnosticsBag = new DiagnosticBag();
-
- for (int i = 0; i < state.Generators.Length; i++)
- {
- var generator = state.Generators[i];
- var generatorState = state.GeneratorStates[i];
-
- // initialize the generator if needed
- if (!generatorState.Info.Initialized)
- {
- generatorState = InitializeGenerator(generator, diagnosticsBag, cancellationToken);
- }
- stateBuilder.Add(generatorState);
-
- // create the syntax receiver if requested
- if (generatorState.Info.SyntaxReceiverCreator is object)
- {
- var rx = generatorState.Info.SyntaxReceiverCreator();
- receivers.Add(generator, rx);
- }
- }
-
- // Run a syntax walk if any of the generators requested it
- if (receivers.Count > 0)
- {
- GeneratorSyntaxWalker walker = new GeneratorSyntaxWalker(receivers.Values.ToImmutableArray());
- foreach (var syntaxTree in compilation.SyntaxTrees)
- {
- walker.Visit(syntaxTree.GetRoot(cancellationToken));
- }
- }
-
- // https://github.com/dotnet/roslyn/issues/42629: should be possible to parallelize this
- for (int i = 0; i < state.Generators.Length; i++)
- {
- var generator = state.Generators[i];
- var generatorState = stateBuilder[i];
- try
- {
- // don't try and generate if initialization failed
- if (!generatorState.Info.Initialized)
- {
- continue;
- }
+ var state = RunGeneratorsCore(compilation, diagnosticsBag: null, cancellationToken); //don't directly collect diagnostics on this path
+ return FromState(state);
+ }
- // we create a new context for each run of the generator. We'll never re-use existing state, only replace anything we have
- _ = receivers.TryGetValue(generator, out var syntaxReceiverOpt);
- var context = new SourceGeneratorContext(compilation, state.AdditionalTexts.NullToEmpty(), state.OptionsProvider, syntaxReceiverOpt, diagnosticsBag);
- generator.Execute(context);
- stateBuilder[i] = generatorState.WithSources(ParseAdditionalSources(generator, context.AdditionalSources.ToImmutableAndFree(), cancellationToken));
- }
- catch
- {
- diagnosticsBag.Add(Diagnostic.Create(MessageProvider, MessageProvider.WRN_GeneratorFailedDuringGeneration, generator.GetType().Name));
- }
- }
- state = state.With(generatorStates: stateBuilder.ToImmutableAndFree());
- diagnostics = diagnosticsBag.ToReadOnlyAndFree();
+ public GeneratorDriver RunFullGeneration(Compilation compilation, out Compilation outputCompilation, out ImmutableArray diagnostics, CancellationToken cancellationToken = default)
+ {
+ var diagnosticsBag = DiagnosticBag.GetInstance();
+ var state = RunGeneratorsCore(compilation, diagnosticsBag, cancellationToken);
// build the final state, and return
+ diagnostics = diagnosticsBag.ToReadOnlyAndFree();
return BuildFinalCompilation(compilation, out outputCompilation, state, cancellationToken);
}
@@ -183,6 +121,138 @@ public GeneratorDriver AddAdditionalTexts(ImmutableArray additio
return FromState(newState);
}
+ public GeneratorDriverRunResult GetRunResult()
+ {
+ var results = _state.Generators.ZipAsArray(
+ _state.GeneratorStates,
+ (generator, generatorState)
+ => new GeneratorRunResult(generator,
+ diagnostics: generatorState.Diagnostics,
+ exception: generatorState.Exception,
+ generatedSources: generatorState.SourceTexts.ZipAsArray(
+ generatorState.Trees,
+ (sourceText, tree) => new GeneratedSourceResult(tree, sourceText.Text, sourceText.HintName))));
+ return new GeneratorDriverRunResult(results);
+ }
+
+ internal GeneratorDriverState RunGeneratorsCore(Compilation compilation, DiagnosticBag? diagnosticsBag, CancellationToken cancellationToken = default)
+ {
+ // with no generators, there is no work to do
+ if (_state.Generators.IsEmpty)
+ {
+ return _state;
+ }
+
+ // run the actual generation
+ var state = StateWithPendingEditsApplied(_state);
+ var stateBuilder = ArrayBuilder.GetInstance(state.Generators.Length);
+ var walkerBuilder = ArrayBuilder.GetInstance(state.Generators.Length, fillWithValue: null); // we know there is at max 1 per generator
+ int receiverCount = 0;
+
+ for (int i = 0; i < state.Generators.Length; i++)
+ {
+ var generator = state.Generators[i];
+ var generatorState = state.GeneratorStates[i];
+
+ // initialize the generator if needed
+ if (!generatorState.Info.Initialized)
+ {
+ InitializationContext context = new InitializationContext(cancellationToken);
+ Exception? ex = null;
+ try
+ {
+ generator.Initialize(context);
+ }
+ catch (Exception e)
+ {
+ ex = e;
+ }
+ generatorState = ex is null
+ ? new GeneratorState(context.InfoBuilder.ToImmutable())
+ : SetGeneratorException(MessageProvider, GeneratorState.Uninitialized, generator, ex, diagnosticsBag, isInit: true);
+ }
+
+ // create the syntax receiver if requested
+ if (generatorState.Info.SyntaxReceiverCreator is object)
+ {
+ try
+ {
+ var rx = generatorState.Info.SyntaxReceiverCreator();
+ walkerBuilder.SetItem(i, new GeneratorSyntaxWalker(rx));
+ generatorState = generatorState.WithReceiver(rx);
+ receiverCount++;
+ }
+ catch (Exception e)
+ {
+ generatorState = SetGeneratorException(MessageProvider, generatorState, generator, e, diagnosticsBag);
+ }
+ }
+
+ stateBuilder.Add(generatorState);
+ }
+
+
+ // Run a syntax walk if any of the generators requested it
+ if (receiverCount > 0)
+ {
+ foreach (var tree in compilation.SyntaxTrees)
+ {
+ var root = tree.GetRoot(cancellationToken);
+
+ // https://github.com/dotnet/roslyn/issues/42629: should be possible to parallelize this
+ for (int i = 0; i < walkerBuilder.Count; i++)
+ {
+ var walker = walkerBuilder[i];
+ if (walker is object)
+ {
+ try
+ {
+ walker.Visit(root);
+ }
+ catch (Exception e)
+ {
+ stateBuilder[i] = SetGeneratorException(MessageProvider, stateBuilder[i], state.Generators[i], e, diagnosticsBag);
+ walkerBuilder.SetItem(i, null); // don't re-visit this walker for any other trees
+ }
+ }
+ }
+ }
+ }
+ walkerBuilder.Free();
+
+ // https://github.com/dotnet/roslyn/issues/42629: should be possible to parallelize this
+ for (int i = 0; i < state.Generators.Length; i++)
+ {
+ var generator = state.Generators[i];
+ var generatorState = stateBuilder[i];
+
+ // don't try and generate if initialization or syntax walk failed
+ if (generatorState.Exception is object)
+ {
+ continue;
+ }
+ Debug.Assert(generatorState.Info.Initialized);
+
+ // we create a new context for each run of the generator. We'll never re-use existing state, only replace anything we have
+ var context = new SourceGeneratorContext(compilation, state.AdditionalTexts.NullToEmpty(), state.OptionsProvider, generatorState.SyntaxReceiver);
+ try
+ {
+ generator.Execute(context);
+ }
+ catch (Exception e)
+ {
+ stateBuilder[i] = SetGeneratorException(MessageProvider, generatorState, generator, e, diagnosticsBag);
+ continue;
+ }
+
+ (var sources, var diagnostics) = context.ToImmutableAndFree();
+ stateBuilder[i] = new GeneratorState(generatorState.Info, sources, ParseAdditionalSources(generator, sources, cancellationToken), diagnostics);
+ diagnosticsBag?.AddRange(diagnostics);
+ }
+ state = state.With(generatorStates: stateBuilder.ToImmutableAndFree());
+ return state;
+ }
+
// When we expose this publicly, remove arbitrary edit adding and replace with dedicated edit types
internal GeneratorDriver WithPendingEdits(ImmutableArray edits)
{
@@ -203,7 +273,7 @@ private GeneratorDriverState ApplyPartialEdit(GeneratorDriverState state, Pendin
if (edit.AcceptedBy(generatorState.Info))
{
// attempt to apply the edit
- var context = new EditContext(generatorState.Sources.Keys.ToImmutableArray(), cancellationToken);
+ var context = new EditContext(generatorState.SourceTexts.ToImmutableArray(), cancellationToken);
var succeeded = edit.TryApply(generatorState.Info, context);
if (!succeeded)
{
@@ -213,7 +283,8 @@ private GeneratorDriverState ApplyPartialEdit(GeneratorDriverState state, Pendin
}
// update the state with the new edits
- state = state.With(generatorStates: state.GeneratorStates.SetItem(i, generatorState.WithSources(ParseAdditionalSources(generator, context.AdditionalSources.ToImmutableAndFree(), cancellationToken))));
+ var additionalSources = context.AdditionalSources.ToImmutableAndFree();
+ state = state.With(generatorStates: state.GeneratorStates.SetItem(i, new GeneratorState(generatorState.Info, sourceTexts: additionalSources, trees: ParseAdditionalSources(generator, additionalSources, cancellationToken), diagnostics: ImmutableArray.Empty)));
}
}
state = edit.Commit(state);
@@ -234,28 +305,12 @@ private static GeneratorDriverState StateWithPendingEditsApplied(GeneratorDriver
return state.With(edits: ImmutableArray.Empty, editsFailed: false);
}
- private GeneratorState InitializeGenerator(ISourceGenerator generator, DiagnosticBag diagnostics, CancellationToken cancellationToken)
- {
- GeneratorInfo info = default;
- try
- {
- InitializationContext context = new InitializationContext(cancellationToken);
- generator.Initialize(context);
- info = context.InfoBuilder.ToImmutable();
- }
- catch
- {
- diagnostics.Add(Diagnostic.Create(MessageProvider, MessageProvider.WRN_GeneratorFailedDuringInitialization, generator.GetType().Name));
- }
- return new GeneratorState(info);
- }
-
private static Compilation RemoveGeneratedSyntaxTrees(GeneratorDriverState state, Compilation compilation)
{
ArrayBuilder trees = ArrayBuilder.GetInstance();
foreach (var generatorState in state.GeneratorStates)
{
- foreach (var (_, tree) in generatorState.Sources)
+ foreach (var tree in generatorState.Trees)
{
if (tree is object && compilation.ContainsSyntaxTree(tree))
{
@@ -269,16 +324,16 @@ private static Compilation RemoveGeneratedSyntaxTrees(GeneratorDriverState state
return comp;
}
- private ImmutableDictionary ParseAdditionalSources(ISourceGenerator generator, ImmutableArray generatedSources, CancellationToken cancellationToken)
+ private ImmutableArray ParseAdditionalSources(ISourceGenerator generator, ImmutableArray generatedSources, CancellationToken cancellationToken)
{
- var trees = PooledDictionary.GetInstance();
+ var trees = ArrayBuilder.GetInstance(generatedSources.Length);
var type = generator.GetType();
var prefix = $"{type.Module.ModuleVersionId}_{type.FullName}";
foreach (var source in generatedSources)
{
- trees.Add(source, ParseGeneratedSourceText(source, $"{prefix}_{source.HintName}", cancellationToken));
+ trees.Add(ParseGeneratedSourceText(source, $"{prefix}_{source.HintName}", cancellationToken));
}
- return trees.ToImmutableDictionaryAndFree();
+ return trees.ToImmutableAndFree();
}
private GeneratorDriver BuildFinalCompilation(Compilation compilation, out Compilation outputCompilation, GeneratorDriverState state, CancellationToken cancellationToken)
@@ -286,7 +341,7 @@ private GeneratorDriver BuildFinalCompilation(Compilation compilation, out Compi
ArrayBuilder trees = ArrayBuilder.GetInstance();
foreach (var generatorState in state.GeneratorStates)
{
- trees.AddRange(generatorState.Sources.Values);
+ trees.AddRange(generatorState.Trees);
}
outputCompilation = compilation.AddSyntaxTrees(trees);
trees.Free();
@@ -296,6 +351,14 @@ private GeneratorDriver BuildFinalCompilation(Compilation compilation, out Compi
return FromState(state);
}
+ private static GeneratorState SetGeneratorException(CommonMessageProvider provider, GeneratorState generatorState, ISourceGenerator generator, Exception e, DiagnosticBag? diagnosticBag, bool isInit = false)
+ {
+ var message = isInit ? provider.WRN_GeneratorFailedDuringInitialization : provider.WRN_GeneratorFailedDuringGeneration;
+ var diagnostic = Diagnostic.Create(provider, message, generator.GetType().Name);
+ diagnosticBag?.Add(diagnostic);
+ return new GeneratorState(generatorState.Info, e, diagnostic);
+ }
+
internal abstract CommonMessageProvider MessageProvider { get; }
internal abstract GeneratorDriver FromState(GeneratorDriverState state);
diff --git a/src/Compilers/Core/Portable/SourceGeneration/GeneratorState.cs b/src/Compilers/Core/Portable/SourceGeneration/GeneratorState.cs
index 561ab96fb07695d40b4a545c4a1b19c5d3b6dde3..48ba08c000f0f5165058b6270ecb2e71985eeda4 100644
--- a/src/Compilers/Core/Portable/SourceGeneration/GeneratorState.cs
+++ b/src/Compilers/Core/Portable/SourceGeneration/GeneratorState.cs
@@ -2,31 +2,81 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System;
using System.Collections.Immutable;
+using System.Diagnostics;
#nullable enable
namespace Microsoft.CodeAnalysis
{
+ ///
+ /// Represents the current state of a generator
+ ///
internal readonly struct GeneratorState
{
+ ///
+ /// Gets an uninitalized generator state
+ ///
+ internal static GeneratorState Uninitialized;
+
+ ///
+ /// Creates a new generator state that just contains information
+ ///
public GeneratorState(GeneratorInfo info)
- : this(info, ImmutableDictionary.Empty)
+ : this(info, ImmutableArray.Empty, ImmutableArray.Empty, ImmutableArray.Empty, syntaxReceiver: null, exception: null)
+ {
+ }
+
+ ///
+ /// Creates a new generator state that contains an exception and the associated diagnostic
+ ///
+ public GeneratorState(GeneratorInfo info, Exception e, Diagnostic error)
+ : this(info, ImmutableArray.Empty, ImmutableArray.Empty, ImmutableArray.Create(error), syntaxReceiver: null, exception: e)
{
}
- private GeneratorState(GeneratorInfo info, ImmutableDictionary sources)
+ ///
+ /// Creates a generator state that contains results
+ ///
+ public GeneratorState(GeneratorInfo info, ImmutableArray sourceTexts, ImmutableArray trees, ImmutableArray diagnostics)
+ : this(info, sourceTexts, trees, diagnostics, syntaxReceiver: null, exception: null)
{
- this.Sources = sources;
+ }
+
+ private GeneratorState(GeneratorInfo info, ImmutableArray sourceTexts, ImmutableArray trees, ImmutableArray diagnostics, ISyntaxReceiver? syntaxReceiver, Exception? exception)
+ {
+ this.SourceTexts = sourceTexts;
+ this.Trees = trees;
this.Info = info;
+ this.Diagnostics = diagnostics;
+ this.SyntaxReceiver = syntaxReceiver;
+ this.Exception = exception;
}
- internal ImmutableDictionary Sources { get; }
+ internal ImmutableArray SourceTexts { get; }
+
+ internal ImmutableArray Trees { get; }
internal GeneratorInfo Info { get; }
- internal GeneratorState WithSources(ImmutableDictionary sources)
+ internal ISyntaxReceiver? SyntaxReceiver { get; }
+
+ internal Exception? Exception { get; }
+
+ internal ImmutableArray Diagnostics { get; }
+
+ ///
+ /// Adds an to this generator state
+ ///
+ internal GeneratorState WithReceiver(ISyntaxReceiver syntaxReceiver)
{
- return new GeneratorState(this.Info, sources);
+ Debug.Assert(this.Exception is null);
+ return new GeneratorState(this.Info,
+ sourceTexts: this.SourceTexts,
+ trees: this.Trees,
+ diagnostics: this.Diagnostics,
+ syntaxReceiver: syntaxReceiver,
+ exception: null);
}
}
}
diff --git a/src/Compilers/Core/Portable/SourceGeneration/GeneratorSyntaxWalker.cs b/src/Compilers/Core/Portable/SourceGeneration/GeneratorSyntaxWalker.cs
index 7503895a664598631a2e7e91b085674948ba1968..8b804b74d33582865acc5721db5d7d2ea98a2ad7 100644
--- a/src/Compilers/Core/Portable/SourceGeneration/GeneratorSyntaxWalker.cs
+++ b/src/Compilers/Core/Portable/SourceGeneration/GeneratorSyntaxWalker.cs
@@ -2,28 +2,22 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-using System.Collections.Immutable;
-
#nullable enable
namespace Microsoft.CodeAnalysis
{
internal sealed class GeneratorSyntaxWalker : SyntaxWalker
{
- private readonly ImmutableArray _syntaxReceivers;
+ private readonly ISyntaxReceiver _syntaxReceiver;
- public GeneratorSyntaxWalker(ImmutableArray syntaxReceivers)
+ internal GeneratorSyntaxWalker(ISyntaxReceiver syntaxReceiver)
{
- _syntaxReceivers = syntaxReceivers;
+ _syntaxReceiver = syntaxReceiver;
}
public override void Visit(SyntaxNode node)
{
- foreach (var receiver in _syntaxReceivers)
- {
- receiver.OnVisitSyntaxNode(node);
- }
+ _syntaxReceiver.OnVisitSyntaxNode(node);
base.Visit(node);
}
}
-
}
diff --git a/src/Compilers/Core/Portable/SourceGeneration/RunResults.cs b/src/Compilers/Core/Portable/SourceGeneration/RunResults.cs
new file mode 100644
index 0000000000000000000000000000000000000000..ca17e69216e0faf3611fe1ec1beffe849b41dbcb
--- /dev/null
+++ b/src/Compilers/Core/Portable/SourceGeneration/RunResults.cs
@@ -0,0 +1,146 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+using Microsoft.CodeAnalysis.Text;
+
+#nullable enable
+
+namespace Microsoft.CodeAnalysis
+{
+ ///
+ /// Represents the results of running a generation pass over a set of s.
+ ///
+ public class GeneratorDriverRunResult
+ {
+ private ImmutableArray _lazyDiagnostics;
+
+ private ImmutableArray _lazyGeneratedTrees;
+
+ internal GeneratorDriverRunResult(ImmutableArray results)
+ {
+ this.Results = results;
+ }
+
+ ///
+ /// The individual result of each that was run in this generator pass, one per generator.
+ ///
+ public ImmutableArray Results { get; }
+
+ ///
+ /// The s produced by all generators run during this generation pass.
+ ///
+ ///
+ /// This is equivalent to the union of all in .
+ ///
+ public ImmutableArray Diagnostics
+ {
+ get
+ {
+ if (_lazyDiagnostics.IsDefault)
+ {
+ ImmutableInterlocked.InterlockedInitialize(ref _lazyDiagnostics, Results.SelectMany(r => r.Diagnostics).ToImmutableArray());
+ }
+ return _lazyDiagnostics;
+ }
+ }
+
+ ///
+ /// The s produced during this generation pass by parsing each added by each generator.
+ ///
+ ///
+ /// This is equivalent to the union of all s in each in each
+ ///
+ public ImmutableArray GeneratedTrees
+ {
+ get
+ {
+ if (_lazyGeneratedTrees.IsDefault)
+ {
+ ImmutableInterlocked.InterlockedInitialize(ref _lazyGeneratedTrees, Results.SelectMany(r => r.GeneratedSources.Select(g => g.SyntaxTree)).ToImmutableArray());
+ }
+ return _lazyGeneratedTrees;
+ }
+ }
+ }
+
+ ///
+ /// Represents the results of a single generation pass.
+ ///
+ public readonly struct GeneratorRunResult
+ {
+ internal GeneratorRunResult(ISourceGenerator generator, ImmutableArray generatedSources, ImmutableArray diagnostics, Exception? exception)
+ {
+ Debug.Assert(exception is null || (generatedSources.IsEmpty && diagnostics.Length == 1));
+
+ this.Generator = generator;
+ this.GeneratedSources = generatedSources;
+ this.Diagnostics = diagnostics;
+ this.Exception = exception;
+ }
+
+ ///
+ /// The that produced this result.
+ ///
+ public ISourceGenerator Generator { get; }
+
+ ///
+ /// The sources that were added by during the generation pass this result represents.
+ ///
+ public ImmutableArray GeneratedSources { get; }
+
+ ///
+ /// A collection of s reported by
+ ///
+ ///
+ /// When generation fails due to an being thrown, a single diagnostic is added
+ /// to represent the failure. Any generator reported diagnostics up to the failure point are not included.
+ ///
+ public ImmutableArray Diagnostics { get; }
+
+ ///
+ /// An instance that was thrown by the generator, or null if the generator completed without error.
+ ///
+ ///
+ /// When this property has a value, property is guaranteed to be empty, and the
+ /// collection will contain a single diagnostic indicating that the generator failed.
+ ///
+ public Exception? Exception { get; }
+ }
+
+ ///
+ /// Represents the results of an calling .
+ ///
+ ///
+ /// This contains the original added by the generator, along with the parsed representation of that text in .
+ ///
+ public readonly struct GeneratedSourceResult
+ {
+ internal GeneratedSourceResult(SyntaxTree tree, SourceText text, string hintName)
+ {
+ this.SyntaxTree = tree;
+ this.SourceText = text;
+ this.HintName = hintName;
+ }
+
+ ///
+ /// The that was produced from parsing the .
+ ///
+ public SyntaxTree SyntaxTree { get; }
+
+ ///
+ /// The that was added by the generator.
+ ///
+ public SourceText SourceText { get; }
+
+ ///
+ /// An identifier provided by the generator that identifies the added .
+ ///
+ public string HintName { get; }
+ }
+}
diff --git a/src/Compilers/Core/Portable/SourceGeneration/SourceGeneratorContext.cs b/src/Compilers/Core/Portable/SourceGeneration/SourceGeneratorContext.cs
index 3a23d44933141117e1d30eacaf85b668d73ee068..e962d90211454b832610ccf58e73e5e99f264880 100644
--- a/src/Compilers/Core/Portable/SourceGeneration/SourceGeneratorContext.cs
+++ b/src/Compilers/Core/Portable/SourceGeneration/SourceGeneratorContext.cs
@@ -18,15 +18,17 @@ namespace Microsoft.CodeAnalysis
{
private readonly DiagnosticBag _diagnostics;
- internal SourceGeneratorContext(Compilation compilation, ImmutableArray additionalTexts, AnalyzerConfigOptionsProvider optionsProvider, ISyntaxReceiver? syntaxReceiver, DiagnosticBag diagnostics, CancellationToken cancellationToken = default)
+ private readonly AdditionalSourcesCollection _additionalSources;
+
+ internal SourceGeneratorContext(Compilation compilation, ImmutableArray additionalTexts, AnalyzerConfigOptionsProvider optionsProvider, ISyntaxReceiver? syntaxReceiver, CancellationToken cancellationToken = default)
{
Compilation = compilation;
AdditionalFiles = additionalTexts;
AnalyzerConfigOptions = optionsProvider;
SyntaxReceiver = syntaxReceiver;
CancellationToken = cancellationToken;
- AdditionalSources = new AdditionalSourcesCollection();
- _diagnostics = diagnostics;
+ _additionalSources = new AdditionalSourcesCollection();
+ _diagnostics = new DiagnosticBag();
}
///
@@ -59,14 +61,12 @@ internal SourceGeneratorContext(Compilation compilation, ImmutableArray
public CancellationToken CancellationToken { get; }
- internal AdditionalSourcesCollection AdditionalSources { get; }
-
///
/// Adds a to the compilation
///
/// An identifier that can be used to reference this source text, must be unique within this generator
/// The to add to the compilation
- public void AddSource(string hintName, SourceText sourceText) => AdditionalSources.Add(hintName, sourceText);
+ public void AddSource(string hintName, SourceText sourceText) => _additionalSources.Add(hintName, sourceText);
///
/// Adds a to the users compilation
@@ -76,6 +76,9 @@ internal SourceGeneratorContext(Compilation compilation, ImmutableArray settings.
///
public void ReportDiagnostic(Diagnostic diagnostic) => _diagnostics.Add(diagnostic);
+
+ internal (ImmutableArray sources, ImmutableArray diagnostics) ToImmutableAndFree()
+ => (_additionalSources.ToImmutableAndFree(), _diagnostics.ToReadOnlyAndFree());
}
///
diff --git a/src/Test/Utilities/Portable/SourceGeneration/TestGenerators.cs b/src/Test/Utilities/Portable/SourceGeneration/TestGenerators.cs
index 3f8a1282a6f6f41076e71c5f79aff522a45393eb..d7f8b65d098fbe55d5de1dbd1c02e1cfeb66e5f6 100644
--- a/src/Test/Utilities/Portable/SourceGeneration/TestGenerators.cs
+++ b/src/Test/Utilities/Portable/SourceGeneration/TestGenerators.cs
@@ -26,7 +26,7 @@ public SingleFileTestGenerator(string content, string hintName = "generatedFile"
public void Execute(SourceGeneratorContext context)
{
- context.AdditionalSources.Add(this._hintName, SourceText.From(_content, Encoding.UTF8));
+ context.AddSource(this._hintName, SourceText.From(_content, Encoding.UTF8));
}
public void Initialize(InitializationContext context)
@@ -59,7 +59,7 @@ public void Execute(SourceGeneratorContext context)
_onExecute(context);
if (!string.IsNullOrWhiteSpace(_source))
{
- context.AdditionalSources.Add("source.cs", SourceText.From(_source, Encoding.UTF8));
+ context.AddSource("source.cs", SourceText.From(_source, Encoding.UTF8));
}
}
public void Initialize(InitializationContext context) => _onInit(context);
@@ -80,7 +80,7 @@ public void Execute(SourceGeneratorContext context)
{
foreach (var file in context.AdditionalFiles)
{
- AddSourceForAdditionalFile(context.AdditionalSources, file);
+ context.AddSource(GetGeneratedFileName(file.Path), SourceText.From("", Encoding.UTF8));
}
}
@@ -93,14 +93,12 @@ bool UpdateContext(EditContext context, AdditionalFileEdit edit)
{
if (edit is AdditionalFileAddedEdit add && CanApplyChanges)
{
- AddSourceForAdditionalFile(context.AdditionalSources, add.AddedText);
+ context.AdditionalSources.Add(GetGeneratedFileName(add.AddedText.Path), SourceText.From("", Encoding.UTF8));
return true;
}
return false;
}
- private void AddSourceForAdditionalFile(AdditionalSourcesCollection sources, AdditionalText file) => sources.Add(GetGeneratedFileName(file.Path), SourceText.From("", Encoding.UTF8));
-
private string GetGeneratedFileName(string path) => $"{Path.GetFileNameWithoutExtension(path.Replace('\\', Path.DirectorySeparatorChar))}.generated";
}