Skip to content

Sort ProvidedTypes iteration for deterministic source output#413

Merged
eiriktsarpalis merged 2 commits intoeiriktsarpalis:mainfrom
AArnott:dev/andarno/deterministic-source-gen-output
Apr 15, 2026
Merged

Sort ProvidedTypes iteration for deterministic source output#413
eiriktsarpalis merged 2 commits intoeiriktsarpalis:mainfrom
AArnott:dev/andarno/deterministic-source-gen-output

Conversation

@AArnott
Copy link
Copy Markdown
Contributor

@AArnott AArnott commented Apr 14, 2026

The source generator iterates provider.ProvidedTypes.Values in two places:

  1. SourceFormatter.AddAllSourceFiles — emits per-type source files
  2. SourceFormatter.FormatGetShapeProviderMethod — emits switch cases in GetTypeShape

Since ProvidedTypes is an ImmutableEquatableDictionary backed by Dictionary<TKey, TValue>, iteration order is non-deterministic. This causes the generated source to vary between compilations even when inputs are identical.

Impact

This breaks incremental build for downstream projects. When a project referencing a PolyType-using library is built:

  1. Any source change triggers recompilation of the library
  2. PolyType source generator re-runs, producing fields/cases in a different order
  3. The reference assembly content changes (different field numbering in the compiler's <>O display class)
  4. Downstream projects see a newer reference assembly and recompile unnecessarily

In our case, a single implementation-only change in one project caused csc to run for a downstream project that should have been skipped entirely.

Fix

Sort by SourceIdentifier (a unique, stable, type-name-derived key) before iterating in both locations. This is a two-line change.

The source generator iterates provider.ProvidedTypes.Values in two places:
SourceFormatter.AddAllSourceFiles and SourceFormatter.FormatGetShapeProviderMethod.
Since ProvidedTypes is backed by Dictionary<TKey, TValue>, iteration order is
non-deterministic, causing the generated source to vary between compilations even
when inputs are identical.

This breaks incremental build in projects that reference assemblies using PolyType,
because the reference assembly content changes on every build, forcing downstream
projects to recompile unnecessarily.

Fix: sort by SourceIdentifier (a unique, stable, type-name-derived key) before
iterating.
Comment thread src/PolyType.SourceGenerator/SourceFormatter/SourceFormatter.cs
…generator

Sort iterations over ImmutableEquatableSet and ImmutableEquatableDictionary
collections that feed into generated source output:

- ShapeableImplementations (ImmutableEquatableSet<TypeId>): sorted by
  FullyQualifiedName in three iteration sites that emit interface
  declarations, method implementations, and type checks.
- EnumShapeModel.Members (ImmutableEquatableDictionary<string, string>):
  sorted by key when emitting enum member dictionary initializers.
- AssociatedTypes (ImmutableEquatableSet<AssociatedTypeId>): sorted by
  ClosedTypeReflectionName when emitting associated type switch cases.

Updated snapshot baselines to reflect the new deterministic ordering.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@AArnott
Copy link
Copy Markdown
Contributor Author

AArnott commented Apr 15, 2026

Thanks for making this more comprehensive. :)

@eiriktsarpalis eiriktsarpalis merged commit e2aa18f into eiriktsarpalis:main Apr 15, 2026
7 checks passed
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