perf: make the incremental pipeline actually incremental - #49
Merged
Conversation
Four tests assert cached-not-executed behavior via trackIncrementalGeneratorSteps: editing one class must not regenerate others, an unrelated edit must regenerate nothing, code edits must not re-parse migration files, and a migration edit must only re-run its class. All four fail against the current pipeline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Restructures the generator around a fully resolved, value-equatable SerializationModel built in a ForAttributeWithMetadataName transform. Symbol- and compilation-dependent resolution (attribute processing, rules engine, validation, MarkDirty/timer/after-deserialization lookup, type shells) happens in the transform; generation is symbol-free and consumes only the model, so the output stage caches on model equality. Migration JSON files parse once per content change in their own tracked node; a cheap compilation-combined augment step resolves the one value-type fact content structs need; a value-equatable map attaches each class's migrations. Diagnostics travel as equatable DiagnosticInfo and materialize at output time. Location is excluded from model equality so edits elsewhere in a file do not defeat caching. The step-tracked incrementality tests flip green: editing one class leaves every other output cached, editing a non-serializable file or a method body inside a serializable class regenerates nothing, migration files are not re-parsed on code edits, and a migration edit re-runs only its class. A 150-class probe pins the behavior at scale (SourceOutput executed=0 after an inert edit). Correctness: all 17 snapshots byte-identical, and the DiffTool manifest over a 3,786-source corpus is byte-identical to main. Benchmarks (150 classes, migrations included; baseline re-measured on main after fixing the benchmark's missing analyzer-private Humanizer dependency, which had the earlier runs measuring crash paths): - single-edit warm re-run: 61.2ms/16.4MB -> 47.9ms/10.4MB, with output re-executions dropping from 150 to at most 1 (the IDE no longer churns every generated document per keystroke) - no-change re-run: ~65us both (the old pipeline only cached when the compilation was reference-identical, which no real edit ever is) - cold full run: 20.7ms -> 29.9ms; the transform now fronts the resolution work, a deliberate trade against the per-edit wins Breaking (rule API): GenerateDeserializationMethod and GenerateMigrationProperty no longer take a Compilation; IPostDeserializeMethod takes the model; value-type structs must not declare Deserialize (SG3009). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Aug 22, 2026
kamronbatman
added a commit
that referenced
this pull request
Aug 22, 2026
…te JSON work (#51) GetTypeByMetadataName walks assemblies on every call, and the transforms resolved the same handful of names per field per class per compilation - tens of thousands of walks per keystroke at corpus scale. A per-compilation ConditionalWeakTable + ConcurrentDictionary cache collapses every call site (53) to a dictionary hit, including the migration augment stage's arbitrary schema type names. The augment stage also skips resolution entirely for properties without a save flag, since the value-type fact only affects the nullable suffix on save-flagged content struct fields. Migration JSON parsing and schema writing use a source-generated JsonSerializerContext, removing reflection-based metadata construction from the analyzer process. Benchmarks (150 classes): cold full run 29.9ms -> 6.5ms (3.2x faster than even the pre-#49 pipeline's 20.7ms), single-edit warm re-run 47.9ms -> 19.6ms, no-change re-run unchanged at ~64us. Output proven neutral: all snapshots byte-identical and the 3,786-source corpus manifest is unchanged. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
kamronbatman
added a commit
that referenced
this pull request
Aug 22, 2026
* chore: bump Generator, Annotations, and SchemaGenerator to 3.0.0 Major per the rule-API break in #49; Annotations carries the new [AnchoredDateTime]; the schema tool picks up .slnx support and the fixed generator. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: restore csproj line endings; keep the bump minimal Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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
The pipeline previously combined the full
Compilationinto every output node and cached on symbol-bearing records that never compare equal — so every keystroke re-ran everything: full codegen for every class and a re-parse of every migration JSON (#47's baseline measured the warm re-run as more expensive than a cold run). This PR restructures the generator around a fully resolved, value-equatableSerializationModelso the expensive stages cache for real.Design
ForAttributeWithMetadataName→BuildSerializationModel): all symbol/compilation-dependent work — attribute processing, the rules engine, every validation diagnostic, MarkDirty/timer/after-deserialization resolution, nesting shells — resolves into an equatable model of strings, enums, andEquatableArrays.DiagnosticInfo).Locationis deliberately excluded from model equality so same-file edits don't defeat caching; a cached warning's position can lag until the surface changes.Proof
SourceOutput: executed=0, cached=150.The per-edit wall-time and allocation wins understate the IDE impact: the workspace no longer re-adds and re-parses every generated document on every keystroke. The cold-run regression is the transform fronting resolution work — a deliberate trade against the continuous per-edit cost.
Breaking (rule API, for the major bump)
GenerateDeserializationMethod/GenerateMigrationPropertyno longer take aCompilation;IPostDeserializeMethodreceives the model; timer-method resolution and its SG3008 check moved to model building.🤖 Generated with Claude Code