Files
sx/issues/0056-param-impl-not-deduped-across-diamond-import.md
agra d8076b9333 lang: rename signed integer types sN -> iN
Surface rename of the signed integer family: s1..s64 become i1..i64
(u1..u64, usize, isize unchanged). 'string' keeps the s-prefix arm in
name classification; width parsing moves to the i-prefix arm next to
isize.

Internal TypeId tags follow the surface (.s8/.s16/.s32/.s64 ->
.i8/.i16/.i32/.i64), as do mono-key mangle fragments (ptr_i64,
tu_i64_bool) and all display/diagnostic formatting (i{d}).

Migrated in the same sweep: stdlib + examples + issue repros + FFI C
companions (shared symbol names like ffi_id_i64), expected
stdout/stderr/ir snapshots, specs.md, readme.md, CLAUDE.md/AGENTS.md,
implementation_plan.md, docs/, issue writeups. Vendored stb_image and
historical flow state left untouched.

zig build test: 426/426; examples suite: 595/595.
2026-06-12 09:31:53 +03:00

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, foreign 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).