diff --git a/src/Compilers/CSharp/Test/Semantic/SourceGeneration/GeneratorDriverTests.cs b/src/Compilers/CSharp/Test/Semantic/SourceGeneration/GeneratorDriverTests.cs index a9cd72a3c0984..40b4fddff6129 100644 --- a/src/Compilers/CSharp/Test/Semantic/SourceGeneration/GeneratorDriverTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/SourceGeneration/GeneratorDriverTests.cs @@ -3149,5 +3149,52 @@ public void Returning_Null_From_SelectMany_Gives_Empty_Array() Assert.Empty(result.GeneratedSources); Assert.Empty(result.Diagnostics); } + + [Fact] + public void Post_Init_Trees_Are_Reparsed_When_ParseOptions_Change() + { + var source = "class C{}"; + var postInitSource = @" +#pragma warning disable CS0169 +class D { (int, bool) _field; }"; + + var parseOptions = TestOptions.RegularPreview; + Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions); + compilation.VerifyDiagnostics(); + + var generator = new PipelineCallbackGenerator(ctx => + { + ctx.RegisterPostInitializationOutput(c => c.AddSource("D", postInitSource)); + }).AsSourceGenerator(); + + GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { generator }, parseOptions: parseOptions, driverOptions: new GeneratorDriverOptions(IncrementalGeneratorOutputKind.None, trackIncrementalGeneratorSteps: true)); + driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out compilation, out var diagnostics); + compilation.VerifyDiagnostics(); + Assert.Empty(diagnostics); + + // change the parse options so that the tree is no longer accepted + parseOptions = parseOptions.WithLanguageVersion(LanguageVersion.CSharp2); + compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions); + driver = driver.WithUpdatedParseOptions(parseOptions); + + // change some other options to ensure the parseOption change tracking flows correctly + driver = driver.AddAdditionalTexts(ImmutableArray.Empty); + + driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out compilation, out diagnostics); + diagnostics.Verify(); + compilation.VerifyDiagnostics( + // Microsoft.CodeAnalysis.Test.Utilities\Roslyn.Test.Utilities.TestGenerators.PipelineCallbackGenerator\D.cs(3,12): error CS8022: Feature 'tuples' is not available in C# 2. Please use language version 7.0 or greater. + // class D { (int, bool) _field; } + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion2, "(int, bool)").WithArguments("tuples", "7.0").WithLocation(3, 12) + ); + + // change them back to something where it is supported + parseOptions = parseOptions.WithLanguageVersion(LanguageVersion.CSharp8); + compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions); + driver = driver.WithUpdatedParseOptions(parseOptions); + driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out compilation, out diagnostics); + diagnostics.Verify(); + compilation.VerifyDiagnostics(); + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/SourceGeneration/StateTableTests.cs b/src/Compilers/CSharp/Test/Semantic/SourceGeneration/StateTableTests.cs index b474ad41ba4de..c00434e9c15b7 100644 --- a/src/Compilers/CSharp/Test/Semantic/SourceGeneration/StateTableTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/SourceGeneration/StateTableTests.cs @@ -1021,7 +1021,8 @@ private DriverStateTable.Builder GetBuilder(DriverStateTable previous, bool trac SyntaxStore.Empty, disabledOutputs: IncrementalGeneratorOutputKind.None, runtime: TimeSpan.Zero, - trackIncrementalGeneratorSteps: trackIncrementalGeneratorSteps); + trackIncrementalGeneratorSteps: trackIncrementalGeneratorSteps, + parseOptionsChanged: false); return new DriverStateTable.Builder(c, state, SyntaxStore.Empty.ToBuilder(c, ImmutableArray.Empty, trackIncrementalGeneratorSteps, cancellationToken: default)); } diff --git a/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriver.cs b/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriver.cs index fd97951e0e3cf..aff21470866e5 100644 --- a/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriver.cs +++ b/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriver.cs @@ -37,7 +37,7 @@ internal GeneratorDriver(GeneratorDriverState state) internal GeneratorDriver(ParseOptions parseOptions, ImmutableArray generators, AnalyzerConfigOptionsProvider optionsProvider, ImmutableArray 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.DisabledOutputs, runtime: TimeSpan.Zero, driverOptions.TrackIncrementalGeneratorSteps); + _state = new GeneratorDriverState(parseOptions, optionsProvider, generators, incrementalGenerators, additionalTexts, ImmutableArray.Create(new GeneratorState[generators.Length]), DriverStateTable.Empty, SyntaxStore.Empty, driverOptions.DisabledOutputs, runtime: TimeSpan.Zero, driverOptions.TrackIncrementalGeneratorSteps, parseOptionsChanged: true); } public GeneratorDriver RunGenerators(Compilation compilation, CancellationToken cancellationToken = default) @@ -145,7 +145,7 @@ public GeneratorDriver ReplaceAdditionalText(AdditionalText oldText, AdditionalT public GeneratorDriver ReplaceAdditionalTexts(ImmutableArray newTexts) => FromState(_state.With(additionalTexts: newTexts)); public GeneratorDriver WithUpdatedParseOptions(ParseOptions newOptions) => newOptions is object - ? FromState(_state.With(parseOptions: newOptions)) + ? FromState(_state.With(parseOptions: newOptions, parseOptionsChanged: true)) : throw new ArgumentNullException(nameof(newOptions)); public GeneratorDriver WithUpdatedAnalyzerConfigOptions(AnalyzerConfigOptionsProvider newOptions) => newOptions is object @@ -248,6 +248,12 @@ internal GeneratorDriverState RunGeneratorsCore(Compilation compilation, Diagnos ? new GeneratorState(postInitSources, inputNodes, outputNodes) : SetGeneratorException(MessageProvider, GeneratorState.Empty, sourceGenerator, ex, diagnosticsBag, isInit: true); } + else if (state.ParseOptionsChanged && generatorState.PostInitTrees.Length > 0) + { + // the generator is initalized, but we need to reparse the post-init trees as the parse options have changed + var reparsedInitSources = ParseAdditionalSources(sourceGenerator, generatorState.PostInitTrees.SelectAsArray(t => new GeneratedSourceText(t.HintName, t.Text)), cancellationToken); + generatorState = new GeneratorState(reparsedInitSources, generatorState.InputNodes, generatorState.OutputNodes); + } // if the pipeline registered any syntax input nodes, record them if (!generatorState.InputNodes.IsEmpty) @@ -298,7 +304,7 @@ internal GeneratorDriverState RunGeneratorsCore(Compilation compilation, Diagnos } } - state = state.With(stateTable: driverStateBuilder.ToImmutable(), syntaxStore: syntaxStoreBuilder.ToImmutable(), generatorStates: stateBuilder.ToImmutableAndFree(), runTime: timer.Elapsed); + state = state.With(stateTable: driverStateBuilder.ToImmutable(), syntaxStore: syntaxStoreBuilder.ToImmutable(), generatorStates: stateBuilder.ToImmutableAndFree(), runTime: timer.Elapsed, parseOptionsChanged: false); return state; } diff --git a/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriverState.cs b/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriverState.cs index dc77d95c9d0b0..3cc44d2edaf5c 100644 --- a/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriverState.cs +++ b/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriverState.cs @@ -21,7 +21,8 @@ internal GeneratorDriverState(ParseOptions parseOptions, SyntaxStore syntaxStore, IncrementalGeneratorOutputKind disabledOutputs, TimeSpan runtime, - bool trackIncrementalGeneratorSteps) + bool trackIncrementalGeneratorSteps, + bool parseOptionsChanged) { Generators = sourceGenerators; IncrementalGenerators = incrementalGenerators; @@ -34,6 +35,7 @@ internal GeneratorDriverState(ParseOptions parseOptions, DisabledOutputs = disabledOutputs; RunTime = runtime; TrackIncrementalSteps = trackIncrementalGeneratorSteps; + ParseOptionsChanged = parseOptionsChanged; Debug.Assert(Generators.Length == GeneratorStates.Length); Debug.Assert(IncrementalGenerators.Length == GeneratorStates.Length); } @@ -93,6 +95,11 @@ internal GeneratorDriverState(ParseOptions parseOptions, internal readonly bool TrackIncrementalSteps; + /// + /// Tracks if the have been changed meaning post init trees will need to be re-parsed. + /// + internal readonly bool ParseOptionsChanged; + internal GeneratorDriverState With( ImmutableArray? sourceGenerators = null, ImmutableArray? incrementalGenerators = null, @@ -103,7 +110,8 @@ internal GeneratorDriverState With( ParseOptions? parseOptions = null, AnalyzerConfigOptionsProvider? optionsProvider = null, IncrementalGeneratorOutputKind? disabledOutputs = null, - TimeSpan? runTime = null) + TimeSpan? runTime = null, + bool? parseOptionsChanged = null) { return new GeneratorDriverState( parseOptions ?? this.ParseOptions, @@ -116,7 +124,8 @@ internal GeneratorDriverState With( syntaxStore ?? this.SyntaxStore, disabledOutputs ?? this.DisabledOutputs, runTime ?? this.RunTime, - this.TrackIncrementalSteps + this.TrackIncrementalSteps, + parseOptionsChanged ?? this.ParseOptionsChanged ); } }