From c7c756d9c81fb54bff66a5c7947aa508f9a4ec2c Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Wed, 20 May 2026 03:42:13 +0900 Subject: [PATCH] selfhost/cmd/wcc/check: resolve cross-module type refs in is/as MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit resolvealias only walked N_TNAME with unqualified names; cross-module type aliases (parser emits them as one TNAME with str="pkg.alias" via parse.ww:258-265 joindotted) returned the AST verbatim, and checkisas at :1098-1108 then flagged "operand is not a tagged union" on every `match (x: lib.maybe) { ... }` shape. resolvealias now recognizes the joined-dotted form: split on the rightmost '.', scopelookupinmodule(c.cur, head, leaf), recurse if the body is itself an alias. Mirrors cstage resolve_typename at cmd/wcc/check.c:74-83. scruttype gains an N_DOT scrutinee arm — `match (pkg.var) { ... }` or `pkg.var is T` now resolve through scopelookupinmodule. Module head gating distinguishes top-level imported sym refs from struct field access (both spell as N_DOT in the AST). Standalone correctness fix; surfaces no current fixture failure (those were enum-int reinterprets, tracked separately as #52). Sets up #50 to wire checkfile into the wwstage cgen pipeline once #52 also lands. --- selfhost/cmd/w6c/main.combined.ww | 43 ++++++++++++++++++++++++++-- selfhost/cmd/wcc/check.ww | 43 ++++++++++++++++++++++++++-- selfhost/cmd/wwdump/main.combined.ww | 43 ++++++++++++++++++++++++++-- 3 files changed, 123 insertions(+), 6 deletions(-) diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 63a54036..5b0a1176 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -7423,7 +7423,32 @@ fn resolvealias(c: *checker, n: *node) *node = { let cur: *node = n; for (cur != nil) { if (cur.kind != nkind.N_TNAME) { return cur; }; - let s: *sym = scopelookup(c.cur, cur.str); + let nm: str = cur.str; + // #51: pkg.alias type refs land here as a single TNAME whose + // str is the joined form (lib/ww/parse/parse.ww:258-265 in + // parsetype). Split on the rightmost '.' and bind the leaf in + // the head module's scope. Mirrors cstage resolve_typename + // cmd/wcc/check.c:74-83 strrchr branch — without this the + // raw `os.oserror` lookup misses and checkisas false-positives + // every cross-module tagged scrutinee. + let dotidx: i32 = -1; + let i: i32 = 0; + for (i < nm.len) { + if (nm[i] == 46u8) { dotidx = i; }; + i += 1; + }; + let s: *sym = nil; + if (dotidx >= 0) { + let head: str; + head.ptr = nm.ptr; + head.len = dotidx; + let leaf: str; + leaf.ptr = nm.ptr + ((dotidx + 1): u64); + leaf.len = nm.len - dotidx - 1; + s = scopelookupinmodule(c.cur, head, leaf); + } else { + s = scopelookup(c.cur, nm); + }; if (s == nil) { return cur; }; if (s.skind != skind.SK_TYPE) { return cur; }; let body: *node = nil; @@ -7504,7 +7529,7 @@ fn iserrvariant(c: *checker, tagged: *node, v: *node) bool = { // scruttype — resolve the type expression for a match's // scrutinee. Handles nkind.N_IDENT (look up local/param's declared -// type) and nkind.N_DOT (struct-field access). Returns nil if we +// type) and nkind.N_DOT (module-qualified ref). Returns nil if we // can't statically determine the type. Used by exhaustiveness. fn scruttype(c: *checker, e: *node) *node = { if (e == nil) { return nil; }; @@ -7515,6 +7540,20 @@ fn scruttype(c: *checker, e: *node) *node = { // For nkind.N_LET / nkind.N_PARAM: declared type is decl.lhs. return s.decl.lhs; }; + // #51: `match (pkg.var)` / `pkg.var is T` — module-qualified ref. + // lhs is N_IDENT (module bareword), str is the leaf. Bind via + // scopelookupinmodule so the declared type carries the same + // shape resolvealias' dotted-name branch now consumes. Falls + // silently to nil when lhs is a value (struct-field access) — + // the rest of the lenient-check contract. + if (e.kind == nkind.N_DOT) { + if (e.lhs == nil) { return nil; }; + if (e.lhs.kind != nkind.N_IDENT) { return nil; }; + let s: *sym = scopelookupinmodule(c.cur, e.lhs.str, e.str); + if (s == nil) { return nil; }; + if (s.decl == nil) { return nil; }; + return s.decl.lhs; + }; return nil; }; diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index 39f36a5b..9fbe167a 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -429,7 +429,32 @@ fn resolvealias(c: *checker, n: *node) *node = { let cur: *node = n; for (cur != nil) { if (cur.kind != nkind.N_TNAME) { return cur; }; - let s: *sym = scopelookup(c.cur, cur.str); + let nm: str = cur.str; + // #51: pkg.alias type refs land here as a single TNAME whose + // str is the joined form (lib/ww/parse/parse.ww:258-265 in + // parsetype). Split on the rightmost '.' and bind the leaf in + // the head module's scope. Mirrors cstage resolve_typename + // cmd/wcc/check.c:74-83 strrchr branch — without this the + // raw `os.oserror` lookup misses and checkisas false-positives + // every cross-module tagged scrutinee. + let dotidx: i32 = -1; + let i: i32 = 0; + for (i < nm.len) { + if (nm[i] == 46u8) { dotidx = i; }; + i += 1; + }; + let s: *sym = nil; + if (dotidx >= 0) { + let head: str; + head.ptr = nm.ptr; + head.len = dotidx; + let leaf: str; + leaf.ptr = nm.ptr + ((dotidx + 1): u64); + leaf.len = nm.len - dotidx - 1; + s = scopelookupinmodule(c.cur, head, leaf); + } else { + s = scopelookup(c.cur, nm); + }; if (s == nil) { return cur; }; if (s.skind != skind.SK_TYPE) { return cur; }; let body: *node = nil; @@ -510,7 +535,7 @@ fn iserrvariant(c: *checker, tagged: *node, v: *node) bool = { // scruttype — resolve the type expression for a match's // scrutinee. Handles nkind.N_IDENT (look up local/param's declared -// type) and nkind.N_DOT (struct-field access). Returns nil if we +// type) and nkind.N_DOT (module-qualified ref). Returns nil if we // can't statically determine the type. Used by exhaustiveness. fn scruttype(c: *checker, e: *node) *node = { if (e == nil) { return nil; }; @@ -521,6 +546,20 @@ fn scruttype(c: *checker, e: *node) *node = { // For nkind.N_LET / nkind.N_PARAM: declared type is decl.lhs. return s.decl.lhs; }; + // #51: `match (pkg.var)` / `pkg.var is T` — module-qualified ref. + // lhs is N_IDENT (module bareword), str is the leaf. Bind via + // scopelookupinmodule so the declared type carries the same + // shape resolvealias' dotted-name branch now consumes. Falls + // silently to nil when lhs is a value (struct-field access) — + // the rest of the lenient-check contract. + if (e.kind == nkind.N_DOT) { + if (e.lhs == nil) { return nil; }; + if (e.lhs.kind != nkind.N_IDENT) { return nil; }; + let s: *sym = scopelookupinmodule(c.cur, e.lhs.str, e.str); + if (s == nil) { return nil; }; + if (s.decl == nil) { return nil; }; + return s.decl.lhs; + }; return nil; }; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 3ff6ab72..29eefdfc 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -7423,7 +7423,32 @@ fn resolvealias(c: *checker, n: *node) *node = { let cur: *node = n; for (cur != nil) { if (cur.kind != nkind.N_TNAME) { return cur; }; - let s: *sym = scopelookup(c.cur, cur.str); + let nm: str = cur.str; + // #51: pkg.alias type refs land here as a single TNAME whose + // str is the joined form (lib/ww/parse/parse.ww:258-265 in + // parsetype). Split on the rightmost '.' and bind the leaf in + // the head module's scope. Mirrors cstage resolve_typename + // cmd/wcc/check.c:74-83 strrchr branch — without this the + // raw `os.oserror` lookup misses and checkisas false-positives + // every cross-module tagged scrutinee. + let dotidx: i32 = -1; + let i: i32 = 0; + for (i < nm.len) { + if (nm[i] == 46u8) { dotidx = i; }; + i += 1; + }; + let s: *sym = nil; + if (dotidx >= 0) { + let head: str; + head.ptr = nm.ptr; + head.len = dotidx; + let leaf: str; + leaf.ptr = nm.ptr + ((dotidx + 1): u64); + leaf.len = nm.len - dotidx - 1; + s = scopelookupinmodule(c.cur, head, leaf); + } else { + s = scopelookup(c.cur, nm); + }; if (s == nil) { return cur; }; if (s.skind != skind.SK_TYPE) { return cur; }; let body: *node = nil; @@ -7504,7 +7529,7 @@ fn iserrvariant(c: *checker, tagged: *node, v: *node) bool = { // scruttype — resolve the type expression for a match's // scrutinee. Handles nkind.N_IDENT (look up local/param's declared -// type) and nkind.N_DOT (struct-field access). Returns nil if we +// type) and nkind.N_DOT (module-qualified ref). Returns nil if we // can't statically determine the type. Used by exhaustiveness. fn scruttype(c: *checker, e: *node) *node = { if (e == nil) { return nil; }; @@ -7515,6 +7540,20 @@ fn scruttype(c: *checker, e: *node) *node = { // For nkind.N_LET / nkind.N_PARAM: declared type is decl.lhs. return s.decl.lhs; }; + // #51: `match (pkg.var)` / `pkg.var is T` — module-qualified ref. + // lhs is N_IDENT (module bareword), str is the leaf. Bind via + // scopelookupinmodule so the declared type carries the same + // shape resolvealias' dotted-name branch now consumes. Falls + // silently to nil when lhs is a value (struct-field access) — + // the rest of the lenient-check contract. + if (e.kind == nkind.N_DOT) { + if (e.lhs == nil) { return nil; }; + if (e.lhs.kind != nkind.N_IDENT) { return nil; }; + let s: *sym = scopelookupinmodule(c.cur, e.lhs.str, e.str); + if (s == nil) { return nil; }; + if (s.decl == nil) { return nil; }; + return s.decl.lhs; + }; return nil; };