File: SourceGeneration\GeneratorDriver.cs
Web Access
Project: src\roslyn\src\Compilers\Core\Portable\Microsoft.CodeAnalysis.csproj (Microsoft.CodeAnalysis)
// 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,
    }
}