selfhost/cmd/wcc/check+test: reject bare alloc(v) at let-init in wwstage
Cstage's check.c:981-1006/1052-1082 builds a real (*T|nomem) / ([]T|nomem) return type for the alloc builtin; wwstage was returning nil from exprtype's N_CALL arm (alloc is SK_FN with decl=nil under seedprimitives), and checkletassign early-returned on nil src, silently accepting `let p: *T = alloc(v);` without `!`. Stage asymmetry that #30 papered over until now. Three coordinated edits in check.ww: - exprtype N_CALL: synthesize N_TTAGGED{N_TPTR{argt}, nomem} or {N_TSLICE{u8}, nomem} for bare alloc (same-module gated, mirrors cstage check.c:981-985 / task #23). - exprtype N_TRYUNW: project the success variant so `let p:*T = alloc(v)!;` resolves rhs to *T. - isassignable: tagged → non-tagged is unconditionally not assignable, forcing match/?/!. 950_selfcheck.c rows pin both ptr and slice forms.
This commit is contained in:
@@ -7423,6 +7423,54 @@ fn exprtype(c: *checker, e: *node) *node = {
|
||||
if (k == nkind.N_CALL) {
|
||||
let callee: *node = e.lhs;
|
||||
if (callee == nil) { return nil; };
|
||||
// #31: synthesize the `alloc(value)` / `alloc([], n)` builtin
|
||||
// return shape so checkletassign sees the same `(*T | nomem)` /
|
||||
// `([]T | nomem)` cstage's check.c stamps at L981-1006. Without
|
||||
// this, exprtype returns the seeded decl's nil lhs and the let
|
||||
// silently accepts `let p: *T = alloc(v);` — rule 10 trap.
|
||||
// Same-module gate mirrors cstage's `c->cur_mod &&
|
||||
// scope_lookup_in_module(...)` check from task #23.
|
||||
if (callee.kind == nkind.N_IDENT) {
|
||||
if (streq(callee.str, "alloc")) {
|
||||
let shadowed: bool = false;
|
||||
if (c.curmod.len > 0) {
|
||||
if (scopelookupinmodule(c.cur, c.curmod, "alloc") != nil) {
|
||||
shadowed = true;
|
||||
};
|
||||
};
|
||||
if (!shadowed) {
|
||||
if (e.list != nil) {
|
||||
// Slice form: `alloc([], n)`.
|
||||
if (e.list.kind == nkind.N_ARRLIT) {
|
||||
if (e.list.list == nil) {
|
||||
if (e.list.next != nil) {
|
||||
if (e.list.next.next == nil) {
|
||||
let sl: *node = newnode(c.a, nkind.N_TSLICE, "", 0, 0);
|
||||
sl.lhs = mktname(c, "u8");
|
||||
let nome: *node = mktname(c, "nomem");
|
||||
sl.next = nome;
|
||||
let tt: *node = newnode(c.a, nkind.N_TTAGGED, "", 0, 0);
|
||||
tt.list = sl;
|
||||
return tt;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// Value form: `alloc(value)`.
|
||||
if (e.list.next == nil) {
|
||||
let argt: *node = exprtype(c, e.list);
|
||||
let ptr: *node = newnode(c.a, nkind.N_TPTR, "", 0, 0);
|
||||
ptr.lhs = argt;
|
||||
let nome: *node = mktname(c, "nomem");
|
||||
ptr.next = nome;
|
||||
let tt: *node = newnode(c.a, nkind.N_TTAGGED, "", 0, 0);
|
||||
tt.list = ptr;
|
||||
return tt;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
let nm: str;
|
||||
nm.ptr = nil; nm.len = 0;
|
||||
if (callee.kind == nkind.N_IDENT) { nm = callee.str; };
|
||||
@@ -7453,6 +7501,24 @@ fn exprtype(c: *checker, e: *node) *node = {
|
||||
};
|
||||
return ou.list;
|
||||
};
|
||||
if (k == nkind.N_TRYUNW) {
|
||||
// `e!` abort-on-error unwrap; success variant is what the
|
||||
// receiver gets, identical to `?` shape modulo control flow.
|
||||
// #31: required so `let p: *T = alloc(v)!;` resolves to *T.
|
||||
let opt: *node = exprtype(c, e.lhs);
|
||||
let ou: *node = resolvealias(c, unwrapbang(opt));
|
||||
if (ou == nil) { return nil; };
|
||||
if (ou.kind != nkind.N_TTAGGED) { return nil; };
|
||||
if (taggedhaserr(c, ou)) {
|
||||
let v: *node = ou.list;
|
||||
for (v != nil) {
|
||||
if (!iserrvariant(c, ou, v)) { return v; };
|
||||
v = v.next;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
return ou.list;
|
||||
};
|
||||
if (k == nkind.N_TYPEASSERT) {
|
||||
// `e as T` → T
|
||||
return e.rhs;
|
||||
@@ -7604,6 +7670,12 @@ fn isassignable(c: *checker, dst: *node, src: *node, confident: *bool) bool = {
|
||||
*confident = false;
|
||||
return true;
|
||||
};
|
||||
// tagged → non-tagged: requires `?` / `!` / match to project a
|
||||
// variant. #31: this is what traps `let p: *T = alloc(v);`
|
||||
// where the builtin returns `(*T | nomem)` and the LHS is bare.
|
||||
if (su.kind == nkind.N_TTAGGED && du.kind != nkind.N_TTAGGED) {
|
||||
return false;
|
||||
};
|
||||
// Two known primitives with different names are confidently
|
||||
// incompatible. `i32 ↔ bool`, `str ↔ i32`, etc.
|
||||
if (du.kind == nkind.N_TNAME && su.kind == nkind.N_TNAME) {
|
||||
|
||||
@@ -561,6 +561,54 @@ fn exprtype(c: *checker, e: *node) *node = {
|
||||
if (k == nkind.N_CALL) {
|
||||
let callee: *node = e.lhs;
|
||||
if (callee == nil) { return nil; };
|
||||
// #31: synthesize the `alloc(value)` / `alloc([], n)` builtin
|
||||
// return shape so checkletassign sees the same `(*T | nomem)` /
|
||||
// `([]T | nomem)` cstage's check.c stamps at L981-1006. Without
|
||||
// this, exprtype returns the seeded decl's nil lhs and the let
|
||||
// silently accepts `let p: *T = alloc(v);` — rule 10 trap.
|
||||
// Same-module gate mirrors cstage's `c->cur_mod &&
|
||||
// scope_lookup_in_module(...)` check from task #23.
|
||||
if (callee.kind == nkind.N_IDENT) {
|
||||
if (streq(callee.str, "alloc")) {
|
||||
let shadowed: bool = false;
|
||||
if (c.curmod.len > 0) {
|
||||
if (scopelookupinmodule(c.cur, c.curmod, "alloc") != nil) {
|
||||
shadowed = true;
|
||||
};
|
||||
};
|
||||
if (!shadowed) {
|
||||
if (e.list != nil) {
|
||||
// Slice form: `alloc([], n)`.
|
||||
if (e.list.kind == nkind.N_ARRLIT) {
|
||||
if (e.list.list == nil) {
|
||||
if (e.list.next != nil) {
|
||||
if (e.list.next.next == nil) {
|
||||
let sl: *node = newnode(c.a, nkind.N_TSLICE, "", 0, 0);
|
||||
sl.lhs = mktname(c, "u8");
|
||||
let nome: *node = mktname(c, "nomem");
|
||||
sl.next = nome;
|
||||
let tt: *node = newnode(c.a, nkind.N_TTAGGED, "", 0, 0);
|
||||
tt.list = sl;
|
||||
return tt;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// Value form: `alloc(value)`.
|
||||
if (e.list.next == nil) {
|
||||
let argt: *node = exprtype(c, e.list);
|
||||
let ptr: *node = newnode(c.a, nkind.N_TPTR, "", 0, 0);
|
||||
ptr.lhs = argt;
|
||||
let nome: *node = mktname(c, "nomem");
|
||||
ptr.next = nome;
|
||||
let tt: *node = newnode(c.a, nkind.N_TTAGGED, "", 0, 0);
|
||||
tt.list = ptr;
|
||||
return tt;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
let nm: str;
|
||||
nm.ptr = nil; nm.len = 0;
|
||||
if (callee.kind == nkind.N_IDENT) { nm = callee.str; };
|
||||
@@ -591,6 +639,24 @@ fn exprtype(c: *checker, e: *node) *node = {
|
||||
};
|
||||
return ou.list;
|
||||
};
|
||||
if (k == nkind.N_TRYUNW) {
|
||||
// `e!` abort-on-error unwrap; success variant is what the
|
||||
// receiver gets, identical to `?` shape modulo control flow.
|
||||
// #31: required so `let p: *T = alloc(v)!;` resolves to *T.
|
||||
let opt: *node = exprtype(c, e.lhs);
|
||||
let ou: *node = resolvealias(c, unwrapbang(opt));
|
||||
if (ou == nil) { return nil; };
|
||||
if (ou.kind != nkind.N_TTAGGED) { return nil; };
|
||||
if (taggedhaserr(c, ou)) {
|
||||
let v: *node = ou.list;
|
||||
for (v != nil) {
|
||||
if (!iserrvariant(c, ou, v)) { return v; };
|
||||
v = v.next;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
return ou.list;
|
||||
};
|
||||
if (k == nkind.N_TYPEASSERT) {
|
||||
// `e as T` → T
|
||||
return e.rhs;
|
||||
@@ -742,6 +808,12 @@ fn isassignable(c: *checker, dst: *node, src: *node, confident: *bool) bool = {
|
||||
*confident = false;
|
||||
return true;
|
||||
};
|
||||
// tagged → non-tagged: requires `?` / `!` / match to project a
|
||||
// variant. #31: this is what traps `let p: *T = alloc(v);`
|
||||
// where the builtin returns `(*T | nomem)` and the LHS is bare.
|
||||
if (su.kind == nkind.N_TTAGGED && du.kind != nkind.N_TTAGGED) {
|
||||
return false;
|
||||
};
|
||||
// Two known primitives with different names are confidently
|
||||
// incompatible. `i32 ↔ bool`, `str ↔ i32`, etc.
|
||||
if (du.kind == nkind.N_TNAME && su.kind == nkind.N_TNAME) {
|
||||
|
||||
@@ -7423,6 +7423,54 @@ fn exprtype(c: *checker, e: *node) *node = {
|
||||
if (k == nkind.N_CALL) {
|
||||
let callee: *node = e.lhs;
|
||||
if (callee == nil) { return nil; };
|
||||
// #31: synthesize the `alloc(value)` / `alloc([], n)` builtin
|
||||
// return shape so checkletassign sees the same `(*T | nomem)` /
|
||||
// `([]T | nomem)` cstage's check.c stamps at L981-1006. Without
|
||||
// this, exprtype returns the seeded decl's nil lhs and the let
|
||||
// silently accepts `let p: *T = alloc(v);` — rule 10 trap.
|
||||
// Same-module gate mirrors cstage's `c->cur_mod &&
|
||||
// scope_lookup_in_module(...)` check from task #23.
|
||||
if (callee.kind == nkind.N_IDENT) {
|
||||
if (streq(callee.str, "alloc")) {
|
||||
let shadowed: bool = false;
|
||||
if (c.curmod.len > 0) {
|
||||
if (scopelookupinmodule(c.cur, c.curmod, "alloc") != nil) {
|
||||
shadowed = true;
|
||||
};
|
||||
};
|
||||
if (!shadowed) {
|
||||
if (e.list != nil) {
|
||||
// Slice form: `alloc([], n)`.
|
||||
if (e.list.kind == nkind.N_ARRLIT) {
|
||||
if (e.list.list == nil) {
|
||||
if (e.list.next != nil) {
|
||||
if (e.list.next.next == nil) {
|
||||
let sl: *node = newnode(c.a, nkind.N_TSLICE, "", 0, 0);
|
||||
sl.lhs = mktname(c, "u8");
|
||||
let nome: *node = mktname(c, "nomem");
|
||||
sl.next = nome;
|
||||
let tt: *node = newnode(c.a, nkind.N_TTAGGED, "", 0, 0);
|
||||
tt.list = sl;
|
||||
return tt;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// Value form: `alloc(value)`.
|
||||
if (e.list.next == nil) {
|
||||
let argt: *node = exprtype(c, e.list);
|
||||
let ptr: *node = newnode(c.a, nkind.N_TPTR, "", 0, 0);
|
||||
ptr.lhs = argt;
|
||||
let nome: *node = mktname(c, "nomem");
|
||||
ptr.next = nome;
|
||||
let tt: *node = newnode(c.a, nkind.N_TTAGGED, "", 0, 0);
|
||||
tt.list = ptr;
|
||||
return tt;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
let nm: str;
|
||||
nm.ptr = nil; nm.len = 0;
|
||||
if (callee.kind == nkind.N_IDENT) { nm = callee.str; };
|
||||
@@ -7453,6 +7501,24 @@ fn exprtype(c: *checker, e: *node) *node = {
|
||||
};
|
||||
return ou.list;
|
||||
};
|
||||
if (k == nkind.N_TRYUNW) {
|
||||
// `e!` abort-on-error unwrap; success variant is what the
|
||||
// receiver gets, identical to `?` shape modulo control flow.
|
||||
// #31: required so `let p: *T = alloc(v)!;` resolves to *T.
|
||||
let opt: *node = exprtype(c, e.lhs);
|
||||
let ou: *node = resolvealias(c, unwrapbang(opt));
|
||||
if (ou == nil) { return nil; };
|
||||
if (ou.kind != nkind.N_TTAGGED) { return nil; };
|
||||
if (taggedhaserr(c, ou)) {
|
||||
let v: *node = ou.list;
|
||||
for (v != nil) {
|
||||
if (!iserrvariant(c, ou, v)) { return v; };
|
||||
v = v.next;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
return ou.list;
|
||||
};
|
||||
if (k == nkind.N_TYPEASSERT) {
|
||||
// `e as T` → T
|
||||
return e.rhs;
|
||||
@@ -7604,6 +7670,12 @@ fn isassignable(c: *checker, dst: *node, src: *node, confident: *bool) bool = {
|
||||
*confident = false;
|
||||
return true;
|
||||
};
|
||||
// tagged → non-tagged: requires `?` / `!` / match to project a
|
||||
// variant. #31: this is what traps `let p: *T = alloc(v);`
|
||||
// where the builtin returns `(*T | nomem)` and the LHS is bare.
|
||||
if (su.kind == nkind.N_TTAGGED && du.kind != nkind.N_TTAGGED) {
|
||||
return false;
|
||||
};
|
||||
// Two known primitives with different names are confidently
|
||||
// incompatible. `i32 ↔ bool`, `str ↔ i32`, etc.
|
||||
if (du.kind == nkind.N_TNAME && su.kind == nkind.N_TNAME) {
|
||||
|
||||
Reference in New Issue
Block a user