compiler: load imports and enforce package exports
This commit is contained in:
@@ -77,6 +77,7 @@ main(int argc, char **argv)
|
|||||||
|
|
||||||
check_init(&c, a);
|
check_init(&c, a);
|
||||||
c.is_test = testmode;
|
c.is_test = testmode;
|
||||||
|
c.sep_mode = sepmode;
|
||||||
check_file(&c, file);
|
check_file(&c, file);
|
||||||
if (c.errs) return 1;
|
if (c.errs) return 1;
|
||||||
|
|
||||||
|
|||||||
@@ -543,8 +543,13 @@ wwi_emit(Checker *c, FILE *of, Node *file)
|
|||||||
k++;
|
k++;
|
||||||
}
|
}
|
||||||
qsort(us, (size_t)nuse, sizeof *us, usecmp);
|
qsort(us, (size_t)nuse, sizeof *us, usecmp);
|
||||||
for (int i = 0; i < nuse; i++)
|
const char *previous = NULL;
|
||||||
|
for (int i = 0; i < nuse; i++) {
|
||||||
|
if (previous && strcmp(previous, us[i].path) == 0)
|
||||||
|
continue;
|
||||||
fprintf(of, "import %s;\n", us[i].path);
|
fprintf(of, "import %s;\n", us[i].path);
|
||||||
|
previous = us[i].path;
|
||||||
|
}
|
||||||
free(us);
|
free(us);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
154
cmd/wcc/check.c
154
cmd/wcc/check.c
@@ -59,6 +59,9 @@ lookup_builtin(const char *name)
|
|||||||
|
|
||||||
static const char *decl_mod(Node *file, Node *d);
|
static const char *decl_mod(Node *file, Node *d);
|
||||||
static const char *use_path(Node *file, const char *curmod, const char *alias);
|
static const char *use_path(Node *file, const char *curmod, const char *alias);
|
||||||
|
static int src_imports(Node *file, const char *modtag, const char *name);
|
||||||
|
static Sym *lookup_visible(Checker *c, const char *name);
|
||||||
|
static Sym *lookup_visible_type(Checker *c, const char *name);
|
||||||
static void resolve_typedecl(Checker *c, Node *d);
|
static void resolve_typedecl(Checker *c, Node *d);
|
||||||
|
|
||||||
static Type *
|
static Type *
|
||||||
@@ -69,7 +72,7 @@ resolve_typename(Checker *c, Node *n)
|
|||||||
if (bi) return bi;
|
if (bi) return bi;
|
||||||
/* #225: kind-filtered so a same-named value binding (param/let/fn)
|
/* #225: kind-filtered so a same-named value binding (param/let/fn)
|
||||||
* in a closer scope can't hide the type binding it shadows. */
|
* in a closer scope can't hide the type binding it shadows. */
|
||||||
Sym *s = scope_lookup_type(c->cur, c->cur_mod, nm);
|
Sym *s = lookup_visible_type(c, nm);
|
||||||
if (s == NULL && nm) {
|
if (s == NULL && nm) {
|
||||||
/* module-qualified: io.stream → strip the last dot prefix
|
/* module-qualified: io.stream → strip the last dot prefix
|
||||||
* and look up the leaf, filtering on the importing module's
|
* and look up the leaf, filtering on the importing module's
|
||||||
@@ -88,9 +91,9 @@ resolve_typename(Checker *c, Node *n)
|
|||||||
* import path (symbols are path-keyed). */
|
* import path (symbols are path-keyed). */
|
||||||
const char *mk = use_path(c->file, c->cur_mod,
|
const char *mk = use_path(c->file, c->cur_mod,
|
||||||
head);
|
head);
|
||||||
if (mk == NULL) mk = head;
|
if (mk != NULL)
|
||||||
s = scope_lookup_in_module(c->cur, mk,
|
s = scope_lookup_in_module(c->cur, mk,
|
||||||
dot + 1);
|
dot + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -422,7 +425,7 @@ assignable_addrfn(Checker *c, Type *dst, Node *rhs)
|
|||||||
if (rhs->kind != N_UN || rhs->op != TK_AMP) return 0;
|
if (rhs->kind != N_UN || rhs->op != TK_AMP) return 0;
|
||||||
Node *id = rhs->lhs;
|
Node *id = rhs->lhs;
|
||||||
if (id == NULL || id->kind != N_IDENT || id->str == NULL) return 0;
|
if (id == NULL || id->kind != N_IDENT || id->str == NULL) return 0;
|
||||||
Sym *s = scope_lookup_prefer(c->cur, c->cur_mod, id->str);
|
Sym *s = lookup_visible(c, id->str);
|
||||||
if (s == NULL || s->kind != SK_FN) return 0;
|
if (s == NULL || s->kind != SK_FN) return 0;
|
||||||
Type *fnty = s->type;
|
Type *fnty = s->type;
|
||||||
if (fnty == NULL || fnty->kind != TY_FN) return 0;
|
if (fnty == NULL || fnty->kind != TY_FN) return 0;
|
||||||
@@ -606,7 +609,7 @@ eval_def_const(Checker *c, Node *n, u64 *out, int depth)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
case N_IDENT: {
|
case N_IDENT: {
|
||||||
Sym *s = scope_lookup_prefer(c->cur, c->cur_mod, n->str);
|
Sym *s = lookup_visible(c, n->str);
|
||||||
if (s == NULL || s->kind != SK_DEF ||
|
if (s == NULL || s->kind != SK_DEF ||
|
||||||
s->decl == NULL || s->decl->rhs == NULL)
|
s->decl == NULL || s->decl->rhs == NULL)
|
||||||
return 0;
|
return 0;
|
||||||
@@ -616,7 +619,7 @@ eval_def_const(Checker *c, Node *n, u64 *out, int depth)
|
|||||||
if (n->lhs == NULL || n->lhs->kind != N_IDENT) return 0;
|
if (n->lhs == NULL || n->lhs->kind != N_IDENT) return 0;
|
||||||
/* M1 #22: map the qualifier alias to its dotted import path. */
|
/* M1 #22: map the qualifier alias to its dotted import path. */
|
||||||
const char *mk = use_path(c->file, c->cur_mod, n->lhs->str);
|
const char *mk = use_path(c->file, c->cur_mod, n->lhs->str);
|
||||||
if (mk == NULL) mk = n->lhs->str;
|
if (mk == NULL) return 0;
|
||||||
Sym *s = scope_lookup_in_module(c->cur, mk, n->str);
|
Sym *s = scope_lookup_in_module(c->cur, mk, n->str);
|
||||||
if (s == NULL || s->kind != SK_DEF ||
|
if (s == NULL || s->kind != SK_DEF ||
|
||||||
s->decl == NULL || s->decl->rhs == NULL)
|
s->decl == NULL || s->decl->rhs == NULL)
|
||||||
@@ -1414,7 +1417,7 @@ cexpr(Checker *c, Node *n)
|
|||||||
if (n->str && n->str[0] == '\0')
|
if (n->str && n->str[0] == '\0')
|
||||||
return n->type = err(c, n->pos,
|
return n->type = err(c, n->pos,
|
||||||
"`_` is only valid as a binding or discard lvalue");
|
"`_` is only valid as a binding or discard lvalue");
|
||||||
Sym *s = scope_lookup_prefer(c->cur, c->cur_mod, n->str);
|
Sym *s = lookup_visible(c, n->str);
|
||||||
if (s == NULL)
|
if (s == NULL)
|
||||||
return n->type = err(c, n->pos, "undefined: %s", n->str);
|
return n->type = err(c, n->pos, "undefined: %s", n->str);
|
||||||
/* SK_USE has no concrete value type; the only legal use is
|
/* SK_USE has no concrete value type; the only legal use is
|
||||||
@@ -1444,8 +1447,7 @@ cexpr(Checker *c, Node *n)
|
|||||||
* Color sitting at the head of the flat scope chain —
|
* Color sitting at the head of the flat scope chain —
|
||||||
* symmetric with wwstage's enumlookup graduation. */
|
* symmetric with wwstage's enumlookup graduation. */
|
||||||
if (n->lhs && n->lhs->kind == N_IDENT) {
|
if (n->lhs && n->lhs->kind == N_IDENT) {
|
||||||
Sym *ms = scope_lookup_prefer(c->cur, c->cur_mod,
|
Sym *ms = lookup_visible(c, n->lhs->str);
|
||||||
n->lhs->str);
|
|
||||||
if (ms && (ms->kind == SK_USE || ms->use_alias)) {
|
if (ms && (ms->kind == SK_USE || ms->use_alias)) {
|
||||||
/* Module-qualified ref. `use_alias` covers
|
/* Module-qualified ref. `use_alias` covers
|
||||||
* the self-import case where the module's
|
* the self-import case where the module's
|
||||||
@@ -1460,17 +1462,28 @@ cexpr(Checker *c, Node *n)
|
|||||||
* that path before looking up the leaf. */
|
* that path before looking up the leaf. */
|
||||||
const char *mk = use_path(c->file, c->cur_mod,
|
const char *mk = use_path(c->file, c->cur_mod,
|
||||||
n->lhs->str);
|
n->lhs->str);
|
||||||
if (mk == NULL) mk = n->lhs->str;
|
if (mk == NULL) {
|
||||||
Sym *fs = scope_lookup_in_module(c->cur,
|
if (ms->kind == SK_USE)
|
||||||
mk, n->str);
|
return n->type = err(c, n->pos,
|
||||||
if (fs)
|
"package '%s' is not directly imported",
|
||||||
return n->type = fs->type;
|
n->lhs->str);
|
||||||
if (ms->kind == SK_USE) {
|
} else {
|
||||||
/* Pure SK_USE with missing leaf:
|
Sym *fs = scope_lookup_in_module(c->cur,
|
||||||
* external declaration. Codegen
|
mk, n->str);
|
||||||
* emits CALL/MOVQ by the leaf name
|
if (fs)
|
||||||
* and the linker resolves it. */
|
return n->type = fs->type;
|
||||||
return n->type = ty_err;
|
/* A bare `w6c -T` intentionally leaves the
|
||||||
|
* compiler-generated test.run hook external; the
|
||||||
|
* ordinary driver supplies lib/test. This is the only
|
||||||
|
* missing direct member that is not a package export
|
||||||
|
* error. */
|
||||||
|
if (c->sep_mode && ms->kind == SK_USE
|
||||||
|
&& n != c->synth_test_run)
|
||||||
|
return n->type = err(c, n->pos,
|
||||||
|
"package '%s' has no exported declaration '%s'",
|
||||||
|
n->lhs->str, n->str);
|
||||||
|
if (ms->kind == SK_USE)
|
||||||
|
return n->type = ty_err;
|
||||||
}
|
}
|
||||||
/* SK_TYPE with use_alias=1 and no leaf
|
/* SK_TYPE with use_alias=1 and no leaf
|
||||||
* found: fall through so the enum / type-
|
* found: fall through so the enum / type-
|
||||||
@@ -1616,8 +1629,7 @@ cexpr(Checker *c, Node *n)
|
|||||||
* def-as-constant splice-index is deferred (no
|
* def-as-constant splice-index is deferred (no
|
||||||
* consumer). */
|
* consumer). */
|
||||||
if (n->lhs->kind == N_IDENT) {
|
if (n->lhs->kind == N_IDENT) {
|
||||||
Sym *s = scope_lookup_prefer(c->cur,
|
Sym *s = lookup_visible(c, n->lhs->str);
|
||||||
c->cur_mod, n->lhs->str);
|
|
||||||
if (s && s->kind == SK_DEF)
|
if (s && s->kind == SK_DEF)
|
||||||
return n->type = err(c, n->pos,
|
return n->type = err(c, n->pos,
|
||||||
"cannot index a def-constant str "
|
"cannot index a def-constant str "
|
||||||
@@ -1855,7 +1867,7 @@ cexpr(Checker *c, Node *n)
|
|||||||
* (e.g. lib/os/os.ww) keeps working unchanged. */
|
* (e.g. lib/os/os.ww) keeps working unchanged. */
|
||||||
if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str &&
|
if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str &&
|
||||||
strcmp(n->lhs->str, "abort") == 0 &&
|
strcmp(n->lhs->str, "abort") == 0 &&
|
||||||
scope_lookup_prefer(c->cur, c->cur_mod, "abort") == NULL) {
|
lookup_visible(c, "abort") == NULL) {
|
||||||
if (n->list) {
|
if (n->list) {
|
||||||
Type *mt = cexpr(c, n->list);
|
Type *mt = cexpr(c, n->list);
|
||||||
if (mt != ty_err && !type_assignable(ty_str, mt))
|
if (mt != ty_err && !type_assignable(ty_str, mt))
|
||||||
@@ -1870,7 +1882,7 @@ cexpr(Checker *c, Node *n)
|
|||||||
if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str &&
|
if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str &&
|
||||||
strcmp(n->lhs->str, "assert") == 0 &&
|
strcmp(n->lhs->str, "assert") == 0 &&
|
||||||
n->list != NULL &&
|
n->list != NULL &&
|
||||||
scope_lookup_prefer(c->cur, c->cur_mod, "assert") == NULL) {
|
lookup_visible(c, "assert") == NULL) {
|
||||||
Type *ct = cexpr(c, n->list);
|
Type *ct = cexpr(c, n->list);
|
||||||
if (ct != ty_err && ct != ty_bool && ct != ty_untyped_bool)
|
if (ct != ty_err && ct != ty_bool && ct != ty_untyped_bool)
|
||||||
err(c, n->pos, "assert: cond must be bool");
|
err(c, n->pos, "assert: cond must be bool");
|
||||||
@@ -2019,8 +2031,7 @@ cexpr(Checker *c, Node *n)
|
|||||||
return n->type = ty_void;
|
return n->type = ty_void;
|
||||||
}
|
}
|
||||||
if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str) {
|
if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str) {
|
||||||
Sym *s = scope_lookup_prefer(c->cur, c->cur_mod,
|
Sym *s = lookup_visible(c, n->lhs->str);
|
||||||
n->lhs->str);
|
|
||||||
if (s && s->is_const)
|
if (s && s->is_const)
|
||||||
err(c, n->pos, "cannot assign to const `%s`",
|
err(c, n->pos, "cannot assign to const `%s`",
|
||||||
n->lhs->str);
|
n->lhs->str);
|
||||||
@@ -2072,8 +2083,7 @@ cexpr(Checker *c, Node *n)
|
|||||||
* resolve_type for the synthetic-type-expr case. */
|
* resolve_type for the synthetic-type-expr case. */
|
||||||
Type *t = NULL;
|
Type *t = NULL;
|
||||||
if (n->lhs && n->lhs->kind == N_IDENT) {
|
if (n->lhs && n->lhs->kind == N_IDENT) {
|
||||||
Sym *s = scope_lookup_prefer(c->cur, c->cur_mod,
|
Sym *s = lookup_visible(c, n->lhs->str);
|
||||||
n->lhs->str);
|
|
||||||
if (s == NULL || s->kind != SK_TYPE)
|
if (s == NULL || s->kind != SK_TYPE)
|
||||||
t = err(c, n->pos, "unknown struct type '%s'",
|
t = err(c, n->pos, "unknown struct type '%s'",
|
||||||
n->lhs->str);
|
n->lhs->str);
|
||||||
@@ -2865,28 +2875,34 @@ decl_mod(Node *file, Node *d)
|
|||||||
* concatenated unit may bind the same leaf alias to different paths
|
* concatenated unit may bind the same leaf alias to different paths
|
||||||
* (sha256's `import crypto.math` and strconv's `import math` both bind
|
* (sha256's `import crypto.math` and strconv's `import math` both bind
|
||||||
* alias `math`). The import declared in the SAME module as the
|
* alias `math`). The import declared in the SAME module as the
|
||||||
* reference (`curmod`) is the authoritative one; preferring it closes
|
* reference (`curmod`) is the authoritative one; requiring it closes both
|
||||||
* the cross-module mis-resolution a first-match scan caused. Falls back
|
* cross-module mis-resolution and accidental transitive-import visibility.
|
||||||
* to any matching alias when the referencing module has no own import
|
* Returns NULL if the referencing package has no such `use`.
|
||||||
* (single-occurrence case, unchanged). Returns NULL if no such `use`.
|
|
||||||
*/
|
*/
|
||||||
static const char *
|
static const char *
|
||||||
use_path(Node *file, const char *curmod, const char *alias)
|
use_path(Node *file, const char *curmod, const char *alias)
|
||||||
{
|
{
|
||||||
if (file == NULL || alias == NULL) return NULL;
|
if (file == NULL || alias == NULL) return NULL;
|
||||||
const char *any = NULL;
|
/* Legacy inline multi-package units may spell a package's own
|
||||||
|
* declarations as `pkg.member`. That is self-qualification, not an
|
||||||
|
* imported namespace; preserve it without reopening transitive lookup. */
|
||||||
|
if (curmod != NULL) {
|
||||||
|
const char *dot = strrchr(curmod, '.');
|
||||||
|
const char *leaf = dot ? dot + 1 : curmod;
|
||||||
|
if (strcmp(alias, leaf) == 0) return curmod;
|
||||||
|
}
|
||||||
for (Node *u = file->list; u; u = u->next) {
|
for (Node *u = file->list; u; u = u->next) {
|
||||||
if (u->kind != N_USE || u->str == NULL
|
if (u->kind != N_USE || u->str == NULL
|
||||||
|| strcmp(u->str, alias) != 0)
|
|| strcmp(u->str, alias) != 0)
|
||||||
continue;
|
continue;
|
||||||
const char *p = u->usepath ? u->usepath : u->str;
|
const char *p = u->usepath ? u->usepath : u->str;
|
||||||
int same = (u->module == NULL) ? (curmod == NULL)
|
const char *um = decl_mod(file, u);
|
||||||
: (curmod != NULL && strcmp(u->module, curmod) == 0);
|
int same = (um == NULL) ? (curmod == NULL)
|
||||||
|
: (curmod != NULL && strcmp(um, curmod) == 0);
|
||||||
if (same)
|
if (same)
|
||||||
return p;
|
return p;
|
||||||
if (any == NULL) any = p;
|
|
||||||
}
|
}
|
||||||
return any;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -2967,6 +2983,67 @@ src_imports(Node *file, const char *modtag, const char *name)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* A flattened interface symbol is source-visible as a bare name only when
|
||||||
|
* the referencing package directly imports the symbol's defining path. The
|
||||||
|
* strict scope helpers above handle lexical locals, builtins and same-package
|
||||||
|
* declarations first; this second pass restores WW's existing direct-import
|
||||||
|
* convenience without admitting a transitive interface by accident. */
|
||||||
|
static int
|
||||||
|
direct_module_visible(Checker *c, const char *mod)
|
||||||
|
{
|
||||||
|
if (c == NULL || mod == NULL || mod[0] == '\0') return 0;
|
||||||
|
const char *dot = strrchr(mod, '.');
|
||||||
|
const char *alias = dot ? dot + 1 : mod;
|
||||||
|
const char *path = use_path(c->file, c->cur_mod, alias);
|
||||||
|
return path != NULL && strcmp(path, mod) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Sym *
|
||||||
|
lookup_visible(Checker *c, const char *name)
|
||||||
|
{
|
||||||
|
Sym *s = scope_lookup_prefer(c->cur, c->cur_mod, name);
|
||||||
|
/* A real source declaration shadows a decl-less pseudo-builtin. Keep
|
||||||
|
* the builtin as fallback while checking direct imported declarations. */
|
||||||
|
Sym *builtin = NULL;
|
||||||
|
if (s != NULL) {
|
||||||
|
if (s->decl != NULL) return s;
|
||||||
|
builtin = s;
|
||||||
|
}
|
||||||
|
/* N_USE entries are historically coalesced by leaf in the flat scope,
|
||||||
|
* so the retained symbol may carry another source package's owner. The
|
||||||
|
* source-owned alias map is authoritative: if this package directly
|
||||||
|
* imports NAME, return the coalesced module marker only as a marker; the
|
||||||
|
* N_DOT path maps the alias to the correct full path again. */
|
||||||
|
if (use_path(c->file, c->cur_mod, name) != NULL) {
|
||||||
|
for (Scope *p = c->cur; p; p = p->parent)
|
||||||
|
for (Sym *b = p->first; b; b = b->next)
|
||||||
|
if (strcmp(b->name, name) == 0
|
||||||
|
&& (b->kind == SK_USE || b->use_alias))
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
for (Scope *p = c->cur; p; p = p->parent) {
|
||||||
|
for (Sym *b = p->first; b; b = b->next)
|
||||||
|
if (strcmp(b->name, name) == 0
|
||||||
|
&& direct_module_visible(c, b->mod))
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
return builtin;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Sym *
|
||||||
|
lookup_visible_type(Checker *c, const char *name)
|
||||||
|
{
|
||||||
|
Sym *s = scope_lookup_type(c->cur, c->cur_mod, name);
|
||||||
|
if (s != NULL) return s;
|
||||||
|
for (Scope *p = c->cur; p; p = p->parent) {
|
||||||
|
for (Sym *b = p->first; b; b = b->next)
|
||||||
|
if (b->kind == SK_TYPE && strcmp(b->name, name) == 0
|
||||||
|
&& direct_module_visible(c, b->mod))
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* check_module_shadow — refuse value bindings that shadow an
|
* check_module_shadow — refuse value bindings that shadow an
|
||||||
* in-scope imported module bareword. "Value names and module names
|
* in-scope imported module bareword. "Value names and module names
|
||||||
@@ -3402,6 +3479,7 @@ check_file(Checker *c, Node *file)
|
|||||||
dot->lhs = newnode(c->a, N_IDENT, fp);
|
dot->lhs = newnode(c->a, N_IDENT, fp);
|
||||||
dot->lhs->str = "test";
|
dot->lhs->str = "test";
|
||||||
dot->str = "run";
|
dot->str = "run";
|
||||||
|
c->synth_test_run = dot;
|
||||||
call->lhs = dot;
|
call->lhs = dot;
|
||||||
call->list = arg;
|
call->list = arg;
|
||||||
Node *ret = newnode(c->a, N_RETURN, fp);
|
Node *ret = newnode(c->a, N_RETURN, fp);
|
||||||
|
|||||||
180
cmd/wcc/parse.c
180
cmd/wcc/parse.c
@@ -100,6 +100,59 @@ static Node *parsetype(Parser *p);
|
|||||||
static Node *parseblock(Parser *p);
|
static Node *parseblock(Parser *p);
|
||||||
static Node *parsestmt(Parser *p);
|
static Node *parsestmt(Parser *p);
|
||||||
|
|
||||||
|
static void
|
||||||
|
skipdecl(Parser *p)
|
||||||
|
{
|
||||||
|
int paren = 0, bracket = 0, brace = 0;
|
||||||
|
while (p->cur.kind != TK_EOF) {
|
||||||
|
switch (p->cur.kind) {
|
||||||
|
case TK_LPAREN: paren++; break;
|
||||||
|
case TK_RPAREN: if (paren > 0) paren--; break;
|
||||||
|
case TK_LBRACK: bracket++; break;
|
||||||
|
case TK_RBRACK: if (bracket > 0) bracket--; break;
|
||||||
|
case TK_LBRACE: brace++; break;
|
||||||
|
case TK_RBRACE: if (brace > 0) brace--; break;
|
||||||
|
case TK_SEMI:
|
||||||
|
advance(p);
|
||||||
|
if (paren == 0 && bracket == 0 && brace == 0)
|
||||||
|
return;
|
||||||
|
continue;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
advance(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Consume attribute syntax without parsing its expressions or any following
|
||||||
|
* declaration. The imports-only pass needs only to recognize that the next
|
||||||
|
* declaration is an attributed import, which full parsing rejects. */
|
||||||
|
static void
|
||||||
|
skipimportattrs(Parser *p)
|
||||||
|
{
|
||||||
|
while (p->cur.kind == TK_AT) {
|
||||||
|
advance(p);
|
||||||
|
if (p->cur.kind == TK_IDENT)
|
||||||
|
advance(p);
|
||||||
|
else {
|
||||||
|
errorf(p->cur.pos, "expected identifier, got %s",
|
||||||
|
tokname(p->cur.kind));
|
||||||
|
p->errs++;
|
||||||
|
}
|
||||||
|
if (!accept(p, TK_LPAREN)) continue;
|
||||||
|
int depth = 1;
|
||||||
|
while (p->cur.kind != TK_EOF && depth > 0) {
|
||||||
|
if (p->cur.kind == TK_LPAREN) depth++;
|
||||||
|
else if (p->cur.kind == TK_RPAREN) depth--;
|
||||||
|
advance(p);
|
||||||
|
}
|
||||||
|
if (depth > 0) {
|
||||||
|
errorf(p->cur.pos, "expected ')' after attribute");
|
||||||
|
p->errs++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static Node *
|
static Node *
|
||||||
parseparams(Parser *p)
|
parseparams(Parser *p)
|
||||||
{
|
{
|
||||||
@@ -1277,25 +1330,134 @@ parseuse(Parser *p)
|
|||||||
/* M1 #22: accumulate the full dotted import path (n->module) so the
|
/* M1 #22: accumulate the full dotted import path (n->module) so the
|
||||||
* checker can match decl identity on the path, while n->str stays the
|
* checker can match decl identity on the path, while n->str stays the
|
||||||
* leaf alias the user writes (`utf8.x`). */
|
* leaf alias the user writes (`utf8.x`). */
|
||||||
char pathbuf[256];
|
|
||||||
size_t pl = 0;
|
|
||||||
const char *leaf = expectident(p);
|
const char *leaf = expectident(p);
|
||||||
for (size_t i = 0; leaf[i] && pl + 1 < sizeof pathbuf; i++)
|
const char *path = leaf;
|
||||||
pathbuf[pl++] = leaf[i];
|
|
||||||
while (accept(p, TK_DOT)) {
|
while (accept(p, TK_DOT)) {
|
||||||
leaf = expectident(p);
|
leaf = expectident(p);
|
||||||
if (pl + 1 < sizeof pathbuf) pathbuf[pl++] = '.';
|
path = aprintf(p->a, "%s.%s", path, leaf);
|
||||||
for (size_t i = 0; leaf[i] && pl + 1 < sizeof pathbuf; i++)
|
|
||||||
pathbuf[pl++] = leaf[i];
|
|
||||||
}
|
}
|
||||||
pathbuf[pl] = '\0';
|
|
||||||
n->str = leaf;
|
n->str = leaf;
|
||||||
n->strlen = strlen(leaf);
|
n->strlen = strlen(leaf);
|
||||||
n->usepath = astrndup(p->a, pathbuf, pl);
|
n->usepath = path;
|
||||||
expect(p, TK_SEMI);
|
expect(p, TK_SEMI);
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Node *
|
||||||
|
parseimports(Parser *p)
|
||||||
|
{
|
||||||
|
Pos fp = { p->l->file, 1, 1 };
|
||||||
|
Node *file = newnode(p->a, N_FILE, fp);
|
||||||
|
Node *head = NULL, *tail = NULL;
|
||||||
|
Node *packages = NULL, *packagetail = NULL;
|
||||||
|
int sawpackage = 0;
|
||||||
|
|
||||||
|
while (p->cur.kind != TK_EOF) {
|
||||||
|
/* Compiler/driver bundle markers carry package identity out of
|
||||||
|
* band. They are not source declarations, so keep scanning for
|
||||||
|
* the following package clause and imports. */
|
||||||
|
if (p->cur.kind == TK_MODPATH) {
|
||||||
|
sawpackage = 0;
|
||||||
|
p->pathmod = p->cur.text;
|
||||||
|
p->curmod = p->cur.text;
|
||||||
|
p->resetmod = NULL;
|
||||||
|
advance(p);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (p->cur.kind == TK_MODRESET) {
|
||||||
|
const char *rp = p->cur.text;
|
||||||
|
sawpackage = 0;
|
||||||
|
advance(p);
|
||||||
|
p->pathmod = NULL;
|
||||||
|
p->curmod = rp;
|
||||||
|
p->resetmod = rp;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (p->cur.kind == TK_MODULE) {
|
||||||
|
Pos pp = p->cur.pos;
|
||||||
|
advance(p);
|
||||||
|
if (p->cur.kind != TK_IDENT) {
|
||||||
|
errorf(p->cur.pos, "invalid or missing package clause");
|
||||||
|
p->errs++;
|
||||||
|
sawpackage = 1;
|
||||||
|
skipdecl(p);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const char *name = expectident(p);
|
||||||
|
expect(p, TK_SEMI);
|
||||||
|
p->curmod = name;
|
||||||
|
Node *package = newnode(p->a, N_FILE, pp);
|
||||||
|
package->module = name;
|
||||||
|
if (packages == NULL)
|
||||||
|
packages = package;
|
||||||
|
else
|
||||||
|
packagetail->next = package;
|
||||||
|
packagetail = package;
|
||||||
|
if (!sawpackage) {
|
||||||
|
file->module = name;
|
||||||
|
file->pos = pp;
|
||||||
|
sawpackage = 1;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!sawpackage && p->pathmod == NULL && p->resetmod == NULL) {
|
||||||
|
errorf(p->cur.pos, "invalid or missing package clause");
|
||||||
|
p->errs++;
|
||||||
|
sawpackage = 1;
|
||||||
|
}
|
||||||
|
if (p->cur.kind == TK_USE) {
|
||||||
|
Node *d = parseuse(p);
|
||||||
|
d->module = p->curmod;
|
||||||
|
if (head == NULL)
|
||||||
|
head = d;
|
||||||
|
else
|
||||||
|
tail->next = d;
|
||||||
|
tail = d;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (p->cur.kind == TK_AT) {
|
||||||
|
skipimportattrs(p);
|
||||||
|
if (p->cur.kind == TK_EXPORT) advance(p);
|
||||||
|
if (p->cur.kind == TK_USE) {
|
||||||
|
errorf(p->cur.pos,
|
||||||
|
"import cannot be exported or attributed");
|
||||||
|
p->errs++;
|
||||||
|
Node *d = parseuse(p);
|
||||||
|
d->module = p->curmod;
|
||||||
|
if (head == NULL)
|
||||||
|
head = d;
|
||||||
|
else
|
||||||
|
tail->next = d;
|
||||||
|
tail = d;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
skipdecl(p);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (p->cur.kind == TK_EXPORT && peek(p).kind == TK_USE) {
|
||||||
|
advance(p);
|
||||||
|
errorf(p->cur.pos,
|
||||||
|
"import cannot be exported or attributed");
|
||||||
|
p->errs++;
|
||||||
|
Node *d = parseuse(p);
|
||||||
|
d->module = p->curmod;
|
||||||
|
if (head == NULL)
|
||||||
|
head = d;
|
||||||
|
else
|
||||||
|
tail->next = d;
|
||||||
|
tail = d;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
skipdecl(p);
|
||||||
|
}
|
||||||
|
file->list = head;
|
||||||
|
/* The package-clause chain lets a directory loader validate every
|
||||||
|
* selected file without inventing a second header grammar. It lives in
|
||||||
|
* body because list is the public imports chain. */
|
||||||
|
file->body = packages;
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
static Node *
|
static Node *
|
||||||
parsedef(Parser *p, int exp)
|
parsedef(Parser *p, int exp)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -68,8 +68,10 @@ scope_lookup_in_module(Scope *s, const char *mod, const char *name)
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Within each scope's bucket: Pass 1 prefers entries whose `sym.mod`
|
* 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
|
* matches the caller's `mod`; Pass 2 accepts only a source-visible bare
|
||||||
* regardless of mod (the existing scope_lookup semantics). We only
|
* entry. For a primary package that means mod==NULL. For an imported
|
||||||
|
* package it means a lexical local (a child scope) or a decl-less builtin,
|
||||||
|
* never another package's flattened interface value. We only
|
||||||
* descend to the parent scope when the current scope has no matching
|
* 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
|
* 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
|
* same-name fn from a parent scope, even when the parent entry
|
||||||
@@ -83,14 +85,16 @@ scope_lookup_in_module(Scope *s, const char *mod, const char *name)
|
|||||||
Sym *
|
Sym *
|
||||||
scope_lookup_prefer(Scope *s, const char *mod, const char *name)
|
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) {
|
for (Scope *p = s; p; p = p->parent) {
|
||||||
u64 h = hashstr(name) % p->nbuckets;
|
u64 h = hashstr(name) % p->nbuckets;
|
||||||
Sym *fallback = NULL;
|
Sym *fallback = NULL;
|
||||||
for (Sym *b = p->buckets[h]; b; b = b->hashnext) {
|
for (Sym *b = p->buckets[h]; b; b = b->hashnext) {
|
||||||
if (strcmp(b->name, name) != 0) continue;
|
if (strcmp(b->name, name) != 0) continue;
|
||||||
if (b->mod && strcmp(b->mod, mod) == 0) return b;
|
if (mod && b->mod && strcmp(b->mod, mod) == 0)
|
||||||
if (fallback == NULL) fallback = b;
|
return b;
|
||||||
|
if (b->mod == NULL && (mod == NULL || p->parent != NULL
|
||||||
|
|| b->decl == NULL) && fallback == NULL)
|
||||||
|
fallback = b;
|
||||||
}
|
}
|
||||||
if (fallback) return fallback;
|
if (fallback) return fallback;
|
||||||
}
|
}
|
||||||
@@ -115,7 +119,9 @@ scope_lookup_type(Scope *s, const char *mod, const char *name)
|
|||||||
if (b->kind != SK_TYPE) continue;
|
if (b->kind != SK_TYPE) continue;
|
||||||
if (strcmp(b->name, name) != 0) continue;
|
if (strcmp(b->name, name) != 0) continue;
|
||||||
if (mod && b->mod && strcmp(b->mod, mod) == 0) return b;
|
if (mod && b->mod && strcmp(b->mod, mod) == 0) return b;
|
||||||
if (fallback == NULL) fallback = b;
|
if (b->mod == NULL && (mod == NULL || b->decl == NULL)
|
||||||
|
&& fallback == NULL)
|
||||||
|
fallback = b;
|
||||||
}
|
}
|
||||||
if (fallback) return fallback;
|
if (fallback) return fallback;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -383,6 +383,9 @@ struct Parser {
|
|||||||
|
|
||||||
void parserinit(Parser*, Arena*, Lex*);
|
void parserinit(Parser*, Arena*, Lex*);
|
||||||
Node *parsefile(Parser*);
|
Node *parsefile(Parser*);
|
||||||
|
/* Imports-only N_FILE: module/pos identify the first package clause,
|
||||||
|
* list holds N_USE declarations, and body holds package-clause markers. */
|
||||||
|
Node *parseimports(Parser*);
|
||||||
Node *parseexpr_top(Parser*); /* for testing: parse one expression */
|
Node *parseexpr_top(Parser*); /* for testing: parse one expression */
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
@@ -585,6 +588,10 @@ struct Checker {
|
|||||||
int errs;
|
int errs;
|
||||||
int is_test; /* #15: `w6c -T` — collect @test fns + synth
|
int is_test; /* #15: `w6c -T` — collect @test fns + synth
|
||||||
* the entry; loud-reject a user main. */
|
* the entry; loud-reject a user main. */
|
||||||
|
int sep_mode; /* -c package compilation: imported interfaces are
|
||||||
|
* present, so absent members are hard export errors. */
|
||||||
|
Node *synth_test_run; /* exact compiler-generated test.run DOT;
|
||||||
|
* its unresolved external hook is intentional */
|
||||||
Node *alloc_octx; /* #3/B': the one empty `alloc([], n)` call node
|
Node *alloc_octx; /* #3/B': the one empty `alloc([], n)` call node
|
||||||
* that has let-declared slice context this walk;
|
* that has let-declared slice context this walk;
|
||||||
* any OTHER empty alloc has no element-type hint
|
* any OTHER empty alloc has no element-type hint
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
// (the package, not the file, is the unit of testing).
|
// (the package, not the file, is the unit of testing).
|
||||||
package strings_test;
|
package strings_test;
|
||||||
|
|
||||||
|
import bytes;
|
||||||
import strings;
|
import strings;
|
||||||
import os;
|
import os;
|
||||||
import test;
|
import test;
|
||||||
|
|||||||
@@ -77,9 +77,26 @@ fn accepttok(p: *parser, k: tkind) bool = {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn putdec(v: i32) void = {
|
||||||
|
let digits: [16]u8;
|
||||||
|
let n: i32 = 0;
|
||||||
|
let x: i32 = v;
|
||||||
|
if (x <= 0) { digits[n] = '0'; n += 1; }
|
||||||
|
else {
|
||||||
|
for (x > 0) {
|
||||||
|
digits[n] = ((x % 10) + ('0': i32)): u8;
|
||||||
|
n += 1;
|
||||||
|
x = x / 10;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
for (n > 0) { n -= 1; os.write(2, &digits[n], 1u64); };
|
||||||
|
};
|
||||||
|
|
||||||
fn errmsg(p: *parser, msg: str) void = {
|
fn errmsg(p: *parser, msg: str) void = {
|
||||||
let pre = "parse: ";
|
os.write(2, p.curfile.ptr, p.curfile.len: u64);
|
||||||
os.write(2, pre.ptr, pre.len: u64);
|
os.write(2, ":".ptr, 1u64); putdec(p.curline);
|
||||||
|
os.write(2, ":".ptr, 1u64); putdec(p.curcol);
|
||||||
|
os.write(2, ": error: ".ptr, 9u64);
|
||||||
os.write(2, msg.ptr, msg.len: u64);
|
os.write(2, msg.ptr, msg.len: u64);
|
||||||
os.write(2, "\n".ptr, 1u64);
|
os.write(2, "\n".ptr, 1u64);
|
||||||
p.errs += 1;
|
p.errs += 1;
|
||||||
@@ -91,11 +108,12 @@ fn expecttok(p: *parser, k: tkind, what: str) bool = {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Returns the empty str on error (and advances to make progress).
|
// Returns false without consuming the unexpected token; the enclosing
|
||||||
|
// production owns recovery, matching the C frontend.
|
||||||
fn expectident(p: *parser, into: *str) bool = {
|
fn expectident(p: *parser, into: *str) bool = {
|
||||||
if (p.curkind != tkind.TK_IDENT) {
|
if (p.curkind != tkind.TK_IDENT) {
|
||||||
errmsg(p, "expected identifier");
|
errmsg(p, strings.concat("expected identifier, got ",
|
||||||
advance(p);
|
tokname(p.curkind)));
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
*into = p.curtext;
|
*into = p.curtext;
|
||||||
@@ -413,6 +431,153 @@ fn isassignop(k: tkind) bool = {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn skipimportdecl(p: *parser) void = {
|
||||||
|
let paren: i32 = 0;
|
||||||
|
let bracket: i32 = 0;
|
||||||
|
let brace: i32 = 0;
|
||||||
|
for (p.curkind != tkind.TK_EOF) {
|
||||||
|
if (p.curkind == tkind.TK_LPAREN) { paren += 1; }
|
||||||
|
else { if (p.curkind == tkind.TK_RPAREN) {
|
||||||
|
if (paren > 0) { paren -= 1; };
|
||||||
|
} else { if (p.curkind == tkind.TK_LBRACK) { bracket += 1; }
|
||||||
|
else { if (p.curkind == tkind.TK_RBRACK) {
|
||||||
|
if (bracket > 0) { bracket -= 1; };
|
||||||
|
} else { if (p.curkind == tkind.TK_LBRACE) { brace += 1; }
|
||||||
|
else { if (p.curkind == tkind.TK_RBRACE) {
|
||||||
|
if (brace > 0) { brace -= 1; };
|
||||||
|
} else { if (p.curkind == tkind.TK_SEMI) {
|
||||||
|
advance(p);
|
||||||
|
if (paren == 0 && bracket == 0 && brace == 0) { return; };
|
||||||
|
continue;
|
||||||
|
};};};};};};};
|
||||||
|
advance(p);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Consume attributes without parsing their argument expressions. The
|
||||||
|
// imports-only pass only needs to recognize and reject an attributed import.
|
||||||
|
fn skipimportattrs(p: *parser) void = {
|
||||||
|
for (p.curkind == tkind.TK_AT) {
|
||||||
|
advance(p);
|
||||||
|
if (p.curkind == tkind.TK_IDENT) { advance(p); }
|
||||||
|
else {
|
||||||
|
errmsg(p, strings.concat("expected identifier, got ",
|
||||||
|
tokname(p.curkind)));
|
||||||
|
};
|
||||||
|
if (p.curkind == tkind.TK_LPAREN) {
|
||||||
|
advance(p);
|
||||||
|
let depth: i32 = 1;
|
||||||
|
for (p.curkind != tkind.TK_EOF && depth > 0) {
|
||||||
|
if (p.curkind == tkind.TK_LPAREN) { depth += 1; }
|
||||||
|
else { if (p.curkind == tkind.TK_RPAREN) { depth -= 1; }; };
|
||||||
|
advance(p);
|
||||||
|
};
|
||||||
|
if (depth > 0) { errmsg(p, "expected ')' after attribute"); };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export fn parseimports(p: *parser) *node = {
|
||||||
|
let f = newnode(nkind.N_FILE, p.curfile, 1, 1);
|
||||||
|
let head: *node = nil;
|
||||||
|
let tail: *node = nil;
|
||||||
|
let packages: *node = nil;
|
||||||
|
let packagetail: *node = nil;
|
||||||
|
let sawpackage: bool = false;
|
||||||
|
for (p.curkind != tkind.TK_EOF) {
|
||||||
|
// Compiler/driver bundle markers carry package identity out of
|
||||||
|
// band. They are not source declarations, so keep scanning for
|
||||||
|
// the following package clause and imports.
|
||||||
|
if (p.curkind == tkind.TK_MODPATH) {
|
||||||
|
sawpackage = false;
|
||||||
|
p.pathmod = p.curtext;
|
||||||
|
p.curmod = p.curtext;
|
||||||
|
p.resetmod = "";
|
||||||
|
advance(p);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if (p.curkind == tkind.TK_MODRESET) {
|
||||||
|
let rp: str = p.curtext;
|
||||||
|
sawpackage = false;
|
||||||
|
advance(p);
|
||||||
|
p.pathmod = "";
|
||||||
|
p.curmod = rp;
|
||||||
|
p.resetmod = rp;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if (p.curkind == tkind.TK_MODULE) {
|
||||||
|
let pf: str = p.curfile;
|
||||||
|
let pl: i32 = p.curline;
|
||||||
|
let pc: i32 = p.curcol;
|
||||||
|
advance(p);
|
||||||
|
if (p.curkind != tkind.TK_IDENT) {
|
||||||
|
errmsg(p, "invalid or missing package clause");
|
||||||
|
sawpackage = true;
|
||||||
|
skipimportdecl(p);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let name: str;
|
||||||
|
expectident(p, &name);
|
||||||
|
expecttok(p, tkind.TK_SEMI, "expected ';' after module name");
|
||||||
|
p.curmod = name;
|
||||||
|
let pm = newnode(nkind.N_FILE, pf, pl, pc);
|
||||||
|
pm.nmod = name;
|
||||||
|
if (packages == nil) { packages = pm; }
|
||||||
|
else { packagetail.next = pm; };
|
||||||
|
packagetail = pm;
|
||||||
|
if (!sawpackage) {
|
||||||
|
f.nmod = name;
|
||||||
|
f.file = pf;
|
||||||
|
f.line = pl;
|
||||||
|
f.col = pc;
|
||||||
|
sawpackage = true;
|
||||||
|
};
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if (!sawpackage && p.pathmod.len == 0 && p.resetmod.len == 0) {
|
||||||
|
errmsg(p, "invalid or missing package clause");
|
||||||
|
sawpackage = true;
|
||||||
|
};
|
||||||
|
if (p.curkind == tkind.TK_USE) {
|
||||||
|
let d: *node = parseuse(p);
|
||||||
|
d.nmod = p.curmod;
|
||||||
|
if (head == nil) { head = d; } else { tail.next = d; };
|
||||||
|
tail = d;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if (p.curkind == tkind.TK_AT) {
|
||||||
|
skipimportattrs(p);
|
||||||
|
if (p.curkind == tkind.TK_EXPORT) { advance(p); };
|
||||||
|
if (p.curkind == tkind.TK_USE) {
|
||||||
|
errmsg(p, "import cannot be exported or attributed");
|
||||||
|
let d: *node = parseuse(p);
|
||||||
|
d.nmod = p.curmod;
|
||||||
|
if (head == nil) { head = d; } else { tail.next = d; };
|
||||||
|
tail = d;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
skipimportdecl(p);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if (p.curkind == tkind.TK_EXPORT) {
|
||||||
|
advance(p);
|
||||||
|
if (p.curkind == tkind.TK_USE) {
|
||||||
|
errmsg(p, "import cannot be exported or attributed");
|
||||||
|
let d: *node = parseuse(p);
|
||||||
|
d.nmod = p.curmod;
|
||||||
|
if (head == nil) { head = d; } else { tail.next = d; };
|
||||||
|
tail = d;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
skipimportdecl(p);
|
||||||
|
};
|
||||||
|
f.list = head;
|
||||||
|
// body carries package-clause markers; list remains imports only.
|
||||||
|
f.body = packages;
|
||||||
|
return f;
|
||||||
|
};
|
||||||
|
|
||||||
// Forward references between parseunary/parseexpr/parsebin/parsepostfix
|
// Forward references between parseunary/parseexpr/parsebin/parsepostfix
|
||||||
// are resolved by the two-pass checker — no body-less prototypes needed.
|
// are resolved by the two-pass checker — no body-less prototypes needed.
|
||||||
|
|
||||||
@@ -523,6 +688,9 @@ export fn parsefile(p: *parser) *node = {
|
|||||||
|
|
||||||
let d: *node = nil;
|
let d: *node = nil;
|
||||||
if (p.curkind == tkind.TK_USE) {
|
if (p.curkind == tkind.TK_USE) {
|
||||||
|
if (attrs != nil || exported != 0) {
|
||||||
|
errmsg(p, "import cannot be exported or attributed");
|
||||||
|
};
|
||||||
d = parseuse(p);
|
d = parseuse(p);
|
||||||
} else { if (p.curkind == tkind.TK_DEF) {
|
} else { if (p.curkind == tkind.TK_DEF) {
|
||||||
d = parsedef(p, exported);
|
d = parsedef(p, exported);
|
||||||
|
|||||||
@@ -110,11 +110,9 @@ export fn scopelookup(s: *scope, name: str) *sym = {
|
|||||||
// scopelookupinmodule(c, leaf, leaf) won't find it.
|
// scopelookupinmodule(c, leaf, leaf) won't find it.
|
||||||
//
|
//
|
||||||
// #58/#50: within each scope, Pass-1 prefers an SK_TYPE whose `sym.mod`
|
// #58/#50: within each scope, Pass-1 prefers an SK_TYPE whose `sym.mod`
|
||||||
// matches `mod`; Pass-2 falls back to the first SK_TYPE regardless of
|
// matches `mod`; Pass-2 accepts only a primary-package or builtin type. The
|
||||||
// mod (chain-first, the prior behavior). scopedefineinmodule PREPENDS,
|
// synthesized global `nomem` has an empty source location and is builtin too.
|
||||||
// so chain-first = last-registered — when two modules export the same
|
// Flattened transitive interface types are never an unqualified fallback.
|
||||||
// type leaf the bare walk silently picked the newest-installed one,
|
|
||||||
// install-order-dependent, while cstage is deterministic on cur_mod.
|
|
||||||
// Mirrors cstage cmd/wcc/sym.c scope_lookup_type(s, mod, name) (the
|
// Mirrors cstage cmd/wcc/sym.c scope_lookup_type(s, mod, name) (the
|
||||||
// kind-filtered + mod-preferring single walk); sole caller passes
|
// kind-filtered + mod-preferring single walk); sole caller passes
|
||||||
// c.curmod. i32-correct: streq throughout, only `.len > 0` guards (the
|
// c.curmod. i32-correct: streq throughout, only `.len > 0` guards (the
|
||||||
@@ -136,7 +134,12 @@ export fn scopelookuptype(s: *scope, mod: str, name: str) *sym = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
if (fallback == nil) { fallback = b; };
|
let builtin: bool = b.decl == nil;
|
||||||
|
if (!builtin && b.decl.file.len == 0 && b.decl.line == 0) {
|
||||||
|
builtin = true;
|
||||||
|
};
|
||||||
|
if (b.mod.len == 0 && (mod.len == 0 || builtin)
|
||||||
|
&& fallback == nil) { fallback = b; };
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
b = b.hashnext;
|
b = b.hashnext;
|
||||||
@@ -220,8 +223,10 @@ export fn scopelookupinmodule(s: *scope, mod: str, name: str) *sym = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Within each scope's bucket: Pass 1 prefers entries whose
|
// Within each scope's bucket: Pass 1 prefers entries whose
|
||||||
// `sym.mod` matches `mod`; Pass 2 falls back to the first match
|
// `sym.mod` matches `mod`; Pass 2 accepts only a source-visible bare entry.
|
||||||
// regardless of mod (same semantics as scopelookup). We only descend
|
// For an imported package that means a lexical local (child scope) or a
|
||||||
|
// decl-less builtin, never another package's flattened interface symbol.
|
||||||
|
// We only descend
|
||||||
// to the parent scope when the current scope has no matching entry at
|
// 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
|
// 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.
|
// fn from a parent scope, even when the parent entry mod-matches.
|
||||||
@@ -234,7 +239,6 @@ export fn scopelookupinmodule(s: *scope, mod: str, name: str) *sym = {
|
|||||||
// io.read that happens to hash earlier into the flat scope. Mirrors
|
// io.read that happens to hash earlier into the flat scope. Mirrors
|
||||||
// cmd/wcc/sym.c scope_lookup_prefer.
|
// cmd/wcc/sym.c scope_lookup_prefer.
|
||||||
export fn scopelookupprefer(s: *scope, mod: str, name: str) *sym = {
|
export fn scopelookupprefer(s: *scope, mod: str, name: str) *sym = {
|
||||||
if (mod.len == 0) { return scopelookup(s, name); };
|
|
||||||
let p: *scope = s;
|
let p: *scope = s;
|
||||||
for (p != nil) {
|
for (p != nil) {
|
||||||
let h: u64 = hashstr(name);
|
let h: u64 = hashstr(name);
|
||||||
@@ -243,12 +247,13 @@ export fn scopelookupprefer(s: *scope, mod: str, name: str) *sym = {
|
|||||||
let fallback: *sym = nil;
|
let fallback: *sym = nil;
|
||||||
for (b != nil) {
|
for (b != nil) {
|
||||||
if (streq(b.name, name)) {
|
if (streq(b.name, name)) {
|
||||||
if (b.mod.len > 0) {
|
if (mod.len > 0 && b.mod.len > 0) {
|
||||||
if (streq(b.mod, mod)) {
|
if (streq(b.mod, mod)) {
|
||||||
return b;
|
return b;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
if (fallback == nil) { fallback = b; };
|
if (b.mod.len == 0 && (mod.len == 0 || p.parent != nil
|
||||||
|
|| b.decl == nil) && fallback == nil) { fallback = b; };
|
||||||
};
|
};
|
||||||
b = b.hashnext;
|
b = b.hashnext;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -172,6 +172,7 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
|||||||
let ck: checker;
|
let ck: checker;
|
||||||
checkinit(&ck, &tc);
|
checkinit(&ck, &tc);
|
||||||
ck.istest = testmode;
|
ck.istest = testmode;
|
||||||
|
ck.sepmode = sepmode;
|
||||||
checkfile(&ck, f);
|
checkfile(&ck, f);
|
||||||
if (ck.errs > 0) { return 1; };
|
if (ck.errs > 0) { return 1; };
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package wcc;
|
|||||||
import os;
|
import os;
|
||||||
import syntax;
|
import syntax;
|
||||||
import strconv;
|
import strconv;
|
||||||
|
import strings;
|
||||||
|
|
||||||
type checker = struct {
|
type checker = struct {
|
||||||
tc: *syntax.tctx,
|
tc: *syntax.tctx,
|
||||||
@@ -19,6 +20,10 @@ type checker = struct {
|
|||||||
// twin (c->matcharms)
|
// twin (c->matcharms)
|
||||||
istest: i32, // #15: `w6c_ww -T` — collect @test fns +
|
istest: i32, // #15: `w6c_ww -T` — collect @test fns +
|
||||||
// synth the entry; loud-reject a user main.
|
// synth the entry; loud-reject a user main.
|
||||||
|
sepmode: i32, // -c package compilation: imported interfaces are
|
||||||
|
// present, so absent members are hard export errors.
|
||||||
|
synthtestrun: *syntax.node, // exact generated test.run DOT; its
|
||||||
|
// unresolved external hook is intentional
|
||||||
verbose: i32, // when non-zero, log each unresolved name
|
verbose: i32, // when non-zero, log each unresolved name
|
||||||
fnret: *syntax.node, // enclosing fn's return type AST (for `?`)
|
fnret: *syntax.node, // enclosing fn's return type AST (for `?`)
|
||||||
curmod: str, // importing-module bareword for the decl
|
curmod: str, // importing-module bareword for the decl
|
||||||
@@ -164,17 +169,31 @@ fn declmod(file: *syntax.node, d: *syntax.node) str = {
|
|||||||
// to the full dotted import path it binds (`encoding.utf8`), for the
|
// to the full dotted import path it binds (`encoding.utf8`), for the
|
||||||
// module-qualified resolution and codegen hint (M1 #22). Single-level
|
// module-qualified resolution and codegen hint (M1 #22). Single-level
|
||||||
// packages have usepath == alias so the result is unchanged. The
|
// packages have usepath == alias so the result is unchanged. The
|
||||||
// current tree has one occurrence per leaf, so the map is unambiguous.
|
// Only an import owned by the referencing package is visible; a matching
|
||||||
fn usepathfor(file: *syntax.node, alias: str) str = {
|
// alias carried by a transitive interface is deliberately ignored.
|
||||||
|
fn usepathfor(file: *syntax.node, modtag: str, alias: str) str = {
|
||||||
let empty: str;
|
let empty: str;
|
||||||
if (file == nil) { return empty; };
|
if (file == nil) { return empty; };
|
||||||
if (alias.len == 0) { return empty; };
|
if (alias.len == 0) { return empty; };
|
||||||
|
if (modtag.len != 0) {
|
||||||
|
let (prefix, suffix) = strings.rcut(modtag, ".");
|
||||||
|
let leaf: str = suffix;
|
||||||
|
if (leaf.len == 0) { leaf = modtag; };
|
||||||
|
if (syntax.streq(alias, leaf)) { return modtag; };
|
||||||
|
};
|
||||||
let u: *syntax.node = file.list;
|
let u: *syntax.node = file.list;
|
||||||
for (u != nil) {
|
for (u != nil) {
|
||||||
if (u.kind == syntax.nkind.N_USE) {
|
if (u.kind == syntax.nkind.N_USE) {
|
||||||
if (syntax.streq(u.str, alias)) {
|
if (syntax.streq(u.str, alias)) {
|
||||||
if (u.usepath.len != 0) { return u.usepath; };
|
let um: str = declmod(file, u);
|
||||||
return u.str;
|
let same: bool = false;
|
||||||
|
if (modtag.len == 0) {
|
||||||
|
if (um.len == 0) { same = true; };
|
||||||
|
} else { if (syntax.streq(um, modtag)) { same = true; }; };
|
||||||
|
if (same) {
|
||||||
|
if (u.usepath.len != 0) { return u.usepath; };
|
||||||
|
return u.str;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
u = u.next;
|
u = u.next;
|
||||||
@@ -182,12 +201,10 @@ fn usepathfor(file: *syntax.node, alias: str) str = {
|
|||||||
return empty;
|
return empty;
|
||||||
};
|
};
|
||||||
|
|
||||||
// modkeyfor — usepathfor with leaf-alias fallback: the module key for a
|
// modkeyfor — the module key for a directly imported alias. Empty means
|
||||||
// path-keyed scopelookupinmodule given the alias the user wrote (M1 #22).
|
// the referencing package did not itself declare that import.
|
||||||
fn modkeyfor(c: *checker, alias: str) str = {
|
fn modkeyfor(c: *checker, alias: str) str = {
|
||||||
let mk: str = usepathfor(c.file, alias);
|
return usepathfor(c.file, c.curmod, alias);
|
||||||
if (mk.len == 0) { return alias; };
|
|
||||||
return mk;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// srcimports — does the source file that contributed decl-module
|
// srcimports — does the source file that contributed decl-module
|
||||||
@@ -225,6 +242,132 @@ fn srcimports(file: *syntax.node, modtag: str, name: str) bool = {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Bare imported declarations remain WW source syntax, but only across a
|
||||||
|
// direct edge. The syntax scope helpers resolve lexical locals, builtins and
|
||||||
|
// same-package declarations first; this pass admits a flattened interface
|
||||||
|
// symbol iff the referencing source package itself imported its module path.
|
||||||
|
fn directmodvisible(c: *checker, mod: str) bool = {
|
||||||
|
if (mod.len == 0) { return false; };
|
||||||
|
let (prefix, suffix) = strings.rcut(mod, ".");
|
||||||
|
let alias: str = suffix;
|
||||||
|
if (alias.len == 0) { alias = mod; };
|
||||||
|
let path: str = usepathfor(c.file, c.curmod, alias);
|
||||||
|
return path.len != 0 && syntax.streq(path, mod);
|
||||||
|
};
|
||||||
|
|
||||||
|
fn lookupvisible(c: *checker, name: str) *syntax.sym = {
|
||||||
|
let found: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, name);
|
||||||
|
let builtin: *syntax.sym = nil;
|
||||||
|
if (found != nil) {
|
||||||
|
if (found.decl != nil) { return found; };
|
||||||
|
builtin = found;
|
||||||
|
};
|
||||||
|
// Flat scope installation coalesces same-leaf N_USE entries. The
|
||||||
|
// source-owned alias map, not the retained marker's mod field, decides
|
||||||
|
// whether this package can use the qualifier.
|
||||||
|
if (usepathfor(c.file, c.curmod, name).len != 0) {
|
||||||
|
let q: *syntax.scope = c.cur;
|
||||||
|
for (q != nil) {
|
||||||
|
let u: *syntax.sym = q.first;
|
||||||
|
for (u != nil) {
|
||||||
|
if (syntax.streq(u.name, name)
|
||||||
|
&& (u.skind == syntax.skind.SK_USE
|
||||||
|
|| u.use_alias != 0i32)) { return u; };
|
||||||
|
u = u.snext;
|
||||||
|
};
|
||||||
|
q = q.parent;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
let p: *syntax.scope = c.cur;
|
||||||
|
for (p != nil) {
|
||||||
|
let b: *syntax.sym = p.first;
|
||||||
|
for (b != nil) {
|
||||||
|
if (syntax.streq(b.name, name) && directmodvisible(c, b.mod)) {
|
||||||
|
return b;
|
||||||
|
};
|
||||||
|
b = b.snext;
|
||||||
|
};
|
||||||
|
p = p.parent;
|
||||||
|
};
|
||||||
|
return builtin;
|
||||||
|
};
|
||||||
|
|
||||||
|
fn lookupvisibletype(c: *checker, name: str) *syntax.sym = {
|
||||||
|
// C resolves intrinsic type names before consulting package symbols.
|
||||||
|
// Select the empty-module seed here so an imported interface cannot
|
||||||
|
// redefine a builtin for wwstage; size/opaque have no seed and remain
|
||||||
|
// handled directly by tinfofornode.
|
||||||
|
if (builtintypename(name)) {
|
||||||
|
let empty: str;
|
||||||
|
return syntax.scopelookuptype(c.cur, empty, name);
|
||||||
|
};
|
||||||
|
let found: *syntax.sym = syntax.scopelookuptype(c.cur, c.curmod, name);
|
||||||
|
if (found != nil) { return found; };
|
||||||
|
let p: *syntax.scope = c.cur;
|
||||||
|
for (p != nil) {
|
||||||
|
let b: *syntax.sym = p.first;
|
||||||
|
for (b != nil) {
|
||||||
|
if (b.skind == syntax.skind.SK_TYPE
|
||||||
|
&& syntax.streq(b.name, name)
|
||||||
|
&& directmodvisible(c, b.mod)) { return b; };
|
||||||
|
b = b.snext;
|
||||||
|
};
|
||||||
|
p = p.parent;
|
||||||
|
};
|
||||||
|
return nil;
|
||||||
|
};
|
||||||
|
|
||||||
|
fn builtintypename(name: str) bool = {
|
||||||
|
return syntax.streq(name, "void")
|
||||||
|
|| syntax.streq(name, "bool")
|
||||||
|
|| syntax.streq(name, "rune")
|
||||||
|
|| syntax.streq(name, "i8")
|
||||||
|
|| syntax.streq(name, "i16")
|
||||||
|
|| syntax.streq(name, "i32")
|
||||||
|
|| syntax.streq(name, "i64")
|
||||||
|
|| syntax.streq(name, "u8")
|
||||||
|
|| syntax.streq(name, "u16")
|
||||||
|
|| syntax.streq(name, "u32")
|
||||||
|
|| syntax.streq(name, "u64")
|
||||||
|
|| syntax.streq(name, "int")
|
||||||
|
|| syntax.streq(name, "uint")
|
||||||
|
|| syntax.streq(name, "uintptr")
|
||||||
|
|| syntax.streq(name, "size")
|
||||||
|
|| syntax.streq(name, "opaque")
|
||||||
|
|| syntax.streq(name, "f32")
|
||||||
|
|| syntax.streq(name, "f64")
|
||||||
|
|| syntax.streq(name, "str")
|
||||||
|
|| syntax.streq(name, "never")
|
||||||
|
|| syntax.streq(name, "nomem")
|
||||||
|
|| syntax.streq(name, "untyped_int")
|
||||||
|
|| syntax.streq(name, "untyped_float")
|
||||||
|
|| syntax.streq(name, "untyped_str")
|
||||||
|
|| syntax.streq(name, "untyped_rune")
|
||||||
|
|| syntax.streq(name, "untyped_bool")
|
||||||
|
|| syntax.streq(name, "untyped_nil");
|
||||||
|
};
|
||||||
|
|
||||||
|
fn packageaccesserr(c: *checker, e: *syntax.node, pkg: str, member: str,
|
||||||
|
missing: bool) void = {
|
||||||
|
cerr(e.file); cerr(":");
|
||||||
|
cerr(strconv.i32tos(e.line, strconv.base.DEC)); cerr(":");
|
||||||
|
cerr(strconv.i32tos(e.col, strconv.base.DEC));
|
||||||
|
cerr(": error: package '"); cerr(pkg);
|
||||||
|
if (missing) {
|
||||||
|
cerr("' has no exported declaration '"); cerr(member); cerr("'\n");
|
||||||
|
} else {
|
||||||
|
cerr("' is not directly imported\n");
|
||||||
|
};
|
||||||
|
c.errs += 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
// A bare `w6c -T` leaves the compiler-generated test.run hook external;
|
||||||
|
// the ordinary driver supplies lib/test. No user-written missing member is
|
||||||
|
// exempt from package export checking.
|
||||||
|
fn synthesizedtestrun(c: *checker, e: *syntax.node) bool = {
|
||||||
|
return c.synthtestrun != nil && c.synthtestrun == e;
|
||||||
|
};
|
||||||
|
|
||||||
// checkmoduleshadow — enforce "value names and module names are
|
// checkmoduleshadow — enforce "value names and module names are
|
||||||
// disjoint" at nested-scope binds. Mirrors cstage check_module_shadow
|
// disjoint" at nested-scope binds. Mirrors cstage check_module_shadow
|
||||||
// (cmd/wcc/check.c). Fires for fn params / lets / forrange iters /
|
// (cmd/wcc/check.c). Fires for fn params / lets / forrange iters /
|
||||||
@@ -498,7 +641,7 @@ fn resolvewalk(c: *checker, n: *syntax.node) void = {
|
|||||||
if (k == syntax.nkind.N_IDENT) {
|
if (k == syntax.nkind.N_IDENT) {
|
||||||
let nm: str = n.str;
|
let nm: str = n.str;
|
||||||
if (nm.len > 0) {
|
if (nm.len > 0) {
|
||||||
let s: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, nm);
|
let s: *syntax.sym = lookupvisible(c, nm);
|
||||||
if (s == nil) {
|
if (s == nil) {
|
||||||
// Unshadowed abort/assert binds no sym BY
|
// Unshadowed abort/assert binds no sym BY
|
||||||
// DESIGN (the EXPR_ASSERT family has no callee
|
// DESIGN (the EXPR_ASSERT family has no callee
|
||||||
@@ -522,13 +665,14 @@ fn resolvewalk(c: *checker, n: *syntax.node) void = {
|
|||||||
if (k == syntax.nkind.N_TNAME) {
|
if (k == syntax.nkind.N_TNAME) {
|
||||||
let nm: str = n.str;
|
let nm: str = n.str;
|
||||||
if (nm.len > 0) {
|
if (nm.len > 0) {
|
||||||
let s: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, nm);
|
let s: *syntax.sym = lookupvisibletype(c, nm);
|
||||||
|
let builtin: bool = builtintypename(nm);
|
||||||
// `pkg.Type` — strip the last dot prefix and look up
|
// `pkg.Type` — strip the last dot prefix and look up
|
||||||
// the leaf with a mod filter so same-leaf-name types
|
// the leaf with a mod filter so same-leaf-name types
|
||||||
// from different imports (`bufio.stream` vs
|
// from different imports (`bufio.stream` vs
|
||||||
// `io.stream`) disambiguate to the right one.
|
// `io.stream`) disambiguate to the right one.
|
||||||
// Mirrors cmd/wcc/check.c resolve_typename.
|
// Mirrors cmd/wcc/check.c resolve_typename.
|
||||||
if (s == nil) {
|
if (s == nil && !builtin) {
|
||||||
let dot: i32 = nm.len - 1;
|
let dot: i32 = nm.len - 1;
|
||||||
for (dot >= 0) {
|
for (dot >= 0) {
|
||||||
if (nm[dot] == 46u8) { break; };
|
if (nm[dot] == 46u8) { break; };
|
||||||
@@ -543,18 +687,39 @@ fn resolvewalk(c: *checker, n: *syntax.node) void = {
|
|||||||
let leaf: str;
|
let leaf: str;
|
||||||
leaf.ptr = nm.ptr + (dot + 1): u64;
|
leaf.ptr = nm.ptr + (dot + 1): u64;
|
||||||
leaf.len = nm.len - (dot + 1);
|
leaf.len = nm.len - (dot + 1);
|
||||||
s = syntax.scopelookupinmodule(c.cur, modkeyfor(c, head), leaf);
|
let mk: str = modkeyfor(c, head);
|
||||||
|
if (mk.len != 0) {
|
||||||
|
s = syntax.scopelookupinmodule(c.cur, mk, leaf);
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
if (s == nil) {
|
if (s == nil && !builtin) {
|
||||||
|
if (syntax.scopelookup(c.cur, nm) != nil) {
|
||||||
|
cerr(n.file); cerr(":");
|
||||||
|
cerr(strconv.i32tos(n.line, strconv.base.DEC)); cerr(":");
|
||||||
|
cerr(strconv.i32tos(n.col, strconv.base.DEC));
|
||||||
|
cerr(": error: unknown type '"); cerr(nm); cerr("'\n");
|
||||||
|
c.errs += 1;
|
||||||
|
n.type_ = c.tc.tyerr: *void;
|
||||||
|
};
|
||||||
c.nunresolved += 1;
|
c.nunresolved += 1;
|
||||||
if (c.verbose != 0) {
|
if (c.verbose != 0) {
|
||||||
cerr(" unresolved tname: ");
|
cerr(" unresolved tname: ");
|
||||||
cerr(nm);
|
cerr(nm);
|
||||||
cerr("\n");
|
cerr("\n");
|
||||||
};
|
};
|
||||||
} else { c.nresolved += 1; };
|
} else {
|
||||||
|
c.nresolved += 1;
|
||||||
|
// Resolve and cache the type while c.curmod still names the
|
||||||
|
// declaration that owns this syntax node. Exported structs may
|
||||||
|
// contain a direct dependency's type; consumers need that cached
|
||||||
|
// shape for structural field access, but must not gain source
|
||||||
|
// visibility of the dependency qualifier. Cstage does the same
|
||||||
|
// owner-scoped work in resolve_typedecl.
|
||||||
|
let ti: *syntax.tinfo = tinfofornode(c, n);
|
||||||
|
if (ti != nil) { n.type_ = ti: *void; };
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -991,6 +1156,25 @@ fn unwrapbang(n: *syntax.node) *syntax.node = {
|
|||||||
fn aliassym(c: *checker, n: *syntax.node) *syntax.sym = {
|
fn aliassym(c: *checker, n: *syntax.node) *syntax.sym = {
|
||||||
if (n == nil) { return nil; };
|
if (n == nil) { return nil; };
|
||||||
if (n.kind != syntax.nkind.N_TNAME) { return nil; };
|
if (n.kind != syntax.nkind.N_TNAME) { return nil; };
|
||||||
|
// An exported signature is resolved while its owning package is the
|
||||||
|
// current module. Preserve that compiler-owned binding when the exact
|
||||||
|
// same AST node is later inspected structurally by a consumer (for
|
||||||
|
// example `os.filestat.atime.sec`, where atime is time.instant). This
|
||||||
|
// does not make a consumer-written `time.instant` visible: such a node
|
||||||
|
// has no owner-resolved named cache unless the consumer imports time.
|
||||||
|
let bound: *syntax.tinfo = n.type_: *syntax.tinfo;
|
||||||
|
if (bound != nil) { if (bound.kind == syntax.tykind.TY_NAMED) {
|
||||||
|
let owner: *syntax.scope = c.cur;
|
||||||
|
for (owner != nil) {
|
||||||
|
let bs: *syntax.sym = owner.first;
|
||||||
|
for (bs != nil) {
|
||||||
|
if (bs.skind == syntax.skind.SK_TYPE
|
||||||
|
&& bs.type_ == bound) { return bs; };
|
||||||
|
bs = bs.snext;
|
||||||
|
};
|
||||||
|
owner = owner.parent;
|
||||||
|
};
|
||||||
|
}; };
|
||||||
let nm: str = n.str;
|
let nm: str = n.str;
|
||||||
// #51: pkg.alias type refs land here as a single TNAME whose
|
// #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
|
// str is the joined form (lib/ww/parse/parse.ww:258-265 in
|
||||||
@@ -1013,7 +1197,10 @@ fn aliassym(c: *checker, n: *syntax.node) *syntax.sym = {
|
|||||||
let leaf: str;
|
let leaf: str;
|
||||||
leaf.ptr = nm.ptr + ((dotidx + 1): u64);
|
leaf.ptr = nm.ptr + ((dotidx + 1): u64);
|
||||||
leaf.len = nm.len - dotidx - 1;
|
leaf.len = nm.len - dotidx - 1;
|
||||||
s = syntax.scopelookupinmodule(c.cur, modkeyfor(c, head), leaf);
|
let mk: str = modkeyfor(c, head);
|
||||||
|
if (mk.len != 0) {
|
||||||
|
s = syntax.scopelookupinmodule(c.cur, mk, leaf);
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
// #53: same-module preference. Mirrors cstage
|
// #53: same-module preference. Mirrors cstage
|
||||||
// cmd/wcc/check.c:66 scope_lookup_prefer. Without this,
|
// cmd/wcc/check.c:66 scope_lookup_prefer. Without this,
|
||||||
@@ -1026,7 +1213,7 @@ fn aliassym(c: *checker, n: *syntax.node) *syntax.sym = {
|
|||||||
// scopelookupprefer (the #56/#4/#11a wave). The only bare-leaf
|
// scopelookupprefer (the #56/#4/#11a wave). The only bare-leaf
|
||||||
// type lookups left — varianterr (:1051) + scruttype (:1098) —
|
// type lookups left — varianterr (:1051) + scruttype (:1098) —
|
||||||
// stay mod-blind but have no reproducible divergence; #58.
|
// stay mod-blind but have no reproducible divergence; #58.
|
||||||
s = syntax.scopelookupprefer(c.cur, c.curmod, nm);
|
s = lookupvisibletype(c, nm);
|
||||||
// #61 A.5: bare TNAME that collides with an imported
|
// #61 A.5: bare TNAME that collides with an imported
|
||||||
// module bareword. Two shapes hit this:
|
// module bareword. Two shapes hit this:
|
||||||
// - `let l: lex;` where `lex` struct lives in
|
// - `let l: lex;` where `lex` struct lives in
|
||||||
@@ -1049,7 +1236,7 @@ fn aliassym(c: *checker, n: *syntax.node) *syntax.sym = {
|
|||||||
// otherwise resolve install-order-dependent here
|
// otherwise resolve install-order-dependent here
|
||||||
// when a value binding shadows the leaf; cstage
|
// when a value binding shadows the leaf; cstage
|
||||||
// passes c->cur_mod to scope_lookup_type (sym.c).
|
// passes c->cur_mod to scope_lookup_type (sym.c).
|
||||||
let sm: *syntax.sym = syntax.scopelookuptype(c.cur, c.curmod, nm);
|
let sm: *syntax.sym = lookupvisibletype(c, nm);
|
||||||
if (sm != nil) { s = sm; };
|
if (sm != nil) { s = sm; };
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -1271,7 +1458,9 @@ fn scruttype(c: *checker, e: *syntax.node) *syntax.node = {
|
|||||||
if (e.kind == syntax.nkind.N_DOT) {
|
if (e.kind == syntax.nkind.N_DOT) {
|
||||||
if (e.lhs == nil) { return nil; };
|
if (e.lhs == nil) { return nil; };
|
||||||
if (e.lhs.kind != syntax.nkind.N_IDENT) { return nil; };
|
if (e.lhs.kind != syntax.nkind.N_IDENT) { return nil; };
|
||||||
let s: *syntax.sym = syntax.scopelookupinmodule(c.cur, modkeyfor(c, e.lhs.str), e.str);
|
let mk: str = modkeyfor(c, e.lhs.str);
|
||||||
|
if (mk.len == 0) { return nil; };
|
||||||
|
let s: *syntax.sym = syntax.scopelookupinmodule(c.cur, mk, e.str);
|
||||||
if (s == nil) { return nil; };
|
if (s == nil) { return nil; };
|
||||||
if (s.decl == nil) { return nil; };
|
if (s.decl == nil) { return nil; };
|
||||||
return s.decl.lhs;
|
return s.decl.lhs;
|
||||||
@@ -1862,7 +2051,7 @@ fn evaldefconst(c: *checker, n: *syntax.node, out: *u64, depth: i32) bool = {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
if (k == syntax.nkind.N_IDENT) {
|
if (k == syntax.nkind.N_IDENT) {
|
||||||
let s: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, n.str);
|
let s: *syntax.sym = lookupvisible(c, n.str);
|
||||||
if (s == nil) { return false; };
|
if (s == nil) { return false; };
|
||||||
if (s.skind != syntax.skind.SK_DEF) { return false; };
|
if (s.skind != syntax.skind.SK_DEF) { return false; };
|
||||||
if (s.decl == nil) { return false; };
|
if (s.decl == nil) { return false; };
|
||||||
@@ -1872,7 +2061,9 @@ fn evaldefconst(c: *checker, n: *syntax.node, out: *u64, depth: i32) bool = {
|
|||||||
if (k == syntax.nkind.N_DOT) {
|
if (k == syntax.nkind.N_DOT) {
|
||||||
if (n.lhs == nil) { return false; };
|
if (n.lhs == nil) { return false; };
|
||||||
if (n.lhs.kind != syntax.nkind.N_IDENT) { return false; };
|
if (n.lhs.kind != syntax.nkind.N_IDENT) { return false; };
|
||||||
let s: *syntax.sym = syntax.scopelookupinmodule(c.cur, modkeyfor(c, n.lhs.str), n.str);
|
let mk: str = modkeyfor(c, n.lhs.str);
|
||||||
|
if (mk.len == 0) { return false; };
|
||||||
|
let s: *syntax.sym = syntax.scopelookupinmodule(c.cur, mk, n.str);
|
||||||
if (s == nil) { return false; };
|
if (s == nil) { return false; };
|
||||||
if (s.skind != syntax.skind.SK_DEF) { return false; };
|
if (s.skind != syntax.skind.SK_DEF) { return false; };
|
||||||
if (s.decl == nil) { return false; };
|
if (s.decl == nil) { return false; };
|
||||||
@@ -3287,7 +3478,7 @@ fn unoptype(c: *checker, e: *syntax.node) *syntax.node = {
|
|||||||
// the foreign signature (scopedefineinmodule prepends);
|
// the foreign signature (scopedefineinmodule prepends);
|
||||||
// cstage types a fn ident via scope_lookup_prefer with
|
// cstage types a fn ident via scope_lookup_prefer with
|
||||||
// cur_mod (cmd/wcc/check.c:1305).
|
// cur_mod (cmd/wcc/check.c:1305).
|
||||||
let fs: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, e.lhs.str);
|
let fs: *syntax.sym = lookupvisible(c, e.lhs.str);
|
||||||
if (fs != nil) {
|
if (fs != nil) {
|
||||||
if (fs.skind == syntax.skind.SK_FN) {
|
if (fs.skind == syntax.skind.SK_FN) {
|
||||||
if (fs.decl != nil) {
|
if (fs.decl != nil) {
|
||||||
@@ -3313,7 +3504,11 @@ fn unoptype(c: *checker, e: *syntax.node) *syntax.node = {
|
|||||||
// cstage's actual acceptance reason.
|
// cstage's actual acceptance reason.
|
||||||
if (e.lhs.kind == syntax.nkind.N_DOT) {
|
if (e.lhs.kind == syntax.nkind.N_DOT) {
|
||||||
if (e.lhs.lhs != nil && e.lhs.lhs.kind == syntax.nkind.N_IDENT) {
|
if (e.lhs.lhs != nil && e.lhs.lhs.kind == syntax.nkind.N_IDENT) {
|
||||||
let fs: *syntax.sym = syntax.scopelookupinmodule(c.cur, modkeyfor(c, e.lhs.lhs.str), e.lhs.str);
|
let fs: *syntax.sym = nil;
|
||||||
|
let mk: str = modkeyfor(c, e.lhs.lhs.str);
|
||||||
|
if (mk.len != 0) {
|
||||||
|
fs = syntax.scopelookupinmodule(c.cur, mk, e.lhs.str);
|
||||||
|
};
|
||||||
if (fs != nil) {
|
if (fs != nil) {
|
||||||
if (fs.skind == syntax.skind.SK_FN) {
|
if (fs.skind == syntax.skind.SK_FN) {
|
||||||
if (fs.decl != nil) {
|
if (fs.decl != nil) {
|
||||||
@@ -3388,7 +3583,7 @@ fn indexresult(c: *checker, e: *syntax.node) *syntax.node = {
|
|||||||
// valid; only the SK_DEF scalar-str operand is
|
// valid; only the SK_DEF scalar-str operand is
|
||||||
// unindexable. cstage twin: check.c N_INDEX TY_STR arm.
|
// unindexable. cstage twin: check.c N_INDEX TY_STR arm.
|
||||||
if (e.lhs != nil && e.lhs.kind == syntax.nkind.N_IDENT) {
|
if (e.lhs != nil && e.lhs.kind == syntax.nkind.N_IDENT) {
|
||||||
let s: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, e.lhs.str);
|
let s: *syntax.sym = lookupvisible(c, e.lhs.str);
|
||||||
if (s != nil && s.skind == syntax.skind.SK_DEF) {
|
if (s != nil && s.skind == syntax.skind.SK_DEF) {
|
||||||
cerr("error: cannot index a def-constant str '");
|
cerr("error: cannot index a def-constant str '");
|
||||||
cerr(e.lhs.str);
|
cerr(e.lhs.str);
|
||||||
@@ -3536,8 +3731,22 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
|||||||
// bare `error` then binds strconv.error not io.error). Mirrors
|
// bare `error` then binds strconv.error not io.error). Mirrors
|
||||||
// cstage cmd/wcc/check.c:66 scope_lookup_prefer; sibling #56 at
|
// cstage cmd/wcc/check.c:66 scope_lookup_prefer; sibling #56 at
|
||||||
// L2439, #53 at L688. Tracked in the cluster note at L685-687.
|
// L2439, #53 at L688. Tracked in the cluster note at L685-687.
|
||||||
let s: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, e.str);
|
let s: *syntax.sym = lookupvisible(c, e.str);
|
||||||
if (s == nil) { return nil; };
|
if (s == nil) {
|
||||||
|
// A same-named flattened symbol that fails lookupvisible is a
|
||||||
|
// transitive implementation fact, not an unresolved external.
|
||||||
|
// Diagnose it like cstage's N_IDENT path and stamp tyerr so
|
||||||
|
// later call checking does not obscure the causal error.
|
||||||
|
if (syntax.scopelookup(c.cur, e.str) != nil) {
|
||||||
|
cerr(e.file); cerr(":");
|
||||||
|
cerr(strconv.i32tos(e.line, strconv.base.DEC)); cerr(":");
|
||||||
|
cerr(strconv.i32tos(e.col, strconv.base.DEC));
|
||||||
|
cerr(": error: undefined: "); cerr(e.str); cerr("\n");
|
||||||
|
c.errs += 1;
|
||||||
|
e.type_ = c.tc.tyerr: *void;
|
||||||
|
};
|
||||||
|
return nil;
|
||||||
|
};
|
||||||
if (s.decl == nil) { return nil; };
|
if (s.decl == nil) { return nil; };
|
||||||
// #34: a bare fn-name rvalue types as its FN TYPE, not its return
|
// #34: a bare fn-name rvalue types as its FN TYPE, not its return
|
||||||
// type. decl.lhs is the RETURN type for an N_FNDECL, so synthesize
|
// type. decl.lhs is the RETURN type for an N_FNDECL, so synthesize
|
||||||
@@ -3632,6 +3841,7 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
|||||||
return e.rhs;
|
return e.rhs;
|
||||||
};
|
};
|
||||||
if (k == syntax.nkind.N_CALL) {
|
if (k == syntax.nkind.N_CALL) {
|
||||||
|
if (e.type_ == c.tc.tyerr: *void) { return nil; };
|
||||||
let callee: *syntax.node = e.lhs;
|
let callee: *syntax.node = e.lhs;
|
||||||
if (callee == nil) { return nil; };
|
if (callee == nil) { return nil; };
|
||||||
// #31: synthesize the `alloc(value)` / `alloc([], n)` builtin
|
// #31: synthesize the `alloc(value)` / `alloc([], n)` builtin
|
||||||
@@ -3809,7 +4019,7 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
|||||||
// The TY_ERR stamp on the callee is cgen's routing key
|
// The TY_ERR stamp on the callee is cgen's routing key
|
||||||
// (cstage spells it `n->lhs->type = ty_err`).
|
// (cstage spells it `n->lhs->type = ty_err`).
|
||||||
if (syntax.streq(callee.str, "abort")
|
if (syntax.streq(callee.str, "abort")
|
||||||
&& syntax.scopelookupprefer(c.cur, c.curmod, "abort") == nil) {
|
&& lookupvisible(c, "abort") == nil) {
|
||||||
if (e.list != nil) {
|
if (e.list != nil) {
|
||||||
let mt: *syntax.node = exprtype(c, e.list, nil);
|
let mt: *syntax.node = exprtype(c, e.list, nil);
|
||||||
let conf: bool = false;
|
let conf: bool = false;
|
||||||
@@ -3828,7 +4038,7 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
|||||||
return tn;
|
return tn;
|
||||||
};
|
};
|
||||||
if (syntax.streq(callee.str, "assert") && e.list != nil
|
if (syntax.streq(callee.str, "assert") && e.list != nil
|
||||||
&& syntax.scopelookupprefer(c.cur, c.curmod, "assert") == nil) {
|
&& lookupvisible(c, "assert") == nil) {
|
||||||
let ct: *syntax.node = exprtype(c, e.list, nil);
|
let ct: *syntax.node = exprtype(c, e.list, nil);
|
||||||
if (ct != nil) {
|
if (ct != nil) {
|
||||||
// No alias peel: cstage compares ty_bool by
|
// No alias peel: cstage compares ty_bool by
|
||||||
@@ -4010,11 +4220,11 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
|||||||
if (nm.len > 0) {
|
if (nm.len > 0) {
|
||||||
let s: *syntax.sym = nil;
|
let s: *syntax.sym = nil;
|
||||||
if (callee.kind == syntax.nkind.N_IDENT) {
|
if (callee.kind == syntax.nkind.N_IDENT) {
|
||||||
s = syntax.scopelookupprefer(c.cur, c.curmod, nm);
|
s = lookupvisible(c, nm);
|
||||||
} else {
|
} else {
|
||||||
let ms: *syntax.sym = nil;
|
let ms: *syntax.sym = nil;
|
||||||
if (callee.lhs != nil && callee.lhs.kind == syntax.nkind.N_IDENT) {
|
if (callee.lhs != nil && callee.lhs.kind == syntax.nkind.N_IDENT) {
|
||||||
ms = syntax.scopelookupprefer(c.cur, c.curmod, callee.lhs.str);
|
ms = lookupvisible(c, callee.lhs.str);
|
||||||
if (ms != nil && ms.skind != syntax.skind.SK_USE) {
|
if (ms != nil && ms.skind != syntax.skind.SK_USE) {
|
||||||
let mu: *syntax.sym = syntax.scopelookupuselocal(ms.scope, callee.lhs.str);
|
let mu: *syntax.sym = syntax.scopelookupuselocal(ms.scope, callee.lhs.str);
|
||||||
if (mu != nil) { ms = mu; };
|
if (mu != nil) { ms = mu; };
|
||||||
@@ -4036,7 +4246,17 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
|||||||
// global-leaf path) and harec check_autodereference
|
// global-leaf path) and harec check_autodereference
|
||||||
// (ref/harec/src/check.c:1566-1581).
|
// (ref/harec/src/check.c:1566-1581).
|
||||||
if (ms != nil && (ms.skind == syntax.skind.SK_USE || ms.use_alias != 0i32)) {
|
if (ms != nil && (ms.skind == syntax.skind.SK_USE || ms.use_alias != 0i32)) {
|
||||||
s = syntax.scopelookupinmodule(c.cur, modkeyfor(c, callee.lhs.str), nm);
|
let mk: str = modkeyfor(c, callee.lhs.str);
|
||||||
|
if (mk.len == 0) {
|
||||||
|
if (ms.skind == syntax.skind.SK_USE) {
|
||||||
|
packageaccesserr(c, callee, callee.lhs.str, nm, false);
|
||||||
|
e.type_ = c.tc.tyerr: *void;
|
||||||
|
callee.type_ = c.tc.tyerr: *void;
|
||||||
|
return nil;
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
s = syntax.scopelookupinmodule(c.cur, mk, nm);
|
||||||
|
};
|
||||||
// Module-qualified callee whose leaf isn't scope-keyed
|
// Module-qualified callee whose leaf isn't scope-keyed
|
||||||
// under its module: align to cstage, which stamps ty_err
|
// under its module: align to cstage, which stamps ty_err
|
||||||
// here and lets cgen emit the call (cmd/wcc/check.c:1834-
|
// here and lets cgen emit the call (cmd/wcc/check.c:1834-
|
||||||
@@ -4050,6 +4270,10 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
|||||||
// keys the CALL off run's `//ww:module test` directive
|
// keys the CALL off run's `//ww:module test` directive
|
||||||
// either way, so the resolution change is asm-neutral.
|
// either way, so the resolution change is asm-neutral.
|
||||||
if (s == nil) {
|
if (s == nil) {
|
||||||
|
if (c.sepmode != 0 && ms.skind == syntax.skind.SK_USE && mk.len != 0
|
||||||
|
&& !synthesizedtestrun(c, callee)) {
|
||||||
|
packageaccesserr(c, callee, callee.lhs.str, nm, true);
|
||||||
|
};
|
||||||
e.type_ = c.tc.tyerr: *void;
|
e.type_ = c.tc.tyerr: *void;
|
||||||
callee.type_ = c.tc.tyerr: *void; // N_DOT node itself (asserttyped checks it)
|
callee.type_ = c.tc.tyerr: *void; // N_DOT node itself (asserttyped checks it)
|
||||||
return nil;
|
return nil;
|
||||||
@@ -4106,6 +4330,7 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
|||||||
return nil;
|
return nil;
|
||||||
};
|
};
|
||||||
if (k == syntax.nkind.N_DOT) {
|
if (k == syntax.nkind.N_DOT) {
|
||||||
|
if (e.type_ == c.tc.tyerr: *void) { return nil; };
|
||||||
// A.6.1.5a — fold cases only. Mirrors cstage cmd/wcc/check.c
|
// A.6.1.5a — fold cases only. Mirrors cstage cmd/wcc/check.c
|
||||||
// :740-832. Struct field + pseudo-field (.len/.cap/.ptr) lands
|
// :740-832. Struct field + pseudo-field (.len/.cap/.ptr) lands
|
||||||
// in A.6.1.5b. SK_USE gates case 1; a #6a-D dot-lhs collision
|
// in A.6.1.5b. SK_USE gates case 1; a #6a-D dot-lhs collision
|
||||||
@@ -4120,7 +4345,7 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
|||||||
// ref/harec/src/check.c:4419-4434.
|
// ref/harec/src/check.c:4419-4434.
|
||||||
let lhsn: *syntax.node = e.lhs;
|
let lhsn: *syntax.node = e.lhs;
|
||||||
if (lhsn != nil) { if (lhsn.kind == syntax.nkind.N_IDENT) {
|
if (lhsn != nil) { if (lhsn.kind == syntax.nkind.N_IDENT) {
|
||||||
let ms: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, lhsn.str);
|
let ms: *syntax.sym = lookupvisible(c, lhsn.str);
|
||||||
if (ms != nil && ms.skind != syntax.skind.SK_USE) {
|
if (ms != nil && ms.skind != syntax.skind.SK_USE) {
|
||||||
let mu: *syntax.sym = syntax.scopelookupuselocal(ms.scope, lhsn.str);
|
let mu: *syntax.sym = syntax.scopelookupuselocal(ms.scope, lhsn.str);
|
||||||
if (mu != nil) { ms = mu; };
|
if (mu != nil) { ms = mu; };
|
||||||
@@ -4133,7 +4358,16 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
|||||||
// qualified resolution and the lenient checker policy
|
// qualified resolution and the lenient checker policy
|
||||||
// keeps the silent miss documented at scruttype L656.
|
// keeps the silent miss documented at scruttype L656.
|
||||||
if (ms.skind == syntax.skind.SK_USE || ms.use_alias != 0i32) {
|
if (ms.skind == syntax.skind.SK_USE || ms.use_alias != 0i32) {
|
||||||
let fs: *syntax.sym = syntax.scopelookupinmodule(c.cur, modkeyfor(c, lhsn.str), e.str);
|
let mk: str = modkeyfor(c, lhsn.str);
|
||||||
|
if (mk.len == 0 && ms.skind == syntax.skind.SK_USE) {
|
||||||
|
packageaccesserr(c, e, lhsn.str, e.str, false);
|
||||||
|
e.type_ = c.tc.tyerr: *void;
|
||||||
|
return nil;
|
||||||
|
};
|
||||||
|
let fs: *syntax.sym = nil;
|
||||||
|
if (mk.len != 0) {
|
||||||
|
fs = syntax.scopelookupinmodule(c.cur, mk, e.str);
|
||||||
|
};
|
||||||
if (fs != nil) { if (fs.decl != nil) {
|
if (fs != nil) { if (fs.decl != nil) {
|
||||||
// #34: a module-qualified bare fn rvalue `mod.fn` types as
|
// #34: a module-qualified bare fn rvalue `mod.fn` types as
|
||||||
// its FN TYPE (twin of the N_IDENT arm, :2688); decl.lhs is
|
// its FN TYPE (twin of the N_IDENT arm, :2688); decl.lhs is
|
||||||
@@ -4165,6 +4399,12 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
|||||||
return tn;
|
return tn;
|
||||||
};
|
};
|
||||||
}; };
|
}; };
|
||||||
|
if (c.sepmode != 0 && fs == nil && ms.skind == syntax.skind.SK_USE && mk.len != 0
|
||||||
|
&& !synthesizedtestrun(c, e)) {
|
||||||
|
packageaccesserr(c, e, lhsn.str, e.str, true);
|
||||||
|
e.type_ = c.tc.tyerr: *void;
|
||||||
|
return nil;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
// Fold case 2 inner: bare `EnumT.MEMBER` where EnumT
|
// Fold case 2 inner: bare `EnumT.MEMBER` where EnumT
|
||||||
// is an SK_TYPE in the flat scope. Mirror cstage
|
// is an SK_TYPE in the flat scope. Mirror cstage
|
||||||
@@ -4310,7 +4550,7 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
|||||||
// style anonymous struct lit we don't yet parse — bail.
|
// style anonymous struct lit we don't yet parse — bail.
|
||||||
if (e.lhs == nil) { return nil; };
|
if (e.lhs == nil) { return nil; };
|
||||||
if (e.lhs.kind == syntax.nkind.N_IDENT) {
|
if (e.lhs.kind == syntax.nkind.N_IDENT) {
|
||||||
let ms: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, e.lhs.str);
|
let ms: *syntax.sym = lookupvisible(c, e.lhs.str);
|
||||||
if (ms != nil) { if (ms.skind == syntax.skind.SK_TYPE) { if (ms.decl != nil) {
|
if (ms != nil) { if (ms.skind == syntax.skind.SK_TYPE) { if (ms.decl != nil) {
|
||||||
let tn: *syntax.node = ms.decl.lhs;
|
let tn: *syntax.node = ms.decl.lhs;
|
||||||
if (tn != nil) {
|
if (tn != nil) {
|
||||||
@@ -4906,7 +5146,7 @@ fn assignableaddrfn(c: *checker, dst: *syntax.node, rhs: *syntax.node) bool = {
|
|||||||
// silently admitting a *fn into a foreign-sig slot or rejecting a
|
// silently admitting a *fn into a foreign-sig slot or rejecting a
|
||||||
// valid same-module &fn. cstage uses scope_lookup_prefer with
|
// valid same-module &fn. cstage uses scope_lookup_prefer with
|
||||||
// cur_mod (cmd/wcc/check.c:410, assignable_addrfn).
|
// cur_mod (cmd/wcc/check.c:410, assignable_addrfn).
|
||||||
let s: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, id.str);
|
let s: *syntax.sym = lookupvisible(c, id.str);
|
||||||
if (s == nil) { return false; };
|
if (s == nil) { return false; };
|
||||||
if (s.skind != syntax.skind.SK_FN) { return false; };
|
if (s.skind != syntax.skind.SK_FN) { return false; };
|
||||||
if (s.decl == nil) { return false; };
|
if (s.decl == nil) { return false; };
|
||||||
@@ -6043,7 +6283,7 @@ fn desugararrayslice(c: *checker, dsttn: *syntax.node, srctn: *syntax.node, val:
|
|||||||
fn calleefndecl(c: *checker, callee: *syntax.node) *syntax.node = {
|
fn calleefndecl(c: *checker, callee: *syntax.node) *syntax.node = {
|
||||||
if (callee == nil) { return nil; };
|
if (callee == nil) { return nil; };
|
||||||
if (callee.kind == syntax.nkind.N_IDENT) {
|
if (callee.kind == syntax.nkind.N_IDENT) {
|
||||||
let s: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, callee.str);
|
let s: *syntax.sym = lookupvisible(c, callee.str);
|
||||||
if (s != nil) {
|
if (s != nil) {
|
||||||
if (s.skind == syntax.skind.SK_FN) { return s.decl; };
|
if (s.skind == syntax.skind.SK_FN) { return s.decl; };
|
||||||
};
|
};
|
||||||
@@ -6052,14 +6292,18 @@ fn calleefndecl(c: *checker, callee: *syntax.node) *syntax.node = {
|
|||||||
if (callee.kind == syntax.nkind.N_DOT) {
|
if (callee.kind == syntax.nkind.N_DOT) {
|
||||||
if (callee.lhs != nil) {
|
if (callee.lhs != nil) {
|
||||||
if (callee.lhs.kind == syntax.nkind.N_IDENT) {
|
if (callee.lhs.kind == syntax.nkind.N_IDENT) {
|
||||||
let ms: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, callee.lhs.str);
|
let ms: *syntax.sym = lookupvisible(c, callee.lhs.str);
|
||||||
if (ms != nil && ms.skind != syntax.skind.SK_USE) {
|
if (ms != nil && ms.skind != syntax.skind.SK_USE) {
|
||||||
let mu: *syntax.sym = syntax.scopelookupuselocal(ms.scope, callee.lhs.str);
|
let mu: *syntax.sym = syntax.scopelookupuselocal(ms.scope, callee.lhs.str);
|
||||||
if (mu != nil) { ms = mu; };
|
if (mu != nil) { ms = mu; };
|
||||||
};
|
};
|
||||||
if (ms != nil) {
|
if (ms != nil) {
|
||||||
if (ms.skind == syntax.skind.SK_USE || ms.use_alias != 0i32) {
|
if (ms.skind == syntax.skind.SK_USE || ms.use_alias != 0i32) {
|
||||||
let fs: *syntax.sym = syntax.scopelookupinmodule(c.cur, modkeyfor(c, callee.lhs.str), callee.str);
|
let fs: *syntax.sym = nil;
|
||||||
|
let mk: str = modkeyfor(c, callee.lhs.str);
|
||||||
|
if (mk.len != 0) {
|
||||||
|
fs = syntax.scopelookupinmodule(c.cur, mk, callee.str);
|
||||||
|
};
|
||||||
if (fs != nil) {
|
if (fs != nil) {
|
||||||
if (fs.skind == syntax.skind.SK_FN) { return fs.decl; };
|
if (fs.skind == syntax.skind.SK_FN) { return fs.decl; };
|
||||||
};
|
};
|
||||||
@@ -6218,7 +6462,7 @@ fn checkassign(c: *checker, n: *syntax.node) void = {
|
|||||||
// cmd/wcc/check.c:1889-1896.
|
// cmd/wcc/check.c:1889-1896.
|
||||||
if (n.lhs.kind == syntax.nkind.N_IDENT) {
|
if (n.lhs.kind == syntax.nkind.N_IDENT) {
|
||||||
if (n.lhs.str.len > 0) {
|
if (n.lhs.str.len > 0) {
|
||||||
let s: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, n.lhs.str);
|
let s: *syntax.sym = lookupvisible(c, n.lhs.str);
|
||||||
if (s != nil) {
|
if (s != nil) {
|
||||||
if (s.is_const != 0i32) {
|
if (s.is_const != 0i32) {
|
||||||
cerr("error: cannot assign to const binding\n");
|
cerr("error: cannot assign to const binding\n");
|
||||||
@@ -6763,7 +7007,7 @@ fn exprtypeoftry(c: *checker, e: *syntax.node) *syntax.node = {
|
|||||||
// either spuriously rejecting valid `?` code or skipping the F8
|
// either spuriously rejecting valid `?` code or skipping the F8
|
||||||
// reject. Mirrors exprtype's own N_IDENT arm (scopelookupprefer
|
// reject. Mirrors exprtype's own N_IDENT arm (scopelookupprefer
|
||||||
// at :2897); cstage types the operand via cexpr with cur_mod.
|
// at :2897); cstage types the operand via cexpr with cur_mod.
|
||||||
let s: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, e.str);
|
let s: *syntax.sym = lookupvisible(c, e.str);
|
||||||
if (s == nil) { return nil; };
|
if (s == nil) { return nil; };
|
||||||
if (s.decl == nil) { return nil; };
|
if (s.decl == nil) { return nil; };
|
||||||
return s.decl.lhs;
|
return s.decl.lhs;
|
||||||
@@ -6791,7 +7035,7 @@ fn exprtypeoftry(c: *checker, e: *syntax.node) *syntax.node = {
|
|||||||
// N_CALL arm (scopelookupprefer at :3336). The N_DOT-callee leaf
|
// N_CALL arm (scopelookupprefer at :3336). The N_DOT-callee leaf
|
||||||
// (callee.str above) still resolves bare-leaf, not via the module
|
// (callee.str above) still resolves bare-leaf, not via the module
|
||||||
// qualifier callee.lhs.str — that module-keyed fix rides task #51.
|
// qualifier callee.lhs.str — that module-keyed fix rides task #51.
|
||||||
let s: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, nm);
|
let s: *syntax.sym = lookupvisible(c, nm);
|
||||||
if (s == nil) { return nil; };
|
if (s == nil) { return nil; };
|
||||||
if (s.skind != syntax.skind.SK_FN) { return nil; };
|
if (s.skind != syntax.skind.SK_FN) { return nil; };
|
||||||
if (s.decl == nil) { return nil; };
|
if (s.decl == nil) { return nil; };
|
||||||
@@ -7028,7 +7272,7 @@ fn isassertfam(c: *checker, id: *syntax.node) bool = {
|
|||||||
if (!syntax.streq(id.str, "abort") && !syntax.streq(id.str, "assert")) {
|
if (!syntax.streq(id.str, "abort") && !syntax.streq(id.str, "assert")) {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
return syntax.scopelookupprefer(c.cur, c.curmod, id.str) == nil;
|
return lookupvisible(c, id.str) == nil;
|
||||||
};
|
};
|
||||||
|
|
||||||
// asserttyped — post-checker invariant gate (#15, A.6.2.1e). Walks the
|
// asserttyped — post-checker invariant gate (#15, A.6.2.1e). Walks the
|
||||||
@@ -7089,7 +7333,10 @@ fn asserttyped(c: *checker, n: *syntax.node, indot: bool) void = {
|
|||||||
if (isexpr && k == syntax.nkind.N_IDENT) {
|
if (isexpr && k == syntax.nkind.N_IDENT) {
|
||||||
if (indot) { skip = true; };
|
if (indot) { skip = true; };
|
||||||
if (!skip) {
|
if (!skip) {
|
||||||
let s: *syntax.sym = syntax.scopelookup(c.cur, n.str);
|
// Use the same source-visibility rules as expression
|
||||||
|
// resolution. A transitive same-leaf declaration must not hide
|
||||||
|
// a decl-less pseudo-builtin from this invariant gate.
|
||||||
|
let s: *syntax.sym = lookupvisible(c, n.str);
|
||||||
if (s != nil) {
|
if (s != nil) {
|
||||||
if (s.skind == syntax.skind.SK_USE) { skip = true; };
|
if (s.skind == syntax.skind.SK_USE) { skip = true; };
|
||||||
if (s.decl == nil) { skip = true; };
|
if (s.decl == nil) { skip = true; };
|
||||||
@@ -7146,6 +7393,8 @@ export fn checkinit(c: *checker, tc: *syntax.tctx) void = {
|
|||||||
c.nunresolved = 0;
|
c.nunresolved = 0;
|
||||||
c.errs = 0;
|
c.errs = 0;
|
||||||
c.istest = 0i32; // #15: caller (w6c main) sets it after init
|
c.istest = 0i32; // #15: caller (w6c main) sets it after init
|
||||||
|
c.sepmode = 0i32; // caller (w6c main) sets it from -c
|
||||||
|
c.synthtestrun = nil;
|
||||||
c.verbose = 0;
|
c.verbose = 0;
|
||||||
c.fnret = nil;
|
c.fnret = nil;
|
||||||
let empty: str;
|
let empty: str;
|
||||||
@@ -7395,6 +7644,7 @@ export fn checkfile(c: *checker, file: *syntax.node) void = {
|
|||||||
did.str = "test";
|
did.str = "test";
|
||||||
dot.lhs = did;
|
dot.lhs = did;
|
||||||
dot.str = "run";
|
dot.str = "run";
|
||||||
|
c.synthtestrun = dot;
|
||||||
call.lhs = dot;
|
call.lhs = dot;
|
||||||
call.list = arg;
|
call.list = arg;
|
||||||
let ret: *syntax.node = syntax.newnode(syntax.nkind.N_RETURN, pf, pl, pc);
|
let ret: *syntax.node = syntax.newnode(syntax.nkind.N_RETURN, pf, pl, pc);
|
||||||
|
|||||||
@@ -668,10 +668,14 @@ export fn wwiemit(c: *checker, file: *syntax.node, path: str) i32 = {
|
|||||||
};
|
};
|
||||||
wwisortdecls(upaths, unodes, nuse);
|
wwisortdecls(upaths, unodes, nuse);
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
|
let previous: str = "";
|
||||||
for (i < nuse) {
|
for (i < nuse) {
|
||||||
wputs(fd, "import ");
|
if (previous.len == 0 || !syntax.streq(previous, upaths[i])) {
|
||||||
wputs(fd, upaths[i]);
|
wputs(fd, "import ");
|
||||||
wputs(fd, ";\n");
|
wputs(fd, upaths[i]);
|
||||||
|
wputs(fd, ";\n");
|
||||||
|
previous = upaths[i];
|
||||||
|
};
|
||||||
i += 1;
|
i += 1;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -77,6 +77,29 @@ must_contain(const char *src, const char *needle)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static char *
|
||||||
|
imports_to_str(const char *src, int *errs, const char **pkg)
|
||||||
|
{
|
||||||
|
Arena *a = newarena();
|
||||||
|
Lex l;
|
||||||
|
Parser p;
|
||||||
|
lexinit(&l, a, "imports.ww", src, strlen(src));
|
||||||
|
parserinit(&p, a, &l);
|
||||||
|
Node *n = parseimports(&p);
|
||||||
|
*errs = p.errs + l.errs;
|
||||||
|
*pkg = n->module ? strdup(n->module) : NULL;
|
||||||
|
|
||||||
|
char *buf = NULL;
|
||||||
|
size_t len = 0;
|
||||||
|
FILE *f = open_memstream(&buf, &len);
|
||||||
|
if (f != NULL) {
|
||||||
|
astprint(f, n);
|
||||||
|
fclose(f);
|
||||||
|
}
|
||||||
|
freearena(a);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
main(void)
|
main(void)
|
||||||
{
|
{
|
||||||
@@ -179,6 +202,68 @@ main(void)
|
|||||||
if (!must_contain("fn f() i32 = { return a + b; };", "(bin +")) fail++;
|
if (!must_contain("fn f() i32 = { return a + b; };", "(bin +")) fail++;
|
||||||
if (!must_contain("@symbol(\"x\") fn f() void;", "(attr \"symbol\""))fail++;
|
if (!must_contain("@symbol(\"x\") fn f() void;", "(attr \"symbol\""))fail++;
|
||||||
|
|
||||||
|
{
|
||||||
|
const char *src =
|
||||||
|
"package main;\n"
|
||||||
|
"import zed;\n"
|
||||||
|
"fn f() void = { let s: str = \"import fake;\"; };\n"
|
||||||
|
"/* import hidden; */\n"
|
||||||
|
"import alpha;\n";
|
||||||
|
int errs;
|
||||||
|
const char *pkg;
|
||||||
|
char *got = imports_to_str(src, &errs, &pkg);
|
||||||
|
if (errs != 0 || pkg == NULL || strcmp(pkg, "main") != 0
|
||||||
|
|| got == NULL || strstr(got, "(use \"zed\"") == NULL
|
||||||
|
|| strstr(got, "(use \"alpha\"") == NULL
|
||||||
|
|| strstr(got, "fake") != NULL || strstr(got, "hidden") != NULL) {
|
||||||
|
fprintf(stderr, "imports-only parse mismatch:\n%s\n",
|
||||||
|
got ? got : "<null>");
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
free((void *)pkg);
|
||||||
|
free(got);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
int errs;
|
||||||
|
const char *pkg;
|
||||||
|
char *got = imports_to_str("package main;\nimport ;\n", &errs,
|
||||||
|
&pkg);
|
||||||
|
if (errs == 0) {
|
||||||
|
fputs("malformed import accepted\n", stderr);
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
free((void *)pkg);
|
||||||
|
free(got);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
int errs;
|
||||||
|
const char *pkg;
|
||||||
|
char *got = imports_to_str(
|
||||||
|
"package main;\n@trace import alpha;\n", &errs, &pkg);
|
||||||
|
if (errs == 0 || got == NULL
|
||||||
|
|| strstr(got, "(use \"alpha\"") == NULL) {
|
||||||
|
fputs("attributed import was not rejected and retained\n",
|
||||||
|
stderr);
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
free((void *)pkg);
|
||||||
|
free(got);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
int errs;
|
||||||
|
const char *pkg;
|
||||||
|
char *got = imports_to_str(
|
||||||
|
"//ww:module example.main\n"
|
||||||
|
"package main;\nimport alpha;\n", &errs, &pkg);
|
||||||
|
if (errs != 0 || pkg == NULL || strcmp(pkg, "main") != 0
|
||||||
|
|| got == NULL || strstr(got, "(use \"alpha\"") == NULL) {
|
||||||
|
fputs("imports-only parse rejected module boundary\n", stderr);
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
free((void *)pkg);
|
||||||
|
free(got);
|
||||||
|
}
|
||||||
|
|
||||||
if (fail) {
|
if (fail) {
|
||||||
fprintf(stderr, "%d parse tests failed\n", fail);
|
fprintf(stderr, "%d parse tests failed\n", fail);
|
||||||
return 1;
|
return 1;
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
//ww:error c "is not a variant of" ww "ambiguous without nominal layout"
|
//ww:error c "is not a variant of" ww "not a variant of scrutinee"
|
||||||
// migrated from test/wcc/787_xmod_variant_match.c: a same-leaf variant from a DIFFERENT module is NOT a variant of the scrutinee — guards #13 against bare leaf-strip false-accept.
|
// migrated from test/wcc/787_xmod_variant_match.c: a same-leaf variant from a
|
||||||
|
// DIFFERENT module is NOT a variant of the scrutinee — guards #13 against bare
|
||||||
|
// leaf-strip false-accept. Both checkers now resolve the direct qualifier before
|
||||||
|
// codegen and reject at the case boundary.
|
||||||
package pkg;
|
package pkg;
|
||||||
export type a = !void;
|
export type a = !void;
|
||||||
export type b = !void;
|
export type b = !void;
|
||||||
|
|||||||
Reference in New Issue
Block a user