fix: reject bare import bindings

This commit is contained in:
2026-08-22 15:17:10 +09:00
parent 91fce80b37
commit 89f519d5cb
6 changed files with 1490 additions and 38 deletions

View File

@@ -68,12 +68,17 @@ static int src_imports(Node *file, const char *modtag, int source,
const char *name);
static Sym *lookup_visible(Checker *c, const char *name);
static Sym *lookup_visible_type(Checker *c, const char *name);
static Sym *lookup_bare_import_binding(Checker *c, const char *name);
static int reject_bare_import_values(Checker *c, Node *n);
static int reject_bare_import_types(Checker *c, Node *n);
static void resolve_typedecl(Checker *c, Node *d);
static Type *
resolve_typename(Checker *c, Node *n)
{
const char *nm = n->str;
if (lookup_bare_import_binding(c, nm) != NULL)
return err(c, n->pos, "%s (package name) is not a type", nm);
Type *bi = lookup_builtin(nm);
if (bi) return bi;
/* #225: kind-filtered so a same-named value binding (param/let/fn)
@@ -328,6 +333,11 @@ eval_enum_value(Checker *c, Node *n, Tfield *prev, u64 *out)
if (fold_int_literal(n, out)) return 1;
switch (n->kind) {
case N_IDENT: {
if (lookup_bare_import_binding(c, n->str) != NULL) {
n->type = err(c, n->pos,
"use of package %s not in selector", n->str);
return 0;
}
for (Tfield *f = prev; f; f = f->next) {
if (f->name && n->str &&
strcmp(f->name, n->str) == 0) {
@@ -342,8 +352,12 @@ eval_enum_value(Checker *c, Node *n, Tfield *prev, u64 *out)
case N_BIN: {
u64 a, b;
if (!eval_enum_value(c, n->lhs, prev, &a) ||
!eval_enum_value(c, n->rhs, prev, &b))
!eval_enum_value(c, n->rhs, prev, &b)) {
if ((n->lhs && n->lhs->type == ty_err) ||
(n->rhs && n->rhs->type == ty_err))
n->type = ty_err;
return 0;
}
if (fold_binop(n->op, a, b, out))
return 1;
if ((n->op == TK_SLASH || n->op == TK_PERCENT) && b == 0)
@@ -355,8 +369,10 @@ eval_enum_value(Checker *c, Node *n, Tfield *prev, u64 *out)
}
case N_UN: {
u64 v;
if (!eval_enum_value(c, n->lhs, prev, &v))
if (!eval_enum_value(c, n->lhs, prev, &v)) {
if (n->lhs && n->lhs->type == ty_err) n->type = ty_err;
return 0;
}
switch (n->op) {
case TK_MINUS: *out = (u64)(-(i64)v); return 1;
case TK_TILDE: *out = ~v; return 1;
@@ -578,8 +594,12 @@ eval_def_const(Checker *c, Node *n, u64 *out, int depth)
case N_BIN: {
u64 a, b;
if (!eval_def_const(c, n->lhs, &a, depth + 1) ||
!eval_def_const(c, n->rhs, &b, depth + 1))
!eval_def_const(c, n->rhs, &b, depth + 1)) {
if ((n->lhs && n->lhs->type == ty_err) ||
(n->rhs && n->rhs->type == ty_err))
n->type = ty_err;
return 0;
}
if (fold_binop(n->op, a, b, out))
return 1;
if ((n->op == TK_SLASH || n->op == TK_PERCENT) && b == 0)
@@ -593,7 +613,10 @@ eval_def_const(Checker *c, Node *n, u64 *out, int depth)
/* fold_int_literal already covers unary-over-leaf; this
* arm catches unary over a resolved ref, e.g. `-A`. */
u64 v;
if (!eval_def_const(c, n->lhs, &v, depth + 1)) return 0;
if (!eval_def_const(c, n->lhs, &v, depth + 1)) {
if (n->lhs && n->lhs->type == ty_err) n->type = ty_err;
return 0;
}
switch (n->op) {
case TK_MINUS: *out = (u64)(-(i64)v); return 1;
case TK_TILDE: *out = ~v; return 1;
@@ -610,7 +633,10 @@ eval_def_const(Checker *c, Node *n, u64 *out, int depth)
* N_CAST arm in pass 2). Strip the cast, keeping the value;
* a narrowing cast that loses the value fails loud. */
u64 v;
if (!eval_def_const(c, n->lhs, &v, depth + 1)) return 0;
if (!eval_def_const(c, n->lhs, &v, depth + 1)) {
if (n->lhs && n->lhs->type == ty_err) n->type = ty_err;
return 0;
}
if (!def_cast_fits(n->type, v)) {
err(c, n->pos,
"def value: narrowing cast loses value");
@@ -620,6 +646,12 @@ eval_def_const(Checker *c, Node *n, u64 *out, int depth)
return 1;
}
case N_IDENT: {
if (lookup_bare_import_binding(c, n->str) != NULL) {
if (n->type != ty_err)
n->type = err(c, n->pos,
"use of package %s not in selector", n->str);
return 0;
}
Sym *s = lookup_visible(c, n->str);
if (s == NULL || s->kind != SK_DEF ||
s->decl == NULL || s->decl->rhs == NULL)
@@ -751,13 +783,16 @@ resolve_type(Checker *c, Node *n)
* sentinel; clet patches it from the initialiser. */
} else if (n->rhs->kind == N_INTLIT) {
len = n->rhs->uval;
} else if (reject_bare_import_values(c, n->rhs)) {
/* These leaves own their package-name diagnostics; do not
* replace them with a dependent constant-fold error. */
} else if (eval_def_const(c, n->rhs, &v, 0)) {
/* #141: a def-dimensioned `[MAX]u8`; fold the
* const-expr dimension (the same machinery #133's
* let-init fold uses). The err below stays for a
* genuinely non-const rhs. */
len = v;
} else {
} else if (n->rhs->type != ty_err) {
err(c, n->pos, "array length must be an integer literal");
}
Type *elem = resolve_type(c, n->lhs);
@@ -1053,6 +1088,8 @@ resolve_type(Checker *c, Node *n)
u64 val;
if (m->lhs == NULL) {
val = prev + 1;
} else if (reject_bare_import_values(c, m->lhs)) {
val = prev + 1;
} else if (!eval_enum_value(c, m->lhs, head, &val)) {
val = prev + 1;
}
@@ -1296,6 +1333,9 @@ cbinop(Checker *c, Node *n)
{
Type *l = cexpr(c, n->lhs);
Type *r = cexpr(c, n->rhs);
/* Both operands have now been checked. An invalid operand owns the
* diagnostic; do not add a dependent operator-type error. */
if (l == ty_err || r == ty_err) return ty_err;
/* #120 (B): a binop/compare with one f32 operand lowers an untyped-
* float peer to f32 — harec unifies both operands to the operand type
* (ref/harec/src/check.c:1347-1348). A comparison's result is bool, so
@@ -1360,6 +1400,7 @@ static Type *
cunop(Checker *c, Node *n)
{
Type *t = cexpr(c, n->lhs);
if (t == ty_err) return ty_err;
switch (n->op) {
case TK_MINUS: case TK_PLUS:
if (!type_isnum(t))
@@ -1442,6 +1483,9 @@ 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");
if (lookup_bare_import_binding(c, n->str) != NULL)
return n->type = err(c, n->pos,
"use of package %s not in selector", n->str);
Sym *s = lookup_visible(c, n->str);
if (s == NULL)
return n->type = err(c, n->pos, "undefined: %s", n->str);
@@ -1689,6 +1733,22 @@ cexpr(Checker *c, Node *n)
type_name(c->a, base));
}
case N_CALL: {
/* Package qualifiers are not callable values, even when their local
* name has builtin spelling (len, size, alloc, ...). Diagnose before
* any builtin rewrite can erase the callee identifier. */
if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str
&& lookup_bare_import_binding(c, n->lhs->str) != NULL) {
int type_args = strcmp(n->lhs->str, "size") == 0
|| strcmp(n->lhs->str, "align") == 0;
(void)cexpr(c, n->lhs);
for (Node *a = n->list; a; a = a->next) {
if (type_args)
(void)resolve_type(c, a);
else
(void)cexpr(c, a);
}
return n->type = ty_err;
}
/* Hare-style builtins: len(x), append(s, v), alloc(...).
* Recognised by name with no scope binding; we type-check
* the args ourselves and skip the normal call resolution. */
@@ -2117,12 +2177,18 @@ 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 = lookup_visible(c, n->lhs->str);
if (s == NULL || s->kind != SK_TYPE)
t = err(c, n->pos, "unknown struct type '%s'",
n->lhs->str);
else
t = s->type;
if (lookup_bare_import_binding(c, n->lhs->str) != NULL) {
t = err(c, n->lhs->pos,
"%s (package name) is not a type", n->lhs->str);
n->lhs->type = ty_err;
} else {
Sym *s = lookup_visible(c, n->lhs->str);
if (s == NULL || s->kind != SK_TYPE)
t = err(c, n->pos, "unknown struct type '%s'",
n->lhs->str);
else
t = s->type;
}
} else {
t = resolve_type(c, n->lhs);
}
@@ -3031,7 +3097,7 @@ src_imports(Node *file, const char *modtag, int source, const char *name)
{
if (file == NULL || name == NULL || name[0] == '\0') return 0;
for (Node *u = file->list; u; u = u->next) {
if (u->kind != N_USE || u->useblank
if (u->kind != N_USE || u->useblank || invalid_init_import(u)
|| u->sourceid != source) continue;
/* Skip self-imports: lib/fmt/fmt_test.ww carries `use fmt;`
* even though its module tag is also "fmt"; that directive
@@ -3062,8 +3128,11 @@ lookup_visible(Checker *c, const char *name)
* the builtin as fallback while checking direct imported declarations. */
Sym *builtin = NULL;
if (s != NULL) {
if (s->decl != NULL) return s;
builtin = s;
/* An SK_USE declaration belongs to one contributing source file,
* unlike ordinary package declarations. Defer it to the source-owned
* lookup below instead of leaking a sibling file's qualifier. */
if (s->decl != NULL && s->kind != SK_USE) return s;
if (s->decl == NULL) builtin = s;
}
/* Flat scope installation may coalesce equal qualifiers from distinct
* files. The source-owned binding is authoritative, but a bare mention is
@@ -3078,6 +3147,152 @@ lookup_visible(Checker *c, const char *name)
return builtin;
}
/* Return the effective package-name object for a bare identifier without
* marking the import used. The import edge and the visible symbol must both
* agree: the former enforces file ownership, while the latter lets an existing
* closer declaration win and retains the flat-scope use_alias bridge. */
static Sym *
lookup_bare_import_binding(Checker *c, const char *name)
{
if (c == NULL || name == NULL || name[0] == '\0') return NULL;
Sym *s = scope_lookup_prefer(c->cur, c->cur_mod, name);
if (s == NULL || (s->kind != SK_USE && !s->use_alias)) return NULL;
if (src_imports(c->file, c->cur_mod, c->cur_source, name)) return s;
return NULL;
}
/* Walk a type subtree only far enough to classify package-name objects. This
* is deliberately not general type checking: the enum/array constant folders
* need the package diagnostic before they reduce an unsupported outer shape
* to a generic constant error. */
static int
reject_bare_import_types(Checker *c, Node *n)
{
if (n == NULL) return 0;
if (n->kind == N_IDENT || n->kind == N_TNAME) {
if (lookup_bare_import_binding(c, n->str) != NULL) {
if (n->type != ty_err)
n->type = err(c, n->pos,
"%s (package name) is not a type", n->str);
return 1;
}
/* Qualified type syntax is a legal selector use. Mark the exact
* source-owned binding even if the surrounding constant shape is
* independently invalid. */
if (n->kind == N_TNAME && n->str != NULL) {
const char *dot = strrchr(n->str, '.');
if (dot != NULL) {
char *head = astrndup(c->a, n->str,
(u64)(dot - n->str));
if (lookup_bare_import_binding(c, head) != NULL)
(void)use_path(c->file, c->cur_mod,
c->cur_source, head);
}
}
return 0;
}
int bad = 0;
if (n->kind == N_TARRAY) {
bad |= reject_bare_import_types(c, n->lhs);
bad |= reject_bare_import_values(c, n->rhs);
return bad;
}
if (n->kind == N_TENUM) {
bad |= reject_bare_import_types(c, n->lhs);
for (Node *p = n->list; p; p = p->next)
bad |= reject_bare_import_values(c, p->lhs);
return bad;
}
if (n->kind == N_TENUMMEMBER)
return reject_bare_import_values(c, n->lhs);
bad |= reject_bare_import_types(c, n->lhs);
bad |= reject_bare_import_types(c, n->rhs);
bad |= reject_bare_import_types(c, n->cond);
bad |= reject_bare_import_types(c, n->body);
bad |= reject_bare_import_types(c, n->els);
for (Node *p = n->list; p; p = p->next)
bad |= reject_bare_import_types(c, p);
return bad;
}
/* Reject every bare package-name value in a constant-expression subtree in
* source order. Direct package receivers remain legal selector qualifiers;
* cast operands, struct-literal heads, and size/align arguments retain their
* type context. */
static int
reject_bare_import_values(Checker *c, Node *n)
{
if (n == NULL) return 0;
switch (n->kind) {
case N_TPTR: case N_TSLICE: case N_TARRAY: case N_TFN:
case N_TSTRUCT: case N_TFIELD: case N_TCHAN:
case N_TTUPLE: case N_TTAGGED: case N_TBANG: case N_TENUM:
case N_TENUMMEMBER:
return reject_bare_import_types(c, n);
case N_IDENT: case N_TNAME:
if (lookup_bare_import_binding(c, n->str) != NULL) {
if (n->type != ty_err)
n->type = err(c, n->pos,
"use of package %s not in selector", n->str);
return 1;
}
if (n->kind == N_TNAME && n->str != NULL) {
const char *dot = strrchr(n->str, '.');
if (dot != NULL) {
char *head = astrndup(c->a, n->str,
(u64)(dot - n->str));
if (lookup_bare_import_binding(c, head) != NULL)
(void)use_path(c->file, c->cur_mod,
c->cur_source, head);
}
}
return 0;
case N_DOT: {
int bad = 0;
if (n->lhs && n->lhs->kind == N_IDENT
&& lookup_bare_import_binding(c, n->lhs->str) != NULL) {
(void)use_path(c->file, c->cur_mod, c->cur_source,
n->lhs->str);
} else {
bad |= reject_bare_import_values(c, n->lhs);
}
return bad;
}
case N_CAST: case N_TYPEASSERT: case N_TYPETEST: {
int bad = reject_bare_import_values(c, n->lhs);
bad |= reject_bare_import_types(c, n->rhs);
return bad;
}
case N_STRUCTLIT: {
int bad = reject_bare_import_types(c, n->lhs);
for (Node *p = n->list; p; p = p->next)
bad |= reject_bare_import_values(c, p->lhs);
return bad;
}
case N_CALL: {
int bad = reject_bare_import_values(c, n->lhs);
int typearg = n->lhs && n->lhs->kind == N_IDENT
&& (strcmp(n->lhs->str, "size") == 0
|| strcmp(n->lhs->str, "align") == 0);
for (Node *p = n->list; p; p = p->next)
bad |= typearg ? reject_bare_import_types(c, p)
: reject_bare_import_values(c, p);
return bad;
}
default:
break;
}
int bad = 0;
bad |= reject_bare_import_values(c, n->lhs);
bad |= reject_bare_import_values(c, n->rhs);
bad |= reject_bare_import_values(c, n->cond);
bad |= reject_bare_import_values(c, n->body);
bad |= reject_bare_import_values(c, n->els);
for (Node *p = n->list; p; p = p->next)
bad |= reject_bare_import_values(c, p);
return bad;
}
static Sym *
lookup_visible_type(Checker *c, const char *name)
{