Skip to content

fixed generator cache being stale on cross-file edit#71

Merged
koenbeuk merged 1 commit into
mainfrom
fix/generator-incremental-cache
Jun 4, 2026
Merged

fixed generator cache being stale on cross-file edit#71
koenbeuk merged 1 commit into
mainfrom
fix/generator-incremental-cache

Conversation

@koenbeuk
Copy link
Copy Markdown
Collaborator

@koenbeuk koenbeuk commented Jun 4, 2026

Example: long Foo(Bar t) => t.Baz keeps emitting an int→long Convert after
Bar.Baz is changed to long in another file. Fixed with this PR and simplifies future additions

Copilot AI review requested due to automatic review settings June 4, 2026 00:04
@codecov
Copy link
Copy Markdown

codecov Bot commented Jun 4, 2026

Codecov Report

❌ Patch coverage is 82.92683% with 21 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...eSharp.Generator/Infrastructure/GeneratorOutput.cs 72.72% 10 Missing and 2 partials ⚠️
...c/ExpressiveSharp.Generator/ExpressiveGenerator.cs 87.32% 3 Missing and 6 partials ⚠️

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a stale incremental-generator cache scenario where a cross-file type change could leave generated output out-of-date, by restructuring the pipeline to recompute per-compilation change and then “value-gate” downstream emission using value-equatable projections. It also introduces a regression test to ensure cross-file edits don’t serve stale output and unrelated edits don’t churn generated sources.

Changes:

  • Refactors ExpressiveGenerator (and related interpretation/emission paths) to compute member outputs per compilation change, capturing AddSource/ReportDiagnostic into a replayable GeneratorOutputContext, and gating emission via value-equatable GeneratedSource / EquatableArray<T>.
  • Updates PolyfillInterceptorGenerator and interpreter/emitter code to accept GeneratorOutputContext in shared emission paths (so they can run inside incremental Select).
  • Adds IncrementalCachingTests and removes previously-used custom comparers that could incorrectly treat cross-file changes as unchanged.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/IncrementalCachingTests.cs Adds regression coverage for cross-file edits vs incremental staleness and for unrelated edits vs churn.
src/ExpressiveSharp.Generator/ExpressiveGenerator.cs Reworks incremental pipeline to recompute on compilation changes and gate downstream emission on value-equatable projections; adds tracking name for tests.
src/ExpressiveSharp.Generator/Infrastructure/GeneratorOutput.cs Introduces GeneratorOutputContext, GeneratedSource, MemberComputation, and EquatableArray<T> to support value-gated incremental emission.
src/ExpressiveSharp.Generator/PolyfillInterceptorGenerator.cs Switches shared emit path to collect into GeneratorOutputContext and flush within RegisterSourceOutput.
src/ExpressiveSharp.Generator/Interpretation/ExpressiveInterpreter.cs Updates interpretation entrypoint to accept GeneratorOutputContext.
src/ExpressiveSharp.Generator/Interpretation/ExpressiveInterpreter.BodyProcessors.cs Threads GeneratorOutputContext through body processing/emission calls.
src/ExpressiveSharp.Generator/Interpretation/ExpressiveInterpreter.Helpers.cs Updates helper diagnostic reporting to use GeneratorOutputContext.
src/ExpressiveSharp.Generator/Interpretation/ExpressiveForInterpreter.cs Updates [ExpressiveFor] interpretation to use GeneratorOutputContext.
src/ExpressiveSharp.Generator/Interpretation/ExpressivePropertyInterpreter.cs Updates [ExpressiveProperty] interpretation to use GeneratorOutputContext.
src/ExpressiveSharp.Generator/Emitter/ExpressionTreeEmitter.cs Replaces SourceProductionContext? with GeneratorOutputContext? for emission-time diagnostics/source capture.
src/ExpressiveSharp.Generator/Emitter/SynthesizedPropertyEmitter.cs Updates emitter API to accept GeneratorOutputContext.
src/ExpressiveSharp.Generator/Comparers/MemberDeclarationSyntaxEqualityComparer.cs Removes comparer previously used for incremental caching decisions.
src/ExpressiveSharp.Generator/Comparers/MemberDeclarationSyntaxAndCompilationEqualityComparer.cs Removes comparer previously used for incremental caching decisions.
src/ExpressiveSharp.Generator/Comparers/ExpressiveForMemberCompilationEqualityComparer.cs Removes comparer previously used for incremental caching decisions in [ExpressiveFor] pipeline.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +61 to +84
var c1 = CreateCompilation(memberTree, modelsV1);
var driver = CreateDriver().RunGenerators(c1);

// Edit ONLY Models.cs (Member.cs's tree reference is preserved, so a naive comparer would
// treat this member as unchanged).
var modelsV2 = CSharpSyntaxTree.ParseText(ModelsLongVersion, path: "Models.cs");
var c2 = c1.ReplaceSyntaxTree(modelsV1, modelsV2);

driver = driver.RunGenerators(c2);
var incremental = GetMemberSourceText(driver.GetRunResult());

// Ground truth: a fresh driver on the final compilation.
var fresh = GetMemberSourceText(CreateDriver().RunGenerators(c2).GetRunResult());

TestContext.WriteLine("Incremental:\n" + incremental);
TestContext.WriteLine("Fresh:\n" + fresh);

Assert.AreEqual(fresh, incremental,
"After a cross-file type change, incremental output must match a from-scratch run (no stale cache).");
// Sanity: the change really did alter the output (int->long removes the Convert), so the test
// is actually exercising staleness rather than comparing two identical strings.
StringAssert.Contains(incremental, "long", "Expected the long-typed property to be reflected.");
Assert.IsFalse(incremental.Contains("Convert"),
"With Number typed as long, no widening Convert should remain in the generated tree.");
@koenbeuk koenbeuk merged commit 5fcdd86 into main Jun 4, 2026
18 checks passed
@koenbeuk koenbeuk deleted the fix/generator-incremental-cache branch June 4, 2026 00:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants