diff --git a/bend2/comp.ts b/bend2/comp.ts index f2c29dad0..21a668ec3 100644 --- a/bend2/comp.ts +++ b/bend2/comp.ts @@ -163,7 +163,16 @@ const BOX: Lay = { ks: ["box"], arms: null }; const W64: Lay = { ks: ["w64"], arms: null }; -const WORDS: Record = { U32: W32, F32: W32, Nat: W64 }; +// A datatype's name indexes this table, so it has a null prototype: +// `__proto__` or `toString` would otherwise inherit a value that is not +// a Lay, like OPERATIONS and OPTIMIZED below. +const WORDS: Record = Object.setPrototypeOf( + { U32: W32, F32: W32, Nat: W64 }, null); + +// The printed cell kind of a datatype the runtime knows, 7 for any +// other: a datatype's name indexes it, so it is null-prototyped too. +const SHOWN: Record = Object.setPrototypeOf( + { U32: 0, F32: 1, Nat: 2, Char: 3, String: 4, Array: 6 }, null); // The widest flat datatype: the shader's Tri is 24 words. const WIDE = 256; @@ -1301,8 +1310,7 @@ function show_main(book: Bend.Book): Show | null { if (adt === null || adt.k === "IO.OP" || tld?.$ !== "ADT") { return refuse(); } - const kind = { U32: 0, F32: 1, Nat: 2, Char: 3, String: 4, Array: 6 }[adt.k] - ?? 7; + const kind = SHOWN[adt.k] ?? 7; const id = show.cells.push(kind) - 1; ids.set(key, id); const refs: [number, HTerm, Lay][] = []; diff --git a/tests/compile/proto_name_array_cell.bend b/tests/compile/proto_name_array_cell.bend new file mode 100644 index 000000000..f75d8a5bd --- /dev/null +++ b/tests/compile/proto_name_array_cell.bend @@ -0,0 +1,27 @@ +# an Array whose element datatype is named after an Object.prototype +# member: the cell's layout is read through the same name-indexed table +# by lay_el, which died in lay.ks.some rather than lay.ks.length. The +# array must reach the printed result: reading one element alone walks +# no layout and compiles on base +import Base + +type toString is Data: + E{v: U32} + +law main.head: + for a: Array + U32 + +def main.head(a): + match a: + case ALeaf{item}: + match item: + case E{v}: + v + case ANode{left, right}: + 0 + +def main() -> Array & U32: + ([E{4} : toString*1n], main.head([E{7} : toString*1n])) + +#|([E{4}], 7) diff --git a/tests/compile/proto_name_ctor_field.bend b/tests/compile/proto_name_ctor_field.bend new file mode 100644 index 000000000..dc2d1db8b --- /dev/null +++ b/tests/compile/proto_name_ctor_field.bend @@ -0,0 +1,23 @@ +# (control) a CONSTRUCTOR and a FIELD named after an Object.prototype +# member compile on base too: only a DATATYPE's name indexes WORDS and +# the printed-kind table, and a constructor's own emitted key is already +# a computed property. This pins the fix's scope -- it must not need to +# change, and must not break, the constructor and field name paths +import Base + +type Box is Data: + __proto__{toString: U32} + +law main.out: + for b: Box + U32 + +def main.out(b): + match b: + case __proto__{toString}: + toString + +def main() -> Box & U32: + (__proto__{5}, main.out(__proto__{5})) + +#|(__proto__{5}, 5) diff --git a/tests/compile/proto_name_datatype.bend b/tests/compile/proto_name_datatype.bend new file mode 100644 index 000000000..825c64de9 --- /dev/null +++ b/tests/compile/proto_name_datatype.bend @@ -0,0 +1,41 @@ +# a datatype named after an Object.prototype member is an ordinary +# datatype: the compiler's name-indexed tables must not answer an +# inherited member for it. WORDS gave __proto__ the prototype object +# instead of undefined, so lay_of returned a non-Lay and both emitters +# died in lay.ks.length; the printed-kind table gave toString a +# function, which left the emitted show descriptor holding no cells +import Base + +type __proto__ is Data: + P{v: U32} + +type constructor is Data: + C{p: __proto__} + +type toString is Data: + T{c: constructor, w: U32} + +law main.wrap: + for n: U32 + toString + +def main.wrap(n): + T{C{P{n}}, 7} + +law main.out: + for t: toString + U32 + +def main.out(t): + match t: + case T{c, w}: + match c: + case C{p}: + match p: + case P{v}: + (v + w : U32) + +def main() -> toString & U32: + (main.wrap(35), main.out(main.wrap(35))) + +#|(T{C{P{35}}, 7}, 42) diff --git a/tests/compile/proto_name_type_param.bend b/tests/compile/proto_name_type_param.bend new file mode 100644 index 000000000..3a3be16ac --- /dev/null +++ b/tests/compile/proto_name_type_param.bend @@ -0,0 +1,21 @@ +# a parametrized datatype named after an Object.prototype member: its +# name reaches the same name-indexed tables through the type argument, +# so it broke where the flat case did (lay_of -> lay.ks.length) +import Base + +type __proto__ is Data: + W{item: A} + +law main.out: + for w: __proto__ + U32 + +def main.out(w): + match w: + case W{item}: + item + +def main() -> __proto__ & U32: + (W{9}, main.out(W{9})) + +#|(W{9}, 9) diff --git a/tests/compile/proto_name_value_of.bend b/tests/compile/proto_name_value_of.bend new file mode 100644 index 000000000..6323050da --- /dev/null +++ b/tests/compile/proto_name_value_of.bend @@ -0,0 +1,33 @@ +# the Object.prototype members the issue does not name reach the same +# name-indexed tables: valueOf and hasOwnProperty inherit FUNCTIONS and +# propertyIsEnumerable one more, so the lookup is never undefined for +# them either. One nesting per name, so a table that answered only the +# three reported names would still die here +import Base + +type valueOf is Data: + V{n: U32} + +type hasOwnProperty is Data: + H{v: valueOf} + +type propertyIsEnumerable is Data: + Q{h: hasOwnProperty, m: U32} + +law main.out: + for q: propertyIsEnumerable + U32 + +def main.out(q): + match q: + case Q{h, m}: + match h: + case H{v}: + match v: + case V{n}: + (n + m : U32) + +def main() -> propertyIsEnumerable & U32: + (Q{H{V{20}}, 22}, main.out(Q{H{V{20}}, 22})) + +#|(Q{H{V{20}}, 22}, 42)