Family surface: version-agnostic dispatch members on welded family bases - #1
Merged
Merged
Conversation
…mily bases Two or more welded classes deriving one welded base (the shape a versioned class template welded per range makes) now gain a synthesized surface ON the base: the member intersection the concretes bind identically, each member a type-switch dispatch to the concrete class. Pure managed text over the concretes' own accessors — no new thunks, no new P/Invokes, the shim is byte-identical. Hoist rules: - identical C# spellings hoist with the exact type (scalars, strings, the shared scalar-sequence wrappers), settable when every concrete's is; - a welded-class member hoists as the member types' common welded base — the getter upcasts, the setter downcasts (InvalidCastException names a wrong-era assignment); - a sequence of welded elements hoists as a read-only FamilyVector<Base> live view (Count + indexer + duck-typed foreach over the concrete wrapper); - a method overload spelled identically on every concrete hoists as a forwarding dispatch; a bare base instance throws InvalidOperationException from the default arm. Mechanics: the field/method emitters record each class's member manifest (names + resolved C# spellings, placeholders included) beside their emissions; the class writer flushes it into document::family_records; the render pass groups records by resolved first-welded-base and appends one 'partial class <Base>' block per family after its namespace's declarations. options::family_surface (default ON) restores the prior artifact when off. Existing surfaces are untouched byte-for-byte (goldens unchanged): the shared inheritance cases' sibling classes declare disjoint members, so no family there has a non-empty intersection. New family.hpp/gen_family.cpp pair + FamilyTests.cs lock the whole matrix (41 managed tests). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…the option Synthesizing members onto a base is too intrusive to infer from structure alone — any two welded classes sharing a welded base would qualify. The surface is now synthesized ONLY for a base carrying [[=welder::mark::family_surface]] (skarndev/welder#3) covering this rod's language; an unmarked base is never touched, however hoistable its family's intersection is. The mark lives in welder CORE, not this rod, because base definitions sit in consumers' core headers, which must parse without any rod fetched (and gcc-16 ignores annotations on redeclarations, so the bindings layer cannot attach one after the fact). options::family_surface is gone — the mark is the one switch. Manifest recording is unconditional now: an unmarked class's manifest still resolves member types when it appears inside a marked family's members. welder pin moves from main to the mark commit (restore main once welder#3 lands). family.hpp marks its two bases and adds an Unmarked family whose perfectly hoistable intersection must synthesize nothing; FamilyTests locks that negative (42 managed tests). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Design reversal of the previous commit's welder-core dependency: the family_surface mark did not belong in welder core while only this rod honors it (skarndev/welder#3 closed unmerged). The marker now lives here — <welder/rods/csharp/marks.hpp> defines [[=welder::rods::csharp::family_surface]] (a bare tag; no language mask — it IS this rod's mark) plus the family_surface_marked(type) query, and the class opener reads it directly. The welder pin returns to main. A consumer whose welded headers must also parse WITHOUT this rod (a Python-only build where welder-csharp is not fetched) spells the mark behind a build-driven macro that expands to nothing when the rod is absent — documented in marks.hpp; gcc-16 collects annotations only from the DEFINING declaration, so the macro must be consistent across every TU of one build tree. Same tests, same results: 42 managed (marked bases synthesize, the unmarked family's hoistable intersection synthesizes nothing), goldens untouched, 7/7 ctest. 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.
What
Welded families — two or more welded classes deriving one welded base (the shape a versioned class template welded per range makes) — can gain a synthesized version-agnostic surface on the base: the member intersection the concretes bind identically, each member a type-switch dispatch to the concrete class. Pure managed text over the concretes' own accessors: no new thunks, no new P/Invokes, the shim is byte-identical.
Strictly opt-in, via the rod's own mark: the surface is synthesized only for a base carrying
[[=welder::rods::csharp::family_surface]](<welder/rods/csharp/marks.hpp>). An unmarked base is never touched, however hoistable its family's intersection is — synthesizing members onto a base is too intrusive to infer from structure alone. The mark deliberately lives in the rod, not welder core: no other backend honors it, so core vocabulary stays untouched (the welder pin remainsmain). A consumer whose welded headers must also parse without this rod (a Python-only build) spells the mark behind a build-driven macro that expands to nothing when the rod is absent — the pattern is documented inmarks.hpp.Hoist rules (for a marked base)
InvalidCastExceptionnames a wrong-era assignment).FamilyVector<ElementBase>live view:Count, indexer, duck-typedforeachover the concrete wrapper.InvalidOperationExceptionfrom the default arm.Why
Downstream (wowlib),
ForVersion(...)returned a family base that carried verbs but no data — every property access needed a downcast, and thedynamicwrappers consumers built around it were slow (DLR). This gives base-typed code the same intersection semantics Python gets from its union aliases, at type-switch cost in front of the same P/Invoke the concrete path makes.Mechanics
The field/method emitters record each class's member manifest (names + resolved C# spellings, placeholders included) beside their emissions; the class writer flushes it into
document::family_recordsalong with the base's mark state; the render pass groups records by resolved first-welded-base and appends onepartial class <Base>block per marked family after its namespace's declarations.Tests
family.hpp/gen_family.cpp+FamilyTests.cslock the whole matrix: exact-type read/write, base-typed welded members incl. live-view write-through,FamilyVectoriteration, wrong-eraInvalidCastException, method dispatch, era-gated exclusion, bare-base throw — and the opt-in negative: an unmarked family whose perfectly hoistable intersection must synthesize nothing. 42 managed tests, full ctest suite 7/7, against stock weldermain.🤖 Generated with Claude Code