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)
|
||||
{
|
||||
|
||||
@@ -9545,6 +9545,221 @@ No format bump. Build workdir format remains `18`, test workdir format remains
|
||||
`19`, semantic storage format remains `3`, and no test-result cache is
|
||||
introduced.
|
||||
|
||||
### 11.48 Implemented selector-only effective import bindings
|
||||
|
||||
Pinned Go's applicable semantic rule is that an imported package's effective
|
||||
name denotes a package-name object, not a value or type, and that object may be
|
||||
used only to qualify a selector. A bare value occurrence is rejected as
|
||||
`use of package BINDING not in selector`; a bare type occurrence is rejected as
|
||||
`BINDING (package name) is not a type`. A bare occurrence does not mark the
|
||||
import used, while a selector does. These rules and diagnostics are **behavior
|
||||
directly implemented or asserted by pinned Go**.
|
||||
|
||||
The rule honestly applies within WW's local, dotted-import, manifest-free
|
||||
model. WW already resolves every ordinary import to an effective default or
|
||||
explicit file-local qualifier and already represents qualified value and type
|
||||
selectors. Treating that existing qualifier as a package-name object closes a
|
||||
checker hole without adding quoted, grouped, dot, generalized, network, module,
|
||||
manifest, registry, or source-expression import machinery. Applying Go's
|
||||
package-name-object rule to WW's already representable import binding is
|
||||
**behavior derived from the pinned implementation**.
|
||||
|
||||
#### Pinned evidence and fact classification
|
||||
|
||||
The sole semantic authority is official Go 1.26.5 at
|
||||
`c19862e5f8415b4f24b189d065ed739517c548ba`:
|
||||
|
||||
- [`ident` in `cmd/compile/internal/types2/typexpr.go`, lines
|
||||
18–60](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/types2/typexpr.go#L18-L60)
|
||||
resolves an identifier to its object and, in a type context, rejects an
|
||||
object that is not a type name as `NAME (KIND) is not a type`;
|
||||
- [the package-name value branch in that file, lines
|
||||
89–92](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/types2/typexpr.go#L89-L92)
|
||||
rejects a package name outside a selector as
|
||||
`use of package NAME not in selector`;
|
||||
- [`objectKind` in `cmd/compile/internal/types2/object.go`, lines
|
||||
675–680](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/types2/object.go#L675-L680)
|
||||
names a `PkgName` object's kind `package name`;
|
||||
- [selector checking in `cmd/compile/internal/types2/call.go`, lines
|
||||
672–735](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/types2/call.go#L672-L735),
|
||||
especially lines 681–691, admits a package name in the selector position and
|
||||
marks that exact package-name object used;
|
||||
- [import declaration in `cmd/compile/internal/types2/resolver.go`, lines
|
||||
248–340](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/types2/resolver.go#L248-L340)
|
||||
creates the `PkgName` in file scope, and
|
||||
[`unusedImports`, lines
|
||||
706–740](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/types2/resolver.go#L706-L740)
|
||||
diagnoses each nonblank package-name object not marked by a selector;
|
||||
- [compiler diagnostic sorting in `cmd/compile/internal/base/print.go`, lines
|
||||
70–92](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/base/print.go#L70-L92)
|
||||
preserves stable source-position ordering; and
|
||||
- [binary-expression checking in `cmd/compile/internal/types2/expr.go`, lines
|
||||
788–802](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/types2/expr.go#L788-L802)
|
||||
checks both operands before returning for either invalid result, so two bare
|
||||
package-name operands retain two independently owned diagnostics.
|
||||
|
||||
Those source branches are **behavior directly implemented or asserted by
|
||||
pinned Go**. Official assertions are
|
||||
[`test/fixedbugs/issue11361.go`, lines
|
||||
7–11](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/test/fixedbugs/issue11361.go#L7-L11),
|
||||
which pairs an unused import with its bare package-value error;
|
||||
[`src/internal/types/testdata/check/builtins0.go`, lines
|
||||
613–617](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/internal/types/testdata/check/builtins0.go#L613-L617),
|
||||
which rejects a package name passed as a value; and
|
||||
[`src/internal/types/testdata/check/decls1.go`, lines
|
||||
45–73](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/internal/types/testdata/check/decls1.go#L45-L73),
|
||||
especially line 66, which asserts `math (package name) is not a type`.
|
||||
|
||||
From those pinned mechanisms it follows that a bare-only named import receives
|
||||
both its unused-import diagnostic and its context-specific package-name
|
||||
diagnostic; a legal selector removes only the unused diagnostic and never
|
||||
excuses a distinct bare occurrence; an explicit alias supplies the displayed
|
||||
name; and a sibling file without its own binding follows ordinary name lookup.
|
||||
Those conclusions are **behavior derived from the pinned implementation**.
|
||||
|
||||
#### Fresh four-axis audit and direct pre-fix measurements
|
||||
|
||||
The fresh audit also confirmed multiple named source operands, explicit
|
||||
`*_test.ww` build operands, shared top-level test-process state and fatal-abort
|
||||
behavior, Go's regular-expression `-run` matching, and lexical shadowing of import
|
||||
bindings as applicable but unselected differences. Ordinary directory
|
||||
production selection was aligned. Grouped, quoted, and dot imports remain
|
||||
inapplicable to WW's deliberately narrower syntax. Those classifications are
|
||||
audit conclusions, not claims that this slice closes the unselected behavior.
|
||||
|
||||
Before this slice, the following observations were **directly measured WW
|
||||
behavior**. With a dependency declared `package wire` and exporting a value and
|
||||
type, both stages accepted legal `wire.Value` and `wire.Number` controls and
|
||||
produced byte-identical 8,201-byte executables with SHA-256
|
||||
`7fb0e95229aa22714d880070388ee5169697543076536267a8c709429cce0fcb`;
|
||||
both ran with status 41. A bare-only value caused only the generated-unit
|
||||
unused-import diagnostic in each stage. Once a legal selector marked the import
|
||||
used, Cstage let a separate bare value reach the linker as
|
||||
`undefined reference to 'wire'`, while WWstage let it reach code generation as
|
||||
`cgident: unresolvable identifier (rule 7)`. An explicit alias reproduced the
|
||||
same stage split using that alias. Reversing occurrence order or adding a
|
||||
second bare occurrence did not restore checker ownership.
|
||||
|
||||
A bare type plus a legal selector was reported by both stages as
|
||||
`unknown type 'wire'`, rather than the pinned package-name diagnostic. With no
|
||||
selector, Cstage also retained the unused-import error, while WWstage emitted
|
||||
only the unknown-type error. A sibling source that did not import `wire` let a
|
||||
bare value reach the linker or code generator rather than following ordinary
|
||||
undefined-name checking. Blank-import controls did follow ordinary undefined
|
||||
lookup, missing targets retained missing-package precedence, and the valid
|
||||
qualified/blank control retained artifact-byte and runtime parity. All cold
|
||||
failed work directories examined were empty and no output was published.
|
||||
|
||||
The same checker escape was measured in same-package, external-test, honest
|
||||
test-only, production-test, and imported-dependency paths: the test coordinator
|
||||
reported `FAIL`, but Cstage failed in the linker while WWstage failed in code
|
||||
generation. These status, stream, artifact, and cleanup results are **directly
|
||||
measured WW behavior** from the pre-change Cstage/WWstage probes; they are not
|
||||
attributed to Go.
|
||||
|
||||
The directly measured pre-fix result across the permanent axes was therefore:
|
||||
|
||||
- **Go-like build:** legal selector controls loaded, compiled, linked,
|
||||
published, and ran byte-identically, but a bare package name escaped semantic
|
||||
checking and failed at different backend phases;
|
||||
- **Go-like test:** every applicable generated test source role reached the
|
||||
ordinary package failure result, but its checker diagnostic and failure phase
|
||||
differed between stages;
|
||||
- **Go-like package:** effective qualifiers were already source-local during
|
||||
legal selector resolution, but sibling and bare lookup could escape that
|
||||
source-local package-name boundary; and
|
||||
- **Go-like import:** unused accounting recognized some selector uses, but a
|
||||
bare import binding was not consistently classified as a non-value,
|
||||
non-type package-name object.
|
||||
|
||||
#### Ownership and complete four-axis contract
|
||||
|
||||
The true semantic owner is identifier and type-name resolution in the Cstage
|
||||
and WWstage semantic checkers. The checker must distinguish a package-name
|
||||
object belonging to the current source from ordinary lexical or package-scope
|
||||
objects before generic undefined, type, code-generation, or link recovery. A
|
||||
selector remains the only construct that consumes such an object and marks its
|
||||
import used. A bare type name must not accidentally mark the import used. Both
|
||||
implementations must produce the same normalized diagnostics at the same source
|
||||
positions and retain ordinary undefined or unknown-type behavior when the
|
||||
current file has no binding.
|
||||
|
||||
- **Go-like build:** every selected source is rejected during semantic checking
|
||||
before compiler output, assembly, archive, link, or installation when it
|
||||
contains a bare package-name object. Legal selectors retain the existing
|
||||
build graph, actions, artifact bytes, runtime result, and publication rules.
|
||||
- **Go-like test:** production, same-package, external-test, and honest
|
||||
test-only sources use the same checker rule. Rejection precedes test-process
|
||||
construction and execution; discovery, filters, descriptors, result
|
||||
accounting, fatal/skip behavior, timeouts, and process cleanup do not change.
|
||||
- **Go-like package:** the binding remains owned by exactly its source file.
|
||||
Declared package names, canonical package and variant identity, selected
|
||||
source membership, exported declarations, initialization, and symbol naming
|
||||
do not change.
|
||||
- **Go-like import:** default and explicit named bindings become consistently
|
||||
selector-only package-name objects. Selectors alone satisfy unused-import
|
||||
accounting. Blank imports create no binding, rejected effective `init`
|
||||
imports install none, and missing-target resolution keeps precedence.
|
||||
|
||||
#### Lifecycle, parity, proof, and formats
|
||||
|
||||
Loading and source eligibility, filename ordering, package-clause validation,
|
||||
canonical dotted resolution, contextual local/vendor expansion, source-role
|
||||
classification, and graph-edge construction remain unchanged. The diagnostic
|
||||
is a property of an already resolved file-local binding; physical directories
|
||||
remain loader and presentation metadata and never acquire package, import,
|
||||
graph, action, artifact, symbol, `.wwi`, publication, or persistence identity.
|
||||
|
||||
Semantic rejection occurs within the compiler action before code generation,
|
||||
so no assembler, archiver, linker, test runtime, or installer may run for the
|
||||
invalid action.
|
||||
Diagnostic source order and precedence must remain stable: missing-package,
|
||||
invalid effective-`init`, blank-no-binding, import collision, and ordinary
|
||||
undefined-name paths keep their established owners; a valid binding receives
|
||||
the exact package-name diagnostic and, unless a selector separately used it,
|
||||
its independent unused-import diagnostic. Repeated invalid bare occurrences
|
||||
are diagnosed independently.
|
||||
|
||||
Cold rejection must publish no executable, archive, interface, retained test
|
||||
binary, or partial semantic action. Warm rejection must preserve the previous
|
||||
public product and complete committed work generation byte-for-byte. A later
|
||||
valid request must recover through existing invalidation and reuse rules.
|
||||
Neither rejected source text nor its physical directory may create a new
|
||||
identity or persistence key. Valid controls must retain comparable Cstage and
|
||||
WWstage diagnostic and artifact-byte parity.
|
||||
|
||||
The invalid consumer's compiler action starts and rejects during semantic
|
||||
checking, before its code-generation or downstream producer boundary. Already
|
||||
valid dependency producers may also have completed before that rejection.
|
||||
Assembler, archiver, linker, installer, test-runtime, and runtime-failure paths
|
||||
for the invalid consumer are therefore unreachable. Existing producer/runtime
|
||||
failure, rollback, publication transactions, parallel-product isolation,
|
||||
concurrent-request locking, cancellation, interruption escalation, child
|
||||
ownership, and descendant cleanup remain unchanged for other actions. Rejection
|
||||
must remove request-owned stages and leave no active `.new`, `.install`,
|
||||
`.wwtxn.*`, adjacent `.sepwork`, capture, result, scratch, or tool-stage
|
||||
transaction residue.
|
||||
|
||||
The WW-native `bare_import_bindings_require_selectors` observer is required to
|
||||
prove exact cross-stage status and normalized diagnostic parity for bare value
|
||||
and type contexts, explicit aliases, selector order, repeated occurrences,
|
||||
unused accounting, sibling-file isolation, blank/`init`/missing controls,
|
||||
ordinary and imported builds, builtin-spelled package callees (`len`, `size`,
|
||||
and `align`), nested value/type contexts, and every applicable test source role.
|
||||
In particular, parser-shaped `size`/`align` type arguments must keep type
|
||||
checking after the callee is classified as a package-name object and must never
|
||||
escape through generic internal-expression recovery. The observer must also
|
||||
prove valid artifact-byte/runtime parity, cold empty rollback, warm prior state
|
||||
preservation, invalidation and recovery, and residue cleanup. Broader
|
||||
producer/runtime failure, concurrency, interruption, and descendant-process
|
||||
behavior retain their existing owners because this slice adds no such
|
||||
boundary. These are proof requirements; validation and final post-change byte
|
||||
measurements are recorded only after they are run.
|
||||
|
||||
No format bump. Build workdir format remains `18`, test workdir format remains
|
||||
`19`, semantic storage format remains `3`, and no test-result cache is
|
||||
introduced.
|
||||
|
||||
## 12. Candidate architectures and hard-gate decision
|
||||
|
||||
Five candidates were developed as coherent systems, not as feature bins.
|
||||
|
||||
20
docs/spec.md
20
docs/spec.md
@@ -323,11 +323,25 @@ ImportPath = ident { "." ident } .
|
||||
package declares `package wire;`, the importing file sees its exported names
|
||||
as `wire.Name`; `codec.Name` is not an additional binding. An explicit alias
|
||||
replaces only that visible qualifier: `import stable acme.codec;` exposes
|
||||
`stable.Name`, not `wire.Name` or `codec.Name`. Both kinds of binding are
|
||||
scoped to that source file. A sibling file must declare its own import.
|
||||
`stable.Name`, not `wire.Name` or `codec.Name`. Each effective default or
|
||||
explicit named binding is a file-local package-name object usable only as
|
||||
the left qualifier of a selector. A bare occurrence in a value context is
|
||||
rejected as `use of package BINDING not in selector`; a bare occurrence in
|
||||
a type context is rejected as `BINDING (package name) is not a type`. Such a
|
||||
rejected bare occurrence does not count as use of the import for
|
||||
unused-import accounting. A legal selector does count as use, but does not
|
||||
excuse any separate bare occurrence. Both kinds of named binding are scoped
|
||||
to that source file. A sibling file must declare its own import; without one,
|
||||
its otherwise equal spelling follows ordinary undefined-name or unknown-type
|
||||
lookup rather than package-name diagnostics. Builtin spelling does not alter
|
||||
the object: an import bound as `len`, `size`, `align`, or another builtin name
|
||||
remains a selector-only package-name object.
|
||||
Neither form exposes an imported declaration as a bare `Name`; ordinary
|
||||
unqualified lookup remains limited to lexical, builtin, and same-package
|
||||
declarations.
|
||||
declarations. A blank import creates no package-name object, an effective
|
||||
`init` import is rejected before installing one, and a missing target fails
|
||||
during import resolution, so none of those cases acquires the bare-package
|
||||
diagnostics or satisfies a named import's unused accounting.
|
||||
- An import whose effective file-local qualifier is `init` is invalid. This
|
||||
includes both `import init acme.codec;` and an unaliased import whose target
|
||||
declares `package init;`. Each resolved occurrence is rejected at its first
|
||||
|
||||
@@ -583,6 +583,35 @@ exact-argv, command, and persistent-workdir observers, the package suite proves
|
||||
archive-only link argv and exact warm/rejection-state behavior without
|
||||
duplicating those broader mechanisms in this observer.
|
||||
|
||||
The focused dual-stage `bare_import_bindings_require_selectors` observer is the
|
||||
acceptance owner for the rule that an effective default or explicit import
|
||||
binding is a file-local package-name object, not a value or type. Its generated
|
||||
matrix must prove exact `use of package BINDING not in selector` value
|
||||
diagnostics and `BINDING (package name) is not a type` type diagnostics; an
|
||||
unused diagnostic for every named binding that has only bare occurrences; and
|
||||
the absence of that unused diagnostic once a legal selector independently uses
|
||||
the binding. It must cover occurrence order, repeated bare occurrences,
|
||||
explicit aliases, builtin-spelled bindings including `len`, `size`, and `align`,
|
||||
qualified value and type controls, nested value and type contexts, sibling-file
|
||||
isolation, blank imports, rejected `init` bindings, and missing-target
|
||||
precedence. Type-shaped arguments parsed for `size` and `align` must retain type
|
||||
checking when either spelling is instead an import binding; neither stage may
|
||||
fall into an internal expression-kind diagnostic.
|
||||
|
||||
The same observer must exercise ordinary root and imported builds plus
|
||||
production, same-package test, external-test, and honest test-only source
|
||||
roles. Cstage and WWstage must agree on status and normalized stdout/stderr,
|
||||
including diagnostic order and source position, and valid controls must produce
|
||||
byte-identical comparable artifacts and equal runtime results. Its lifecycle
|
||||
matrix must include cold rejection with no public or retained product, warm
|
||||
preservation of every prior committed work-file and public byte, imported
|
||||
dependency failure, invalidation followed by rejection, subsequent valid
|
||||
recovery, and exact transaction-residue checks. Legal selector controls retain
|
||||
ordinary graph/action, compilation, linking, publication, persistence, and
|
||||
reuse behavior. Producer/runtime failure, concurrency, interruption, process
|
||||
topology, and descendant cleanup remain with their established observers
|
||||
because this checker-owned rejection creates no producer or runtime boundary.
|
||||
|
||||
The focused dual-stage `effective_init_imports_never_enter_binding_recovery`
|
||||
observer is
|
||||
the acceptance owner for imports whose effective file-local qualifier is
|
||||
|
||||
@@ -237,6 +237,7 @@ fn srcimports(file: *syntax.node, modtag: str, source: i32, name: str) bool = {
|
||||
let u: *syntax.node = file.list;
|
||||
for (u != nil) {
|
||||
if (u.kind == syntax.nkind.N_USE && u.useblank == 0
|
||||
&& !invalidinitimport(u)
|
||||
&& u.sourceid == source) {
|
||||
// Skip self-imports: lib/fmt/fmt_test.ww carries
|
||||
// `use fmt;` while its module tag is also "fmt".
|
||||
@@ -267,8 +268,10 @@ 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;
|
||||
// A pure SK_USE belongs to one source file. Defer it to the
|
||||
// source-owned path below instead of leaking a sibling qualifier.
|
||||
if (found.decl != nil && found.skind != syntax.skind.SK_USE) { return found; };
|
||||
if (found.decl == nil) { builtin = found; };
|
||||
};
|
||||
// Flat scope installation may coalesce equal qualifiers from distinct
|
||||
// files. A bare mention only locates the marker; the DOT resolution owns
|
||||
@@ -289,6 +292,45 @@ fn lookupvisible(c: *checker, name: str) *syntax.sym = {
|
||||
return builtin;
|
||||
};
|
||||
|
||||
// Return the source-local package-name object for a bare identifier without
|
||||
// marking the import used. A closer lexical value wins; a coexisting SK_USE or
|
||||
// promoted use_alias retains package-qualifier identity in the flat scope.
|
||||
fn lookupbareimportbinding(c: *checker, name: str) *syntax.sym = {
|
||||
if (name.len == 0) { return nil; };
|
||||
let found: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, name);
|
||||
if (found == nil) { return nil; };
|
||||
if (found.skind != syntax.skind.SK_USE && found.use_alias == 0i32) {
|
||||
let use: *syntax.sym = syntax.scopelookupuselocal(found.scope, name);
|
||||
if (use != nil) { found = use; };
|
||||
};
|
||||
if (found.skind == syntax.skind.SK_USE || found.use_alias != 0i32) {
|
||||
if (srcimports(c.file, c.curmod, c.cursource, name)) { return found; };
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
|
||||
fn bareimportvalueerr(c: *checker, n: *syntax.node) void = {
|
||||
if (n.type_ == c.tc.tyerr: *void) { return; };
|
||||
cerr(n.file); cerr(":");
|
||||
cerr(strconv.i32tos(n.line, strconv.base.DEC)); cerr(":");
|
||||
cerr(strconv.i32tos(n.col, strconv.base.DEC));
|
||||
cerr(": error: use of package "); cerr(n.str);
|
||||
cerr(" not in selector\n");
|
||||
c.errs += 1;
|
||||
n.type_ = c.tc.tyerr: *void;
|
||||
};
|
||||
|
||||
fn bareimporttypeerr(c: *checker, n: *syntax.node) void = {
|
||||
if (n.type_ == c.tc.tyerr: *void) { return; };
|
||||
cerr(n.file); cerr(":");
|
||||
cerr(strconv.i32tos(n.line, strconv.base.DEC)); cerr(":");
|
||||
cerr(strconv.i32tos(n.col, strconv.base.DEC));
|
||||
cerr(": error: "); cerr(n.str);
|
||||
cerr(" (package name) is not a type\n");
|
||||
c.errs += 1;
|
||||
n.type_ = c.tc.tyerr: *void;
|
||||
};
|
||||
|
||||
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
|
||||
@@ -672,6 +714,11 @@ fn resolvewalk(c: *checker, n: *syntax.node) void = {
|
||||
if (c.sepmode != 0 && n.type_ != nil) { c.nresolved += 1; return; };
|
||||
let nm: str = n.str;
|
||||
if (nm.len > 0) {
|
||||
if (lookupbareimportbinding(c, nm) != nil) {
|
||||
bareimporttypeerr(c, n);
|
||||
c.nresolved += 1;
|
||||
return;
|
||||
};
|
||||
let s: *syntax.sym = lookupvisibletype(c, nm);
|
||||
let builtin: bool = builtintypename(nm);
|
||||
// `pkg.Type` — strip the last dot prefix and look up
|
||||
@@ -1011,13 +1058,35 @@ fn resolvewalk(c: *checker, n: *syntax.node) void = {
|
||||
if (k == syntax.nkind.N_DOT) {
|
||||
// Walk only the base; the .field name is a member, not a
|
||||
// free identifier.
|
||||
if (n.lhs != nil) { resolvewalk(c, n.lhs); };
|
||||
if (n.lhs != nil) {
|
||||
if (n.lhs.kind == syntax.nkind.N_IDENT
|
||||
&& lookupbareimportbinding(c, n.lhs.str) != nil) {
|
||||
// A package receiver is a lookup target, not a value expression.
|
||||
c.nresolved += 1;
|
||||
} else { resolvewalk(c, n.lhs); };
|
||||
};
|
||||
// A.6.0: branch returns early; stamp here so the post-walk
|
||||
// dispatch below sees N_DOT covered.
|
||||
let _t: *syntax.node = exprtype(c, n, nil);
|
||||
return;
|
||||
};
|
||||
|
||||
if (k == syntax.nkind.N_STRUCTLIT) {
|
||||
// The literal head is a type designator. A package binding there gets
|
||||
// the type diagnostic, never the ordinary bare-value diagnostic.
|
||||
if (n.lhs != nil) {
|
||||
if (n.lhs.kind == syntax.nkind.N_IDENT
|
||||
&& lookupbareimportbinding(c, n.lhs.str) != nil) {
|
||||
bareimporttypeerr(c, n.lhs);
|
||||
c.nresolved += 1;
|
||||
} else { resolvewalk(c, n.lhs); };
|
||||
};
|
||||
let sf: *syntax.node = n.list;
|
||||
for (sf != nil) { resolvewalk(c, sf); sf = sf.next; };
|
||||
let _st: *syntax.node = exprtype(c, n, nil);
|
||||
return;
|
||||
};
|
||||
|
||||
if (k == syntax.nkind.N_FIELD) {
|
||||
if (n.lhs != nil) { resolvewalk(c, n.lhs); };
|
||||
return;
|
||||
@@ -2098,8 +2167,18 @@ fn evaldefconst(c: *checker, n: *syntax.node, out: *u64, depth: i32) bool = {
|
||||
if (k == syntax.nkind.N_BIN) {
|
||||
let a: u64 = 0u64;
|
||||
let b: u64 = 0u64;
|
||||
if (!evaldefconst(c, n.lhs, &a, depth + 1)) { return false; };
|
||||
if (!evaldefconst(c, n.rhs, &b, depth + 1)) { return false; };
|
||||
if (!evaldefconst(c, n.lhs, &a, depth + 1)) {
|
||||
if (n.lhs != nil && n.lhs.type_ == c.tc.tyerr: *void) {
|
||||
n.type_ = c.tc.tyerr: *void;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
if (!evaldefconst(c, n.rhs, &b, depth + 1)) {
|
||||
if (n.rhs != nil && n.rhs.type_ == c.tc.tyerr: *void) {
|
||||
n.type_ = c.tc.tyerr: *void;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
if (foldbinop(n.op, a, b, out)) { return true; };
|
||||
if ((n.op == syntax.tkind.TK_SLASH || n.op == syntax.tkind.TK_PERCENT) && b == 0u64) {
|
||||
deffolderr(c, n, "def value: division by zero");
|
||||
@@ -2112,7 +2191,12 @@ fn evaldefconst(c: *checker, n: *syntax.node, out: *u64, depth: i32) bool = {
|
||||
// foldintliteral already covers unary-over-leaf; this arm
|
||||
// catches unary over a resolved ref, e.g. `-A`.
|
||||
let v: u64 = 0u64;
|
||||
if (!evaldefconst(c, n.lhs, &v, depth + 1)) { return false; };
|
||||
if (!evaldefconst(c, n.lhs, &v, depth + 1)) {
|
||||
if (n.lhs != nil && n.lhs.type_ == c.tc.tyerr: *void) {
|
||||
n.type_ = c.tc.tyerr: *void;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
if (n.op == syntax.tkind.TK_MINUS) { *out = (-(v: i64)): u64; return true; };
|
||||
if (n.op == syntax.tkind.TK_TILDE) { *out = ~v; return true; };
|
||||
if (n.op == syntax.tkind.TK_PLUS) { *out = v; return true; };
|
||||
@@ -2124,7 +2208,12 @@ fn evaldefconst(c: *checker, n: *syntax.node, out: *u64, depth: i32) bool = {
|
||||
// exprtype's N_CAST arm during resolvewalk). Strip the cast
|
||||
// keeping the value; a narrowing cast that loses it fails loud.
|
||||
let v: u64 = 0u64;
|
||||
if (!evaldefconst(c, n.lhs, &v, depth + 1)) { return false; };
|
||||
if (!evaldefconst(c, n.lhs, &v, depth + 1)) {
|
||||
if (n.lhs != nil && n.lhs.type_ == c.tc.tyerr: *void) {
|
||||
n.type_ = c.tc.tyerr: *void;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
let t: *syntax.tinfo = (n.type_): *syntax.tinfo;
|
||||
if (!defcastfits(t, v)) {
|
||||
deffolderr(c, n, "def value: narrowing cast loses value");
|
||||
@@ -2134,6 +2223,10 @@ fn evaldefconst(c: *checker, n: *syntax.node, out: *u64, depth: i32) bool = {
|
||||
return true;
|
||||
};
|
||||
if (k == syntax.nkind.N_IDENT) {
|
||||
if (lookupbareimportbinding(c, n.str) != nil) {
|
||||
bareimportvalueerr(c, n);
|
||||
return false;
|
||||
};
|
||||
let s: *syntax.sym = lookupvisible(c, n.str);
|
||||
if (s == nil) { return false; };
|
||||
if (s.skind != syntax.skind.SK_DEF) { return false; };
|
||||
@@ -2419,6 +2512,130 @@ fn validatestructfields(c: *checker, n: *syntax.node) void = {
|
||||
// resolvewalk's eager type-decl dispatch, sibling to validatestructfields
|
||||
// (rule-10 symmetric with cstage's once-per-resolve_type), not from the
|
||||
// per-query size/align arms.
|
||||
fn bareimporttypenode(k: syntax.nkind) bool = {
|
||||
return k == syntax.nkind.N_TPTR || k == syntax.nkind.N_TSLICE
|
||||
|| k == syntax.nkind.N_TARRAY
|
||||
|| k == syntax.nkind.N_TFN || k == syntax.nkind.N_TSTRUCT
|
||||
|| k == syntax.nkind.N_TFIELD || k == syntax.nkind.N_TCHAN
|
||||
|| k == syntax.nkind.N_TTUPLE || k == syntax.nkind.N_TTAGGED
|
||||
|| k == syntax.nkind.N_TBANG || k == syntax.nkind.N_TENUM
|
||||
|| k == syntax.nkind.N_TENUMMEMBER || k == syntax.nkind.N_TPARAM;
|
||||
};
|
||||
|
||||
fn rejectbareimporttypes(c: *checker, n: *syntax.node) bool = {
|
||||
if (n == nil) { return false; };
|
||||
if (n.kind == syntax.nkind.N_IDENT || n.kind == syntax.nkind.N_TNAME) {
|
||||
if (lookupbareimportbinding(c, n.str) != nil) {
|
||||
bareimporttypeerr(c, n);
|
||||
return true;
|
||||
};
|
||||
// A qualified type is a legal selector use even when its surrounding
|
||||
// constant-expression shape is independently invalid.
|
||||
if (n.kind == syntax.nkind.N_TNAME && strings.contains(n.str, ".")) {
|
||||
let (head, leaf) = strings.rcut(n.str, ".");
|
||||
if (lookupbareimportbinding(c, head) != nil) {
|
||||
let marked: str = usepathfor(c.file, c.curmod, c.cursource, head);
|
||||
};
|
||||
};
|
||||
return false;
|
||||
};
|
||||
let bad: bool = false;
|
||||
if (n.kind == syntax.nkind.N_TARRAY) {
|
||||
if (rejectbareimporttypes(c, n.lhs)) { bad = true; };
|
||||
if (rejectbareimportvalues(c, n.rhs)) { bad = true; };
|
||||
return bad;
|
||||
};
|
||||
if (n.kind == syntax.nkind.N_TENUM) {
|
||||
if (rejectbareimporttypes(c, n.lhs)) { bad = true; };
|
||||
let m: *syntax.node = n.list;
|
||||
for (m != nil) {
|
||||
if (rejectbareimportvalues(c, m.lhs)) { bad = true; };
|
||||
m = m.next;
|
||||
};
|
||||
return bad;
|
||||
};
|
||||
if (n.kind == syntax.nkind.N_TENUMMEMBER) {
|
||||
return rejectbareimportvalues(c, n.lhs);
|
||||
};
|
||||
if (rejectbareimporttypes(c, n.lhs)) { bad = true; };
|
||||
if (rejectbareimporttypes(c, n.rhs)) { bad = true; };
|
||||
if (rejectbareimporttypes(c, n.cond)) { bad = true; };
|
||||
if (rejectbareimporttypes(c, n.body)) { bad = true; };
|
||||
if (rejectbareimporttypes(c, n.els)) { bad = true; };
|
||||
let p: *syntax.node = n.list;
|
||||
for (p != nil) {
|
||||
if (rejectbareimporttypes(c, p)) { bad = true; };
|
||||
p = p.next;
|
||||
};
|
||||
return bad;
|
||||
};
|
||||
|
||||
fn rejectbareimportvalues(c: *checker, n: *syntax.node) bool = {
|
||||
if (n == nil) { return false; };
|
||||
if (bareimporttypenode(n.kind)) { return rejectbareimporttypes(c, n); };
|
||||
if (n.kind == syntax.nkind.N_IDENT || n.kind == syntax.nkind.N_TNAME) {
|
||||
if (lookupbareimportbinding(c, n.str) != nil) {
|
||||
bareimportvalueerr(c, n);
|
||||
return true;
|
||||
};
|
||||
if (n.kind == syntax.nkind.N_TNAME && strings.contains(n.str, ".")) {
|
||||
let (head, leaf) = strings.rcut(n.str, ".");
|
||||
if (lookupbareimportbinding(c, head) != nil) {
|
||||
let marked: str = usepathfor(c.file, c.curmod, c.cursource, head);
|
||||
};
|
||||
};
|
||||
return false;
|
||||
};
|
||||
if (n.kind == syntax.nkind.N_DOT) {
|
||||
if (n.lhs != nil && n.lhs.kind == syntax.nkind.N_IDENT
|
||||
&& lookupbareimportbinding(c, n.lhs.str) != nil) {
|
||||
let marked: str = usepathfor(c.file, c.curmod, c.cursource, n.lhs.str);
|
||||
return false;
|
||||
};
|
||||
return rejectbareimportvalues(c, n.lhs);
|
||||
};
|
||||
if (n.kind == syntax.nkind.N_CAST || n.kind == syntax.nkind.N_TYPEASSERT
|
||||
|| n.kind == syntax.nkind.N_TYPETEST) {
|
||||
let bad: bool = rejectbareimportvalues(c, n.lhs);
|
||||
if (rejectbareimporttypes(c, n.rhs)) { bad = true; };
|
||||
return bad;
|
||||
};
|
||||
if (n.kind == syntax.nkind.N_STRUCTLIT) {
|
||||
let bad: bool = rejectbareimporttypes(c, n.lhs);
|
||||
let f: *syntax.node = n.list;
|
||||
for (f != nil) {
|
||||
if (rejectbareimportvalues(c, f.lhs)) { bad = true; };
|
||||
f = f.next;
|
||||
};
|
||||
return bad;
|
||||
};
|
||||
if (n.kind == syntax.nkind.N_CALL) {
|
||||
let bad: bool = rejectbareimportvalues(c, n.lhs);
|
||||
let typearg: bool = n.lhs != nil && n.lhs.kind == syntax.nkind.N_IDENT
|
||||
&& (syntax.streq(n.lhs.str, "size") || syntax.streq(n.lhs.str, "align"));
|
||||
let a: *syntax.node = n.list;
|
||||
for (a != nil) {
|
||||
if (typearg) {
|
||||
if (rejectbareimporttypes(c, a)) { bad = true; };
|
||||
} else { if (rejectbareimportvalues(c, a)) { bad = true; }; };
|
||||
a = a.next;
|
||||
};
|
||||
return bad;
|
||||
};
|
||||
let bad: bool = false;
|
||||
if (rejectbareimportvalues(c, n.lhs)) { bad = true; };
|
||||
if (rejectbareimportvalues(c, n.rhs)) { bad = true; };
|
||||
if (rejectbareimportvalues(c, n.cond)) { bad = true; };
|
||||
if (rejectbareimportvalues(c, n.body)) { bad = true; };
|
||||
if (rejectbareimportvalues(c, n.els)) { bad = true; };
|
||||
let p: *syntax.node = n.list;
|
||||
for (p != nil) {
|
||||
if (rejectbareimportvalues(c, p)) { bad = true; };
|
||||
p = p.next;
|
||||
};
|
||||
return bad;
|
||||
};
|
||||
|
||||
fn validateenummembers(c: *checker, n: *syntax.node) void = {
|
||||
// storage type: cstage resolves n->lhs then gates type_isint
|
||||
// (check.c:1004-1009); default storage is i32, always integer.
|
||||
@@ -2464,7 +2681,8 @@ fn validateenummembers(c: *checker, n: *syntax.node) void = {
|
||||
// `until = m` enforces harec's forward-only sibling-ref discipline.
|
||||
if (m.lhs != nil) {
|
||||
let v: u64 = 0u64;
|
||||
if (!enumvalfold(n, m, m.lhs, &v)) {
|
||||
let barebad: bool = rejectbareimportvalues(c, m.lhs);
|
||||
if (!barebad && !enumvalfold(n, m, m.lhs, &v)) {
|
||||
cerr(m.file);
|
||||
cerr(": error: enum value must be a constant integer expression\n");
|
||||
c.errs += 1;
|
||||
@@ -2475,14 +2693,20 @@ fn validateenummembers(c: *checker, n: *syntax.node) void = {
|
||||
};
|
||||
|
||||
// stampnilexpr — stamp nil-typed nodes in a constant expr subtree to
|
||||
// `ti`. lhs/rhs cover the enum constexpr grammar enumvalfold accepts
|
||||
// (literals, unary, binary, sibling backref); non-nil nodes keep the
|
||||
// `ti`. The specialised fold accepts lhs/rhs, but invalid outer shapes still
|
||||
// pass through the checker and must not leak nil-typed descendants into the
|
||||
// final invariant walk. Non-nil nodes, including package-name errors, keep the
|
||||
// type exprtype already derived.
|
||||
fn stampnilexpr(n: *syntax.node, ti: *syntax.tinfo) void = {
|
||||
if (n == nil) { return; };
|
||||
if (n.type_ == nil) { n.type_ = ti: *void; };
|
||||
stampnilexpr(n.lhs, ti);
|
||||
stampnilexpr(n.rhs, ti);
|
||||
stampnilexpr(n.cond, ti);
|
||||
stampnilexpr(n.body, ti);
|
||||
stampnilexpr(n.els, ti);
|
||||
let p: *syntax.node = n.list;
|
||||
for (p != nil) { stampnilexpr(p, ti); p = p.next; };
|
||||
};
|
||||
|
||||
// #61 A.5 helper: per-element slot size when `pt` appears inside a
|
||||
@@ -2624,6 +2848,13 @@ fn tinfofornode(c: *checker, n: *syntax.node) *syntax.tinfo = {
|
||||
switch (k) {
|
||||
case syntax.nkind.N_TNAME:
|
||||
let nm: str = n.str;
|
||||
// Synthetic mktname nodes have no file. Only parsed source syntax can
|
||||
// denote a bare package binding; internal primitive/type stamps must not.
|
||||
if (n.file.len != 0 && lookupbareimportbinding(c, nm) != nil) {
|
||||
bareimporttypeerr(c, n);
|
||||
syntax.tinfocachebind(c.tc, n, c.tc.tyerr);
|
||||
return c.tc.tyerr;
|
||||
};
|
||||
if (syntax.streq(nm, "void")) { r = c.tc.tyvoid; };
|
||||
if (syntax.streq(nm, "bool")) { r = c.tc.tybool; };
|
||||
if (syntax.streq(nm, "rune")) { r = c.tc.tyrune; };
|
||||
@@ -2760,13 +2991,16 @@ fn tinfofornode(c: *checker, n: *syntax.node) *syntax.tinfo = {
|
||||
elen = n.rhs.uval;
|
||||
} else {
|
||||
let v: u64 = 0u64;
|
||||
if (evaldefconst(c, n.rhs, &v, 0)) {
|
||||
let barebad: bool = rejectbareimportvalues(c, n.rhs);
|
||||
if (!barebad && evaldefconst(c, n.rhs, &v, 0)) {
|
||||
elen = v;
|
||||
} else {
|
||||
cerr(n.file); cerr(": ");
|
||||
cerr("error: array length must be an integer literal\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
} else { if (!barebad) {
|
||||
if (n.rhs.type_ != c.tc.tyerr: *void) {
|
||||
cerr(n.file); cerr(": ");
|
||||
cerr("error: array length must be an integer literal\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
}; };
|
||||
};
|
||||
};
|
||||
let sub: *syntax.tinfo = tinfofornode(c, n.lhs);
|
||||
@@ -3352,6 +3586,13 @@ fn binoptype(c: *checker, e: *syntax.node) *syntax.node = {
|
||||
let op: syntax.tkind = e.op;
|
||||
let ltn: *syntax.node = exprtype(c, e.lhs, nil);
|
||||
let rtn: *syntax.node = exprtype(c, e.rhs, nil);
|
||||
// Both operands were checked. Preserve their causal error on the parent and
|
||||
// do not manufacture an operator-type recovery diagnostic or nil stamp.
|
||||
if ((e.lhs != nil && e.lhs.type_ == c.tc.tyerr: *void)
|
||||
|| (e.rhs != nil && e.rhs.type_ == c.tc.tyerr: *void)) {
|
||||
e.type_ = c.tc.tyerr: *void;
|
||||
return nil;
|
||||
};
|
||||
// #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
|
||||
@@ -3485,6 +3726,10 @@ fn binoptype(c: *checker, e: *syntax.node) *syntax.node = {
|
||||
fn unoptype(c: *checker, e: *syntax.node) *syntax.node = {
|
||||
let op: syntax.tkind = e.op;
|
||||
let opt: *syntax.node = exprtype(c, e.lhs, nil);
|
||||
if (e.lhs != nil && e.lhs.type_ == c.tc.tyerr: *void) {
|
||||
e.type_ = c.tc.tyerr: *void;
|
||||
return nil;
|
||||
};
|
||||
// #38/F2 (review item 5): unop operand-kind gates the wwstage checker
|
||||
// elided. Mirror cstage cunop (cmd/wcc/check.c:1224-1238): unary +/-
|
||||
// want a numeric operand, ~ wants an integer, ! wants a bool. A nil
|
||||
@@ -3820,6 +4065,10 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
||||
// resolvewalk visits the dot receiver before its parent. Preserve the
|
||||
// first causal undefined error just as cstage's cached cexpr does.
|
||||
if (e.type_ == c.tc.tyerr: *void) { return nil; };
|
||||
if (lookupbareimportbinding(c, e.str) != nil) {
|
||||
bareimportvalueerr(c, e);
|
||||
return nil;
|
||||
};
|
||||
// #55: bare-leaf value-ident must prefer curmod. Flat-scope
|
||||
// scopelookup bucket-walks and can bind a same-leaf symbol from
|
||||
// the wrong module under a foreign curmod, dragging its decl's
|
||||
@@ -3848,6 +4097,14 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
||||
return nil;
|
||||
};
|
||||
if (s.decl == nil) { return nil; };
|
||||
// Propagate an errored inferred initializer through its binding. Without
|
||||
// this, a later `bad.field` is left nil-typed and asserttyped obscures the
|
||||
// causal checker diagnostic emitted for the initializer.
|
||||
if (s.decl.kind == syntax.nkind.N_LET && s.decl.lhs == nil
|
||||
&& s.decl.rhs != nil && s.decl.rhs.type_ == c.tc.tyerr: *void) {
|
||||
e.type_ = c.tc.tyerr: *void;
|
||||
return nil;
|
||||
};
|
||||
e.refdecl = s.decl;
|
||||
// #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
|
||||
@@ -3906,6 +4163,7 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
||||
};
|
||||
if (k == syntax.nkind.N_BIN) {
|
||||
let tn: *syntax.node = binoptype(c, e);
|
||||
if (e.type_ == c.tc.tyerr: *void) { return nil; };
|
||||
// #59.9: checkisas pre-stamps an enum OR-fold (`(m.A|m.B) as
|
||||
// u32`) TY_ENUM and folds the member N_DOTs to int literals;
|
||||
// this post-order revisit re-derives from those now-untyped
|
||||
@@ -3927,6 +4185,7 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
||||
};
|
||||
if (k == syntax.nkind.N_UN) {
|
||||
let tn: *syntax.node = unoptype(c, e);
|
||||
if (e.type_ == c.tc.tyerr: *void) { return nil; };
|
||||
e.type_ = tinfofornode(c, tn): *void;
|
||||
return tn;
|
||||
};
|
||||
@@ -3945,6 +4204,14 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
||||
if (e.type_ == c.tc.tyerr: *void) { return nil; };
|
||||
let callee: *syntax.node = e.lhs;
|
||||
if (callee == nil) { return nil; };
|
||||
// Package qualifiers with builtin spelling remain package objects. Reject
|
||||
// before alloc/size/len/etc. can rewrite away the callee identifier.
|
||||
if (callee.kind == syntax.nkind.N_IDENT
|
||||
&& lookupbareimportbinding(c, callee.str) != nil) {
|
||||
bareimportvalueerr(c, callee);
|
||||
e.type_ = c.tc.tyerr: *void;
|
||||
return nil;
|
||||
};
|
||||
// A module-qualified leaf may already have been rejected while the
|
||||
// N_DOT callee was checked on an earlier resolve walk. cstage caches
|
||||
// that failure on the call; mirror its once-only diagnostic here rather
|
||||
@@ -4534,6 +4801,9 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
||||
e.type_ = c.tc.tyerr: *void;
|
||||
return nil;
|
||||
};
|
||||
// Raw unresolved external selectors retain selector semantics;
|
||||
// never reinterpret the package receiver as a bare value below.
|
||||
if (fs == nil && ms.skind == syntax.skind.SK_USE) { return nil; };
|
||||
};
|
||||
// Fold case 2 inner: bare `EnumT.MEMBER` where EnumT
|
||||
// is an SK_TYPE in the flat scope. Mirror cstage
|
||||
@@ -4683,6 +4953,11 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
||||
// style anonymous struct lit we don't yet parse — bail.
|
||||
if (e.lhs == nil) { return nil; };
|
||||
if (e.lhs.kind == syntax.nkind.N_IDENT) {
|
||||
if (lookupbareimportbinding(c, e.lhs.str) != nil) {
|
||||
bareimporttypeerr(c, e.lhs);
|
||||
e.type_ = c.tc.tyerr: *void;
|
||||
return nil;
|
||||
};
|
||||
let ms: *syntax.sym = lookupvisible(c, e.lhs.str);
|
||||
if (ms != nil) { if (ms.skind == syntax.skind.SK_TYPE) { if (ms.decl != nil) {
|
||||
let tn: *syntax.node = ms.decl.lhs;
|
||||
@@ -7578,8 +7853,10 @@ fn markimportusesnode(c: *checker, n: *syntax.node, owner: str,
|
||||
source: i32) void = {
|
||||
if (n == nil) { return; };
|
||||
if (n.kind == syntax.nkind.N_TNAME) {
|
||||
let (head, leaf) = strings.rcut(n.str, ".");
|
||||
if (head.len > 0) {
|
||||
// strings.rcut returns the whole input as its head on a miss. Require
|
||||
// a real qualifier separator so a bare package TNAME never counts as use.
|
||||
if (strings.contains(n.str, ".")) {
|
||||
let (head, leaf) = strings.rcut(n.str, ".");
|
||||
findusepath(c.file, owner, source, head, true);
|
||||
};
|
||||
} else { if (n.kind == syntax.nkind.N_DOT && n.lhs != nil
|
||||
|
||||
@@ -13267,7 +13267,7 @@ fn runtimepath(relative: str) str = {
|
||||
"cmd.duplicate", "cmd.collision", "cmd.crosscollision",
|
||||
"cmd.programuser"];
|
||||
let rejectneedles: []str = ["undefined: codec",
|
||||
"package 'wire' is not directly imported",
|
||||
"undefined: wire",
|
||||
"same redeclared in this block",
|
||||
"same already declared through import of package same",
|
||||
"same already declared through import of package same",
|
||||
@@ -13297,6 +13297,7 @@ fn runtimepath(relative: str) str = {
|
||||
"$WORK/cmd.collision.unit.new:3:1: error: \"scope.one\" imported as same and not used\n",
|
||||
"$WORK/cmd.collision.unit.new:4:1: error: same already declared through import of package same (\"scope.one\")\n",
|
||||
"\t$WORK/cmd.collision.unit.new:3:1: other declaration of same\n",
|
||||
"$WORK/cmd.collision.unit.new:5:26: error: use of package same not in selector\n",
|
||||
"ww: w6c failed for cmd.collision\n")));
|
||||
};
|
||||
if (si == 0) { diagnosticrefs[rj] = strings.dup(normalized); }
|
||||
@@ -13681,7 +13682,7 @@ fn runtimepath(relative: str) str = {
|
||||
"", "", "", "", "", ""];
|
||||
let needles: []str = ["undefined: value", "unknown type 'Thing'",
|
||||
"undefined: DEFINED", "undefined: CONSTANT", "undefined: VARIABLE",
|
||||
"unknown type 'wire.Thing'", "package 'stable' is not directly imported",
|
||||
"unknown type 'wire.Thing'", "undefined: stable",
|
||||
"wire redeclared in this block", "stable redeclared in this block",
|
||||
"wire redeclared in this block",
|
||||
"stable already declared through import of package stable",
|
||||
@@ -13728,6 +13729,7 @@ fn runtimepath(relative: str) str = {
|
||||
"$WORK/cmd.declcollision.unit.new:3:1: error: \"pkg.one\" imported as stable and not used\n",
|
||||
"$WORK/cmd.declcollision.unit.new:4:1: error: stable already declared through import of package stable (\"pkg.one\")\n",
|
||||
"\t$WORK/cmd.declcollision.unit.new:3:1: other declaration of stable\n",
|
||||
"$WORK/cmd.declcollision.unit.new:5:26: error: use of package stable not in selector\n",
|
||||
"ww: w6c failed for cmd.declcollision\n"),
|
||||
strings.concat(
|
||||
"$WORK/cmd.unusedalias.unit.new:3:1: error: \"pkg.one\" imported as stable and not used\n",
|
||||
@@ -19041,3 +19043,703 @@ fn runtimepath(relative: str) str = {
|
||||
&& !directoryhasfragment(root, ".install"));
|
||||
clean(root);
|
||||
};
|
||||
|
||||
// An effective import name is a file-local package qualifier. It may qualify
|
||||
// selectors, but it is neither a value nor a type by itself. Keep that rule at
|
||||
// semantic checking, before a malformed binding can leak into code generation
|
||||
// or linking, and preserve the ordinary undefined boundary in sibling files and
|
||||
// after blank imports.
|
||||
@test fn bare_import_bindings_require_selectors() void = {
|
||||
let root: str = fresh();
|
||||
let source: str = strings.concat(root, "/source");
|
||||
let dependency: str = strings.concat(source, "/dep/wire");
|
||||
let sizedependency: str = strings.concat(source, "/dep/size");
|
||||
let cases: []str = ["bareonly", "aliasonly", "selectorbare",
|
||||
"bareselector", "multiple", "typeonly", "aliastype", "typeselector",
|
||||
"builtinalias", "builtincallee", "arraydim", "enumvalue",
|
||||
"nestedconst", "nestedarg", "structhead", "structfieldtype",
|
||||
"casttype", "builtincall", "sibling", "siblingtype", "transitive",
|
||||
"blank", "initcase", "missing", "packagecalleeprim",
|
||||
"packagecalleetype", "packagecalleealign", "arraypackagecallee",
|
||||
"enumpackagecalleealign"];
|
||||
let ci: i32 = 0;
|
||||
mkdirall(dependency);
|
||||
mkdirall(sizedependency);
|
||||
for (ci < cases.len) {
|
||||
mkdirall(strings.concat(source, "/", cases[ci]));
|
||||
ci += 1;
|
||||
};
|
||||
writefile(strings.concat(dependency, "/wire.ww"), strings.concat(
|
||||
"package wire;\n",
|
||||
"export type record = struct { n: i32 };\n",
|
||||
"export fn value() i32 = { return 41; };\n"));
|
||||
writefile(strings.concat(sizedependency, "/size.ww"),
|
||||
"package size;\nexport fn value() i32 = { return 1; };\n");
|
||||
writefile(strings.concat(source, "/bareonly/main.ww"), strings.concat(
|
||||
"package main;\nimport dep.wire;\n",
|
||||
"fn main() i32 = { let bad: i32 = wire; return bad; };\n"));
|
||||
writefile(strings.concat(source, "/aliasonly/main.ww"), strings.concat(
|
||||
"package main;\nimport stable dep.wire;\n",
|
||||
"fn main() i32 = { let bad: i32 = stable; return bad; };\n"));
|
||||
writefile(strings.concat(source, "/selectorbare/main.ww"), strings.concat(
|
||||
"package main;\nimport dep.wire;\n",
|
||||
"fn main() i32 = { let ok: i32 = wire.value(); ",
|
||||
"let bad: i32 = wire; return ok + bad; };\n"));
|
||||
writefile(strings.concat(source, "/bareselector/main.ww"), strings.concat(
|
||||
"package main;\nimport dep.wire;\n",
|
||||
"fn main() i32 = { let bad: i32 = wire; ",
|
||||
"return bad + wire.value(); };\n"));
|
||||
writefile(strings.concat(source, "/multiple/main.ww"), strings.concat(
|
||||
"package main;\nimport dep.wire;\n",
|
||||
"fn main() i32 = { let ok: i32 = wire.value(); ",
|
||||
"let a: i32 = wire; let b: i32 = wire; return ok + a + b; };\n"));
|
||||
writefile(strings.concat(source, "/typeonly/main.ww"), strings.concat(
|
||||
"package main;\nimport dep.wire;\n",
|
||||
"fn bad(v: wire) i32 = { return 0; };\n",
|
||||
"fn main() i32 = { return 0; };\n"));
|
||||
writefile(strings.concat(source, "/aliastype/main.ww"), strings.concat(
|
||||
"package main;\nimport stable dep.wire;\n",
|
||||
"fn bad(v: stable) i32 = { return 0; };\n",
|
||||
"fn main() i32 = { return 0; };\n"));
|
||||
writefile(strings.concat(source, "/typeselector/main.ww"), strings.concat(
|
||||
"package main;\nimport dep.wire;\n",
|
||||
"fn bad(v: wire) i32 = { return 0; };\n",
|
||||
"fn main() i32 = { return wire.value(); };\n"));
|
||||
writefile(strings.concat(source, "/builtinalias/main.ww"), strings.concat(
|
||||
"package main;\nimport len dep.wire;\n",
|
||||
"fn main() i32 = { let ok: i32 = len.value(); ",
|
||||
"let bad: i32 = len; return ok + bad; };\n"));
|
||||
writefile(strings.concat(source, "/builtincallee/main.ww"), strings.concat(
|
||||
"package main;\nimport len dep.wire;\n",
|
||||
"fn main() i32 = { return len(\"abc\"); };\n"));
|
||||
writefile(strings.concat(source, "/arraydim/main.ww"), strings.concat(
|
||||
"package main;\nimport dep.wire;\n",
|
||||
"let bad: [wire]i32;\nfn main() i32 = { return 0; };\n"));
|
||||
writefile(strings.concat(source, "/enumvalue/main.ww"), strings.concat(
|
||||
"package main;\nimport dep.wire;\n",
|
||||
"type bad = enum { ZERO = wire };\n",
|
||||
"fn main() i32 = { return 0; };\n"));
|
||||
writefile(strings.concat(source, "/nestedconst/main.ww"), strings.concat(
|
||||
"package main;\nimport dep.wire;\n",
|
||||
"def BAD: i32 = 1 + (wire + wire);\n",
|
||||
"fn main() i32 = { return 0; };\n"));
|
||||
writefile(strings.concat(source, "/nestedarg/main.ww"), strings.concat(
|
||||
"package main;\nimport dep.wire;\n",
|
||||
"fn take(v: i32) i32 = { return v; };\n",
|
||||
"fn main() i32 = { return take(wire.value() + (1 + wire)); };\n"));
|
||||
writefile(strings.concat(source, "/structhead/main.ww"), strings.concat(
|
||||
"package main;\nimport dep.wire;\n",
|
||||
"fn main() i32 = { let bad = wire { n = 1 }; return bad.n; };\n"));
|
||||
writefile(strings.concat(source, "/structfieldtype/main.ww"), strings.concat(
|
||||
"package main;\nimport dep.wire;\n",
|
||||
"type holder = struct { bad: wire };\n",
|
||||
"fn main() i32 = { return 0; };\n"));
|
||||
writefile(strings.concat(source, "/casttype/main.ww"), strings.concat(
|
||||
"package main;\nimport dep.wire;\n",
|
||||
"fn main() i32 = { let bad = 0: wire; return 0; };\n"));
|
||||
writefile(strings.concat(source, "/builtincall/main.ww"), strings.concat(
|
||||
"package main;\nimport dep.wire;\n",
|
||||
"fn main() size = { return size(wire); };\n"));
|
||||
writefile(strings.concat(source, "/sibling/a.ww"), strings.concat(
|
||||
"package main;\nimport dep.wire;\n",
|
||||
"fn anchor() i32 = { return wire.value(); };\n"));
|
||||
writefile(strings.concat(source, "/sibling/b.ww"), strings.concat(
|
||||
"package main;\n",
|
||||
"fn bad() i32 = { let v: i32 = wire; return v; };\n",
|
||||
"fn main() i32 = { return anchor() + bad(); };\n"));
|
||||
writefile(strings.concat(source, "/siblingtype/a.ww"), strings.concat(
|
||||
"package main;\nimport dep.wire;\n",
|
||||
"fn anchor() i32 = { return wire.value(); };\n"));
|
||||
writefile(strings.concat(source, "/siblingtype/b.ww"), strings.concat(
|
||||
"package main;\nfn bad(v: wire) i32 = { return 0; };\n",
|
||||
"fn main() i32 = { return anchor(); };\n"));
|
||||
let middle: str = strings.concat(source, "/dep/middle");
|
||||
mkdirall(middle);
|
||||
writefile(strings.concat(middle, "/middle.ww"), strings.concat(
|
||||
"package middle;\nimport dep.wire;\n",
|
||||
"export fn value() i32 = { return wire.value(); };\n"));
|
||||
writefile(strings.concat(source, "/transitive/main.ww"), strings.concat(
|
||||
"package main;\nimport dep.middle;\n",
|
||||
"fn main() i32 = { let ok: i32 = middle.value(); ",
|
||||
"let bad: i32 = wire; return ok + bad; };\n"));
|
||||
writefile(strings.concat(source, "/blank/main.ww"), strings.concat(
|
||||
"package main;\nimport _ dep.wire;\n",
|
||||
"fn main() i32 = { let v: i32 = wire; return v; };\n"));
|
||||
writefile(strings.concat(source, "/initcase/main.ww"), strings.concat(
|
||||
"package main;\nimport init dep.wire;\n",
|
||||
"fn main() i32 = { return 0; };\n"));
|
||||
writefile(strings.concat(source, "/missing/main.ww"), strings.concat(
|
||||
"package main;\nimport stable dep.absent;\n",
|
||||
"fn main() i32 = { let v: i32 = stable; return v; };\n"));
|
||||
writefile(strings.concat(source, "/packagecalleeprim/main.ww"), strings.concat(
|
||||
"package main;\nimport dep.size;\n",
|
||||
"fn main() i32 = { return size(i32): i32; };\n"));
|
||||
writefile(strings.concat(source, "/packagecalleetype/main.ww"), strings.concat(
|
||||
"package main;\nimport dep.size;\nimport dep.wire;\n",
|
||||
"fn main() i32 = { return size(wire): i32; };\n"));
|
||||
writefile(strings.concat(source, "/packagecalleealign/main.ww"), strings.concat(
|
||||
"package main;\nimport align dep.size;\nimport dep.wire;\n",
|
||||
"fn main() i32 = { return align(wire): i32; };\n"));
|
||||
writefile(strings.concat(source, "/arraypackagecallee/main.ww"), strings.concat(
|
||||
"package main;\nimport dep.size;\nimport dep.wire;\n",
|
||||
"let bad: [size(wire)]u8;\nfn main() i32 = { return 0; };\n"));
|
||||
writefile(strings.concat(source, "/enumpackagecalleealign/main.ww"),
|
||||
strings.concat("package main;\nimport align dep.size;\nimport dep.wire;\n",
|
||||
"type bad = enum { ZERO = align(wire) };\n",
|
||||
"fn main() i32 = { return 0; };\n"));
|
||||
|
||||
let valuecore: str = "use of package wire not in selector";
|
||||
let aliascore: str = "use of package stable not in selector";
|
||||
let builtinaliascore: str = "use of package len not in selector";
|
||||
let sizecore: str = "use of package size not in selector";
|
||||
let aligncore: str = "use of package align not in selector";
|
||||
let typecore: str = "wire (package name) is not a type";
|
||||
let aliastypecore: str = "stable (package name) is not a type";
|
||||
let kinds: []i32 = [1, 6, 1, 1, 7, 2, 9, 2, 8, 8, 1, 1, 7, 1,
|
||||
2, 2, 2, 2, 3, 10, 3, 3, 4, 5, 11, 12, 13, 12, 13];
|
||||
let unusedcounts: []i32 = [1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1,
|
||||
0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 2, 2, 2, 2];
|
||||
let stages: []str = ["ww", "ww_ww"];
|
||||
let tags: []str = ["c", "ww"];
|
||||
let diagrefs: []str = alloc([], cases.len: u64)!;
|
||||
ci = 0;
|
||||
for (ci < cases.len) { append(diagrefs, ""); ci += 1; };
|
||||
let si: i32 = 0;
|
||||
for (si < stages.len) {
|
||||
ci = 0;
|
||||
for (ci < cases.len) {
|
||||
let work: str = strings.concat(root, "/matrix-work-", tags[si],
|
||||
"-", boundarypkgname(ci));
|
||||
let output: str = strings.concat(root, "/matrix-output-", tags[si],
|
||||
"-", boundarypkgname(ci));
|
||||
mkdirall(work);
|
||||
let av: []str = [driver(stages[si]), "build", "-w", work,
|
||||
"-I", source, "-o", output, cases[ci]];
|
||||
let out: commandout;
|
||||
runcommand(root, strings.concat("bare-import-matrix-", tags[si],
|
||||
"-", boundarypkgname(ci)), av,
|
||||
(120i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
assert(out.stdout.len == 0 && !os.exists(output)
|
||||
&& !os.exists(strings.concat(output, ".new"))
|
||||
&& !os.exists(strings.concat(output, ".sepwork"))
|
||||
&& directoryisempty(work));
|
||||
assert(!has(out.stderr, "undefined reference")
|
||||
&& !has(out.stderr, "cgident: unresolvable identifier"));
|
||||
if (kinds[ci] == 1) {
|
||||
assert(occurrences(out.stderr, valuecore) == 1);
|
||||
} else if (kinds[ci] == 2) {
|
||||
assert(occurrences(out.stderr, typecore) == 1);
|
||||
} else if (kinds[ci] == 3) {
|
||||
assert(occurrences(out.stderr, "undefined: wire") == 1
|
||||
&& !has(out.stderr, "package name) is not a type")
|
||||
&& !has(out.stderr, "not in selector"));
|
||||
} else if (kinds[ci] == 4) {
|
||||
assert(occurrences(out.stderr,
|
||||
"cannot import package as init - init must be a func") == 1
|
||||
&& !has(out.stderr, "not in selector")
|
||||
&& !has(out.stderr, "package name) is not a type"));
|
||||
} else if (kinds[ci] == 5) {
|
||||
assert(occurrences(out.stderr,
|
||||
"cannot find package dep.absent") == 1
|
||||
&& !has(out.stderr, "not in selector")
|
||||
&& !has(out.stderr, "package name) is not a type"));
|
||||
} else if (kinds[ci] == 6) {
|
||||
assert(occurrences(out.stderr, aliascore) == 1);
|
||||
} else if (kinds[ci] == 7) {
|
||||
assert(occurrences(out.stderr, valuecore) == 2);
|
||||
} else if (kinds[ci] == 8) {
|
||||
assert(occurrences(out.stderr, builtinaliascore) == 1);
|
||||
} else if (kinds[ci] == 10) {
|
||||
assert(occurrences(out.stderr, "unknown type 'wire'") == 1
|
||||
&& !has(out.stderr, "package name) is not a type")
|
||||
&& !has(out.stderr, "not in selector"));
|
||||
} else if (kinds[ci] == 11) {
|
||||
assert(occurrences(out.stderr, sizecore) == 1
|
||||
&& !has(out.stderr, "internal: unhandled expr kind")
|
||||
&& !has(out.stderr, "package name) is not a type"));
|
||||
} else if (kinds[ci] == 12) {
|
||||
assert(occurrences(out.stderr, sizecore) == 1
|
||||
&& occurrences(out.stderr, typecore) == 1
|
||||
&& !has(out.stderr, "internal: unhandled expr kind"));
|
||||
} else if (kinds[ci] == 13) {
|
||||
assert(occurrences(out.stderr, aligncore) == 1
|
||||
&& occurrences(out.stderr, typecore) == 1
|
||||
&& !has(out.stderr, "internal: unhandled expr kind"));
|
||||
} else {
|
||||
assert(occurrences(out.stderr, aliastypecore) == 1);
|
||||
};
|
||||
assert(occurrences(out.stderr, "not used") == unusedcounts[ci]);
|
||||
assert(!has(out.stderr, "array length must be an integer literal")
|
||||
&& !has(out.stderr,
|
||||
"enum value must be a constant integer expression")
|
||||
&& !has(out.stderr, "arithmetic on non-numeric type")
|
||||
&& !has(out.stderr, "unknown struct type")
|
||||
&& !has(out.stderr, "called object is not a function")
|
||||
&& !has(out.stderr, "asserttyped:"));
|
||||
let normalized: str = normalizedtrace(out.stderr,
|
||||
strings.concat(work, "/"), output);
|
||||
if (ci == 0) {
|
||||
assert(same(normalized, strings.concat(
|
||||
"$WORK/bareonly.unit.new:3:1: error: ",
|
||||
"\"dep.wire\" imported and not used\n",
|
||||
"$WORK/bareonly.unit.new:4:34: error: ", valuecore, "\n",
|
||||
"ww: w6c failed for bareonly\n")));
|
||||
} else if (ci == 4) {
|
||||
assert(same(normalized, strings.concat(
|
||||
"$WORK/multiple.unit.new:4:60: error: ", valuecore, "\n",
|
||||
"$WORK/multiple.unit.new:4:79: error: ", valuecore, "\n",
|
||||
"ww: w6c failed for multiple\n")));
|
||||
} else if (ci == 5) {
|
||||
assert(same(normalized, strings.concat(
|
||||
"$WORK/typeonly.unit.new:3:1: error: ",
|
||||
"\"dep.wire\" imported and not used\n",
|
||||
"$WORK/typeonly.unit.new:4:11: error: ", typecore, "\n",
|
||||
"ww: w6c failed for typeonly\n")));
|
||||
} else if (ci == 9) {
|
||||
assert(same(normalized, strings.concat(
|
||||
"$WORK/builtincallee.unit.new:3:1: error: ",
|
||||
"\"dep.wire\" imported as len and not used\n",
|
||||
"$WORK/builtincallee.unit.new:4:26: error: ",
|
||||
builtinaliascore, "\n",
|
||||
"ww: w6c failed for builtincallee\n")));
|
||||
} else if (ci == 10) {
|
||||
assert(same(normalized, strings.concat(
|
||||
"$WORK/arraydim.unit.new:3:1: error: ",
|
||||
"\"dep.wire\" imported and not used\n",
|
||||
"$WORK/arraydim.unit.new:4:11: error: ", valuecore, "\n",
|
||||
"ww: w6c failed for arraydim\n")));
|
||||
} else if (ci == 12) {
|
||||
assert(same(normalized, strings.concat(
|
||||
"$WORK/nestedconst.unit.new:3:1: error: ",
|
||||
"\"dep.wire\" imported and not used\n",
|
||||
"$WORK/nestedconst.unit.new:4:21: error: ", valuecore, "\n",
|
||||
"$WORK/nestedconst.unit.new:4:28: error: ", valuecore, "\n",
|
||||
"ww: w6c failed for nestedconst\n")));
|
||||
} else if (ci == 13) {
|
||||
assert(same(normalized, strings.concat(
|
||||
"$WORK/nestedarg.unit.new:5:51: error: ", valuecore, "\n",
|
||||
"ww: w6c failed for nestedarg\n")));
|
||||
} else if (ci == 18) {
|
||||
assert(same(normalized, strings.concat(
|
||||
"$WORK/sibling.unit.new:8:31: error: undefined: wire\n",
|
||||
"ww: w6c failed for sibling\n")));
|
||||
} else if (ci == 24) {
|
||||
assert(same(normalized, strings.concat(
|
||||
"$WORK/packagecalleeprim.unit.new:3:1: error: ",
|
||||
"\"dep.size\" imported and not used\n",
|
||||
"$WORK/packagecalleeprim.unit.new:4:26: error: ", sizecore,
|
||||
"\nww: w6c failed for packagecalleeprim\n")));
|
||||
} else if (ci == 25) {
|
||||
assert(same(normalized, strings.concat(
|
||||
"$WORK/packagecalleetype.unit.new:3:1: error: ",
|
||||
"\"dep.size\" imported and not used\n",
|
||||
"$WORK/packagecalleetype.unit.new:4:1: error: ",
|
||||
"\"dep.wire\" imported and not used\n",
|
||||
"$WORK/packagecalleetype.unit.new:5:26: error: ", sizecore,
|
||||
"\n$WORK/packagecalleetype.unit.new:5:31: error: ", typecore,
|
||||
"\nww: w6c failed for packagecalleetype\n")));
|
||||
} else if (ci == 26) {
|
||||
assert(same(normalized, strings.concat(
|
||||
"$WORK/packagecalleealign.unit.new:3:1: error: ",
|
||||
"\"dep.size\" imported as align and not used\n",
|
||||
"$WORK/packagecalleealign.unit.new:4:1: error: ",
|
||||
"\"dep.wire\" imported and not used\n",
|
||||
"$WORK/packagecalleealign.unit.new:5:26: error: ", aligncore,
|
||||
"\n$WORK/packagecalleealign.unit.new:5:32: error: ", typecore,
|
||||
"\nww: w6c failed for packagecalleealign\n")));
|
||||
} else if (ci == 27) {
|
||||
assert(same(normalized, strings.concat(
|
||||
"$WORK/arraypackagecallee.unit.new:3:1: error: ",
|
||||
"\"dep.size\" imported and not used\n",
|
||||
"$WORK/arraypackagecallee.unit.new:4:1: error: ",
|
||||
"\"dep.wire\" imported and not used\n",
|
||||
"$WORK/arraypackagecallee.unit.new:5:11: error: ", sizecore,
|
||||
"\n$WORK/arraypackagecallee.unit.new:5:16: error: ", typecore,
|
||||
"\nww: w6c failed for arraypackagecallee\n")));
|
||||
} else if (ci == 28) {
|
||||
assert(same(normalized, strings.concat(
|
||||
"$WORK/enumpackagecalleealign.unit.new:3:1: error: ",
|
||||
"\"dep.size\" imported as align and not used\n",
|
||||
"$WORK/enumpackagecalleealign.unit.new:4:1: error: ",
|
||||
"\"dep.wire\" imported and not used\n",
|
||||
"$WORK/enumpackagecalleealign.unit.new:5:26: error: ", aligncore,
|
||||
"\n$WORK/enumpackagecalleealign.unit.new:5:32: error: ", typecore,
|
||||
"\nww: w6c failed for enumpackagecalleealign\n")));
|
||||
};
|
||||
if (si == 0) { diagrefs[ci] = strings.dup(normalized); }
|
||||
else { assert(same(diagrefs[ci], normalized)); };
|
||||
ci += 1;
|
||||
};
|
||||
si += 1;
|
||||
};
|
||||
|
||||
// A raw source operand uses the same file-local import object and checker.
|
||||
let raw: str = strings.concat(root, "/raw.ww");
|
||||
writefile(raw, strings.concat(
|
||||
"package main;\nimport dep.wire;\n",
|
||||
"fn main() i32 = { let ok: i32 = wire.value(); ",
|
||||
"let bad: i32 = wire; return ok + bad; };\n"));
|
||||
let rawdiag: str = "";
|
||||
si = 0;
|
||||
for (si < stages.len) {
|
||||
let work: str = strings.concat(root, "/raw-work-", tags[si]);
|
||||
let output: str = strings.concat(root, "/raw-output-", tags[si]);
|
||||
mkdirall(work);
|
||||
let av: []str = [driver(stages[si]), "build", "-w", work,
|
||||
"-I", source, "-o", output, raw];
|
||||
let out: commandout;
|
||||
runcommand(root, strings.concat("bare-import-raw-", tags[si]), av,
|
||||
(120i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
assert(out.stdout.len == 0 && occurrences(out.stderr, valuecore) == 1
|
||||
&& !has(out.stderr, "imported as wire and not used")
|
||||
&& !os.exists(output) && directoryisempty(work));
|
||||
let normalized: str = normalizedtrace(out.stderr,
|
||||
strings.concat(work, "/"), output);
|
||||
if (si == 0) { rawdiag = strings.dup(normalized); }
|
||||
else { assert(same(rawdiag, normalized)); };
|
||||
si += 1;
|
||||
};
|
||||
|
||||
// Valid selector use still owns import usage, artifact construction, and
|
||||
// execution. Every comparable semantic action and public binary is exact.
|
||||
let control: str = strings.concat(source, "/control");
|
||||
mkdirall(control);
|
||||
writefile(strings.concat(control, "/main.ww"), strings.concat(
|
||||
"package main;\nimport dep.wire;\n",
|
||||
"fn main() i32 = { let r: wire.record = wire.record { n = 0 }; ",
|
||||
"return wire.value() + r.n; };\n"));
|
||||
let suffixes: []str = [".unit.ww", ".wwi", ".s", ".o", ".a",
|
||||
".init.unit.ww", ".init.s", ".init.o"];
|
||||
let controlrefs: []str = alloc([], suffixes.len: u64)!;
|
||||
let controlbin: str = "";
|
||||
si = 0;
|
||||
for (si < stages.len) {
|
||||
let work: str = strings.concat(root, "/control-work-", tags[si]);
|
||||
let output: str = strings.concat(root, "/control-output-", tags[si]);
|
||||
mkdirall(work);
|
||||
let av: []str = [driver(stages[si]), "build", "-w", work,
|
||||
"-I", source, "-o", output, "control"];
|
||||
let out: commandout;
|
||||
runcommand(root, strings.concat("bare-import-control-", tags[si]), av,
|
||||
(120i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0);
|
||||
assert(out.stdout.len == 0 && out.stderr.len == 0 && os.exists(output)
|
||||
&& !directoryhasnew(work)
|
||||
&& !directoryhasfragment(work, ".wwtxn."));
|
||||
let runav: []str = [output];
|
||||
runcommand(root, strings.concat("bare-import-control-run-", tags[si]),
|
||||
runav, time.second, &out);
|
||||
expectexit(&out, 41);
|
||||
assert(out.stdout.len == 0 && out.stderr.len == 0);
|
||||
let fi: i32 = 0;
|
||||
for (fi < suffixes.len) {
|
||||
let bytes: str = readfile(strings.concat(work, "/control",
|
||||
suffixes[fi]));
|
||||
if (si == 0) { append(controlrefs, strings.dup(bytes)); }
|
||||
else { assert(same(controlrefs[fi], bytes)); };
|
||||
fi += 1;
|
||||
};
|
||||
if (si == 0) { controlbin = strings.dup(readfile(output)); }
|
||||
else { assert(same(controlbin, readfile(output))); };
|
||||
si += 1;
|
||||
};
|
||||
|
||||
// Production, same-package, external, honest test-only, a production
|
||||
// function reached by a test, and an imported dependency all reject before
|
||||
// test-main publication or execution. A filter cannot hide source errors.
|
||||
let testtargets: []str = ["prodcase", "samecase", "externalcase",
|
||||
"onlycase", "calledcase", "importedcase"];
|
||||
ci = 0;
|
||||
for (ci < testtargets.len) {
|
||||
mkdirall(strings.concat(source, "/", testtargets[ci]));
|
||||
ci += 1;
|
||||
};
|
||||
writefile(strings.concat(source, "/prodcase/prod.ww"), strings.concat(
|
||||
"package prodcase;\nimport dep.wire;\n",
|
||||
"fn bad() i32 = { let ok: i32 = wire.value(); ",
|
||||
"let v: i32 = wire; return ok + v; };\n"));
|
||||
writefile(strings.concat(source, "/prodcase/prod_test.ww"),
|
||||
"package prodcase;\n@test fn must_not_run() void = { abort(); };\n");
|
||||
writefile(strings.concat(source, "/samecase/prod.ww"),
|
||||
"package samecase;\nfn value() i32 = { return 1; };\n");
|
||||
writefile(strings.concat(source, "/samecase/same_test.ww"), strings.concat(
|
||||
"package samecase;\nimport dep.wire;\n",
|
||||
"@test fn must_not_run() void = { let ok: i32 = wire.value(); ",
|
||||
"let bad: i32 = wire; abort(); };\n"));
|
||||
writefile(strings.concat(source, "/externalcase/prod.ww"), strings.concat(
|
||||
"package externalcase;\n",
|
||||
"export fn value() i32 = { return 1; };\n"));
|
||||
writefile(strings.concat(source, "/externalcase/external_test.ww"),
|
||||
strings.concat("package externalcase_test;\nimport dep.wire;\n",
|
||||
"@test fn must_not_run() void = { let ok: i32 = wire.value(); ",
|
||||
"let bad: i32 = wire; abort(); };\n"));
|
||||
writefile(strings.concat(source, "/onlycase/only_test.ww"), strings.concat(
|
||||
"package onlycase;\nimport dep.wire;\n",
|
||||
"@test fn must_not_run() void = { let ok: i32 = wire.value(); ",
|
||||
"let bad: i32 = wire; abort(); };\n"));
|
||||
writefile(strings.concat(source, "/calledcase/prod.ww"), strings.concat(
|
||||
"package calledcase;\nimport dep.wire;\n",
|
||||
"fn bad() i32 = { let ok: i32 = wire.value(); ",
|
||||
"let v: i32 = wire; return ok + v; };\n"));
|
||||
writefile(strings.concat(source, "/calledcase/called_test.ww"),
|
||||
"package calledcase;\n@test fn must_not_run() void = { assert(bad() > 0); };\n");
|
||||
let baddep: str = strings.concat(source, "/dep/baddep");
|
||||
let buildimport: str = strings.concat(source, "/buildimport");
|
||||
mkdirall(baddep);
|
||||
mkdirall(buildimport);
|
||||
writefile(strings.concat(baddep, "/baddep.ww"), strings.concat(
|
||||
"package baddep;\nimport dep.wire;\n",
|
||||
"export fn bad() i32 = { let ok: i32 = wire.value(); ",
|
||||
"let v: i32 = wire; return ok + v; };\n"));
|
||||
writefile(strings.concat(source, "/importedcase/prod.ww"), strings.concat(
|
||||
"package importedcase;\nimport dep.baddep;\n",
|
||||
"fn value() i32 = { return baddep.bad(); };\n"));
|
||||
writefile(strings.concat(source, "/importedcase/imported_test.ww"),
|
||||
"package importedcase;\n@test fn must_not_run() void = { assert(value() > 0); };\n");
|
||||
writefile(strings.concat(buildimport, "/main.ww"), strings.concat(
|
||||
"package main;\nimport dep.baddep;\n",
|
||||
"fn main() i32 = { return baddep.bad(); };\n"));
|
||||
let testdiagrefs: []str = alloc([], testtargets.len: u64)!;
|
||||
ci = 0;
|
||||
for (ci < testtargets.len) { append(testdiagrefs, ""); ci += 1; };
|
||||
si = 0;
|
||||
for (si < stages.len) {
|
||||
ci = 0;
|
||||
for (ci < testtargets.len) {
|
||||
let work: str = strings.concat(root, "/test-work-", tags[si], "-",
|
||||
boundarypkgname(ci));
|
||||
let output: str = strings.concat(root, "/test-output-", tags[si], "-",
|
||||
boundarypkgname(ci));
|
||||
mkdirall(work);
|
||||
let av: []str = [driver(stages[si]), "test", "-w", work,
|
||||
"-I", source, "-run", "must_not_run", "-o", output,
|
||||
testtargets[ci]];
|
||||
let out: commandout;
|
||||
runcommand(root, strings.concat("bare-import-test-", tags[si], "-",
|
||||
boundarypkgname(ci)), av,
|
||||
(120i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
assert(same(out.stdout, "FAIL\n")
|
||||
&& occurrences(out.stderr, valuecore) == 1
|
||||
&& !has(out.stderr, "undefined reference")
|
||||
&& !has(out.stderr, "cgident: unresolvable identifier")
|
||||
&& !has(out.stdout, "must_not_run ...")
|
||||
&& !os.exists(output)
|
||||
&& !os.exists(strings.concat(output, ".new"))
|
||||
&& !os.exists(strings.concat(output, ".sepwork"))
|
||||
&& directoryisempty(work));
|
||||
let normalized: str = normalizedtrace(out.stderr,
|
||||
strings.concat(work, "/"), output);
|
||||
if (si == 0) { testdiagrefs[ci] = strings.dup(normalized); }
|
||||
else { assert(same(testdiagrefs[ci], normalized)); };
|
||||
ci += 1;
|
||||
};
|
||||
si += 1;
|
||||
};
|
||||
|
||||
// Ordinary build reaches the same invalid dependency compiler action, and
|
||||
// a genuinely empty test selection still compiles selected source before
|
||||
// considering whether any runtime test would match.
|
||||
let buildimportdiag: str = "";
|
||||
let nomatchdiag: str = "";
|
||||
si = 0;
|
||||
for (si < stages.len) {
|
||||
let buildwork: str = strings.concat(root, "/buildimport-work-", tags[si]);
|
||||
let buildout: str = strings.concat(root, "/buildimport-output-", tags[si]);
|
||||
mkdirall(buildwork);
|
||||
let buildav: []str = [driver(stages[si]), "build", "-w", buildwork,
|
||||
"-I", source, "-o", buildout, "buildimport"];
|
||||
let out: commandout;
|
||||
runcommand(root, strings.concat("bare-import-build-dependency-", tags[si]),
|
||||
buildav, (120i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
assert(out.stdout.len == 0 && occurrences(out.stderr, valuecore) == 1
|
||||
&& !has(out.stderr, "undefined reference")
|
||||
&& !has(out.stderr, "cgident: unresolvable identifier")
|
||||
&& !has(out.stderr, "asserttyped:")
|
||||
&& !os.exists(buildout)
|
||||
&& !os.exists(strings.concat(buildout, ".new"))
|
||||
&& !os.exists(strings.concat(buildout, ".sepwork"))
|
||||
&& directoryisempty(buildwork));
|
||||
let buildnormalized: str = normalizedtrace(out.stderr,
|
||||
strings.concat(buildwork, "/"), buildout);
|
||||
if (si == 0) { buildimportdiag = strings.dup(buildnormalized); }
|
||||
else { assert(same(buildimportdiag, buildnormalized)); };
|
||||
|
||||
let testwork: str = strings.concat(root, "/nomatch-work-", tags[si]);
|
||||
let testout: str = strings.concat(root, "/nomatch-output-", tags[si]);
|
||||
mkdirall(testwork);
|
||||
let testav: []str = [driver(stages[si]), "test", "-w", testwork,
|
||||
"-I", source, "-run", "does_not_match_any_test", "-o", testout,
|
||||
"samecase"];
|
||||
runcommand(root, strings.concat("bare-import-test-nomatch-", tags[si]),
|
||||
testav, (120i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
assert(same(out.stdout, "FAIL\n")
|
||||
&& occurrences(out.stderr, valuecore) == 1
|
||||
&& !has(out.stdout, "testing: warning: no tests to run")
|
||||
&& !has(out.stdout, " discovered, ")
|
||||
&& !has(out.stdout, "must_not_run ...")
|
||||
&& !has(out.stderr, "undefined reference")
|
||||
&& !has(out.stderr, "cgident: unresolvable identifier")
|
||||
&& !has(out.stderr, "asserttyped:")
|
||||
&& !os.exists(testout)
|
||||
&& !os.exists(strings.concat(testout, ".new"))
|
||||
&& !os.exists(strings.concat(testout, ".sepwork"))
|
||||
&& directoryisempty(testwork));
|
||||
let testnormalized: str = normalizedtrace(out.stderr,
|
||||
strings.concat(testwork, "/"), testout);
|
||||
if (si == 0) { nomatchdiag = strings.dup(testnormalized); }
|
||||
else { assert(same(nomatchdiag, testnormalized)); };
|
||||
si += 1;
|
||||
};
|
||||
|
||||
// A warm command keeps its committed graph/actions, package artifacts, and
|
||||
// public binary across rejection; restoring exact source reuses the same
|
||||
// semantic generation. The same publication rule protects a filtered,
|
||||
// retained test binary, which remains directly executable after rejection.
|
||||
let warm: str = strings.concat(source, "/warm");
|
||||
mkdirall(warm);
|
||||
let warmfile: str = strings.concat(warm, "/main.ww");
|
||||
let warmvalid: str = strings.concat(
|
||||
"package main;\nimport dep.wire;\n",
|
||||
"fn main() i32 = { return wire.value(); };\n");
|
||||
let warminvalid: str = strings.concat(
|
||||
"package main;\nimport dep.wire;\n",
|
||||
"fn main() i32 = { let ok: i32 = wire.value(); ",
|
||||
"let bad: i32 = wire; return ok + bad; };\n");
|
||||
writefile(warmfile, warmvalid);
|
||||
let retainedcase: str = strings.concat(source, "/retainedcase");
|
||||
mkdirall(retainedcase);
|
||||
let retainedfile: str = strings.concat(retainedcase, "/retained_test.ww");
|
||||
let retainedvalid: str = strings.concat(
|
||||
"package retainedcase;\nimport dep.wire;\n",
|
||||
"@test fn kept() void = { assert(wire.value() == 41); };\n");
|
||||
let retainedinvalid: str = strings.concat(
|
||||
"package retainedcase;\nimport dep.wire;\n",
|
||||
"@test fn kept() void = { let ok: i32 = wire.value(); ",
|
||||
"let bad: i32 = wire; assert(ok + bad > 0); };\n");
|
||||
writefile(retainedfile, retainedvalid);
|
||||
let warmpaths: []str = ["/.wwtool.ww", "/.wwtool.w6c", "/.wwtool.w6a",
|
||||
"/.wwtool.stamp", "/dep.wire.unit.ww", "/dep.wire.wwi",
|
||||
"/dep.wire.s", "/dep.wire.o", "/dep.wire.a", "/warm.unit.ww",
|
||||
"/warm.wwi", "/warm.s", "/warm.o", "/warm.a",
|
||||
"/warm.init.unit.ww", "/warm.init.s", "/warm.init.o"];
|
||||
let warmcross: []str = alloc([], warmpaths.len: u64)!;
|
||||
let warmbincross: str = "";
|
||||
let retainedcross: str = "";
|
||||
let directoutrefs: []str = ["", ""];
|
||||
let directerrrefs: []str = ["", ""];
|
||||
si = 0;
|
||||
for (si < stages.len) {
|
||||
let work: str = strings.concat(root, "/warm-work-", tags[si]);
|
||||
let output: str = strings.concat(root, "/warm-output-", tags[si]);
|
||||
mkdirall(work);
|
||||
let av: []str = [driver(stages[si]), "build", "-w", work,
|
||||
"-I", source, "-o", output, "warm"];
|
||||
let out: commandout;
|
||||
runcommand(root, strings.concat("bare-import-warm-cold-", tags[si]), av,
|
||||
(120i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0);
|
||||
assert(out.stdout.len == 0 && out.stderr.len == 0);
|
||||
let snapshots: []str = alloc([], warmpaths.len: u64)!;
|
||||
let pi: i32 = 0;
|
||||
for (pi < warmpaths.len) {
|
||||
let bytes: str = strings.dup(readfile(strings.concat(work,
|
||||
warmpaths[pi])));
|
||||
append(snapshots, bytes);
|
||||
if (si == 0) { append(warmcross, strings.dup(bytes)); }
|
||||
else if (pi >= 4) { assert(same(warmcross[pi], bytes)); };
|
||||
pi += 1;
|
||||
};
|
||||
let binbytes: str = strings.dup(readfile(output));
|
||||
if (si == 0) { warmbincross = strings.dup(binbytes); }
|
||||
else { assert(same(warmbincross, binbytes)); };
|
||||
rewritefile(warmfile, warminvalid);
|
||||
runcommand(root, strings.concat("bare-import-warm-reject-", tags[si]), av,
|
||||
(120i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
assert(out.stdout.len == 0 && occurrences(out.stderr, valuecore) == 1
|
||||
&& same(binbytes, readfile(output))
|
||||
&& !os.exists(strings.concat(output, ".new"))
|
||||
&& !os.exists(strings.concat(output, ".sepwork")));
|
||||
pi = 0;
|
||||
for (pi < warmpaths.len) {
|
||||
assert(same(snapshots[pi], readfile(strings.concat(work,
|
||||
warmpaths[pi]))));
|
||||
pi += 1;
|
||||
};
|
||||
assert(!directoryhasnew(work)
|
||||
&& !directoryhasfragment(work, ".wwtxn.")
|
||||
&& !directoryhasfragment(work, ".install"));
|
||||
rewritefile(warmfile, warmvalid);
|
||||
runcommand(root, strings.concat("bare-import-warm-restored-", tags[si]),
|
||||
av, (120i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0);
|
||||
assert(out.stdout.len == 0 && out.stderr.len == 0
|
||||
&& same(binbytes, readfile(output)));
|
||||
pi = 0;
|
||||
for (pi < warmpaths.len) {
|
||||
assert(same(snapshots[pi], readfile(strings.concat(work,
|
||||
warmpaths[pi]))));
|
||||
pi += 1;
|
||||
};
|
||||
|
||||
let testwork: str = strings.concat(root, "/retained-work-", tags[si]);
|
||||
let retained: str = strings.concat(root, "/retained-", tags[si], ".test");
|
||||
mkdirall(testwork);
|
||||
let compileav: []str = [driver(stages[si]), "test", "-w",
|
||||
testwork, "-I", source, "-run", "kept", "-o", retained,
|
||||
"retainedcase"];
|
||||
runcommand(root, strings.concat("bare-import-retained-cold-", tags[si]),
|
||||
compileav, (120i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0);
|
||||
assert(out.stderr.len == 0 && os.exists(retained)
|
||||
&& occurrences(out.stdout, "retainedcase.kept ... ok\n") == 1
|
||||
&& occurrences(out.stdout,
|
||||
"1 passed, 0 failed, 0 skipped, 0 harness errors\n") == 1);
|
||||
let retainedstdout: str = strings.dup(out.stdout);
|
||||
let retainedbytes: str = strings.dup(readfile(retained));
|
||||
if (si == 0) { retainedcross = strings.dup(retainedbytes); }
|
||||
else { assert(same(retainedcross, retainedbytes)); };
|
||||
rewritefile(retainedfile, retainedinvalid);
|
||||
runcommand(root, strings.concat("bare-import-retained-reject-", tags[si]),
|
||||
compileav, (120i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
assert(same(out.stdout, "FAIL\n")
|
||||
&& occurrences(out.stderr, valuecore) == 1
|
||||
&& same(retainedbytes, readfile(retained))
|
||||
&& !os.exists(strings.concat(retained, ".new"))
|
||||
&& !os.exists(strings.concat(retained, ".sepwork"))
|
||||
&& !directoryhasnew(testwork)
|
||||
&& !directoryhasfragment(testwork, ".wwtxn."));
|
||||
let directav: []str = [retained, "-package=retainedcase", "kept"];
|
||||
runcommand(root, strings.concat("bare-import-retained-direct-", tags[si]),
|
||||
directav, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0);
|
||||
assert(out.stderr.len == 0
|
||||
&& occurrences(out.stdout, "retainedcase.kept ... ok\n") == 1
|
||||
&& occurrences(out.stdout,
|
||||
"1 passed, 0 failed, 0 skipped, 0 harness errors\n") == 1);
|
||||
directoutrefs[si] = strings.dup(out.stdout);
|
||||
directerrrefs[si] = strings.dup(out.stderr);
|
||||
rewritefile(retainedfile, retainedvalid);
|
||||
runcommand(root, strings.concat("bare-import-retained-restored-", tags[si]),
|
||||
compileav, (120i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0);
|
||||
assert(same(out.stdout, retainedstdout) && out.stderr.len == 0
|
||||
&& same(retainedbytes, readfile(retained))
|
||||
&& !directoryhasnew(testwork)
|
||||
&& !directoryhasfragment(testwork, ".wwtxn.")
|
||||
&& !directoryhasfragment(testwork, ".install"));
|
||||
si += 1;
|
||||
};
|
||||
assert(same(directoutrefs[0], directoutrefs[1])
|
||||
&& same(directerrrefs[0], directerrrefs[1]));
|
||||
assert(!directoryhasnew(root)
|
||||
&& !directoryhasfragment(root, ".wwtxn.")
|
||||
&& !directoryhasfragment(root, ".install"));
|
||||
clean(root);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user