Summary
Under --dynamic, passing an island call expression directly to Buffer.from() crashes the compiler with an internal error. The equivalent new Uint8Array(...) construction over the same expression emits a clean SC2020 with an actionable hint. Both hit the same underlying situation — a byte-container constructor over an any-typed value returned from the embedded engine — so the Buffer.from path looks like it is missing the guard that the Uint8Array path has.
Environment
|
|
| scriptc |
0.0.17 (@scriptc/compiler 0.0.17) |
| OS |
Ubuntu 24.04, x86_64 |
| clang |
18.1.3 |
| cmake |
3.28.3 |
| Node (baseline) |
22.22.2 |
| npm package under test |
zapo-js@1.6.2 (any package returning a Uint8Array reproduces) |
Repro
tsconfig.json:
{
"compilerOptions": {
"target": "es2022",
"module": "nodenext",
"moduleResolution": "nodenext",
"strict": true,
"types": ["node"],
"skipLibCheck": true
}
}
repro.ts:
import { sha256 } from "zapo-js/crypto";
console.log(Buffer.from(sha256(Buffer.from("zapo"))).toString("hex"));
$ scriptc build repro.ts --dynamic
error SC9001: internal compiler error: in main: bytesNew source of kind jsval — please report this
1 | import { sha256 } from "zapo-js/crypto";
2 | console.log(Buffer.from(sha256(Buffer.from("zapo"))).toString("hex"));
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Also fires as in %init.0 when the nested call sits in a top-level const.
Contrast: the Uint8Array path
Same shape, no Buffer anywhere:
import { sha256 } from "zapo-js/crypto";
const input = new Uint8Array([122, 97, 112, 111]);
const c = new Uint8Array(sha256(input));
error SC2020: 'new Uint8Array over 'any' values' is part of the standard library types
but has no scriptc lowering yet
hint: supported: new Uint8Array(), (length), (typedArray) — always a copy — or
(number[]); ArrayBuffers and views do not exist here (narrow unions first)
Clean diagnostic, code frame, actionable hint. That is the behaviour I would expect from the Buffer.from case too.
Workaround
Binding the call to a const first makes the declared .d.ts type apply instead of any, and both constructors then work:
const r = sha256(input);
const c = new Uint8Array(r); // ok, copies
const b = Buffer.from(r); // ok
Notes
Everything else I tried on island-returned Uint8Array values works through an intermediate binding: .length, indexing, .slice(), .subarray(), .set(), and passing to a statically-compiled TS function. The one other rejection I hit was r instanceof Uint8Array → SC1090.
Summary
Under
--dynamic, passing an island call expression directly toBuffer.from()crashes the compiler with an internal error. The equivalentnew Uint8Array(...)construction over the same expression emits a cleanSC2020with an actionable hint. Both hit the same underlying situation — a byte-container constructor over anany-typed value returned from the embedded engine — so theBuffer.frompath looks like it is missing the guard that theUint8Arraypath has.Environment
@scriptc/compiler0.0.17)zapo-js@1.6.2(any package returning aUint8Arrayreproduces)Repro
tsconfig.json:{ "compilerOptions": { "target": "es2022", "module": "nodenext", "moduleResolution": "nodenext", "strict": true, "types": ["node"], "skipLibCheck": true } }repro.ts:Also fires as
in %init.0when the nested call sits in a top-levelconst.Contrast: the Uint8Array path
Same shape, no
Bufferanywhere:Clean diagnostic, code frame, actionable hint. That is the behaviour I would expect from the
Buffer.fromcase too.Workaround
Binding the call to a
constfirst makes the declared.d.tstype apply instead ofany, and both constructors then work:Notes
Everything else I tried on island-returned
Uint8Arrayvalues works through an intermediate binding:.length, indexing,.slice(),.subarray(),.set(), and passing to a statically-compiled TS function. The one other rejection I hit wasr instanceof Uint8Array→SC1090.