|
// 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.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.SourceGeneration;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis
{
/// <summary>
/// Responsible for orchestrating a source generation pass
/// </summary>
/// <remarks>
/// GeneratorDriver is an immutable class that can be manipulated by returning a mutated copy of itself.
/// In the compiler we only ever create a single instance and ignore the mutated copy. The IDE may perform
/// multiple edits, or generation passes of the same driver, re-using the state as needed.
/// </remarks>
public abstract class GeneratorDriver
{
internal readonly GeneratorDriverState _state;
internal GeneratorDriver(GeneratorDriverState state)
{
Debug.Assert(state.Generators.GroupBy(s => s.GetGeneratorType()).Count() == state.Generators.Length); // ensure we don't have duplicate generator types
_state = 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, compilationCache: CompilationCache.Empty);
}
/// <summary>
/// Run generators and produce an updated <see cref="GeneratorDriver"/> containing the results.
/// </summary>
/// <param name="compilation">The compilation to run generators against</param>
/// <returns>An updated driver that contains the results of the generators running.</returns>
public GeneratorDriver RunGenerators(Compilation compilation) => RunGenerators(compilation, generatorFilter: null, cancellationToken: default);
// 4.11 BACKCOMPAT OVERLOAD -- DO NOT TOUCH
public GeneratorDriver RunGenerators(Compilation compilation, CancellationToken cancellationToken) => RunGenerators(compilation, generatorFilter: null, cancellationToken);
/// <summary>
/// Run generators and produce an updated <see cref="GeneratorDriver"/> containing the results.
/// </summary>
/// <param name="compilation">The compilation to run generators against</param>
/// <param name="generatorFilter">A filter that specifies which generators to run. If <c>null</c> all generators will run.</param>
/// <param name="cancellationToken">Used to cancel an in progress operation.</param>
/// <returns>An updated driver that contains the results of the generators running.</returns>
public GeneratorDriver RunGenerators(Compilation compilation, Func<GeneratorFilterContext, bool>? generatorFilter, CancellationToken cancellationToken = default)
{
var state = RunGeneratorsCore(compilation, diagnosticsBag: null, generatorFilter, cancellationToken);
return FromState(state);
}
public GeneratorDriver RunGeneratorsAndUpdateCompilation(Compilation compilation, out Compilation outputCompilation, out ImmutableArray<Diagnostic> diagnostics, CancellationToken cancellationToken = default)
{
var diagnosticsBag = DiagnosticBag.GetInstance();
ArrayBuilder<SyntaxTree>? trees = null;
try
{
var state = RunGeneratorsCore(compilation, diagnosticsBag, generatorFilter: null, cancellationToken);
// build the output compilation
diagnostics = diagnosticsBag.ToReadOnlyAndFree();
diagnosticsBag = null;
trees = ArrayBuilder<SyntaxTree>.GetInstance();
foreach (var generatorState in state.GeneratorStates)
{
trees.AddRange(generatorState.PostInitTrees.Select(t => t.Tree));
trees.AddRange(generatorState.PreCompilationTrees.Select(t => t.Tree));
trees.AddRange(generatorState.GeneratedTrees.Select(t => t.Tree));
}
outputCompilation = compilation.AddSyntaxTrees(trees);
return FromState(state);
}
finally
{
diagnosticsBag?.Free();
trees?.Free();
}
}
public GeneratorDriver AddGenerators(ImmutableArray<ISourceGenerator> generators)
{
var incrementalGenerators = GetIncrementalGenerators(generators, SourceExtension);
var newState = _state.With(sourceGenerators: _state.Generators.AddRange(generators),
incrementalGenerators: _state.IncrementalGenerators.AddRange(incrementalGenerators),
generatorStates: _state.GeneratorStates.AddRange(new GeneratorState[generators.Length]));
return FromState(newState);
}
public GeneratorDriver ReplaceGenerators(ImmutableArray<ISourceGenerator> generators)
{
var incrementalGenerators = GetIncrementalGenerators(generators, SourceExtension);
var states = ArrayBuilder<GeneratorState>.GetInstance(generators.Length);
foreach (var generator in generators)
{
var existingIndex = _state.Generators.IndexOf(generator);
if (existingIndex >= 0)
{
states.Add(_state.GeneratorStates[existingIndex]);
}
else
{
states.Add(default);
}
}
return FromState(_state.With(generators, incrementalGenerators, states.ToImmutableAndFree()));
}
public GeneratorDriver RemoveGenerators(ImmutableArray<ISourceGenerator> generators)
{
var newGenerators = _state.Generators;
var newStates = _state.GeneratorStates;
var newIncrementalGenerators = _state.IncrementalGenerators;
for (int i = 0; i < newGenerators.Length; i++)
{
if (generators.Contains(newGenerators[i]))
{
newGenerators = newGenerators.RemoveAt(i);
newStates = newStates.RemoveAt(i);
newIncrementalGenerators = newIncrementalGenerators.RemoveAt(i);
i--;
}
}
return FromState(_state.With(sourceGenerators: newGenerators, incrementalGenerators: newIncrementalGenerators, generatorStates: newStates));
}
public GeneratorDriver AddAdditionalTexts(ImmutableArray<AdditionalText> additionalTexts)
{
var newState = _state.With(additionalTexts: _state.AdditionalTexts.AddRange(additionalTexts));
return FromState(newState);
}
public GeneratorDriver RemoveAdditionalTexts(ImmutableArray<AdditionalText> additionalTexts)
{
var newState = _state.With(additionalTexts: _state.AdditionalTexts.RemoveRange(additionalTexts));
return FromState(newState);
}
public GeneratorDriver ReplaceAdditionalText(AdditionalText oldText, AdditionalText newText)
{
if (oldText is null)
{
throw new ArgumentNullException(nameof(oldText));
}
if (newText is null)
{
throw new ArgumentNullException(nameof(newText));
}
var newState = _state.With(additionalTexts: _state.AdditionalTexts.Replace(oldText, newText));
return FromState(newState);
}
public GeneratorDriver ReplaceAdditionalTexts(ImmutableArray<AdditionalText> newTexts) => FromState(_state.With(additionalTexts: newTexts));
public GeneratorDriver WithUpdatedParseOptions(ParseOptions newOptions) => newOptions is object
? FromState(_state.With(parseOptions: newOptions))
: throw new ArgumentNullException(nameof(newOptions));
public GeneratorDriver WithUpdatedAnalyzerConfigOptions(AnalyzerConfigOptionsProvider newOptions) => newOptions is object
? FromState(_state.With(optionsProvider: newOptions))
: throw new ArgumentNullException(nameof(newOptions));
public GeneratorDriverRunResult GetRunResult()
{
var results = _state.Generators.ZipAsArray(
_state.GeneratorStates,
(generator, generatorState)
=> new GeneratorRunResult(generator,
diagnostics: generatorState.Diagnostics,
exception: generatorState.Exception,
generatedSources: getGeneratorSources(generatorState),
elapsedTime: generatorState.ElapsedTime,
namedSteps: generatorState.ExecutedSteps,
outputSteps: generatorState.OutputSteps,
hostOutputs: generatorState.HostOutputs));
return new GeneratorDriverRunResult(results, _state.RunTime);
static ImmutableArray<GeneratedSourceResult> getGeneratorSources(GeneratorState generatorState)
{
if (!generatorState.Initialized)
{
return default;
}
ArrayBuilder<GeneratedSourceResult> sources = ArrayBuilder<GeneratedSourceResult>.GetInstance(
generatorState.PostInitTrees.Length +
generatorState.PreCompilationTrees.Length +
generatorState.GeneratedTrees.Length);
foreach (var tree in generatorState.PostInitTrees)
{
sources.Add(new GeneratedSourceResult(tree.Tree, tree.Text, tree.HintName));
}
foreach (var tree in generatorState.PreCompilationTrees)
{
sources.Add(new GeneratedSourceResult(tree.Tree, tree.Text, tree.HintName));
}
foreach (var tree in generatorState.GeneratedTrees)
{
sources.Add(new GeneratedSourceResult(tree.Tree, tree.Text, tree.HintName));
}
return sources.ToImmutableAndFree();
}
}
public GeneratorDriverTimingInfo GetTimingInfo()
{
var generatorTimings = _state.Generators.ZipAsArray(_state.GeneratorStates, (generator, generatorState) => new GeneratorTimingInfo(generator, generatorState.ElapsedTime));
return new GeneratorDriverTimingInfo(_state.RunTime, generatorTimings);
}
internal GeneratorDriverState RunGeneratorsCore(Compilation compilation, DiagnosticBag? diagnosticsBag, Func<GeneratorFilterContext, bool>? generatorFilter = null, CancellationToken cancellationToken = default)
{
// with no generators, there is no work to do
if (_state.Generators.IsEmpty)
{
return _state.With(stateTable: DriverStateTable.Empty, runTime: TimeSpan.Zero);
}
// The input compilation reference is used as part of the compilation cache key
// below. We capture it before any driver-side AddSyntaxTrees.
var inputCompilation = compilation;
// run the actual generation
using var timer = CodeAnalysisEventSource.Log.CreateGeneratorDriverRunTimer();
var state = _state;
var stateBuilder = ArrayBuilder<GeneratorState>.GetInstance(state.Generators.Length);
var constantSourcesBuilder = ArrayBuilder<SyntaxTree>.GetInstance();
var syntaxInputNodes = ArrayBuilder<SyntaxInputNode>.GetInstance();
try
{
for (int i = 0; i < state.IncrementalGenerators.Length; i++)
{
var generator = state.IncrementalGenerators[i];
var generatorState = state.GeneratorStates[i];
var sourceGenerator = state.Generators[i];
if (shouldSkipGenerator(sourceGenerator))
{
stateBuilder.Add(generatorState);
continue;
}
// initialize the generator if needed
if (!generatorState.Initialized)
{
var outputBuilder = ArrayBuilder<IIncrementalGeneratorOutputNode>.GetInstance();
var inputBuilder = ArrayBuilder<SyntaxInputNode>.GetInstance();
var postInitSources = ImmutableArray<GeneratedSyntaxTree>.Empty;
var pipelineContext = new IncrementalGeneratorInitializationContext(
inputBuilder, outputBuilder, this.SyntaxHelper, this.SourceExtension, this.EmbeddedAttributeDefinition, compilation.CatchAnalyzerExceptions);
Exception? ex = null;
try
{
generator.Initialize(pipelineContext);
}
catch (Exception e) when (handleGeneratorException(compilation, MessageProvider, sourceGenerator, e, isInit: true))
{
ex = e;
}
var outputNodes = outputBuilder.ToImmutableAndFree();
var inputNodes = inputBuilder.ToImmutableAndFree();
// run post init
if (ex is null)
{
try
{
IncrementalExecutionContext context = UpdateOutputs(outputNodes, IncrementalGeneratorOutputKind.PostInit, new GeneratorRunStateTable.Builder(false), ImmutableHashSet<string>.Empty, cancellationToken);
postInitSources = ParseAdditionalSources(sourceGenerator, context.ToImmutableAndFree().sources, cancellationToken);
}
catch (UserFunctionException e) when (handleGeneratorException(compilation, MessageProvider, sourceGenerator, e, isInit: true))
{
ex = e.InnerException;
}
}
generatorState = ex is null
? new GeneratorState(postInitSources, inputNodes, outputNodes)
: SetGeneratorException(compilation, MessageProvider, GeneratorState.Empty, sourceGenerator, ex, diagnosticsBag, phase: GeneratorRunPhase.Init, runTime: null, cancellationToken);
}
else if (generatorState.RequiresInputTreeReparse(state.ParseOptions))
{
// the generator is initialized, but we need to reparse constant trees as the parse options have changed
var reparsedInitSources = generatorState.PostInitTrees.Length > 0
? ParseAdditionalSources(sourceGenerator, generatorState.PostInitTrees.SelectAsArray(t => new GeneratedSourceText(t.HintName, t.Text)), cancellationToken)
: generatorState.PostInitTrees;
var reparsedPreCompilationSources = generatorState.PreCompilationTrees.Length > 0
? ParseAdditionalSources(sourceGenerator, generatorState.PreCompilationTrees.SelectAsArray(t => new GeneratedSourceText(t.HintName, t.Text)), cancellationToken)
: generatorState.PreCompilationTrees;
generatorState = new GeneratorState(reparsedInitSources, generatorState.InputNodes, generatorState.OutputNodes, reparsedPreCompilationSources);
}
// if the pipeline registered any syntax input nodes, record them
if (!generatorState.InputNodes.IsEmpty)
{
syntaxInputNodes.AddRange(generatorState.InputNodes);
}
// record any constant sources
if (generatorState.PostInitTrees.Length > 0)
{
constantSourcesBuilder.AddRange(generatorState.PostInitTrees.Select(t => t.Tree));
}
stateBuilder.Add(generatorState);
}
// update the compilation with any constant sources
if (constantSourcesBuilder.Count > 0)
{
compilation = compilation.AddSyntaxTrees(constantSourcesBuilder);
}
constantSourcesBuilder.Free();
constantSourcesBuilder = null;
var driverStateBuilder = new DriverStateTable.Builder(_state, compilation, syntaxInputNodes.ToImmutableAndFree(), cancellationToken);
syntaxInputNodes = null;
// Pre-compilation pass: evaluate pre-compilation output nodes for all generators
// and add their sources to the compilation before standard output nodes execute.
// Pre-compilation nodes only depend on non-compilation inputs (AdditionalTexts, ParseOptions, etc.)
// so they cannot access the compilation or syntax store — doing so will throw.
// Create per-generator step tracking builders that will be shared across both passes
var generatorRunStateBuilders = new GeneratorRunStateTable.Builder[state.IncrementalGenerators.Length];
for (int i = 0; i < state.IncrementalGenerators.Length; i++)
{
generatorRunStateBuilders[i] = new GeneratorRunStateTable.Builder(state.TrackIncrementalSteps);
}
for (int i = 0; i < state.IncrementalGenerators.Length; i++)
{
var generatorState = stateBuilder[i];
if (shouldSkipGenerator(state.Generators[i]) || generatorState.OutputNodes.Length == 0)
{
continue;
}
try
{
var preCompReserved = collectHintNames(generatorState.PostInitTrees);
var preCompilationContext = UpdateOutputs(generatorState.OutputNodes, IncrementalGeneratorOutputKind.PreCompilation, generatorRunStateBuilders[i], preCompReserved, cancellationToken, driverStateBuilder);
var (sources, _, _, _) = preCompilationContext.ToImmutableAndFree();
var parsedSources = ReuseOrParsePreCompilationSources(state.Generators[i], sources, generatorState.PreCompilationTrees, cancellationToken);
stateBuilder[i] = generatorState.WithPreCompilationTrees(parsedSources);
}
catch (UserFunctionException ufe) when (handleGeneratorException(compilation, MessageProvider, state.Generators[i], ufe.InnerException, isInit: false))
{
stateBuilder[i] = SetGeneratorException(compilation, MessageProvider, generatorState, state.Generators[i], ufe.InnerException, diagnosticsBag, phase: GeneratorRunPhase.PreCompilation, runTime: null, cancellationToken);
}
}
// Accumulate the inputs that determine the compilation seen by standard-phase
// generators, then ask the cache whether to reuse the previous run's compilation
// reference or build a new one. Filtered generators don't run their pre-comp
// callback this pass, but their state still carries the previously collected
// PreCompilationTrees -- feeding those (and the just-produced trees from unfiltered
// generators) into the cache keeps the resulting compilation stable across runs.
var cacheBuilder = state.CompilationCache.ToBuilder(inputCompilation, compilation);
for (int i = 0; i < state.IncrementalGenerators.Length; i++)
{
var generatorState = stateBuilder[i];
if (!generatorState.PostInitTrees.IsDefaultOrEmpty)
{
foreach (var tree in generatorState.PostInitTrees)
{
cacheBuilder.AddPostInitTree(tree.Tree);
}
}
if (!generatorState.PreCompilationTrees.IsDefaultOrEmpty)
{
foreach (var tree in generatorState.PreCompilationTrees)
{
cacheBuilder.AddPreCompTree(i, tree);
}
}
}
state = state.With(compilationCache: cacheBuilder.ToImmutableAndFree());
compilation = state.CompilationCache.Compilation;
driverStateBuilder.SetCompilation(compilation);
for (int i = 0; i < state.IncrementalGenerators.Length; i++)
{
var generatorState = stateBuilder[i];
if (shouldSkipGenerator(state.Generators[i]) || generatorState.OutputNodes.Length == 0 || generatorState.PreCompilationFailed)
{
continue;
}
using var generatorTimer = CodeAnalysisEventSource.Log.CreateSingleGeneratorRunTimer(state.Generators[i], (t) => t.Add(driverStateBuilder.SyntaxStore.GetRuntimeAdjustment(stateBuilder[i].InputNodes)));
try
{
// Reserve hint names from prior phases (PostInit and PreCompilation) so that
// standard-phase outputs cannot collide with them. Hint names must be unique
// across all phases for a single generator.
var standardReserved = collectHintNames(generatorState.PostInitTrees, generatorState.PreCompilationTrees);
// We do not support incremental step tracking for v1 generators, as the pipeline is implicitly defined.
var context = UpdateOutputs(generatorState.OutputNodes, IncrementalGeneratorOutputKind.Source | IncrementalGeneratorOutputKind.Implementation | IncrementalGeneratorOutputKind.Host, generatorRunStateBuilders[i], standardReserved, cancellationToken, driverStateBuilder);
(var sources, var generatorDiagnostics, var generatorRunStateTable, var hostOutputs) = context.ToImmutableAndFree();
generatorDiagnostics = FilterDiagnostics(compilation, generatorDiagnostics, driverDiagnostics: diagnosticsBag, cancellationToken);
stateBuilder[i] = generatorState.WithResults(ParseAdditionalSources(state.Generators[i], sources, cancellationToken), generatorDiagnostics, generatorRunStateTable.ExecutedSteps, generatorRunStateTable.OutputSteps, hostOutputs, generatorTimer.Elapsed);
}
catch (UserFunctionException ufe) when (handleGeneratorException(compilation, MessageProvider, state.Generators[i], ufe.InnerException, isInit: false))
{
stateBuilder[i] = SetGeneratorException(compilation, MessageProvider, generatorState, state.Generators[i], ufe.InnerException, diagnosticsBag, phase: GeneratorRunPhase.Standard, runTime: generatorTimer.Elapsed, cancellationToken);
}
}
state = state.With(stateTable: driverStateBuilder.ToImmutable(), syntaxStore: driverStateBuilder.SyntaxStore.ToImmutable(), generatorStates: stateBuilder.ToImmutableAndFree(), runTime: timer.Elapsed);
stateBuilder = null;
return state;
}
finally
{
stateBuilder?.Free();
constantSourcesBuilder?.Free();
syntaxInputNodes?.Free();
}
static bool handleGeneratorException(Compilation compilation, CommonMessageProvider messageProvider, ISourceGenerator sourceGenerator, Exception e, bool isInit)
{
if (!compilation.CatchAnalyzerExceptions)
{
Debug.Assert(false);
Environment.FailFast(CreateGeneratorExceptionDiagnostic(messageProvider, sourceGenerator, e, isInit).ToString());
return false;
}
return true;
}
bool shouldSkipGenerator(ISourceGenerator generator) => generatorFilter?.Invoke(new GeneratorFilterContext(generator, cancellationToken)) == false;
static ImmutableHashSet<string> collectHintNames(ImmutableArray<GeneratedSyntaxTree> trees, ImmutableArray<GeneratedSyntaxTree> additionalTrees = default)
{
if (trees.IsEmpty && (additionalTrees.IsDefaultOrEmpty))
{
return ImmutableHashSet<string>.Empty;
}
var builder = ImmutableHashSet.CreateBuilder<string>(StringComparer.OrdinalIgnoreCase);
foreach (var tree in trees)
{
builder.Add(tree.HintName);
}
if (!additionalTrees.IsDefaultOrEmpty)
{
foreach (var tree in additionalTrees)
{
builder.Add(tree.HintName);
}
}
return builder.ToImmutable();
}
}
private IncrementalExecutionContext UpdateOutputs(ImmutableArray<IIncrementalGeneratorOutputNode> outputNodes, IncrementalGeneratorOutputKind outputKind, GeneratorRunStateTable.Builder generatorRunStateBuilder, ImmutableHashSet<string> reservedHintNames, CancellationToken cancellationToken, DriverStateTable.Builder? driverStateBuilder = null)
{
Debug.Assert(outputKind != IncrementalGeneratorOutputKind.None);
var sources = new AdditionalSourcesCollection(SourceExtension, reservedHintNames);
IncrementalExecutionContext context = new IncrementalExecutionContext(driverStateBuilder, generatorRunStateBuilder, sources);
try
{
foreach (var outputNode in outputNodes)
{
// if we're looking for this output kind, and it has not been explicitly disabled
if (outputKind.HasFlag(outputNode.Kind) && !_state.DisabledOutputs.HasFlag(outputNode.Kind))
{
outputNode.AppendOutputs(context, cancellationToken);
}
}
return context;
}
catch
{
context.Free();
throw;
}
}
private ImmutableArray<GeneratedSyntaxTree> ParseAdditionalSources(ISourceGenerator generator, ImmutableArray<GeneratedSourceText> generatedSources, CancellationToken cancellationToken)
{
var trees = ArrayBuilder<GeneratedSyntaxTree>.GetInstance(generatedSources.Length);
try
{
var prefix = GetFilePathPrefixForGenerator(this._state.BaseDirectory, generator);
foreach (var source in generatedSources)
{
var tree = ParseGeneratedSourceText(source, Path.Combine(prefix, source.HintName), cancellationToken);
trees.Add(new GeneratedSyntaxTree(source.HintName, source.Text, tree));
}
return trees.ToImmutableAndFree();
}
catch
{
trees.Free();
throw;
}
}
/// <summary>
/// Like <see cref="ParseAdditionalSources"/>, but reuses a previously-parsed
/// <see cref="GeneratedSyntaxTree"/> when the corresponding new <see cref="GeneratedSourceText"/>
/// has the same <see cref="Microsoft.CodeAnalysis.Text.SourceText"/> reference and hint name at the same position --
/// indicating the upstream pre-compilation callback was cached.
/// </summary>
/// <remarks>
/// This serves two purposes: it skips wasted re-parsing of unchanged generator output,
/// and it keeps the trees seen by the standard phase reference-stable across runs. The
/// latter matters because the compilation cache reuses the previous run's
/// <see cref="Compilation"/> (and the syntax trees it contains) on a hit; if we
/// re-parsed pre-compilation sources, a cached standard-phase output's diagnostic could
/// still hold a <see cref="Location"/> pointing at a tree that's no longer present in
/// the run's output compilation.
/// </remarks>
private ImmutableArray<GeneratedSyntaxTree> ReuseOrParsePreCompilationSources(ISourceGenerator generator, ImmutableArray<GeneratedSourceText> sources, ImmutableArray<GeneratedSyntaxTree> previousTrees, CancellationToken cancellationToken)
{
var trees = ArrayBuilder<GeneratedSyntaxTree>.GetInstance(sources.Length);
try
{
var prefix = GetFilePathPrefixForGenerator(this._state.BaseDirectory, generator);
for (int j = 0; j < sources.Length; j++)
{
var source = sources[j];
if (!previousTrees.IsDefaultOrEmpty
&& j < previousTrees.Length
&& ReferenceEquals(source.Text, previousTrees[j].Text)
&& string.Equals(source.HintName, previousTrees[j].HintName, StringComparison.OrdinalIgnoreCase))
{
trees.Add(previousTrees[j]);
}
else
{
var tree = ParseGeneratedSourceText(source, Path.Combine(prefix, source.HintName), cancellationToken);
trees.Add(new GeneratedSyntaxTree(source.HintName, source.Text, tree));
}
}
return trees.ToImmutableAndFree();
}
catch
{
trees.Free();
throw;
}
}
private static GeneratorState SetGeneratorException(Compilation compilation, CommonMessageProvider provider, GeneratorState generatorState, ISourceGenerator generator, Exception e, DiagnosticBag? diagnosticBag, GeneratorRunPhase phase, TimeSpan? runTime, CancellationToken cancellationToken)
{
if (CodeAnalysisEventSource.Log.IsEnabled())
{
CodeAnalysisEventSource.Log.GeneratorException(generator.GetGeneratorType().Name, e.ToString());
}
var diagnostic = CreateGeneratorExceptionDiagnostic(provider, generator, e, isInit: phase == GeneratorRunPhase.Init);
var filtered = compilation.Options.FilterDiagnostic(diagnostic, cancellationToken);
// Build output respects the compilation's diagnostic options: a suppressed warning isn't
// added to the driver diagnostic bag.
if (filtered is not null)
{
diagnosticBag?.Add(filtered);
}
// The per-generator run result always carries the failure diagnostic when an exception
// is recorded -- this preserves the documented invariant of GeneratorRunResult and
// ensures that a pre-compilation phase failure is recorded so the standard phase will
// skip this generator (otherwise standard-phase outputs would run with stale/missing
// pre-comp trees).
return generatorState.WithError(e, filtered ?? diagnostic, runTime ?? TimeSpan.Zero, phase);
}
private static Diagnostic CreateGeneratorExceptionDiagnostic(CommonMessageProvider provider, ISourceGenerator generator, Exception e, bool isInit)
{
var errorCode = isInit ? provider.WRN_GeneratorFailedDuringInitialization : provider.WRN_GeneratorFailedDuringGeneration;
// ISSUE: We should not call `e.CreateDiagnosticDescription()`, and instead pass formattable parts like `StackTrace`.
// ISSUE: Exceptions also don't support IFormattable, so will always be in the current UI Culture.
// ISSUE: See https://github.com/dotnet/roslyn/issues/46939
var descriptor = new DiagnosticDescriptor(
provider.GetIdForErrorCode(errorCode),
provider.GetTitle(errorCode),
provider.GetMessageFormat(errorCode),
category: "Compiler",
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true,
customTags: WellKnownDiagnosticTags.AnalyzerException);
return Diagnostic.Create(descriptor, Location.None, generator.GetGeneratorType().Name, e.GetType().Name, e.Message, e.CreateDiagnosticDescription());
}
private static ImmutableArray<Diagnostic> FilterDiagnostics(Compilation compilation, ImmutableArray<Diagnostic> generatorDiagnostics, DiagnosticBag? driverDiagnostics, CancellationToken cancellationToken)
{
if (generatorDiagnostics.IsEmpty)
{
return generatorDiagnostics;
}
var suppressMessageState = new SuppressMessageAttributeState(compilation);
ArrayBuilder<Diagnostic> filteredDiagnostics = ArrayBuilder<Diagnostic>.GetInstance();
try
{
foreach (var diag in generatorDiagnostics)
{
try
{
DiagnosticAnalysisContextHelpers.VerifyArguments(diag, compilation, isSupportedDiagnostic: static (_, _) => true, cancellationToken);
}
catch (ArgumentException ex)
{
throw new UserFunctionException(ex);
}
if (compilation.Options.FilterDiagnostic(diag, cancellationToken) is { } filtered &&
suppressMessageState.ApplySourceSuppressions(filtered) is { } effective)
{
filteredDiagnostics.Add(effective);
driverDiagnostics?.Add(effective);
}
}
return filteredDiagnostics.ToImmutableAndFree();
}
catch
{
filteredDiagnostics.Free();
throw;
}
}
internal static string GetFilePathPrefixForGenerator(string? baseDirectory, ISourceGenerator generator)
{
var type = generator.GetGeneratorType();
return Path.Combine(baseDirectory ?? "", type.Assembly.GetName().Name ?? string.Empty, type.FullName!);
}
private static ImmutableArray<IIncrementalGenerator> GetIncrementalGenerators(ImmutableArray<ISourceGenerator> generators, string sourceExtension)
{
return generators.SelectAsArray(g => g switch
{
IncrementalGeneratorWrapper igw => igw.Generator,
IIncrementalGenerator ig => ig,
_ => new SourceGeneratorAdaptor(g, sourceExtension)
});
}
internal abstract CommonMessageProvider MessageProvider { get; }
internal abstract GeneratorDriver FromState(GeneratorDriverState state);
internal abstract SyntaxTree ParseGeneratedSourceText(GeneratedSourceText input, string fileName, CancellationToken cancellationToken);
internal abstract string SourceExtension { get; }
internal abstract string EmbeddedAttributeDefinition { get; }
internal abstract ISyntaxHelper SyntaxHelper { get; }
}
internal enum GeneratorRunPhase
{
Init,
PreCompilation,
Standard,
}
}
|