Repository navigation
C#: the family surface — ForVersion results carry data, not just verbs - #2
Merged
Merged
Conversation
…verbs The tester-reported gap: C#'s ForVersion returned the family base, which had no data members — every property access needed a downcast across up to nine range classes, and the dynamic wrappers people built around it (DLR) were slow. Python never had this problem: for_version returns the concrete class and AnyWMO gives checkers intersection semantics. welder-csharp 6c5bdbf closes it at the rod: every welded family base gains a synthesized version-agnostic surface — the member intersection the era classes bind identically, as type-switch dispatch members. Identical spellings hoist exactly (incl. the shared scalar-seq wrappers, zero-copy views intact); welded members hoist as their own family base (getter upcasts, setter downcasts); welded-element sequences hoist as read-only FamilyVector<Base> live views; identically-spelled methods (Read/Write/ Validate) hoist as forwarding dispatch. Era-gated members stay on the concretes — pattern matching, the same contract as Python's isinstance narrowing. Pure managed text: no new P/Invokes, the shim is byte-identical. Here: bump the welder-csharp pin; DELETE the facade script's FS_VERBS block (the rod now hoists the fs verbs itself — emitting both would be CS0111; the script keeps the one thing the rod cannot know, the era->range factory mapping); rewrite the guide's C# tabs to the base-typed spelling; new FamilySurfaceTests lock the tree walk (base WMO -> FamilyVector<WMOGroup> -> base body -> shared Indices), the wrong-era InvalidCastException, base Validate dispatch, and the bare-base InvalidOperationException. 33 tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… a macro The blanket synthesis had no right to shove members into every common base; each family base now opts in explicitly. The marker is the C# rod's own ([[=welder::rods::csharp::family_surface]], welder-csharp ffa71b2) — NOT welder core vocabulary (a core mark was tried and reverted, welder#3 closed: core should not name a feature only one rod honors). The layering squeeze and its resolution: the *Base definitions live in format headers that must parse in Python-only builds where welder-csharp is not even fetched, and gcc-16 reads annotations off the DEFINING declaration only (verified: annotated redeclarations are silently dropped), so the bindings layer cannot attach the mark after the fact. The mark therefore rides the base annotation lists behind WOWLIB_CS_FAMILY_SURFACE (core/lang.hpp — the header that already respells the rod's lang identity): on WOWLIB_BUILD_CSHARP configures, Dependencies.cmake defines WOWLIB_CSHARP_ROD and puts the rod's headers on the include path DIRECTORY-WIDE, so the library, the generator TUs and the shim all agree on every class's annotation list; everywhere else the macro expands to nothing and the annotation never exists (a marked header syntax-checks clean with no rod on the path). All 21 *Base classes carry the macro (after weld_as, before doc; the trailing comma rides inside the macro — 18 families synthesize today, Skeleton/WDTOcclusion/WDTParticulates are single-range until a future split). welder pin returns to main-era 542176e; welder-csharp pin bumps to ffa71b2. Same surface, same tests: 18 family blocks, 33/33 green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… on main PR#1 merged (true merge, tree byte-identical to the ffa71b2 branch head this previously pinned) — repoint at the main-history SHA. 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.
The gap
C#'s
ForVersionreturned the family base, which had no data members — every property access needed a pattern-match downcast across up to nine range classes, and thedynamicwrappers people built around it were slow (DLR call-site binding + reflection). Python never had this problem:for_version()returns the concrete class, andAnyWMO-style unions give type checkers intersection semantics.The fix
skarndev/welder-csharp#1 closes it at the rod: a welded family base that opts in gains a synthesized version-agnostic surface — the member intersection the era classes bind identically, as type-switch dispatch members. Base-typed code now reads, writes and processes without naming an era:
WMO.Root→WMORoot); a wrong-era assignment throwsInvalidCastException.FamilyVector<Base>live views.Read/Write/Validate) hoist as forwarding dispatch.isinstancenarrowing.Performance: one
isinstchain in front of the same P/Invoke the concrete path makes — no DLR, no reflection.The opt-in
Synthesizing members onto a base is too intrusive to infer from structure, so it is strictly opt-in per base via the rod's own mark,
[[=welder::rods::csharp::family_surface]](deliberately not welder-core vocabulary — no other backend honors it). The*Basedefinitions live in format headers that must parse in Python-only builds where welder-csharp isn't even fetched, and gcc-16 reads annotations off the defining declaration only — so the mark rides the annotation lists behindWOWLIB_CS_FAMILY_SURFACE(core/lang.hpp, the header that already respells the rod's lang identity). OnWOWLIB_BUILD_CSHARPconfigures,Dependencies.cmakedefinesWOWLIB_CSHARP_RODand puts the rod's headers on the include path directory-wide, so every TU of the tree agrees on each class's annotation list; everywhere else the macro expands to nothing and the annotation never exists. All 21*Baseclasses carry it (18 families synthesize today; Skeleton/WDTOcclusion/WDTParticulates are single-range until a future split).Changes here
ffa71b2(merge Family surface: version-agnostic dispatch members on welded family bases welder-csharp#1 first); welder stays at its existing pin.*Baseannotations + theWOWLIB_CS_FAMILY_SURFACEmacro + the CMake wiring.tools/gen_cs_format_facades.py: theFS_VERBSdispatch block is deleted — the rod hoists the fs verbs itself now (emitting both would be CS0111). The script keeps the one thing the rod cannot know: the era→range factory mapping (ForVersion+ per-era statics).FamilySurfaceTestslock the tree walk (baseWMO→FamilyVector<WMOGroup>→ base body → sharedIndices), wrong-eraInvalidCastException, baseValidatedispatch, and the bare-baseInvalidOperationException.dotnet test tests/csharp: 33/33 green against the regenerated gcc16-csharp tree; a marked format header also syntax-checks clean with no rod on the include path (the Python-only shape).🤖 Generated with Claude Code