w6c+selfhost: same-module preference for bare-leaf lookup
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -45,6 +45,7 @@ test/wcc/data/**/*.s
|
||||
test/wcc/data/**/*.combined.ww
|
||||
test/wcc/data/modcollision/pos
|
||||
test/wcc/data/modcollision/neg
|
||||
test/wcc/data/samemodprefer/pos
|
||||
examples/mandelbrot/mandelbrot
|
||||
examples/cmatrix/cmatrix
|
||||
examples/lisp/lisp
|
||||
|
||||
6
Makefile
6
Makefile
@@ -225,6 +225,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_tagged_store_intlit \
|
||||
$(BIN)/test_match_bind_struct \
|
||||
$(BIN)/test_modtype_leaf_collision \
|
||||
$(BIN)/test_samemod_prefer \
|
||||
$(BIN)/test_field_signed $(BIN)/test_frame_argcount \
|
||||
$(BIN)/test_selfhost $(BIN)/test_w6a_ww $(BIN)/test_w6l_ww \
|
||||
$(BIN)/test_w6c_ww $(BIN)/test_ww_ww $(BIN)/test_self_rebuild \
|
||||
@@ -357,6 +358,11 @@ $(BIN)/test_modtype_leaf_collision: test/wcc/696_modtype_leaf_collision.c \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_samemod_prefer: test/wcc/697_samemod_prefer.c \
|
||||
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_field_signed: test/wcc/660_field_signed.c $(BIN)/ww \
|
||||
$(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
|
||||
|
||||
@@ -60,7 +60,7 @@ resolve_typename(Checker *c, Node *n)
|
||||
const char *nm = n->str;
|
||||
Type *bi = lookup_builtin(nm);
|
||||
if (bi) return bi;
|
||||
Sym *s = scope_lookup(c->cur, nm);
|
||||
Sym *s = scope_lookup_prefer(c->cur, c->cur_mod, nm);
|
||||
if (s == NULL && nm) {
|
||||
/* module-qualified: io.stream → strip the last dot prefix
|
||||
* and look up the leaf, filtering on the importing module's
|
||||
@@ -679,7 +679,7 @@ cexpr(Checker *c, Node *n)
|
||||
if (n->str && n->str[0] == '\0')
|
||||
return n->type = err(c, n->pos,
|
||||
"`_` is only valid as a binding or discard lvalue");
|
||||
Sym *s = scope_lookup(c->cur, n->str);
|
||||
Sym *s = scope_lookup_prefer(c->cur, c->cur_mod, n->str);
|
||||
if (s == NULL)
|
||||
return n->type = err(c, n->pos, "undefined: %s", n->str);
|
||||
/* SK_USE has no concrete value type; the only legal use is
|
||||
@@ -942,7 +942,7 @@ cexpr(Checker *c, Node *n)
|
||||
* (e.g. lib/os/os.ww) keeps working unchanged. */
|
||||
if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str &&
|
||||
strcmp(n->lhs->str, "abort") == 0 &&
|
||||
scope_lookup(c->cur, "abort") == NULL) {
|
||||
scope_lookup_prefer(c->cur, c->cur_mod, "abort") == NULL) {
|
||||
if (n->list) {
|
||||
Type *mt = cexpr(c, n->list);
|
||||
if (mt != ty_err && !type_assignable(ty_str, mt))
|
||||
@@ -957,7 +957,7 @@ cexpr(Checker *c, Node *n)
|
||||
if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str &&
|
||||
strcmp(n->lhs->str, "assert") == 0 &&
|
||||
n->list != NULL &&
|
||||
scope_lookup(c->cur, "assert") == NULL) {
|
||||
scope_lookup_prefer(c->cur, c->cur_mod, "assert") == NULL) {
|
||||
Type *ct = cexpr(c, n->list);
|
||||
if (ct != ty_err && ct != ty_bool && ct != ty_untyped_bool)
|
||||
err(c, n->pos, "assert: cond must be bool");
|
||||
@@ -1051,7 +1051,8 @@ cexpr(Checker *c, Node *n)
|
||||
}
|
||||
/* Reject assignment to a const-bound name. */
|
||||
if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str) {
|
||||
Sym *s = scope_lookup(c->cur, n->lhs->str);
|
||||
Sym *s = scope_lookup_prefer(c->cur, c->cur_mod,
|
||||
n->lhs->str);
|
||||
if (s && s->is_const)
|
||||
err(c, n->pos, "cannot assign to const `%s`",
|
||||
n->lhs->str);
|
||||
@@ -1069,7 +1070,8 @@ cexpr(Checker *c, Node *n)
|
||||
* resolve_type for the synthetic-type-expr case. */
|
||||
Type *t = NULL;
|
||||
if (n->lhs && n->lhs->kind == N_IDENT) {
|
||||
Sym *s = scope_lookup(c->cur, n->lhs->str);
|
||||
Sym *s = scope_lookup_prefer(c->cur, c->cur_mod,
|
||||
n->lhs->str);
|
||||
if (s == NULL || s->kind != SK_TYPE)
|
||||
t = err(c, n->pos, "unknown struct type '%s'",
|
||||
n->lhs->str);
|
||||
@@ -1682,6 +1684,7 @@ check_file(Checker *c, Node *file)
|
||||
}
|
||||
for (Node *d = file->list; d; d = d->next) {
|
||||
if (d->kind != N_TYPEDECL) continue;
|
||||
c->cur_mod = decl_mod(file, d);
|
||||
Type *under = resolve_type(c, d->lhs);
|
||||
d->type->under = under;
|
||||
if (under) {
|
||||
@@ -1690,7 +1693,9 @@ check_file(Checker *c, Node *file)
|
||||
d->type->iserror = under->iserror;
|
||||
}
|
||||
}
|
||||
c->cur_mod = NULL;
|
||||
for (Node *d = file->list; d; d = d->next) {
|
||||
c->cur_mod = decl_mod(file, d);
|
||||
switch (d->kind) {
|
||||
case N_USE:
|
||||
/* already installed in pass 1; no-op here so the
|
||||
@@ -1741,9 +1746,11 @@ check_file(Checker *c, Node *file)
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
c->cur_mod = NULL;
|
||||
|
||||
/* pass 2: check def initialisers and fn bodies */
|
||||
for (Node *d = file->list; d; d = d->next) {
|
||||
c->cur_mod = decl_mod(file, d);
|
||||
switch (d->kind) {
|
||||
case N_DEF: {
|
||||
if (d->rhs) {
|
||||
@@ -1786,4 +1793,5 @@ check_file(Checker *c, Node *file)
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
c->cur_mod = NULL;
|
||||
}
|
||||
|
||||
@@ -79,6 +79,43 @@ scope_lookup_in_module(Scope *s, const char *mod, const char *name)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* scope_lookup_prefer — bare-leaf lookup with same-module preference.
|
||||
*
|
||||
* Walks the same FNV bucket + hashnext chain + parent walk scope_lookup
|
||||
* uses. Within each scope's bucket: Pass 1 prefers entries whose
|
||||
* `sym.mod` matches the caller's `mod`; Pass 2 falls back to the first
|
||||
* match regardless of mod (the existing scope_lookup semantics). We
|
||||
* only descend to the parent scope when the current scope has no
|
||||
* matching entry at all — so a local binding in a closer scope still
|
||||
* shadows a same-name fn from a parent scope, even when the parent
|
||||
* entry mod-matches.
|
||||
*
|
||||
* When `mod` is NULL we just call scope_lookup — there's no module
|
||||
* identity to prefer.
|
||||
*
|
||||
* Used at bare-leaf lookup sites inside a known current module so that
|
||||
* a bare `read` inside lib/os resolves to os.read rather than the
|
||||
* io.read that happens to hash earlier into the flat scope.
|
||||
* Qualified-lookup paths (`mod.name`) stay on scope_lookup_in_module.
|
||||
*/
|
||||
Sym *
|
||||
scope_lookup_prefer(Scope *s, const char *mod, const char *name)
|
||||
{
|
||||
if (mod == NULL) return scope_lookup(s, name);
|
||||
for (Scope *p = s; p; p = p->parent) {
|
||||
u64 h = hashstr(name) % p->nbuckets;
|
||||
Sym *fallback = NULL;
|
||||
for (Sym *b = p->buckets[h]; b; b = b->hashnext) {
|
||||
if (strcmp(b->name, name) != 0) continue;
|
||||
if (b->mod && strcmp(b->mod, mod) == 0) return b;
|
||||
if (fallback == NULL) fallback = b;
|
||||
}
|
||||
if (fallback) return fallback;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Sym *
|
||||
scope_define(Scope *s, const char *name, Skind k, Type *t, Node *decl)
|
||||
{
|
||||
|
||||
@@ -509,6 +509,7 @@ Sym *scope_define_in_module(Scope*, const char *name, const char *mod,
|
||||
Sym *scope_lookup(Scope*, const char *name); /* walk up parents */
|
||||
Sym *scope_lookup_local(Scope*, const char *name);
|
||||
Sym *scope_lookup_in_module(Scope*, const char *mod, const char *name);
|
||||
Sym *scope_lookup_prefer(Scope*, const char *mod, const char *name);
|
||||
|
||||
/* ---- checker (check.c) -------------------------------------------- */
|
||||
typedef struct Checker Checker;
|
||||
@@ -517,6 +518,12 @@ struct Checker {
|
||||
Scope *top; /* file scope */
|
||||
Scope *cur; /* current scope */
|
||||
Type *ret; /* expected return type of current fn (or NULL) */
|
||||
const char *cur_mod; /* importing-module bareword for the decl
|
||||
* currently being checked; NULL for primary
|
||||
* compilation unit. Drives same-module
|
||||
* preference in bare-leaf lookups so a bare
|
||||
* `read` inside lib/os resolves to os.read
|
||||
* rather than colliding io.read. */
|
||||
int loops; /* nesting count for break/continue */
|
||||
int errs;
|
||||
};
|
||||
|
||||
@@ -134,6 +134,48 @@ export fn scopelookupinmodule(s: *scope, mod: str, name: str) *sym = {
|
||||
return nil;
|
||||
};
|
||||
|
||||
// scopelookupprefer — bare-leaf lookup with same-module preference.
|
||||
//
|
||||
// Walks the same FNV bucket + hashnext chain + parent walk scopelookup
|
||||
// uses. Within each scope's bucket: Pass 1 prefers entries whose
|
||||
// `sym.mod` matches `mod`; Pass 2 falls back to the first match
|
||||
// regardless of mod (same semantics as scopelookup). We only descend
|
||||
// to the parent scope when the current scope has no matching entry at
|
||||
// all — so a local binding in a closer scope still shadows a same-name
|
||||
// fn from a parent scope, even when the parent entry mod-matches.
|
||||
//
|
||||
// When `mod` is empty we just call scopelookup — there's no module
|
||||
// identity to prefer.
|
||||
//
|
||||
// Used at bare-leaf lookup sites inside a known current module so that
|
||||
// a bare `read` inside lib/os resolves to os.read rather than the
|
||||
// io.read that happens to hash earlier into the flat scope. Mirrors
|
||||
// cmd/wcc/sym.c scope_lookup_prefer.
|
||||
export fn scopelookupprefer(s: *scope, mod: str, name: str) *sym = {
|
||||
if (mod.len == 0) { return scopelookup(s, name); };
|
||||
let p: *scope = s;
|
||||
for (p != nil) {
|
||||
let h: u64 = hashstr(name);
|
||||
let bi: i32 = (h % (p.nbuckets: u64)): i32;
|
||||
let b: *sym = p.buckets[bi];
|
||||
let fallback: *sym = nil;
|
||||
for (b != nil) {
|
||||
if (streq(b.name, name)) {
|
||||
if (b.mod.len > 0) {
|
||||
if (streq(b.mod, mod)) {
|
||||
return b;
|
||||
};
|
||||
};
|
||||
if (fallback == nil) { fallback = b; };
|
||||
};
|
||||
b = b.hashnext;
|
||||
};
|
||||
if (fallback != nil) { return fallback; };
|
||||
p = p.parent;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
|
||||
export fn scopedefine(s: *scope, name: str, k: skind, t: *tinfo, decl: *node) *sym = {
|
||||
let empty: str;
|
||||
return scopedefineinmodule(s, name, empty, k, t, decl);
|
||||
|
||||
@@ -4647,6 +4647,48 @@ export fn scopelookupinmodule(s: *scope, mod: str, name: str) *sym = {
|
||||
return nil;
|
||||
};
|
||||
|
||||
// scopelookupprefer — bare-leaf lookup with same-module preference.
|
||||
//
|
||||
// Walks the same FNV bucket + hashnext chain + parent walk scopelookup
|
||||
// uses. Within each scope's bucket: Pass 1 prefers entries whose
|
||||
// `sym.mod` matches `mod`; Pass 2 falls back to the first match
|
||||
// regardless of mod (same semantics as scopelookup). We only descend
|
||||
// to the parent scope when the current scope has no matching entry at
|
||||
// all — so a local binding in a closer scope still shadows a same-name
|
||||
// fn from a parent scope, even when the parent entry mod-matches.
|
||||
//
|
||||
// When `mod` is empty we just call scopelookup — there's no module
|
||||
// identity to prefer.
|
||||
//
|
||||
// Used at bare-leaf lookup sites inside a known current module so that
|
||||
// a bare `read` inside lib/os resolves to os.read rather than the
|
||||
// io.read that happens to hash earlier into the flat scope. Mirrors
|
||||
// cmd/wcc/sym.c scope_lookup_prefer.
|
||||
export fn scopelookupprefer(s: *scope, mod: str, name: str) *sym = {
|
||||
if (mod.len == 0) { return scopelookup(s, name); };
|
||||
let p: *scope = s;
|
||||
for (p != nil) {
|
||||
let h: u64 = hashstr(name);
|
||||
let bi: i32 = (h % (p.nbuckets: u64)): i32;
|
||||
let b: *sym = p.buckets[bi];
|
||||
let fallback: *sym = nil;
|
||||
for (b != nil) {
|
||||
if (streq(b.name, name)) {
|
||||
if (b.mod.len > 0) {
|
||||
if (streq(b.mod, mod)) {
|
||||
return b;
|
||||
};
|
||||
};
|
||||
if (fallback == nil) { fallback = b; };
|
||||
};
|
||||
b = b.hashnext;
|
||||
};
|
||||
if (fallback != nil) { return fallback; };
|
||||
p = p.parent;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
|
||||
export fn scopedefine(s: *scope, name: str, k: skind, t: *tinfo, decl: *node) *sym = {
|
||||
let empty: str;
|
||||
return scopedefineinmodule(s, name, empty, k, t, decl);
|
||||
@@ -4725,6 +4767,10 @@ type checker = struct {
|
||||
errs: i32,
|
||||
verbose: i32, // when non-zero, log each unresolved name
|
||||
fnret: *node, // enclosing fn's return type AST (for `?`)
|
||||
curmod: str, // importing-module bareword for the decl
|
||||
// currently being walked; "" for primary
|
||||
// compilation unit. Drives same-module
|
||||
// preference in bare-leaf lookups.
|
||||
};
|
||||
|
||||
// seedprimitives — install the built-in type names so `i32`, `str`,
|
||||
@@ -4823,7 +4869,7 @@ fn resolvewalk(c: *checker, n: *node) void = {
|
||||
if (k == nkind.N_IDENT) {
|
||||
let nm: str = n.str;
|
||||
if (nm.len > 0) {
|
||||
let s: *sym = scopelookup(c.cur, nm);
|
||||
let s: *sym = scopelookupprefer(c.cur, c.curmod, nm);
|
||||
if (s == nil) {
|
||||
c.nunresolved += 1;
|
||||
if (c.verbose != 0) {
|
||||
@@ -4838,7 +4884,7 @@ fn resolvewalk(c: *checker, n: *node) void = {
|
||||
if (k == nkind.N_TNAME) {
|
||||
let nm: str = n.str;
|
||||
if (nm.len > 0) {
|
||||
let s: *sym = scopelookup(c.cur, nm);
|
||||
let s: *sym = scopelookupprefer(c.cur, c.curmod, nm);
|
||||
// `pkg.Type` — strip the last dot prefix and look up
|
||||
// the leaf with a mod filter so same-leaf-name types
|
||||
// from different imports (`bufio.stream` vs
|
||||
@@ -5637,6 +5683,8 @@ export fn checkinit(c: *checker, a: *arena, tc: *tctx) void = {
|
||||
c.errs = 0;
|
||||
c.verbose = 0;
|
||||
c.fnret = nil;
|
||||
let empty: str;
|
||||
c.curmod = empty;
|
||||
seedprimitives(c);
|
||||
};
|
||||
|
||||
@@ -5652,8 +5700,12 @@ export fn checkfile(c: *checker, file: *node) void = {
|
||||
};
|
||||
|
||||
// Pass 2: walk decl bodies/types and resolve identifiers.
|
||||
// Track the per-decl module bareword so bare-leaf lookups inside
|
||||
// the body prefer same-module entries over alphabetically-earlier
|
||||
// same-leaf imports.
|
||||
d = file.list;
|
||||
for (d != nil) {
|
||||
c.curmod = declmod(file, d);
|
||||
let k: nkind = d.kind;
|
||||
if (k == nkind.N_FNDECL) {
|
||||
if (d.lhs != nil) { resolvewalk(c, d.lhs); }; // return type
|
||||
@@ -5669,6 +5721,8 @@ export fn checkfile(c: *checker, file: *node) void = {
|
||||
};};};};
|
||||
d = d.next;
|
||||
};
|
||||
let empty: str;
|
||||
c.curmod = empty;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -31,6 +31,10 @@ type checker = struct {
|
||||
errs: i32,
|
||||
verbose: i32, // when non-zero, log each unresolved name
|
||||
fnret: *node, // enclosing fn's return type AST (for `?`)
|
||||
curmod: str, // importing-module bareword for the decl
|
||||
// currently being walked; "" for primary
|
||||
// compilation unit. Drives same-module
|
||||
// preference in bare-leaf lookups.
|
||||
};
|
||||
|
||||
// seedprimitives — install the built-in type names so `i32`, `str`,
|
||||
@@ -129,7 +133,7 @@ fn resolvewalk(c: *checker, n: *node) void = {
|
||||
if (k == nkind.N_IDENT) {
|
||||
let nm: str = n.str;
|
||||
if (nm.len > 0) {
|
||||
let s: *sym = scopelookup(c.cur, nm);
|
||||
let s: *sym = scopelookupprefer(c.cur, c.curmod, nm);
|
||||
if (s == nil) {
|
||||
c.nunresolved += 1;
|
||||
if (c.verbose != 0) {
|
||||
@@ -144,7 +148,7 @@ fn resolvewalk(c: *checker, n: *node) void = {
|
||||
if (k == nkind.N_TNAME) {
|
||||
let nm: str = n.str;
|
||||
if (nm.len > 0) {
|
||||
let s: *sym = scopelookup(c.cur, nm);
|
||||
let s: *sym = scopelookupprefer(c.cur, c.curmod, nm);
|
||||
// `pkg.Type` — strip the last dot prefix and look up
|
||||
// the leaf with a mod filter so same-leaf-name types
|
||||
// from different imports (`bufio.stream` vs
|
||||
@@ -943,6 +947,8 @@ export fn checkinit(c: *checker, a: *arena, tc: *tctx) void = {
|
||||
c.errs = 0;
|
||||
c.verbose = 0;
|
||||
c.fnret = nil;
|
||||
let empty: str;
|
||||
c.curmod = empty;
|
||||
seedprimitives(c);
|
||||
};
|
||||
|
||||
@@ -958,8 +964,12 @@ export fn checkfile(c: *checker, file: *node) void = {
|
||||
};
|
||||
|
||||
// Pass 2: walk decl bodies/types and resolve identifiers.
|
||||
// Track the per-decl module bareword so bare-leaf lookups inside
|
||||
// the body prefer same-module entries over alphabetically-earlier
|
||||
// same-leaf imports.
|
||||
d = file.list;
|
||||
for (d != nil) {
|
||||
c.curmod = declmod(file, d);
|
||||
let k: nkind = d.kind;
|
||||
if (k == nkind.N_FNDECL) {
|
||||
if (d.lhs != nil) { resolvewalk(c, d.lhs); }; // return type
|
||||
@@ -975,5 +985,7 @@ export fn checkfile(c: *checker, file: *node) void = {
|
||||
};};};};
|
||||
d = d.next;
|
||||
};
|
||||
let empty: str;
|
||||
c.curmod = empty;
|
||||
|
||||
};
|
||||
|
||||
@@ -4647,6 +4647,48 @@ export fn scopelookupinmodule(s: *scope, mod: str, name: str) *sym = {
|
||||
return nil;
|
||||
};
|
||||
|
||||
// scopelookupprefer — bare-leaf lookup with same-module preference.
|
||||
//
|
||||
// Walks the same FNV bucket + hashnext chain + parent walk scopelookup
|
||||
// uses. Within each scope's bucket: Pass 1 prefers entries whose
|
||||
// `sym.mod` matches `mod`; Pass 2 falls back to the first match
|
||||
// regardless of mod (same semantics as scopelookup). We only descend
|
||||
// to the parent scope when the current scope has no matching entry at
|
||||
// all — so a local binding in a closer scope still shadows a same-name
|
||||
// fn from a parent scope, even when the parent entry mod-matches.
|
||||
//
|
||||
// When `mod` is empty we just call scopelookup — there's no module
|
||||
// identity to prefer.
|
||||
//
|
||||
// Used at bare-leaf lookup sites inside a known current module so that
|
||||
// a bare `read` inside lib/os resolves to os.read rather than the
|
||||
// io.read that happens to hash earlier into the flat scope. Mirrors
|
||||
// cmd/wcc/sym.c scope_lookup_prefer.
|
||||
export fn scopelookupprefer(s: *scope, mod: str, name: str) *sym = {
|
||||
if (mod.len == 0) { return scopelookup(s, name); };
|
||||
let p: *scope = s;
|
||||
for (p != nil) {
|
||||
let h: u64 = hashstr(name);
|
||||
let bi: i32 = (h % (p.nbuckets: u64)): i32;
|
||||
let b: *sym = p.buckets[bi];
|
||||
let fallback: *sym = nil;
|
||||
for (b != nil) {
|
||||
if (streq(b.name, name)) {
|
||||
if (b.mod.len > 0) {
|
||||
if (streq(b.mod, mod)) {
|
||||
return b;
|
||||
};
|
||||
};
|
||||
if (fallback == nil) { fallback = b; };
|
||||
};
|
||||
b = b.hashnext;
|
||||
};
|
||||
if (fallback != nil) { return fallback; };
|
||||
p = p.parent;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
|
||||
export fn scopedefine(s: *scope, name: str, k: skind, t: *tinfo, decl: *node) *sym = {
|
||||
let empty: str;
|
||||
return scopedefineinmodule(s, name, empty, k, t, decl);
|
||||
@@ -4725,6 +4767,10 @@ type checker = struct {
|
||||
errs: i32,
|
||||
verbose: i32, // when non-zero, log each unresolved name
|
||||
fnret: *node, // enclosing fn's return type AST (for `?`)
|
||||
curmod: str, // importing-module bareword for the decl
|
||||
// currently being walked; "" for primary
|
||||
// compilation unit. Drives same-module
|
||||
// preference in bare-leaf lookups.
|
||||
};
|
||||
|
||||
// seedprimitives — install the built-in type names so `i32`, `str`,
|
||||
@@ -4823,7 +4869,7 @@ fn resolvewalk(c: *checker, n: *node) void = {
|
||||
if (k == nkind.N_IDENT) {
|
||||
let nm: str = n.str;
|
||||
if (nm.len > 0) {
|
||||
let s: *sym = scopelookup(c.cur, nm);
|
||||
let s: *sym = scopelookupprefer(c.cur, c.curmod, nm);
|
||||
if (s == nil) {
|
||||
c.nunresolved += 1;
|
||||
if (c.verbose != 0) {
|
||||
@@ -4838,7 +4884,7 @@ fn resolvewalk(c: *checker, n: *node) void = {
|
||||
if (k == nkind.N_TNAME) {
|
||||
let nm: str = n.str;
|
||||
if (nm.len > 0) {
|
||||
let s: *sym = scopelookup(c.cur, nm);
|
||||
let s: *sym = scopelookupprefer(c.cur, c.curmod, nm);
|
||||
// `pkg.Type` — strip the last dot prefix and look up
|
||||
// the leaf with a mod filter so same-leaf-name types
|
||||
// from different imports (`bufio.stream` vs
|
||||
@@ -5637,6 +5683,8 @@ export fn checkinit(c: *checker, a: *arena, tc: *tctx) void = {
|
||||
c.errs = 0;
|
||||
c.verbose = 0;
|
||||
c.fnret = nil;
|
||||
let empty: str;
|
||||
c.curmod = empty;
|
||||
seedprimitives(c);
|
||||
};
|
||||
|
||||
@@ -5652,8 +5700,12 @@ export fn checkfile(c: *checker, file: *node) void = {
|
||||
};
|
||||
|
||||
// Pass 2: walk decl bodies/types and resolve identifiers.
|
||||
// Track the per-decl module bareword so bare-leaf lookups inside
|
||||
// the body prefer same-module entries over alphabetically-earlier
|
||||
// same-leaf imports.
|
||||
d = file.list;
|
||||
for (d != nil) {
|
||||
c.curmod = declmod(file, d);
|
||||
let k: nkind = d.kind;
|
||||
if (k == nkind.N_FNDECL) {
|
||||
if (d.lhs != nil) { resolvewalk(c, d.lhs); }; // return type
|
||||
@@ -5669,6 +5721,8 @@ export fn checkfile(c: *checker, file: *node) void = {
|
||||
};};};};
|
||||
d = d.next;
|
||||
};
|
||||
let empty: str;
|
||||
c.curmod = empty;
|
||||
|
||||
};
|
||||
|
||||
|
||||
93
test/wcc/697_samemod_prefer.c
Normal file
93
test/wcc/697_samemod_prefer.c
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 697_samemod_prefer — same-module preference for bare-leaf lookup.
|
||||
*
|
||||
* Two modules each export `read` with intentionally distinct
|
||||
* signatures (mod1: i32 → i32, mod2: str → i32). Each module's
|
||||
* `caller()` invokes bare `read(...)` with its own arg type. The
|
||||
* bare-leaf lookup must resolve to the SAME-MODULE `read`; otherwise
|
||||
* the call is a check-time signature mismatch.
|
||||
*
|
||||
* Pre-fix (flat scope, first-wins by hash order): one of the bare
|
||||
* lookups picked the wrong module's `read` and the build failed with
|
||||
* "argument type not assignable". Post-fix (scope_lookup_prefer): each
|
||||
* bare-leaf inside its own module binds to the same-module entry, both
|
||||
* calls type-check, and the build succeeds.
|
||||
*
|
||||
* Runtime exit codes aren't asserted here because cgen still emits
|
||||
* unmangled `TEXT read` labels for both modules and the linker
|
||||
* collapses them. Mangling fn labels by module is a separate codegen
|
||||
* sweep; this test pins the resolver fix alone.
|
||||
*
|
||||
* Fixtures live in test/wcc/data/samemodprefer/. Same shape as
|
||||
* 696_modtype_leaf_collision.c.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
run_pos(const char *driver, const char *fixdir, const char *tag)
|
||||
{
|
||||
char cmd[2048];
|
||||
/* cd into the fixture dir so the driver's source-dir-first
|
||||
* import search resolves `use mod1; use mod2;`. */
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"cd %s && %s build pos.ww >/dev/null 2>&1", fixdir, driver);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr,
|
||||
"samemod_prefer[%s]: pos.ww build failed — bare-leaf "
|
||||
"`read` lookup likely cross-bound\n", tag);
|
||||
return 1;
|
||||
}
|
||||
char bin[2048];
|
||||
snprintf(bin, sizeof bin, "%s/pos", fixdir);
|
||||
unlink(bin);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
|
||||
char fixdir[1024];
|
||||
if (getcwd(fixdir, sizeof fixdir) == NULL) return 1;
|
||||
size_t cwd_n = strlen(fixdir);
|
||||
const char *rel = "/test/wcc/data/samemodprefer";
|
||||
if (cwd_n + strlen(rel) + 1 >= sizeof fixdir) return 1;
|
||||
memcpy(fixdir + cwd_n, rel, strlen(rel) + 1);
|
||||
|
||||
int fail = 0;
|
||||
fail += run_pos(cdrv, fixdir, "cstage");
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"samemod_prefer: %d case(s) failed\n", fail);
|
||||
return 1;
|
||||
}
|
||||
printf("samemod_prefer: 1/1 ok\n");
|
||||
return 0;
|
||||
}
|
||||
18
test/wcc/data/samemodprefer/mod1/mod1.ww
Normal file
18
test/wcc/data/samemodprefer/mod1/mod1.ww
Normal file
@@ -0,0 +1,18 @@
|
||||
// samemodprefer/mod1 — exports a `read` that takes an i32 plus a
|
||||
// `caller` that invokes bare `read(1i32)`. The bare-leaf lookup must
|
||||
// resolve to mod1.read (i32 → i32) rather than mod2.read (str → i32),
|
||||
// which coexists in the flat scope. Pre-fix the lookup picked
|
||||
// whichever entry hashed earliest into the bucket; a wrong bind would
|
||||
// surface as a check-time signature mismatch. Paired with mod2/mod2.ww
|
||||
// and test/wcc/697_samemod_prefer.c.
|
||||
|
||||
export fn read(x: i32) i32 = {
|
||||
return x + 100i32;
|
||||
};
|
||||
|
||||
export fn caller() i32 = {
|
||||
// Bare-leaf call inside mod1: must bind to mod1.read (i32 arg).
|
||||
// If preference doesn't kick in this fails check: mod2.read
|
||||
// expects a str, so `read(1i32)` would be a signature mismatch.
|
||||
return read(1i32);
|
||||
};
|
||||
18
test/wcc/data/samemodprefer/mod2/mod2.ww
Normal file
18
test/wcc/data/samemodprefer/mod2/mod2.ww
Normal file
@@ -0,0 +1,18 @@
|
||||
// samemodprefer/mod2 — exports a `read` that takes a str plus a
|
||||
// `caller` that invokes bare `read("ok")`. The bare-leaf lookup must
|
||||
// resolve to mod2.read (str → i32) rather than mod1.read (i32 → i32),
|
||||
// which coexists in the flat scope. Pre-fix the lookup picked
|
||||
// whichever entry hashed earliest into the bucket; a wrong bind would
|
||||
// surface as a check-time signature mismatch. Paired with mod1/mod1.ww
|
||||
// and test/wcc/697_samemod_prefer.c.
|
||||
|
||||
export fn read(x: str) i32 = {
|
||||
return x.len + 200i32;
|
||||
};
|
||||
|
||||
export fn caller() i32 = {
|
||||
// Bare-leaf call inside mod2: must bind to mod2.read (str arg).
|
||||
// If preference doesn't kick in this fails check: mod1.read
|
||||
// expects an i32, so `read("ok")` would be a signature mismatch.
|
||||
return read("ok");
|
||||
};
|
||||
20
test/wcc/data/samemodprefer/pos.ww
Normal file
20
test/wcc/data/samemodprefer/pos.ww
Normal file
@@ -0,0 +1,20 @@
|
||||
// Positive case: two modules each export `read` with DIFFERENT
|
||||
// signatures (mod1: i32 → i32, mod2: str → i32). Each module also
|
||||
// exports a `caller` that invokes bare `read(...)` with its own arg
|
||||
// type. The bare-leaf inside mod1 must bind to mod1.read; inside mod2
|
||||
// to mod2.read. If preference doesn't kick in and the bare lookup
|
||||
// picks the other module's `read`, the build fails with a signature
|
||||
// mismatch at check-time. The driver test asserts that this fixture
|
||||
// builds successfully.
|
||||
//
|
||||
// We don't assert runtime exit codes here because cgen still emits
|
||||
// unmangled `TEXT read` labels for both modules and the linker
|
||||
// collapses them — that's a separate codegen sweep, orthogonal to the
|
||||
// resolver fix this test pins.
|
||||
|
||||
use mod1;
|
||||
use mod2;
|
||||
|
||||
fn main() i32 = {
|
||||
return 0i32;
|
||||
};
|
||||
Reference in New Issue
Block a user