diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index b01a81ff..15e516a7 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -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) { diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index 454af26e..34876d13 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -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) { diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 250e98cd..868d393d 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -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) { diff --git a/test/wcc/950_selfcheck.c b/test/wcc/950_selfcheck.c index f01e3451..b664acdb 100644 --- a/test/wcc/950_selfcheck.c +++ b/test/wcc/950_selfcheck.c @@ -120,6 +120,30 @@ static const struct row rows[] = { " return \"hi\";\n" "};\n", "return: not assignable" }, + /* #31: alloc(value) returns (*T | nomem); bare LHS without `!` + * must be rejected — pre-fix, exprtype returned the seeded + * builtin's nil lhs and checkletassign silently passed. */ + { "fn caller() void = {\n" + " let p: *u8 = alloc(0u8: u8);\n" + "};\n", + "let: not assignable" }, + /* #31: with `!` the unwrap projects the *u8 success variant, + * so the let must be accepted. */ + { "fn caller() void = {\n" + " let p: *u8 = alloc(0u8: u8)!;\n" + "};\n", + NULL }, + /* #31: slice form `alloc([], n)` returns ([]u8 | nomem); the + * tagged → non-tagged rule must also fire for the bare LHS. */ + { "fn caller() void = {\n" + " let s: []u8 = alloc([], 16);\n" + "};\n", + "let: not assignable" }, + /* #31: with `!` the slice success variant projects to []u8. */ + { "fn caller() void = {\n" + " let s: []u8 = alloc([], 16)!;\n" + "};\n", + NULL }, }; int