cmd+selfhost+test: predeclare nomem in universe scope
Per Hare convention, `nomem` is a language-level error type — no
import required, in scope alongside void/done/rune/str. ref/hare uses
it bare at errors/string.ha:14, types/c/strings.ha:89, net/uri/parse.ha:17
with no `use`. Precondition for graduating the `alloc` builtin to
`(*T | nomem)` returns.
cstage: ty_nomem is NAMED{under=ty_void, iserror=1}, installed by
typesinit and surfaced via lookup_builtin. wwstage seeds the same
shape in both check.ww (scope) and cgen.ww (aliases) — separate
tables, both consulted; without the cgen seed wwstage drops the
zero-init for `let e: nomem;` locals and breaks byte-identity.
Tests: tagged_ptr_ret.ww and trypromote.ww drop their local
`type nomem = !void;` aliases. 990_selfhost.c adds a regression that
a value named `nomem` does not collide with the predeclared type.
This commit is contained in:
@@ -53,6 +53,7 @@ lookup_builtin(const char *name)
|
|||||||
if (strcmp(name, "f64") == 0) return ty_f64;
|
if (strcmp(name, "f64") == 0) return ty_f64;
|
||||||
if (strcmp(name, "str") == 0) return ty_str;
|
if (strcmp(name, "str") == 0) return ty_str;
|
||||||
if (strcmp(name, "never") == 0) return ty_never;
|
if (strcmp(name, "never") == 0) return ty_never;
|
||||||
|
if (strcmp(name, "nomem") == 0) return ty_nomem; /* #29 */
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ Type *ty_int, *ty_uint, *ty_uintptr;
|
|||||||
Type *ty_f32, *ty_f64, *ty_str;
|
Type *ty_f32, *ty_f64, *ty_str;
|
||||||
Type *ty_err;
|
Type *ty_err;
|
||||||
Type *ty_never;
|
Type *ty_never;
|
||||||
|
Type *ty_nomem;
|
||||||
Type *ty_untyped_int, *ty_untyped_float, *ty_untyped_str;
|
Type *ty_untyped_int, *ty_untyped_float, *ty_untyped_str;
|
||||||
Type *ty_untyped_rune, *ty_untyped_bool, *ty_untyped_nil;
|
Type *ty_untyped_rune, *ty_untyped_bool, *ty_untyped_nil;
|
||||||
|
|
||||||
@@ -63,6 +64,16 @@ typesinit(Arena *a)
|
|||||||
ty_str = prim(a, TY_STR, "str", 16, 8);
|
ty_str = prim(a, TY_STR, "str", 16, 8);
|
||||||
ty_err = prim(a, TY_ERR, "<err>", 0, 1);
|
ty_err = prim(a, TY_ERR, "<err>", 0, 1);
|
||||||
ty_never = prim(a, TY_NEVER, "never", 0, 1);
|
ty_never = prim(a, TY_NEVER, "never", 0, 1);
|
||||||
|
/* #29: predeclared `type nomem = !void;`. NAMED so variant_match
|
||||||
|
* compares by pointer identity (singleton across all references);
|
||||||
|
* under=ty_void + iserror=1 triggers the `!T` error-tag machinery
|
||||||
|
* the same way a user-declared alias would. */
|
||||||
|
ty_nomem = newtype(a, TY_NAMED);
|
||||||
|
ty_nomem->name = "nomem";
|
||||||
|
ty_nomem->under = ty_void;
|
||||||
|
ty_nomem->size = ty_void->size;
|
||||||
|
ty_nomem->align = ty_void->align;
|
||||||
|
ty_nomem->iserror = 1;
|
||||||
|
|
||||||
ty_untyped_int = prim(a, TY_UNTYPED_INT, "untyped_int", 0, 1);
|
ty_untyped_int = prim(a, TY_UNTYPED_INT, "untyped_int", 0, 1);
|
||||||
ty_untyped_float = prim(a, TY_UNTYPED_FLOAT, "untyped_float", 0, 1);
|
ty_untyped_float = prim(a, TY_UNTYPED_FLOAT, "untyped_float", 0, 1);
|
||||||
|
|||||||
@@ -440,6 +440,7 @@ extern Type *ty_int, *ty_uint, *ty_uintptr;
|
|||||||
extern Type *ty_f32, *ty_f64, *ty_str;
|
extern Type *ty_f32, *ty_f64, *ty_str;
|
||||||
extern Type *ty_err;
|
extern Type *ty_err;
|
||||||
extern Type *ty_never;
|
extern Type *ty_never;
|
||||||
|
extern Type *ty_nomem; /* task #29: predeclared `!void` alias */
|
||||||
extern Type *ty_untyped_int, *ty_untyped_float, *ty_untyped_str;
|
extern Type *ty_untyped_int, *ty_untyped_float, *ty_untyped_str;
|
||||||
extern Type *ty_untyped_rune, *ty_untyped_bool, *ty_untyped_nil;
|
extern Type *ty_untyped_rune, *ty_untyped_bool, *ty_untyped_nil;
|
||||||
|
|
||||||
|
|||||||
@@ -6859,6 +6859,23 @@ fn seedprimitives(c: *checker) void = {
|
|||||||
scopedefine(c.top, "f64", skind.SK_TYPE, c.tc.tyf64, nil);
|
scopedefine(c.top, "f64", skind.SK_TYPE, c.tc.tyf64, nil);
|
||||||
scopedefine(c.top, "str", skind.SK_TYPE, c.tc.tystr, nil);
|
scopedefine(c.top, "str", skind.SK_TYPE, c.tc.tystr, nil);
|
||||||
scopedefine(c.top, "never", skind.SK_TYPE, c.tc.tynever, nil);
|
scopedefine(c.top, "never", skind.SK_TYPE, c.tc.tynever, nil);
|
||||||
|
// #29: predeclare `type nomem = !void;` so user code needn't
|
||||||
|
// declare it locally. Synthesize an nkind.N_TYPEDECL whose lhs is
|
||||||
|
// nkind.N_TBANG{nkind.N_TNAME("void")} so varianterr and other
|
||||||
|
// iserror-aware paths treat `nomem` identically to a user-written
|
||||||
|
// alias. Mirrors cmd/wcc/check.c lookup_builtin returning
|
||||||
|
// ty_nomem (NAMED, under=ty_void, iserror=1). Note: cgen owns a
|
||||||
|
// separate alias chain — see collectaliases in cgen.ww for the
|
||||||
|
// companion seed.
|
||||||
|
let empty: str;
|
||||||
|
let tnvoid: *node = newnode(c.a, nkind.N_TNAME, empty, 0, 0);
|
||||||
|
tnvoid.str = "void";
|
||||||
|
let bang: *node = newnode(c.a, nkind.N_TBANG, empty, 0, 0);
|
||||||
|
bang.lhs = tnvoid;
|
||||||
|
let nomemdecl: *node = newnode(c.a, nkind.N_TYPEDECL, empty, 0, 0);
|
||||||
|
nomemdecl.str = "nomem";
|
||||||
|
nomemdecl.lhs = bang;
|
||||||
|
scopedefine(c.top, "nomem", skind.SK_TYPE, nil, nomemdecl);
|
||||||
// `nil`, `true`, `false` are keywords — handled at the lex/parser
|
// `nil`, `true`, `false` are keywords — handled at the lex/parser
|
||||||
// level, no symbol needed.
|
// level, no symbol needed.
|
||||||
// `len`, `alloc`, `free`, `append` are pseudo-builtins; scopedefine
|
// `len`, `alloc`, `free`, `append` are pseudo-builtins; scopedefine
|
||||||
@@ -19634,6 +19651,27 @@ type aliasent = struct {
|
|||||||
|
|
||||||
fn collectaliases(c: *cgen, file: *node) void = {
|
fn collectaliases(c: *cgen, file: *node) void = {
|
||||||
c.aliases = nil;
|
c.aliases = nil;
|
||||||
|
// #29: seed `type nomem = !void;` here AS WELL AS in check.ww's
|
||||||
|
// seedprimitives. The two seeds aren't redundant: wwstage's check
|
||||||
|
// owns c.top (used by name resolution); cgen owns its own
|
||||||
|
// c.aliases chain (used by resolvetype / slotsize / TBANG checks).
|
||||||
|
// Without this seed, resolvetype("nomem") returns the raw N_TNAME
|
||||||
|
// — slotsize falls through to 8B without zero-init, diverging from
|
||||||
|
// cstage's `let e: nomem;` MOVQ $0 emit on the slot (rule 10).
|
||||||
|
// Inserted at the head so the user-decl loop below prepends; the
|
||||||
|
// same-module / any-match passes in aliaslookup then let a local
|
||||||
|
// `type nomem = !void;` shadow this fallback within its module.
|
||||||
|
let empty: str;
|
||||||
|
let tnvoid: *node = newnode(c.a, nkind.N_TNAME, empty, 0, 0);
|
||||||
|
tnvoid.str = "void";
|
||||||
|
let bang: *node = newnode(c.a, nkind.N_TBANG, empty, 0, 0);
|
||||||
|
bang.lhs = tnvoid;
|
||||||
|
let nomemal: *aliasent = amalloc(c.a, 64u64): *aliasent;
|
||||||
|
nomemal.aname = "nomem";
|
||||||
|
nomemal.amod = empty;
|
||||||
|
nomemal.target = bang;
|
||||||
|
nomemal.aanext = nil;
|
||||||
|
c.aliases = nomemal;
|
||||||
let d: *node = file.list;
|
let d: *node = file.list;
|
||||||
for (d != nil) {
|
for (d != nil) {
|
||||||
if (d.kind == nkind.N_TYPEDECL) {
|
if (d.kind == nkind.N_TYPEDECL) {
|
||||||
|
|||||||
@@ -55,6 +55,27 @@ type aliasent = struct {
|
|||||||
|
|
||||||
fn collectaliases(c: *cgen, file: *node) void = {
|
fn collectaliases(c: *cgen, file: *node) void = {
|
||||||
c.aliases = nil;
|
c.aliases = nil;
|
||||||
|
// #29: seed `type nomem = !void;` here AS WELL AS in check.ww's
|
||||||
|
// seedprimitives. The two seeds aren't redundant: wwstage's check
|
||||||
|
// owns c.top (used by name resolution); cgen owns its own
|
||||||
|
// c.aliases chain (used by resolvetype / slotsize / TBANG checks).
|
||||||
|
// Without this seed, resolvetype("nomem") returns the raw N_TNAME
|
||||||
|
// — slotsize falls through to 8B without zero-init, diverging from
|
||||||
|
// cstage's `let e: nomem;` MOVQ $0 emit on the slot (rule 10).
|
||||||
|
// Inserted at the head so the user-decl loop below prepends; the
|
||||||
|
// same-module / any-match passes in aliaslookup then let a local
|
||||||
|
// `type nomem = !void;` shadow this fallback within its module.
|
||||||
|
let empty: str;
|
||||||
|
let tnvoid: *node = newnode(c.a, nkind.N_TNAME, empty, 0, 0);
|
||||||
|
tnvoid.str = "void";
|
||||||
|
let bang: *node = newnode(c.a, nkind.N_TBANG, empty, 0, 0);
|
||||||
|
bang.lhs = tnvoid;
|
||||||
|
let nomemal: *aliasent = amalloc(c.a, 64u64): *aliasent;
|
||||||
|
nomemal.aname = "nomem";
|
||||||
|
nomemal.amod = empty;
|
||||||
|
nomemal.target = bang;
|
||||||
|
nomemal.aanext = nil;
|
||||||
|
c.aliases = nomemal;
|
||||||
let d: *node = file.list;
|
let d: *node = file.list;
|
||||||
for (d != nil) {
|
for (d != nil) {
|
||||||
if (d.kind == nkind.N_TYPEDECL) {
|
if (d.kind == nkind.N_TYPEDECL) {
|
||||||
|
|||||||
@@ -63,6 +63,23 @@ fn seedprimitives(c: *checker) void = {
|
|||||||
scopedefine(c.top, "f64", skind.SK_TYPE, c.tc.tyf64, nil);
|
scopedefine(c.top, "f64", skind.SK_TYPE, c.tc.tyf64, nil);
|
||||||
scopedefine(c.top, "str", skind.SK_TYPE, c.tc.tystr, nil);
|
scopedefine(c.top, "str", skind.SK_TYPE, c.tc.tystr, nil);
|
||||||
scopedefine(c.top, "never", skind.SK_TYPE, c.tc.tynever, nil);
|
scopedefine(c.top, "never", skind.SK_TYPE, c.tc.tynever, nil);
|
||||||
|
// #29: predeclare `type nomem = !void;` so user code needn't
|
||||||
|
// declare it locally. Synthesize an nkind.N_TYPEDECL whose lhs is
|
||||||
|
// nkind.N_TBANG{nkind.N_TNAME("void")} so varianterr and other
|
||||||
|
// iserror-aware paths treat `nomem` identically to a user-written
|
||||||
|
// alias. Mirrors cmd/wcc/check.c lookup_builtin returning
|
||||||
|
// ty_nomem (NAMED, under=ty_void, iserror=1). Note: cgen owns a
|
||||||
|
// separate alias chain — see collectaliases in cgen.ww for the
|
||||||
|
// companion seed.
|
||||||
|
let empty: str;
|
||||||
|
let tnvoid: *node = newnode(c.a, nkind.N_TNAME, empty, 0, 0);
|
||||||
|
tnvoid.str = "void";
|
||||||
|
let bang: *node = newnode(c.a, nkind.N_TBANG, empty, 0, 0);
|
||||||
|
bang.lhs = tnvoid;
|
||||||
|
let nomemdecl: *node = newnode(c.a, nkind.N_TYPEDECL, empty, 0, 0);
|
||||||
|
nomemdecl.str = "nomem";
|
||||||
|
nomemdecl.lhs = bang;
|
||||||
|
scopedefine(c.top, "nomem", skind.SK_TYPE, nil, nomemdecl);
|
||||||
// `nil`, `true`, `false` are keywords — handled at the lex/parser
|
// `nil`, `true`, `false` are keywords — handled at the lex/parser
|
||||||
// level, no symbol needed.
|
// level, no symbol needed.
|
||||||
// `len`, `alloc`, `free`, `append` are pseudo-builtins; scopedefine
|
// `len`, `alloc`, `free`, `append` are pseudo-builtins; scopedefine
|
||||||
|
|||||||
@@ -6859,6 +6859,23 @@ fn seedprimitives(c: *checker) void = {
|
|||||||
scopedefine(c.top, "f64", skind.SK_TYPE, c.tc.tyf64, nil);
|
scopedefine(c.top, "f64", skind.SK_TYPE, c.tc.tyf64, nil);
|
||||||
scopedefine(c.top, "str", skind.SK_TYPE, c.tc.tystr, nil);
|
scopedefine(c.top, "str", skind.SK_TYPE, c.tc.tystr, nil);
|
||||||
scopedefine(c.top, "never", skind.SK_TYPE, c.tc.tynever, nil);
|
scopedefine(c.top, "never", skind.SK_TYPE, c.tc.tynever, nil);
|
||||||
|
// #29: predeclare `type nomem = !void;` so user code needn't
|
||||||
|
// declare it locally. Synthesize an nkind.N_TYPEDECL whose lhs is
|
||||||
|
// nkind.N_TBANG{nkind.N_TNAME("void")} so varianterr and other
|
||||||
|
// iserror-aware paths treat `nomem` identically to a user-written
|
||||||
|
// alias. Mirrors cmd/wcc/check.c lookup_builtin returning
|
||||||
|
// ty_nomem (NAMED, under=ty_void, iserror=1). Note: cgen owns a
|
||||||
|
// separate alias chain — see collectaliases in cgen.ww for the
|
||||||
|
// companion seed.
|
||||||
|
let empty: str;
|
||||||
|
let tnvoid: *node = newnode(c.a, nkind.N_TNAME, empty, 0, 0);
|
||||||
|
tnvoid.str = "void";
|
||||||
|
let bang: *node = newnode(c.a, nkind.N_TBANG, empty, 0, 0);
|
||||||
|
bang.lhs = tnvoid;
|
||||||
|
let nomemdecl: *node = newnode(c.a, nkind.N_TYPEDECL, empty, 0, 0);
|
||||||
|
nomemdecl.str = "nomem";
|
||||||
|
nomemdecl.lhs = bang;
|
||||||
|
scopedefine(c.top, "nomem", skind.SK_TYPE, nil, nomemdecl);
|
||||||
// `nil`, `true`, `false` are keywords — handled at the lex/parser
|
// `nil`, `true`, `false` are keywords — handled at the lex/parser
|
||||||
// level, no symbol needed.
|
// level, no symbol needed.
|
||||||
// `len`, `alloc`, `free`, `append` are pseudo-builtins; scopedefine
|
// `len`, `alloc`, `free`, `append` are pseudo-builtins; scopedefine
|
||||||
@@ -19634,6 +19651,27 @@ type aliasent = struct {
|
|||||||
|
|
||||||
fn collectaliases(c: *cgen, file: *node) void = {
|
fn collectaliases(c: *cgen, file: *node) void = {
|
||||||
c.aliases = nil;
|
c.aliases = nil;
|
||||||
|
// #29: seed `type nomem = !void;` here AS WELL AS in check.ww's
|
||||||
|
// seedprimitives. The two seeds aren't redundant: wwstage's check
|
||||||
|
// owns c.top (used by name resolution); cgen owns its own
|
||||||
|
// c.aliases chain (used by resolvetype / slotsize / TBANG checks).
|
||||||
|
// Without this seed, resolvetype("nomem") returns the raw N_TNAME
|
||||||
|
// — slotsize falls through to 8B without zero-init, diverging from
|
||||||
|
// cstage's `let e: nomem;` MOVQ $0 emit on the slot (rule 10).
|
||||||
|
// Inserted at the head so the user-decl loop below prepends; the
|
||||||
|
// same-module / any-match passes in aliaslookup then let a local
|
||||||
|
// `type nomem = !void;` shadow this fallback within its module.
|
||||||
|
let empty: str;
|
||||||
|
let tnvoid: *node = newnode(c.a, nkind.N_TNAME, empty, 0, 0);
|
||||||
|
tnvoid.str = "void";
|
||||||
|
let bang: *node = newnode(c.a, nkind.N_TBANG, empty, 0, 0);
|
||||||
|
bang.lhs = tnvoid;
|
||||||
|
let nomemal: *aliasent = amalloc(c.a, 64u64): *aliasent;
|
||||||
|
nomemal.aname = "nomem";
|
||||||
|
nomemal.amod = empty;
|
||||||
|
nomemal.target = bang;
|
||||||
|
nomemal.aanext = nil;
|
||||||
|
c.aliases = nomemal;
|
||||||
let d: *node = file.list;
|
let d: *node = file.list;
|
||||||
for (d != nil) {
|
for (d != nil) {
|
||||||
if (d.kind == nkind.N_TYPEDECL) {
|
if (d.kind == nkind.N_TYPEDECL) {
|
||||||
|
|||||||
@@ -5,22 +5,19 @@
|
|||||||
// while wwstage emitted the documented general tagged-return ABI
|
// while wwstage emitted the documented general tagged-return ABI
|
||||||
// (AX=tag, DX=word0). Per CLAUDE.md rule 10 the richer side aligns DOWN —
|
// (AX=tag, DX=word0). Per CLAUDE.md rule 10 the richer side aligns DOWN —
|
||||||
// cstage now restricts the nullable fold to literal `void` variants, so
|
// cstage now restricts the nullable fold to literal `void` variants, so
|
||||||
// `(*T | nomem)` (`type nomem = !void;`) takes the general path on both
|
// `(*T | nomem)` takes the general path on both stages and the 993/995
|
||||||
// stages and the 993/995 byte-identity tests stay green once #17 lands a
|
// byte-identity tests stay green.
|
||||||
// (*T | nomem) signature in lib/.
|
|
||||||
//
|
//
|
||||||
// nomem is declared locally because it is not yet predeclared in the
|
// Task #29: `nomem` is now predeclared in the compiler universe scope, so
|
||||||
// universe scope (that move is #17). Two match arms cover both runtime
|
// neither `import errors;` nor a local `type nomem = !void;` is needed.
|
||||||
// outcomes — success unwrap (tag=0, ptr payload in DX) and error
|
// Two match arms cover both runtime outcomes — success unwrap (tag=0,
|
||||||
// propagation (tag=1) — exercising the same AX/DX ABI both stages must
|
// ptr payload in DX) and error propagation (tag=1) — exercising the
|
||||||
// agree on.
|
// same AX/DX ABI both stages must agree on.
|
||||||
|
|
||||||
package test;
|
package test;
|
||||||
|
|
||||||
import fmt;
|
import fmt;
|
||||||
|
|
||||||
type nomem = !void;
|
|
||||||
|
|
||||||
fn alloc1(fail: i64) (*u8 | nomem) = {
|
fn alloc1(fail: i64) (*u8 | nomem) = {
|
||||||
if (fail != 0i64) { let e: nomem; return e; };
|
if (fail != 0i64) { let e: nomem; return e; };
|
||||||
let buf: [1]u8;
|
let buf: [1]u8;
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ package test;
|
|||||||
|
|
||||||
import fmt;
|
import fmt;
|
||||||
|
|
||||||
type nomem = !void;
|
// #29: `nomem` is predeclared in the universe scope — no local
|
||||||
|
// `type nomem = !void;` (or `import errors;`) needed.
|
||||||
|
|
||||||
fn stub(fail: i64) (i64 | nomem) = {
|
fn stub(fail: i64) (i64 | nomem) = {
|
||||||
if (fail != 0i64) { let e: nomem; return e; };
|
if (fail != 0i64) { let e: nomem; return e; };
|
||||||
|
|||||||
@@ -447,6 +447,16 @@ probe_ww_compile(const char *bin)
|
|||||||
" o.p = in_;\n"
|
" o.p = in_;\n"
|
||||||
" return o.p.val: i32;\n"
|
" return o.p.val: i32;\n"
|
||||||
"};", 42 },
|
"};", 42 },
|
||||||
|
/* #29: `nomem` predeclared in the universe scope as a TYPE
|
||||||
|
* must not block a value of the same name. scope_lookup is
|
||||||
|
* kind-aware, so `let nomem: i32 = 42;` shadows the type in
|
||||||
|
* value position. Byte-identity vs cstage proves both stages
|
||||||
|
* route the value-position lookup past the universe-scope
|
||||||
|
* type seed. */
|
||||||
|
{ "fn main() i32 = {\n"
|
||||||
|
" let nomem: i32 = 42;\n"
|
||||||
|
" return nomem;\n"
|
||||||
|
"};", 42 },
|
||||||
{ NULL, 0 },
|
{ NULL, 0 },
|
||||||
};
|
};
|
||||||
char rt[1024];
|
char rt[1024];
|
||||||
|
|||||||
Reference in New Issue
Block a user