selfhost/cmd/wcc/check: resolve cross-module type refs in is/as

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.
This commit is contained in:
2026-05-20 03:42:13 +09:00
parent cd44cc1893
commit c7c756d9c8
3 changed files with 123 additions and 6 deletions

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};