Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 17 additions & 13 deletions bend2/comp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2750,7 +2750,8 @@ function c_ids(src: string, m = ""): string {
const q = [m === "" ? k : m + ":" + k, k].find((q) => q in FL.book.ctrs
|| q in FL.book.tlds || FL.ids.has(p + "_" + q))
?? die(`${p}(${k}) names no constructor or def`);
return FL.js ? JSON.stringify(Bend.name_key(q)) : name_id(p + "_", q);
return FL.js ? JSON.stringify(p === "CID" && q in FL.book.ctrs
? js_tag(q) : Bend.name_key(q)) : name_id(p + "_", q);
});
}

Expand Down Expand Up @@ -2957,6 +2958,13 @@ function js_open(sc: Scope, x: Of<"Let">): HTerm {
: Bend.Var(emit_hold(sc, [js_expr(sc, v, null)], x.k[j])[0], 0)));
}

// Constructor tags name the constructor within its declaring file, not
// the namespace a loading book gives that file. Matches stay type-local;
// foreign effect identities and public def names keep their namespaces.
function js_tag(k: Name): string {
return k.slice(k.indexOf(":") + 1);
}

function js_key(n: Name): string {
return (n === "__proto__" ? `["${n}"]` : `"${n}"`) + ": ";
}
Expand Down Expand Up @@ -2996,7 +3004,7 @@ function js_expr(sc: Scope, tm: HTerm, ty0: HTerm | null): string {
return tpl(native[x.k].intr, exprs);
}
const fs = ctr_live(FL.book.ctrs[x.k]);
return `{$: "${Bend.name_key(x.k)}"${exprs.map((z, j) =>
return `{$: "${js_tag(x.k)}"${exprs.map((z, j) =>
", " + js_key(fs[j][1]) + z).join("")}}`;
}
case "Let": {
Expand Down Expand Up @@ -3082,7 +3090,7 @@ function js_match(sc: Scope, x: HTerm, ty: HTerm | null, args: string[]): void {
? [wd(j)] : [wd(j) + "[\"head\"]", wd(j + 1)].slice(0, e)]);
} else {
const native = OPTIMIZED[adt.k];
const is = (c: Name) => `${s}.$ === "${Bend.name_key(c)}"`;
const is = (c: Name) => `${s}.$ === "${js_tag(c)}"`;
lv = mat_ctrs(x, adt).map(([k, h]): [string, HTerm, string[]] =>
k === "" || k === "_" ? [k && mat_ops(is), h, [s]] : native === undefined
? [is(k), h, ctr_live(FL.book.ctrs[k]).map(([, f]) => `${s}["${f}"]`)]
Expand Down Expand Up @@ -3179,18 +3187,14 @@ function js_marshal(A: HTerm | null, out: boolean): string {
const f = js_marshal(B, out);
return f === "" ? [] : [put(f, "r", `"${n}"`)];
});
return [`case "${Bend.name_key(c.k)}": r = ${fs.length ? "{...v}"
return [`case "${js_tag(c.k)}": r = ${fs.length ? "{...v}"
: "v"};`, ...fs, "break;"].join(" ");
});
const tk = Bend.name_key(t.k);
const tags = cs.map((c) => Bend.name_key(c.k)).join(", ");
const tags = cs.map((c) => js_tag(c.k)).join(", ");
body.push("switch (v.$) {", ...arms,
// TODO(#1105): a tag is the key the loading book gives its constructor,
// so it depends on the root file; make tags the same in every book
`default: throw "bend: ${tk} has no tag " + v?.$ + " (its tags: ${
tags}); a tag names its constructor as the"
+ " loading file sees it, which a later version will make the same"
+ " everywhere (#1105)";`, "}");
tags})";`, "}");
}
FL.spins.push({ ...seg_new("", BOX, ["v"]), lines: [`function ${name}(v, q) {`,
"const own = q === undefined;", "if (own) q = [];", "let r;", ...body,
Expand Down Expand Up @@ -3247,7 +3251,7 @@ export function js_book(book: Bend.Book): string {
const show = show_main();
return `${lib}\n${RUNTIME_MAIN}\ncli(process.argv.slice(1));\nio_exit(${
js_sat("main")}, ${JSON.stringify(show && show.map((c) =>
typeof c === "string" ? Bend.name_key(c) : c))});`;
typeof c === "string" ? [js_tag(c), Bend.name_key(c)] : c))});`;
}

// RuntimeC
Expand Down Expand Up @@ -6131,9 +6135,9 @@ function show_val(D, d, v, chain) {
const fs = Object.values(typeof v === "boolean"
? { $: v ? "True" : "False" } : v);
let a = d + 3;
for (; D[a + 1] !== fs[0]; a += 4 + 2 * D[a + 2]) {}
for (; D[a + 1][0] !== fs[0]; a += 4 + 2 * D[a + 2]) {}
const o = "{[("[D[a + 3]];
let s = o === "{" ? fs[0] + "{" : chain === o ? "" : o;
let s = o === "{" ? D[a + 1][1] + "{" : chain === o ? "" : o;
for (const [j, f] of fs.slice(1).entries()) {
if (o === "[" ? j === 0 && chain === o : j > 0) {
s += ", ";
Expand Down
11 changes: 11 additions & 0 deletions tests/io/constructor_interchange.bend
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Base

def Host.constructor_interchange() -> IO(U32):
import "./constructor_interchange.js"

def main() -> IO(Unit):
do IO<Unit>:
status : U32 <- Host.constructor_interchange()
IO.print(U32.show(status))

#|0
79 changes: 79 additions & 0 deletions tests/io/constructor_interchange.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
function constructor_interchange() {
const fs = require("node:fs");
const path = require("node:path");
const child = require("node:child_process");
const dir = fs.mkdtempSync(path.join(process.cwd(), ".bend-constructor-interchange-"));
try {
fs.writeFileSync(path.join(dir, "a.bend"), `import Base

type Counter is Data:
Counter{count: Nat}

type Choice is Data:
Left{count: Nat}
Right{count: Nat}

type Flag is Data:
Off{}
On{}

def Counter.make() -> Counter: Counter{5n}
def Choice.left() -> Choice: Left{7n}
def Choice.right() -> Choice: Right{7n}
def Flag.off() -> Flag: Off{}
def Flag.on() -> Flag: On{}
`);
fs.writeFileSync(path.join(dir, "b.bend"), `import Base
import ./a.bend as A

def dec(c: A.Counter) -> Nat:
A.Counter{count} = c
Nat.sub(count, 1n)

def score(c: A.Choice) -> Nat:
match c:
case A.Left{count}: (count + 1n : Nat)
case A.Right{count}: (count + 2n : Nat)

def flag(c: A.Flag) -> U32:
match c:
case A.Off{}: 0
case A.On{}: 1
`);
const bun = typeof Bun !== "undefined";
const command = bun ? process.execPath : "bun";
const prefix = bun && Bun.main.startsWith("/$bunfs/")
? [] : [path.resolve("bend2/main.ts")];
for (const name of ["a", "b"]) {
const built = child.spawnSync(command,
[...prefix, path.join(dir, name + ".bend"),
"-o", path.join(dir, name + ".mjs")], {
encoding: "utf8",
env: { ...process.env, HOME: dir, BEND_ORIGIN: "http://127.0.0.1:9" },
});
if (built.status !== 0) {
throw built.error ?? new Error(built.stderr);
}
}
const A = require(path.join(dir, "a.mjs")).default;
const B = require(path.join(dir, "b.mjs")).default;
if (B.dec(A["Counter.make"]()) !== 4n
|| B.score(A["Choice.left"]()) !== 8n
|| B.score(A["Choice.right"]()) !== 9n
|| B.flag(A["Flag.off"]()) !== 0
|| B.flag(A["Flag.on"]()) !== 1) {
return 1;
}
let refused = false;
try {
B.dec({ $: "Bogus", count: 5n });
} catch (_) {
refused = true;
}
return refused ? 0 : 2;
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
}

io_eff(CID(Host.constructor_interchange), constructor_interchange);
34 changes: 5 additions & 29 deletions tests/io/marshal_tag_unknown.bend
Original file line number Diff line number Diff line change
@@ -1,35 +1,11 @@
# io (io_marshal_tag_unknown): a value the host hands in converts by
# its constructor tag, so a tag its type lacks fail-stops at the seam,
# naming the tag and the type's tags; it passed through unconverted, and
# its BigInt Nat failed far off (#1105)
import Base

type Box is Data:
Box{n: Nat}

law good:
IO(Box)

def good():
import "./marshal_tag_unknown.js"

law bad:
IO(Box)

def bad():
import "./marshal_tag_unknown.js"

def n_of(b: Box) -> Nat:
Box{n} = b
n
def Host.marshal_tag_unknown_test() -> IO(U32):
import "./marshal_tag_unknown_test.js"

def main() -> IO(Unit):
do IO<Unit>:
a : Box <- good()
u : Unit <- IO.print(Nat.show(Nat.add(n_of(a), 1n)))
b : Box <- bad()
IO.print(Nat.show(Nat.add(n_of(b), 1n)))
status : U32 <- Host.marshal_tag_unknown_test()
IO.print(U32.show(status))

#|6
#|bend: Box has no tag Bogus (its tags: Box); a tag names its constructor as the loading file sees it, which a later version will make the same everywhere (#1105)
#|exit 1
#|0
49 changes: 49 additions & 0 deletions tests/io/marshal_tag_unknown_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
function marshal_tag_unknown_test() {
const fs = require("node:fs");
const path = require("node:path");
const child = require("node:child_process");
const dir = fs.mkdtempSync(path.join(process.cwd(), ".bend-tag-result-"));
try {
fs.copyFileSync(path.resolve("tests/io/marshal_tag_unknown.js"),
path.join(dir, "marshal_tag_unknown.js"));
fs.writeFileSync(path.join(dir, "main.bend"), `import Base

type Box is Data:
Box{n: Nat}

def good() -> IO(Box):
import "./marshal_tag_unknown.js"

def bad() -> IO(Box):
import "./marshal_tag_unknown.js"

def n_of(b: Box) -> Nat:
Box{n} = b
n

def main() -> IO(Unit):
do IO<Unit>:
a : Box <- good()
u : Unit <- IO.print(Nat.show(Nat.add(n_of(a), 1n)))
b : Box <- bad()
IO.print(Nat.show(Nat.add(n_of(b), 1n)))
`);
const bun = typeof Bun !== "undefined";
const command = bun ? process.execPath : "bun";
const prefix = bun && Bun.main.startsWith("/$bunfs/")
? [] : [path.resolve("bend2/main.ts")];
const ran = child.spawnSync(command,
[...prefix, path.join(dir, "main.bend")], {
encoding: "utf8",
env: { ...process.env, HOME: dir, BEND_ORIGIN: "http://127.0.0.1:9" },
});
if (ran.error) {
throw ran.error;
}
return ran.status === 1 && ran.stdout.trim() === "6" ? 0 : 1;
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
}

io_eff(CID(Host.marshal_tag_unknown_test), marshal_tag_unknown_test);