fix: reject bare import bindings
This commit is contained in:
245
cmd/wcc/check.c
245
cmd/wcc/check.c
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user