feat(generator): preserve positional-record DTOs in factory signatures (TRIM-001) - #69
Merged
Conversation
… full Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…aft) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s (TRIM-001) DtoTypeWalker's factory walk required a public parameterless ctor, so positional records in factory signatures got no trimming preservation — a publish-trimmed client threw DeserializeNoConstructor on them (the zTreatment StartVisitResultV2 cut-over failure). Discovered types now bucket-sort by ctor shape at every level of the walk: parameterless -> DtoConstructorRegistry.Register<T>(() => new T()), parameterized-only -> DtoConstructorRegistry.PreserveType<T>() (rooting only; deserialization flows through RecordBypassConverterFactory). The bucket threads through the incremental pipeline as EquatableArray on the transform-output records; all three registrar renderers emit both calls. The dead WalkEventRoot walker is retired into the shared bucketed walk. - Unit: RecordDtoDiscoveryTests pins bucket assignment across return, parameter, nested (both directions), collection, mixed-ctor, interface/ class/static renderer paths, and the unchanged exclusions - Trimmed harness: RecordDtoSmokeTest deserializes records never constructed in client code (negative control verified: disabling the emission fails the harness with exit 1) - Docs: trimming.md, CLAUDE-DESIGN.md, AllPatterns.cs updated to the two-bucket contract Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…cord-struct + dedupe coverage The gate caught false trimmed-harness coverage: _ProcessRecord's constructed body rooted the record ctors (guarded-dead bodies are retained on the client, the TRIM-005 behavior), so the return/nested checks passed even with PreserveType emission disabled. The harness method now returns null — discovery is signature-based — and two-stage negative controls prove each shape depends solely on the emission. Adds RecordStruct_LandsInRegisterBucket and SameRecordFromTwoMethods_SinglePreserveTypeEmission; queues the pre-existing incremental-cache test hole as TRIM-006. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…cope 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
Second plan of the TRIM todo. Positional records in factory method signatures were silently dropped by DTO discovery —
WalkFactoryReturnrequired a public parameterless ctor, so a record like zTreatment'sStartVisitResultV2(returned from[Remote, Execute]) got no preservation and the publish-trimmed client threwDeserializeNoConstructor. This was the largest contributor to zTreatment's ~50-entry LinkerConfig.xml workaround (PCB-003).Changes
DtoTypeWalker.WalkDtoGraph): every discovered signature type — roots and nested alike — buckets by ctor shape: parameterless →Register<T>(() => new T())(unchanged), parameterized-only →PreserveType<T>()(DAM-All rooting; deserialization flows through the existingRecordBypassConverterFactory). Applies to return types, non-service parameters, and property descent (which now enters record graphs). The deadWalkEventRootis retired into the shared walk.EquatableArray<string>on the transform-output records (TypeInfo/MethodInfo) — the incremental-cache boundary — per plan review; all three registrar renderers (class/interface/static) emit both call kinds.RecordDtoDiscoveryTests(return/parameter/nested both directions/collection/mixed-ctor/record struct/cross-method dedupe/exclusions, across all three renderer paths) +RecordDtoSmokeTestin the trimmed CI harness. The harness records are never constructed in harness code, so their ctors are rooted solely by the emittedPreserveType— verified by two-stage negative controls (emission disabled → harness exits 1 on the record checks).docs/trimming.md("What Qualifies as a DTO" no longer conflatesRecordBypassConverterFactorywith preservation), CLAUDE-DESIGN.md registry section/criteria table/FAQ,AllPatterns.csExampleRecordResultremarks.Reviews (in
docs/todos/TRIM-dto-trimming-preservation-gaps/reviews/)Test plan
🤖 Generated with Claude Code