w6c+wwstage: reject untyped empty-[] alloc — require context, loud cannot-infer (#3 B', subsumes #5)
An empty `[]` carries no element type; ww gets it only from a let annotation (the #45 retype). Both stages used to silently default the element to u8, and in value-form positions (return / call-arg) the lowering miscompiled — malloc(8) ignoring n, a 16B *u8|nomem where a 24B slice was expected (#5). Now every empty alloc that isn't a let-annotated binding fails to infer with a loud error, aligning ww DOWN to harec (ref/harec/src/check.c:1801-1802). Mechanism: clet / checkletassign flags the single alloc call node that a `let x: []T =` rescues (save/restore around the init walk); the alloc branch errors on any empty alloc that isn't that node. The #45 wide-T retype path is kept. wwstage needs an extra not-yet-stamped guard because resolvewalk re-types value nodes context-free after checkletassign. Tests: negative cstage-driver 729 (table-driven: bare-let, return, call-arg, assignment) + positive @test in attest_pass.ww exercising the u8 and the wide-i32 (#45) paths at runtime. Both stages reject symmetrically; byte-id verified on []u8 and []i32.
This commit is contained in:
@@ -10444,6 +10444,12 @@ type checker = struct {
|
||||
file: *node, // N_FILE root; used by checkmoduleshadow
|
||||
// to consult the declaring source's own
|
||||
// `use` directives.
|
||||
allococtx: *node, // #3/B': the one empty alloc([], n) call node
|
||||
// with let-declared slice context this walk;
|
||||
// any other empty alloc has no element hint
|
||||
// and must fail to infer (harec
|
||||
// check.c:1801). Set by checkletassign
|
||||
// around its exprtype, nil elsewhere.
|
||||
};
|
||||
|
||||
// seedprimitives — install the built-in type names so `i32`, `str`,
|
||||
@@ -12810,6 +12816,23 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
if (e.list.list == nil) {
|
||||
if (e.list.next != nil) {
|
||||
if (e.list.next.next == nil) {
|
||||
// B' (#3): an empty `[]` has no element type;
|
||||
// ww gets it only from a let annotation (the
|
||||
// #45 retype). Any other empty alloc has no
|
||||
// hint, so refuse to guess rather than default
|
||||
// to u8 (was a silent u8-default + #5 value-form
|
||||
// miscompile). Align DOWN to harec, which errors
|
||||
// the same way: ref/harec/src/check.c:1801-1802.
|
||||
// The e.type_ != nil guard is the wwstage-only half:
|
||||
// resolvewalk re-types every value node context-free
|
||||
// (L648) AFTER checkletassign already rescued+stamped
|
||||
// this node, so a stamped node is a rescued one — do
|
||||
// not re-error it. cstage cexpr is single-visit (clet
|
||||
// only) so it needs only the allococtx check.
|
||||
if (e != c.allococtx && e.type_ == nil) {
|
||||
deffolderr(c, e, "cannot infer slice element type for alloc([], n) without a type hint; annotate the binding, e.g. let x: []T = alloc([], n)");
|
||||
return nil;
|
||||
};
|
||||
let sl: *node = newnode(nkind.N_TSLICE, "", 0, 0);
|
||||
sl.lhs = mktname(c, "u8");
|
||||
let nome: *node = mktname(c, "nomem");
|
||||
@@ -14322,7 +14345,40 @@ fn checkletassign(c: *checker, n: *node) void = {
|
||||
if (n.rhs == nil) { return; }; // no init
|
||||
// hint = nil for A.6.0; A.6.1 will pass n.lhs once STRUCTLIT/ARRLIT
|
||||
// arms consume it. Plumbing-only at this point.
|
||||
// B' (#3): a `let x: []T = alloc([], n)` is the one context that lets
|
||||
// the empty alloc infer its element type (the #45 retype runs AFTER
|
||||
// exprtype, so flag the exact call node up front; exprtype errors on
|
||||
// any empty alloc that isn't this one). Peel the same ?/! wrapper #45
|
||||
// peels so the flagged node matches.
|
||||
let octx: *node = nil;
|
||||
if (n.lhs != nil && n.lhs.kind == nkind.N_TSLICE) {
|
||||
let inner: *node = n.rhs;
|
||||
if (inner.kind == nkind.N_TRYPROP) {
|
||||
inner = inner.lhs;
|
||||
} else { if (inner.kind == nkind.N_TRYUNW) {
|
||||
inner = inner.lhs;
|
||||
}; };
|
||||
if (inner != nil && inner.kind == nkind.N_CALL) {
|
||||
let callee: *node = inner.lhs;
|
||||
let a0: *node = inner.list;
|
||||
let a1: *node = nil;
|
||||
let a2: *node = nil;
|
||||
if (a0 != nil) { a1 = a0.next; };
|
||||
if (a1 != nil) { a2 = a1.next; };
|
||||
if (callee != nil
|
||||
&& callee.kind == nkind.N_IDENT
|
||||
&& streq(callee.str, "alloc")
|
||||
&& a0 != nil && a0.kind == nkind.N_ARRLIT
|
||||
&& a0.list == nil
|
||||
&& a1 != nil && a2 == nil) {
|
||||
octx = inner;
|
||||
};
|
||||
};
|
||||
};
|
||||
let savedoctx: *node = c.allococtx;
|
||||
c.allococtx = octx;
|
||||
let src: *node = exprtype(c, n.rhs, nil);
|
||||
c.allococtx = savedoctx;
|
||||
// Inferred binding (`let r = expr;`, no type annotation). Mirror
|
||||
// cstage cmd/wcc/check.c:1477 clet `if (t == NULL && initt) t =
|
||||
// type_default(initt);` and ref/harec/src/check.c:1422
|
||||
@@ -14779,6 +14835,7 @@ export fn checkinit(c: *checker, tc: *tctx) void = {
|
||||
let empty: str;
|
||||
c.curmod = empty;
|
||||
c.file = nil;
|
||||
c.allococtx = nil;
|
||||
seedprimitives(c);
|
||||
};
|
||||
|
||||
|
||||
@@ -39,6 +39,12 @@ type checker = struct {
|
||||
file: *node, // N_FILE root; used by checkmoduleshadow
|
||||
// to consult the declaring source's own
|
||||
// `use` directives.
|
||||
allococtx: *node, // #3/B': the one empty alloc([], n) call node
|
||||
// with let-declared slice context this walk;
|
||||
// any other empty alloc has no element hint
|
||||
// and must fail to infer (harec
|
||||
// check.c:1801). Set by checkletassign
|
||||
// around its exprtype, nil elsewhere.
|
||||
};
|
||||
|
||||
// seedprimitives — install the built-in type names so `i32`, `str`,
|
||||
@@ -2405,6 +2411,23 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
if (e.list.list == nil) {
|
||||
if (e.list.next != nil) {
|
||||
if (e.list.next.next == nil) {
|
||||
// B' (#3): an empty `[]` has no element type;
|
||||
// ww gets it only from a let annotation (the
|
||||
// #45 retype). Any other empty alloc has no
|
||||
// hint, so refuse to guess rather than default
|
||||
// to u8 (was a silent u8-default + #5 value-form
|
||||
// miscompile). Align DOWN to harec, which errors
|
||||
// the same way: ref/harec/src/check.c:1801-1802.
|
||||
// The e.type_ != nil guard is the wwstage-only half:
|
||||
// resolvewalk re-types every value node context-free
|
||||
// (L648) AFTER checkletassign already rescued+stamped
|
||||
// this node, so a stamped node is a rescued one — do
|
||||
// not re-error it. cstage cexpr is single-visit (clet
|
||||
// only) so it needs only the allococtx check.
|
||||
if (e != c.allococtx && e.type_ == nil) {
|
||||
deffolderr(c, e, "cannot infer slice element type for alloc([], n) without a type hint; annotate the binding, e.g. let x: []T = alloc([], n)");
|
||||
return nil;
|
||||
};
|
||||
let sl: *node = newnode(nkind.N_TSLICE, "", 0, 0);
|
||||
sl.lhs = mktname(c, "u8");
|
||||
let nome: *node = mktname(c, "nomem");
|
||||
@@ -3917,7 +3940,40 @@ fn checkletassign(c: *checker, n: *node) void = {
|
||||
if (n.rhs == nil) { return; }; // no init
|
||||
// hint = nil for A.6.0; A.6.1 will pass n.lhs once STRUCTLIT/ARRLIT
|
||||
// arms consume it. Plumbing-only at this point.
|
||||
// B' (#3): a `let x: []T = alloc([], n)` is the one context that lets
|
||||
// the empty alloc infer its element type (the #45 retype runs AFTER
|
||||
// exprtype, so flag the exact call node up front; exprtype errors on
|
||||
// any empty alloc that isn't this one). Peel the same ?/! wrapper #45
|
||||
// peels so the flagged node matches.
|
||||
let octx: *node = nil;
|
||||
if (n.lhs != nil && n.lhs.kind == nkind.N_TSLICE) {
|
||||
let inner: *node = n.rhs;
|
||||
if (inner.kind == nkind.N_TRYPROP) {
|
||||
inner = inner.lhs;
|
||||
} else { if (inner.kind == nkind.N_TRYUNW) {
|
||||
inner = inner.lhs;
|
||||
}; };
|
||||
if (inner != nil && inner.kind == nkind.N_CALL) {
|
||||
let callee: *node = inner.lhs;
|
||||
let a0: *node = inner.list;
|
||||
let a1: *node = nil;
|
||||
let a2: *node = nil;
|
||||
if (a0 != nil) { a1 = a0.next; };
|
||||
if (a1 != nil) { a2 = a1.next; };
|
||||
if (callee != nil
|
||||
&& callee.kind == nkind.N_IDENT
|
||||
&& streq(callee.str, "alloc")
|
||||
&& a0 != nil && a0.kind == nkind.N_ARRLIT
|
||||
&& a0.list == nil
|
||||
&& a1 != nil && a2 == nil) {
|
||||
octx = inner;
|
||||
};
|
||||
};
|
||||
};
|
||||
let savedoctx: *node = c.allococtx;
|
||||
c.allococtx = octx;
|
||||
let src: *node = exprtype(c, n.rhs, nil);
|
||||
c.allococtx = savedoctx;
|
||||
// Inferred binding (`let r = expr;`, no type annotation). Mirror
|
||||
// cstage cmd/wcc/check.c:1477 clet `if (t == NULL && initt) t =
|
||||
// type_default(initt);` and ref/harec/src/check.c:1422
|
||||
@@ -4374,6 +4430,7 @@ export fn checkinit(c: *checker, tc: *tctx) void = {
|
||||
let empty: str;
|
||||
c.curmod = empty;
|
||||
c.file = nil;
|
||||
c.allococtx = nil;
|
||||
seedprimitives(c);
|
||||
};
|
||||
|
||||
|
||||
@@ -10444,6 +10444,12 @@ type checker = struct {
|
||||
file: *node, // N_FILE root; used by checkmoduleshadow
|
||||
// to consult the declaring source's own
|
||||
// `use` directives.
|
||||
allococtx: *node, // #3/B': the one empty alloc([], n) call node
|
||||
// with let-declared slice context this walk;
|
||||
// any other empty alloc has no element hint
|
||||
// and must fail to infer (harec
|
||||
// check.c:1801). Set by checkletassign
|
||||
// around its exprtype, nil elsewhere.
|
||||
};
|
||||
|
||||
// seedprimitives — install the built-in type names so `i32`, `str`,
|
||||
@@ -12810,6 +12816,23 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
if (e.list.list == nil) {
|
||||
if (e.list.next != nil) {
|
||||
if (e.list.next.next == nil) {
|
||||
// B' (#3): an empty `[]` has no element type;
|
||||
// ww gets it only from a let annotation (the
|
||||
// #45 retype). Any other empty alloc has no
|
||||
// hint, so refuse to guess rather than default
|
||||
// to u8 (was a silent u8-default + #5 value-form
|
||||
// miscompile). Align DOWN to harec, which errors
|
||||
// the same way: ref/harec/src/check.c:1801-1802.
|
||||
// The e.type_ != nil guard is the wwstage-only half:
|
||||
// resolvewalk re-types every value node context-free
|
||||
// (L648) AFTER checkletassign already rescued+stamped
|
||||
// this node, so a stamped node is a rescued one — do
|
||||
// not re-error it. cstage cexpr is single-visit (clet
|
||||
// only) so it needs only the allococtx check.
|
||||
if (e != c.allococtx && e.type_ == nil) {
|
||||
deffolderr(c, e, "cannot infer slice element type for alloc([], n) without a type hint; annotate the binding, e.g. let x: []T = alloc([], n)");
|
||||
return nil;
|
||||
};
|
||||
let sl: *node = newnode(nkind.N_TSLICE, "", 0, 0);
|
||||
sl.lhs = mktname(c, "u8");
|
||||
let nome: *node = mktname(c, "nomem");
|
||||
@@ -14322,7 +14345,40 @@ fn checkletassign(c: *checker, n: *node) void = {
|
||||
if (n.rhs == nil) { return; }; // no init
|
||||
// hint = nil for A.6.0; A.6.1 will pass n.lhs once STRUCTLIT/ARRLIT
|
||||
// arms consume it. Plumbing-only at this point.
|
||||
// B' (#3): a `let x: []T = alloc([], n)` is the one context that lets
|
||||
// the empty alloc infer its element type (the #45 retype runs AFTER
|
||||
// exprtype, so flag the exact call node up front; exprtype errors on
|
||||
// any empty alloc that isn't this one). Peel the same ?/! wrapper #45
|
||||
// peels so the flagged node matches.
|
||||
let octx: *node = nil;
|
||||
if (n.lhs != nil && n.lhs.kind == nkind.N_TSLICE) {
|
||||
let inner: *node = n.rhs;
|
||||
if (inner.kind == nkind.N_TRYPROP) {
|
||||
inner = inner.lhs;
|
||||
} else { if (inner.kind == nkind.N_TRYUNW) {
|
||||
inner = inner.lhs;
|
||||
}; };
|
||||
if (inner != nil && inner.kind == nkind.N_CALL) {
|
||||
let callee: *node = inner.lhs;
|
||||
let a0: *node = inner.list;
|
||||
let a1: *node = nil;
|
||||
let a2: *node = nil;
|
||||
if (a0 != nil) { a1 = a0.next; };
|
||||
if (a1 != nil) { a2 = a1.next; };
|
||||
if (callee != nil
|
||||
&& callee.kind == nkind.N_IDENT
|
||||
&& streq(callee.str, "alloc")
|
||||
&& a0 != nil && a0.kind == nkind.N_ARRLIT
|
||||
&& a0.list == nil
|
||||
&& a1 != nil && a2 == nil) {
|
||||
octx = inner;
|
||||
};
|
||||
};
|
||||
};
|
||||
let savedoctx: *node = c.allococtx;
|
||||
c.allococtx = octx;
|
||||
let src: *node = exprtype(c, n.rhs, nil);
|
||||
c.allococtx = savedoctx;
|
||||
// Inferred binding (`let r = expr;`, no type annotation). Mirror
|
||||
// cstage cmd/wcc/check.c:1477 clet `if (t == NULL && initt) t =
|
||||
// type_default(initt);` and ref/harec/src/check.c:1422
|
||||
@@ -14779,6 +14835,7 @@ export fn checkinit(c: *checker, tc: *tctx) void = {
|
||||
let empty: str;
|
||||
c.curmod = empty;
|
||||
c.file = nil;
|
||||
c.allococtx = nil;
|
||||
seedprimitives(c);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user