Rewrote 20 issue writeups to the extern/runtime-class vocabulary (#foreign→extern, foreign_class_map→runtime_class_map, parseForeignClassDecl→parseRuntimeClassDecl, findForeignMethodInChain→findRuntimeMethodInChain, dedupeForeignSymbol→ dedupeExternSymbol, is_foreign_c_api→is_extern_c_api, stale filename refs to the renamed examples, foreign-class→runtime-class, bare foreign→extern). Renamed issues/0043-…-foreign-class-…→…-runtime-class-…. PHASE 9 COMPLETE — 9.4 GATE PASSES: zero 'foreign' across src/library/examples/ issues/docs/editors/specs/readme/CLAUDE, excluding only the SQLite API constant SQLITE_CONSTRAINT_FOREIGNKEY + vendored sqlite3.c/.h (upstream third-party). Suite green (644 corpus / 443 unit, 0 failed).
2.6 KiB
0056 — parameterised-protocol impl not deduped across a diamond import
FIXED (examples/issue-0056-diamond-param-impl.sx, helpers in
examples/issue-0056/). The flat decl list in
src/imports.zig now dedups by node identity as well
as by name. ResolvedModule.mergeFlat and the directory-import merge loop each
carry a seen_nodes: std.AutoHashMap(*Node, void) alongside the existing
seen_in_list name set, and skip a decl whose pointer was already appended.
Symptom
A module containing a parameterised-protocol impl (impl Into(T) for S) could
not be imported through more than one path. Under a diamond —
main ─┬─ mid_a ─┐
└─ mid_b ─┴─ common (holds `impl Into(Wrapped) for i64`)
— compilation failed with:
error: duplicate impl 'Into' for source 'i64' in .../common.sx
This bit the moment modules/std/objc.sx (imported by main.sx,
platform/uikit.sx, and gpu/metal.sx — a diamond) gained an
impl Into(*NSString) for string.
Root cause
mergeFlat/addOwnDecl dedup the global flat decl list by
decl.data.declName(). Named decls (structs, fns, runtime classes) dedup
fine across diamonds. But impl_block is anonymous — declName() returns
null (see src/ast.zig Data.declName) — so the dedup
guard was skipped and the same cached impl node (modules are cached in
ModuleCache, so both paths share one node pointer) was appended once per
path. registerParamImpl in src/ir/lower.zig then saw
two entries with the same defining_module and raised the same-file
"duplicate impl" diagnostic.
The pre-existing impl Into(Block) for Closure(...) in
modules/std/objc_block.sx never tripped this because that module is not
imported through any diamond.
Reproduction
examples/issue-0056/common.sx:
Wrapped :: struct { v: i64; }
impl Into(Wrapped) for i64 {
convert :: (self: i64) -> Wrapped {
return .{ v = self };
}
}
mid_a.sx / mid_b.sx each #import "common.sx";. The diamond main:
#import "modules/std.sx";
#import "issue-0056/mid_a.sx";
#import "issue-0056/mid_b.sx";
main :: () -> i32 {
w : Wrapped = xx 7; // pre-fix: duplicate impl 'Into' for source 'i64'
print("{}\n", w.v); // post-fix: prints 7
0;
}
Fix
Dedup the flat decl list by node identity in addition to name — a physical
AST node must be lowered once regardless of how many import paths reach it.
Named-decl first-wins behaviour is unchanged. Regression test:
examples/issue-0056-diamond-param-impl.sx (in tests/run_examples.sh).