Repository navigation
Use typed keys for every type lookup so same-named types cannot collide (S4) - #8530
Merged
Aaronontheweb merged 1 commit intoSep 11, 2026
Conversation
Aaronontheweb
added this pull request to stack #8531
September 9, 2026 01:53
This was referenced Sep 9, 2026
Merged
Aaronontheweb
force-pushed
the
feature/serialization-v2-generator-typekey
branch
from
September 9, 2026 13:31
b441744 to
9983589
Compare
This was referenced Sep 9, 2026
Aaronontheweb
force-pushed
the
feature/serialization-v2-generator-typekey
branch
from
September 9, 2026 17:14
9983589 to
1ba4067
Compare
Aaronontheweb
force-pushed
the
feature/serialization-v2-generator-typekey
branch
from
September 9, 2026 18:08
1ba4067 to
089b1a3
Compare
Aaronontheweb
force-pushed
the
feature/serialization-v2-generator-typekey
branch
from
September 9, 2026 22:52
089b1a3 to
417ae14
Compare
Aaronontheweb
force-pushed
the
feature/serialization-v2-generator-typekey
branch
from
September 10, 2026 23:44
417ae14 to
72345ad
Compare
…ion closed Adds TypeKey, a symbol-free value type keyed on CLR metadata name (arity suffix, '+' for nesting) plus recursive type arguments, carrying its fully-qualified display string verbatim but never comparing on it. Every dictionary the generator pipeline keys by a type's identity now uses TypeKey instead of a raw display-name string: MessageInfo, TypeMapping, UnionMemberInfo, ClosedSetMember, FormatterInfo, and the resolved-message and formatter dictionaries. This closes a real collision: a nested type and a namespace-qualified type can render the same fully-qualified display string but now carry distinct metadata names, so they no longer alias the same dictionary entry. ClosedGenericRegistrationInfo becomes a light spec (target TypeKey, manifest, allow-empty) instead of eagerly embedding a full MessageInfo. Each closed construction's schema is still produced at extraction time (the only phase with symbols), through the same ExtractMessageCore routine every other message goes through, and stored beside the serializer in a new SerializerInfo.ClosedGenericSchemas array rather than inside the registration. The resolve stage folds this into the same schema table as every other declared message, and separately resolves the serializer's own registrations against its own formatters -- self-scoped, so an edit to an unrelated message can never change it. The AKKASG029 protocol-coverage scan compares TypeKeys built from each candidate interface's metadata name instead of formatting every interface of every candidate type to a display string for an ordinal compare, gated by a cheap allocation-free metadata-name suffix pre-filter that skips the expensive path entirely for interfaces that cannot possibly match (e.g. a record's compiler-synthesized IEquatable<T>) -- this scan runs once per candidate type per serializer and is never cached, so that filter is most of the win. TypeKey.FromSymbol also avoids a second, independent ToDisplayString pass for non-generic types (GetFullyQualifiedTypeName's own text is identical), and BuildMetadataName/GetNamespace fast-path the common non-nested/single-segment-namespace case instead of always allocating a Stack<string>. Net effect measured via SourceGeneratorBenchmarks: allocations are at or below the pre-S4 baseline on every row (roughly 3-14% lower), not the ~25% regression an earlier, unfiltered version of this pass had. Emitted text, diagnostics, and tracking-name/stage-count all stay unchanged; GeneratorResolveSpec, GeneratorValidatorSpec, and GeneratorResolvedSerializerSnapshotSpec are updated only where constructor signatures changed. Adds TypeKeySpec covering equality, folding, the nested-vs-namespace-qualified and closed-generic-construction collision cases, and verbatim display-name carry-through.
Aaronontheweb
force-pushed
the
feature/serialization-v2-generator-typekey
branch
from
September 11, 2026 15:05
72345ad to
3bc3f9e
Compare
Aaronontheweb
deleted the
feature/serialization-v2-generator-typekey
branch
September 11, 2026 17:29
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changes
Nothing in output or diagnostics. Every lookup keyed by a type now uses a
TypeKeyinstead of a name string: the message table, union members, formatter overrides, closed-generic registrations, closed-set members, and the resolved serializer's table.TypeKeyis. The CLR metadata name, with+for a nested type and the arity suffix for a generic one, plus the type arguments. The display name rides along unchanged, so generated code and messages read exactly as before.A.Outer+Innerand a namespace-qualifiedA.Outer.Innerrender the same dotted name. Today C# rejects that pair in one assembly. F1 admits types from referenced assemblies, where the pair can come from two assemblies. Two tests pin that the keys differ, one on hand-built keys and one on real Roslyn symbols.AllowEmpty. The construction's schema is built by the same extraction routine every message uses and stored beside the serializer, keyed like every other schema.One detail this caught: a generic definition's display string changes under the new key, which would have changed the text of AKKASG022. The definition keeps its old display name.
Savings
Measured back to back on the same machine, base against branch, three paired runs. Allocations are the same on every run.
A first version added about 2 MB per run. Two causes, both measured and fixed: the coverage scan built a full key for every interface of every type, including the compiler-generated
IEquatable<T>on records, and the key builder made a second pass over the display string and allocated a stack in the common case. That is why the result is below the base instead of level with it.How it was checked
309 tests pass (302 plus 7 in
TypeKeySpec). Generator builds with warnings as errors.Akka.Remotebuilds. Golden, wire, and both model snapshots unchanged. Caching pins unchanged.