strconv: graduate to (T | invalid | overflow); add void expression

`type invalid = i32` (payload: byte index of first bad rune; mirrors
Hare's strconv::invalid = !size) and `type overflow = void` (Hare's
overflow = !void). stoi64/stou64 now return these instead of the
str-error placeholder. atoi64 dropped — lib/CLAUDE.md says graduate
in one go, don't keep both shapes around.

To produce the void variant payload, `void` is now a real
expression literal (TK_VOID kw, N_VOIDLIT). It evaluates to ty_void;
codegen emits MOVQ $0, AX. Both kinds are appended at the tail of
their enums to keep prior numeric values byte-stable for the
wwdump-diff fixtures.

check_file reorder: USE declarations are now installed in pass 1
alongside the type-decl placeholders so dotted type references
(`strconv.invalid` from a typedecl body) resolve. DEF/FN/LET silently
overwrite a USE-occupied slot — matches the old behavior where USE
silently no-op'd when a same-name fn/def existed (the conflict
manifested in selfhost main.combined.ww at `use parse;` colliding
with `export fn parse(a)`).

selfhost mirror: lib/ww/lex/tok.ww kwtab+name; lib/ww/ast.ww
N_VOIDLIT def+print; lib/ww/parse/{expr,parse}.ww TK_VOID handling;
selfhost/cmd/wcc/cgenexpr.ww N_VOIDLIT codegen.
This commit is contained in:
2026-05-12 02:02:08 +09:00
parent 1e2f55aed8
commit 4085742853
16 changed files with 250 additions and 198 deletions

View File

@@ -572,7 +572,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
}
case N_TRUE: cgexpr_int(c, 1); break;
case N_FALSE:
case N_NIL: cgexpr_int(c, 0); break;
case N_NIL:
case N_VOIDLIT: cgexpr_int(c, 0); break;
case N_IDENT: {
int off = localfind(locals, n->str);
if (off != 0) {

View File

@@ -83,6 +83,7 @@ nkname(Nkind k)
case N_MASSIGN: return "massign";
case N_TYPETEST: return "typetest";
case N_TYPEASSERT: return "typeassert";
case N_VOIDLIT: return "voidlit";
case N_LAST: return "last";
}
return "?";

View File

@@ -408,6 +408,7 @@ cexpr(Checker *c, Node *n)
case N_TRUE:
case N_FALSE: n->type = ty_untyped_bool; return n->type;
case N_NIL: n->type = ty_untyped_nil; return n->type;
case N_VOIDLIT: n->type = ty_void; return n->type;
case N_IDENT: {
if (n->str && n->str[0] == '\0')
return n->type = err(c, n->pos,
@@ -1201,8 +1202,14 @@ check_file(Checker *c, Node *file)
* For self-referential types we install the named-type placeholder
* BEFORE resolving its body; the body may legitimately mention
* the type itself (`type stream = struct { read: fn(*stream)... }`).
*/
* USE declarations are installed in this same step so dotted type
* references (`strconv.invalid`) resolve when typedecl bodies are
* walked in the next pass. */
for (Node *d = file->list; d; d = d->next) {
if (d->kind == N_USE) {
scope_define(c->cur, d->str, SK_USE, NULL, d);
continue;
}
if (d->kind != N_TYPEDECL) continue;
Type *named = type_named(c->a, d->str, NULL);
if (!scope_define(c->cur, d->str, SK_TYPE, named, d))
@@ -1221,27 +1228,40 @@ check_file(Checker *c, Node *file)
for (Node *d = file->list; d; d = d->next) {
switch (d->kind) {
case N_USE:
scope_define(c->cur, d->str, SK_USE, NULL, d);
/* already installed in pass 1; no-op here so the
* old fall-through doesn't re-define. */
break;
case N_DEF: {
Type *t = resolve_type(c, d->lhs);
d->type = t;
if (!scope_define(c->cur, d->str, SK_DEF, t, d))
Sym *prev = scope_lookup_local(c->cur, d->str);
if (prev && prev->kind == SK_USE) {
prev->kind = SK_DEF; prev->type = t; prev->decl = d;
} else if (!scope_define(c->cur, d->str, SK_DEF, t, d))
err(c, d->pos, "duplicate def %s", d->str);
break;
}
case N_FNDECL: {
Type *t = build_fn_type(c, d);
d->type = t;
if (!scope_define(c->cur, d->str, SK_FN, t, d))
Sym *prev = scope_lookup_local(c->cur, d->str);
if (prev && prev->kind == SK_USE) {
prev->kind = SK_FN; prev->type = t; prev->decl = d;
} else if (!scope_define(c->cur, d->str, SK_FN, t, d))
err(c, d->pos, "duplicate fn %s", d->str);
break;
}
case N_LET: {
Type *t = d->lhs ? resolve_type(c, d->lhs) : NULL;
d->type = t;
if (d->str && d->str[0])
scope_define(c->cur, d->str, SK_VAR, t, d);
if (d->str && d->str[0]) {
Sym *prev = scope_lookup_local(c->cur, d->str);
if (prev && prev->kind == SK_USE) {
prev->kind = SK_VAR; prev->type = t;
prev->decl = d;
} else
scope_define(c->cur, d->str, SK_VAR, t, d);
}
break;
}
default: break;

View File

@@ -222,6 +222,16 @@ parsetype(Parser *p)
n->lhs = parsetype(p);
return n;
}
case TK_VOID: {
/* `void` keyword in type-expr context. Synthesise an
* N_TNAME so type-resolution treats it like any other
* primitive name. */
Node *n = newnode(p->a, N_TNAME, pp);
n->str = "void";
n->strlen = 4;
advance(p);
return n;
}
case TK_IDENT: {
Node *n = newnode(p->a, N_TNAME, pp);
n->str = p->cur.text;
@@ -455,6 +465,7 @@ parseprimary(Parser *p)
case TK_TRUE: advance(p); return newnode(p->a, N_TRUE, pp);
case TK_FALSE: advance(p); return newnode(p->a, N_FALSE, pp);
case TK_NIL: advance(p); return newnode(p->a, N_NIL, pp);
case TK_VOID: advance(p); return newnode(p->a, N_VOIDLIT, pp);
case TK_UNDER: {
/* Bare `_` — valid only as a discard lvalue. We yield an N_IDENT
* with empty str; the checker rejects it outside assignment

View File

@@ -40,7 +40,8 @@ static const struct kwent kwtab[] = {
{ "switch", TK_SWITCH },
{ "true", TK_TRUE },
{ "type", TK_TYPE },
{ "use", TK_USE }
{ "use", TK_USE },
{ "void", TK_VOID }
};
Tkind
@@ -91,6 +92,7 @@ tokname(Tkind k)
case TK_FALSE: return "false";
case TK_AS: return "as";
case TK_IS: return "is";
case TK_VOID: return "void";
case TK_STATIC: return "static";
case TK_MATCH: return "match";
case TK_CONST: return "const";

View File

@@ -176,6 +176,7 @@ typedef enum {
* to keep the numeric value of every existing kind unchanged —
* the selfhost wwdump-diff test (990) is byte-sensitive. */
TK_IS, /* Hare-style type test: e is T */
TK_VOID, /* `void` — both a type name and a zero-size value */
TK_LAST /* sentinel for tables */
} Tkind;
@@ -294,6 +295,7 @@ typedef enum {
* byte-for-byte. lhs=value, rhs=variant type expr. */
N_TYPETEST, /* lhs is T → bool */
N_TYPEASSERT, /* lhs as T → T (abort if tag mismatch) */
N_VOIDLIT, /* `void` as expression — zero-size void value */
N_LAST
} Nkind;