Repository navigation
codegen(deno-esm): Deno externs missing from codegen lookup → ReferenceError at runtime (endsWith / stripSuffix / numToFixed2) #470
Description
Summary
Three pub extern fn declarations in stdlib/Deno.affine (endsWith, stripSuffix, numToFixed2) have no entry in lib/codegen_deno.ml's lookup table. The codegen happily emits call sites for them, but never defines them in the compiled output. At runtime: ReferenceError: endsWith is not defined.
Repro
A minimal port (hand-trimmed from the live standards/scripts/check-ts-allowlist.affine port that hit this):
use Deno::{ endsWith };
pub fn main() -> Int {
if endsWith("foo.ts", ".ts") { 0 } else { 1 }
}
$ affinescript check minimal.affine
Type checking passed
$ affinescript compile --deno-esm -o /tmp/min.deno.js minimal.affine
Compiled minimal.affine -> /tmp/min.deno.js (Deno-ESM)
$ deno run /tmp/min.deno.js
error: Uncaught (in promise) ReferenceError: endsWith is not defined
at main (file:///tmp/min.deno.js:N:M)Root cause
lib/codegen_deno.ml has a comment at ~line 541-542:
// not externs — endsWith/stripSuffix/pathJoin/etc. are NOT here:
// they are real AffineScript built on `ends_with`/`substring`/`++`.
The intent is clear: these externs are supposed to be backed by real AS bodies in stdlib/string.affine (e.g. fn ends_with). But the Deno-ESM backend doesn't link stdlib bodies for extern fn declarations — only for pub fn definitions actually imported via use.
So we get a worst-of-both-worlds:
- The codegen lookup table (
b "..."lines) coverspathJoin,walkRecursive,regexMatch, etc. — those get inline__as_*JS shims. - Real stdlib
pub fns (e.g.string::split) get their bodies emitted when imported. - But
pub extern fndeclarations whose "real implementation" lives elsewhere fall in a gap — neither pathway covers them.
endsWith, stripSuffix, numToFixed2 are the three that fall in the gap today (per comm -23 of the two name lists).
Workaround (used in standards#310)
Drop the use Deno::{ endsWith }, inline a string_sub-backed helper:
fn ends_with(s: String, suffix: String) -> Bool {
let slen = len(s);
let sfxlen = len(suffix);
if sfxlen > slen { false }
else { string_sub(s, slen - sfxlen, sfxlen) == suffix }
}
This is exactly the defensive pattern the merged seed in standards#283 used; my rewrite removed it on the assumption the extern would link. It didn't.
Proposed fix (pick one)
-
Add the missing lookup entries to
codegen_deno.ml— pure mechanical fix, mirrors what's done forpathJoin:b "endsWith" (fun a -> Printf.sprintf "String(%s).endsWith(%s)" (arg 0 a) (arg 1 a)); b "stripSuffix" (fun a -> Printf.sprintf "((s,x) => s.endsWith(x) ? s.slice(0, -x.length) : s)(%s, %s)" (arg 0 a) (arg 1 a)); b "numToFixed2" (fun a -> Printf.sprintf "Number(%s).toFixed(2)" (arg 0 a));
Cheap; ships with the next compiler bump; no stdlib linkage required.
-
Compile-in the real AS bodies when a
use Deno::{ endsWith }is encountered. More general; would also unblock similar gaps in other stdlib modules. Larger change.
Option 1 is the right STEP. Option 2 is the right LONG-TERM. Recommend doing option 1 now and tracking option 2 as a separate stdlib-linkage issue.
Related
- standards#283 — seed PR (used defensive inline helpers; merged but didn't compile)
- standards#310 — compile + runtime fix that re-introduced the workaround
- standards#311 — workflow swap that ships the compiled output
Acceptance
-
codegen_deno.mlgains lookup entries forendsWith,stripSuffix,numToFixed2 - Minimal repro above compiles and runs (
deno runreturns 0) - A test in
tests/codegen-deno/covers each (probably extendsdeno_scripting.affine) - Follow-up issue filed for general extern→stdlib-body linkage (option 2)
Activity
hyperpolymath commentedon Jun 1, 2026
Status update — AS stdlib codegen pattern now established (2026-06-01) 📌
For ticket coordinators / anyone tracking this gap:
lib/codegen_deno.ml. Tests live intests/codegen-deno/*.What this means for the gap tracked in this ticket:
stdlib/*.affine→ wire indeno_builtinstable + prelude → smoke-test intests/codegen-deno/*.🤖 Cross-estate AS-status sweep on 2026-06-01.