From f3aeb9c3d7aded05cd9109956952ba0d0ec35e92 Mon Sep 17 00:00:00 2001 From: oxura Date: Sun, 11 Oct 2026 21:15:28 +0600 Subject: [PATCH 1/3] fix(cli): resolve and run hub imports in independent checkup scopes --- README.md | 2 ++ bend2/comp.ts | 14 +++++----- bend2/main.ts | 75 +++++++++++++++++++++++++++++++++++++++++---------- gates/ping.ts | 20 ++++++++++++++ 4 files changed, 90 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 02737ec3e7..563a12e96c 100644 --- a/README.md +++ b/README.md @@ -206,6 +206,8 @@ def add_zero(x): - Formalization: [bendtt.lean](bend2/bendtt.lean), the kernel of `--verdict` and its proofs, in Lean. - Benches: [bench/](bench), every bench used to make the charts above. - Formatter: [bend-fmt-lsp](tools/bend-fmt-lsp), a formatting-only Bend 2 language server. +- Independent module smoke checks: `bend aggregation.bend --checkup` checks + and runs local, content-hash and named hub imports in their own scope. - Community language server: [bend2-lsp](https://github.com/don2e4/bend2-lsp), with formatting, diagnostics, and hover. - Community workshop: [Bend 2 pocket workshop](https://np.github.io/bend-workshop/), the checker and JS compiler in one HTML page, with goals and proof tools, phone first. diff --git a/bend2/comp.ts b/bend2/comp.ts index fa3f69f898..04b8314c46 100644 --- a/bend2/comp.ts +++ b/bend2/comp.ts @@ -1294,8 +1294,8 @@ function io_base(book: Bend.Book, t: HTerm): HTerm[] | null { return h.$ === "Ref" && h.k === "IO" ? xs : null; } -export function io_type(book: Bend.Book): HTerm | null { - const main = book.tlds["main"]; +export function io_type(book: Bend.Book, entry = "main"): HTerm | null { + const main = book.tlds[entry]; const xs = main?.$ === "Def" ? io_base(book, main.T) : null; if (xs && def_foreign(main)) { die("main must be a filled def: a foreign main cannot anchor IO"); @@ -1303,9 +1303,9 @@ export function io_type(book: Bend.Book): HTerm | null { return xs?.length === 1 ? xs[0] : null; } -export function io_run(book: Bend.Book, args: string[]): number { - const src = `${js_lib(book)}\n${RUNTIME_MAIN}\ncli_args = ${ - JSON.stringify(args)};\nreturn io_run(${js_sat("main")});`; +export function io_run(book: Bend.Book, args: string[], entry = "main"): number { + const src = `${js_lib(book, false, entry)}\n${RUNTIME_MAIN}\ncli_args = ${ + JSON.stringify(args)};\nreturn io_run(${js_sat(entry)});`; return new Function("require", src)(import.meta.require); } @@ -3213,14 +3213,14 @@ function js_host(k: Name): string { // A module (for the .bend loader and -o .mjs) roots and exports each // def a host can call. -export function js_lib(book: Bend.Book, mod = false): string { +export function js_lib(book: Bend.Book, mod = false, entry = "main"): string { FL = file_new(book, true); const outs = !mod ? null : [...new Set(FL.book.order)].filter((k) => { const t = FL.book.tlds[k]; return done_live(t) && t.b !== true && t.x === 0 && io_base(FL.book, t.T) === null; }); - file_book(outs ?? ["main"]); + file_book(outs ?? [entry]); const sc = scope_new(); for (const [k, def] of done_defs(fun_runs)) { memo_gc(); diff --git a/bend2/main.ts b/bend2/main.ts index f262925086..572cec5183 100755 --- a/bend2/main.ts +++ b/bend2/main.ts @@ -325,10 +325,15 @@ async function cli_file(args: string[]): Promise { } } -// cli_checkup checks and runs each import of the file alone (Base read -// once, seeded into every module that imports it); one that fails fails it. +// cli_checkup checks and runs imports independently, reusing checked Base +// when the source is already available; one failure fails the command. async function cli_checkup(file: string): Promise { const base = await book_read(BASE); + const lib = path.resolve(process.env.BEND_LIB + ?? path.join(os.homedir(), ".bend", "lib")); + const hub = /^0x[0-9a-f]+\//; + const plain = /^(\/|(\.\.\/)*)([A-Za-z_][\w-]*\/)*[A-Za-z_][\w-]*$/; + const hash = /^0x[0-9a-f]{32}$/; let bad = false; for (const raw of fs.readFileSync(file, "utf8").split("\n")) { const m = /^import\s+(\S+)\s+as\s+[A-Za-z_][A-Za-z0-9_]*\s*$/ @@ -336,13 +341,55 @@ async function cli_checkup(file: string): Promise { if (m === null) { continue; } - const at = m[1].startsWith("/") ? m[1] - : path.join(path.dirname(file), m[1]); cli_say(1, "--- " + m[1] + " ---\n"); let code = 1; try { - const own = /^import Base$/m.test(fs.readFileSync(at, "utf8")); - code = book_run(await book_read(at, own ? base : undefined), [at]); + let at = m[1]; + const nv = /^([^/]*@[^/]*)\//.exec(at); + if (nv !== null) { + if (!Bend.NAMED.test(nv[1])) { + throw "Error: a package named @"; + } + const cache = path.join(lib, "names", nv[1]); + let got = fs.existsSync(cache) ? fs.readFileSync(cache, "utf8").trim() : ""; + if (!hash.test(got)) { + const res = await fetch(Bend.BEND_HUB + "/name/" + nv[1], + { signal: AbortSignal.timeout(30000) }).catch(() => null); + got = res?.ok ? (await res.text().catch(() => "")).trim() : ""; + if (!hash.test(got)) { + throw "Error: no package named " + nv[1] + " on " + Bend.BEND_HUB; + } + fs.mkdirSync(path.dirname(cache), { recursive: true }); + fs.writeFileSync(cache, got + "\n"); + } + at = got + at.slice(nv[1].length); + } + let ns = ""; + if (hub.test(at)) { + const rel = path.posix.normalize(at); + if (!at.endsWith(".bend") || !plain.test(at.replace(hub, "").slice(0, -5)) + || !hub.test(rel) || !plain.test(rel.replace(hub, "").slice(0, -5))) { + throw "Error: a hub import of a .bend file in its package"; + } + at = path.join(lib, rel); + ns = rel.slice(0, -5); + if (fs.existsSync(at)) { + at = fs.realpathSync(at); + ns = path.posix.relative(fs.realpathSync(lib), at).slice(0, -5); + if (!hub.test(ns) || !plain.test(ns.replace(hub, ""))) { + throw "Error: a hub file inside the library cache"; + } + } + } else { + at = path.resolve(path.dirname(file), at); + } + const own = fs.existsSync(at) && /^import Base$/m.test(fs.readFileSync(at, "utf8")); + const book = await book_read(at, own ? base : undefined, new Map(), ns); + if (ns && path.posix.relative(fs.realpathSync(lib), + fs.realpathSync(at)).slice(0, -5) !== ns) { + throw "Error: a hub file inside the library cache"; + } + code = book_run(book, [at], ns ? ns + ":main" : "main"); } catch (e) { cli_say(2, book_err(e) + "\n"); } @@ -888,13 +935,13 @@ function cli_fail(msg: string): never { // ==== async function book_read(file: string, base?: Bend.Book, - seen = new Map()): Promise { + seen = new Map(), ns = ""): Promise { const book = base === undefined ? Bend.book_nil() : book_seed(base); if (base !== undefined) { seen.set(BASE, ""); } try { - await Bend.book_load(book, file, "", seen); + await Bend.book_load(book, file, ns, seen); const laws = path.join(path.dirname(file), "LAWS.bend"); if (path.basename(file) === "PROOF.bend" && fs.existsSync(laws) && !seen.has(fs.realpathSync(laws))) { @@ -929,19 +976,19 @@ function book_seed(base: Bend.Book): Bend.Book { return book; } -function book_main(book: Bend.Book): Bend.Def | null { - const main = book.tlds["main"]; +function book_main(book: Bend.Book, entry = "main"): Bend.Def | null { + const main = book.tlds[entry]; return main === undefined || main.$ !== "Def" || (main.v === null && main.i === undefined) ? null : main; } -function book_run(book: Bend.Book, argv: string[]): number { - const main = book_main(book); +function book_run(book: Bend.Book, argv: string[], entry = "main"): number { + const main = book_main(book, entry); if (main === null) { return cli_verdict(book, false); } - if (Comp.io_type(book) !== null) { - return Comp.io_run(book, argv); + if (Comp.io_type(book, entry) !== null) { + return Comp.io_run(book, argv, entry); } const snf = Bend.term_snf(book, main.v as Bend.HTerm); cli_say(1, Bend.term_show(Bend.term_lower(snf)) + "\n"); diff --git a/gates/ping.ts b/gates/ping.ts index a72189e0ac..3f0105d58a 100644 --- a/gates/ping.ts +++ b/gates/ping.ts @@ -325,6 +325,26 @@ try { const got = await bend_closed(args); check("a closed reader keeps success: " + args.join(" "), got.code === 0); } + const checkup_files = { + "pure.bend": "import Base\ndef main() -> Nat:\n 5n\n", + "io.bend": "import Base\ndef main() -> IO(Unit):\n IO.print(\"hub-io\")\n", + }; + const checkup_pkg = pkg_hash(checkup_files); + const checkup_cache = path.join(TMP, "checkup-lib"); + fs.mkdirSync(path.join(checkup_cache, checkup_pkg), { recursive: true }); + fs.mkdirSync(path.join(checkup_cache, "names")); + for (const [name, text] of Object.entries(checkup_files)) { + fs.writeFileSync(path.join(checkup_cache, checkup_pkg, name), text); + } + fs.writeFileSync(path.join(checkup_cache, "names", "checkup@1.0.0.0"), checkup_pkg); + const checkup_hub = path.join(TMP, "checkup-hub.bend"); + fs.writeFileSync(checkup_hub, "import " + checkup_pkg + + "/pure.bend as Pure\nimport checkup@1.0.0.0/io.bend as Effect\n"); + const checkup_result = await bend([checkup_hub, "--checkup"], + { BEND_LIB: checkup_cache, BEND_NO_TELEMETRY: "1" }); + check("hash and named hub checkups execute pure and effectful entries", + checkup_result.code === 0 && checkup_result.out.includes("\n5n\n") + && checkup_result.out.endsWith("hub-io\n")); const two = "import Base\ndef two() -> Nat:\n 2n\n"; const use = (at: string) => "import Base\nimport ./" + at + " as T\ndef main() -> Nat:\n T.two\n"; From 67d6f8e451883f8ca9e1705bff508220b27dd8a9 Mon Sep 17 00:00:00 2001 From: Nicolas Abril Date: Sun, 11 Oct 2026 23:42:29 +0200 Subject: [PATCH 2/3] A hub checkup imports its module from a one-line root, so the loader resolves it --checkup wrote each hub import into a temporary root (import as Hub) and loads that, so name lookup, fetching, hash checks and library confinement are the loader's own; the module keeps its hub namespace and its main becomes the root's main. A first parse with Base marked seen finds the module's file to see whether it imports Base, so Base is seeded as for local imports. The entry parameter through io_type, io_run, js_lib, book_main and book_run and the ns parameter of book_read go: comp.ts is unchanged, main.ts is +246 tokens over main instead of +608. --- bend2/comp.ts | 14 ++++---- bend2/main.ts | 91 ++++++++++++++++++--------------------------------- 2 files changed, 39 insertions(+), 66 deletions(-) diff --git a/bend2/comp.ts b/bend2/comp.ts index 04b8314c46..fa3f69f898 100644 --- a/bend2/comp.ts +++ b/bend2/comp.ts @@ -1294,8 +1294,8 @@ function io_base(book: Bend.Book, t: HTerm): HTerm[] | null { return h.$ === "Ref" && h.k === "IO" ? xs : null; } -export function io_type(book: Bend.Book, entry = "main"): HTerm | null { - const main = book.tlds[entry]; +export function io_type(book: Bend.Book): HTerm | null { + const main = book.tlds["main"]; const xs = main?.$ === "Def" ? io_base(book, main.T) : null; if (xs && def_foreign(main)) { die("main must be a filled def: a foreign main cannot anchor IO"); @@ -1303,9 +1303,9 @@ export function io_type(book: Bend.Book, entry = "main"): HTerm | null { return xs?.length === 1 ? xs[0] : null; } -export function io_run(book: Bend.Book, args: string[], entry = "main"): number { - const src = `${js_lib(book, false, entry)}\n${RUNTIME_MAIN}\ncli_args = ${ - JSON.stringify(args)};\nreturn io_run(${js_sat(entry)});`; +export function io_run(book: Bend.Book, args: string[]): number { + const src = `${js_lib(book)}\n${RUNTIME_MAIN}\ncli_args = ${ + JSON.stringify(args)};\nreturn io_run(${js_sat("main")});`; return new Function("require", src)(import.meta.require); } @@ -3213,14 +3213,14 @@ function js_host(k: Name): string { // A module (for the .bend loader and -o .mjs) roots and exports each // def a host can call. -export function js_lib(book: Bend.Book, mod = false, entry = "main"): string { +export function js_lib(book: Bend.Book, mod = false): string { FL = file_new(book, true); const outs = !mod ? null : [...new Set(FL.book.order)].filter((k) => { const t = FL.book.tlds[k]; return done_live(t) && t.b !== true && t.x === 0 && io_base(FL.book, t.T) === null; }); - file_book(outs ?? [entry]); + file_book(outs ?? ["main"]); const sc = scope_new(); for (const [k, def] of done_defs(fun_runs)) { memo_gc(); diff --git a/bend2/main.ts b/bend2/main.ts index 572cec5183..721e7b54d4 100755 --- a/bend2/main.ts +++ b/bend2/main.ts @@ -325,15 +325,15 @@ async function cli_file(args: string[]): Promise { } } -// cli_checkup checks and runs imports independently, reusing checked Base -// when the source is already available; one failure fails the command. +// cli_checkup checks and runs each import of the file alone (Base read +// once, seeded into every module that imports it); one that fails fails it. +// A hub import loads from a root that imports only it, so it resolves as any +// import does and keeps its namespace; a first parse (Base skipped) finds its +// file, to see if it imports Base, and its main is the root's main. async function cli_checkup(file: string): Promise { const base = await book_read(BASE); - const lib = path.resolve(process.env.BEND_LIB - ?? path.join(os.homedir(), ".bend", "lib")); - const hub = /^0x[0-9a-f]+\//; - const plain = /^(\/|(\.\.\/)*)([A-Za-z_][\w-]*\/)*[A-Za-z_][\w-]*$/; - const hash = /^0x[0-9a-f]{32}$/; + const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "bend-")); + const hub = path.join(tmp, "hub.bend"); let bad = false; for (const raw of fs.readFileSync(file, "utf8").split("\n")) { const m = /^import\s+(\S+)\s+as\s+[A-Za-z_][A-Za-z0-9_]*\s*$/ @@ -341,55 +341,27 @@ async function cli_checkup(file: string): Promise { if (m === null) { continue; } + const at = /^(0x[0-9a-f]+|[^/]*@[^/]*)\//.test(m[1]) ? hub + : path.resolve(path.dirname(file), m[1]); cli_say(1, "--- " + m[1] + " ---\n"); let code = 1; try { - let at = m[1]; - const nv = /^([^/]*@[^/]*)\//.exec(at); - if (nv !== null) { - if (!Bend.NAMED.test(nv[1])) { - throw "Error: a package named @"; - } - const cache = path.join(lib, "names", nv[1]); - let got = fs.existsSync(cache) ? fs.readFileSync(cache, "utf8").trim() : ""; - if (!hash.test(got)) { - const res = await fetch(Bend.BEND_HUB + "/name/" + nv[1], - { signal: AbortSignal.timeout(30000) }).catch(() => null); - got = res?.ok ? (await res.text().catch(() => "")).trim() : ""; - if (!hash.test(got)) { - throw "Error: no package named " + nv[1] + " on " + Bend.BEND_HUB; - } - fs.mkdirSync(path.dirname(cache), { recursive: true }); - fs.writeFileSync(cache, got + "\n"); - } - at = got + at.slice(nv[1].length); - } - let ns = ""; - if (hub.test(at)) { - const rel = path.posix.normalize(at); - if (!at.endsWith(".bend") || !plain.test(at.replace(hub, "").slice(0, -5)) - || !hub.test(rel) || !plain.test(rel.replace(hub, "").slice(0, -5))) { - throw "Error: a hub import of a .bend file in its package"; - } - at = path.join(lib, rel); - ns = rel.slice(0, -5); - if (fs.existsSync(at)) { - at = fs.realpathSync(at); - ns = path.posix.relative(fs.realpathSync(lib), at).slice(0, -5); - if (!hub.test(ns) || !plain.test(ns.replace(hub, ""))) { - throw "Error: a hub file inside the library cache"; - } - } - } else { - at = path.resolve(path.dirname(file), at); + fs.writeFileSync(hub, "import " + m[1] + " as Hub\n"); + let seen = new Map([[BASE, ""]]); + if (at === hub) { + await Bend.book_load(Bend.book_nil(), hub, "", seen).catch((e) => { + throw new Check_Fail(e); + }); } - const own = fs.existsSync(at) && /^import Base$/m.test(fs.readFileSync(at, "utf8")); - const book = await book_read(at, own ? base : undefined, new Map(), ns); - if (ns && path.posix.relative(fs.realpathSync(lib), - fs.realpathSync(at)).slice(0, -5) !== ns) { - throw "Error: a hub file inside the library cache"; + const [, , got = at] = seen.keys(); + const own = /^import Base$/m.test(fs.readFileSync(got, "utf8")); + seen = new Map(); + const book = await book_read(at, own ? base : undefined, seen); + const main = book.tlds[seen.get(got) + ":main"]; + if (at === hub && main !== undefined) { + book.tlds["main"] = main; } - code = book_run(book, [at], ns ? ns + ":main" : "main"); + code = book_run(book, [at]); } catch (e) { cli_say(2, book_err(e) + "\n"); } @@ -398,6 +370,7 @@ async function cli_checkup(file: string): Promise { bad = true; } } + fs.rmSync(tmp, { recursive: true }); if (bad) { process.exit(1); } @@ -935,13 +908,13 @@ function cli_fail(msg: string): never { // ==== async function book_read(file: string, base?: Bend.Book, - seen = new Map(), ns = ""): Promise { + seen = new Map()): Promise { const book = base === undefined ? Bend.book_nil() : book_seed(base); if (base !== undefined) { seen.set(BASE, ""); } try { - await Bend.book_load(book, file, ns, seen); + await Bend.book_load(book, file, "", seen); const laws = path.join(path.dirname(file), "LAWS.bend"); if (path.basename(file) === "PROOF.bend" && fs.existsSync(laws) && !seen.has(fs.realpathSync(laws))) { @@ -976,19 +949,19 @@ function book_seed(base: Bend.Book): Bend.Book { return book; } -function book_main(book: Bend.Book, entry = "main"): Bend.Def | null { - const main = book.tlds[entry]; +function book_main(book: Bend.Book): Bend.Def | null { + const main = book.tlds["main"]; return main === undefined || main.$ !== "Def" || (main.v === null && main.i === undefined) ? null : main; } -function book_run(book: Bend.Book, argv: string[], entry = "main"): number { - const main = book_main(book, entry); +function book_run(book: Bend.Book, argv: string[]): number { + const main = book_main(book); if (main === null) { return cli_verdict(book, false); } - if (Comp.io_type(book, entry) !== null) { - return Comp.io_run(book, argv, entry); + if (Comp.io_type(book) !== null) { + return Comp.io_run(book, argv); } const snf = Bend.term_snf(book, main.v as Bend.HTerm); cli_say(1, Bend.term_show(Bend.term_lower(snf)) + "\n"); From cfd7b3d80ef5a6cbf45453d265f119cc8763e8ad Mon Sep 17 00:00:00 2001 From: oxura Date: Mon, 12 Oct 2026 14:03:20 +0600 Subject: [PATCH 3/3] fix(cli): confine generated hub checkup roots with loader namespaces --- README.md | 2 ++ bend2/main.ts | 10 ++++++---- gates/ping.ts | 16 ++++++++++++++++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 563a12e96c..793b9ced8a 100644 --- a/README.md +++ b/README.md @@ -208,6 +208,8 @@ def add_zero(x): - Formatter: [bend-fmt-lsp](tools/bend-fmt-lsp), a formatting-only Bend 2 language server. - Independent module smoke checks: `bend aggregation.bend --checkup` checks and runs local, content-hash and named hub imports in their own scope. + Hub entries stay inside the library cache, including after path normalization + or symlink resolution; a failed entry does not stop the later checks. - Community language server: [bend2-lsp](https://github.com/don2e4/bend2-lsp), with formatting, diagnostics, and hover. - Community workshop: [Bend 2 pocket workshop](https://np.github.io/bend-workshop/), the checker and JS compiler in one HTML page, with goals and proof tools, phone first. diff --git a/bend2/main.ts b/bend2/main.ts index 721e7b54d4..7afbe62998 100755 --- a/bend2/main.ts +++ b/bend2/main.ts @@ -343,20 +343,22 @@ async function cli_checkup(file: string): Promise { } const at = /^(0x[0-9a-f]+|[^/]*@[^/]*)\//.test(m[1]) ? hub : path.resolve(path.dirname(file), m[1]); + // A hub-owned root makes the loader confine every import to its cache. + const ns = at === hub ? "0x0/checkup" : ""; cli_say(1, "--- " + m[1] + " ---\n"); let code = 1; try { fs.writeFileSync(hub, "import " + m[1] + " as Hub\n"); let seen = new Map([[BASE, ""]]); if (at === hub) { - await Bend.book_load(Bend.book_nil(), hub, "", seen).catch((e) => { + await Bend.book_load(Bend.book_nil(), hub, ns, seen).catch((e) => { throw new Check_Fail(e); }); } const [, , got = at] = seen.keys(); const own = /^import Base$/m.test(fs.readFileSync(got, "utf8")); seen = new Map(); - const book = await book_read(at, own ? base : undefined, seen); + const book = await book_read(at, own ? base : undefined, seen, ns); const main = book.tlds[seen.get(got) + ":main"]; if (at === hub && main !== undefined) { book.tlds["main"] = main; @@ -908,13 +910,13 @@ function cli_fail(msg: string): never { // ==== async function book_read(file: string, base?: Bend.Book, - seen = new Map()): Promise { + seen = new Map(), ns = ""): Promise { const book = base === undefined ? Bend.book_nil() : book_seed(base); if (base !== undefined) { seen.set(BASE, ""); } try { - await Bend.book_load(book, file, "", seen); + await Bend.book_load(book, file, ns, seen); const laws = path.join(path.dirname(file), "LAWS.bend"); if (path.basename(file) === "PROOF.bend" && fs.existsSync(laws) && !seen.has(fs.realpathSync(laws))) { diff --git a/gates/ping.ts b/gates/ping.ts index 3f0105d58a..1944a485f6 100644 --- a/gates/ping.ts +++ b/gates/ping.ts @@ -345,6 +345,22 @@ try { check("hash and named hub checkups execute pure and effectful entries", checkup_result.code === 0 && checkup_result.out.includes("\n5n\n") && checkup_result.out.endsWith("hub-io\n")); + const checkup_outside = path.join(TMP, "checkup-outside.bend"); + fs.writeFileSync(checkup_outside, "import Base\ndef main() -> Nat:\n 99n\n"); + fs.writeFileSync(checkup_hub, "import " + checkup_pkg + + "/../../checkup-outside.bend as Outside\nimport checkup@1.0.0.0/io.bend as Effect\n"); + const checkup_escape = await bend([checkup_hub, "--checkup"], + { BEND_LIB: checkup_cache, BEND_NO_TELEMETRY: "1" }); + check("a hub traversal fails without running the outside file; later imports run", + checkup_escape.code === 1 && !checkup_escape.out.includes("\n99n\n") + && checkup_escape.out.endsWith("hub-io\n")); + fs.unlinkSync(path.join(checkup_cache, checkup_pkg, "pure.bend")); + fs.symlinkSync(checkup_outside, path.join(checkup_cache, checkup_pkg, "pure.bend")); + fs.writeFileSync(checkup_hub, "import checkup@1.0.0.0/pure.bend as Outside\n"); + const checkup_link = await bend([checkup_hub, "--checkup"], + { BEND_LIB: checkup_cache, BEND_NO_TELEMETRY: "1" }); + check("a named hub entry cannot run through a symlink outside its cache", + checkup_link.code === 1 && !checkup_link.out.includes("\n99n\n")); const two = "import Base\ndef two() -> Nat:\n 2n\n"; const use = (at: string) => "import Base\nimport ./" + at + " as T\ndef main() -> Nat:\n T.two\n";