Repository navigation
feat: catch a type missing from the JsonSerializerContext (RXH024) - #7
Merged
Merged
Conversation
Katyara11
force-pushed
the
feat/json-context-coverage
branch
from
November 7, 2026 11:26
232cfc2 to
eae94fb
Compare
Closes the last sharp edge in the AOT story. The generator already knows every type a contract sends and receives, so the user should not be the one tracking whether their JsonSerializerContext kept up. A missing registration is now a build warning naming the type, the method, the exact [JsonSerializable] line and the context to add it to — instead of a resolver error on the first call that needed it, in a deployed trimmed application, a long way from the registration nobody updated. The context itself cannot be generated, and I verified why rather than assuming. Roslyn output registered with RegisterPostInitializationOutput joins the compilation every generator sees: a JsonSerializerContext emitted that way IS picked up and populated by System.Text.Json's generator, and serializes at run time. Output registered with RegisterSourceOutput is not — the other generator never sees it and the emitted partial fails CS0534 for the abstract members nobody implemented. The type set can only come from analysing the contracts, which means RegisterSourceOutput, so emission is impossible rather than merely awkward. Recorded in docs/aot.md and on JsonContextCoverage so the next person does not retry it. Covers [Body] parameters that reach the serializer, Task<T>, Task<ApiResponse<T>> and every [Response] type — including the collection type for Task<IReadOnlyList<T>>, since a probe confirmed registering T does not cover IReadOnlyList<T>. That is the easiest gap to miss by hand and the one most worth catching. Two blind spots, both chosen to avoid warning when nothing is wrong, because a diagnostic that cries wolf gets suppressed wholesale and then protects nobody. Bare primitives and string are never reported: a probe showed a context registering one record resolved its int and string property types for free, while bool and every collection came back null — so nothing is unconditionally free, but those are overwhelmingly likely to be. And a type registered by any context counts, since nothing at compile time can tell which context is wired to which client. A warning rather than an error: a mixed setup, one client on a source-generated context and another deliberately on the reflection-based serializer, is legitimate and an error would fail that build with no recourse but suppression. Under warnings-as-errors — this repository included — it fails the build anyway, which is how the AOT smoke test now guards its own context. Reads the type set off the existing model rather than re-walking symbols, so the generator keeps exactly one understanding of what each return shape serializes; a second walk would drift the first time a shape is added. 1416 tests; build clean with warnings-as-errors, format clean, AOT publishes with zero trim warnings and runs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Katyara11
force-pushed
the
feat/json-context-coverage
branch
from
November 7, 2026 11:37
eae94fb to
c439da6
Compare
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.
Summary
Closes the last sharp edge in the AOT story: a type your contracts serialize that your
JsonSerializerContextdoes not register is nowRXH024at build time, instead of a resolvererror on the first call that needs it in a deployed trimmed application.
The generator already knows the complete type set, so you no longer keep the context in step by
hand.
Changes
RXH024(warning) — reports a serialized type noJsonSerializerContextregisters. Themessage names the type, the method, the exact
[JsonSerializable]line, and the context.src/Runax.Http.SourceGenerator/JsonContextCoverage.cs— the type collection, the context scanand the comparison, plus the recorded reason the context cannot simply be emitted.
ClientGenerator— the check joins every contract's typesagainst every context, which no per-interface analysis can see.
aot.md,serialization.md,diagnostics.md, package README,CHANGELOG.Type
Checklist
Notes for review
Why this is a diagnostic and not a generated context
I verified the constraint instead of assuming it, and the answer is more precise than "generators
cannot see each other's output":
RegisterPostInitializationOutputRegisterSourceOutput(derived from user code)CS0534, abstract members unimplementedPost-initialization output joins the compilation every generator sees; analysis-derived output does
not. The type set can only come from analysing the contracts, which means
RegisterSourceOutput—so emitting the context is impossible, not merely awkward. Recorded in
docs/aot.mdand onJsonContextCoverageso nobody retries it.Two deliberate blind spots
A diagnostic that cries wolf gets suppressed wholesale and then protects nobody, so both of these
err toward silence:
stringare never reported. A probe showed a context registering asingle record resolved its
intandstringproperty types for free, whilebooland everycollection came back null. So nothing is unconditionally free — but those are overwhelmingly
likely to be, and a method whose body or response is a bare primitive is rare.
and several may be. Taking the union is the only choice that cannot invent a false positive.
Warning, not error
A mixed setup — one client on a source-generated context, another deliberately on the
reflection-based serializer — is legitimate, and an error would fail that build with no recourse
but suppression. Under warnings-as-errors it fails the build anyway, which is how the AOT smoke
test guards its own context: deleting a
[JsonSerializable]fromAotJsonContextnow fails thebuild, which I confirmed before restoring it.
Collections are the gap worth catching
Registering
Repositorydoes not coverIReadOnlyList<Repository>— confirmed by probe. Amethod returning
Task<IReadOnlyList<T>>is exactly where someone forgets, and it is covered.Implementation note
The type set is read off the existing
ClientModelrather than re-walking symbols, so thegenerator keeps one understanding of what each return shape serializes. A second walk would drift
the first time a return shape is added.
ClientGeneratoredits are confined toInitialize,AnalysisResultand one return statement, to keep the rebase against the parallel query-binding PRmechanical.
Verification
1527 tests across net8.0/net9.0/net10.0;
--warnaserrorclean,dotnet formatclean, AOT publishwith zero trim warnings and the binary runs. All doc links and anchors resolve.
Rebased onto
maintwice while open, since #5 and #6 landed first. #6 claimedRXH022andRXH023, so this diagnostic isRXH024— ids are never reused, so the renumber rather than ashared number is the right resolution. The two generator changes touch the same files but not the
same code paths, and the combined suite is green.