Skip to content

Fix incremental source generator caching with proper equality#114

Merged
jonathanpeppers merged 3 commits into
mainfrom
jonathanpeppers/fix-incremental-generator-equality
May 3, 2026
Merged

Fix incremental source generator caching with proper equality#114
jonathanpeppers merged 3 commits into
mainfrom
jonathanpeppers/fix-incremental-generator-equality

Conversation

@jonathanpeppers

Copy link
Copy Markdown
Owner

The incremental source generator was effectively non-incremental -- it regenerated all output on every build even when nothing relevant changed. The root cause was that the pipeline model types (GenerationInfo, NetworkRequest) lacked value equality, so the Roslyn incremental cache always missed (falling back to ReferenceEquals, which is always false for newly constructed objects).

Approach

Implement IEquatable<T> on both model types and switch to collection types with structural equality:

  • NetworkRequest[] -> ImmutableArray<NetworkRequest> (value equality built-in)
  • HashSet<string> -> sorted ImmutableArray<string> (deterministic ordering for stable comparison)
  • GetHashCode() uses an internal xxHash32-based HashCode polyfill ported from dotnet/runtime, since System.HashCode is not available on netstandard2.0 and adding Microsoft.Bcl.HashCode would require bundling the DLL in the analyzer package

Testing

Added 4 incremental caching tests that use trackIncrementalGeneratorSteps: true and IncrementalStepRunReason to verify:

  • Same compilation re-run -> output is Cached
  • Unrelated code change -> output is Cached
  • Attribute size changed -> output is Modified
  • Attribute element type changed -> output is Modified

All 459 tests pass.

Files changed

  • SortingNetworks.Generators/HashCode.cs -- New internal xxHash32 polyfill from dotnet/runtime (BSD 2-Clause, MIT licensed)
  • SortingNetworks.Generators/SortingNetworkGenerator.cs -- IEquatable<T> on model types, ImmutableArray collections, HashCode usage
  • SortingNetworks.Tests/SourceGeneratorDriver.cs -- RunGeneratorTwice helper for incremental cache testing
  • SortingNetworks.Tests/GeneratorTests.cs -- 4 new incremental caching tests

jonathanpeppers and others added 2 commits May 3, 2026 10:53
GenerationInfo and NetworkRequest were plain classes without IEquatable<T>,
causing the incremental pipeline to always regenerate on every build since
ReferenceEquals was used for cache comparison (always false for new objects).

Changes:
- Implement IEquatable<T> on GenerationInfo and NetworkRequest
- Replace HashSet<string> with sorted ImmutableArray<string> for stable
  value equality on FallbackTypes/FallbackTypesWithComparer
- Replace NetworkRequest[] with ImmutableArray<NetworkRequest>
- Add RunGeneratorTwice helper to SourceGeneratorDriver for incremental tests
- Add 4 tests verifying cache hits on same/unrelated changes and cache
  misses when attributes actually change

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace ad-hoc hash * 397 ^ value patterns with a proper HashCode
polyfill based on the xxHash32 implementation from dotnet/runtime.
This provides better hash distribution via HashCode.Combine and the
Add/ToHashCode pattern.

The polyfill is internal to the generator assembly, avoiding the need
to redistribute a NuGet dependency (Microsoft.Bcl.HashCode) in the
analyzer package. Uses a fixed seed since randomization is unnecessary
for incremental generator caching.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 3, 2026 15:58

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Improves Roslyn incremental source generator caching so unchanged inputs don’t trigger full regeneration, by giving the generator’s pipeline model types stable value equality and adding tests that verify incremental cache behavior via tracked step reasons.

Changes:

  • Implement IEquatable<T> / GetHashCode() for generator pipeline model types and switch collections to ImmutableArray (with deterministic ordering where needed).
  • Add an internal HashCode (xxHash32-based) polyfill for netstandard2.0.
  • Add incremental caching tests and a helper to run the generator twice with trackIncrementalGeneratorSteps: true.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
SortingNetworks.Generators/HashCode.cs Adds an internal HashCode polyfill used for stable hashing in generator model types.
SortingNetworks.Generators/SortingNetworkGenerator.cs Adds structural equality to pipeline model types and switches to ImmutableArray for cache-friendly comparisons.
SortingNetworks.Tests/SourceGeneratorDriver.cs Adds RunGeneratorTwice helper to enable incremental-step reason assertions.
SortingNetworks.Tests/GeneratorTests.cs Adds 4 tests validating Cached vs Modified incremental behavior.

Comment thread SortingNetworks.Tests/GeneratorTests.cs Outdated
Comment thread SortingNetworks.Tests/GeneratorTests.cs Outdated
Comment thread SortingNetworks.Generators/HashCode.cs Outdated
…e unnecessary pragma

- Tests now query TrackedOutputSteps["SourceOutput"] specifically instead
  of flattening all steps, preventing false positives from vacuous Assert.All
  and brittleness if new tracked steps are added later.
- Assert.NotEmpty added before Assert.All to guard against vacuous passes.
- Removed unnecessary #pragma warning disable CS0809 from HashCode polyfill.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jonathanpeppers jonathanpeppers merged commit a893711 into main May 3, 2026
6 checks passed
@jonathanpeppers jonathanpeppers deleted the jonathanpeppers/fix-incremental-generator-equality branch May 3, 2026 19:30
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