Merge main

This commit is contained in:
Chris Tate
2026-10-19 19:56:12 -05:00
638 changed files with 28676 additions and 6359 deletions
+1
View File
@@ -35,6 +35,7 @@ output/
!tests/fixtures/npm/cases/workspace-copied/node_modules/*/dist/
!tests/fixtures/provenance/node_modules/*/dist/
!tests/fixtures/npm-static/node_modules/*/dist/
!tests/corpus/*/node_modules/*/dist/
.scriptc/
*.log
.DS_Store
+2
View File
@@ -46,6 +46,8 @@ SCRIPTC_TEST_WORKERS=4 SCRIPTC_SAN=1 pnpm test # sanitized lane
Corpus programs are differential tests against Node: every program runs under Node and as a compiled native binary, and stdout, stderr, and exit codes must match byte-for-byte. A new feature lands with corpus programs that pin its behavior both ways.
Record a new or remeasured program's CI cost (used to balance the test shards) in `tests/harness/corpus-costs/<program>.json`, not in `tests/harness/corpus-costs.json`; see the README there.
Before opening a PR that adds or changes corpus, diagnostic, or frontend fixtures, run `pnpm test:ts7 --baselines-only`. This fast completeness check uses the same fixture discovery as the parity suite; it does not type-check or replace behavioral parity. Generate missing preflight/order baselines, review their diagnostics and module order, and run the affected `order-parity.test.ts` batches before pushing. Keep existing baseline entries unchanged unless the corresponding behavior intentionally changes. The full `pnpm test:ts7` command also checks completeness before starting its sweep.
A change that must not cost good code anything, such as a compatibility fix that replaces a refusal, proves it with the zero-cost gate. The gate compiles the benchmark workloads, the self-hosted compiler, and the TypeScript corpus with two built checkouts. It requires byte-identical IR, LLVM, and release executables, and compile time within tolerance. `--allow=<regex>` names the change's pattern signature: programs whose sources match may differ, and the PR lists them. Any other difference fails the gate, which names the first differing function. The gate also enforces the two-tier runtime separation: no executable may gain symbols from `DYNAMIC_TIER_PREFIXES` in `scripts/zero-cost-compare.mjs`, and fully typed benchmarks may link none; extend that list as dynamic-tier units land. `--quick` checks only the benchmarks; `--cachegrind` adds per-function instruction counts for executables that differ. Run it in a Sandbox with both checkouts prepared:
+1 -1
View File
@@ -76,7 +76,7 @@ Coverage and `scriptc build` report the same diagnostic codes. Common ones inclu
Some code compiles but can behave differently than under Node.js. Coverage reports the cases it can detect as `SC6xxx` warnings, and `scriptc build` prints the same warnings after a successful build. They never fail a build, but `--fail-on=divergences` can fail a CI job on them.
- `SC6001`: a record passed to, stored as, asserted as, or stored as an array element of a record type with different properties is copied, and the program then writes through the copy or compares it by identity.
- `SC6002`: an object literal's key order differs from its type's declaration order, and the program observes the order through `JSON.stringify`, `Object.keys`, `Object.values`, `Object.entries`, `for...in`, or console output.
- `SC6002`: values of one record type are built in different key orders, so the type keeps its declaration order, an object literal's key order differs from it, and the program observes the order through `JSON.stringify`, `Object.keys`, `Object.values`, `Object.entries`, `for...in`, or console output.
- `SC6003`: `localeCompare` compares a string literal outside the built-in Latin-script collation.
- `SC6004`: a call passes a class instance to a function that casts it to an unrelated class.
- `SC6005`: a `Date` string literal uses a form the native parser does not accept.
+2
View File
@@ -64,6 +64,8 @@ title: "Limitations"
<include>../limitations/builtin-apis/eventemitter.mdx</include>
<include>../limitations/builtin-apis/asynclocalstorage.mdx</include>
<include>../limitations/builtin-apis/streams-and-readline.mdx</include>
<include>../limitations/builtin-apis/commonjs-metadata.mdx</include>
@@ -0,0 +1,2 @@
- **AsyncLocalStorage:** timer, interval, `setImmediate`, `process.nextTick` and `queueMicrotask` callbacks run with the stores that were active when they were scheduled, as in Node.
Completion callbacks of callback-style I/O APIs, such as `fs.readFile` and `crypto.randomBytes`, run with no store, where Node runs them with the stores that were active when the operation started.
@@ -1,5 +1,5 @@
- **Date:** Date unions, year/month constructors, setters, statically typed TypeScript identity comparisons, thrown Date values, and locale/string formatters are unsupported.
Zero-argument and single number/string constructors, storage and passing, `getTime`, `valueOf`, `toISOString`, `toJSON` (a `string | null`: null for an invalid date, as in Node), calendar getters, `getTimezoneOffset`, and single-string `Date.parse` compile.
- **Date:** Date unions, year/month constructors, setters, statically typed TypeScript identity comparisons, thrown Date values, and locale formatters are unsupported.
Zero-argument and single number/string constructors, storage and passing, `getTime`, `valueOf`, `toISOString`, `toUTCString`, `toJSON` (a `string | null`: null for an invalid date, as in Node), `toString`, `toDateString`, `toTimeString`, `String(date)` and template literal conversion, calendar getters, `getTimezoneOffset`, and single-string `Date.parse` compile.
JavaScript Dates retain identity through checked storage and support Date-copy construction and JSON serialization.
A TypeScript Date converted to `unknown` becomes a Date with the same time value; each conversion creates a new Date, so two `unknown` values converted from one TypeScript Date are not `===`.
See [Dates and timezones](#dates-and-timezones) for parsing restrictions.
@@ -3,5 +3,7 @@
In TypeScript, `h.return!()` and `h.return?.()` compile when `h` is a helper call or a `const` initialized with one.
`flatMap` callbacks return arrays, generators, Sets or Maps; a string result throws Node's TypeError.
`Iterator.from` accepts arrays, strings, typed arrays, Sets, Maps and iterator objects such as generators.
In JavaScript, untyped array, string, Map and Set iterators answer the same methods.
`Iterator.from` over an object with its own `next` method or over an untyped value, the `Iterator` global as a value, and printing typed helper objects are unsupported.
In JavaScript, untyped generators and array, string, Map and Set iterators answer the same methods, and array destructuring of untyped values steps generators, iterators, Maps and Sets the way Node does.
Helper methods on untyped async generators are unsupported.
`console.log`, `util.inspect` and `Object.prototype.toString` print iterator objects as Node does: `Object [Iterator Helper] {}`, `Object [Generator] {}`, `Object [Array Iterator] {}`, and Map and Set iterators with the entries they have left.
`Iterator.from` over an object with its own `next` method or over an untyped value, the `Iterator` global as a value, and `String()` and template literals of typed iterator objects are unsupported.
@@ -21,3 +21,10 @@
- **Timeouts:** when a test's `{ timeout }` elapses, its running subtests are cancelled, as in Node.
Subtests do not take their parent's `timeout` as their own, where Node's do, and the subtests of a timed-out test with `{ concurrency }` above 1 run to completion before the test reports.
A body that keeps running after its timeout and calls `t.test` again runs that subtest, where Node reports it as not started.
- **Thrown values:** a test that throws an object or array that is not an `Error`, such as `throw { code: 42 }`, reports `[object Object]` in the failing section, where Node prints the value as `util.inspect` renders it.
- **Test names:** a test without a name, or with an empty one, takes its function's name or `<anonymous>`, as in Node, when the function's name is known at compile time: a function expression, a function declaration, or a `const` bound to an arrow or anonymous function.
A name computed at run time, with a function the program received as a parameter, is used as given, so an empty one stays empty where Node uses the function's name.
`test(fn)` and `test("", fn)` with such a function throw `SC2020`.
- **Current context:** `getTestContext()` returns the context of the test, suite or hook whose code is running, as in Node: `undefined` at module top level, the root's context in module-level hooks, the suite's in a suite body and in the suite's hooks, and the test's in its body, in the code it awaits, and in the timers, immediates, `process.nextTick` and `queueMicrotask` callbacks it schedules.
Node returns, for a suite, a `TestContext` that is a different object from the `SuiteContext` passed to the suite body and its `before` and `after` hooks; in a compiled program they are the same object, so comparing them with `===` gives `true`.
Completion callbacks of callback-style I/O APIs, such as `fs.readFile`, get `undefined`, as AsyncLocalStorage stores do.
@@ -4,3 +4,10 @@ Other V8-specific forms and offset-less local date-times produce an Invalid Date
Local calendar getters use the operating system's timezone database.
Historical results can differ from Node.js when timezone data differs.
For valid extreme years outside a platform calendar API's range, scriptc uses a calendar-equivalent year in the 400-year Gregorian cycle to obtain the timezone rule.
`toString`, `toTimeString`, `String(date)` and template literals print the zone's long name after the GMT offset, as Node does (`GMT-0500 (Central Daylight Time)`). scriptc identifies the zone the way Node does, from `TZ` when it names a zone, else from `/etc/localtime`.
Outside Windows, a write to `process.env.TZ` changes the zone for later conversions and getters.
The names are the en-US names for the IANA zones; Node prints them in the language of the locale that `LC_ALL`, `LC_MESSAGES` or `LANG` selects, so with a non-English locale the name differs from Node's.
A `TZ` value that is a file path or a POSIX rule string (such as `EST5EDT,M3.2.0,M11.1.0`) gives offsets and names that differ from Node's, which ignores those forms.
On Windows, the name is the `GMT+hh:mm` form unless `TZ` names a zone.
On `wasm32-wasi`, local time is UTC and the name is `Coordinated Universal Time`.
@@ -10,9 +10,9 @@ A type whose values are only read field by field, from fields the class does not
Object spread of a class instance is refused.
Dense array literals created directly in checked storage preserve identity, and supported native handles pass by reference.
`Object.keys`, `Object.values`, `Object.entries`, and `JSON.stringify` use a record's declaration order rather than per-object insertion order.
Their order matches Node.js when objects are built in declaration order.
`SC6002` reports object literals whose order differs where the program observes it.
`Object.keys`, `Object.values`, `Object.entries`, `for...in`, `JSON.stringify`, and `util.inspect` enumerate a record's keys in one order per record type rather than per object. scriptc chooses that order from the order in which the program's object literals create the keys, follows object spread, rest destructuring, and record conversions back to the values they copy, and places an optional key that the program assigns after construction after the keys the object was built with.
The order matches Node.js when every value of the type is created in the same order.
When values of one record type are created in different orders, including values of two types with the same properties and property types, or when a key is deleted and assigned again, the type keeps the declaration order of its first type instead, and `SC6002` reports object literals whose order differs where the program observes it.
A record converted from a checked value, such as `const rows: { score: number }[] = JSON.parse(text)`, keeps the properties its type does not declare.
`JSON.stringify`, `Object.keys`, `Object.values`, `Object.entries`, `util.inspect`, and untyped views of the record see them, together with the declared fields, in the source object's key order.
@@ -1,5 +1,5 @@
Strings are stored as UTF-8, with lone surrogates kept as their own code units.
Length and string methods use UTF-16 semantics, including for lone surrogates, but relational comparisons such as `<` and `>` use code-point order.
Length, string methods and relational comparisons such as `<` and `>` use UTF-16 semantics, including for lone surrogates.
`split` with a string separator and `replace` or `replaceAll` with a string pattern don't match a lone surrogate against half of a surrogate pair; regular-expression patterns do.
Output, file contents, and other encoded text write each lone surrogate as U+FFFD, as Node does.
File system paths and network host names that contain a lone surrogate reach the operating system unconverted.
@@ -0,0 +1,7 @@
{
"schemaVersion": 3,
"nodeVersion": "24.22.0",
"nodeCommit": "f14cd3aa7bf9ca8df2cc5b8bb14beb0f335b1d93",
"artifactVersion": "ee72820d45a79aa154f0",
"rowCount": 3880
}
+72 -72
View File
@@ -208,10 +208,10 @@
"entries": 152,
"apiEntries": 152,
"static": {
"supported": 5,
"supported": 6,
"partial": 39,
"refused": 21,
"not-implemented": 87,
"not-implemented": 86,
"by-design": 0,
"unreviewed": 0,
"not-applicable": 0
@@ -314,9 +314,9 @@
"apiEntries": 280,
"static": {
"supported": 7,
"partial": 198,
"partial": 200,
"refused": 0,
"not-implemented": 75,
"not-implemented": 73,
"by-design": 0,
"unreviewed": 0,
"not-applicable": 0
@@ -328,10 +328,10 @@
"entries": 92,
"apiEntries": 92,
"static": {
"supported": 0,
"partial": 12,
"supported": 1,
"partial": 15,
"refused": 1,
"not-implemented": 79,
"not-implemented": 75,
"by-design": 0,
"unreviewed": 0,
"not-applicable": 0
@@ -689,9 +689,9 @@
"apiEntries": 117,
"static": {
"supported": 0,
"partial": 64,
"partial": 65,
"refused": 0,
"not-implemented": 53,
"not-implemented": 52,
"by-design": 0,
"unreviewed": 0,
"not-applicable": 0
@@ -853,10 +853,10 @@
"entries": 156,
"apiEntries": 156,
"static": {
"supported": 1,
"partial": 10,
"supported": 2,
"partial": 21,
"refused": 0,
"not-implemented": 145,
"not-implemented": 133,
"by-design": 0,
"unreviewed": 0,
"not-applicable": 0
@@ -12325,9 +12325,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "supported",
"detail": "Implemented by a dedicated static compiler/runtime path.",
"verification": "test-backed"
}
},
{
@@ -30288,9 +30288,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the call shapes accepted by the compiler lowering.",
"verification": "test-backed"
}
},
{
@@ -35256,9 +35256,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the call shapes accepted by the compiler lowering.",
"verification": "test-backed"
}
},
{
@@ -35693,9 +35693,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -36360,9 +36360,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -37165,9 +37165,9 @@
"inherited": false
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -37303,9 +37303,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "supported",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -67344,9 +67344,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -80183,9 +80183,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "supported",
"detail": "Implemented by a dedicated static compiler/runtime path.",
"verification": "test-backed"
}
},
{
@@ -80229,9 +80229,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -80252,9 +80252,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -80275,9 +80275,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -80298,9 +80298,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -80321,9 +80321,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -80344,9 +80344,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -80436,9 +80436,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -80574,9 +80574,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -80735,9 +80735,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -80758,9 +80758,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -80804,9 +80804,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -3,21 +3,21 @@
"nodeVersion": "24.22.0",
"nodeCommit": "f14cd3aa7bf9ca8df2cc5b8bb14beb0f335b1d93",
"summary": {
"taskCount": 3456,
"tierItemCount": 3456,
"taskCount": 3453,
"tierItemCount": 3453,
"tiers": {
"static": {
"audit-partial": 1162,
"verify-gap": 2166,
"audit-partial": 1179,
"verify-gap": 2146,
"replace-refusal": 41,
"classify": 2,
"implement": 85
}
},
"priorities": {
"high": 2289,
"high": 2287,
"normal": 694,
"low": 473
"low": 472
}
},
"tasks": [
@@ -880,7 +880,8 @@
"confidence": "high",
"source": "compiler-dedicated:AsyncLocalStorage",
"tests": [
"tests/corpus/2213-async-local-storage.cjs"
"tests/corpus/2213-async-local-storage.cjs",
"tests/corpus/5815-als-callback-context.ts"
]
}
}
@@ -932,7 +933,8 @@
"confidence": "high",
"source": "compiler-dedicated:asyncLocalStorage.disable",
"tests": [
"tests/corpus/2213-async-local-storage.cjs"
"tests/corpus/2213-async-local-storage.cjs",
"tests/corpus/5815-als-callback-context.ts"
]
}
}
@@ -959,7 +961,8 @@
"confidence": "high",
"source": "compiler-dedicated:asyncLocalStorage.getStore",
"tests": [
"tests/corpus/2213-async-local-storage.cjs"
"tests/corpus/2213-async-local-storage.cjs",
"tests/corpus/5815-als-callback-context.ts"
]
}
}
@@ -986,7 +989,8 @@
"confidence": "high",
"source": "compiler-dedicated:asyncLocalStorage.enterWith",
"tests": [
"tests/corpus/2213-async-local-storage.cjs"
"tests/corpus/2213-async-local-storage.cjs",
"tests/corpus/5815-als-callback-context.ts"
]
}
}
@@ -1013,7 +1017,8 @@
"confidence": "high",
"source": "compiler-dedicated:asyncLocalStorage.run",
"tests": [
"tests/corpus/2213-async-local-storage.cjs"
"tests/corpus/2213-async-local-storage.cjs",
"tests/corpus/5815-als-callback-context.ts"
]
}
}
@@ -1040,7 +1045,8 @@
"confidence": "high",
"source": "compiler-dedicated:asyncLocalStorage.exit",
"tests": [
"tests/corpus/2213-async-local-storage.cjs"
"tests/corpus/2213-async-local-storage.cjs",
"tests/corpus/5815-als-callback-context.ts"
]
}
}
@@ -1771,7 +1777,8 @@
"confidence": "high",
"source": "compiler-dedicated:AsyncLocalStorage",
"tests": [
"tests/corpus/2213-async-local-storage.cjs"
"tests/corpus/2213-async-local-storage.cjs",
"tests/corpus/5815-als-callback-context.ts"
]
}
}
@@ -7352,31 +7359,6 @@
}
}
},
{
"id": "crypto:2ab8394e29a7",
"chapter": "crypto",
"label": "crypto.getRandomValues(typedArray)",
"signature": "`crypto.getRandomValues(typedArray)`",
"apiSymbol": "crypto.getRandomValues",
"docsUrl": "https://nodejs.org/docs/v24.22.0/api/crypto.html#cryptogetrandomvaluestypedarray",
"nodeStability": {
"index": "2",
"level": 2,
"text": "Stable",
"inherited": true
},
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:crypto",
"tests": []
}
}
},
{
"id": "crypto:21f22c71fb61",
"chapter": "crypto",
@@ -27060,12 +27042,15 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:fs",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "surface-manifest:node-builtin.fs.constants",
"tests": [
"tests/corpus/6207-fs-constants-object.ts",
"tests/corpus/6208-fs-constants-require.cjs"
]
}
}
},
@@ -32757,12 +32742,15 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:fs",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "surface-manifest:node-builtin.fs.constants",
"tests": [
"tests/corpus/6207-fs-constants-object.ts",
"tests/corpus/6208-fs-constants-require.cjs"
]
}
}
},
@@ -33213,12 +33201,18 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:globals",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:globals.CryptoKey",
"tests": [
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6292-webcrypto-hmac-errors.ts",
"tests/corpus/6293-webcrypto-digest.ts",
"tests/corpus/6294-webcrypto-js-values.mjs",
"tests/corpus/6295-jose-hs256/main.mjs"
]
}
}
},
@@ -33944,12 +33938,18 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:globals",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:globals.SubtleCrypto",
"tests": [
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6292-webcrypto-hmac-errors.ts",
"tests/corpus/6293-webcrypto-digest.ts",
"tests/corpus/6294-webcrypto-js-values.mjs",
"tests/corpus/6295-jose-hs256/main.mjs"
]
}
}
},
@@ -34865,12 +34865,16 @@
"priority": "low",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:globals",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:globals.navigator",
"tests": [
"tests/fixtures/node-types/navigator.ts",
"tests/harness/project-config.test.ts",
"tests/corpus/6331-jose-root-entry/main.ts"
]
}
}
},
@@ -34999,31 +35003,6 @@
}
}
},
{
"id": "globals:fc0a32e8c26f",
"chapter": "globals",
"label": "userAgent",
"signature": "`userAgent` Type: {string}",
"apiSymbol": "userAgent",
"docsUrl": "https://nodejs.org/docs/v24.22.0/api/globals.html#navigator",
"nodeStability": {
"index": "1.1",
"level": 1,
"text": "Active development. Disable this API with the [`--no-experimental-global-navigator`][] CLI flag.",
"inherited": true
},
"priority": "low",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:globals",
"tests": []
}
}
},
{
"id": "globals:b412bf9a456d",
"chapter": "globals",
@@ -68875,12 +68854,16 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:test",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:test.getTestContext",
"tests": [
"tests/fixtures/node-test/cases/get-test-context.ts",
"tests/fixtures/node-test/cases/get-test-context-cancelled.ts",
"tests/harness/node-test.test.ts"
]
}
}
},
@@ -81016,31 +80999,6 @@
}
}
},
{
"id": "webcrypto:dcc24f369f43",
"chapter": "webcrypto",
"label": "crypto.getRandomValues(typedArray)",
"signature": "`crypto.getRandomValues(typedArray)`",
"apiSymbol": "crypto.getRandomValues",
"docsUrl": "https://nodejs.org/docs/v24.22.0/api/webcrypto.html#cryptogetrandomvaluestypedarray",
"nodeStability": {
"index": "2",
"level": 2,
"text": "Stable",
"inherited": true
},
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:webcrypto",
"tests": []
}
}
},
{
"id": "webcrypto:8c570ec75666",
"chapter": "webcrypto",
@@ -81057,12 +81015,18 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:webcrypto",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:webcrypto.subtle",
"tests": [
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6292-webcrypto-hmac-errors.ts",
"tests/corpus/6293-webcrypto-digest.ts",
"tests/corpus/6294-webcrypto-js-values.mjs",
"tests/corpus/6295-jose-hs256/main.mjs"
]
}
}
},
@@ -81082,12 +81046,16 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:webcrypto",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:webcrypto.CryptoKey",
"tests": [
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6294-webcrypto-js-values.mjs",
"tests/corpus/6295-jose-hs256/main.mjs"
]
}
}
},
@@ -81107,12 +81075,16 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:webcrypto",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:webcrypto.cryptoKey.algorithm",
"tests": [
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6294-webcrypto-js-values.mjs",
"tests/corpus/6295-jose-hs256/main.mjs"
]
}
}
},
@@ -81132,12 +81104,16 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:webcrypto",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:webcrypto.extractable",
"tests": [
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6294-webcrypto-js-values.mjs",
"tests/corpus/6295-jose-hs256/main.mjs"
]
}
}
},
@@ -81157,12 +81133,16 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:webcrypto",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:webcrypto.type",
"tests": [
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6294-webcrypto-js-values.mjs",
"tests/corpus/6295-jose-hs256/main.mjs"
]
}
}
},
@@ -81182,12 +81162,16 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:webcrypto",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:webcrypto.usages",
"tests": [
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6294-webcrypto-js-values.mjs",
"tests/corpus/6295-jose-hs256/main.mjs"
]
}
}
},
@@ -81282,12 +81266,18 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:webcrypto",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:webcrypto.SubtleCrypto",
"tests": [
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6292-webcrypto-hmac-errors.ts",
"tests/corpus/6293-webcrypto-digest.ts",
"tests/corpus/6294-webcrypto-js-values.mjs",
"tests/corpus/6295-jose-hs256/main.mjs"
]
}
}
},
@@ -81432,12 +81422,19 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:webcrypto",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:webcrypto.subtle.digest",
"tests": [
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6292-webcrypto-hmac-errors.ts",
"tests/corpus/6293-webcrypto-digest.ts",
"tests/corpus/6294-webcrypto-js-values.mjs",
"tests/corpus/6295-jose-hs256/main.mjs",
"tests/fixtures/node-types/webcrypto-hmac.ts"
]
}
}
},
@@ -81607,12 +81604,19 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:webcrypto",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:webcrypto.subtle.importKey",
"tests": [
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6292-webcrypto-hmac-errors.ts",
"tests/corpus/6293-webcrypto-digest.ts",
"tests/corpus/6294-webcrypto-js-values.mjs",
"tests/corpus/6295-jose-hs256/main.mjs",
"tests/fixtures/node-types/webcrypto-hmac.ts"
]
}
}
},
@@ -81632,12 +81636,19 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:webcrypto",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:webcrypto.subtle.sign",
"tests": [
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6292-webcrypto-hmac-errors.ts",
"tests/corpus/6293-webcrypto-digest.ts",
"tests/corpus/6294-webcrypto-js-values.mjs",
"tests/corpus/6295-jose-hs256/main.mjs",
"tests/fixtures/node-types/webcrypto-hmac.ts"
]
}
}
},
@@ -81682,12 +81693,19 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:webcrypto",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:webcrypto.subtle.verify",
"tests": [
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6292-webcrypto-hmac-errors.ts",
"tests/corpus/6293-webcrypto-digest.ts",
"tests/corpus/6294-webcrypto-js-values.mjs",
"tests/corpus/6295-jose-hs256/main.mjs",
"tests/fixtures/node-types/webcrypto-hmac.ts"
]
}
}
},
@@ -190,10 +190,10 @@
"entries": 179,
"apiEntries": 152,
"static": {
"supported": 5,
"supported": 6,
"partial": 39,
"refused": 21,
"not-implemented": 87,
"not-implemented": 86,
"by-design": 0,
"unreviewed": 0,
"not-applicable": 0
@@ -326,9 +326,9 @@
"apiEntries": 280,
"static": {
"supported": 7,
"partial": 198,
"partial": 200,
"refused": 0,
"not-implemented": 75,
"not-implemented": 73,
"by-design": 0,
"unreviewed": 0,
"not-applicable": 0
@@ -340,10 +340,10 @@
"entries": 94,
"apiEntries": 92,
"static": {
"supported": 0,
"partial": 12,
"supported": 1,
"partial": 15,
"refused": 1,
"not-implemented": 79,
"not-implemented": 75,
"by-design": 0,
"unreviewed": 0,
"not-applicable": 0
@@ -746,9 +746,9 @@
"apiEntries": 117,
"static": {
"supported": 0,
"partial": 64,
"partial": 65,
"refused": 0,
"not-implemented": 53,
"not-implemented": 52,
"by-design": 0,
"unreviewed": 0,
"not-applicable": 0
@@ -910,10 +910,10 @@
"entries": 176,
"apiEntries": 156,
"static": {
"supported": 1,
"partial": 10,
"supported": 2,
"partial": 21,
"refused": 0,
"not-implemented": 145,
"not-implemented": 133,
"by-design": 0,
"unreviewed": 0,
"not-applicable": 0
@@ -1953,7 +1953,8 @@
"status": "partial",
"evidence": "derived:descendants",
"tests": [
"tests/corpus/2213-async-local-storage.cjs"
"tests/corpus/2213-async-local-storage.cjs",
"tests/corpus/5815-als-callback-context.ts"
]
},
"anchorSource": "chapter"
@@ -2002,7 +2003,8 @@
"status": "partial",
"evidence": "compiler-dedicated:AsyncLocalStorage",
"tests": [
"tests/corpus/2213-async-local-storage.cjs"
"tests/corpus/2213-async-local-storage.cjs",
"tests/corpus/5815-als-callback-context.ts"
]
},
"anchorSource": "exact"
@@ -2074,7 +2076,8 @@
"status": "partial",
"evidence": "compiler-dedicated:asyncLocalStorage.disable",
"tests": [
"tests/corpus/2213-async-local-storage.cjs"
"tests/corpus/2213-async-local-storage.cjs",
"tests/corpus/5815-als-callback-context.ts"
]
},
"anchorSource": "exact"
@@ -2100,7 +2103,8 @@
"status": "partial",
"evidence": "compiler-dedicated:asyncLocalStorage.getStore",
"tests": [
"tests/corpus/2213-async-local-storage.cjs"
"tests/corpus/2213-async-local-storage.cjs",
"tests/corpus/5815-als-callback-context.ts"
]
},
"anchorSource": "exact"
@@ -2126,7 +2130,8 @@
"status": "partial",
"evidence": "compiler-dedicated:asyncLocalStorage.enterWith",
"tests": [
"tests/corpus/2213-async-local-storage.cjs"
"tests/corpus/2213-async-local-storage.cjs",
"tests/corpus/5815-als-callback-context.ts"
]
},
"anchorSource": "exact"
@@ -2152,7 +2157,8 @@
"status": "partial",
"evidence": "compiler-dedicated:asyncLocalStorage.run",
"tests": [
"tests/corpus/2213-async-local-storage.cjs"
"tests/corpus/2213-async-local-storage.cjs",
"tests/corpus/5815-als-callback-context.ts"
]
},
"anchorSource": "exact"
@@ -2178,7 +2184,8 @@
"status": "partial",
"evidence": "compiler-dedicated:asyncLocalStorage.exit",
"tests": [
"tests/corpus/2213-async-local-storage.cjs"
"tests/corpus/2213-async-local-storage.cjs",
"tests/corpus/5815-als-callback-context.ts"
]
},
"anchorSource": "exact"
@@ -2481,6 +2488,7 @@
"evidence": "derived:descendants",
"tests": [
"tests/corpus/2213-async-local-storage.cjs",
"tests/corpus/5815-als-callback-context.ts",
"tests/harness/node-test.test.ts"
]
},
@@ -3042,7 +3050,8 @@
"status": "partial",
"evidence": "compiler-dedicated:AsyncLocalStorage",
"tests": [
"tests/corpus/2213-async-local-storage.cjs"
"tests/corpus/2213-async-local-storage.cjs",
"tests/corpus/5815-als-callback-context.ts"
]
},
"anchorSource": "exact"
@@ -19863,6 +19872,8 @@
"tests/corpus/5762-crypto-hash-stream.cjs",
"tests/corpus/5768-crypto-hash-unknown.ts",
"tests/corpus/5769-crypto-hash-unknown.cjs",
"tests/corpus/6204-crypto-get-random-values.ts",
"tests/corpus/6205-crypto-get-random-values.js",
"tests/corpus/node24-crypto-kdf-errors.ts",
"tests/corpus/node24-crypto-kdf-options.ts",
"tests/corpus/node24-crypto-kdf.ts",
@@ -21019,8 +21030,12 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:crypto"
"status": "supported",
"evidence": "compiler-dedicated:crypto.getRandomValues",
"tests": [
"tests/corpus/6204-crypto-get-random-values.ts",
"tests/corpus/6205-crypto-get-random-values.js"
]
},
"anchorSource": "exact"
},
@@ -44129,6 +44144,8 @@
"tests/corpus/5568-fs-cp-errors.js",
"tests/corpus/5569-fs-cp-symlinks.ts",
"tests/corpus/5570-fs-cp-async.ts",
"tests/corpus/6207-fs-constants-object.ts",
"tests/corpus/6208-fs-constants-require.cjs",
"tests/corpus/fs-native-realpath.ts",
"tests/corpus/node24-fs-links-callbacks.cjs",
"tests/corpus/node24-fs-links-errors.cjs",
@@ -45916,8 +45933,12 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:fs"
"status": "partial",
"evidence": "surface-manifest:node-builtin.fs.constants",
"tests": [
"tests/corpus/6207-fs-constants-object.ts",
"tests/corpus/6208-fs-constants-require.cjs"
]
},
"anchorSource": "ancestor"
},
@@ -51788,8 +51809,12 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:fs"
"status": "partial",
"evidence": "surface-manifest:node-builtin.fs.constants",
"tests": [
"tests/corpus/6207-fs-constants-object.ts",
"tests/corpus/6208-fs-constants-require.cjs"
]
},
"anchorSource": "ancestor"
},
@@ -52217,8 +52242,16 @@
"tests/corpus/5288-blob-validation.js",
"tests/corpus/5290-blob-dynamic.js",
"tests/corpus/5830-timers-promises-signal.ts",
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6292-webcrypto-hmac-errors.ts",
"tests/corpus/6293-webcrypto-digest.ts",
"tests/corpus/6294-webcrypto-js-values.mjs",
"tests/corpus/6295-jose-hs256/main.mjs",
"tests/corpus/6331-jose-root-entry/main.ts",
"tests/corpus/text-codec-values/main.ts",
"tests/harness/fetch-conformance.test.ts"
"tests/fixtures/node-types/navigator.ts",
"tests/harness/fetch-conformance.test.ts",
"tests/harness/project-config.test.ts"
]
},
"anchorSource": "chapter"
@@ -52639,8 +52672,15 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:globals"
"status": "partial",
"evidence": "compiler-feature:globals.CryptoKey",
"tests": [
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6292-webcrypto-hmac-errors.ts",
"tests/corpus/6293-webcrypto-digest.ts",
"tests/corpus/6294-webcrypto-js-values.mjs",
"tests/corpus/6295-jose-hs256/main.mjs"
]
},
"anchorSource": "exact"
},
@@ -53313,8 +53353,15 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:globals"
"status": "partial",
"evidence": "compiler-feature:globals.SubtleCrypto",
"tests": [
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6292-webcrypto-hmac-errors.ts",
"tests/corpus/6293-webcrypto-digest.ts",
"tests/corpus/6294-webcrypto-js-values.mjs",
"tests/corpus/6295-jose-hs256/main.mjs"
]
},
"anchorSource": "exact"
},
@@ -54219,8 +54266,13 @@
"inherited": false
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:globals"
"status": "partial",
"evidence": "compiler-feature:globals.navigator",
"tests": [
"tests/fixtures/node-types/navigator.ts",
"tests/harness/project-config.test.ts",
"tests/corpus/6331-jose-root-entry/main.ts"
]
},
"anchorSource": "exact"
},
@@ -54357,8 +54409,12 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:globals"
"status": "supported",
"evidence": "compiler-feature:globals.userAgent",
"tests": [
"tests/fixtures/node-types/navigator.ts",
"tests/harness/project-config.test.ts"
]
},
"anchorSource": "ancestor"
},
@@ -91046,6 +91102,8 @@
"tests/fixtures/node-test/cases/ctx-hooks.ts",
"tests/fixtures/node-test/cases/ctx-members.ts",
"tests/fixtures/node-test/cases/ctx-plan.ts",
"tests/fixtures/node-test/cases/get-test-context-cancelled.ts",
"tests/fixtures/node-test/cases/get-test-context.ts",
"tests/fixtures/node-test/cases/mock-fn.ts",
"tests/fixtures/node-test/cases/mock-method.ts",
"tests/fixtures/node-test/cases/mock-timers-date.ts",
@@ -94794,8 +94852,13 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:test"
"status": "partial",
"evidence": "compiler-feature:test.getTestContext",
"tests": [
"tests/fixtures/node-test/cases/get-test-context.ts",
"tests/fixtures/node-test/cases/get-test-context-cancelled.ts",
"tests/harness/node-test.test.ts"
]
},
"anchorSource": "exact"
},
@@ -106838,10 +106901,18 @@
"tests/corpus/2890-crypto-static-utility.ts",
"tests/corpus/5442-crypto-random-values.ts",
"tests/corpus/5443-crypto-random-arguments.js",
"tests/corpus/6204-crypto-get-random-values.ts",
"tests/corpus/6205-crypto-get-random-values.js",
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6292-webcrypto-hmac-errors.ts",
"tests/corpus/6293-webcrypto-digest.ts",
"tests/corpus/6294-webcrypto-js-values.mjs",
"tests/corpus/6295-jose-hs256/main.mjs",
"tests/corpus/node24-crypto-kdf-errors.ts",
"tests/corpus/node24-crypto-kdf-options.ts",
"tests/corpus/node24-crypto-kdf.ts",
"tests/corpus/node24-crypto-sha2.ts",
"tests/fixtures/node-types/webcrypto-hmac.ts",
"tests/harness/npm-static.test.ts"
]
},
@@ -110322,8 +110393,12 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:webcrypto"
"status": "supported",
"evidence": "compiler-dedicated:crypto.getRandomValues",
"tests": [
"tests/corpus/6204-crypto-get-random-values.ts",
"tests/corpus/6205-crypto-get-random-values.js"
]
},
"anchorSource": "exact"
},
@@ -110373,8 +110448,15 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:webcrypto"
"status": "partial",
"evidence": "compiler-feature:webcrypto.subtle",
"tests": [
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6292-webcrypto-hmac-errors.ts",
"tests/corpus/6293-webcrypto-digest.ts",
"tests/corpus/6294-webcrypto-js-values.mjs",
"tests/corpus/6295-jose-hs256/main.mjs"
]
},
"anchorSource": "ancestor"
},
@@ -110396,8 +110478,13 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:webcrypto"
"status": "partial",
"evidence": "compiler-feature:webcrypto.CryptoKey",
"tests": [
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6294-webcrypto-js-values.mjs",
"tests/corpus/6295-jose-hs256/main.mjs"
]
},
"anchorSource": "exact"
},
@@ -110419,8 +110506,13 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:webcrypto"
"status": "partial",
"evidence": "compiler-feature:webcrypto.cryptoKey.algorithm",
"tests": [
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6294-webcrypto-js-values.mjs",
"tests/corpus/6295-jose-hs256/main.mjs"
]
},
"anchorSource": "exact"
},
@@ -110442,8 +110534,13 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:webcrypto"
"status": "partial",
"evidence": "compiler-feature:webcrypto.extractable",
"tests": [
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6294-webcrypto-js-values.mjs",
"tests/corpus/6295-jose-hs256/main.mjs"
]
},
"anchorSource": "ancestor"
},
@@ -110465,8 +110562,13 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:webcrypto"
"status": "partial",
"evidence": "compiler-feature:webcrypto.type",
"tests": [
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6294-webcrypto-js-values.mjs",
"tests/corpus/6295-jose-hs256/main.mjs"
]
},
"anchorSource": "ancestor"
},
@@ -110488,8 +110590,13 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:webcrypto"
"status": "partial",
"evidence": "compiler-feature:webcrypto.usages",
"tests": [
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6294-webcrypto-js-values.mjs",
"tests/corpus/6295-jose-hs256/main.mjs"
]
},
"anchorSource": "ancestor"
},
@@ -110580,8 +110687,15 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:webcrypto"
"status": "partial",
"evidence": "compiler-feature:webcrypto.SubtleCrypto",
"tests": [
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6292-webcrypto-hmac-errors.ts",
"tests/corpus/6293-webcrypto-digest.ts",
"tests/corpus/6294-webcrypto-js-values.mjs",
"tests/corpus/6295-jose-hs256/main.mjs"
]
},
"anchorSource": "exact"
},
@@ -110718,8 +110832,16 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:webcrypto"
"status": "partial",
"evidence": "compiler-feature:webcrypto.subtle.digest",
"tests": [
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6292-webcrypto-hmac-errors.ts",
"tests/corpus/6293-webcrypto-digest.ts",
"tests/corpus/6294-webcrypto-js-values.mjs",
"tests/corpus/6295-jose-hs256/main.mjs",
"tests/fixtures/node-types/webcrypto-hmac.ts"
]
},
"anchorSource": "exact"
},
@@ -110879,8 +111001,16 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:webcrypto"
"status": "partial",
"evidence": "compiler-feature:webcrypto.subtle.importKey",
"tests": [
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6292-webcrypto-hmac-errors.ts",
"tests/corpus/6293-webcrypto-digest.ts",
"tests/corpus/6294-webcrypto-js-values.mjs",
"tests/corpus/6295-jose-hs256/main.mjs",
"tests/fixtures/node-types/webcrypto-hmac.ts"
]
},
"anchorSource": "exact"
},
@@ -110902,8 +111032,16 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:webcrypto"
"status": "partial",
"evidence": "compiler-feature:webcrypto.subtle.sign",
"tests": [
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6292-webcrypto-hmac-errors.ts",
"tests/corpus/6293-webcrypto-digest.ts",
"tests/corpus/6294-webcrypto-js-values.mjs",
"tests/corpus/6295-jose-hs256/main.mjs",
"tests/fixtures/node-types/webcrypto-hmac.ts"
]
},
"anchorSource": "exact"
},
@@ -110948,8 +111086,16 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:webcrypto"
"status": "partial",
"evidence": "compiler-feature:webcrypto.subtle.verify",
"tests": [
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6292-webcrypto-hmac-errors.ts",
"tests/corpus/6293-webcrypto-digest.ts",
"tests/corpus/6294-webcrypto-js-values.mjs",
"tests/corpus/6295-jose-hs256/main.mjs",
"tests/fixtures/node-types/webcrypto-hmac.ts"
]
},
"anchorSource": "exact"
},
+14 -2
View File
@@ -36,7 +36,7 @@
"syntax.for-await-readable-streams": ["tests/corpus/2861-readable-iterator-close.ts", "tests/corpus/2862-web-readable-async-iteration.ts"],
"syntax.for-await-class-async-iterators": ["tests/corpus/2863-class-async-iterator.ts"],
"node-builtin.assert": ["tests/corpus/1600-assert-passing.ts", "tests/corpus/1609-assert-async.ts"],
"node-builtin.async_hooks": ["tests/corpus/2213-async-local-storage.cjs"],
"node-builtin.async_hooks": ["tests/corpus/2213-async-local-storage.cjs", "tests/corpus/5815-als-callback-context.ts"],
"node-builtin.buffer": ["tests/corpus/1402-buffer-encodings.ts", "tests/corpus/1660-buffer-read-write-num.ts", "tests/corpus/2902-bigint-bytes.ts", "tests/corpus/2904-bigint-errors.ts"],
"node-builtin.child_process": ["tests/corpus/1361-spawn-events.ts", "tests/corpus/1565-spawn-pipe-streams.ts", "tests/corpus/2911-execfile-callback.ts", "tests/corpus/2939-child-shell-text.ts", "tests/corpus/2946-child-fork-ipc/main.ts", "tests/corpus/3127-spawn-runtime-options.ts", "tests/corpus/3130-child-generic-streams.ts", "tests/corpus/3131-child-rest-listeners.js", "tests/corpus/3132-child-generic-stream-completion.ts"],
"node-builtin.cluster": ["tests/corpus/957-builtins-namespace.ts"],
@@ -111,6 +111,8 @@
"node-builtin.os.uptime": ["tests/corpus/5140-os-host-facts.ts"],
"node-builtin.os.version": ["tests/corpus/5140-os-host-facts.ts"],
"node-builtin.os.constants": ["tests/corpus/5141-os-constants.ts"],
"node-builtin.fs.constants": ["tests/corpus/6207-fs-constants-object.ts", "tests/corpus/6208-fs-constants-require.cjs"],
"node-builtin.crypto.getRandomValues": ["tests/corpus/6204-crypto-get-random-values.ts", "tests/corpus/6205-crypto-get-random-values.js"],
"node-builtin.url.parse": ["tests/corpus/5220-url-parse-warnings.ts", "tests/corpus/5221-url-parse-shapes.ts", "tests/corpus/5222-url-parse-hosts.ts", "tests/corpus/5223-url-parse-query.ts", "tests/corpus/5227-url-legacy-dependency/main.ts", "tests/corpus/5229-url-legacy-errors.cjs", "packages/runtime/test/url-legacy.test.ts", "tests/fixtures/node-types/url-legacy.ts"],
"node-builtin.url.format": ["tests/corpus/5224-url-format-objects.ts", "tests/corpus/5225-url-format-whatwg.ts", "tests/corpus/5227-url-legacy-dependency/main.ts", "tests/corpus/5229-url-legacy-errors.cjs", "packages/runtime/test/url-legacy.test.ts", "tests/fixtures/node-types/url-legacy.ts"],
"node-builtin.url.resolve": ["tests/corpus/5226-url-resolve.ts", "tests/corpus/5227-url-legacy-dependency/main.ts", "tests/corpus/5229-url-legacy-errors.cjs", "packages/runtime/test/url-legacy.test.ts", "tests/fixtures/node-types/url-legacy.ts"],
@@ -177,11 +179,19 @@
"stdlib.set": ["tests/corpus/typed-collection-iterators.ts", "tests/corpus/typed-collection-iterator-references.ts", "tests/corpus/checked-collection-iterators.ts"],
"stdlib.headers": ["tests/harness/fetch-conformance.test.ts"],
"stdlib.request": ["tests/harness/fetch-conformance.test.ts"],
"stdlib.webcrypto.subtle": ["tests/corpus/6291-webcrypto-hmac-sign-verify.ts", "tests/corpus/6292-webcrypto-hmac-errors.ts", "tests/corpus/6293-webcrypto-digest.ts", "tests/corpus/6294-webcrypto-js-values.mjs", "tests/corpus/6295-jose-hs256/main.mjs"],
"stdlib.webcrypto.CryptoKey": ["tests/corpus/6291-webcrypto-hmac-sign-verify.ts", "tests/corpus/6294-webcrypto-js-values.mjs", "tests/corpus/6295-jose-hs256/main.mjs"],
"stdlib.response": ["tests/harness/fetch-conformance.test.ts"],
"stdlib.readable-stream": ["tests/harness/fetch-conformance.test.ts"],
"stdlib.abort": ["tests/harness/fetch-conformance.test.ts"]
},
"features": [
{ "chapter": "webcrypto", "symbols": ["subtle.importKey", "subtle.sign", "subtle.verify", "subtle.digest"], "status": "partial", "evidence": ["tests/corpus/6291-webcrypto-hmac-sign-verify.ts", "tests/corpus/6292-webcrypto-hmac-errors.ts", "tests/corpus/6293-webcrypto-digest.ts", "tests/corpus/6294-webcrypto-js-values.mjs", "tests/corpus/6295-jose-hs256/main.mjs", "tests/fixtures/node-types/webcrypto-hmac.ts"] },
{ "chapter": "webcrypto", "signatures": ["Class: `SubtleCrypto`", "`subtle` Type: {SubtleCrypto}"], "status": "partial", "evidence": ["tests/corpus/6291-webcrypto-hmac-sign-verify.ts", "tests/corpus/6292-webcrypto-hmac-errors.ts", "tests/corpus/6293-webcrypto-digest.ts", "tests/corpus/6294-webcrypto-js-values.mjs", "tests/corpus/6295-jose-hs256/main.mjs"] },
{ "chapter": "webcrypto", "signatures": ["Class: `CryptoKey`", "`cryptoKey.algorithm`", "`extractable` Type: {boolean}", "`type` Type: {string} One of `'secret'`, `'private'`, or `'public'`.", "`usages` Type: {string\\[]}"], "status": "partial", "evidence": ["tests/corpus/6291-webcrypto-hmac-sign-verify.ts", "tests/corpus/6294-webcrypto-js-values.mjs", "tests/corpus/6295-jose-hs256/main.mjs"] },
{ "chapter": "globals", "signatures": ["`navigator`"], "status": "partial", "evidence": ["tests/fixtures/node-types/navigator.ts", "tests/harness/project-config.test.ts", "tests/corpus/6331-jose-root-entry/main.ts"] },
{ "chapter": "globals", "signatures": ["`userAgent` Type: {string}"], "status": "supported", "evidence": ["tests/fixtures/node-types/navigator.ts", "tests/harness/project-config.test.ts"] },
{ "chapter": "globals", "signatures": ["Class: `CryptoKey`", "Class: `SubtleCrypto`"], "status": "partial", "evidence": ["tests/corpus/6291-webcrypto-hmac-sign-verify.ts", "tests/corpus/6292-webcrypto-hmac-errors.ts", "tests/corpus/6293-webcrypto-digest.ts", "tests/corpus/6294-webcrypto-js-values.mjs", "tests/corpus/6295-jose-hs256/main.mjs"] },
{ "chapter": "errors", "parent": "Class: `SystemError`", "symbols": ["code", "errno", "syscall", "path", "dest"], "status": "partial", "evidence": ["tests/corpus/caught-system-error-fields.ts", "tests/fixtures/node-types/terminal-error-fields.ts", "tests/harness/project-config.test.ts"] },
{"chapter": "events", "symbols": ["EventTarget", "eventTarget.addEventListener", "eventTarget.removeEventListener", "eventTarget.dispatchEvent"], "status": "partial", "evidence": ["tests/corpus/5130-event-target-dispatch.js", "tests/corpus/5131-event-target-listener-errors.js", "tests/corpus/5132-event-target-typed.ts", "tests/corpus/5133-abort-signal-event-target.ts", "tests/corpus/5135-event-target-warnings.js", "tests/corpus/5240-event-target-subclass.ts", "tests/corpus/5242-event-target-subclass-hierarchy.ts", "tests/corpus/5243-event-target-subclass-dynamic.ts", "tests/corpus/5244-event-target-subclass.js", "tests/corpus/5245-event-target-subclass-signals.ts", "tests/corpus/5248-event-target-subclass-warnings.ts"]},
{"chapter": "events", "symbols": ["Event"], "status": "partial", "evidence": ["tests/corpus/5130-event-target-dispatch.js", "tests/corpus/5132-event-target-typed.ts", "tests/corpus/5241-event-subclass.ts", "tests/corpus/5242-event-target-subclass-hierarchy.ts", "tests/corpus/5244-event-target-subclass.js", "tests/corpus/5247-event-target-subclass-lifetimes.ts"]},
@@ -366,6 +376,7 @@
{ "chapter": "test", "parent": "Class: `MockTimers`", "signatures": ["`timers[Symbol.dispose]()`"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/mock-timers-date.ts", "tests/harness/node-test.test.ts"] },
{ "chapter": "test", "parent": "Class: `MockFunctionContext`", "symbols": ["calls", "ctx.callCount", "ctx.mockImplementation", "ctx.mockImplementationOnce", "ctx.resetCalls", "ctx.restore"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/mock-fn.ts", "tests/fixtures/node-test/cases/mock-method.ts", "tests/harness/node-test.test.ts"] },
{ "chapter": "test", "parent": "Class: `TestContext`", "symbols": ["context.before", "context.after", "context.beforeEach", "context.afterEach", "context.plan", "context.fullName"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/ctx-hooks.ts", "tests/fixtures/node-test/cases/ctx-plan.ts", "tests/harness/node-test.test.ts"] },
{ "chapter": "test", "symbols": ["getTestContext"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/get-test-context.ts", "tests/fixtures/node-test/cases/get-test-context-cancelled.ts", "tests/harness/node-test.test.ts"] },
{ "chapter": "test", "symbols": ["describe.skip", "describe.todo", "describe.only", "suite.skip", "suite.todo", "suite.only", "it.skip", "it.todo", "it.only"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/suite-variants.ts", "tests/fixtures/node-test/cases/only-nested.ts", "tests/fixtures/node-test/cases/todo-suites.ts", "tests/harness/node-test.test.ts"] },
{ "chapter": "util", "signatures": ["`util.promisify(original)`"], "status": "partial", "evidence": ["tests/corpus/1473-promisify-execfile.ts", "tests/corpus/1573-promisify-execfile-env-spread.ts", "tests/corpus/2912-promisify-fs-readfile.ts", "tests/corpus/5625-promisify-exec.ts", "tests/corpus/5626-promisify-forms.ts", "tests/fixtures/node-types/promisify-fs.ts", "tests/corpus/5628-promisify-timers.ts", "tests/fixtures/node-types/promisify-timers.ts", "tests/corpus/5629-promisify-stream.ts", "tests/corpus/5630-promisify-zlib.ts", "tests/fixtures/node-types/promisify-zlib.ts", "tests/corpus/5631-promisify-crypto.ts", "tests/fixtures/node-types/promisify-crypto.ts", "tests/corpus/5632-promisify-dns.ts", "tests/fixtures/node-types/promisify-dns.ts", "tests/harness/project-config.test.ts"] },
{ "chapter": "typescript", "symbols": ["type"], "status": "supported", "evidence": ["tests/corpus/2702-type-import-link-static/main.ts", "tests/corpus/2703-type-import-link-static-reexport/main.ts", "tests/corpus/2704-type-import-link-static-success/main.ts", "tests/corpus/2707-type-import-link-static-default-reexport/main.ts", "tests/harness/errors.test.ts"] },
@@ -390,12 +401,13 @@
{ "symbols": ["crypto.createHash", "Hash", "hash.update", "hash.digest", "hash.copy", "crypto.createHmac", "Hmac", "hmac.update", "hmac.digest", "crypto.hash", "crypto.pbkdf2", "crypto.pbkdf2Sync"], "status": "partial", "evidence": ["tests/corpus/5760-crypto-hash-stream.ts", "tests/corpus/5768-crypto-hash-unknown.ts", "tests/corpus/5769-crypto-hash-unknown.cjs", "tests/corpus/5761-crypto-hash-stream-states.ts", "tests/corpus/5762-crypto-hash-stream.cjs", "tests/corpus/2890-crypto-static-utility.ts", "tests/corpus/node24-crypto-sha2.ts", "tests/corpus/5435-crypto-hash-algorithms.ts", "tests/corpus/5436-crypto-hash-names.js", "tests/corpus/5437-crypto-hmac-algorithms.ts", "tests/corpus/5438-crypto-kdf-digests.ts", "tests/corpus/5440-crypto-hash-encodings.js", "tests/corpus/5441-crypto-hash-output-length.ts", "tests/harness/npm-static.test.ts"] },
{ "symbols": ["crypto.randomFill", "crypto.randomFillSync", "crypto.randomUUID", "crypto.randomUUIDv7"], "status": "supported", "evidence": ["tests/corpus/5442-crypto-random-values.ts", "tests/corpus/5443-crypto-random-arguments.js", "tests/corpus/1358-crypto-random.ts"] },
{ "symbols": ["crypto.createSecretKey", "crypto.generateKey", "crypto.generateKeySync", "KeyObject", "keyObject.export", "keyObject.equals", "keyObject.symmetricKeySize", "keyObject.type"], "status": "partial", "evidence": ["tests/corpus/5445-crypto-secret-key-objects.ts", "tests/corpus/5446-crypto-secret-key-errors.js"] },
{ "symbols": ["crypto.getRandomValues"], "status": "supported", "evidence": ["tests/corpus/6204-crypto-get-random-values.ts", "tests/corpus/6205-crypto-get-random-values.js"] },
{ "symbols": ["crypto.timingSafeEqual"], "status": "supported", "evidence": ["tests/corpus/5444-crypto-timing-safe-equal.js", "tests/corpus/2890-crypto-static-utility.ts"] },
{ "symbols": ["crypto.randomInt"], "status": "partial", "evidence": ["tests/corpus/5442-crypto-random-values.ts", "tests/corpus/2890-crypto-static-utility.ts"] },
{ "symbols": ["crypto.hkdf", "crypto.hkdfSync", "crypto.scrypt", "crypto.scryptSync"], "status": "partial", "evidence": ["tests/corpus/node24-crypto-kdf.ts", "tests/corpus/node24-crypto-kdf-errors.ts", "tests/corpus/node24-crypto-kdf-options.ts", "tests/corpus/5438-crypto-kdf-digests.ts"] },
{ "symbols": ["EventEmitter", "emitter.on", "emitter.addListener", "emitter.once", "emitter.prependListener", "emitter.prependOnceListener", "emitter.off", "emitter.removeListener", "emitter.removeAllListeners", "emitter.emit", "emitter.listenerCount", "emitter.listeners", "emitter.rawListeners", "emitter.eventNames", "emitter.setMaxListeners", "emitter.getMaxListeners", "events.once"], "status": "partial", "evidence": ["tests/corpus/1677-emitter-listeners.ts", "tests/corpus/2620-ee-override-once-order.ts"] },
{ "symbols": ["events.getEventListeners", "events.getMaxListeners", "events.listenerCount", "events.setMaxListeners", "events.defaultMaxListeners"], "status": "partial", "evidence": ["tests/corpus/5885-events-max-listener-helpers.ts", "tests/corpus/5846-events-esm-default-max.ts", "tests/corpus/5847-events-cjs-default-max.cjs"] },
{ "symbols": ["AsyncLocalStorage", "asyncLocalStorage.run", "asyncLocalStorage.exit", "asyncLocalStorage.getStore", "asyncLocalStorage.enterWith", "asyncLocalStorage.disable"], "status": "partial", "evidence": ["tests/corpus/2213-async-local-storage.cjs"] },
{ "symbols": ["AsyncLocalStorage", "asyncLocalStorage.run", "asyncLocalStorage.exit", "asyncLocalStorage.getStore", "asyncLocalStorage.enterWith", "asyncLocalStorage.disable"], "status": "partial", "evidence": ["tests/corpus/2213-async-local-storage.cjs", "tests/corpus/5815-als-callback-context.ts"] },
{ "symbols": ["StringDecoder", "stringDecoder.write", "stringDecoder.end"], "status": "partial", "evidence": ["tests/corpus/1662-string-decoder-encodings.ts", "tests/corpus/node24-string-decoder.ts", "tests/corpus/5882-string-decoder-runtime-encoding.ts"] },
{ "symbols": ["setImmediate", "setInterval", "setTimeout", "clearImmediate", "clearInterval", "clearTimeout", "timersPromises.setTimeout", "timersPromises.setImmediate", "timersPromises.setInterval", "immediate.hasRef", "immediate.ref", "immediate.unref", "timeout.hasRef", "timeout.ref", "timeout.unref", "timeout.refresh"], "status": "partial", "evidence": ["tests/corpus/1804-timers-module.ts", "tests/corpus/1805-timer-callback-args.ts", "tests/corpus/2093-timers-promises.ts", "tests/corpus/2852-timers-promises-interval.ts", "tests/corpus/5830-timers-promises-signal.ts"] },
{ "symbols": ["timersPromises.scheduler.wait", "timersPromises.scheduler.yield"], "status": "partial", "evidence": ["tests/corpus/5839-timers-promises-scheduler.ts", "tests/corpus/5889-timers-scheduler-signal.ts"] },
+215 -21
View File
@@ -511,14 +511,119 @@ declare var DOMException: {
readonly prototype: DOMException;
};
/* Capability-gated globals harness code probes behind feature guards
* (globalThis.crypto under a hasCrypto check, the SQLite-backed storage
* pair): typed `unknown` — no lowering exists, the guard is expected to
* be false in a compiled binary, and any reached use fences per site. */
declare var crypto: unknown;
/* WebCrypto's HMAC slice (scr_webcrypto.c): crypto.subtle's importKey for
* raw HMAC keys, HMAC sign/verify and the SHA-1/SHA-2 digests, typed as
* exactly that surface (the declarations mirror @types/node's webcrypto
* shapes). Every other SubtleCrypto method and crypto member is absent,
* so a use is an honest type error. CryptoKey values are native handles
* whose four members read through the handle. */
declare var crypto: Crypto;
/* The Crypto and SubtleCrypto constructors (feature probes only) stay
* `unknown`; the values keep this declaration order. */
declare var Crypto: unknown;
declare var CryptoKey: unknown;
declare var CryptoKey: {
prototype: CryptoKey;
new (): CryptoKey;
};
declare var SubtleCrypto: unknown;
type KeyUsage =
| "encrypt"
| "decrypt"
| "sign"
| "verify"
| "deriveKey"
| "deriveBits"
| "encapsulateBits"
| "decapsulateBits"
| "encapsulateKey"
| "decapsulateKey"
| "wrapKey"
| "unwrapKey";
type KeyType = "private" | "public" | "secret";
interface Algorithm {
name: string;
}
type AlgorithmIdentifier = Algorithm | string;
type HashAlgorithmIdentifier = AlgorithmIdentifier;
interface HmacImportParams extends Algorithm {
hash: HashAlgorithmIdentifier;
length?: number;
}
interface KeyAlgorithm {
name: string;
}
interface HmacKeyAlgorithm extends KeyAlgorithm {
hash: KeyAlgorithm;
length: number;
}
interface CryptoKey {
readonly algorithm: KeyAlgorithm;
readonly extractable: boolean;
readonly type: KeyType;
readonly usages: KeyUsage[];
}
type KeyFormat =
| "jwk"
| "pkcs8"
| "raw"
| "raw-private"
| "raw-public"
| "raw-secret"
| "raw-seed"
| "spki";
interface JsonWebKey {
alg?: string;
crv?: string;
d?: string;
dp?: string;
dq?: string;
e?: string;
ext?: boolean;
k?: string;
key_ops?: string[];
kty?: string;
n?: string;
p?: string;
q?: string;
qi?: string;
use?: string;
x?: string;
y?: string;
}
/* The 'jwk' form type-checks like @types/node's; the compiler refuses it
* (the JsonWebKey import is not implemented). */
interface SubtleCrypto {
digest(algorithm: AlgorithmIdentifier, data: BufferSource): Promise<ArrayBuffer>;
importKey(
format: "jwk",
keyData: JsonWebKey,
algorithm: AlgorithmIdentifier | HmacImportParams,
extractable: boolean,
keyUsages: readonly KeyUsage[],
): Promise<CryptoKey>;
importKey(
format: Exclude<KeyFormat, "jwk">,
keyData: BufferSource,
algorithm: AlgorithmIdentifier | HmacImportParams,
extractable: boolean,
keyUsages: readonly KeyUsage[],
): Promise<CryptoKey>;
sign(algorithm: AlgorithmIdentifier, key: CryptoKey, data: BufferSource): Promise<ArrayBuffer>;
verify(
algorithm: AlgorithmIdentifier,
key: CryptoKey,
signature: BufferSource,
data: BufferSource,
): Promise<boolean>;
}
interface Crypto {
readonly subtle: SubtleCrypto;
}
/* Capability-gated globals harness code probes behind feature guards
* (the SQLite-backed storage pair): typed `unknown` — no lowering exists,
* the guard is expected to be false in a compiled binary, and any reached
* use fences per site. */
declare var localStorage: unknown;
declare var sessionStorage: unknown;
@@ -1493,6 +1598,51 @@ declare module "node:fs" {
readonly COPYFILE_EXCL: number;
readonly COPYFILE_FICLONE: number;
readonly COPYFILE_FICLONE_FORCE: number;
readonly UV_FS_SYMLINK_DIR: number;
readonly UV_FS_SYMLINK_JUNCTION: number;
readonly UV_DIRENT_UNKNOWN: number;
readonly UV_DIRENT_FILE: number;
readonly UV_DIRENT_DIR: number;
readonly UV_DIRENT_LINK: number;
readonly UV_DIRENT_FIFO: number;
readonly UV_DIRENT_SOCKET: number;
readonly UV_DIRENT_CHAR: number;
readonly UV_DIRENT_BLOCK: number;
readonly UV_FS_O_FILEMAP: number;
readonly UV_FS_O_TEMPORARY: number;
readonly UV_FS_O_SHORT_LIVED: number;
readonly UV_FS_O_SEQUENTIAL: number;
readonly UV_FS_O_RANDOM: number;
readonly UV_FS_COPYFILE_EXCL: number;
readonly UV_FS_COPYFILE_FICLONE: number;
readonly UV_FS_COPYFILE_FICLONE_FORCE: number;
readonly S_IFMT: number;
readonly S_IFREG: number;
readonly S_IFDIR: number;
readonly S_IFCHR: number;
readonly S_IFBLK: number;
readonly S_IFIFO: number;
readonly S_IFLNK: number;
readonly S_IFSOCK: number;
readonly O_NOCTTY: number;
readonly O_DIRECTORY: number;
readonly O_NOATIME: number;
readonly O_SYNC: number;
readonly O_DSYNC: number;
readonly O_SYMLINK: number;
readonly O_DIRECT: number;
readonly S_IRWXU: number;
readonly S_IRUSR: number;
readonly S_IWUSR: number;
readonly S_IXUSR: number;
readonly S_IRWXG: number;
readonly S_IRGRP: number;
readonly S_IWGRP: number;
readonly S_IXGRP: number;
readonly S_IRWXO: number;
readonly S_IROTH: number;
readonly S_IWOTH: number;
readonly S_IXOTH: number;
};
/* A stat(2) snapshot (statSync follows symlinks, lstatSync does not —
* Node's split) — immutable; the supported surface is exactly these
@@ -2557,6 +2707,23 @@ declare module "crypto" {
a: ArrayBufferView | ArrayBufferLike,
b: ArrayBufferView | ArrayBufferLike,
): boolean;
/* WebCrypto's getRandomValues (also the webcrypto object's): integer
* typed arrays, filled in place and returned. The crypto GLOBAL stays
* unknown in fallback builds. */
type RandomValuesArray =
| Int8Array
| Uint8Array
| Uint8ClampedArray
| Int16Array
| Uint16Array
| Int32Array
| Uint32Array
| BigInt64Array
| BigUint64Array;
export function getRandomValues<T extends RandomValuesArray>(typedArray: T): T;
export const webcrypto: {
getRandomValues<T extends RandomValuesArray>(typedArray: T): T;
};
export function randomFillSync<T extends ArrayBufferView | ArrayBufferLike>(
buffer: T,
offset?: number,
@@ -3405,8 +3572,10 @@ declare module "node:test" {
readonly tags: readonly string[];
readonly assert: TestContextAssert;
readonly mock: MockTracker;
test(name: string, fn?: TestFn): Promise<void>;
test(name: string, options: TestOptions, fn?: TestFn): Promise<void>;
test(name?: string, fn?: TestFn): Promise<void>;
test(name?: string, options?: TestOptions, fn?: TestFn): Promise<void>;
test(options?: TestOptions, fn?: TestFn): Promise<void>;
test(fn?: TestFn): Promise<void>;
skip(message?: string): void;
todo(message?: string): void;
diagnostic(message: string): void;
@@ -3436,23 +3605,44 @@ declare module "node:test" {
match(value: string, regExp: RegExp, message?: string): void;
doesNotMatch(value: string, regExp: RegExp, message?: string): void;
}
function test(name: string, fn?: TestFn): Promise<void>;
function test(name: string, options: TestOptions, fn?: TestFn): Promise<void>;
/* The name, options and function are each optional, as in Node: a test
* without a name takes the function's name, or "<anonymous>". */
function test(name?: string, fn?: TestFn): Promise<void>;
function test(name?: string, options?: TestOptions, fn?: TestFn): Promise<void>;
function test(options?: TestOptions, fn?: TestFn): Promise<void>;
function test(fn?: TestFn): Promise<void>;
namespace test {
function skip(name: string, fn?: TestFn): Promise<void>;
function skip(name: string, options: TestOptions, fn?: TestFn): Promise<void>;
function todo(name: string, fn?: TestFn): Promise<void>;
function todo(name: string, options: TestOptions, fn?: TestFn): Promise<void>;
function only(name: string, fn?: TestFn): Promise<void>;
function only(name: string, options: TestOptions, fn?: TestFn): Promise<void>;
function skip(name?: string, fn?: TestFn): Promise<void>;
function skip(name?: string, options?: TestOptions, fn?: TestFn): Promise<void>;
function skip(options?: TestOptions, fn?: TestFn): Promise<void>;
function skip(fn?: TestFn): Promise<void>;
function todo(name?: string, fn?: TestFn): Promise<void>;
function todo(name?: string, options?: TestOptions, fn?: TestFn): Promise<void>;
function todo(options?: TestOptions, fn?: TestFn): Promise<void>;
function todo(fn?: TestFn): Promise<void>;
function only(name?: string, fn?: TestFn): Promise<void>;
function only(name?: string, options?: TestOptions, fn?: TestFn): Promise<void>;
function only(options?: TestOptions, fn?: TestFn): Promise<void>;
function only(fn?: TestFn): Promise<void>;
}
const it: typeof test;
function describe(name: string, fn?: SuiteFn): void;
function describe(name: string, options: TestOptions, fn?: SuiteFn): void;
function describe(name?: string, fn?: SuiteFn): void;
function describe(name?: string, options?: TestOptions, fn?: SuiteFn): void;
function describe(options?: TestOptions, fn?: SuiteFn): void;
function describe(fn?: SuiteFn): void;
namespace describe {
function skip(name: string, fn?: SuiteFn): void;
function todo(name: string, fn?: SuiteFn): void;
function only(name: string, fn?: SuiteFn): void;
function skip(name?: string, fn?: SuiteFn): void;
function skip(name?: string, options?: TestOptions, fn?: SuiteFn): void;
function skip(options?: TestOptions, fn?: SuiteFn): void;
function skip(fn?: SuiteFn): void;
function todo(name?: string, fn?: SuiteFn): void;
function todo(name?: string, options?: TestOptions, fn?: SuiteFn): void;
function todo(options?: TestOptions, fn?: SuiteFn): void;
function todo(fn?: SuiteFn): void;
function only(name?: string, fn?: SuiteFn): void;
function only(name?: string, options?: TestOptions, fn?: SuiteFn): void;
function only(options?: TestOptions, fn?: SuiteFn): void;
function only(fn?: SuiteFn): void;
}
const suite: typeof describe;
function before(fn: HookFn): void;
@@ -3527,6 +3717,9 @@ declare module "node:test" {
[Symbol.dispose](): void;
}
const mock: MockTracker;
/* The context of the test, suite or hook running in the current async
* context; undefined outside one. */
function getTestContext(): TestContext | SuiteContext | undefined;
/* Declared for typechecking only — every use fences (no lowering).
* `assert` is the module-level custom-assertion registry (v24's
* `const { assert } = require('node:test')`), not TestContext.assert. */
@@ -3544,6 +3737,7 @@ declare module "node:test" {
afterEach,
mock,
assert,
getTestContext,
run,
snapshot,
TestContext,
@@ -3,11 +3,13 @@ import { type IrModule } from "../ir/ir.js";
import { moduleRuntimeFeatures } from "../ir/runtime-features.js";
import { hasForeignFfiCallback } from "./ffi-callbacks.js";
import type { NativeLinkFeatures } from "./native-link-info.js";
import { youngNurseryRequested } from "./young-nursery.js";
export function executableLinkFeatures(mod: IrModule): NativeLinkFeatures {
const features = moduleRuntimeFeatures(mod);
return {
...(mod.workers ? { workers: true } : {}),
...(youngNurseryRequested() && mod.lib === undefined ? { youngNursery: true } : {}),
regex: features.regex,
copying: features.copying,
textDecoderLegacy: features.legacyTextDecoder,
@@ -38,5 +40,6 @@ export function executableLinkFeatures(mod: IrModule): NativeLinkFeatures {
tls: features.tls,
tlsCa: features.tlsCa,
cipher: features.cipher,
webcrypto: features.webcrypto,
};
}
@@ -52,6 +52,7 @@ export async function compilePackedLibrary(
nodeTest: false,
tls: false,
cipher: false,
webcrypto: false,
tlsCa: false,
};
const selection = await loadRuntimePack({
@@ -42,5 +42,10 @@ export function borrowsJsonInputs(fn: string): boolean {
* loop. Runtime steps borrow that state but may invoke indexed getters, so
* this permits stable-local borrowing without preserving reference edges. */
export function borrowsIteratorInputs(fn: string): boolean {
return fn === "dyn.iteratorCanStep" || fn === "dyn.iteratorStep" || fn === "dyn.iteratorStepDone";
return (
fn === "dyn.iteratorCanStep" ||
fn === "dyn.iteratorStep" ||
fn === "dyn.iteratorStepDone" ||
fn === "dyn.collectionIteratorIndex"
);
}
@@ -1,5 +1,5 @@
import { expect, test } from "vitest";
import { F64, STRING, VOID, type IrModule } from "../../ir/ir.js";
import { DYN, F64, STRING, VOID, type IrModule } from "../../ir/ir.js";
import { buildClassGraph, classMembershipIntervals, emitClassMembershipHelper } from "./classes.js";
import { emitLlvmModule } from "./emitter.js";
import {
@@ -316,3 +316,52 @@ test("unconstructed narrowing layouts may omit abstract slots while live classes
});
expect(() => emitLlvmModule(mod)).toThrow("no implementation of run for Dormant");
});
test("the young-in nursery flag adds the nursery entry to traced constructors only", () => {
const linked = { kind: "object" as const, className: "Linked" };
const mod: IrModule = {
irVersion: 16,
sourceFile: loc.file,
entry: "main",
functions: [{ name: "main", params: [], returnType: VOID, locals: [], body: [], loc }],
classes: [
{ name: "Scalar", fields: [{ name: "value", type: F64 }], methods: [], loc },
// A dyn field can reach anything: the class carries a collector header.
{
name: "Linked",
fields: [
{ name: "next", type: linked },
{ name: "hook", type: DYN },
],
methods: [],
loc,
},
],
};
const helper = (llvm: string, name: string): string => {
const start = llvm.indexOf(`define internal ptr @${name}(`);
expect(start).toBeGreaterThanOrEqual(0);
return llvm.slice(start, llvm.indexOf("\n}\n", start));
};
const saved = process.env["SCRIPTC_YOUNG_NURSERY"];
try {
delete process.env["SCRIPTC_YOUNG_NURSERY"];
const off = emitLlvmModule(mod, { pointerBits: 64 });
expect(off).not.toContain("scr_yin");
process.env["SCRIPTC_YOUNG_NURSERY"] = "1";
const on = emitLlvmModule(mod, { pointerBits: 64 });
const linkedNew = helper(on, mangleClassNew("Linked"));
expect(linkedNew).toContain("@scr_yin_skip");
expect(linkedNew).toContain("call void @scr_yin_enter_slow(ptr %o)");
expect(linkedNew).toContain("store i8 3, ptr %ye.genp");
// An acyclic class has no collector header and never enters.
expect(helper(on, mangleClassNew("Scalar"))).not.toContain("scr_yin");
// Targets without the inline allocator call the whole entry.
const narrow = emitLlvmModule(mod, { pointerBits: 32 });
expect(helper(narrow, mangleClassNew("Linked"))).toContain("call void @scr_yin_enter(ptr %o)");
} finally {
if (saved === undefined) delete process.env["SCRIPTC_YOUNG_NURSERY"];
else process.env["SCRIPTC_YOUNG_NURSERY"] = saved;
}
});
+28 -4
View File
@@ -11,6 +11,7 @@ import {
RUNTIME_EVENT_TARGET_CLASS,
RUNTIME_ERROR_CLASSES,
RUNTIME_STREAM_CLASSES,
classValueConstructor,
} from "../../ir/ir.js";
import { streamRooted, undefinedArmTag } from "../../ir/analysis.js";
import {
@@ -51,6 +52,7 @@ import {
retainBody,
traceAdapter,
type ShapeHost,
youngEntryLines,
} from "./shapes.js";
/** One virtual method slot of a hierarchy: the ROOT-MOST declaring class
@@ -850,6 +852,11 @@ export function emitClassShapes(
);
}
nw.push(...undefFieldInits(host, meta, nullable));
// Young instances: program-owned traced classes. Classes rooted at a
// runtime base (errors, emitters, events, streams) carry runtime state
// and stay with trial deletion, like every runtime family.
if (traced && !lifetime && (!meta.hierarchy || meta.root.def.runtime !== true))
nw.push(...youngEntryLines(host));
nw.push(` ret ptr ${meta.hierarchy ? "%o.vt" : "%o"}`, `}`, ``);
defs.push(...nw);
@@ -926,14 +933,32 @@ export function emitClassObjDefs(
`llvm emitter bug: class object for ${className} names unknown family ${meta.def.genericOf ?? ""}`,
);
}
const ctor = fnByName.get(`%${className}.constructor`);
const valueCtor = classValueConstructor(meta.def);
const ctor = fnByName.get(valueCtor.fn);
if (!ctor)
throw new InternalCompilerError(
`llvm emitter bug: class object for ${className} without a constructor`,
);
const params = ctor.params.slice(1);
const params = ctor.params.slice(valueCtor.firstArg);
const paramDecls = params.map((p, i) => `${llType(p.type)} %a${i}`).join(", ");
const ctorArgs = params.map((p, i) => `${llType(p.type)} %a${i}`);
const classObj =
`@${mangleClassObj(className)} = internal ${threadLocal ? "thread_local " : ""}global %ScrClassObj ` +
`{ ${host.sizeType} -1, ${host.sizeType} ${intervalMeta.pre}, ${host.sizeType} ${intervalMeta.post}, ptr @${mangleCtorThunk(className)}, ptr ${nameSym}, ${host.sizeType} 0, ${host.sizeType} ${meta.def.jsLength ?? (meta.def.runtime ? 1 : 0)}, ptr null, ptr null } ; class ${className}`;
if (meta.def.runtime) {
// A runtime-provided class: %C.construct allocates through the
// runtime and returns the instance.
out.push(
`define internal ptr @${mangleCtorThunk(className)}(ptr %class${paramDecls ? ", " + paramDecls : ""}) ${FN_ATTRS} { ; construct thunk ${className}`,
`entry:`,
` %o = call ptr @${mangleFunction(valueCtor.fn)}(${ctorArgs.join(", ")})`,
` ret ptr %o`,
`}`,
classObj,
``,
);
continue;
}
if (meta.def.localCaptures !== undefined)
host.declare(`declare ptr @scr_classobj_retain_v(ptr)`);
const localBaseInits: string[] = [];
@@ -992,8 +1017,7 @@ export function emitClassObjDefs(
` call void @${mangleFunction(`%${className}.constructor`)}(${[`ptr %r`, ...ctorArgs].join(", ")})`,
` ret ptr %o`,
`}`,
`@${mangleClassObj(className)} = internal ${threadLocal ? "thread_local " : ""}global %ScrClassObj ` +
`{ ${host.sizeType} -1, ${host.sizeType} ${intervalMeta.pre}, ${host.sizeType} ${intervalMeta.post}, ptr @${mangleCtorThunk(className)}, ptr ${nameSym}, ${host.sizeType} 0, ${host.sizeType} ${meta.def.jsLength ?? 0}, ptr null, ptr null } ; class ${className}`,
classObj,
``,
);
}
@@ -257,7 +257,7 @@ export function emitDenseSlotUnlink(
B.condBr(same, swap, done);
B.startBlock(swap);
B.line(`store ptr ${placeholder}, ptr ${valuePtr}${host.fieldAliasAttachment(valuePtr)}`);
host.releaseValue(current, element);
host.releaseHeap(current, element);
B.br(done);
B.startBlock(done);
}
@@ -315,7 +315,7 @@ export function emitDenseArrayGet(
fastLabel = B.newLabel("arr.get.ref");
B.condBr(nonnull, fastLabel, slow);
B.startBlock(fastLabel);
fast = retain ? host.retainValue(raw, element) : raw;
fast = retain ? host.retainFrame(raw, element) : raw;
}
B.br(join);
B.startBlock(slow);
@@ -493,7 +493,7 @@ export function emitDenseArraySet(
const releaseLabel = B.newLabel("arr.set.release");
B.condBr(hadValue, releaseLabel, join);
B.startBlock(releaseLabel);
host.releaseValue(old, element);
host.releaseHeap(old, element);
}
B.br(join);
// Below the bound the slot holds a present value: replace it, leaving
@@ -508,7 +508,7 @@ export function emitDenseArraySet(
);
}
storeValue(filledValuePtr);
if (filledOld) host.releaseValue(filledOld, element);
if (filledOld) host.releaseHeap(filledOld, element);
B.br(join);
B.startBlock(slow);
call(" cold");
+21 -69
View File
@@ -4014,14 +4014,9 @@ export class LlDyn {
const name = "sc_dyn_iter_n";
this.dynBuilders.set(memoKey, name);
const host = this.host;
host.declare(`declare void @scr_jb_init(ptr)`);
host.declare(`declare ptr @scr_jb_finish(ptr)`);
host.declare(`declare void @scr_jb_puts(ptr, ptr)`);
host.declare(`declare void @scr_throw_error(i32, ptr)`);
host.declare(`declare ptr @scr_dyn_new_arr()`);
host.declare(`declare void @scr_dyn_arr_push(ptr, ptr)`);
host.declare(`declare ptr @scr_dyn_new_num(double)`);
host.declare(`declare ptr @scr_f64_to_scrstr(double)`);
host.declare(`declare void @scr_str_release(ptr)`);
const B = new BlockBuilder();
const kd = this.kindOf(B, "%d");
@@ -4033,6 +4028,21 @@ export class LlDyn {
const lNext = B.newLabel("din.nt");
B.condBr(isTyped, lTyped, lNext);
B.startBlock(lTyped);
// Compiled generators step through the iterator protocol.
host.declare(`declare i1 @scr_dyn_iterates_by_protocol(ptr)`);
host.declare(`declare ptr @scr_dyn_iter_take(ptr, ${host.sizeType}, ptr)`);
const protocol = B.tmp();
const lProtocol = B.newLabel("din.pr");
const lView = B.newLabel("din.vw");
B.line(`${protocol} = call i1 @scr_dyn_iterates_by_protocol(ptr %d)`);
B.condBr(protocol, lProtocol, lView);
B.startBlock(lProtocol);
const stepped = B.tmp();
B.line(
`${stepped} = call ptr @scr_dyn_iter_take(ptr %d, ${host.sizeType} %n, ptr %spelling)`,
);
B.terminate(`ret ptr ${stepped}`);
B.startBlock(lView);
host.declare(`declare ptr @scr_dyn_typed_ref_materialize(ptr)`);
host.declare(`declare void @scr_dyn_release_v(ptr)`);
const materialized = B.tmp();
@@ -4057,71 +4067,13 @@ export class LlDyn {
const lGo = B.newLabel("din.g");
const lThrow = B.newLabel("din.t");
B.condBr(ok, lGo, lThrow);
// V8's exact not-iterable TypeError.
// Other iterables step through the iterator protocol; non-iterables
// throw V8's exact not-iterable TypeError.
B.startBlock(lThrow);
const buf = "%dib";
B.entryAllocas.push(`${buf} = alloca %ScrJsonBuf`);
B.line(`call void @scr_jb_init(ptr ${buf})`);
const hasSpelling = B.tmp();
B.line(`${hasSpelling} = icmp ne ptr %spelling, null`);
const named = B.newLabel("din.named"),
unnamed = B.newLabel("din.unnamed");
B.condBr(hasSpelling, named, unnamed);
B.startBlock(named);
B.line(`call void @scr_jb_puts(ptr ${buf}, ptr %spelling)`);
const namedMessage = B.tmp();
B.line(`${namedMessage} = call ptr @scr_jb_finish(ptr ${buf})`);
B.line(`call void @scr_throw_error(i32 1, ptr ${namedMessage})`);
B.terminate(`ret ptr null`);
B.startBlock(unnamed);
const lU = B.newLabel("din.u");
const lN = B.newLabel("din.n");
const lB2 = B.newLabel("din.b");
const lNum = B.newLabel("din.m");
const lF = B.newLabel("din.f");
const lDef = B.newLabel("din.d");
const lTail = B.newLabel("din.e");
B.terminate(
`switch i32 ${kd}, label %${lDef} [ i32 ${DYN_KIND.UNDEF}, label %${lU} i32 ${DYN_KIND.NULL}, label %${lN} i32 ${DYN_KIND.BOOL}, label %${lB2} i32 ${DYN_KIND.NUM}, label %${lNum} i32 ${DYN_KIND.FUNC}, label %${lF} ]`,
);
B.startBlock(lU);
this.puts(B, buf, "undefined");
B.br(lTail);
B.startBlock(lN);
this.puts(B, buf, "object null");
B.br(lTail);
B.startBlock(lB2);
{
const bv = this.boolOf(B, "%d");
const s = B.tmp();
B.line(
`${s} = select i1 ${bv}, ptr ${host.cstr("boolean true")}, ptr ${host.cstr("boolean false")}`,
);
B.line(`call void @scr_jb_puts(ptr ${buf}, ptr ${s})`);
B.br(lTail);
}
B.startBlock(lNum);
{
this.puts(B, buf, "number ");
const x = this.payloadOf(B, "%d", "double");
const s = B.tmp();
B.line(`${s} = call ptr @scr_f64_to_scrstr(double ${x})`);
this.putScrStr(B, buf, s);
B.line(`call void @scr_str_release(ptr ${s})`);
B.br(lTail);
}
B.startBlock(lF);
this.puts(B, buf, "function");
B.br(lTail);
B.startBlock(lDef);
this.puts(B, buf, "object");
B.br(lTail);
B.startBlock(lTail);
this.puts(B, buf, " is not iterable (cannot read property Symbol(Symbol.iterator))");
const msg = B.tmp();
B.line(`${msg} = call ptr @scr_jb_finish(ptr ${buf})`);
B.line(`call void @scr_throw_error(i32 1, ptr ${msg}) ; SCR_ERR_TYPE`);
B.terminate(`ret ptr null`);
host.declare(`declare ptr @scr_dyn_iter_take(ptr, ${host.sizeType}, ptr)`);
const taken = B.tmp();
B.line(`${taken} = call ptr @scr_dyn_iter_take(ptr %d, ${host.sizeType} %n, ptr %spelling)`);
B.terminate(`ret ptr ${taken}`);
B.startBlock(lGo);
const out = B.tmp();
B.line(`${out} = call ptr @scr_dyn_new_arr()`);
+234 -110
View File
@@ -11,6 +11,7 @@ import {
RECORD_SOURCES_GATE,
WEAK_KEYS_GATE,
} from "./alloc.js";
import { youngNurseryRequested } from "../young-nursery.js";
import { hasLoneSurrogate, llvmBytes as llBytes, wtf8Bytes } from "../literals.js";
import { InternalCompilerError } from "../../errors.js";
import { createHash } from "node:crypto";
@@ -56,6 +57,7 @@ import {
typeKey,
STRING,
VOID,
classJsName,
} from "../../ir/ir.js";
import { moduleRuntimeFeatures, moduleUsesMockTimers } from "../../ir/runtime-features.js";
import { captureFreeFunctions, observesStacks } from "../stack-observations.js";
@@ -968,6 +970,14 @@ export class LlEmitter {
/** The program calls mock.timers members (moduleUsesMockTimers): timer
* calls emit scr_test.c's mock-aware entry points. */
readonly mockTimers: boolean;
private schedulesInContextValue: boolean | undefined = undefined;
/** Whether timer and tick scheduling goes through the *_als entry
* points (RuntimeFeatures asyncContext). */
get schedulesInContext(): boolean {
if (this.schedulesInContextValue === undefined)
this.schedulesInContextValue = moduleRuntimeFeatures(this.mod).asyncContext;
return this.schedulesInContextValue;
}
/** Emitted ref-kind resolve thunks for new Promise, interned per inner
* typeKey → thunk symbol. */
readonly resolveThunks = new Map<string, string>();
@@ -1454,6 +1464,7 @@ export class LlEmitter {
...(this.rcMode !== undefined ? { rcMode: this.rcMode } : {}),
...(this.rcHelpersNoinline ? { rcHelpersNoinline: true } : {}),
nextCycKind: () => FIRST_EMITTED_CYC_KIND + this.cycKinds++,
...(youngNurseryRequested() && mod.lib === undefined ? { youngNursery: true } : {}),
unionsById: this.unionsById,
declare: (decl) => this.declare(decl),
needOom: () => this.needOom(),
@@ -3660,13 +3671,30 @@ export class LlEmitter {
}
defs.push(...helper.define(this.sizeType, FN_ATTRS, scanInline), ``);
}
// Ordering also decides inline when the first bytes differ: byte order
// is the plain comparison's order, and the UTF-16 order whenever both
// bytes are ASCII. Callers only test the sign of the result.
for (const fn of ["scr_str_cmp", "scr_str_cmp_u16"]) {
if (!this.decls.has(`declare i32 @${fn}(ptr, ptr)`)) continue;
// Ordering decides inline from the first differing bytes. The bytes
// before them are equal, so both strings are at the same position of a
// character: either both bytes continue characters with the same lead
// byte, or both start a character. Byte order is the UTF-16 order for
// two continuation bytes, and for two lead bytes when one starts a
// character below U+D000, whose UTF-16 unit is below every unit of a
// character with a lead byte of at least 0xED. Between lead bytes of at
// least 0xED, a supplementary character (0xF0..0xF4, a high surrogate
// unit) orders below U+E000..U+FFFF (0xEE, 0xEF), and a lead byte of
// 0xED (U+D000..U+D7FF or a lone surrogate) takes the runtime.
// Differing words without a byte of at least 0xED decide in byte
// order; other differing words decide by their first differing byte,
// and strings shorter than a word by their first byte. Otherwise a
// proven-ASCII string on either side orders by bytes (scr_str_cmp), and
// other pairs call scr_str_cmp_u16_bytes, which only reads the two
// strings, so LLVM can merge repeated comparisons of one pair. Callers
// only test the sign of the result.
if (this.decls.has(`declare i32 @scr_str_cmp_u16(ptr, ptr)`)) {
const fn = "scr_str_cmp_u16";
this.declare("declare i64 @llvm.bswap.i64(i64)");
const utf16 = fn === "scr_str_cmp_u16";
this.declare("declare i64 @llvm.cttz.i64(i64, i1 immarg)");
this.declare("declare i8 @llvm.umin.i8(i8, i8)");
this.declare("declare i32 @scr_str_cmp(ptr, ptr)");
this.declare("declare i32 @scr_str_cmp_u16_bytes(ptr, ptr)");
defs.push(
`define internal i32 @${fn.replace(/^scr_/, "sc_")}(ptr %a, ptr %b) ${FN_ATTRS} {`,
`entry:`,
@@ -3681,8 +3709,7 @@ export class LlEmitter {
` %bempty = icmp eq ${this.sizeType} %blen, 0`,
` %empty = or i1 %aempty, %bempty`,
` br i1 %empty, label %order, label %words`,
// Both at least 8 bytes: the first differing byte of one word pair.
// Byte-swapped little-endian words compare in byte order.
// Both at least 8 bytes: the first word of each.
`words:`,
` %along = icmp uge ${this.sizeType} %alen, 8`,
` %blong = icmp uge ${this.sizeType} %blen, 8`,
@@ -3694,22 +3721,7 @@ export class LlEmitter {
` %aw = load i64, ptr %awp, align 1`,
` %bw = load i64, ptr %bwp, align 1`,
` %wdiffer = icmp ne i64 %aw, %bw`,
` br i1 %wdiffer, label %wdiff, label %wsame`,
`wdiff:`,
...(utf16
? [
` %wbits = or i64 %aw, %bw`,
` %whigh = and i64 %wbits, -9187201950435737472`,
` %wascii = icmp eq i64 %whigh, 0`,
` br i1 %wascii, label %wordorder, label %order`,
]
: [` br label %wordorder`]),
`wordorder:`,
` %asw = call i64 @llvm.bswap.i64(i64 %aw)`,
` %bsw = call i64 @llvm.bswap.i64(i64 %bw)`,
` %wbelow = icmp ult i64 %asw, %bsw`,
` %wsign = select i1 %wbelow, i32 -1, i32 1`,
` ret i32 %wsign`,
` br i1 %wdiffer, label %differ, label %wsame`,
// Equal first words and one length of at most 16 bytes: the word
// ending at that length covers the rest, and any difference lies
// past the equal first word.
@@ -3725,42 +3737,89 @@ export class LlEmitter {
` %at = load i64, ptr %atp, align 1`,
` %bt = load i64, ptr %btp, align 1`,
` %tdiffer = icmp ne i64 %at, %bt`,
` br i1 %tdiffer, label %tdiff, label %equal`,
`tdiff:`,
...(utf16
? [
` %tbits = or i64 %at, %bt`,
` %thigh = and i64 %tbits, -9187201950435737472`,
` %tascii = icmp eq i64 %thigh, 0`,
` br i1 %tascii, label %tailorder, label %order`,
]
: [` br label %tailorder`]),
`tailorder:`,
` %ats = call i64 @llvm.bswap.i64(i64 %at)`,
` %bts = call i64 @llvm.bswap.i64(i64 %bt)`,
` %tbelow = icmp ult i64 %ats, %bts`,
` %tsign = select i1 %tbelow, i32 -1, i32 1`,
` ret i32 %tsign`,
` br i1 %tdiffer, label %differ, label %equal`,
`first:`,
` %adata = getelementptr inbounds %ScrStr, ptr %a, i32 1`,
` %bdata = getelementptr inbounds %ScrStr, ptr %b, i32 1`,
` %x = load i8, ptr %adata`,
` %y = load i8, ptr %bdata`,
` %differ = icmp ne i8 %x, %y`,
...(utf16
? [
` %bits = or i8 %x, %y`,
` %ascii = icmp sge i8 %bits, 0`,
` %decide = and i1 %differ, %ascii`,
]
: [` %decide = and i1 %differ, true`]),
` br i1 %decide, label %byte, label %order`,
` %fdiffer = icmp ne i8 %x, %y`,
` br i1 %fdiffer, label %bytes, label %order`,
// Two differing words. When neither has a byte of at least 0xED
// (a byte b is when its high bit and bit 7 of (b & 0x7f) + 0x13 are
// set), byte-swapped little-endian words compare in byte order.
// Otherwise the lowest set byte of their xor is the first differing
// byte.
`differ:`,
` %dx = phi i64 [ %aw, %word ], [ %at, %tail ]`,
` %dy = phi i64 [ %bw, %word ], [ %bt, %tail ]`,
` %xl = and i64 %dx, 9187201950435737471`,
` %yl = and i64 %dy, 9187201950435737471`,
` %xa = add i64 %xl, 1374463283923456787`,
` %ya = add i64 %yl, 1374463283923456787`,
` %xh = and i64 %dx, %xa`,
` %yh = and i64 %dy, %ya`,
` %hh = or i64 %xh, %yh`,
` %whigh = and i64 %hh, -9187201950435737472`,
` %dlow = icmp eq i64 %whigh, 0`,
` br i1 %dlow, label %wordorder, label %locate`,
`wordorder:`,
` %xsw = call i64 @llvm.bswap.i64(i64 %dx)`,
` %ysw = call i64 @llvm.bswap.i64(i64 %dy)`,
` %wbelow = icmp ult i64 %xsw, %ysw`,
` %wsign = select i1 %wbelow, i32 -1, i32 1`,
` ret i32 %wsign`,
`locate:`,
` %dxor = xor i64 %dx, %dy`,
` %dtz = call i64 @llvm.cttz.i64(i64 %dxor, i1 true)`,
` %dshift = and i64 %dtz, 56`,
` %dxs = lshr i64 %dx, %dshift`,
` %dys = lshr i64 %dy, %dshift`,
` %dxb = trunc i64 %dxs to i8`,
` %dyb = trunc i64 %dys to i8`,
` br label %bytes`,
// The first differing bytes.
`bytes:`,
` %bx = phi i8 [ %x, %first ], [ %dxb, %locate ]`,
` %by = phi i8 [ %y, %first ], [ %dyb, %locate ]`,
` %bmin = call i8 @llvm.umin.i8(i8 %bx, i8 %by)`,
` %blow = icmp ult i8 %bmin, -19`,
` br i1 %blow, label %byte, label %high`,
// Both at least 0xED. A lone surrogate (0xED) takes the runtime;
// supplementary lead bytes (0xF0..0xF4) order below 0xEE and 0xEF
// once 0x10 is subtracted from them.
`high:`,
` %xed = icmp eq i8 %bx, -19`,
` %yed = icmp eq i8 %by, -19`,
` %ed = or i1 %xed, %yed`,
` br i1 %ed, label %order, label %planes`,
`planes:`,
` %xsupp = icmp uge i8 %bx, -16`,
` %ysupp = icmp uge i8 %by, -16`,
` %xlow = add i8 %bx, -16`,
` %ylow = add i8 %by, -16`,
` %xk = select i1 %xsupp, i8 %xlow, i8 %bx`,
` %yk = select i1 %ysupp, i8 %ylow, i8 %by`,
` %kbelow = icmp ult i8 %xk, %yk`,
` %ksign = select i1 %kbelow, i32 -1, i32 1`,
` ret i32 %ksign`,
`byte:`,
` %below = icmp ult i8 %x, %y`,
` %below = icmp ult i8 %bx, %by`,
` %sign = select i1 %below, i32 -1, i32 1`,
` ret i32 %sign`,
`order:`,
` %c = call i32 @${fn}(ptr %a, ptr %b)`,
` %acw.p = getelementptr inbounds %ScrStr, ptr %a, i32 0, i32 2`,
` %bcw.p = getelementptr inbounds %ScrStr, ptr %b, i32 0, i32 2`,
` %acw = load i32, ptr %acw.p`,
` %bcw = load i32, ptr %bcw.p`,
` %cws = or i32 %acw, %bcw`,
` %anyascii = icmp slt i32 %cws, 0`,
` br i1 %anyascii, label %memorder, label %units`,
`memorder:`,
` %m = call i32 @scr_str_cmp(ptr %a, ptr %b)`,
` ret i32 %m`,
`units:`,
` %c = call i32 @scr_str_cmp_u16_bytes(ptr %a, ptr %b) nounwind willreturn memory(argmem: read)`,
` ret i32 %c`,
`equal:`,
` ret i32 0`,
@@ -4636,6 +4695,9 @@ export class LlEmitter {
// An iterator helper's return() before its first next() closes the
// source generator in its first argument slot.
const closesSource = fn.generator.closesSource === true;
if (fn.generator.tag !== undefined) this.declare(`declare void @scr_gen_set_tag(ptr, ptr)`);
if (fn.generator.inheritsTag === true)
this.declare(`declare void @scr_gen_copy_tag(ptr, ptr)`);
if (closesSource) {
const field = lifted ? 1 : 0;
this.declare(`declare void @scr_gen_close(ptr)`);
@@ -4668,6 +4730,12 @@ export class LlEmitter {
settleAsync === null
? ` %gg = call ptr @${genNew}(ptr @${mangleTrampoline(fn.name)}, ptr %ap, ptr @${mangleGenDrop(fn.name)})`
: ` %gg = call ptr @scr_async_gen_new(ptr @${mangleTrampoline(fn.name)}, ptr %ap, ptr @${mangleGenDrop(fn.name)}, ptr @${settleAsync})`,
...(fn.generator.tag !== undefined
? [` call void @scr_gen_set_tag(ptr %gg, ptr ${this.cstr(fn.generator.tag)})`]
: []),
...(fn.generator.inheritsTag === true
? [` call void @scr_gen_copy_tag(ptr %gg, ptr %a${lifted ? 1 : 0})`]
: []),
...(closesSource
? [
` call void @scr_gen_set_unstarted_return(ptr %gg, ptr @${mangleGenSourceClose(fn.name)})`,
@@ -4945,8 +5013,8 @@ export class LlEmitter {
}
}
/** Strike a refcounted temp from its frame: ownership is being moved. */
moveTemp(v: LlValue): void {
/** Strike a refcounted temp from its frame. */
private disownTemp(v: LlValue): void {
if (!isRefCounted(v.type) || this.isImmortal(v.name)) return;
for (let i = this.frames.length - 1; i >= 0; i--) {
const idx = this.frames[i]!.findIndex((e) => e.name === v.name);
@@ -4960,23 +5028,69 @@ export class LlEmitter {
);
}
/* Ownership transfers name their destination. A heap reference is one
* that heap memory holds: a field, an array or map element, a capture
* box, a union box, a module global, a promise or generator slot, the
* exception cell, or any other runtime structure. A frame reference is
* one that a local, a parameter, a return value, a stack object or a
* temporary holds. Both forms of each operation emit the same code. */
/** Ends a temp's frame ownership: its reference moves into heap memory. */
toHeap(v: LlValue): void {
this.disownTemp(v);
}
/** Ends a temp's frame ownership: its reference moves to another frame
* owner (a local or parameter slot, a callee's parameter, a return
* value, a stack object, another temp). */
toFrame(v: LlValue): void {
this.disownTemp(v);
}
/** The retained (+1) read of a refcounted value — type-directed through
* the `_v` table (immortals skip, exactly the C retain calls). */
retainValue(name: string, type: IrType): string {
private retainCall(name: string, type: IrType): string {
if (this.isImmortal(name)) return name;
const t = this.B.tmp();
this.B.line(`${t} = call ptr ${retainSym(this.shapeHost, type)}(ptr ${name})`);
return t;
}
/** A +1 of `name` that heap memory keeps. */
retainHeap(name: string, type: IrType): string {
return this.retainCall(name, type);
}
/** A +1 of `name` that a frame owns. */
retainFrame(name: string, type: IrType): string {
return this.retainCall(name, type);
}
/** The release call for one owned refcounted value — type-directed like
* releaseCallC (all runtime releases are NULL-tolerant). */
releaseValue(name: string, type: IrType): void {
private releaseCall(name: string, type: IrType): void {
if (this.isImmortal(name)) return;
this.B.line(`call void ${releaseSym(this.shapeHost, type)}(ptr ${name})`);
}
releaseFrame(frame: LlValue[]): void {
/** Releases a reference that heap memory held. */
releaseHeap(name: string, type: IrType): void {
this.releaseCall(name, type);
}
/** Releases a reference that a frame owned. */
releaseFrame(name: string, type: IrType): void {
this.releaseCall(name, type);
}
/** A +1 that the runtime made with the heap retain (a box, generator or
* await read) becomes a frame value. Emits nothing; answers `name`. */
adoptHeap(name: string, _type: IrType): string {
return name;
}
/** Releases a statement frame's temps and owned slots. */
releaseTemps(frame: LlValue[]): void {
for (const v of frame) {
if (v.slot) {
const t = this.B.tmp();
@@ -4984,10 +5098,10 @@ export class LlEmitter {
if (v.boxed) {
this.B.line(`call void ${boxReleaseSym(this.shapeHost)}(ptr ${t})`);
} else {
this.releaseValue(t, v.type);
this.releaseFrame(t, v.type);
}
} else {
this.releaseValue(v.name, v.type);
this.releaseFrame(v.name, v.type);
}
}
}
@@ -5012,7 +5126,7 @@ export class LlEmitter {
} else if (e.boxed) {
this.B.line(`call void ${boxReleaseSym(this.shapeHost)}(ptr ${t})`);
} else {
this.releaseValue(t, e.type); // runtime releases are NULL-tolerant
this.releaseFrame(t, e.type); // runtime releases are NULL-tolerant
}
}
}
@@ -5022,7 +5136,7 @@ export class LlEmitter {
* everything whose normal fall-through releases the jump bypasses.
* Release every scope crossed by the control-flow edge. */
private releaseForJump(frameDepth: number, scopeDepth: number): void {
for (let i = this.frames.length - 1; i >= frameDepth; i--) this.releaseFrame(this.frames[i]!);
for (let i = this.frames.length - 1; i >= frameDepth; i--) this.releaseTemps(this.frames[i]!);
for (let i = this.scopes.length - 1; i >= scopeDepth; i--) this.releaseScope(this.scopes[i]!);
}
@@ -5095,7 +5209,7 @@ export class LlEmitter {
const target = this.tryStack[this.tryStack.length - 1];
const frameDepth = target?.frameDepth ?? 0;
const scopeDepth = target?.scopeDepth ?? 0;
for (let i = this.frames.length - 1; i >= frameDepth; i--) this.releaseFrame(this.frames[i]!);
for (let i = this.frames.length - 1; i >= frameDepth; i--) this.releaseTemps(this.frames[i]!);
let terminator: string;
if (target) {
target.used = true;
@@ -5625,7 +5739,7 @@ export class LlEmitter {
if (target.value !== null) {
this.frames.push([]);
for (const read of speculativeReads(target.value)) reads.set(read, this.emitExpr(read));
this.releaseFrame(this.frames.pop()!);
this.releaseTemps(this.frames.pop()!);
}
this.frames.push([]);
let result: string;
@@ -5949,7 +6063,7 @@ export class LlEmitter {
unionExtract(uName: string, unionId: string, arm: IrType): string {
if (arm.kind === "f64" || arm.kind === "procStream") return this.unionGetF64(uName);
if (arm.kind === "bool") return this.unionGetBool(uName);
return this.retainValue(this.unionPeek(uName, unionId), arm);
return this.retainFrame(this.unionPeek(uName, unionId), arm);
}
/** A scalar f64 arm's payload (scr_union_get_f64 inlined). Inline loads
@@ -6060,7 +6174,7 @@ export class LlEmitter {
classObjSym(className: string): string {
if (!this.classObjs.has(className)) {
const meta = this.classMetaOf(className);
this.classObjs.set(className, { nameSym: this.internLiteral(meta.def.jsName ?? "") });
this.classObjs.set(className, { nameSym: this.internLiteral(classJsName(meta.def)) });
}
return mangleClassObj(className);
}
@@ -6191,11 +6305,11 @@ export class LlEmitter {
* in, and the old value is released AFTER the field is overwritten. */
replaceField(ptr: string, type: IrType, inline: boolean, v: LlValue): void {
if (!inline && isRefCounted(type)) {
this.moveTemp(v);
this.toHeap(v);
const old = this.B.tmp();
this.B.line(`${old} = load ptr, ptr ${ptr}`);
this.storeField(ptr, type, v.name, false);
this.releaseValue(old, type);
this.releaseHeap(old, type);
return;
}
this.storeField(ptr, type, v.name, inline);
@@ -6203,7 +6317,7 @@ export class LlEmitter {
/** Stores `v` (from emitFieldValue) into a fresh field slot. */
initField(ptr: string, type: IrType, inline: boolean, v: LlValue): void {
if (!inline && isRefCounted(type)) this.moveTemp(v);
if (!inline && isRefCounted(type)) this.toHeap(v);
this.storeField(ptr, type, v.name, inline);
}
@@ -6369,7 +6483,7 @@ export class LlEmitter {
if (!stack) B.line(`${box} = ${boxNewCall(this.shapeHost, type)}`);
const value = B.tmp();
B.line(`${value} = load ${this.llType(type)}, ptr %${mangleLocal(localId)}`);
this.boxSet(box, type, isRefCounted(type) ? this.retainValue(value, type) : value);
this.boxSet(box, type, isRefCounted(type) ? this.retainHeap(value, type) : value);
B.line(`store ptr ${box}, ptr ${cache}`);
B.br(join);
B.startBlock(join);
@@ -6515,7 +6629,7 @@ export class LlEmitter {
this.boxSet(
box,
local.type,
borrowed && isRefCounted(local.type) ? this.retainValue(value, local.type) : value,
borrowed && isRefCounted(local.type) ? this.retainHeap(value, local.type) : value,
);
}
@@ -7540,14 +7654,14 @@ export class LlEmitter {
B.line(`${present} = icmp ne ptr %result, null`);
B.condBr(present, own, done);
B.startBlock(own);
B.line(`store ptr ${this.retainValue("%result", fn.returnType)}, ptr ${slot}`);
B.line(`store ptr ${this.retainFrame("%result", fn.returnType)}, ptr ${slot}`);
B.br(done);
B.startBlock(done);
result = B.tmp();
B.line(`${result} = load ptr, ptr ${slot}`);
}
for (const index of borrowed)
if (!kept.has(index)) this.releaseValue(`%p${index}`, fn.params[index]!.type);
if (!kept.has(index)) this.releaseFrame(`%p${index}`, fn.params[index]!.type);
B.terminate(ret === "void" ? "ret void" : `ret ${ret} ${result}`);
const symbol =
kept.size > 0 ? `${mangleBorrowedFunction(fn.name)}.virtual` : mangleFunction(fn.name);
@@ -7708,7 +7822,7 @@ export class LlEmitter {
private emitAdoptingReturn(s: IrStmt & { kind: "return" }, adopted: ExprOf<"awaitExpr">): void {
const B = this.B;
const promise = this.emitExpr(adopted.value);
this.moveTemp(promise);
this.toFrame(promise);
const slot = B.slot();
B.entryAllocas.push(`${slot} = alloca ptr ; adopted return promise`);
B.line(`store ptr ${promise.name}, ptr ${slot}`);
@@ -7742,7 +7856,8 @@ export class LlEmitter {
const v = this.emitExpr(s.value!);
if (this.adoptedOperand !== null)
throw new InternalCompilerError("llvm emitter bug: adopted return promise was not awaited");
this.moveTemp(v);
if (this.currentCoro !== null) this.toHeap(v);
else this.toFrame(v);
this.releaseForJump(0, 0);
if (!B.isTerminated()) {
if (this.currentCoro !== null) {
@@ -8087,7 +8202,7 @@ export class LlEmitter {
const union = emitStackUnion(this, localUnion);
B.line(`store ptr ${union.value.name}, ptr ${b.slot}`);
if (union.payload) {
this.moveTemp(union.payload);
this.toFrame(union.payload);
this.scopes[this.scopes.length - 1]!.push({
slot: union.payloadSlot,
type: union.payload.type,
@@ -8134,7 +8249,7 @@ export class LlEmitter {
if (stack.owner) this.scopes[this.scopes.length - 1]!.push(stack.owner);
if (s.init) {
const value = this.emitExpr(s.init);
if (isRefCounted(value.type)) this.moveTemp(value);
if (isRefCounted(value.type)) this.toFrame(value);
this.boxSet(stack.box, b.type, value.name);
}
break;
@@ -8161,7 +8276,7 @@ export class LlEmitter {
break;
}
const v = this.emitExpr(s.init);
if (isRefCounted(v.type)) this.moveTemp(v); // the box takes ownership
if (isRefCounted(v.type)) this.toHeap(v); // the box takes ownership
if (b.local!.tdz === true && boxAccess(b.type) !== "ref") {
this.tdzScalarInit(box, b.type, v.name);
} else {
@@ -8219,7 +8334,8 @@ export class LlEmitter {
break;
}
const v = this.emitExpr(s.init);
this.moveTemp(v);
if (b.kind === "global") this.toHeap(v);
else this.toFrame(v);
B.line(`store ${this.llType(b.type)} ${v.name}, ptr ${b.slot}`);
this.storeIntegerView(s.localId, v, s.init);
if (isRefCounted(b.type)) {
@@ -8242,10 +8358,10 @@ export class LlEmitter {
const param = this.binding(s.localId);
const slot = param.slot;
const v = this.emitExpr(s.value);
this.moveTemp(v);
this.toFrame(v);
const old = B.tmp();
B.line(`${old} = load ptr, ptr ${owner}`);
this.releaseValue(old, param.type);
this.releaseFrame(old, param.type);
B.line(`store ptr ${v.name}, ptr ${slot}`);
B.line(`store ptr ${v.name}, ptr ${owner}`);
break;
@@ -8272,7 +8388,7 @@ export class LlEmitter {
B.line(`${bits} = load ${struct}, ptr ${value.name}`);
B.line(`store ${struct} ${bits}, ptr ${target}`);
this.disownSlots(owned);
for (const field of old) this.releaseValue(field.name, field.type);
for (const field of old) this.releaseFrame(field.name, field.type);
break;
}
const localStorage = this.localUnionStorage.get(s.localId);
@@ -8296,14 +8412,16 @@ export class LlEmitter {
if (b.kind === "boxed") {
this.writeBindingBox(this.loadBox(b.slot), b.local!, v.name, s.initializes);
// The RHS remains frame-owned until a possible TDZ throw passes.
if (isRefCounted(v.type)) this.moveTemp(v);
if (isRefCounted(v.type)) this.toHeap(v);
break;
}
this.moveTemp(v);
if (b.kind === "global") this.toHeap(v);
else this.toFrame(v);
if (isRefCounted(b.type)) {
const old = B.tmp();
B.line(`${old} = load ptr, ptr ${b.slot}`);
this.releaseValue(old, b.type);
if (b.kind === "global") this.releaseHeap(old, b.type);
else this.releaseFrame(old, b.type);
}
B.line(`store ${this.llType(b.type)} ${v.name}, ptr ${b.slot}`);
this.storeIntegerView(s.localId, v, s.value);
@@ -8354,7 +8472,7 @@ export class LlEmitter {
throw new InternalCompilerError("llvm emitter bug: arraySet on non-array");
const acc = elemAccess(s.arr.type.elem);
this.emitPublishedArrayGuard(arr.name, s.arr.type, 0, idx.name);
if (acc === "ref") this.moveTemp(v);
if (acc === "ref") this.toHeap(v);
emitDenseArraySet(
this,
arr.name,
@@ -8419,7 +8537,7 @@ export class LlEmitter {
if (acc === "ref" && isRefCounted(elem)) {
const old = B.tmp();
B.line(`${old} = inttoptr i64 ${raw} to ptr`);
this.releaseValue(old, elem);
this.releaseHeap(old, elem);
}
B.br(done);
B.startBlock(call);
@@ -8569,7 +8687,7 @@ export class LlEmitter {
const v = this.emitExpr(s.value);
if (this.publishedTypes?.recordGuarded(s.shapeId) === true)
this.emitPublishedFieldGuard(obj.name, null, "[key]");
if (isRefCounted(v.type)) this.moveTemp(v);
if (isRefCounted(v.type)) this.toHeap(v);
const shape = this.recordShape(s.shapeId);
// Signature-free shapes dispatch over their (one-typed) declared
// fields and TRAP on a miss (scr_record_key_miss — JS would add
@@ -8635,12 +8753,12 @@ export class LlEmitter {
if (inline) {
// The slot copies the checked value's bits; nv is released below.
this.storeField(ptr, type, nv, true);
this.releaseValue(nv, type);
this.releaseFrame(nv, type);
} else if (isRefCounted(type)) {
const old = B.tmp();
B.line(`${old} = load ptr, ptr ${ptr}`);
this.storeField(ptr, type, nv, false);
this.releaseValue(old, type);
this.releaseHeap(old, type);
} else {
this.storeField(ptr, type, nv, false);
}
@@ -8670,12 +8788,12 @@ export class LlEmitter {
if (inline) {
// The write owns the moved-in value; the slot copies its bits.
this.storeField(ptr, type, v.name, true);
this.releaseValue(v.name, type);
this.releaseHeap(v.name, type);
} else if (isRefCounted(type)) {
const old = B.tmp();
B.line(`${old} = load ptr, ptr ${ptr}`);
this.storeField(ptr, type, v.name, false);
this.releaseValue(old, type);
this.releaseHeap(old, type);
} else {
this.storeField(ptr, type, v.name, false);
}
@@ -8686,7 +8804,7 @@ export class LlEmitter {
// The MISS on a fixed shape: release the moved-in value, throw
// the catchable TypeError naming the key (scr_record_key_miss —
// JS would add the property, the documented divergence).
if (isRefCounted(iv)) this.releaseValue(v.name, iv);
if (isRefCounted(iv)) this.releaseHeap(v.name, iv);
this.declare(`declare void @scr_record_key_miss(ptr)`);
B.line(`call void @scr_record_key_miss(ptr ${key.name})`);
B.br(join);
@@ -9329,7 +9447,8 @@ export class LlEmitter {
} else v = this.emitReadReceiver(s.value);
} else if (s.value !== null) {
v = this.emitExpr(s.value);
this.moveTemp(v);
if (this.currentCoro !== null) this.toHeap(v);
else this.toFrame(v);
}
// An async generator's `return v` awaits v before any crossed
// finally runs. Past finally regions that turn is taken here and
@@ -9398,7 +9517,7 @@ export class LlEmitter {
// unwind unconditionally (the innermost try handler, or out of the
// function) — the same release path as return/break/continue.
const v = this.emitExpr(s.value);
if (isRefCounted(s.value.type)) this.moveTemp(v); // the cell takes ownership
if (isRefCounted(s.value.type)) this.toHeap(v); // the cell takes ownership
this.emitThrowValue({ name: v.name, type: s.value.type });
this.emitUnwind();
break;
@@ -9444,7 +9563,7 @@ export class LlEmitter {
s.kind !== "rethrow" &&
s.kind !== "runtimeFence"
) {
this.releaseFrame(frame);
this.releaseTemps(frame);
}
}
@@ -9656,7 +9775,7 @@ export class LlEmitter {
} else {
B.line(`${hit} = fcmp oeq double ${disc.name}, ${t.name}`);
}
this.releaseFrame(this.frames.pop()!);
this.releaseTemps(this.frames.pop()!);
const next = B.newLabel("sw.t");
B.condBr(hit, caseLabels[i]!, next);
B.startBlock(next);
@@ -9697,7 +9816,7 @@ export class LlEmitter {
private emitCondition(cond: IrExpr): string {
const v = this.emitExpr(cond);
const frame = this.currentFrame();
this.releaseFrame(frame);
this.releaseTemps(frame);
frame.length = 0;
return v.name;
}
@@ -9709,11 +9828,11 @@ export class LlEmitter {
emitBranchInto(slot: string, expr: IrExpr, integerSlot?: string): boolean {
this.frames.push([]);
const v = this.emitExpr(expr);
this.moveTemp(v);
this.toFrame(v);
this.B.line(`store ${this.llType(expr.type)} ${v.name}, ptr ${slot}`);
if (integerSlot !== undefined && v.uint32 !== undefined)
this.B.line(`store i32 ${v.uint32}, ptr ${integerSlot}`);
this.releaseFrame(this.frames.pop()!);
this.releaseTemps(this.frames.pop()!);
return v.uint32 !== undefined;
}
@@ -9867,12 +9986,13 @@ export class LlEmitter {
const old = B.tmp();
B.line(`${old} = load ptr, ptr ${b.slot}`);
B.line(`store ptr null, ptr ${b.slot}`);
this.releaseValue(old, b.type);
if (b.kind === "global") this.releaseHeap(old, b.type);
else this.releaseFrame(old, b.type);
}
let raw: string;
if (charCode !== null) {
// The snapshot moves into the consuming character append.
this.moveTemp(snapshot);
this.toFrame(snapshot);
this.declare(`declare ptr @scr_str_concat_move_char_code(ptr, double)`);
raw = B.tmp();
B.line(
@@ -9887,11 +10007,15 @@ export class LlEmitter {
}
const result = this.own({ name: raw, type: left.type });
if (retainForYield) {
const stored = this.retainValue(result.name, result.type);
const stored =
b.kind === "local"
? this.retainFrame(result.name, result.type)
: this.retainHeap(result.name, result.type);
if (b.kind === "boxed") this.boxSet(this.loadBox(b.slot), b.type, stored);
else B.line(`store ptr ${stored}, ptr ${b.slot}`);
} else {
this.moveTemp(result);
if (b.kind === "local") this.toFrame(result);
else this.toHeap(result);
if (b.kind === "boxed") this.boxSet(this.loadBox(b.slot), b.type, result.name);
else B.line(`store ptr ${result.name}, ptr ${b.slot}`);
}
@@ -9954,7 +10078,7 @@ export class LlEmitter {
if (owned) {
const old = this.loadField(ptr, fieldType, false);
this.storeField(ptr, fieldType, "null", false);
this.releaseValue(old, fieldType);
this.releaseHeap(old, fieldType);
}
let raw: string;
if (right === null) {
@@ -10399,7 +10523,7 @@ export class LlEmitter {
}
// The result IS the operand: ownership moves to the result temp.
if (!isRefCounted(value.type)) return { name: value.name, type: e.type };
this.moveTemp(value);
this.toFrame(value);
return this.own({ name: value.name, type: e.type });
}
@@ -10652,7 +10776,7 @@ export class LlEmitter {
this.frames.push([]);
const result = this.emitReadReceiver(value);
B.line(`store ptr ${result.name}, ptr ${slot}`);
this.releaseFrame(this.frames.pop()!);
this.releaseTemps(this.frames.pop()!);
B.br(join);
}
B.startBlock(join);
@@ -10748,7 +10872,7 @@ export class LlEmitter {
const { ptr } = this.classFieldPtr(receiver.name, e.className, e.field);
const p = this.B.tmp();
this.B.line(`${p} = load ptr, ptr ${ptr}${this.fieldAliasAttachment(ptr)}`);
const owner = this.own({ name: this.retainValue(p, nullable.arm), type: nullable.arm });
const owner = this.own({ name: this.retainFrame(p, nullable.arm), type: nullable.arm });
return { box: this.stackNullableBox(owner.name, nullable), owner };
}
@@ -10787,7 +10911,7 @@ export class LlEmitter {
B.line(`store ptr ${this.unitInstanceRef(nullable.unionId, nullable.unitTag)}, ptr ${slot}`);
B.br(join);
B.startBlock(ref);
const owned = this.retainValue(p, nullable.arm);
const owned = this.retainHeap(p, nullable.arm);
const box = this.unionNewOwned(nullable.unionId, nullable.refTag, {
name: owned,
type: nullable.arm,
@@ -10810,7 +10934,7 @@ export class LlEmitter {
B.line(`${isRef} = icmp eq i32 ${tag}, ${nullable.refTag}`);
const payload = this.unionPeek(u, nullable.unionId);
B.line(`${sel} = select i1 ${isRef}, ptr ${payload}, ptr null`);
return this.retainValue(sel, nullable.arm);
return this.retainHeap(sel, nullable.arm);
}
/** `obj.f = value` for a nullable-pointer field: the new instance pointer
@@ -10835,7 +10959,7 @@ export class LlEmitter {
) {
if (value.tag === nullable.refTag) {
const inner = this.emitExpr(value.value);
this.moveTemp(inner);
this.toHeap(inner);
next = inner.name;
} else {
// Unit payloads carry nothing; a void payload runs for effects.
@@ -10851,7 +10975,7 @@ export class LlEmitter {
const old = B.tmp();
B.line(`${old} = load ptr, ptr ${ptr}`);
B.line(`store ptr ${next}, ptr ${ptr}${this.fieldAliasAttachment(ptr)}`);
this.releaseValue(old, nullable.arm);
this.releaseHeap(old, nullable.arm);
}
/** A union binding whose slot may hold a private stack box: stack locals
@@ -162,7 +162,7 @@ export function emitIntrinsicExpr(host: LlvmEmitterContext, e: ExprOf<"intrinsic
const p = B.tmp();
B.line(`${p} = call ptr @scr_promise_new()`);
const out = host.own({ name: p, type: e.type });
host.moveTemp(reason); // the cell takes ownership
host.toHeap(reason); // the cell takes ownership
host.emitThrowValue({ name: reason.name, type: reasonT });
B.line(`call void @scr_promise_reject_pending(ptr ${p})`);
return out;
@@ -191,12 +191,12 @@ export function emitIntrinsicExpr(host: LlvmEmitterContext, e: ExprOf<"intrinsic
host.declare(`declare void @scr_promise_fulfill_bool(ptr, i1 zeroext)`);
B.line(`call void @scr_promise_fulfill_bool(ptr ${p}, i1 ${v.name})`);
} else if (t.kind === "string") {
host.moveTemp(v);
host.toHeap(v);
host.declare(`declare void @scr_promise_fulfill_str(ptr, ptr)`);
B.line(`call void @scr_promise_fulfill_str(ptr ${p}, ptr ${v.name})`);
} else {
const rc = vAdapters(host.shapeHost, t);
host.moveTemp(v);
host.toHeap(v);
host.declare(`declare void @scr_promise_fulfill_ref(ptr, ptr, ptr, ptr, ptr)`);
B.line(
`call void @scr_promise_fulfill_ref(ptr ${p}, ptr ${v.name}, ptr ${rc.retain}, ptr ${rc.release}, ptr ${traceArg(host.shapeHost, t)})`,
@@ -537,7 +537,7 @@ export function emitAsyncExpr(
B.line(`call void @scr_gen_yield_bool(i1 ${v.name})`);
}
} else {
host.moveTemp(v); // the OUT slot takes ownership
host.toHeap(v); // the OUT slot takes ownership
if (host.currentCoro !== null) {
const coro = host.currentCoro;
const release = vAdapters(host.shapeHost, yt).release;
@@ -601,7 +601,7 @@ export function emitAsyncExpr(
// Refcounted channels: the slot's +1 moves out.
host.declare(`declare ptr @scr_gen_take_in_ref()`);
B.line(`${t} = call ptr @scr_gen_take_in_ref()`);
return host.own({ name: t, type: e.type });
return host.own({ name: host.adoptHeap(t, e.type), type: e.type });
}
case "genResume": {
// One consumer resume: park the sent value (typed per mode), hop
@@ -731,7 +731,7 @@ export function emitAsyncExpr(
);
host.declare(`declare ptr @scr_gen_take_out_ref(ptr)`);
B.line(`${t} = call ptr @scr_gen_take_out_ref(ptr ${gb.name})`);
return host.own({ name: t, type: e.type });
return host.own({ name: host.adoptHeap(t, e.type), type: e.type });
}
// Valueless: an empty IN slot reads as undefined on any channel.
host.declare(`declare zeroext i1 @scr_gen_step(ptr)`);
@@ -748,7 +748,7 @@ export function emitAsyncExpr(
const g = host.emitExpr(e.gen); // borrowed for the calls below
const sendArg = (store: (aName: string, t: IrType) => void): void => {
const a = host.emitExpr(e.arg!);
if (isRefCounted(e.arg!.type)) host.moveTemp(a); // the slot takes ownership
if (isRefCounted(e.arg!.type)) host.toHeap(a); // the slot takes ownership
store(a.name, e.arg!.type);
};
const parkIn = (name: string, t: IrType): void => {
@@ -786,7 +786,7 @@ export function emitAsyncExpr(
} else {
const a = host.emitExpr(e.arg);
const t = e.arg.type;
if (isRefCounted(t)) host.moveTemp(a);
if (isRefCounted(t)) host.toHeap(a);
if (t.kind === "f64" || t.kind === "date") {
host.declare(`declare ptr @scr_async_gen_next_f64(ptr, double)`);
call = `call ptr @scr_async_gen_next_f64(ptr ${g.name}, double ${a.name})`;
@@ -805,7 +805,7 @@ export function emitAsyncExpr(
} else {
const a = host.emitExpr(e.arg);
const t = e.arg.type;
if (isRefCounted(t)) host.moveTemp(a);
if (isRefCounted(t)) host.toHeap(a);
if (t.kind === "f64" || t.kind === "date") {
host.declare(`declare ptr @scr_async_gen_return_f64(ptr, double)`);
call = `call ptr @scr_async_gen_return_f64(ptr ${g.name}, double ${a.name})`;
@@ -823,7 +823,7 @@ export function emitAsyncExpr(
"llvm emitter bug: async genResume throw with no payload",
);
const a = host.emitExpr(e.arg);
if (isRefCounted(e.arg.type)) host.moveTemp(a);
if (isRefCounted(e.arg.type)) host.toHeap(a);
host.emitThrowValue({ name: a.name, type: e.arg.type });
host.declare(`declare ptr @scr_async_gen_throw(ptr)`);
call = `call ptr @scr_async_gen_throw(ptr ${g.name})`;
@@ -873,7 +873,7 @@ export function emitAsyncExpr(
if (e.arg === null)
throw new InternalCompilerError("llvm emitter bug: genResume throw with no payload");
const a = host.emitExpr(e.arg);
if (isRefCounted(e.arg.type)) host.moveTemp(a); // the cell takes ownership
if (isRefCounted(e.arg.type)) host.toHeap(a); // the cell takes ownership
host.emitThrowValue({ name: a.name, type: e.arg.type });
host.declare(`declare void @scr_gen_resume_throw(ptr)`);
B.line(`call void @scr_gen_resume_throw(ptr ${g.name})`);
@@ -955,7 +955,7 @@ export function emitAsyncExpr(
host.declare(`declare ptr @${refAwait}(ptr)`);
B.line(`${t} = call ptr @${refAwait}(ptr ${pr.name})`);
}
const out = host.own({ name: t, type: e.type });
const out = host.own({ name: host.adoptHeap(t, e.type), type: e.type });
host.emitPendingCheck();
return out;
}
@@ -10,6 +10,7 @@ import {
typeKey,
type IrExpr,
type IrFunction,
classValueConstructor,
} from "../../ir/ir.js";
import { liveRefView } from "./expr-stream-bridges.js";
import { typedRefConstructor, vAdapters } from "./shapes.js";
@@ -165,7 +166,7 @@ export function emitCallExpr(
// This also preserves snapshots when a later argument mutates its
// source, and releases earlier arguments if evaluation throws.
args.forEach((a, index) => {
if (!borrowed?.has(index)) host.moveTemp(a);
if (!borrowed?.has(index)) host.toFrame(a);
});
const abi = awaitEntry ? undefined : host.int32Abis.get(e.callee);
const argList = args
@@ -224,14 +225,14 @@ export function emitCallExpr(
if (e.type.kind === "void") {
B.line(`call void ${target}(${argList})`);
if (host.mayThrow.has(e.callee)) host.emitPendingCheck();
if (borrowed) host.releaseFrame(host.frames.pop()!);
if (borrowed) host.releaseTemps(host.frames.pop()!);
return { name: "", type: e.type };
}
const t = B.tmp();
if (abi?.union) {
B.line(`${t} = call ${SCALAR_UNION_RESULT} ${target}(${argList})`);
if (host.mayThrow.has(e.callee)) host.emitPendingCheck();
if (borrowed) host.releaseFrame(host.frames.pop()!);
if (borrowed) host.releaseTemps(host.frames.pop()!);
if (stackResult) return host.stackScalarUnion(t, e.type);
return host.own({ name: host.boxScalarUnion(t, e.type), type: e.type });
}
@@ -239,7 +240,7 @@ export function emitCallExpr(
B.line(`${t} = call i32 ${target}(${argList})`);
const out = host.int32Result(t, e.type);
if (host.mayThrow.has(e.callee)) host.emitPendingCheck(out);
if (borrowed) host.releaseFrame(host.frames.pop()!);
if (borrowed) host.releaseTemps(host.frames.pop()!);
return out;
}
B.line(`${t} = call ${host.llType(e.type)} ${target}(${argList})`);
@@ -249,14 +250,14 @@ export function emitCallExpr(
// test the exception first, then own the result before the
// argument snapshots that keep it reachable are released.
if (host.mayThrow.has(e.callee)) host.emitPendingCheck();
out = host.own({ name: host.retainValue(t, e.type), type: e.type });
out = host.own({ name: host.retainFrame(t, e.type), type: e.type });
} else {
out = host.own({ name: t, type: e.type });
if (host.mayThrow.has(e.callee)) host.emitPendingCheck(out);
}
if (borrowed) {
host.moveTemp(out);
host.releaseFrame(host.frames.pop()!);
host.toFrame(out);
host.releaseTemps(host.frames.pop()!);
return host.own(out);
}
return out;
@@ -718,7 +719,7 @@ export function emitCallExpr(
// every mention yields the same pointer, so `f === f` is true.
host.fnValues.add(e.fnName);
return host.own({
name: host.retainValue(`@${mangleFnClosure(e.fnName)}`, e.type),
name: host.retainFrame(`@${mangleFnClosure(e.fnName)}`, e.type),
type: e.type,
});
}
@@ -880,7 +881,7 @@ export function emitCallExpr(
lent?.has(i) && lend[i] ? host.emitReadReceiver(a) : host.emitExpr(a),
);
args.forEach((a, i) => {
if (!lent?.has(i)) host.moveTemp(a);
if (!lent?.has(i)) host.toFrame(a);
});
const argListOf = (values: readonly LlValue[]): string =>
[
@@ -891,7 +892,7 @@ export function emitCallExpr(
const ownedArgs = (): LlValue[] =>
lent
? args.map((a, i) =>
lent.has(i) ? { name: host.retainValue(a.name, a.type), type: a.type } : a,
lent.has(i) ? { name: host.retainFrame(a.name, a.type), type: a.type } : a,
)
: args;
const invoke = (target: string, needsReceiver: boolean, values = args): string => {
@@ -978,7 +979,7 @@ export function emitCallExpr(
}
if (e.type.kind === "void") {
if (host.indirectMayThrow) host.emitPendingCheck();
if (lent) host.releaseFrame(host.frames.pop()!);
if (lent) host.releaseTemps(host.frames.pop()!);
return { name: "", type: e.type };
}
// The check runs AFTER the result temp joins the frame: an unwind
@@ -986,15 +987,15 @@ export function emitCallExpr(
const out = host.own({ name: t, type: e.type });
if (host.indirectMayThrow) host.emitPendingCheck();
if (lent) {
host.moveTemp(out);
host.releaseFrame(host.frames.pop()!);
host.toFrame(out);
host.releaseTemps(host.frames.pop()!);
return host.own(out);
}
return out;
}
case "selfRef":
// The running closure itself (env is borrowed; the result owned).
return host.own({ name: host.retainValue("%sc_env", e.type), type: e.type });
return host.own({ name: host.retainFrame("%sc_env", e.type), type: e.type });
case "new": {
// Allocate (fields zeroed, vt stamped), then run the ctor. The
// ctor owns and releases its `this` param like any callee, so it
@@ -1034,7 +1035,7 @@ export function emitCallExpr(
});
return host.own({ name: value, type: e.type });
}
return host.own({ name: host.retainValue(`@${sym}`, e.type), type: e.type });
return host.own({ name: host.retainFrame(`@${sym}`, e.type), type: e.type });
}
case "newValue": {
// Construction through a class VALUE: call the class object's
@@ -1043,20 +1044,21 @@ export function emitCallExpr(
if (e.callee.type.kind !== "classval")
throw new InternalCompilerError("llvm emitter bug: newValue on non-classval callee");
const cls = e.callee.type.className;
const ctor = host.fnByName.get(`%${cls}.constructor`);
const valueCtor = classValueConstructor(host.classMetaOf(cls).def);
const ctor = host.fnByName.get(valueCtor.fn);
if (!ctor)
throw new InternalCompilerError(
`llvm emitter bug: newValue on ${cls} without a constructor`,
);
const callee = host.emitExpr(e.callee);
const args = e.args.map((a) => host.emitExpr(a));
for (const a of args) host.moveTemp(a); // the constructor owns its params
for (const a of args) host.toFrame(a); // the constructor owns its params
const ctorp = B.tmp();
const thunk = B.tmp();
B.line(`${ctorp} = getelementptr inbounds %ScrClassObj, ptr ${callee.name}, i64 0, i32 3`);
B.line(`${thunk} = load ptr, ptr ${ctorp}`);
const argList = args
.map((a, i) => `${host.llType(ctor.params[i + 1]!.type)} ${a.name}`)
.map((a, i) => `${host.llType(ctor.params[i + valueCtor.firstArg]!.type)} ${a.name}`)
.join(", ");
const t = B.tmp();
B.line(`${t} = call ptr ${thunk}(ptr ${callee.name}${argList ? ", " + argList : ""})`);
@@ -1121,7 +1123,7 @@ export function emitCallExpr(
// One ScrPromise* either way — ownership transfers, type-only
//.
const v = host.emitExpr(e.value);
host.moveTemp(v);
host.toFrame(v);
return host.own({ name: v.name, type: e.type });
}
case "upcast":
@@ -1134,7 +1136,7 @@ export function emitCallExpr(
const v = host.emitExpr(e.value);
if (e.kind === "downcast" && e.type.kind === "object" && v.type.kind === "object")
emitDowncastGuard(host, v.name, v.type.className, e.type.className);
if (isRefCounted(v.type)) host.moveTemp(v);
if (isRefCounted(v.type)) host.toFrame(v);
return host.own({ name: v.name, type: e.type });
}
case "instanceOf": {
@@ -1188,7 +1190,7 @@ export function emitCallExpr(
borrowed.has(index) && inputs[index] ? host.emitReadReceiver(a) : host.emitExpr(a),
);
args.forEach((a, index) => {
if (!borrowed.has(index)) host.moveTemp(a);
if (!borrowed.has(index)) host.toFrame(a);
});
const recv = args[0]!.name;
const vtp = B.tmp();
@@ -1239,7 +1241,7 @@ export function emitCallExpr(
B.startBlock(done);
if (e.type.kind === "void") {
if (host.mayThrowMethods.has(e.method)) host.emitPendingCheck();
if (borrowed.size > 0) host.releaseFrame(host.frames.pop()!);
if (borrowed.size > 0) host.releaseTemps(host.frames.pop()!);
return { name: "", type: e.type };
}
const t = B.tmp();
@@ -1247,8 +1249,8 @@ export function emitCallExpr(
const out = host.own({ name: t, type: e.type });
if (host.mayThrowMethods.has(e.method)) host.emitPendingCheck();
if (borrowed.size > 0) {
host.moveTemp(out);
host.releaseFrame(host.frames.pop()!);
host.toFrame(out);
host.releaseTemps(host.frames.pop()!);
return host.own(out);
}
return out;
@@ -1256,7 +1258,7 @@ export function emitCallExpr(
if (e.type.kind === "void") {
B.line(`call void ${fn}(${argList})`);
if (host.mayThrowMethods.has(e.method)) host.emitPendingCheck();
if (borrowed.size > 0) host.releaseFrame(host.frames.pop()!);
if (borrowed.size > 0) host.releaseTemps(host.frames.pop()!);
return { name: "", type: e.type };
}
const t = B.tmp();
@@ -1264,8 +1266,8 @@ export function emitCallExpr(
const out = host.own({ name: t, type: e.type });
if (host.mayThrowMethods.has(e.method)) host.emitPendingCheck();
if (borrowed.size > 0) {
host.moveTemp(out);
host.releaseFrame(host.frames.pop()!);
host.toFrame(out);
host.releaseTemps(host.frames.pop()!);
return host.own(out);
}
return out;
@@ -1392,7 +1394,7 @@ export function emitConstruction(
return host.emitExpr(a);
});
args.forEach((a, i) => {
if (!borrowed.has(i + 1)) host.moveTemp(a);
if (!borrowed.has(i + 1)) host.toFrame(a);
});
const self = borrowed.has(0)
? o
@@ -1408,11 +1410,11 @@ export function emitConstruction(
`call void @${mangleBorrowedFunction(ctorName)}${host.int32Abis.has(ctorName) ? INT32_ABI_SUFFIX : ""}(${argList})`,
);
if (host.mayThrow.has(ctorName)) host.emitPendingCheck();
host.releaseFrame(host.frames.pop()!);
host.releaseTemps(host.frames.pop()!);
return;
}
const args = e.args.map((a) => host.emitExpr(a));
for (const a of args) host.moveTemp(a);
for (const a of args) host.toFrame(a);
const r = B.tmp();
B.line(`${r} = call ptr @${mangleClassRetain(e.className)}(ptr ${o})`);
const argList = [`ptr ${r}`, ...ctorArguments(host, ctorName, ctor, args, e.args)].join(", ");
@@ -1454,8 +1456,8 @@ function emitNumericIndexRead(
B.startBlock(slow);
host.frames.push([]);
const read = emitCallExpr(host, e, false, false, undefined, true);
host.moveTemp(read);
host.releaseFrame(host.frames.pop()!);
host.toFrame(read);
host.releaseTemps(host.frames.pop()!);
const slowLabel = B.currentLabel();
B.terminate(`br label %${join}`);
B.startBlock(join);
@@ -118,7 +118,7 @@ export function emitArrayValues(
sharedBuffer?: string,
): string {
const B = host.B;
if (acc === "ref") values.forEach((value) => host.moveTemp(value));
if (acc === "ref") values.forEach((value) => host.toHeap(value));
if (values.length === 0) {
host.declare("declare double @scr_arr_len(ptr)");
const result = B.tmp();
@@ -219,7 +219,7 @@ function emitDiscarded(host: LlvmEmitterContext, e: IrExpr): void {
B.startBlock(label);
host.frames.push([]);
emitDiscarded(host, arm);
host.releaseFrame(host.frames.pop()!);
host.releaseTemps(host.frames.pop()!);
B.br(lj);
}
B.startBlock(lj);
@@ -235,7 +235,7 @@ function emitDiscarded(host: LlvmEmitterContext, e: IrExpr): void {
const start = host.emitExpr(e.args[0]!);
const count = e.args[1] ? host.emitExpr(e.args[1]).name : F64_INF;
const values = items.map((item) => host.emitExpr(item));
if (acc === "ref") values.forEach((value) => host.moveTemp(value));
if (acc === "ref") values.forEach((value) => host.toHeap(value));
let buffer = "null";
if (values.length > 0) {
buffer = B.tmp();
@@ -1069,7 +1069,7 @@ export function emitMapNew(host: LlvmEmitterContext, e: IrExpr & { kind: "mapNew
for (const pair of e.seed ?? []) {
const k = emitBorrowedInput(host, pair.key);
const v = host.emitExpr(pair.value);
if (vAcc === "ref") host.moveTemp(v); // the value MOVES in
if (vAcc === "ref") host.toHeap(v); // the value MOVES in
host.mapSet(m, kAcc, vAcc, k.name, v.name);
}
return out;
@@ -1370,7 +1370,7 @@ export function emitMapLikeIntrinsic(
const k = emitBorrowedInput(host, e.args[0]!);
const v = host.emitExpr(e.args[1]!);
const vAcc = elemAccess(receiverType.value);
if (vAcc === "ref") host.moveTemp(v);
if (vAcc === "ref") host.toHeap(v);
const ordinary = (): void => host.mapSet(r.name, kAcc, vAcc, k.name, v.name);
if (kAcc === "f64" && vAcc !== "ref" && host.readNumberMapWrites.has(e)) {
const bits = B.tmp();
@@ -58,7 +58,7 @@ export function emitControlExpr(
// and the right runs in its own frame.
const ty = host.llType(e.type);
const l = host.emitExpr(e.left);
host.moveTemp(l);
host.toFrame(l);
const slot = B.slot();
B.entryAllocas.push(`${slot} = alloca ${ty}`);
B.line(`store ${ty} ${l.name}, ptr ${slot}`);
@@ -73,7 +73,7 @@ export function emitControlExpr(
if (e.op === "&&") B.condBr(truthy, rightLabel, joinLabel);
else B.condBr(truthy, joinLabel, rightLabel);
B.startBlock(rightLabel);
if (isRefCounted(e.type)) host.releaseValue(l.name, e.type);
if (isRefCounted(e.type)) host.releaseFrame(l.name, e.type);
const rightInteger = host.emitBranchInto(slot, e.right, integerSlot);
B.br(joinLabel);
B.startBlock(joinLabel);
@@ -157,7 +157,7 @@ export function emitControlExpr(
host.chainSlots.set(e.id, { name: bind, type: e.receiver.type, slot: true });
host.frames.push([]);
host.emitExpr(e.body);
host.releaseFrame(host.frames.pop()!);
host.releaseTemps(host.frames.pop()!);
host.chainSlots.delete(e.id);
B.br(lj);
B.startBlock(lj);
@@ -203,7 +203,7 @@ export function emitControlExpr(
bindPresent();
host.frames.push([]);
host.emitExpr(e.body);
host.releaseFrame(host.frames.pop()!);
host.releaseTemps(host.frames.pop()!);
host.chainSlots.delete(e.id);
B.br(lj);
B.startBlock(lj);
@@ -272,7 +272,7 @@ export function emitControlExpr(
const v = B.tmp();
B.line(`${v} = load ${host.llType(bound.type)}, ptr ${bound.name}`);
if (!isRefCounted(e.type)) return { name: v, type: e.type };
return host.own({ name: host.retainValue(v, e.type), type: e.type });
return host.own({ name: host.retainFrame(v, e.type), type: e.type });
}
case "orDefault": {
// `u || d` narrowed to the single non-unit arm: nullish's dance
@@ -309,7 +309,7 @@ export function emitControlExpr(
return host.own({ name: t, type: e.type });
}
const l = host.emitExpr(e.left);
host.moveTemp(l);
host.toFrame(l);
const ty = host.llType(e.type);
const slot = B.slot();
B.entryAllocas.push(`${slot} = alloca ${ty}`);
@@ -337,11 +337,11 @@ export function emitControlExpr(
B.line(
`store ${ty} ${host.unionExtract(l.name, e.left.type.unionId, e.type)}, ptr ${slot}`,
);
host.releaseValue(l.name, e.left.type);
host.releaseFrame(l.name, e.left.type);
}
B.br(lj);
B.startBlock(lf);
host.releaseValue(l.name, e.left.type);
host.releaseFrame(l.name, e.left.type);
host.emitBranchInto(slot, e.right);
B.br(lj);
B.startBlock(lj);
@@ -359,7 +359,7 @@ export function emitControlExpr(
// The native checked-value kind decides whether the left is
// nullish; the right runs lazily in its branch.
const l = host.emitExpr(e.left);
host.moveTemp(l);
host.toFrame(l);
host.declare(`declare zeroext i1 @scr_dyn_is_nullish(ptr)`);
const slot = B.slot();
B.entryAllocas.push(`${slot} = alloca ptr`);
@@ -370,7 +370,7 @@ export function emitControlExpr(
const lj = B.newLabel("nuld.j");
B.condBr(isN, lu, lv);
B.startBlock(lu);
host.releaseValue(l.name, e.left.type);
host.releaseFrame(l.name, e.left.type);
host.emitBranchInto(slot, e.right);
B.br(lj);
B.startBlock(lv);
@@ -426,7 +426,7 @@ export function emitControlExpr(
return host.own({ name: t, type: e.type });
}
const l = host.emitExpr(e.left);
host.moveTemp(l);
host.toFrame(l);
const ty = host.llType(e.type);
const slot = B.slot();
B.entryAllocas.push(`${slot} = alloca ${ty}`);
@@ -436,7 +436,7 @@ export function emitControlExpr(
const lj = B.newLabel("nul.j");
B.condBr(isUnit, lu, lv);
B.startBlock(lu);
host.releaseValue(l.name, e.left.type);
host.releaseFrame(l.name, e.left.type);
host.emitBranchInto(slot, e.right);
B.br(lj);
B.startBlock(lv);
@@ -445,7 +445,7 @@ export function emitControlExpr(
B.line(`store ${ty} ${l.name}, ptr ${slot}`);
} else {
B.line(`store ${ty} ${host.unionExtract(l.name, def.id, e.type)}, ptr ${slot}`);
host.releaseValue(l.name, e.left.type);
host.releaseFrame(l.name, e.left.type);
}
B.br(lj);
B.startBlock(lj);
@@ -533,7 +533,7 @@ export function emitOptionalChainReceiver(
const isUnit = host.tagInSet(r.name, def.id, unitTags);
const extract = (): string =>
multiple
? host.retainValue(r.name, narrowed)
? host.retainFrame(r.name, narrowed)
: borrowBind
? host.unionPeek(r.name, def.id)
: host.unionExtract(r.name, def.id, narrowed);
@@ -161,7 +161,7 @@ export function emitDynamicExpr(
);
host.emitPendingCheck();
} else {
host.moveTemp(v);
host.toHeap(v);
B.line(`call void @scr_dyn_arr_push(ptr ${pack}, ptr ${v.name})`);
}
});
@@ -253,7 +253,7 @@ export function emitDynamicExpr(
const out = host.own({ name: arr, type: e.type });
for (const el of e.elems) {
const v = host.emitExpr(el);
host.moveTemp(v);
host.toHeap(v);
B.line(`call void @scr_dyn_arr_push(ptr ${arr}, ptr ${v.name})`);
}
return out;
@@ -296,7 +296,7 @@ export function emitDynamicExpr(
return host.own({ name: host.unitInstanceRef(e.unionId, e.tag), type: e.type });
}
const v = host.emitExpr(e.value);
if (isRefCounted(arm)) host.moveTemp(v);
if (isRefCounted(arm)) host.toHeap(v);
return host.own({ name: host.unionNewOwned(e.unionId, e.tag, v), type: e.type });
}
case "unionNarrow": {
@@ -353,7 +353,7 @@ export function emitDynamicExpr(
const value = nullable
? host.nullableToOwnedUnion(v, nullable)
: isRefCounted(e.type)
? host.retainValue(v, e.type)
? host.retainFrame(v, e.type)
: v;
B.line(`store ${ty} ${value}, ptr ${slot}`);
B.br(join);
@@ -465,7 +465,7 @@ export function emitDynamicExpr(
const owned = inline
? host.inlineFieldUnion(ptr, ft)
: isRefCounted(ft)
? host.retainValue(v, ft)
? host.retainFrame(v, ft)
: v;
B.line(`store ${ty} ${owned}, ptr ${slot}`);
B.br(join);
@@ -487,7 +487,7 @@ export function emitDynamicExpr(
ft.kind === "f64" || ft.kind === "bool"
? host.unionNewOwned(e.type.unionId, tag, { name: v, type: ft })
: host.unionNewOwned(e.type.unionId, tag, {
name: host.retainValue(v, ft),
name: host.retainHeap(v, ft),
type: ft,
});
B.line(`store ptr ${wrapped}, ptr ${slot}`);
@@ -1087,7 +1087,7 @@ export function emitDynamicExpr(
B.line(`${pp} = getelementptr inbounds %ScrCaught, ptr ${c.name}, i64 0, i32 4`);
B.line(`${payload} = load ptr, ptr ${pp}`);
if (e.type.kind === "string") {
return host.own({ name: host.retainValue(payload, e.type), type: e.type });
return host.own({ name: host.retainFrame(payload, e.type), type: e.type });
}
if (e.type.kind === "object") {
// Retain through the snapshot's own entry point (the payload's
@@ -47,11 +47,11 @@ export function emitLiteralExpr(
return { name: e.value ? "true" : "false", type: e.type };
case "strLit": {
const sym = host.internLiteral(e.value);
return host.own({ name: host.retainValue(sym, e.type), type: e.type });
return host.own({ name: host.retainFrame(sym, e.type), type: e.type });
}
case "moduleNsRef": {
const sym = host.internLiteral(`module:${e.moduleId}`);
return host.own({ name: host.retainValue(sym, e.type), type: e.type });
return host.own({ name: host.retainFrame(sym, e.type), type: e.type });
}
case "unitLit":
// unitLits are consumed inline by the unionWrap case (a unit arm is
@@ -110,10 +110,10 @@ export function emitLiteralExpr(
const box = host.loadBox(b.slot);
if (b.local!.tdz === true) {
const v = host.tdzBoxRead(box, e.type, b.local!.name);
return host.own({ name: v, type: e.type });
return host.own({ name: host.adoptHeap(v, e.type), type: e.type });
}
const v = host.boxGet(box, e.type);
return host.own({ name: v, type: e.type });
return host.own({ name: host.adoptHeap(v, e.type), type: e.type });
}
const t = B.tmp();
B.line(`${t} = load ${host.llType(b.type)}, ptr ${b.slot}`);
@@ -131,7 +131,7 @@ export function emitLiteralExpr(
return host.markImmortal({ name: t, type: e.type });
// The binding's last read takes over its reference (lastUseRead).
if (host.takeLastUseMove(e, b.slot, b.type)) return host.own({ name: t, type: e.type });
return host.own({ name: host.retainValue(t, e.type), type: e.type });
return host.own({ name: host.retainFrame(t, e.type), type: e.type });
}
return { name: t, type: e.type };
}
@@ -488,8 +488,11 @@ export function emitOperatorExpr(
if (isRefCounted(b.type)) {
const old = B.tmp();
B.line(`${old} = load ptr, ptr ${b.slot}`);
host.releaseValue(old, b.type);
B.line(`store ptr ${host.retainValue(v.name, v.type)}, ptr ${b.slot}`);
if (b.kind === "global") host.releaseHeap(old, b.type);
else host.releaseFrame(old, b.type);
B.line(
`store ptr ${b.kind === "global" ? host.retainHeap(v.name, v.type) : host.retainFrame(v.name, v.type)}, ptr ${b.slot}`,
);
} else {
B.line(`store ${host.llType(b.type)} ${v.name}, ptr ${b.slot}`);
host.storeIntegerView(e.localId, v, e.value);
@@ -599,7 +602,7 @@ export function emitStringExpr(
}
case "strCmp": {
const [l, r] = emitStringInputs(host, [e.left, e.right]);
const fn = e.utf16 === true ? "scr_str_cmp_u16" : "scr_str_cmp";
const fn = "scr_str_cmp_u16";
host.declare(`declare i32 @${fn}(ptr, ptr)`);
const c = B.tmp();
const t = B.tmp();
@@ -632,12 +635,12 @@ export function emitStringExpr(
case "undefinedT":
case "nullT": {
const lit = host.internLiteral(arm.kind === "undefinedT" ? "undefined" : "null");
B.line(`store ptr ${host.retainValue(lit, e.type)}, ptr ${slot}`);
B.line(`store ptr ${host.retainFrame(lit, e.type)}, ptr ${slot}`);
break;
}
case "string":
B.line(
`store ptr ${host.retainValue(host.unionPeek(v.name, def.id), e.type)}, ptr ${slot}`,
`store ptr ${host.retainFrame(host.unionPeek(v.name, def.id), e.type)}, ptr ${slot}`,
);
break;
case "f64": {
@@ -691,7 +694,7 @@ export function emitStringExpr(
// String(record) / `${record}`: Object.prototype.toString's
// constant — the interned literal, retained like a strLit.
const sym = host.internLiteral("[object Object]");
return host.own({ name: host.retainValue(sym, e.type), type: e.type });
return host.own({ name: host.retainFrame(sym, e.type), type: e.type });
}
if (v.type.kind === "caught") {
// String(e) / `${e}` on a catch binding: JS's String() over the
@@ -748,7 +751,7 @@ export function emitStringExpr(
};
host.templateStringsInstances.set(e.key, inst);
}
return host.own({ name: host.retainValue(`@${inst.sym}`, e.type), type: e.type });
return host.own({ name: host.retainFrame(`@${inst.sym}`, e.type), type: e.type });
}
case "regexIntrinsic":
return host.emitRegexIntrinsic(e);
@@ -975,7 +978,7 @@ export function emitRecordExpr(
}
const v = host.loadField(ptr, type, false);
if (isRefCounted(e.type))
return host.own({ name: host.retainValue(v, e.type), type: e.type });
return host.own({ name: host.retainFrame(v, e.type), type: e.type });
return { name: v, type: e.type };
}
case "recordGet": {
@@ -984,7 +987,7 @@ export function emitRecordExpr(
if (inline) return host.own({ name: host.inlineFieldUnion(ptr, type), type: e.type });
const v = host.loadRecordField(ptr, type, false);
if (isRefCounted(e.type))
return host.own({ name: host.retainValue(v, e.type), type: e.type });
return host.own({ name: host.retainFrame(v, e.type), type: e.type });
return { name: v, type: e.type };
}
case "recordLit": {
@@ -1090,7 +1093,7 @@ export function emitRecordLitFields(
if (f.overflow) {
const lit = host.internLiteral(f.name);
const acc = v.type.kind === "f64" ? "f64" : v.type.kind === "bool" ? "bool" : "ref";
if (acc === "ref") host.moveTemp(v);
if (acc === "ref") host.toHeap(v);
const ovf = host.recordOvfPtr(rec, shapeId);
const argTy = acc === "f64" ? "double" : acc === "bool" ? "i1 zeroext" : "ptr";
host.declare(`declare void @scr_map_set_str_${acc}(ptr, ptr, ${argTy})`);
@@ -41,7 +41,7 @@ export function emitUnionWiden(
B.br(join);
});
B.startBlock(join);
if (owned) host.releaseValue(value, { kind: "union", unionId: fromId });
if (owned) host.releaseFrame(value, { kind: "union", unionId: fromId });
const result = B.tmp();
B.line(`${result} = load ptr, ptr ${slot}`);
return result;
@@ -58,7 +58,7 @@ export function emitUnionRetype(
e: IrExpr & { kind: "unionRetype" },
): LlValue {
const value = host.emitExpr(e.value);
host.moveTemp(value);
host.toFrame(value);
const from = host.nullableUnions.get(e.fromId);
const to = host.nullableUnions.get(e.unionId);
if (from && to) {
@@ -131,7 +131,7 @@ function rebuildRetype(
B.br(join);
});
B.startBlock(join);
host.releaseValue(value, { kind: "union", unionId: e.fromId });
host.releaseFrame(value, { kind: "union", unionId: e.fromId });
const result = B.tmp();
B.line(`${result} = load ptr, ptr ${slot}`);
return result;
@@ -147,7 +147,9 @@ function directRegexOperandMethod(method: string): boolean {
case "match":
case "search":
case "matchAll":
case "matchAllCaptures":
case "matchAllInto":
case "matchAllCapturesInto":
case "replace":
case "replaceAll":
case "split":
@@ -192,7 +194,7 @@ export function emitRegexIntrinsic(
} else {
values = preservesRegexInputs(method)
? emitBorrowedInputs(host, inputs)
: method === "matchAllInto"
: method === "matchAllInto" || method === "matchAllCapturesInto"
? inputs.map((a) => emitBorrowedInput(host, a))
: inputs.map((a) => host.emitExpr(a));
}
@@ -216,6 +218,9 @@ export function emitRegexIntrinsic(
// Every match drained eagerly into a fresh +1 string[][]; throws
// Node's TypeError on a non-global regex (catchable).
return fallible("scr_regex_match_all", `ptr ${r.name}, ptr ${args[0]!.name}`);
case "matchAllCaptures":
// matchAll whose rows carry no index, input or groups properties.
return fallible("scr_regex_match_all_captures", `ptr ${r.name}, ptr ${args[0]!.name}`);
case "matchAllInto":
// matchAll's companion-index form: args[1] (a number[]) also
// receives each match's UTF-16 start index.
@@ -223,6 +228,11 @@ export function emitRegexIntrinsic(
"scr_regex_match_all_into",
`ptr ${r.name}, ptr ${args[0]!.name}, ptr ${args[1]!.name}`,
);
case "matchAllCapturesInto":
return fallible(
"scr_regex_match_all_captures_into",
`ptr ${r.name}, ptr ${args[0]!.name}, ptr ${args[1]!.name}`,
);
case "replaceAll":
return fallible(
"scr_regex_replace_all",
@@ -476,10 +486,10 @@ export function keyedRecordReadInto(
if (!typeEquals(vt, resultType)) {
throw new LlvmUnsupportedError(`recordKeyGet:narrow:${vt.kind}`, loc);
}
return owned || !isRefCounted(vt) ? expr : host.retainValue(expr, vt);
return owned || !isRefCounted(vt) ? expr : host.retainFrame(expr, vt);
}
if (typeEquals(vt, resultType)) {
return owned ? expr : host.retainValue(expr, vt);
return owned ? expr : host.retainFrame(expr, vt);
}
if (vt.kind === "union" && resultType.kind === "union") {
return emitUnionWiden(host, expr, vt.unionId, resultType.unionId, owned);
@@ -490,7 +500,7 @@ export function keyedRecordReadInto(
if (vt.kind === "f64" || vt.kind === "bool") {
return host.unionNewOwned(def.id, tag, { name: expr, type: vt });
}
const payload = owned ? expr : host.retainValue(expr, vt);
const payload = owned ? expr : host.retainHeap(expr, vt);
return host.unionNewOwned(def.id, tag, { name: payload, type: vt });
};
for (const f of overflowOnly ? [] : shape.fields) {
@@ -708,6 +708,10 @@ export function streamTypedRefMaterializeAdapter(
}
// Publish both symbols before their bodies: class-array and callback
// converters can request this same adapter while emitting the commit.
// A recursive field type binds capsules of this class there too, so the
// class view ops are published first.
const opsMeta = isDynTypedRefType(t) ? host.classMeta.get(t.className) : undefined;
if (opsMeta && liveClassOpsEligible(opsMeta)) adapter.classOps = `@${snapshot}_cops`;
adapter.commit = `@${snapshot}_commit`;
adapter.commit = host.streamTypedRefCommitAdapter(t, snapshot);
const B = new BlockBuilder();
@@ -719,10 +723,7 @@ export function streamTypedRefMaterializeAdapter(
`llvm emitter bug: typed-ref materialize of unknown class ${t.className}`,
);
}
if (liveClassOpsEligible(meta)) {
adapter.classOps = `@${snapshot}_cops`;
liveClassOps(host, snapshot, meta);
}
if (adapter.classOps !== undefined) liveClassOps(host, snapshot, meta);
if (meta.hierarchy && meta.children.length) {
const vtSlot = B.tmp(),
vt = B.tmp(),
@@ -85,7 +85,7 @@ export function callbackAdapter(
symbol: string,
params: string[],
): AbiArgument[] {
host.moveTemp(value);
host.toHeap(value);
host.declare(`declare void @${symbol}(${params.join(", ")})`);
return [ptr(value.name), ptr(`@${symbol}`)];
}
@@ -47,7 +47,7 @@ export function emitConnectListener(host: LlvmEmitterContext, e: LibCallExpr): L
type = callbackType(cb),
arity = type.params.length,
second = type.params[1];
host.moveTemp(cb);
host.toHeap(cb);
let h1: string,
h2 = "null";
if (second?.kind === "union") {
@@ -131,7 +131,7 @@ export function emitDatagramLibCall(host: LlvmEmitterContext, e: LibCallExpr): L
case "dgram.bindCb":
case "dgram.connect":
case "dgram.connectCb":
if (args[3] !== undefined) host.moveTemp(args[3]);
if (args[3] !== undefined) host.toHeap(args[3]);
return finishRuntimeCall(
host,
e,
@@ -140,7 +140,7 @@ export function emitDatagramLibCall(host: LlvmEmitterContext, e: LibCallExpr): L
);
case "dgram.close":
case "dgram.closeCb":
if (args[1] !== undefined) host.moveTemp(args[1]);
if (args[1] !== undefined) host.toHeap(args[1]);
return finishRuntimeCall(host, e, "scr_dgram_close", [a[0]!, a[1] ?? ptr()]);
case "dgram.address": {
const type = e.type;
@@ -151,7 +151,7 @@ export function emitDatagramLibCall(host: LlvmEmitterContext, e: LibCallExpr): L
const record = host.B.tmp();
host.B.line(`${record} = call ptr @${mangleRecordNew(type.shapeId)}()`);
const out = host.own({ name: record, type });
host.moveTemp(ip);
host.toHeap(ip);
const shape = host.recordsById.get(type.shapeId);
if (shape === undefined) throw new InternalCompilerError("missing datagram address shape");
for (const field of ["address", "family", "port"]) {
@@ -185,7 +185,7 @@ export function emitDatagramLibCall(host: LlvmEmitterContext, e: LibCallExpr): L
? "scr_dgram_msg_thunk1"
: messageAdapter(host, type.params[1]!);
// Program-specific adapters are definitions, so do not redeclare them.
host.moveTemp(cb);
host.toHeap(cb);
if (type.params.length < 2)
host.declare(`declare void @${adapter}(ptr, ptr, ptr, ptr, double, double)`);
return finishRuntimeCall(host, e, "scr_dgram_on_message", [
@@ -213,7 +213,7 @@ export function emitDatagramLibCall(host: LlvmEmitterContext, e: LibCallExpr): L
case "dgram.onListening":
case "dgram.onClose":
case "dgram.onConnect":
host.moveTemp(args[1]!);
host.toHeap(args[1]!);
return finishRuntimeCall(
host,
e,
@@ -227,7 +227,7 @@ export function emitDatagramLibCall(host: LlvmEmitterContext, e: LibCallExpr): L
case "dns.lookup": {
const cb = args[2]!,
symbol = lookupAdapter(host, cb);
host.moveTemp(cb);
host.toHeap(cb);
if (callbackType(cb).params.length === 0)
host.declare("declare void @scr_dns_thunk0(ptr, ptr, ptr, double)");
return finishRuntimeCall(host, e, "scr_dns_lookup", [...a, ptr("@" + symbol)]);
@@ -92,7 +92,7 @@ export function emitIoLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValue
if (cbT.kind !== "func")
throw new InternalCompilerError("llvm emitter bug: rl.question callback not a func");
const args = e.args.map((a) => host.emitExpr(a));
host.moveTemp(args[2]!);
host.toHeap(args[2]!);
const adapter =
cbT.params.length === 0
? "scr_rl_answer_thunk0"
@@ -165,7 +165,7 @@ export function emitIoLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValue
const ev = host.emitExpr(evArg);
const cb = host.emitExpr(e.args[2]!);
const once = host.emitExpr(e.args[3]!);
host.moveTemp(cb);
host.toHeap(cb);
let adapter = "null";
if (evArg.value === 0 || evArg.value === 4) {
const param = cbT.params[0];
@@ -287,7 +287,7 @@ export function emitGenericLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll
const cbT = e.args[4]!.type;
if (cbT.kind !== "func")
throw new InternalCompilerError("llvm emitter bug: randomFill callback not a func");
host.moveTemp(args[4]!);
host.toHeap(args[4]!);
const dyn = e.fn === "crypto.randomFillCbDyn";
const adapter = host.cryptoBytesThunkFor(cbT, false, dyn ? "dyn" : "view");
const symbol = dyn ? "scr_crypto_random_fill_async_dyn" : "scr_crypto_random_fill_async";
@@ -305,7 +305,7 @@ export function emitGenericLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll
const cbT = e.args[2]!.type;
if (cbT.kind !== "func")
throw new InternalCompilerError("llvm emitter bug: generateKey callback not a func");
host.moveTemp(args[2]!);
host.toHeap(args[2]!);
const adapter = host.cryptoBytesThunkFor(cbT, false, "dyn");
host.declare(`declare void @scr_crypto_generate_key_async(ptr, ptr, ptr, ptr)`);
B.line(
@@ -320,7 +320,7 @@ export function emitGenericLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll
const cbT = e.args[3]!.type;
if (cbT.kind !== "func")
throw new InternalCompilerError("llvm emitter bug: randomInt callback not a func");
host.moveTemp(args[3]!);
host.toHeap(args[3]!);
const adapter = host.cryptoIntThunkFor(cbT);
host.declare(`declare void @scr_crypto_random_int_async(double, double, double, ptr, ptr)`);
B.line(
@@ -429,7 +429,7 @@ export function emitGenericLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll
const cbT = e.args[cbIndex]!.type;
if (cbT.kind !== "func")
throw new InternalCompilerError("llvm emitter bug: crypto callback not a func");
host.moveTemp(args[cbIndex]!);
host.toHeap(args[cbIndex]!);
const adapter = host.cryptoBytesThunkFor(cbT, e.fn === "crypto.hkdfCb");
if (e.fn === "crypto.randomBytesCb") {
host.declare(`declare void @scr_crypto_random_bytes_async(double, ptr, ptr)`);
@@ -486,7 +486,7 @@ export function emitGenericLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll
const cbT = e.args[cbIdx]!.type;
if (cbT.kind !== "func")
throw new InternalCompilerError(`llvm emitter bug: ${e.fn} callback not a func`);
host.moveTemp(args[cbIdx]!);
host.toHeap(args[cbIdx]!);
cb = args[cbIdx]!.name;
const sym = cbT.params.length === 0 ? "scr_http_resp_thunk0" : "scr_http_resp_thunk_res";
host.declare(`declare void @${sym}(ptr, ptr)`);
@@ -526,7 +526,13 @@ export function emitGenericLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll
if (MAY_THROW_LIB_FNS.has(e.fn) && LIB_FN_SYMS[e.fn] === undefined) {
throw new LlvmUnsupportedError(`libCall:${e.fn}`, e.loc);
}
const sym = mockTimerSym(host, e) ?? LIB_FN_SYMS[e.fn];
// queueMicrotask's checked-dynamic form carries the AsyncLocalStorage
// context like the static one (lib-process.ts).
const sym =
mockTimerSym(host, e) ??
(e.fn === "timers.queueMicrotaskDyn" && host.schedulesInContext
? "scr_queue_microtask_dyn_als"
: LIB_FN_SYMS[e.fn]);
if (sym === undefined) throw new LlvmUnsupportedError(`libCall:${e.fn}`, e.loc);
const args = READ_ONLY_STRING_LIB_FNS.has(e.fn)
? emitBorrowedInputs(host, e.args)
@@ -716,7 +722,7 @@ export function emitLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValue {
// Literals (`123n`) and constant BigInt("...") texts are immortal
// statics instead of a parse at every evaluation.
const sym = host.internBigIntLiteral(e.args[0].value);
if (sym) return host.own({ name: host.retainValue(sym, e.type), type: e.type });
if (sym) return host.own({ name: host.retainFrame(sym, e.type), type: e.type });
}
if (e.fn.startsWith("bigint.")) {
// Small-value operator trees run in 64-bit registers (bigint-wrap.ts).
@@ -826,6 +832,7 @@ export function emitLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValue {
case "util":
case "bigint":
case "crypto":
case "webcrypto":
case "buffer":
case "bytes":
case "tlsca":
@@ -193,7 +193,7 @@ export function emitFilesystemLibCall(host: LlvmEmitterContext, e: LibCallExpr):
const cbT = e.args[1]!.type;
if (cbT.kind !== "func")
throw new InternalCompilerError("llvm emitter bug: zlib callback not a func");
host.moveTemp(args[1]!);
host.toHeap(args[1]!);
const adapter = host.zlibBytesThunkFor(cbT);
const compressing =
e.fn === "zlib.deflateCb" || e.fn === "zlib.deflateRawCb" || e.fn === "zlib.gzipCb";
@@ -246,7 +246,7 @@ export function emitFilesystemLibCall(host: LlvmEmitterContext, e: LibCallExpr):
const cbT = e.args[3]!.type;
if (cbT.kind !== "func")
throw new InternalCompilerError("llvm emitter bug: zlib callback not a func");
host.moveTemp(args[3]!);
host.toHeap(args[3]!);
const adapter = host.zlibBytesThunkFor(cbT);
const sym =
e.fn === "zlib.cbOpts" ? "scr_zlib_codec_async_opts" : "scr_zlib_codec_async_opts_dyn";
@@ -293,7 +293,7 @@ export function emitFilesystemLibCall(host: LlvmEmitterContext, e: LibCallExpr):
const operands = args.slice(0, withCb ? -1 : undefined).map((a) => `ptr ${a.name}`);
let cb = "null";
if (withCb) {
host.moveTemp(args[args.length - 1]!);
host.toHeap(args[args.length - 1]!);
cb = args[args.length - 1]!.name;
}
const sym =
@@ -356,7 +356,7 @@ export function emitFilesystemLibCall(host: LlvmEmitterContext, e: LibCallExpr):
const cbT = e.args[2]!.type;
if (cbT.kind !== "func")
throw new InternalCompilerError("llvm emitter bug: fs.rename callback not a func");
host.moveTemp(args[2]!);
host.toHeap(args[2]!);
const adapter = host.fsRenameThunkFor(cbT);
host.declare(`declare void @scr_fs_rename_async(ptr, ptr, ptr, ptr)`);
B.line(
@@ -403,7 +403,7 @@ export function emitFilesystemLibCall(host: LlvmEmitterContext, e: LibCallExpr):
const countField =
e.fn === "fileHandle.read" || e.fn === "fileHandle.readv" ? "bytesRead" : "bytesWritten";
host.storeField(host.recordFieldPtr(rec, inner.shapeId, countField).ptr, F64, count, false);
const payload = host.retainValue(args[1]!.name, e.args[1]!.type);
const payload = host.retainHeap(args[1]!.name, e.args[1]!.type);
B.line(
`store ptr ${payload}, ptr ${host.recordFieldPtr(rec, inner.shapeId, vector ? "buffers" : "buffer").ptr}`,
);
@@ -465,7 +465,7 @@ export function emitFilesystemLibCall(host: LlvmEmitterContext, e: LibCallExpr):
throw new InternalCompilerError("llvm emitter bug: fs.watchCb callback not a func");
const path = host.emitExpr(e.args[0]!);
const cb = host.emitExpr(e.args[1]!);
host.moveTemp(cb);
host.toHeap(cb);
const adapter = cbT.params.length === 0 ? "scr_watch_thunk0" : "scr_watch_thunk_event";
host.declare(`declare void @${adapter}(ptr, ptr)`);
host.declare(`declare ptr @scr_fs_watch(ptr, ptr, ptr)`);
@@ -618,7 +618,7 @@ export function emitFilesystemLibCall(host: LlvmEmitterContext, e: LibCallExpr):
B.line(`${nm} = call ptr @scr_fs_scandir_name(ptr ${snap}, ${host.sizeType} ${i}) ; +1`);
B.line(`store ptr ${nm}, ptr ${host.recordFieldPtr(row, recT.shapeId, "name").ptr}`);
B.line(
`store ptr ${host.retainValue(path.name, STRING)}, ptr ${host.recordFieldPtr(row, recT.shapeId, "parentPath").ptr}`,
`store ptr ${host.retainHeap(path.name, STRING)}, ptr ${host.recordFieldPtr(row, recT.shapeId, "parentPath").ptr}`,
);
host.arrPush(arr, "ref", row); // push takes ownership of the row
const i2 = B.tmp();
@@ -630,7 +630,7 @@ export function emitFilesystemLibCall(host: LlvmEmitterContext, e: LibCallExpr):
if (!promiseForm) return arrayOut;
const rc = vAdapters(host.shapeHost, arrayT);
host.declare(`declare ptr @scr_promise_settled_ref(ptr, ptr, ptr, ptr)`);
host.moveTemp(arrayOut); // promise fulfillment owns the result array
host.toHeap(arrayOut); // promise fulfillment owns the result array
const promise = B.tmp();
B.line(
`${promise} = call ptr @scr_promise_settled_ref(ptr ${arr}, ptr ${rc.retain}, ptr ${rc.release}, ptr ${traceArg(host.shapeHost, arrayT)})`,
@@ -696,7 +696,7 @@ export function emitFilesystemLibCall(host: LlvmEmitterContext, e: LibCallExpr):
if (!promiseForm) return value;
const rc = vAdapters(host.shapeHost, unionT);
host.declare(`declare ptr @scr_promise_settled_ref(ptr, ptr, ptr, ptr)`);
host.moveTemp(value);
host.toHeap(value);
const promise = B.tmp();
B.line(
`${promise} = call ptr @scr_promise_settled_ref(ptr ${value.name}, ptr ${rc.retain}, ptr ${rc.release}, ptr ${traceArg(host.shapeHost, unionT)})`,
@@ -941,7 +941,7 @@ export function emitPathUrlLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll
const retained = B.tmp();
B.line(`${retained} = call ptr @scr_arr_retain_v(ptr ${peeked})`);
B.line(`store ptr ${retained}, ptr ${rowsSlot}`);
host.releaseValue(cell, iv);
host.releaseFrame(cell, iv);
B.br(lj);
B.startBlock(lm);
const fresh = B.tmp();
@@ -170,7 +170,7 @@ export function emitHttp2LibCall(host: LlvmEmitterContext, e: LibCallExpr): LlVa
if (tag < 0) throw new InternalCompilerError("SNI callback union lacks its function arm");
type = arms[tag]!;
value = host.unwrapNullableClosure(value, tag);
} else host.moveTemp(cb);
} else host.toHeap(cb);
if (type.kind !== "func" || type.params[1] === undefined)
throw new InternalCompilerError("SNI callback lacks its answer function");
sni = [ptr(value), ptr("@" + sniAnswer(host, type.params[1]))];
@@ -207,7 +207,7 @@ export function emitHttp2LibCall(host: LlvmEmitterContext, e: LibCallExpr): LlVa
case "http2.streamCloseCb": {
const stream = e.fn.startsWith("http2.stream"),
cbIndex = stream ? 2 : 1;
if (args[cbIndex] !== undefined) host.moveTemp(args[cbIndex]!);
if (args[cbIndex] !== undefined) host.toHeap(args[cbIndex]!);
return finishRuntimeCall(
host,
e,
@@ -310,7 +310,7 @@ export function emitHttp2LibCall(host: LlvmEmitterContext, e: LibCallExpr): LlVa
default:
throw new InternalCompilerError(`unhandled HTTP/2 listener ${e.fn}`);
}
if (thunk === null) host.moveTemp(cb);
if (thunk === null) host.toHeap(cb);
const handler = thunk === null ? [a[1]!] : callbackAdapter(host, cb, thunk, params);
return finishRuntimeCall(host, e, runtime, [a[0]!, ...handler, ...a.slice(2)]);
}
@@ -86,7 +86,7 @@ export function emitLookupConnect(host: LlvmEmitterContext, e: LibCallExpr): LlV
);
}
host.usesTimers = true;
host.moveTemp(cb);
host.toHeap(cb);
return finishRuntimeCall(host, e, "scr_net_connect_lookup", [
...args.map((arg) => abiValue(host, arg)),
ptr("@" + symbol),
@@ -42,7 +42,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
if (e.fn === "http.requestConn" || e.fn === "http.requestConnCb") {
const args = e.args.map((arg) => host.emitExpr(arg));
host.usesTimers = true;
host.moveTemp(args[0]!);
host.toHeap(args[0]!);
const cb = args[6];
const handler =
cb === undefined
@@ -72,7 +72,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
const cbT = e.args[0]!.type;
if (cbT.kind !== "func")
throw new InternalCompilerError("llvm emitter bug: net.createServerCb handler not a func");
host.moveTemp(args[0]!);
host.toHeap(args[0]!);
cb = args[0]!.name;
adapter = cbT.params.length === 0 ? "@scr_net_conn_thunk0" : "@scr_net_conn_thunk_sock";
host.declare(`declare void ${adapter}(ptr, ptr)`);
@@ -86,7 +86,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
const args = e.args.map((a) => host.emitExpr(a));
let cb = "null";
if (e.fn === "net.listenCb") {
host.moveTemp(args[2]!);
host.toHeap(args[2]!);
cb = args[2]!.name;
}
host.declare(`declare void @scr_net_listen(ptr, double, ptr)`);
@@ -109,7 +109,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
if (withCallback) {
const cbT = e.args[cbIndex]!.type;
if (cbT.kind === "func") {
host.moveTemp(args[cbIndex]!);
host.toHeap(args[cbIndex]!);
cb = args[cbIndex]!.name;
} else {
if (cbT.kind !== "union")
@@ -192,7 +192,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
const args = e.args.map((a) => host.emitExpr(a));
let cb = "null";
if (e.fn === "net.serverCloseCb") {
host.moveTemp(args[1]!);
host.toHeap(args[1]!);
cb = args[1]!.name;
}
host.declare(`declare void @scr_net_server_close(ptr, ptr)`);
@@ -233,7 +233,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
throw new InternalCompilerError("llvm emitter bug: close override not a func");
const args = e.args.map((a) => host.emitExpr(a));
const wrapSym = host.closeOverrideWrapFor(cbT.params[0]!, cbT.ret.kind === "netServer");
host.moveTemp(args[1]!); // ownership moves into the wrapper's env box
host.toHeap(args[1]!); // ownership moves into the wrapper's env box
const wrap = host.wrapEmitterListener(args[1]!.name, wrapSym);
host.declare(`declare void @scr_net_server_set_close_override(ptr, ptr)`);
B.line(`call void @scr_net_server_set_close_override(ptr ${args[0]!.name}, ptr ${wrap})`);
@@ -244,7 +244,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
if (cbT.kind !== "func")
throw new InternalCompilerError(`llvm emitter bug: ${e.fn} callback not a func`);
const args = e.args.map((a) => host.emitExpr(a));
host.moveTemp(args[1]!);
host.toHeap(args[1]!);
const adapter = cbT.params.length === 0 ? "scr_child_err_thunk0" : "scr_child_err_thunk_error";
const entry =
e.fn === "net.serverOnError" ? "scr_net_server_on_error" : "scr_net_sock_on_error";
@@ -295,7 +295,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
"http.clientOnContinue": "scr_http_client_on_continue",
}[e.fn]!;
const args = e.args.map((a) => host.emitExpr(a));
host.moveTemp(args[1]!);
host.toHeap(args[1]!);
host.declare(`declare void @${entry}(ptr, ptr, i1 zeroext)`);
B.line(`call void @${entry}(ptr ${args[0]!.name}, ptr ${args[1]!.name}, i1 ${args[2]!.name})`);
return { name: "", type: e.type };
@@ -312,7 +312,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
if (cbT.kind !== "func")
throw new InternalCompilerError(`llvm emitter bug: ${e.fn} callback not a func`);
const args = e.args.map((a) => host.emitExpr(a));
host.moveTemp(args[cbIndex]!);
host.toHeap(args[cbIndex]!);
const adapter = cbT.params.length === 0 ? "scr_net_conn_thunk0" : "scr_net_conn_thunk_sock";
host.declare(`declare void @${adapter}(ptr, ptr)`);
if (e.fn === "http.serverSetTimeoutCb") {
@@ -340,7 +340,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
const args = e.args.map((a) => host.emitExpr(a));
let cb = "null";
if (e.fn === "net.connectCb") {
host.moveTemp(args[2]!);
host.toHeap(args[2]!);
cb = args[2]!.name;
}
host.declare(`declare ptr @scr_net_connect_checked(double, ptr, ptr)`);
@@ -357,7 +357,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
if (cbT.kind !== "func")
throw new InternalCompilerError(`llvm emitter bug: ${e.fn} callback not a func`);
const args = e.args.map((a) => host.emitExpr(a));
host.moveTemp(args[1]!);
host.toHeap(args[1]!);
const adapter =
cbT.params.length === 0
? "scr_net_data_thunk0"
@@ -533,7 +533,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
const cbT = e.args[0]!.type;
if (cbT.kind !== "func")
throw new InternalCompilerError("llvm emitter bug: http.createServer handler not a func");
host.moveTemp(args[0]!);
host.toHeap(args[0]!);
cb = args[0]!.name;
const sym =
cbT.params.length === 2
@@ -554,7 +554,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
if (cbT.kind !== "func")
throw new InternalCompilerError(`llvm emitter bug: ${e.fn} handler not a func`);
const args = e.args.map((a) => host.emitExpr(a));
host.moveTemp(args[1]!);
host.toHeap(args[1]!);
const adapter =
cbT.params.length === 2
? "scr_http_handler_thunk2"
@@ -580,7 +580,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
if (cbT.kind !== "func")
throw new InternalCompilerError(`llvm emitter bug: ${e.fn} listener not a func`);
const args = e.args.map((a) => host.emitExpr(a));
host.moveTemp(args[1]!);
host.toHeap(args[1]!);
const adapter =
cbT.params.length === 3
? "scr_http_upgrade_thunk3"
@@ -607,7 +607,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
if (cbT.kind !== "func")
throw new InternalCompilerError(`llvm emitter bug: ${e.fn} callback not a func`);
const args = e.args.map((a) => host.emitExpr(a));
host.moveTemp(args[1]!);
host.toHeap(args[1]!);
const adapter =
e.fn === "http.clientOnResponse"
? cbT.params.length === 0
@@ -630,7 +630,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
}
if (e.fn === "http.resOnFinish" || e.fn === "http.resOnEndCallback") {
const args = e.args.map((a) => host.emitExpr(a));
host.moveTemp(args[1]!);
host.toHeap(args[1]!);
const entry =
e.fn === "http.resOnFinish" ? "scr_http_res_on_finish" : "scr_http_res_on_end_callback";
host.declare(`declare void @${entry}(ptr, ptr)`);
@@ -639,7 +639,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
}
if (e.fn === "http.resSetTimeoutCb" || e.fn === "http.reqSetTimeoutCb") {
const args = e.args.map((a) => host.emitExpr(a));
host.moveTemp(args[2]!);
host.toHeap(args[2]!);
const entry =
e.fn === "http.resSetTimeoutCb" ? "scr_http_res_set_timeout" : "scr_http_req_set_timeout";
host.declare(`declare void @${entry}(ptr, double, ptr)`);
@@ -650,7 +650,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
}
if (e.fn === "http.clientSetTimeoutCb") {
const args = e.args.map((a) => host.emitExpr(a));
host.moveTemp(args[2]!);
host.toHeap(args[2]!);
host.declare("declare void @scr_http_client_set_timeout_cb(ptr, double, ptr)");
B.line(
`call void @scr_http_client_set_timeout_cb(ptr ${args[0]!.name}, double ${args[1]!.name}, ptr ${args[2]!.name})`,
@@ -659,21 +659,21 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
}
if (e.fn === "net.sockOnFinish") {
const args = e.args.map((a) => host.emitExpr(a));
host.moveTemp(args[1]!);
host.toHeap(args[1]!);
host.declare(`declare void @scr_net_sock_on_finish(ptr, ptr)`);
B.line(`call void @scr_net_sock_on_finish(ptr ${args[0]!.name}, ptr ${args[1]!.name})`);
return { name: "", type: e.type };
}
if (e.fn === "net.sockOnWriteFlush") {
const args = e.args.map((a) => host.emitExpr(a));
host.moveTemp(args[1]!);
host.toHeap(args[1]!);
host.declare(`declare void @scr_net_sock_on_write_flush(ptr, ptr)`);
B.line(`call void @scr_net_sock_on_write_flush(ptr ${args[0]!.name}, ptr ${args[1]!.name})`);
return { name: "", type: e.type };
}
if (e.fn === "net.sockSetTimeoutCb") {
const args = e.args.map((a) => host.emitExpr(a));
host.moveTemp(args[2]!);
host.toHeap(args[2]!);
host.declare("declare void @scr_net_sock_set_timeout_cb(ptr, double, ptr)");
B.line(
`call void @scr_net_sock_set_timeout_cb(ptr ${args[0]!.name}, double ${args[1]!.name}, ptr ${args[2]!.name})`,
@@ -18,7 +18,7 @@ export function emitChildProcessLibCall(host: LlvmEmitterContext, e: LibCallExpr
const cmd = host.emitExpr(e.args[0]!);
const argv = host.emitExpr(e.args[1]!);
const cb = host.emitExpr(callbackArg);
host.moveTemp(cb);
host.toHeap(cb);
const shell = host.emitExpr(e.args[3]!);
const adapter = host.execFileThunkFor(cbT);
host.usesTimers = true;
@@ -72,7 +72,7 @@ export function emitChildProcessLibCall(host: LlvmEmitterContext, e: LibCallExpr
if (e.fn === "child.onSpawn") {
const child = host.emitExpr(e.args[0]!);
const cb = host.emitExpr(e.args[1]!);
host.moveTemp(cb);
host.toHeap(cb);
host.declare("declare void @scr_child_on_spawn(ptr, ptr)");
B.line(`call void @scr_child_on_spawn(ptr ${child.name}, ptr ${cb.name})`);
return { name: "", type: e.type };
@@ -87,7 +87,7 @@ export function emitChildProcessLibCall(host: LlvmEmitterContext, e: LibCallExpr
throw new InternalCompilerError(`llvm emitter bug: ${e.fn} callback not a func`);
const child = host.emitExpr(e.args[0]!);
const cb = host.emitExpr(e.args[1]!);
host.moveTemp(cb);
host.toHeap(cb);
let adapter: string;
if (cbT.params.length === 0) {
adapter = "scr_child_exit_thunk0";
@@ -110,7 +110,7 @@ export function emitChildProcessLibCall(host: LlvmEmitterContext, e: LibCallExpr
throw new InternalCompilerError("llvm emitter bug: child.onError callback not a func");
const child = host.emitExpr(e.args[0]!);
const cb = host.emitExpr(e.args[1]!);
host.moveTemp(cb);
host.toHeap(cb);
const adapter = cbT.params.length === 0 ? "scr_child_err_thunk0" : "scr_child_err_thunk_error";
host.declare(`declare void @${adapter}(ptr, ptr)`);
host.declare(`declare void @scr_child_on_error(ptr, ptr, ptr)`);
@@ -136,7 +136,7 @@ export function emitChildProcessLibCall(host: LlvmEmitterContext, e: LibCallExpr
if (cbT.kind !== "func")
throw new InternalCompilerError("llvm emitter bug: child.send callback not a func");
const cb = host.emitExpr(e.args[2]!);
host.moveTemp(cb);
host.toHeap(cb);
suffix = `, ptr ${cb.name}, ptr @${host.ipcSendThunkFor(cbT)}`;
decl += ", ptr, ptr";
}
@@ -160,7 +160,7 @@ export function emitChildProcessLibCall(host: LlvmEmitterContext, e: LibCallExpr
throw new InternalCompilerError(`llvm emitter bug: ${e.fn} callback not a func`);
const cb = host.emitExpr(e.args[1]!);
const once = host.emitExpr(e.args[2]!);
host.moveTemp(cb);
host.toHeap(cb);
if (e.fn === "child.onMessage") {
host.declare(`declare void @scr_child_ipc_on_message(ptr, ptr, ptr, i1 zeroext)`);
B.line(
@@ -337,7 +337,7 @@ export function emitChildProcessLibCall(host: LlvmEmitterContext, e: LibCallExpr
if (cbT.kind !== "func")
throw new InternalCompilerError(`llvm emitter bug: ${e.fn} callback not a func`);
const cb = host.emitExpr(e.args[1]!);
host.moveTemp(cb);
host.toHeap(cb);
const once = host.emitExpr(e.args[2]!);
if (e.fn === "writer.onError") {
const adapter =
@@ -373,17 +373,19 @@ export function emitAsyncContextLibCall(host: LlvmEmitterContext, e: LibCallExpr
// main runs the loop.
host.usesTimers = true;
const cb = host.emitExpr(e.args[0]!);
host.moveTemp(cb);
host.toHeap(cb);
// Programs that enter AsyncLocalStorage stores schedule through the
// *_als entry points, which carry the active stores into the callback.
const sym =
mockTimerSym(host, e) ??
{
"timers.setTimeout": "scr_set_timeout",
"timers.setInterval": "scr_set_interval",
"timers.setTimeoutHandle": "scr_set_timeout_handle",
"timers.setImmediate": "scr_set_immediate",
"process.nextTick": "scr_next_tick",
"timers.queueMicrotask": "scr_queue_microtask",
}[e.fn];
(mockTimerSym(host, e) ??
{
"timers.setTimeout": "scr_set_timeout",
"timers.setInterval": "scr_set_interval",
"timers.setTimeoutHandle": "scr_set_timeout_handle",
"timers.setImmediate": "scr_set_immediate",
"process.nextTick": "scr_next_tick",
"timers.queueMicrotask": "scr_queue_microtask",
}[e.fn]!) + (host.schedulesInContext ? "_als" : "");
const rest = e.args.slice(1).map((a) => host.emitExpr(a));
const argList = [`ptr ${cb.name}`, ...rest.map((a) => `${host.llType(a.type)} ${a.name}`)].join(
", ",
@@ -464,7 +466,7 @@ export function emitProcessLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll
if (cbT.kind !== "func")
throw new InternalCompilerError("llvm emitter bug: process.send callback not a func");
const cb = host.emitExpr(e.args[1]!);
host.moveTemp(cb);
host.toHeap(cb);
suffix = `, ptr ${cb.name}, ptr @${host.ipcSendThunkFor(cbT)}`;
decl += ", ptr, ptr";
}
@@ -486,7 +488,7 @@ export function emitProcessLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll
throw new InternalCompilerError(`llvm emitter bug: ${e.fn} callback not a func`);
const cb = host.emitExpr(e.args[0]!);
const once = host.emitExpr(e.args[1]!);
host.moveTemp(cb);
host.toHeap(cb);
if (e.fn === "process.onMessage") {
host.declare(`declare void @scr_process_ipc_on_message(ptr, ptr, i1 zeroext)`);
B.line(
@@ -507,7 +509,7 @@ export function emitProcessLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll
const cbT = e.args[2]!.type;
if (cbT.kind !== "func")
throw new InternalCompilerError("llvm emitter bug: process write callback not a func");
host.moveTemp(args[2]!);
host.toHeap(args[2]!);
const adapter = host.fsRenameThunkFor(cbT);
const write =
e.fn === "process.stdoutWriteBytesCb"
@@ -538,7 +540,7 @@ export function emitProcessLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll
throw new InternalCompilerError(`llvm emitter bug: ${e.fn} callback not a func`);
const cb = host.emitExpr(e.args[0]!);
const once = host.emitExpr(e.args[1]!);
host.moveTemp(cb);
host.toHeap(cb);
if (e.fn === "stdin.onEnd") {
host.declare(`declare void @scr_stdin_on_end(ptr, i1 zeroext)`);
B.line(`call void @scr_stdin_on_end(ptr ${cb.name}, i1 ${once.name})`);
@@ -586,7 +588,7 @@ export function emitProcessLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll
throw new InternalCompilerError("llvm emitter bug: process.onExit callback not a func");
const cb = host.emitExpr(e.args[0]!);
const once = host.emitExpr(e.args[1]!);
host.moveTemp(cb);
host.toHeap(cb);
const adapter = cbT.params.length === 0 ? "scr_exit_thunk0" : "scr_exit_thunk_code";
host.declare(`declare void @${adapter}(ptr, double)`);
host.declare(`declare void @scr_process_on_exit(ptr, ptr, i1 zeroext)`);
@@ -741,7 +743,7 @@ export function emitErrorsEventsLibCall(host: LlvmEmitterContext, e: LibCallExpr
const cbr = B.tmp();
B.line(`${cbr} = call ptr @scr_closure_retain_v(ptr ${args[2]!.name})`);
const wrapped = host.wrapEmitterListener(cbr, adapterFn);
host.moveTemp(args[2]!);
host.toHeap(args[2]!);
host.declare(
`declare ptr @scr_emitter_on_via(ptr, ptr, ptr, ptr, ptr, i1 zeroext, i1 zeroext)`,
);
@@ -765,7 +767,7 @@ export function emitErrorsEventsLibCall(host: LlvmEmitterContext, e: LibCallExpr
throw new InternalCompilerError("llvm emitter bug: emitter.onDyn adapter not a func");
const args = e.args.map((a) => host.emitExpr(a));
const { fn: adapterFn, shim } = host.emitterFixedAdapter(adT);
host.moveTemp(args[3]!); // the frame's +1 moves into the wrapper's box
host.toHeap(args[3]!); // the frame's +1 moves into the wrapper's box
const wrapped = host.wrapEmitterListener(args[3]!.name, adapterFn);
host.declare(
`declare ptr @scr_emitter_on_dyn(ptr, ptr, ptr, ptr, ptr, i1 zeroext, i1 zeroext)`,
@@ -864,7 +866,7 @@ export function emitErrorsEventsLibCall(host: LlvmEmitterContext, e: LibCallExpr
host.declare(`declare void @scr_ee_inv_fixed2(ptr, ptr)`);
const t = B.tmp();
if (isDyn) {
host.moveTemp(args[3]!);
host.toHeap(args[3]!);
const wrapped = host.wrapEmitterListener(args[3]!.name, adapterFn);
host.declare(
`declare ptr @scr_emitter_on_dyn(ptr, ptr, ptr, ptr, ptr, i1 zeroext, i1 zeroext)`,
@@ -878,7 +880,7 @@ export function emitErrorsEventsLibCall(host: LlvmEmitterContext, e: LibCallExpr
const cbr = B.tmp();
B.line(`${cbr} = call ptr @scr_closure_retain_v(ptr ${args[2]!.name})`);
const wrapped = host.wrapEmitterListener(cbr, adapterFn);
host.moveTemp(args[2]!);
host.toHeap(args[2]!);
host.declare(
`declare ptr @scr_emitter_on_via(ptr, ptr, ptr, ptr, ptr, i1 zeroext, i1 zeroext)`,
);
@@ -94,6 +94,8 @@ export const LIB_FN_SYMS: Record<string, string> = {
"num.parseInt": "scr_parse_int",
"num.parseFloat": "scr_parse_float",
"str.localeCompare": "scr_str_locale_compare",
"str.substr": "scr_str_substr",
"str.localeCase": "scr_str_locale_case",
"str.replaceLiteral": "scr_str_replace_literal",
"str.replaceChars": "scr_str_replace_chars",
"num.fromString": "scr_string_to_number",
@@ -143,6 +145,8 @@ export const LIB_FN_SYMS: Record<string, string> = {
"num.toExponential": "scr_num_to_exponential",
"num.toFixed0": "scr_num_to_fixed0",
"num.toFixed": "scr_num_to_fixed",
"num.toPrecision": "scr_num_to_precision",
"num.toExponentialDigits": "scr_num_to_exponential_digits",
"num.toStringRadix": "scr_num_to_string_radix",
"num.toStringRadixF64": "scr_num_to_string_radix_f64",
"num.sameValue": "scr_num_same_value",
@@ -195,6 +199,7 @@ export const LIB_FN_SYMS: Record<string, string> = {
"os.cpusModel": "scr_os_cpus_model",
"os.cpusField": "scr_os_cpus_field",
"os.constants": "scr_os_constants",
"fs.constants": "scr_fs_constants_object",
"constants.object": "scr_node_constants",
"os.release": "scr_os_release",
"os.userName": "scr_os_user_name",
@@ -326,6 +331,9 @@ export const LIB_FN_SYMS: Record<string, string> = {
"date.getMilliseconds": "scr_date_get_milliseconds",
"date.getUTCMilliseconds": "scr_date_get_milliseconds",
"date.getTimezoneOffset": "scr_date_get_timezone_offset",
"date.toString": "scr_date_to_string",
"date.toDateString": "scr_date_to_date_string",
"date.toTimeString": "scr_date_to_time_string",
"fs.existsSync": "scr_fs_exists",
// ── the throwing slice (MAY_THROW_LIB_FNS members): the generic path
// emits the standard pending check after each — exprs.ts's finish.
@@ -405,6 +413,7 @@ export const LIB_FN_SYMS: Record<string, string> = {
"buffer.fromStr": "scr_bytes_from_str",
"buffer.fromStrChecked": "scr_bytes_from_str_checked",
"buffer.brand": "scr_bytes_as_buffer",
"buffer.isBranded": "scr_bytes_is_buffer",
"buffer.fromDyn": "scr_buffer_from_dyn",
"buffer.concat": "scr_buffer_concat",
"buffer.concatLen": "scr_buffer_concat_len",
@@ -571,12 +580,19 @@ export const LIB_FN_SYMS: Record<string, string> = {
"crypto.randomIntMax": "scr_crypto_random_int_max",
"crypto.randomFillView": "scr_crypto_random_fill",
"crypto.randomFillViewRest": "scr_crypto_random_fill_rest",
"crypto.getRandomValues": "scr_crypto_get_random_values",
"crypto.getRandomValuesDyn": "scr_crypto_get_random_values_dyn",
"crypto.randomFillSyncDyn": "scr_crypto_random_fill_sync_dyn",
"crypto.randomUUIDOpts": "scr_crypto_random_uuid_opts",
"crypto.createSecretKey": "scr_crypto_create_secret_key",
"crypto.generateKeySync": "scr_crypto_generate_key_sync",
"crypto.keyObjectInput": "scr_crypto_key_object_input",
"crypto.isKeyObject": "scr_crypto_is_key_object",
"webcrypto.importKey": "scr_webcrypto_import_key",
"webcrypto.sign": "scr_webcrypto_sign",
"webcrypto.verify": "scr_webcrypto_verify",
"webcrypto.digest": "scr_webcrypto_digest",
"webcrypto.isCryptoKey": "scr_webcrypto_is_crypto_key",
"crypto.randomUUIDv7": "scr_crypto_random_uuid_v7",
"crypto.pbkdf2": "scr_crypto_pbkdf2",
"crypto.hkdf": "scr_crypto_hkdf",
@@ -883,12 +899,16 @@ export const LIB_FN_SYMS: Record<string, string> = {
"dyn.typedRefIs": "scr_dyn_typed_ref_is_key",
"dyn.classIs": "scr_dyn_class_is_key",
"dyn.iterPack": "scr_dyn_iter_pack",
"dyn.destructureRest": "scr_dyn_destructure_rest",
"dyn.iterator": "scr_dyn_iterator",
"dyn.arrayFromIterator": "scr_dyn_array_from_iterator",
"dyn.iteratorResult": "scr_dyn_iterator_result",
"dyn.iteratorCanStep": "scr_dyn_iterator_can_step",
"dyn.iteratorStep": "scr_dyn_iterator_step",
"dyn.iteratorStepDone": "scr_dyn_iterator_step_done",
"dyn.setGeneratorMembers": "scr_dyn_set_generator_members",
"dyn.iteratorHelperMethod": "scr_dyn_iterator_helper_method",
"dyn.collectionIteratorIndex": "scr_dyn_collection_iterator_index",
"dyn.mapSeedEntries": "scr_dyn_map_seed_entries",
"dyn.mapSeedEntry": "scr_dyn_map_seed_entry",
"dyn.arrLen": "scr_dyn_arr_len",
@@ -1409,6 +1429,7 @@ export const LIB_FN_SYMS: Record<string, string> = {
"regex.groupEquals": "scr_regex_group_eq",
"regex.groupNumber": "scr_regex_group_number",
"regex.groupParseInt": "scr_regex_group_parse_int",
"regex.resultProp": "scr_regex_result_prop",
// queueMicrotask's checked-dynamic form (borrowed dyn; a non-function
// throws synchronously), the minted setImmediate value, and the
// timers/promises immediate — all mark the loop live.
@@ -1455,6 +1476,10 @@ export const USES_TIMERS_LIB_FNS = new Set<string>([
"process.emitWarning",
// Decipheriv.setAuthTag reports DEP0182 on the next tick.
"crypto.cipherNew",
// WebCrypto results settle through the deferred crypto queue.
"webcrypto.sign",
"webcrypto.verify",
"webcrypto.digest",
"process.emitWarningText",
// The legacy url entries queue DEP0169/DEP0170 on the next tick.
"url.legacyParse",
@@ -48,7 +48,7 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV
const s0 = host.emitExpr(e.args[0]!);
const cb = host.emitExpr(e.args[1]!);
const once = host.emitExpr(e.args[2]!);
host.moveTemp(cb);
host.toHeap(cb);
const param = cbT.params[0];
let adapter: string;
if (param === undefined) {
@@ -70,7 +70,7 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV
const s0 = host.emitExpr(e.args[0]!);
const cb = host.emitExpr(e.args[1]!);
const once = host.emitExpr(e.args[2]!);
host.moveTemp(cb);
host.toHeap(cb);
host.declare(`declare void @scr_child_stream_on_end(ptr, ptr, i1 zeroext)`);
B.line(`call void @scr_child_stream_on_end(ptr ${s0.name}, ptr ${cb.name}, i1 ${once.name})`);
return { name: "", type: e.type };
@@ -86,7 +86,7 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV
const s0 = host.emitExpr(e.args[0]!);
const cb = host.emitExpr(e.args[1]!);
const once = host.emitExpr(e.args[2]!);
host.moveTemp(cb);
host.toHeap(cb);
host.declare(`declare void @scr_child_stream_thunk_str(ptr, ptr)`);
host.declare(`declare void @scr_child_stream_on_data_str(ptr, ptr, ptr, i1 zeroext)`);
B.line(
@@ -127,7 +127,7 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV
}
const cb = args[at]!;
const cbT = e.args[at]!.type;
host.moveTemp(cb); // the callback closure MOVES into the stream
host.toHeap(cb); // the callback closure MOVES into the stream
cbArgs.push(`ptr ${cb.name}`, `ptr @${host.streamCbThunkFor(canonical[i]!, cbT)}`);
at++;
}
@@ -182,7 +182,7 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV
const recv = host.emitExpr(e.args[0]!);
const cb = host.emitExpr(e.args[1]!);
const cbT = e.args[1]!.type;
host.moveTemp(cb); // the callback closure MOVES into the stream
host.toHeap(cb); // the callback closure MOVES into the stream
const sym = symOf[e.fn]!;
host.declare(`declare void @${sym}(ptr, ptr, ptr)`);
B.line(
@@ -210,7 +210,7 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV
}
const cb = args[at]!;
const cbT = e.args[at]!.type;
host.moveTemp(cb);
host.toHeap(cb);
cbArgs.push(`ptr ${cb.name}`, `ptr @${host.streamCbThunkFor(canonical[i]!, cbT)}`);
at++;
}
@@ -321,7 +321,7 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV
B.line(`${raw} = call ptr @scr_stream_read(ptr ${args[0]!.name}, double ${args[1]!.name})`);
const b = host.own({ name: raw, type: def!.arms[bytesTag]! });
host.emitPendingCheck();
host.moveTemp(b); // moves into the union arm when present
host.toHeap(b); // moves into the union arm when present
return host.wrapNullable(raw, raw, def!.arms[bytesTag]!, bytesTag, e.type, nullTag);
}
if (e.fn === "readable.flowing") {
@@ -383,7 +383,7 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV
const args = e.args.map((a) => host.emitExpr(a));
let cb = "null";
if (e.fn !== "writable.writeDyn" && e.args.length > 2) {
host.moveTemp(args[2]!);
host.toHeap(args[2]!);
cb = args[2]!.name;
}
host.declare(`declare zeroext i1 @${entry}(ptr, ptr, ptr)`);
@@ -412,7 +412,7 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV
else if (flags & 8) chunkD = args[at++]!.name;
let cbName = "null";
if (flags & 4) {
host.moveTemp(args[at]!);
host.toHeap(args[at]!);
cbName = args[at]!.name;
}
if (chunkD !== null) {
@@ -490,7 +490,7 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV
const cbT = e.args[1]!.type;
if (cbT.kind !== "func")
throw new InternalCompilerError("llvm emitter bug: stream.finished callback not a func");
host.moveTemp(args[1]!); // the watcher closure MOVES into the stream
host.toHeap(args[1]!); // the watcher closure MOVES into the stream
const thunk = host.streamCbThunkFor("e", cbT);
host.declare(`declare ptr @scr_stream_finished(ptr, ptr, ptr)`);
B.line(
@@ -599,7 +599,7 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV
throw new InternalCompilerError(
"llvm emitter bug: stream.finishedOpts callback not a func",
);
host.moveTemp(args[cbIdx]!);
host.toHeap(args[cbIdx]!);
const thunk = host.streamCbThunkFor("e", cbT);
host.declare(
`declare ptr @scr_stream_finished_opts(ptr, ptr, ptr, double, double, i1, ptr, ptr)`,
@@ -696,7 +696,7 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV
throw new InternalCompilerError(
"llvm emitter bug: stream.pipelineStages callback not a func",
);
host.moveTemp(args[args.length - 1]!);
host.toHeap(args[args.length - 1]!);
const thunk = host.streamCbThunkFor("e", cbT);
host.declare(`declare ptr @scr_stream_pipeline_stages(${params}, ptr, ptr)`);
B.line(
@@ -732,7 +732,7 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV
const cbT = e.args[1 + n]!.type;
if (cbT.kind !== "func")
throw new InternalCompilerError("llvm emitter bug: stream.pipeline callback not a func");
host.moveTemp(args[1 + n]!);
host.toHeap(args[1 + n]!);
const thunk = host.streamCbThunkFor("e", cbT);
host.declare(`declare ptr @scr_stream_pipeline(double, ptr, ptr, ptr)`);
B.line(
+11 -7
View File
@@ -8,7 +8,7 @@ export function emitTestLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlVal
host.usesTimers = true;
switch (e.fn) {
case "test.register":
host.moveTemp(args[3]!);
host.toHeap(args[3]!);
return finishRuntimeCall(host, e, "scr_test_register", a);
case "test.registerEmpty":
return finishRuntimeCall(host, e, "scr_test_register", [
@@ -17,13 +17,13 @@ export function emitTestLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlVal
...a.slice(3),
]);
case "test.suite":
host.moveTemp(args[3]!);
host.toHeap(args[3]!);
return finishRuntimeCall(host, e, "scr_test_suite", a);
case "test.hook":
host.moveTemp(args[1]!);
host.toHeap(args[1]!);
return finishRuntimeCall(host, e, "scr_test_hook", a);
case "test.sub":
host.moveTemp(args[4]!);
host.toHeap(args[4]!);
return finishRuntimeCall(host, e, "scr_test_sub", a);
case "test.subEmpty": {
const promise = callRuntime(host, "scr_test_sub", "ptr", [
@@ -49,22 +49,26 @@ export function emitTestLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlVal
return finishRuntimeCall(host, e, "scr_test_ctx_passed", a);
case "test.workerId":
return finishRuntimeCall(host, e, "scr_test_worker_id", a);
case "test.hasContext":
return finishRuntimeCall(host, e, "scr_test_has_context", a);
case "test.context":
return finishRuntimeCall(host, e, "scr_test_context", a);
case "test.ctxHook":
host.moveTemp(args[2]!);
host.toHeap(args[2]!);
return finishRuntimeCall(host, e, "scr_test_ctx_hook", a);
case "test.ctxPlan":
return finishRuntimeCall(host, e, "scr_test_ctx_plan", a);
case "test.ctxCount":
return finishRuntimeCall(host, e, "scr_test_ctx_count", a);
case "test.mockTrack":
host.moveTemp(args[0]!);
host.toHeap(args[0]!);
return finishRuntimeCall(host, e, "scr_test_mock_track", a);
case "test.mockRestoreAll":
return finishRuntimeCall(host, e, "scr_test_mock_restore_all", a);
case "test.mockReset":
return finishRuntimeCall(host, e, "scr_test_mock_reset", a);
case "test.ctxMockTrack":
host.moveTemp(args[1]!);
host.toHeap(args[1]!);
return finishRuntimeCall(host, e, "scr_test_ctx_mock_track", a);
case "test.ctxMockRestoreAll":
return finishRuntimeCall(host, e, "scr_test_ctx_mock_restore_all", a);
@@ -94,10 +94,10 @@ export function emitTlsLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValu
}
case "tls.connect":
case "tls.connectCb":
if (args[3] !== undefined) host.moveTemp(args[3]);
if (args[3] !== undefined) host.toHeap(args[3]);
return finishRuntimeCall(host, e, "scr_tls_connect_dyn", [...a.slice(0, 3), a[3] ?? ptr()]);
case "tls.sockOnSecureConnect":
host.moveTemp(args[1]!);
host.toHeap(args[1]!);
return finishRuntimeCall(host, e, "scr_tls_sock_on_secure_connect", a);
case "tls.sockOnSession": {
const cb = args[1]!,
@@ -629,7 +629,7 @@ export function emitLocalArrayRead(
// A slot of a nullable-union array may hold NULL even when present.
const owned = read.nullableElement ? read.type : read.element;
const store = (value: string): void => {
B.line(`store ptr ${read.borrow ? value : host.retainValue(value, owned)}, ptr ${localSlot}`);
B.line(`store ptr ${read.borrow ? value : host.retainFrame(value, owned)}, ptr ${localSlot}`);
};
if (inline)
emitDenseReferenceArrayRead(host, array, index, integerIndex, store, no, slow, join, "ptr");
@@ -676,7 +676,7 @@ export function emitLocalArrayRead(
B.line(`store i64 ${bits}, ptr ${payload}`);
} else {
B.line(
`store ptr ${read.borrow ? value : host.retainValue(value, read.element)}, ptr ${payload}`,
`store ptr ${read.borrow ? value : host.retainFrame(value, read.element)}, ptr ${payload}`,
);
}
B.line(`store i32 ${read.presentTag}, ptr ${tag}`);
@@ -202,7 +202,7 @@ export function storeLocalUnion(
B.startBlock(label);
host.frames.push([]);
storeLocalUnion(host, storage, value);
host.releaseFrame(host.frames.pop()!);
host.releaseTemps(host.frames.pop()!);
B.br(join);
}
B.startBlock(join);
@@ -241,7 +241,7 @@ export function storeLocalUnion(
const copied = host.emitOwnedNullableStack(expr);
source = copied.box;
owner = copied.owner.name;
host.moveTemp(copied.owner);
host.toFrame(copied.owner);
} else if (expr.kind === "varRef" && !canStackUnion(expr, host.boxedUnionsById)) {
// A copy of another binding: snapshot the tag and bits, and retain the
// reference payload (scalar and unit arms own nothing).
@@ -260,7 +260,7 @@ export function storeLocalUnion(
B.line(`${isRef} = icmp eq i32 ${tag}, ${refTag}`);
B.condBr(isRef, yes, join);
B.startBlock(yes);
const retained = host.retainValue(host.unionPeek(source, unionId), storage.ownerType);
const retained = host.retainFrame(host.unionPeek(source, unionId), storage.ownerType);
B.line(`store ptr ${retained}, ptr ${held}`);
B.br(join);
B.startBlock(join);
@@ -272,7 +272,7 @@ export function storeLocalUnion(
source = wrapped.value.name;
if (wrapped.payload) {
owner = wrapped.payload.name;
host.moveTemp(wrapped.payload);
host.toFrame(wrapped.payload);
}
} else {
const read = matchMapRead(expr, host.boxedUnionsById)!;
@@ -294,7 +294,7 @@ export function storeLocalUnion(
const old = B.tmp();
B.line(`${old} = load ptr, ptr ${storage.owner}`);
B.line(`store ptr ${owner}, ptr ${storage.owner}`);
host.releaseValue(old, storage.ownerType);
host.releaseFrame(old, storage.ownerType);
}
}
@@ -326,7 +326,7 @@ export function emitScalarOptional(
bindPresent();
host.frames.push([]);
storeLocalUnion(host, storage, expr.body);
host.releaseFrame(host.frames.pop()!);
host.releaseTemps(host.frames.pop()!);
host.chainSlots.delete(expr.id);
B.br(join);
} else {
@@ -339,7 +339,7 @@ export function emitScalarOptional(
B.startBlock(fallback);
host.frames.push([]);
storeLocalUnion(host, storage, expr.right);
host.releaseFrame(host.frames.pop()!);
host.releaseTemps(host.frames.pop()!);
B.br(join);
}
B.startBlock(join);
@@ -105,7 +105,7 @@ function emitMapEntryGetRef(host: LlvmEmitterContext, entry: string, value: IrTy
join = B.newLabel("mapn.join");
B.condBr(found, hit, miss);
B.startBlock(hit);
const retained = host.retainValue(emitNumberMapSlot(host, entry, "ptr"), value);
const retained = host.retainFrame(emitNumberMapSlot(host, entry, "ptr"), value);
B.br(join);
B.startBlock(miss);
B.br(join);
@@ -251,7 +251,7 @@ export function emitMapIterSlot(
B.line(`${loaded} = icmp ne i64 ${bits}, 0`);
} else {
B.line(`${loaded} = load ${llTy}, ptr ${slot}`);
if (access === "ref") loaded = host.retainValue(loaded, type);
if (access === "ref") loaded = host.retainFrame(loaded, type);
}
const hitEnd = B.currentLabel();
B.br(join);
@@ -29,6 +29,7 @@ export function preservesRegexInputs(method: string): boolean {
case "match":
case "search":
case "matchAll":
case "matchAllCaptures":
case "replace":
case "replaceAll":
case "split":
@@ -104,6 +104,7 @@ const INERT_LIB_CALLS = new Set([
"regex.groupEquals",
"regex.groupNumber",
"regex.groupParseInt",
"regex.resultProp",
"process.cwd",
"process.argv",
"process.execArgv",
+102 -35
View File
@@ -29,6 +29,7 @@ import {
mangleRecordTrace,
} from "../mangle.js";
import {
CYC_HDR,
cycTypeDescriptor,
emitObjectAlloc,
emitObjectFree,
@@ -58,67 +59,132 @@ import {
import { recordMayKeepSource } from "./record-sources.js";
import { LlvmUnsupportedError } from "./unsupported.js";
/** SCR_CYC_MAX_TYPES (scr_rc.h): the registry's size, and the length of
* the young-in nursery's per-id skip countdown. */
const CYC_MAX_TYPES = 32768;
/** What the tables need from the emitter: the extern-declaration ledger
* and the module-wide cycle/shape indexes. */
export interface ShapeHost {
declare(decl: string): void;
/** The hierarchy root of a class (a program class or a runtime base),
* or undefined for classes outside the class graph. */
classRootOf?(className: string): string | undefined;
/** Request the shared OOM abort helper (@sc_oom) — emitted once. */
needOom(): void;
/** Byte offset from an object pointer back to ScrCycHdr.buf. */
readonly cycleBufOffset: number;
declare(decl: string): void;
/** Inline the small-object allocator's fast paths (llvm/alloc.ts). */
readonly inlineAlloc?: boolean;
/** Record and class fields that store a scalar union's tag and bits
* inline (inline-unions.ts): they own nothing. */
readonly inlineUnions: InlineUnionFields;
/** The immutable ScrMapOps descriptor (scr_map.h) of a Map or Set type:
* a module constant holding the LLVM operands { key retain, key release,
* key trace, value retain, value release, value trace, union key kinds },
* or `null` when every operand is null. */
mapOps(operands: readonly string[]): string;
/** The target C ABI's `size_t` / refcount integer type. */
readonly sizeType: "i32" | "i64";
/** Byte offset from an object pointer back to ScrCycHdr.buf. */
readonly cycleBufOffset: number;
readonly tracedShapes: Set<string>;
readonly tracedUnions: Set<string>;
/** Request the shared OOM abort helper (@sc_oom) — emitted once. */
needOom(): void;
/** The next collector statistics index for an emitted ScrCycType
* descriptor (cycTypeDescriptor), counting from FIRST_EMITTED_CYC_KIND
* in emission order.
* Absent: every descriptor gets 0, which keeps no statistics. */
nextCycKind?(): number;
/** Unions represented as a nullable arm pointer (nullable-unions.ts):
* their RC and trace entry points are the arm's NULL-tolerant ones. */
readonly nullableUnions: NullableUnions;
/** Record and class fields that store a scalar union's tag and bits
* inline (inline-unions.ts): they own nothing. */
readonly inlineUnions: InlineUnionFields;
/** The zeroed `%ScrUnion` an inline store reads for a NULL dummy. */
unionNoneRef(): string;
readonly recordsById: Map<string, IrRecordShape>;
readonly recordCloneShapes: ReadonlySet<string>;
/** The program constructs lifetime regions: the ProgramLifetime classes
* allocate in regions and count references in their region chunk instead
* of being empty (llvm/regions.ts). */
regions?: RegionHooks | null;
/** The alias tag of a runtime word that lifetime-region code reads and
* writes outside every field: the chunk count, the chunk owner, the class
* cursors and scr_region_counting (", !tbaa !N", or "" in debug builds). */
regionAliasTag?(word: "count" | "owner" | "cursor" | "counting"): string;
/** Emit the live-object audit notes (`scr_obj_alloc_note` /
* `scr_obj_free_note`) in emitted new/free helpers. Only runtimes built
* with SCR_RC_AUDIT (the sanitized lane) count them; release and dev
* runtime packs define them empty, so plain builds omit the calls.
* Absent means emit. */
readonly objectAudit?: boolean;
/** Inline the small-object allocator's fast paths (llvm/alloc.ts). */
readonly inlineAlloc?: boolean;
/** Worker executables: the inline allocator state is thread-local. */
readonly threadLocalAlloc?: boolean;
/** Inline RC fast-path helpers requested so far ("<family>:<op>"); the
* emitter defines each once through {@link emitInlineRcHelpers}. Null
* keeps every retain/release a runtime call. */
readonly rcHelpers: Set<string> | null;
/** Which fast paths are inlined (see {@link InlineRcMode}). */
readonly rcMode?: InlineRcMode;
/** Define the fast-path helpers `noinline` (see {@link inlineRcOption}). */
readonly rcHelpersNoinline?: boolean;
/** The next collector statistics index for an emitted ScrCycType
* descriptor (cycTypeDescriptor), counting from FIRST_EMITTED_CYC_KIND
* in emission order.
* Absent: every descriptor gets 0, which keeps no statistics. */
nextCycKind?(): number;
/** Which fast paths are inlined (see {@link InlineRcMode}). */
readonly rcMode?: InlineRcMode;
readonly recordCloneShapes: ReadonlySet<string>;
readonly recordsById: Map<string, IrRecordShape>;
/** The alias tag of a runtime word that lifetime-region code reads and
* writes outside every field: the chunk count, the chunk owner, the class
* cursors and scr_region_counting (", !tbaa !N", or "" in debug builds). */
regionAliasTag?(word: "count" | "owner" | "cursor" | "counting"): string;
/** The program constructs lifetime regions: the ProgramLifetime classes
* allocate in regions and count references in their region chunk instead
* of being empty (llvm/regions.ts). */
regions?: RegionHooks | null;
/** The target C ABI's `size_t` / refcount integer type. */
readonly sizeType: "i32" | "i64";
/** Worker executables: the inline allocator state is thread-local. */
readonly threadLocalAlloc?: boolean;
readonly tracedShapes: Set<string>;
readonly tracedUnions: Set<string>;
/** The zeroed `%ScrUnion` an inline store reads for a NULL dummy. */
unionNoneRef(): string;
/** The young-in nursery (backend/young-nursery.ts): `new` hands each
* traced program-owned instance to scr_yin_enter. Absent: off. */
readonly youngNursery?: boolean;
}
/** The nursery entry of a new traced instance in `%o`, after its count is
* stored; nothing when the nursery is off. Where the allocator is inlined
* (64-bit, process-global state) the entry is inline too: a type that is
* skipping the nursery (its scr_yin_skip countdown is nonzero) counts down
* and stays out; otherwise, while the nursery has room (n < lim in ScrYin,
* scr_rc.h), list the object at index n and mark its header young
* (generation SCR_CYC_YOUNG, `buf` the buffered bit and the index), and
* call scr_yin_enter_slow when it has none (it collects the nursery
* first). Other targets call scr_yin_enter, which does all of it.
* Labels use the `ye.` prefix; the lines end in an open block. */
export function youngEntryLines(host: ShapeHost): string[] {
if (host.youngNursery !== true) return [];
if (host.inlineAlloc !== true || host.threadLocalAlloc === true) {
host.declare(`declare void @scr_yin_enter(ptr)`);
return [` call void @scr_yin_enter(ptr %o) ; the young-in nursery`];
}
host.declare(`@scr_yin = external global { ptr, i64, i64, i64 }`);
host.declare(`@scr_yin_skip = external global [${CYC_MAX_TYPES} x i32]`);
host.declare(`declare void @scr_yin_enter_slow(ptr)`);
return [
` %ye.idp = getelementptr inbounds i8, ptr %o, i64 -${CYC_HDR} ; the young-in nursery`,
` %ye.id16 = load i16, ptr %ye.idp ; ScrCycHdr.id`,
` %ye.id = zext i16 %ye.id16 to i64`,
` %ye.sp = getelementptr inbounds [${CYC_MAX_TYPES} x i32], ptr @scr_yin_skip, i64 0, i64 %ye.id`,
` %ye.s = load i32, ptr %ye.sp`,
` %ye.skip = icmp ne i32 %ye.s, 0`,
` br i1 %ye.skip, label %ye.skipped, label %ye.enter`,
`ye.skipped: ; a type that does not die young: counted down, stays out`,
` %ye.s1 = sub i32 %ye.s, 1`,
` store i32 %ye.s1, ptr %ye.sp`,
` br label %ye.done`,
`ye.enter:`,
` %ye.v = load ptr, ptr @scr_yin`,
` %ye.np = getelementptr inbounds i8, ptr @scr_yin, i64 8`,
` %ye.n = load i64, ptr %ye.np`,
` %ye.lp = getelementptr inbounds i8, ptr @scr_yin, i64 16`,
` %ye.l = load i64, ptr %ye.lp`,
` %ye.room = icmp ult i64 %ye.n, %ye.l`,
` br i1 %ye.room, label %ye.fast, label %ye.slow`,
`ye.fast:`,
` %ye.slot = getelementptr inbounds ptr, ptr %ye.v, i64 %ye.n`,
` store ptr %o, ptr %ye.slot`,
` %ye.n1 = add i64 %ye.n, 1`,
` store i64 %ye.n1, ptr %ye.np`,
` %ye.genp = getelementptr inbounds i8, ptr %o, i64 -5`,
` store i8 3, ptr %ye.genp ; ScrCycHdr.gen = SCR_CYC_YOUNG`,
` %ye.bufp = getelementptr inbounds i8, ptr %o, i64 -4`,
` %ye.i = trunc i64 %ye.n to i32`,
` %ye.buf = or i32 %ye.i, -2147483648`,
` store i32 %ye.buf, ptr %ye.bufp ; ScrCycHdr.buf = buffered bit | index`,
` br label %ye.done`,
`ye.slow:`,
` call void @scr_yin_enter_slow(ptr %o)`,
` br label %ye.done`,
`ye.done:`,
];
}
/** What class emission asks of lifetime regions (LifetimeRegions in
@@ -1622,6 +1688,7 @@ export function emitRecordShapes(
` store ptr %ovf, ptr %ovfp`,
);
}
if (traced) nw.push(...youngEntryLines(host));
nw.push(` ret ptr %o`, `}`, ``);
defs.push(...nw);
@@ -102,8 +102,8 @@ export function storeSplitSnapshot(
host.B.line(`store ptr ${snapshot.source}, ptr ${sourceSlot}`);
host.B.line(`store ptr ${snapshot.separator}, ptr ${separatorSlot}`);
host.B.line(`store i32 ${snapshot.limit}, ptr ${limitSlot}`);
host.moveTemp({ name: snapshot.source, type: STRING });
host.moveTemp({ name: snapshot.separator, type: STRING });
host.toFrame({ name: snapshot.source, type: STRING });
host.toFrame({ name: snapshot.separator, type: STRING });
return { sourceSlot, separatorSlot, limitSlot };
}
@@ -236,7 +236,7 @@ function emitRecordCloneFields(
const fieldTy = llFieldType(from.type, from.inline);
const v = B.tmp();
B.line(`${v} = load ${fieldTy}, ptr ${from.ptr}`);
const stored = isRefCounted(from.type) && !from.inline ? host.retainValue(v, from.type) : v;
const stored = isRefCounted(from.type) && !from.inline ? host.retainFrame(v, from.type) : v;
B.line(`store ${fieldTy} ${stored}, ptr ${to.ptr}`);
}
const initialized = new Set<string>();
@@ -432,7 +432,6 @@ test("borrowed literals keep UTF-16 string comparison without temporary owners",
op: "<",
left: str("a"),
right: str("b"),
utf16: true,
type: BOOL,
loc,
});
@@ -443,16 +442,14 @@ test("borrowed literals keep UTF-16 string comparison without temporary owners",
});
test("string comparisons decide identity, lengths and leading bytes before the runtime", () => {
const order = (utf16: boolean) =>
fn(`order${utf16 ? 16 : 8}`, [], {
kind: "strCmp",
op: "<",
left: str("apple"),
right: str("apricot"),
utf16,
type: BOOL,
loc,
});
const order = fn("order", [], {
kind: "strCmp",
op: "<",
left: str("apple"),
right: str("apricot"),
type: BOOL,
loc,
});
const same = fn("same", ["a", "b"], {
kind: "strEq",
left: { kind: "varRef", localId: "a", type: STRING, loc },
@@ -462,23 +459,28 @@ test("string comparisons decide identity, lengths and leading bytes before the r
loc,
});
for (const bits of [32, 64] as const) {
const ir = emitLlvmModule(mod(order(false), order(true), same), { pointerBits: bits });
const ir = emitLlvmModule(mod(order, same), { pointerBits: bits });
const helper = (name: string) =>
new RegExp(`^define internal [^\\n]*@${name}(?:\\.i32)?\\([^]*?^}`, "m").exec(ir)![0];
const eq = helper("sc_str_eq");
expect(eq).toContain("icmp eq ptr %a, %b");
expect(eq).toMatch(/icmp eq i(32|64) %alen, %blen/);
expect(eq).toContain("call zeroext i1 @scr_str_eq(ptr %a, ptr %b)");
const bytes = helper("sc_str_cmp");
expect(bytes).toContain("@llvm.bswap.i64");
expect(bytes).not.toContain("%ascii");
expect(bytes).toContain("call i32 @scr_str_cmp(ptr %a, ptr %b)");
const units = helper("sc_str_cmp_u16");
expect(units).toContain("%wascii = icmp eq i64 %whigh, 0");
expect(units).toContain("%tascii = icmp eq i64 %thigh, 0");
expect(bytes).toContain("%tailable = and i1 %samelen, %short");
expect(units).toContain("%ascii = icmp sge i8 %bits, 0");
expect(units).toContain("call i32 @scr_str_cmp_u16(ptr %a, ptr %b)");
expect(units).toContain("%dx = phi i64 [ %aw, %word ], [ %at, %tail ]");
expect(units).toContain("%dlow = icmp eq i64 %whigh, 0");
expect(units).toContain("@llvm.bswap.i64");
expect(units).toContain("%dtz = call i64 @llvm.cttz.i64(i64 %dxor, i1 true)");
expect(units).toContain("%bx = phi i8 [ %x, %first ], [ %dxb, %locate ]");
expect(units).toContain("%blow = icmp ult i8 %bmin, -19");
expect(units).toContain("%kbelow = icmp ult i8 %xk, %yk");
expect(units).toContain("%below = icmp ult i8 %bx, %by");
expect(units).toContain("%tailable = and i1 %samelen, %short");
expect(units).toContain("%anyascii = icmp slt i32 %cws, 0");
expect(units).toContain("call i32 @scr_str_cmp(ptr %a, ptr %b)");
expect(units).toContain(
"call i32 @scr_str_cmp_u16_bytes(ptr %a, ptr %b) nounwind willreturn memory(argmem: read)",
);
}
});
@@ -91,7 +91,7 @@ export function emitStringSliceSnapshot(
B.line(
`call void @scr_str_slice_range(ptr ${source.name}, double ${args[0]?.name ?? f64Lit(0)}, double ${args[1]?.name ?? F64_INF}, i1 ${slice.method === "substring" ? "true" : "false"}, ptr ${range})`,
);
host.moveTemp(source);
host.toFrame(source);
return { source: sourceSlot, range };
}
@@ -49,7 +49,7 @@ export function emitThreadSlotsIntrinsic(
case "slots.get": {
const read = readSlot(host, block, word);
if (host.nullableUnions.of(e.type) !== null)
return host.own({ name: host.retainValue(read, e.type), type: e.type });
return host.own({ name: host.retainFrame(read, e.type), type: e.type });
return boxedRead(host, read, e.type);
}
case "slots.has": {
@@ -67,13 +67,13 @@ export function emitThreadSlotsIntrinsic(
case "slots.set": {
if (value === null) throw new InternalCompilerError("llvm emitter bug: slots.set value");
// The value moves into the block.
host.moveTemp(value);
host.toHeap(value);
host.declare(`declare void @scr_slots_set(ptr, ptr, ptr, ptr, ptr)`);
B.line(
`call void @scr_slots_set(ptr ${block}, ptr ${key.name}, ptr ${word}, ptr ${value.name}, ptr ${releaseSym(host.shapeHost, value.type)})`,
);
if (e.type.kind === "void") return { name: "", type: e.type };
return host.own({ name: host.retainValue(slots.name, e.type), type: e.type });
return host.own({ name: host.retainFrame(slots.name, e.type), type: e.type });
}
default:
throw new InternalCompilerError(`llvm emitter bug: unknown intrinsic ${e.name}`);
@@ -100,7 +100,7 @@ function boxedRead(host: LlvmEmitterContext, read: string, type: IrType): LlValu
B.condBr(has, some, none);
B.startBlock(some);
const arm = def.arms[refTag]!;
const box = host.unionNewOwned(def.id, refTag, { name: host.retainValue(read, arm), type: arm });
const box = host.unionNewOwned(def.id, refTag, { name: host.retainHeap(read, arm), type: arm });
B.line(`store ptr ${box}, ptr ${result}`);
B.br(done);
B.startBlock(none);
+3 -1
View File
@@ -297,7 +297,9 @@ export function computeMayThrow(
rec.method === "replaceAll" ||
rec.method === "split" ||
rec.method === "matchAll" ||
rec.method === "matchAllInto"
rec.method === "matchAllCaptures" ||
rec.method === "matchAllInto" ||
rec.method === "matchAllCapturesInto"
) {
f.throws = true;
}
@@ -16,6 +16,9 @@ import type { NativeTargetSpec } from "./targets.js";
export interface NativeLinkFeatures {
workers?: boolean;
/** The young-in nursery (backend/young-nursery.ts): the SCR_YIN
* variant of the collector unit. Absent when off. */
youngNursery?: boolean;
regex: boolean;
copying: boolean;
textDecoderLegacy: boolean;
@@ -48,6 +51,9 @@ export interface NativeLinkFeatures {
* archive. */
cipher: boolean;
tlsCa: boolean;
/** crypto.subtle importKey/sign/verify/digest and CryptoKey values:
* scr_webcrypto.c. */
webcrypto: boolean;
}
export interface NativeLinkInfo {
@@ -46,6 +46,8 @@ export interface CcOptions {
frameworks?: readonly string[];
/** Isolated native Worker contexts; every runtime object must use TLS. */
workers?: boolean;
/** Compile the runtime with SCR_YIN (backend/young-nursery.ts). */
youngNursery?: boolean;
regex?: boolean;
/** The program uses one of the copying/typed-array bridge intrinsics
@@ -197,6 +199,8 @@ export interface CcOptions {
/** crypto.createCipheriv/createDecipheriv: compiles scr_cipher.c and links
* the mbedTLS archive (shared with tls). */
cipher?: boolean;
/** crypto.subtle importKey/sign/verify/digest: compiles scr_webcrypto.c. */
webcrypto?: boolean;
/** Internal compiler hook: called only after a strict native artifact hit
* or a successful stable build has installed `outPath`. The executable
* frontend cache uses it to publish its stamp after native dependencies
@@ -445,6 +445,7 @@ export async function compileCInternal(
: [optimization === "dev" ? "-O0" : "-O2"]),
...executableSectionFlags.compile,
...(opts.textDecoderLegacy ? ["-DSCR_TEXT_DECODER_LEGACY"] : []),
...(opts.youngNursery ? ["-DSCR_YIN"] : []),
"-fno-math-errno",
// The runtime object model uses type-punned C: a hierarchy
// upcast is a raw pointer cast, so one object's header (rc, vt) and
@@ -577,6 +578,7 @@ export async function compileCInternal(
...(opts.watch ? [rt(join(rtDir, "scr_watch.c"))] : []),
...(opts.foreignFfi ? [rt(join(rtDir, "scr_ffi_queue.c"))] : []),
...(opts.nodeTest ? [rt(join(rtDir, "scr_test.c"))] : []),
...(opts.webcrypto ? [rt(join(rtDir, "scr_webcrypto.c"))] : []),
// The CA-store unit rides its own gate OR the tls one: scr_tls.c
// references its default-set override unconditionally.
...(tlsCa ? [rt(join(rtDir, "scr_tls_ca.c"))] : []),
@@ -643,6 +645,7 @@ export async function compileCInternal(
: [optimization === "dev" ? "-O0" : "-O2"]),
...executableSectionFlags.compile,
...(opts.textDecoderLegacy ? ["-DSCR_TEXT_DECODER_LEGACY"] : []),
...(opts.youngNursery ? ["-DSCR_YIN"] : []),
"-fno-math-errno",
"-fno-strict-aliasing", // the emitted object model type-puns — see buildArgs
"-Wno-deprecated-declarations",
@@ -80,6 +80,7 @@ export interface RuntimePackManifest {
export interface RuntimeFeatureSet extends NativeLinkFeatures {
workers: boolean;
youngNursery: boolean;
eventTarget: boolean;
nativeFetch: boolean;
netEffective: boolean;
@@ -266,6 +267,7 @@ export function effectiveRuntimeFeatures(
return {
...features,
workers: features.workers === true,
youngNursery: features.youngNursery === true,
eventTarget: features.eventTarget === true,
nativeFetch,
netEffective,
@@ -119,6 +119,7 @@ const features = {
tls: false,
tlsCa: false,
cipher: false,
webcrypto: false,
};
test.each([
@@ -56,6 +56,7 @@ const BASE: NativeLinkFeatures = {
tls: false,
tlsCa: false,
cipher: false,
webcrypto: false,
};
async function fixture(target: NativeTargetSpec = MACOS_ARM64_TARGET) {
@@ -61,6 +61,7 @@ export async function warmNativeCaches(
nodeTest: false,
tls: false,
cipher: false,
webcrypto: false,
tlsCa: false,
},
});
@@ -0,0 +1,9 @@
/** The young-in nursery (phase 1, experimental): compile-time opt-in with
* SCRIPTC_YOUNG_NURSERY=1. Executables then allocate compiled class and
* record instances young (the emitted constructors call scr_yin_enter) and
* link the collector unit built with SCR_YIN, which collects young cycles
* in nursery collections (scr_cycle.c, "young-in nursery"). Unset, nothing
* the compiler emits or links changes. */
export function youngNurseryRequested(env: NodeJS.ProcessEnv = process.env): boolean {
return env["SCRIPTC_YOUNG_NURSERY"] === "1";
}
@@ -11,7 +11,8 @@ afterAll(() => rmSync(dir, { recursive: true, force: true }));
test("unchanged and comment-only reruns replay the verdict; code edits relower", () => {
const entry = join(dir, "main.ts");
const program = `const pairs: [number, number][] = [[1, 2]];\nconst [[a, b]] = pairs;\nconsole.log(a + b);\nconst { length } = [1, 2] as number[];\nconsole.log(length);\n`;
// The blocker: an object pattern over a union source (arrays alone compile).
const program = `const pairs: [number, number][] = [[1, 2]];\nconst [[a, b]] = pairs;\nconsole.log(a + b);\nconst { length } = (a > 9 ? [1, 2] : "ab") as number[] | string;\nconsole.log(length);\n`;
writeFileSync(entry, program);
const entries = new Map<string, string>();
const store: VerdictStore = {
@@ -57,6 +57,7 @@ const native: EarlyExecutableNativeFeatures = {
tls: false,
tlsCa: false,
cipher: false,
webcrypto: false,
};
async function fixture(): Promise<{
@@ -49,6 +49,7 @@ export interface EarlyExecutableNativeFeatures {
/** Omitted is the historical release posture. */
optimization?: "dev" | "speed";
workers?: boolean;
youngNursery?: boolean;
regex: boolean;
copying: boolean;
textDecoderLegacy: boolean;
@@ -79,6 +80,7 @@ export interface EarlyExecutableNativeFeatures {
tls: boolean;
cipher: boolean;
tlsCa: boolean;
webcrypto: boolean;
}
interface EarlyExecutableCacheStamp {
@@ -111,6 +113,8 @@ export interface EarlyExecutableCacheOptions {
windowsSubsystem?: "gui";
/** Omitted unless --detached-exit is set. */
detachedExit?: true;
/** Omitted unless SCRIPTC_YOUNG_NURSERY=1 (backend/young-nursery.ts). */
youngNursery?: true;
npmStatic: readonly string[] | "auto" | null;
/** Raw manifest identity: path and bytes. Native archives remain under the
* stricter native cache's independent dependency validation. */
@@ -190,6 +194,7 @@ const BOOLEAN_NATIVE_KEYS = [
"tls",
"tlsCa",
"cipher",
"webcrypto",
] as const satisfies readonly (keyof EarlyExecutableNativeFeatures)[];
function validNativeFeatures(value: unknown): value is EarlyExecutableNativeFeatures {
@@ -200,7 +205,8 @@ function validNativeFeatures(value: unknown): value is EarlyExecutableNativeFeat
(native.optimization === undefined ||
native.optimization === "dev" ||
native.optimization === "speed") &&
(native.workers === undefined || typeof native.workers === "boolean"),
(native.workers === undefined || typeof native.workers === "boolean") &&
(native.youngNursery === undefined || typeof native.youngNursery === "boolean"),
);
}
@@ -221,6 +227,7 @@ function cacheKey(options: EarlyExecutableCacheOptions): string {
...(options.strip ? ["strip"] : []),
...(options.windowsSubsystem === "gui" ? ["windows-subsystem-gui"] : []),
...(options.detachedExit ? ["detached-exit"] : []),
...(options.youngNursery ? ["young-nursery"] : []),
options.npmStatic === null
? "<npm-static-off>"
: options.npmStatic === "auto"
@@ -260,6 +267,7 @@ function routeKey(options: Omit<EarlyExecutableRouteOptions, "nativeEnvironment"
if (options.strip) hash.update("strip\0");
if (options.windowsSubsystem === "gui") hash.update("windows-subsystem-gui\0");
if (options.detachedExit) hash.update("detached-exit\0");
if (options.youngNursery) hash.update("young-nursery\0");
hash
.update(
options.npmStatic === null
+31 -31
View File
@@ -56,8 +56,8 @@ function identPrefixOf(node: ts.Node, sf: ts.SourceFile): string {
export function bareRequireSpecOf(e: ts.Node): string | null {
if (!ts.isCallExpression(e) || e.questionDotToken !== undefined) return null;
if (!ts.isIdentifier(e.expression) || e.expression.text !== "require") return null;
if (e.arguments.length !== 1) return null;
const arg = e.arguments[0]!;
if (e.arguments.nodes.length !== 1) return null;
const arg = e.arguments.nodes[0]!;
return ts.isStringLiteral(arg) ? arg.text : null;
}
@@ -135,7 +135,7 @@ function scanTableLiteral(
reexports: string[],
): void {
const text = sf.text;
for (const prop of obj.properties) {
for (const prop of obj.properties.nodes) {
if (ts.isGetAccessor(prop)) {
// `get name() {...}` — merve's explicit early termination... unless
// the accessor's NAME is not a plain identifier, where the lexer
@@ -253,8 +253,8 @@ function scanDefineProperty(call: ts.CallExpression, sf: ts.SourceFile, out: Set
) {
return;
}
if (call.arguments.length !== 3) return;
const [recv, nameArg, desc] = call.arguments as unknown as [
if (call.arguments.nodes.length !== 3) return;
const [recv, nameArg, desc] = call.arguments.nodes as unknown as [
ts.Expression,
ts.Expression,
ts.Expression,
@@ -263,7 +263,7 @@ function scanDefineProperty(call: ts.CallExpression, sf: ts.SourceFile, out: Set
if (!ts.isStringLiteral(nameArg)) return;
if (!ts.isObjectLiteralExpression(desc)) return;
let i = 0;
const props = desc.properties;
const props = desc.properties.nodes;
const first = props[0];
if (
first !== undefined &&
@@ -287,8 +287,8 @@ function scanDefineProperty(call: ts.CallExpression, sf: ts.SourceFile, out: Set
// and the getter must be the descriptor's LAST property.
const body = getterBodyOf(p);
if (body === undefined || i !== props.length - 1) return;
if (body.statements.length !== 1) return;
const ret = body.statements[0]!;
if (body.statements.nodes.length !== 1) return;
const ret = body.statements.nodes[0]!;
if (!ts.isReturnStatement(ret) || ret.expression === undefined) return;
const r = ret.expression;
const returnsIdentish =
@@ -313,7 +313,7 @@ function getterBodyOf(p: ts.ObjectLiteralElementLike): ts.Block | undefined {
p.name.text === "get" &&
p.asteriskToken === undefined &&
(ts.getModifiers(p) ?? []).length === 0 &&
p.parameters.length === 0
p.parameters.nodes.length === 0
) {
return p.body;
}
@@ -322,7 +322,7 @@ function getterBodyOf(p: ts.ObjectLiteralElementLike): ts.Block | undefined {
ts.isIdentifier(p.name) &&
p.name.text === "get" &&
ts.isFunctionExpression(p.initializer) &&
p.initializer.parameters.length === 0 &&
p.initializer.parameters.nodes.length === 0 &&
p.initializer.asteriskToken === undefined &&
(ts.getModifiers(p.initializer) ?? []).length === 0
) {
@@ -349,7 +349,7 @@ function starExportSpecOf(call: ts.CallExpression, sf: ts.SourceFile): string |
if (nameNode === undefined) return null;
if (nameNode.text !== "__export" && nameNode.text !== "__exportStar") return null;
if (!sourceSpellsIdentifier(nameNode, sf)) return null;
const arg0 = call.arguments[0];
const arg0 = call.arguments.nodes[0];
if (arg0 === undefined) return null;
if (!byteAdjacentParen(sf, nameNode.getEnd(), arg0.getStart(sf))) return null;
return leadingRequireOf(arg0, sf)?.spec ?? null;
@@ -369,7 +369,7 @@ function byteAdjacentParen(sf: ts.SourceFile, nameEnd: number, argStart: number)
* probed: a member-qualified helper or one space after its paren misses).
* Answers [ID, spec]. */
function starAssignOf(stmt: ts.VariableStatement, sf: ts.SourceFile): [string, string] | null {
const decl = stmt.declarationList.declarations[0];
const decl = stmt.declarationList.declarations.nodes[0];
if (decl === undefined || !ts.isIdentifier(decl.name) || !sourceSpellsIdentifier(decl.name, sf))
return null;
const init = decl.initializer;
@@ -381,9 +381,9 @@ function starAssignOf(stmt: ts.VariableStatement, sf: ts.SourceFile): [string, s
init.questionDotToken === undefined &&
ts.isIdentifier(init.expression) &&
init.expression.text === "_interopRequireWildcard" &&
init.arguments.length >= 1
init.arguments.nodes.length >= 1
) {
const arg0 = init.arguments[0]!;
const arg0 = init.arguments.nodes[0]!;
if (!byteAdjacentParen(sf, init.expression.getEnd(), arg0.getStart(sf))) return null;
const spec = bareRequireSpecOf(arg0);
if (spec !== null) return [decl.name.text, spec];
@@ -434,8 +434,8 @@ function isHasOwnCall(e: ts.Expression, key: string): boolean {
)
cur = cur.expression;
if (!ts.isIdentifier(cur) || cur.text !== "Object") return false;
if (e.arguments.length !== 2) return false;
const [recv, k] = e.arguments as unknown as [ts.Expression, ts.Expression];
if (e.arguments.nodes.length !== 2) return false;
const [recv, k] = e.arguments.nodes as unknown as [ts.Expression, ts.Expression];
return ts.isIdentifier(recv) && ts.isIdentifier(k) && k.text === key;
}
@@ -448,9 +448,9 @@ function isDirectHasOwnCall(e: ts.Expression, key: string): boolean {
ts.isIdentifier(e.expression.name) &&
e.expression.name.text === "hasOwnProperty" &&
ts.isIdentifier(e.expression.expression) &&
e.arguments.length === 1 &&
ts.isIdentifier(e.arguments[0]!) &&
(e.arguments[0] as ts.Identifier).text === key
e.arguments.nodes.length === 1 &&
ts.isIdentifier(e.arguments.nodes[0]!) &&
(e.arguments.nodes[0] as ts.Identifier).text === key
);
}
@@ -477,16 +477,16 @@ function isCopyStatement(stmt: ts.Statement, id1: string, key: string): boolean
) {
return false;
}
if (e.arguments.length !== 3) return false;
const [recv, nameArg, desc] = e.arguments as unknown as [
if (e.arguments.nodes.length !== 3) return false;
const [recv, nameArg, desc] = e.arguments.nodes as unknown as [
ts.Expression,
ts.Expression,
ts.Expression,
];
if (!isExportsTarget(recv)) return false;
if (!ts.isIdentifier(nameArg) || nameArg.text !== key) return false;
if (!ts.isObjectLiteralExpression(desc) || desc.properties.length !== 2) return false;
const [en, get] = desc.properties as unknown as [
if (!ts.isObjectLiteralExpression(desc) || desc.properties.nodes.length !== 2) return false;
const [en, get] = desc.properties.nodes as unknown as [
ts.ObjectLiteralElementLike,
ts.ObjectLiteralElementLike,
];
@@ -494,8 +494,8 @@ function isCopyStatement(stmt: ts.Statement, id1: string, key: string): boolean
return false;
if (en.initializer.kind !== ts.SyntaxKind.TrueKeyword) return false;
const body = getterBodyOf(get);
if (body === undefined || body.statements.length !== 1) return false;
const ret = body.statements[0]!;
if (body === undefined || body.statements.nodes.length !== 1) return false;
const ret = body.statements.nodes[0]!;
if (!ts.isReturnStatement(ret) || ret.expression === undefined) return false;
return isKeyedAccess(ret.expression, (x) => ts.isIdentifier(x) && x.text === id1, key);
}
@@ -541,16 +541,16 @@ function starLoopIdOf(call: ts.CallExpression, sf: ts.SourceFile): string | null
) {
return null;
}
if (keysCall.arguments.length !== 1) return null;
const id1Node = keysCall.arguments[0]!;
if (keysCall.arguments.nodes.length !== 1) return null;
const id1Node = keysCall.arguments.nodes[0]!;
if (!ts.isIdentifier(id1Node) || !sourceSpellsIdentifier(id1Node, sf)) return null;
const id1 = id1Node.text;
const fn = call.arguments[0];
const fn = call.arguments.nodes[0];
if (fn === undefined || !ts.isFunctionExpression(fn)) return null; // arrows never match (probed)
if (fn.name !== undefined) return null; // a NAMED callback misses (probed)
if (fn.asteriskToken !== undefined || (ts.getModifiers(fn) ?? []).length > 0) return null;
if (fn.parameters.length !== 1) return null;
const keyParam = fn.parameters[0]!;
if (fn.parameters.nodes.length !== 1) return null;
const keyParam = fn.parameters.nodes[0]!;
if (
!ts.isIdentifier(keyParam.name) ||
keyParam.initializer !== undefined ||
@@ -558,7 +558,7 @@ function starLoopIdOf(call: ts.CallExpression, sf: ts.SourceFile): string | null
)
return null;
const key = keyParam.name.text;
const stmts = fn.body.statements;
const stmts = fn.body.statements.nodes;
// Form B: the single `if (KEY !== 'default' [&& !hasOwn]) COPY` statement.
if (stmts.length === 1 && ts.isIfStatement(stmts[0]!) && stmts[0]!.elseStatement === undefined) {
@@ -109,7 +109,7 @@ function markerNamed(
if (!MARKERS.has(written)) return null;
let member = written;
const symbol = checker.getSymbolAtLocation(ident);
const decl = symbol ? checker.declarationsOf(symbol)[0] : undefined;
const decl = symbol ? symbol.declarations[0] : undefined;
if (decl === undefined) return null;
let importDecl: ts.Node | undefined;
if (ts.isImportSpecifier(decl) && !qualified) {
@@ -262,7 +262,7 @@ function placeHeritage(
return unsupported(out, node, marker);
// `implements Acyclic<C>` would make the class implement C's own type,
// which the lowering treats as a protocol of C.
if ((node.typeArguments?.length ?? 0) > 0) return unsupported(out, node, marker);
if ((node.typeArguments?.nodes.length ?? 0) > 0) return unsupported(out, node, marker);
const note = (name: string): ContractNote => ({ marker, loc: locOf(node), name });
if (
(ts.isClassDeclaration(owner) || ts.isClassExpression(owner)) &&
@@ -324,8 +324,8 @@ function place(
ts.isTypeReferenceNode(parent) &&
ts.isIdentifier(parent.typeName) &&
(parent.typeName.text === "Array" || parent.typeName.text === "ReadonlyArray") &&
parent.typeArguments?.length === 1 &&
parent.typeArguments[0] === ref
parent.typeArguments?.nodes.length === 1 &&
parent.typeArguments.nodes[0] === ref
) {
node = parent;
parent = parent.parent;
@@ -336,7 +336,7 @@ function place(
// contract of the function the NoThrow annotates.
if (marker !== "NoThrow" && parent !== undefined && ts.isTypeReferenceNode(parent)) {
const wrapper = markerOf(checker, parent);
if (wrapper === "NoThrow" && parent.typeArguments?.[0] === node) {
if (wrapper === "NoThrow" && parent.typeArguments?.nodes[0] === node) {
node = parent;
parent = parent.parent;
}
@@ -18,7 +18,7 @@ export function experimentalImportOf(
): ExperimentalMember | null {
const checker = program.getTypeChecker();
const symbol = checker.getSymbolAtLocation(ident);
const decl = symbol ? checker.declarationsOf(symbol)[0] : undefined;
const decl = symbol ? symbol.declarations[0] : undefined;
if (decl === undefined || !ts.isImportSpecifier(decl)) return null;
const importDecl = decl.parent?.parent?.parent;
if (
@@ -67,7 +67,7 @@ export function lifetimeRegionMemberOf(
): "run" | "release" | null {
const checker = program.getTypeChecker();
const symbol = checker.getSymbolAtLocation(access.name);
const decl = symbol ? checker.declarationsOf(symbol)[0] : undefined;
const decl = symbol ? symbol.declarations[0] : undefined;
if (decl === undefined || !ts.isMethodDeclaration(decl)) return null;
const owner = decl.parent;
if (
@@ -91,7 +91,7 @@ export function internTableMemberOf(
): InternTableMember | null {
const checker = program.getTypeChecker();
const symbol = checker.getSymbolAtLocation(access.name);
const decl = symbol ? checker.declarationsOf(symbol)[0] : undefined;
const decl = symbol ? symbol.declarations[0] : undefined;
if (
decl === undefined ||
!(
@@ -129,7 +129,7 @@ export function threadSlotsMemberOf(
): ThreadSlotsMember | null {
const checker = program.getTypeChecker();
const symbol = checker.getSymbolAtLocation(access.name);
const decl = symbol ? checker.declarationsOf(symbol)[0] : undefined;
const decl = symbol ? symbol.declarations[0] : undefined;
if (decl === undefined || !ts.isMethodDeclaration(decl)) return null;
const owner = decl.parent;
if (
+17 -20
View File
@@ -8,7 +8,7 @@ import { moduleSourceCandidates } from "./module-source-candidates.js";
export function isNodeGlobal(program: ts.Program, expression: ts.Identifier): boolean {
const checker = program.getTypeChecker();
const symbol = checker.getSymbolAtLocation(expression);
const declarations = symbol ? checker.declarationsOf(symbol) : [];
const declarations = symbol ? symbol.declarations : [];
return (
declarations.length > 0 &&
declarations.every((declaration) => {
@@ -45,7 +45,7 @@ export function isBuiltinMemberImport(
): boolean {
const checker = program.getTypeChecker();
const symbol = checker.getSymbolAtLocation(ident);
const decl = symbol ? checker.declarationsOf(symbol)[0] : undefined;
const decl = symbol ? symbol.declarations[0] : undefined;
if (decl !== undefined && ts.isImportSpecifier(decl)) {
const importDecl = decl.parent?.parent?.parent;
if (ts.isImportDeclaration(importDecl) && ts.isStringLiteral(importDecl.moduleSpecifier)) {
@@ -60,7 +60,7 @@ export function isBuiltinMemberImport(
}
if (symbol !== undefined && (symbol.flags & ts.SymbolFlags.Alias) !== 0) {
const target = checker.getAliasedSymbol(symbol);
const targetDecl = checker.declarationsOf(target)[0];
const targetDecl = target.declarations[0];
if (targetDecl !== undefined && targetDecl.getSourceFile().isDeclarationFile) {
for (
let parent: ts.Node | undefined = targetDecl.parent;
@@ -88,7 +88,7 @@ export function isBuiltinNamespaceImport(
): boolean {
const checker = program.getTypeChecker();
const symbol = checker.getSymbolAtLocation(ident);
const decl = symbol ? checker.declarationsOf(symbol)[0] : undefined;
const decl = symbol ? symbol.declarations[0] : undefined;
let importDecl: ts.ImportDeclaration | undefined;
if (decl !== undefined && ts.isNamespaceImport(decl)) {
const candidate = decl.parent?.parent;
@@ -108,12 +108,9 @@ function constInitializer(program: ts.Program, expr: ts.Expression): ts.Expressi
if (!ts.isIdentifier(current)) return null;
const symbol = program.getTypeChecker().getSymbolAtLocation(current);
const decl = symbol
? program
.getTypeChecker()
.declarationsOf(symbol)
.find((candidate): candidate is ts.VariableDeclaration =>
ts.isVariableDeclaration(candidate),
)
? symbol.declarations.find((candidate): candidate is ts.VariableDeclaration =>
ts.isVariableDeclaration(candidate),
)
: undefined;
if (
decl === undefined ||
@@ -158,13 +155,13 @@ function staticBoolean(
if (
ts.isCallExpression(current) &&
!current.questionDotToken &&
current.arguments.length === 1 &&
current.arguments.nodes.length === 1 &&
ts.isPropertyAccessExpression(current.expression) &&
!current.expression.questionDotToken &&
(current.expression.name.text === "endsWith" || current.expression.name.text === "startsWith")
) {
const receiver = staticString(program, current.expression.expression, seen);
const needle = staticString(program, current.arguments[0]!, seen);
const needle = staticString(program, current.arguments.nodes[0]!, seen);
if (receiver === null || needle === null) return null;
return current.expression.name.text === "endsWith"
? receiver.endsWith(needle)
@@ -194,7 +191,7 @@ function staticString(
if (importMetaUrl(current)) return pathToFileURL(current.getSourceFile().fileName).href;
if (ts.isTemplateExpression(current)) {
let value = current.head.text;
for (const span of current.templateSpans) {
for (const span of current.templateSpans.nodes) {
const part = staticString(program, span.expression, seen);
if (part === null) return null;
value += part + span.literal.text;
@@ -219,11 +216,11 @@ function staticString(
if (
ts.isCallExpression(current) &&
!current.questionDotToken &&
current.arguments.length === 1 &&
current.arguments.nodes.length === 1 &&
ts.isIdentifier(current.expression) &&
isBuiltinMemberImport(program, current.expression, "url", "fileURLToPath")
) {
const href = staticString(program, current.arguments[0]!, seen);
const href = staticString(program, current.arguments.nodes[0]!, seen);
if (href === null) return null;
try {
return fileURLToPath(href);
@@ -237,7 +234,7 @@ function staticString(
(isBuiltinMemberImport(program, current.expression, "url", "URL") ||
(current.expression.text === "URL" && isNodeGlobal(program, current.expression)))
) {
const args = current.arguments ?? [];
const args = current.arguments?.nodes ?? [];
if (args.length < 1 || args.length > 2) return null;
const input = staticString(program, args[0]!, seen);
const base = args[1] === undefined ? null : staticString(program, args[1], seen);
@@ -261,7 +258,7 @@ export function staticForkModulePath(program: ts.Program, expr: ts.Expression):
const supported =
(ts.isCallExpression(current) &&
!current.questionDotToken &&
current.arguments.length === 1 &&
current.arguments.nodes.length === 1 &&
ts.isIdentifier(current.expression) &&
isBuiltinMemberImport(program, current.expression, "url", "fileURLToPath")) ||
(ts.isNewExpression(current) &&
@@ -279,7 +276,7 @@ export function staticForkModulePath(program: ts.Program, expr: ts.Expression):
}
export function forkCallModulePath(program: ts.Program, call: ts.CallExpression): string | null {
if (call.questionDotToken || call.arguments[0] === undefined) return null;
if (call.questionDotToken || call.arguments.nodes[0] === undefined) return null;
const callee = strip(call.expression);
const isFork =
(ts.isIdentifier(callee) && isBuiltinMemberImport(program, callee, "child_process", "fork")) ||
@@ -291,7 +288,7 @@ export function forkCallModulePath(program: ts.Program, call: ts.CallExpression)
if (!isFork) {
return null;
}
return staticForkModulePath(program, call.arguments[0]);
return staticForkModulePath(program, call.arguments.nodes[0]);
}
/** Source-order static fork targets in the supplied files, deduplicated by
@@ -304,7 +301,7 @@ export function forkTargetPaths(program: ts.Program, files: readonly ts.SourceFi
const calls = moduleSourceCandidates(program, sourceFile).calls;
const bindings: ts.Node[] = [];
for (const call of calls) {
if (call.questionDotToken || call.arguments[0] === undefined) continue;
if (call.questionDotToken || call.arguments.nodes[0] === undefined) continue;
const callee = strip(call.expression);
if (ts.isIdentifier(callee)) bindings.push(callee);
else if (
@@ -6,14 +6,14 @@ export function functionCanFallThrough(declaration: ts.Node): boolean {
if (
!ts.isFunctionLike(declaration) ||
declaration.asteriskToken ||
declaration.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword)
declaration.modifiers?.nodes.some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword)
)
return false;
const body = declaration.body as ts.Block | ts.Expression | undefined;
if (!body || !ts.isBlock(body)) return false;
const completes = (statement: ts.Statement): boolean => {
if (ts.isReturnStatement(statement) || ts.isThrowStatement(statement)) return false;
if (ts.isBlock(statement)) return sequence(statement.statements);
if (ts.isBlock(statement)) return sequence(statement.statements.nodes);
if (ts.isIfStatement(statement))
return (
completes(statement.thenStatement) ||
@@ -29,13 +29,13 @@ export function functionCanFallThrough(declaration: ts.Node): boolean {
}
if (ts.isSwitchStatement(statement))
return (
!statement.caseBlock.clauses.some(ts.isDefaultClause) ||
statement.caseBlock.clauses.some((clause) => sequence(clause.statements))
!statement.caseBlock.clauses.nodes.some(ts.isDefaultClause) ||
statement.caseBlock.clauses.nodes.some((clause) => sequence(clause.statements.nodes))
);
return true;
};
const sequence = (statements: readonly ts.Statement[]): boolean => statements.every(completes);
return sequence(body.statements);
return sequence(body.statements.nodes);
}
/** A documented JavaScript result must also represent explicit empty returns. */
@@ -44,7 +44,7 @@ export function functionCanReturnUndefined(declaration: ts.Node): boolean {
if (
!ts.isFunctionLike(declaration) ||
declaration.asteriskToken ||
declaration.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword)
declaration.modifiers?.nodes.some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword)
)
return false;
const body = declaration.body;
@@ -25,15 +25,13 @@ export function inferredJsDiagnosticSuppressed(
const symbol = checker.getSymbolAtLocation(node.name);
if (
symbol &&
checker
.declarationsOf(symbol)
.some(
(decl) =>
ts.isPropertyDeclaration(decl) &&
!decl.type &&
!decl.initializer &&
!/@type\b/.test(file.text.slice(decl.pos, decl.getStart(file))),
)
symbol.declarations.some(
(decl) =>
ts.isPropertyDeclaration(decl) &&
!decl.type &&
!decl.initializer &&
!/@type\b/.test(file.text.slice(decl.pos, decl.getStart(file))),
)
) {
// Bare JS fields exist with value undefined before the first write;
// native storage includes that arm even when inference omits it.
@@ -43,16 +41,17 @@ export function inferredJsDiagnosticSuppressed(
}
if (diagnostic.code === 2554 && ts.isCallExpression(node)) {
const signature = checker.getResolvedSignature(node);
const declaration = signature && checker.signatureDeclaration(signature);
const declaration = signature && signature.declaration;
if (
declaration &&
isJsSourceFileName(declaration.getSourceFile().fileName) &&
(ts.isFunctionDeclaration(declaration) ||
ts.isFunctionExpression(declaration) ||
ts.isMethodDeclaration(declaration)) &&
node.arguments.length > declaration.parameters.length &&
node.arguments[declaration.parameters.length]?.getStart(file) === diagnostic.pos &&
node.arguments[node.arguments.length - 1]?.getEnd() === diagnostic.end &&
node.arguments.nodes.length > declaration.parameters.nodes.length &&
node.arguments.nodes[declaration.parameters.nodes.length]?.getStart(file) ===
diagnostic.pos &&
node.arguments.nodes[node.arguments.nodes.length - 1]?.getEnd() === diagnostic.end &&
bodyReadsArguments(declaration)
) {
suppressed = true;
@@ -82,9 +81,7 @@ export function inferredJsDiagnosticSuppressed(
checker.getTypeOfSymbolAtLocation(throwMethod, node.expression),
);
const declaration =
throwSignatures.length === 1
? checker.signatureDeclaration(throwSignatures[0]!)
: undefined;
throwSignatures.length === 1 ? throwSignatures[0]!.declaration : undefined;
// A sole throw cannot return an invalid IteratorResult. Avoid inferring
// termination through arbitrary control flow or explicit return types.
if (
@@ -92,8 +89,8 @@ export function inferredJsDiagnosticSuppressed(
ts.isMethodDeclaration(declaration) &&
declaration.type === undefined &&
isJsSourceFileName(declaration.getSourceFile().fileName) &&
declaration.body?.statements.length === 1 &&
ts.isThrowStatement(declaration.body.statements[0]!)
declaration.body?.statements?.nodes.length === 1 &&
ts.isThrowStatement(declaration.body.statements.nodes[0]!)
) {
suppressed = true;
return "stop";
+24 -24
View File
@@ -141,7 +141,7 @@ function localTypeBindings(file: ts.SourceFile, bindings: TypeBindings): Set<str
const add = (name: ts.Identifier | undefined): void => {
if (name !== undefined && CONTRACT_GLOBAL_TYPES.has(name.text)) names.add(name.text);
};
for (const stmt of file.statements) {
for (const stmt of file.statements.nodes) {
if (
ts.isInterfaceDeclaration(stmt) ||
ts.isTypeAliasDeclaration(stmt) ||
@@ -160,7 +160,7 @@ function localTypeBindings(file: ts.SourceFile, bindings: TypeBindings): Set<str
add(clause.name);
if (clause.namedBindings === undefined) continue;
if (ts.isNamespaceImport(clause.namedBindings)) add(clause.namedBindings.name);
else for (const el of clause.namedBindings.elements) add(el.name);
else for (const el of clause.namedBindings.elements.nodes) add(el.name);
}
bindings.set(file, names);
return names;
@@ -176,7 +176,7 @@ function isUnshadowedGlobalType(
): boolean {
if (localTypeBindings(file, bindings).has(name)) return false;
for (let scope = node.parent; scope !== undefined && scope !== file; scope = scope.parent) {
const params = scope.typeParameters;
const params = scope.typeParameters?.nodes;
if (params?.some((p) => ts.isTypeParameterDeclaration(p) && p.name.text === name) === true)
return false;
}
@@ -254,7 +254,7 @@ function typeShape(
return { k: "array", elem: typeShape(file, node.elementType, bindings) };
if (ts.isTupleTypeNode(node)) {
const elems: ContractTypeShape[] = [];
for (const e of node.elements) {
for (const e of node.elements.nodes) {
// Named tuple members carry the type under `.type`.
const t = ts.isNamedTupleMember(e) ? e.type : (e as ts.TypeNode);
elems.push(typeShape(file, t, bindings));
@@ -262,13 +262,13 @@ function typeShape(
return { k: "tuple", elems };
}
if (ts.isUnionTypeNode(node)) {
return { k: "union", parts: node.types.map((t) => typeShape(file, t, bindings)) };
return { k: "union", parts: node.types.nodes.map((t) => typeShape(file, t, bindings)) };
}
if (ts.isTypeLiteralNode(node)) {
let bad: string | null = null;
const fields = shapeOfMembers(
file,
node.members,
node.members.nodes,
(text) => {
bad = text;
},
@@ -292,16 +292,16 @@ function typeShape(
const name = node.typeName.text;
const global =
CONTRACT_GLOBAL_TYPES.has(name) && isUnshadowedGlobalType(file, node, name, bindings);
if (global && name === "Uint8Array" && (node.typeArguments?.length ?? 0) === 0)
if (global && name === "Uint8Array" && (node.typeArguments?.nodes.length ?? 0) === 0)
return { k: "bytes" };
if (
global &&
(name === "Array" || name === "ReadonlyArray") &&
node.typeArguments?.length === 1
node.typeArguments?.nodes.length === 1
) {
return { k: "array", elem: typeShape(file, node.typeArguments[0]!, bindings) };
return { k: "array", elem: typeShape(file, node.typeArguments.nodes[0]!, bindings) };
}
if ((node.typeArguments?.length ?? 0) > 0)
if ((node.typeArguments?.nodes.length ?? 0) > 0)
return { k: "unsupported", text: node.getText(file) };
return { k: "ref", name };
}
@@ -328,7 +328,7 @@ function stringArray(expr: ts.Expression): string[] | null {
const e = unwrapConst(expr);
if (!ts.isArrayLiteralExpression(e)) return null;
const out: string[] = [];
for (const el of e.elements) {
for (const el of e.elements.nodes) {
const v = unwrapConst(el as ts.Expression);
if (!ts.isStringLiteral(v)) return null;
out.push(v.text);
@@ -340,12 +340,12 @@ function envMsgArray(expr: ts.Expression): { env: string; msg: string }[] | null
const e = unwrapConst(expr);
if (!ts.isArrayLiteralExpression(e)) return null;
const out: { env: string; msg: string }[] = [];
for (const el of e.elements) {
for (const el of e.elements.nodes) {
const v = unwrapConst(el as ts.Expression);
if (!ts.isObjectLiteralExpression(v)) return null;
let env: string | null = null;
let msg: string | null = null;
for (const prop of v.properties) {
for (const prop of v.properties.nodes) {
if (!ts.isPropertyAssignment(prop)) return null;
const name = propName(prop.name);
const val = unwrapConst(prop.initializer);
@@ -410,7 +410,7 @@ export function entryContractFacts(
* and the refusal names every site. */
const collectTypes = (file: ts.SourceFile): void => {
const exportedHere = new Set<string>();
for (const stmt of file.statements) {
for (const stmt of file.statements.nodes) {
if (
(ts.isInterfaceDeclaration(stmt) || ts.isTypeAliasDeclaration(stmt)) &&
isExported(stmt)
@@ -418,7 +418,7 @@ export function entryContractFacts(
exportedHere.add(stmt.name.text);
}
}
for (const stmt of file.statements) {
for (const stmt of file.statements.nodes) {
if (ts.isInterfaceDeclaration(stmt) || ts.isTypeAliasDeclaration(stmt)) {
const name = stmt.name.text;
const exported = isExported(stmt);
@@ -433,14 +433,14 @@ export function entryContractFacts(
let bad: string | null = null;
const fields = shapeOfMembers(
file,
stmt.members,
stmt.members.nodes,
(text) => {
bad = text;
},
bindings,
);
const generic = (stmt.typeParameters?.length ?? 0) > 0;
const heritage = (stmt.heritageClauses?.length ?? 0) > 0;
const generic = (stmt.typeParameters?.nodes.length ?? 0) > 0;
const heritage = (stmt.heritageClauses?.nodes.length ?? 0) > 0;
facts.types.push({
name,
form: "interface",
@@ -455,7 +455,7 @@ export function entryContractFacts(
loc: locOf(file, stmt),
});
} else {
const generic = (stmt.typeParameters?.length ?? 0) > 0;
const generic = (stmt.typeParameters?.nodes.length ?? 0) > 0;
facts.types.push({
name,
form: "alias",
@@ -473,7 +473,7 @@ export function entryContractFacts(
stmt.body !== undefined &&
ts.isModuleBlock(stmt.body)
) {
for (const inner of stmt.body.statements) {
for (const inner of stmt.body.statements.nodes) {
if (ts.isInterfaceDeclaration(inner) || ts.isTypeAliasDeclaration(inner)) {
addSite(inner.name.text, siteOf(file, inner));
}
@@ -494,25 +494,25 @@ export function entryContractFacts(
if (list.length > 1) facts.multiSiteTypes.push({ name, sites: list });
}
for (const stmt of entry.statements) {
for (const stmt of entry.statements.nodes) {
if (ts.isFunctionDeclaration(stmt) && stmt.name !== undefined) {
if (!isExported(stmt)) continue;
if (stmt.body === undefined && facts.functions.some((f) => f.name === stmt.name!.text))
continue; // overload signature
facts.functions.push({
name: stmt.name.text,
params: stmt.parameters.map((p) => ({
params: stmt.parameters.nodes.map((p) => ({
name: ts.isIdentifier(p.name) ? p.name.text : "<pattern>",
shape: p.type !== undefined ? typeShape(entry, p.type, bindings) : null,
})),
returns: stmt.type !== undefined ? typeShape(entry, stmt.type, bindings) : null,
generic: (stmt.typeParameters?.length ?? 0) > 0,
generic: (stmt.typeParameters?.nodes.length ?? 0) > 0,
loc: locOf(entry, stmt),
});
continue;
}
if (ts.isVariableStatement(stmt) && isExported(stmt)) {
for (const decl of stmt.declarationList.declarations) {
for (const decl of stmt.declarationList.declarations.nodes) {
if (!ts.isIdentifier(decl.name)) continue;
const name = decl.name.text;
if (!CONVENTION_CONSTS.has(name)) continue;
@@ -26,7 +26,7 @@ export interface EntryExportInfo {
* Overload signatures collapse onto one entry (same name, one body). */
export function entryFunctionExports(entry: ts.SourceFile): Map<string, EntryExportInfo> {
const out = new Map<string, EntryExportInfo>();
for (const stmt of entry.statements) {
for (const stmt of entry.statements.nodes) {
if (!ts.isFunctionDeclaration(stmt) || stmt.name === undefined) continue;
const mods = ts.canHaveModifiers(stmt) ? ts.getModifiers(stmt) : undefined;
const exported = mods?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) === true;
@@ -36,7 +36,7 @@ export function entryFunctionExports(entry: ts.SourceFile): Map<string, EntryExp
out.set(name, {
name,
loc: { file: entry.fileName, start: stmt.getStart(), end: stmt.end },
generic: (stmt.typeParameters?.length ?? 0) > 0,
generic: (stmt.typeParameters?.nodes.length ?? 0) > 0,
async: mods?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) === true,
generator: stmt.asteriskToken !== undefined,
});
@@ -110,14 +110,14 @@ export function isAssertClassNew(lowerer: Lowerer, expr: ts.Expression): boolean
/** The options of `new Assert(<literal options>)`, or null when an option is
* not a literal or the argument list has another shape. */
export function literalAssertOptions(expr: ts.NewExpression): AssertInstanceOptions | null {
const args = expr.arguments ?? [];
const args = expr.arguments?.nodes ?? [];
const options: AssertInstanceOptions = { strict: true, diff: "simple", skipPrototype: false };
if (args.length === 0) return options;
if (args.length > 1) return null;
const arg = stripTypeCasts(args[0]!);
if (ts.isIdentifier(arg) && arg.text === "undefined") return options;
if (!ts.isObjectLiteralExpression(arg)) return null;
for (const prop of arg.properties) {
for (const prop of arg.properties.nodes) {
if (!ts.isPropertyAssignment(prop)) return null;
const name =
ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name) ? prop.name.text : null;
@@ -176,7 +176,7 @@ export function assertInstanceOf(
): AssertInstanceOptions | null {
if (!ts.isIdentifier(expr)) return null;
const symbol = lowerer.checker.getSymbolAtLocation(expr);
const decl = symbol ? lowerer.checker.declarationsOf(symbol)[0] : undefined;
const decl = symbol ? symbol.declarations[0] : undefined;
if (decl === undefined || !ts.isVariableDeclaration(decl)) return null;
return staticAssertInstanceDecl(lowerer, decl.name, decl.initializer);
}
@@ -0,0 +1,78 @@
/* Type assertions of the global object.
*
* `globalThis as { [KEY]?: State }` is the process-global state idiom: a
* module asserts the global object to a record type and reads and writes
* its members, and every other module that asserts it sees the same
* members. Node evaluates the assertion to the global object itself.
*
* The global object is a checked-dynamic value, and a checked-dynamic value
* asserted to a record or array type takes the checked extraction
* (dynCheck), which builds a new native value. For the global object that
* copy is always wrong: a write through it never reached the global object,
* and a view bound before another module's write never saw it. So an
* assertion of the global object keeps the checked-dynamic value itself: a
* view whose member reads and writes go to the global object, and bindings
* of it hold that object.
*
* Other checked-dynamic operands keep the checked extraction: a native value
* held as `unknown` unwraps by identity there, and a checked-dynamic tree is
* copied at the documented checked-value boundary.
*/
import * as ts from "../ts7/adapter.js";
import type { IrType } from "../../ir/ir.js";
import type { Lowerer } from "./lowerer.js";
import { stdlibGlobalNameOf } from "./surfaces.js";
/** A target whose checked extraction builds a new mutable value. */
export function extractionCopiesReference(lowerer: Lowerer, type: IrType): boolean {
const seen = new Set<string>();
const copies = (t: IrType): boolean => {
if (t.kind === "record" || t.kind === "array") return true;
if (t.kind !== "union" || seen.has(t.unionId)) return false;
seen.add(t.unionId);
return lowerer.unions.get(t.unionId)?.arms.some(copies) ?? false;
};
return copies(type);
}
/** The asserted expression without parentheses, `!` and nested
* assertions (`globalThis as unknown as T`). */
function assertedSource(expr: ts.Expression): ts.Expression {
let e = expr;
while (
ts.isParenthesizedExpression(e) ||
ts.isNonNullExpression(e) ||
ts.isAsExpression(e) ||
ts.isTypeAssertion(e) ||
ts.isSatisfiesExpression(e)
)
e = e.expression;
return e;
}
/** `expr` asserts the global object (`globalThis`, `global`). */
export function assertsGlobalObject(
lowerer: Lowerer,
expr: ts.AsExpression | ts.TypeAssertion,
): boolean {
const name = stdlibGlobalNameOf(lowerer, assertedSource(expr.expression));
return name === "globalThis" || name === "global";
}
/** The assertion a binding initializer is, when it keeps the global object:
* bindings of it hold that object rather than a native value of the
* asserted type. A target with no native layout keeps the object as well
* (the assertion lowers to the checked-dynamic value either way). */
export function keptIdentityAssertion(
lowerer: Lowerer,
initializer: ts.Expression,
): ts.AsExpression | ts.TypeAssertion | undefined {
let e = initializer;
while (ts.isParenthesizedExpression(e)) e = e.expression;
if (!ts.isAsExpression(e) && !ts.isTypeAssertion(e)) return undefined;
if (!assertsGlobalObject(lowerer, e)) return undefined;
const target = lowerer.mapTypeOf(lowerer.checker.getTypeFromTypeNode(e.type));
return !target || target.kind === "dyn" || extractionCopiesReference(lowerer, target)
? e
: undefined;
}
@@ -114,7 +114,7 @@ export function nullishValueUnitOf(
const cached = lowerer.nullishBindings.get(sym);
if (cached !== undefined) return cached;
lowerer.nullishBindings.set(sym, null); // cycle guard: self-referential probes answer non-qualifying
const decl = lowerer.checker.valueDeclarationOf(sym);
const decl = sym.valueDeclaration;
// Statement-position declarators only: a for-loop head (`for (let x =
// null as any; ...)`) declares a LOCAL with per-iteration semantics —
// lowerVarDeclList's contract requires a lowered statement for it, so
@@ -24,7 +24,7 @@ export function lowerAddAbortListener(
expr: ts.CallExpression,
loc: SrcLoc,
): IrExpr {
const args = expr.arguments;
const args = expr.arguments.nodes;
if (args.length > 2 || args.some(ts.isSpreadElement)) {
lowerer.noLowering(`addAbortListener with this argument list`, expr);
}
@@ -192,7 +192,7 @@ export function literalBoolOptions(
if (!ts.isObjectLiteralExpression(node)) return null;
const bools: Record<string, boolean> = {};
const exprs: Record<string, ts.Expression> = {};
for (const p of node.properties) {
for (const p of node.properties.nodes) {
if (!ts.isPropertyAssignment(p) || !ts.isIdentifier(p.name)) return null;
if (exprKeys.includes(p.name.text)) {
exprs[p.name.text] = p.initializer;
@@ -34,7 +34,7 @@ export function lowerDiagnosticsSubscriber(lowerer: Lowerer, node: ts.Expression
// has no other first-class story.
if (ts.isIdentifier(node) && node.text === "setImmediate") {
const sym = lowerer.checker.getSymbolAtLocation(node);
const decls = sym ? lowerer.checker.declarationsOf(sym) : [];
const decls = sym ? sym.declarations : [];
if (decls.length > 0 && decls.every((d) => lowerer.isStdlibFile(d.getSourceFile()))) {
return {
kind: "libCall",
@@ -106,14 +106,14 @@ export function lowerDiagnosticsChannelMethodCall(
if (!lowerer.isStdlibMember(access)) return null;
const name = access.name.text;
const loc = locOf(call);
if (name === "publish" && call.arguments.length === 1) {
if (name === "publish" && call.arguments.nodes.length === 1) {
const receiver = lowerer.lowerExprExpecting(access.expression, F64);
const msg = lowerDiagnosticsMessage(lowerer, call.arguments[0]!);
const msg = lowerDiagnosticsMessage(lowerer, call.arguments.nodes[0]!);
return { kind: "libCall", fn: "dc.publish", args: [receiver, msg], type: VOID, loc };
}
if ((name === "subscribe" || name === "unsubscribe") && call.arguments.length === 1) {
if ((name === "subscribe" || name === "unsubscribe") && call.arguments.nodes.length === 1) {
const receiver = lowerer.lowerExprExpecting(access.expression, F64);
const cb = lowerDiagnosticsSubscriber(lowerer, call.arguments[0]!);
const cb = lowerDiagnosticsSubscriber(lowerer, call.arguments.nodes[0]!);
return name === "subscribe"
? { kind: "libCall", fn: "dc.chanSubscribe", args: [receiver, cb], type: VOID, loc }
: { kind: "libCall", fn: "dc.chanUnsubscribe", args: [receiver, cb], type: BOOL, loc };
@@ -126,18 +126,18 @@ export function lowerDiagnosticsChannelMethodCall(
// this/arguments to fn exactly like the trace calls.
if (
(name === "bindStore" || name === "unbindStore") &&
call.arguments.length >= 1 &&
call.arguments.length <= (name === "bindStore" ? 2 : 1)
call.arguments.nodes.length >= 1 &&
call.arguments.nodes.length <= (name === "bindStore" ? 2 : 1)
) {
if (!isBuiltinClassInstance(lowerer, call.arguments[0]!, "AsyncLocalStorage")) {
if (!isBuiltinClassInstance(lowerer, call.arguments.nodes[0]!, "AsyncLocalStorage")) {
lowerer.noLowering(
`Channel.${name} with this store argument`,
call.arguments[0]!,
call.arguments.nodes[0]!,
"an AsyncLocalStorage instance (node:async_hooks) is the supported store",
);
}
const receiver = lowerer.lowerExprExpecting(access.expression, F64);
const store = lowerer.lowerExprExpecting(call.arguments[0]!, F64);
const store = lowerer.lowerExprExpecting(call.arguments.nodes[0]!, F64);
if (name === "unbindStore") {
return {
kind: "libCall",
@@ -148,8 +148,8 @@ export function lowerDiagnosticsChannelMethodCall(
};
}
const transform: IrExpr =
call.arguments[1] !== undefined
? lowerDiagnosticsSubscriber(lowerer, call.arguments[1]!)
call.arguments.nodes[1] !== undefined
? lowerDiagnosticsSubscriber(lowerer, call.arguments.nodes[1]!)
: dynUndefinedExpr(loc);
return {
kind: "libCall",
@@ -159,15 +159,15 @@ export function lowerDiagnosticsChannelMethodCall(
loc,
};
}
if (name === "runStores" && call.arguments.length >= 2) {
if (name === "runStores" && call.arguments.nodes.length >= 2) {
const receiver = lowerer.lowerExprExpecting(access.expression, F64);
const data = lowerDiagnosticsMessage(lowerer, call.arguments[0]!);
const fn = lowerDiagnosticsSubscriber(lowerer, call.arguments[1]!);
const data = lowerDiagnosticsMessage(lowerer, call.arguments.nodes[0]!);
const fn = lowerDiagnosticsSubscriber(lowerer, call.arguments.nodes[1]!);
const thisArg: IrExpr =
call.arguments[2] !== undefined
? lowerDiagnosticsMessage(lowerer, call.arguments[2]!)
call.arguments.nodes[2] !== undefined
? lowerDiagnosticsMessage(lowerer, call.arguments.nodes[2]!)
: dynUndefinedExpr(loc);
const rest = lowerTracingArguments(lowerer, call.arguments.slice(3), loc);
const rest = lowerTracingArguments(lowerer, call.arguments.nodes.slice(3), loc);
return {
kind: "libCall",
fn: "dc.chanRunStores",
@@ -198,7 +198,7 @@ export function lowerAsyncLocalStorageMethodCall(
if (!lowerer.isStdlibMember(access)) return null;
const name = access.name.text;
const loc = locOf(call);
if (name === "getStore" && call.arguments.length === 0) {
if (name === "getStore" && call.arguments.nodes.length === 0) {
const receiver = lowerer.lowerExprExpecting(access.expression, F64);
return asyncLocalStorageResult(lowerer, call, {
kind: "libCall",
@@ -208,11 +208,11 @@ export function lowerAsyncLocalStorageMethodCall(
loc,
});
}
if (name === "run" && call.arguments.length >= 2) {
if (name === "run" && call.arguments.nodes.length >= 2) {
const receiver = lowerer.lowerExprExpecting(access.expression, F64);
const value = lowerDiagnosticsMessage(lowerer, call.arguments[0]!);
const fn = lowerDiagnosticsSubscriber(lowerer, call.arguments[1]!);
const rest = lowerTracingArguments(lowerer, call.arguments.slice(2), loc);
const value = lowerDiagnosticsMessage(lowerer, call.arguments.nodes[0]!);
const fn = lowerDiagnosticsSubscriber(lowerer, call.arguments.nodes[1]!);
const rest = lowerTracingArguments(lowerer, call.arguments.nodes.slice(2), loc);
return asyncLocalStorageResult(lowerer, call, {
kind: "libCall",
fn: "als.run",
@@ -221,10 +221,10 @@ export function lowerAsyncLocalStorageMethodCall(
loc,
});
}
if (name === "exit" && call.arguments.length >= 1) {
if (name === "exit" && call.arguments.nodes.length >= 1) {
const receiver = lowerer.lowerExprExpecting(access.expression, F64);
const fn = lowerDiagnosticsSubscriber(lowerer, call.arguments[0]!);
const rest = lowerTracingArguments(lowerer, call.arguments.slice(1), loc);
const fn = lowerDiagnosticsSubscriber(lowerer, call.arguments.nodes[0]!);
const rest = lowerTracingArguments(lowerer, call.arguments.nodes.slice(1), loc);
return asyncLocalStorageResult(lowerer, call, {
kind: "libCall",
fn: "als.exitRun",
@@ -233,12 +233,12 @@ export function lowerAsyncLocalStorageMethodCall(
loc,
});
}
if (name === "enterWith" && call.arguments.length === 1) {
if (name === "enterWith" && call.arguments.nodes.length === 1) {
const receiver = lowerer.lowerExprExpecting(access.expression, F64);
const value = lowerDiagnosticsMessage(lowerer, call.arguments[0]!);
const value = lowerDiagnosticsMessage(lowerer, call.arguments.nodes[0]!);
return { kind: "libCall", fn: "als.enterWith", args: [receiver, value], type: VOID, loc };
}
if (name === "disable" && call.arguments.length === 0) {
if (name === "disable" && call.arguments.nodes.length === 0) {
const receiver = lowerer.lowerExprExpecting(access.expression, F64);
return { kind: "libCall", fn: "als.disable", args: [receiver], type: VOID, loc };
}
@@ -301,13 +301,13 @@ function lowerTracingHandlers(lowerer: Lowerer, node: ts.Expression): IrExpr {
if (
ts.isObjectLiteralExpression(e) &&
lowerer.mapTypeOf(lowerer.typeOf(e))?.kind === "record" &&
e.properties.length > 0 &&
e.properties.every((p) => ts.isPropertyAssignment(p) && ts.isIdentifier(p.name))
e.properties.nodes.length > 0 &&
e.properties.nodes.every((p) => ts.isPropertyAssignment(p) && ts.isIdentifier(p.name))
) {
const loc = locOf(e);
return {
kind: "dynObjLit",
fields: e.properties.map((p) => {
fields: e.properties.nodes.map((p) => {
const pa = p as ts.PropertyAssignment;
return {
key: {
@@ -366,32 +366,32 @@ export function lowerTracingChannelMethodCall(
if (!lowerer.isStdlibMember(access)) return null;
const name = access.name.text;
const loc = locOf(call);
if ((name === "subscribe" || name === "unsubscribe") && call.arguments.length === 1) {
if ((name === "subscribe" || name === "unsubscribe") && call.arguments.nodes.length === 1) {
const receiver = lowerer.lowerExprExpecting(access.expression, F64);
const handlers = lowerTracingHandlers(lowerer, call.arguments[0]!);
const handlers = lowerTracingHandlers(lowerer, call.arguments.nodes[0]!);
return name === "subscribe"
? { kind: "libCall", fn: "dc.tcSubscribe", args: [receiver, handlers], type: VOID, loc }
: { kind: "libCall", fn: "dc.tcUnsubscribe", args: [receiver, handlers], type: BOOL, loc };
}
if (
(name === "traceSync" || name === "traceCallback" || name === "tracePromise") &&
call.arguments.length >= 1
call.arguments.nodes.length >= 1
) {
const receiver = lowerer.lowerExprExpecting(access.expression, F64);
const fn = lowerDiagnosticsSubscriber(lowerer, call.arguments[0]!);
const fn = lowerDiagnosticsSubscriber(lowerer, call.arguments.nodes[0]!);
// traceSync(fn, context?, thisArg?, ...args) /
// tracePromise(fn, context?, thisArg?, ...args) /
// traceCallback(fn, position?, context?, thisArg?, ...args)
const shift = name === "traceCallback" ? 1 : 0;
const ctxNode = call.arguments[1 + shift];
const thisNode = call.arguments[2 + shift];
const ctxNode = call.arguments.nodes[1 + shift];
const thisNode = call.arguments.nodes[2 + shift];
const ctx: IrExpr =
ctxNode !== undefined
? lowerDiagnosticsMessage(lowerer, ctxNode)
: { kind: "dynObjLit", fields: [], type: DYN, loc };
const thisArg: IrExpr =
thisNode !== undefined ? lowerDiagnosticsMessage(lowerer, thisNode) : dynUndefinedExpr(loc);
const rest = lowerTracingArguments(lowerer, call.arguments.slice(3 + shift), loc);
const rest = lowerTracingArguments(lowerer, call.arguments.nodes.slice(3 + shift), loc);
if (name === "traceSync") {
return {
kind: "libCall",
@@ -416,8 +416,8 @@ export function lowerTracingChannelMethodCall(
};
}
const pos: IrExpr =
call.arguments[1] !== undefined
? lowerer.lowerExprExpecting(call.arguments[1]!, F64)
call.arguments.nodes[1] !== undefined
? lowerer.lowerExprExpecting(call.arguments.nodes[1]!, F64)
: { kind: "numLit", value: -1, type: F64, loc };
return {
kind: "libCall",
@@ -473,29 +473,32 @@ export function lowerDiagnosticsChannelCall(
bi: { module: string; member: string },
loc: SrcLoc,
): IrExpr | null {
if (bi.member === "channel" && expr.arguments.length === 1) {
const name = diagnosticsChannelNameArgument(lowerer, expr.arguments[0]!);
if (bi.member === "channel" && expr.arguments.nodes.length === 1) {
const name = diagnosticsChannelNameArgument(lowerer, expr.arguments.nodes[0]!);
return { kind: "libCall", fn: "dc.channel", args: [name], type: F64, loc };
}
if ((bi.member === "subscribe" || bi.member === "unsubscribe") && expr.arguments.length === 2) {
const name = diagnosticsChannelNameArgument(lowerer, expr.arguments[0]!);
const cb = lowerDiagnosticsSubscriber(lowerer, expr.arguments[1]!);
if (
(bi.member === "subscribe" || bi.member === "unsubscribe") &&
expr.arguments.nodes.length === 2
) {
const name = diagnosticsChannelNameArgument(lowerer, expr.arguments.nodes[0]!);
const cb = lowerDiagnosticsSubscriber(lowerer, expr.arguments.nodes[1]!);
return bi.member === "subscribe"
? { kind: "libCall", fn: "dc.subscribe", args: [name, cb], type: VOID, loc }
: { kind: "libCall", fn: "dc.unsubscribe", args: [name, cb], type: BOOL, loc };
}
if (bi.member === "hasSubscribers" && expr.arguments.length === 1) {
const name = diagnosticsChannelNameArgument(lowerer, expr.arguments[0]!);
if (bi.member === "hasSubscribers" && expr.arguments.nodes.length === 1) {
const name = diagnosticsChannelNameArgument(lowerer, expr.arguments.nodes[0]!);
return { kind: "libCall", fn: "dc.hasSubscribers", args: [name], type: BOOL, loc };
}
// tracingChannel: the string form interns the five tracing:<name>:*
// channels; the collection form takes an object literal whose five
// event members are Channel-typed values (Node's TracingChannelCollection).
if (bi.member === "tracingChannel" && expr.arguments.length === 1) {
const a = expr.arguments[0]!;
if (bi.member === "tracingChannel" && expr.arguments.nodes.length === 1) {
const a = expr.arguments.nodes[0]!;
if (ts.isObjectLiteralExpression(a)) {
const byEvent = new Map<string, IrExpr>();
for (const p of a.properties) {
for (const p of a.properties.nodes) {
if (
!ts.isPropertyAssignment(p) ||
!ts.isIdentifier(p.name) ||
@@ -45,9 +45,9 @@ export function lowerAtomicsCall(
: 3;
const maximum = member === "wait" ? 4 : member === "notify" ? 3 : minimum;
if (
call.arguments.length < minimum ||
call.arguments.length > maximum ||
call.arguments.some(ts.isSpreadElement)
call.arguments.nodes.length < minimum ||
call.arguments.nodes.length > maximum ||
call.arguments.nodes.some(ts.isSpreadElement)
) {
lowerer.noLowering(
`Atomics.${member} arguments`,
@@ -55,7 +55,7 @@ export function lowerAtomicsCall(
`use ${minimum}${maximum > minimum ? `–${maximum}` : ""} arguments`,
);
}
const source = call.arguments[0]!;
const source = call.arguments.nodes[0]!;
// Generic constraints can remain unions in the checker while this body
// has a concrete typed-array binding. Validate the actual lowered storage.
const array = lowerer.lowerExpr(source);
@@ -75,8 +75,8 @@ export function lowerAtomicsCall(
}
const number = (value: number): IrExpr => ({ kind: "numLit", value, type: F64, loc });
const arg = (index: number, fallback: number): IrExpr =>
call.arguments[index]
? lowerOptionalArgument(lowerer, call.arguments[index]!, F64, number(fallback))
call.arguments.nodes[index]
? lowerOptionalArgument(lowerer, call.arguments.nodes[index]!, F64, number(fallback))
: number(fallback);
const index = arg(1, 0);
if (member === "wait")
@@ -70,7 +70,7 @@ function memberNameOf(access: ts.PropertyAccessExpression | ts.ElementAccessExpr
export function lowerBlobNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr | null {
const cls = blobClassOf(lowerer, expr.expression);
if (cls === null) return null;
const args = expr.arguments ?? [];
const args = expr.arguments?.nodes ?? [];
if (args.some(ts.isSpreadElement)) return null;
const loc = locOf(expr);
const values = args.map((arg) =>
@@ -94,10 +94,10 @@ export function lowerStaticBlobCall(lowerer: Lowerer, call: ts.CallExpression):
const member = access.name.text;
if (access.questionDotToken || !BLOB_CALLS.has(member)) return null;
if (blobReceiverClass(lowerer, access.expression) === null) return null;
if (call.arguments.some(ts.isSpreadElement)) return null;
if (call.arguments.nodes.some(ts.isSpreadElement)) return null;
const loc = locOf(call);
const receiver = lowerer.lowerExprExpecting(access.expression, DYN);
const args = call.arguments.map((arg) => lowerer.lowerExprExpecting(arg, DYN));
const args = call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN));
if (member === "slice") {
return {
kind: "libCall",
@@ -126,7 +126,7 @@ export function registerPromisifiedBuiltinDecl(
if (registerHttpClientFnBinding(lowerer, nameNode, init)) return true;
const e = isPromisifyCall(lowerer, init);
if (!e) return false;
const argNode = e.arguments.length === 1 ? e.arguments[0]! : null;
const argNode = e.arguments.nodes.length === 1 ? e.arguments.nodes[0]! : null;
// `promisify(exec)` over a named import, or `promisify(cp.exec)` over a
// namespace import.
const target = !argNode
@@ -42,9 +42,9 @@ export function lowerExecFileAsyncCall(
): IrExpr {
const what = shell ? "the promisified exec" : "the promisified execFile";
if (
expr.arguments.length < 1 ||
expr.arguments.length > (shell ? 2 : 3) ||
expr.arguments.some(ts.isSpreadElement)
expr.arguments.nodes.length < 1 ||
expr.arguments.nodes.length > (shell ? 2 : 3) ||
expr.arguments.nodes.some(ts.isSpreadElement)
) {
lowerer.noLowering(
`${what} with this argument shape`,
@@ -54,7 +54,7 @@ export function lowerExecFileAsyncCall(
: "the supported form is execFileAsync(file, args?, options?)",
);
}
const command = lowerer.lowerExprExpecting(expr.arguments[0]!, STRING);
const command = lowerer.lowerExprExpecting(expr.arguments.nodes[0]!, STRING);
// exec: /bin/sh -c <command>, execSync's lowering (the command string
// rides as the argv[1] the failure display reads); execFile: file + its
// args list.
@@ -66,8 +66,8 @@ export function lowerExecFileAsyncCall(
type: arrayOf(STRING),
loc,
}
: lowerChildArgsArg(lowerer, expr.arguments[1], loc);
const optsNode = expr.arguments[shell ? 1 : 2];
: lowerChildArgsArg(lowerer, expr.arguments.nodes[1], loc);
const optsNode = expr.arguments.nodes[shell ? 1 : 2];
const emptyStr: IrExpr = { kind: "strLit", value: "", type: STRING, loc };
const helper = execFileAsyncHelper(lowerer, loc, shell);
@@ -93,7 +93,7 @@ export function lowerExecFileAsyncCall(
"pass the options inline so each member can be checked",
);
}
for (const p of optsNode.properties) {
for (const p of optsNode.properties.nodes) {
// The conditional-spread idiom carrying `env` — the portless
// opensslAsync shape: `...(c ? { env: { ...process.env, ...extra
// } } : {})` (either orientation). The condition evaluates ONCE
@@ -35,22 +35,22 @@ export function lowerChildMethodCall(
const loc = locOf(call);
if (name === "send") {
if (
call.arguments.length < 1 ||
call.arguments.length > 2 ||
call.arguments.some(ts.isSpreadElement)
call.arguments.nodes.length < 1 ||
call.arguments.nodes.length > 2 ||
call.arguments.nodes.some(ts.isSpreadElement)
) {
lowerer.noLowering(
`child.send with ${call.arguments.length} arguments`,
`child.send with ${call.arguments.nodes.length} arguments`,
call,
"send(message[, callback]) is supported; handle transfer and options are not",
);
}
const receiver = lowerer.lowerExpr(access.expression);
const json = ipcJsonMessage(lowerer, call.arguments[0]!, loc);
if (call.arguments[1] === undefined) {
const json = ipcJsonMessage(lowerer, call.arguments.nodes[0]!, loc);
if (call.arguments.nodes[1] === undefined) {
return { kind: "libCall", fn: "child.send", args: [receiver, json], type: BOOL, loc };
}
const callback = ipcSendCallback(lowerer, call.arguments[1]);
const callback = ipcSendCallback(lowerer, call.arguments.nodes[1]);
return {
kind: "libCall",
fn: "child.sendCb",
@@ -59,7 +59,7 @@ export function lowerChildMethodCall(
loc,
};
}
if (name === "disconnect" && call.arguments.length === 0) {
if (name === "disconnect" && call.arguments.nodes.length === 0) {
if (!ts.isExpressionStatement(call.parent)) {
lowerer.unsupported(
"SC1090",
@@ -75,8 +75,8 @@ export function lowerChildMethodCall(
loc,
};
}
if ((name === "on" || name === "once") && call.arguments.length === 2) {
const evT = lowerer.typeOf(call.arguments[0]!);
if ((name === "on" || name === "once") && call.arguments.nodes.length === 2) {
const evT = lowerer.typeOf(call.arguments.nodes[0]!);
const event = evT.isStringLiteralType() ? evT.value : null;
if (
event !== "spawn" &&
@@ -88,7 +88,7 @@ export function lowerChildMethodCall(
) {
lowerer.noLowering(
`child.${name}(${event === null ? "non-literal event" : `"${event}"`}, ...)`,
call.arguments[0]!,
call.arguments.nodes[0]!,
'"exit", "close", "error", "message", and "disconnect" are the supported child events (as literals)',
);
}
@@ -105,7 +105,7 @@ export function lowerChildMethodCall(
return {
kind: "libCall",
fn: "child.onMessage",
args: [receiver, ipcMessageListener(lowerer, call.arguments[1]!), once],
args: [receiver, ipcMessageListener(lowerer, call.arguments.nodes[1]!), once],
type: VOID,
loc,
};
@@ -114,24 +114,24 @@ export function lowerChildMethodCall(
return {
kind: "libCall",
fn: "child.onDisconnect",
args: [receiver, ipcDisconnectListener(lowerer, call.arguments[1]!), once],
args: [receiver, ipcDisconnectListener(lowerer, call.arguments.nodes[1]!), once],
type: VOID,
loc,
};
}
const listenerNode = stripTypeCasts(call.arguments[1]!);
const listenerNode = stripTypeCasts(call.arguments.nodes[1]!);
if (
isJsSourceFile(listenerNode.getSourceFile()) &&
(ts.isArrowFunction(listenerNode) || ts.isFunctionExpression(listenerNode)) &&
listenerNode.parameters.some((param) => param.dotDotDotToken) &&
listenerNode.parameters.every((param) => !param.type) &&
listenerNode.parameters.nodes.some((param) => param.dotDotDotToken) &&
listenerNode.parameters.nodes.every((param) => !param.type) &&
!/@(?:param|type)\b/.test(
listenerNode.getSourceFile().text.slice(listenerNode.pos, listenerNode.getStart()),
)
) {
for (const param of listenerNode.parameters) lowerer.checkedCallbackParams.add(param);
for (const param of listenerNode.parameters.nodes) lowerer.checkedCallbackParams.add(param);
}
const cb = lowerer.lowerExpr(call.arguments[1]!);
const cb = lowerer.lowerExpr(call.arguments.nodes[1]!);
if (
(isJsSourceFile(listenerNode.getSourceFile()) && cb.type.kind === "dyn") ||
(cb.type.kind === "func" &&
@@ -149,7 +149,7 @@ export function lowerChildMethodCall(
) {
lowerer.noLowering(
"child event listeners with non-representable parameters",
call.arguments[1]!,
call.arguments.nodes[1]!,
);
}
return {
@@ -168,7 +168,7 @@ export function lowerChildMethodCall(
if (cb.type.kind !== "func" || cb.type.params.length !== 0 || cb.type.ret.kind !== "void") {
lowerer.noLowering(
"spawn event listeners with parameters or a returned value",
call.arguments[1]!,
call.arguments.nodes[1]!,
);
}
return { kind: "libCall", fn: "child.onSpawn", args: [receiver, cb], type: VOID, loc };
@@ -176,7 +176,7 @@ export function lowerChildMethodCall(
if (cb.type.kind !== "func" || cb.type.params.length > (event === "error" ? 1 : 2)) {
lowerer.unsupported(
"SC1090",
call.arguments[1]!,
call.arguments.nodes[1]!,
event !== "error"
? `${event} listeners with more than two parameters (use (code, signal), (code), or ())`
: "error listeners with more than one parameter (use (err) or ())",
@@ -188,7 +188,7 @@ export function lowerChildMethodCall(
// fenced instead of being called through the wrong ABI.
lowerer.unsupported(
"SC1090",
call.arguments[1]!,
call.arguments.nodes[1]!,
"listeners returning a value (make the callback body a block, or return nothing)",
);
}
@@ -206,7 +206,7 @@ export function lowerChildMethodCall(
if (!armsOk) {
lowerer.unsupported(
"SC1090",
call.arguments[1]!,
call.arguments.nodes[1]!,
`${event} listeners whose parameter is not 'number | null' (got '${lowerer.fmt(param!)}')`,
);
}
@@ -228,7 +228,7 @@ export function lowerChildMethodCall(
if (!sigOk) {
lowerer.unsupported(
"SC1090",
call.arguments[1]!,
call.arguments.nodes[1]!,
`${event} listeners whose signal parameter is not 'Signals | null' (got '${lowerer.fmt(sigParam!)}')`,
);
}
@@ -243,7 +243,7 @@ export function lowerChildMethodCall(
if (param !== undefined && !(param.kind === "object" && param.className === "%Error")) {
lowerer.unsupported(
"SC1090",
call.arguments[1]!,
call.arguments.nodes[1]!,
`error listeners whose parameter is not 'Error' (got '${lowerer.fmt(param)}')`,
);
}
@@ -256,11 +256,11 @@ export function lowerChildMethodCall(
// reaped or never spawned (Node's null-handle answer), and a successful
// send sets `killed`.
if (name === "kill") {
if (call.arguments.length > 1) {
lowerer.noLowering(`child.kill with ${call.arguments.length} arguments`, call);
if (call.arguments.nodes.length > 1) {
lowerer.noLowering(`child.kill with ${call.arguments.nodes.length} arguments`, call);
}
const receiver = lowerer.lowerExpr(access.expression);
const sigNode = call.arguments[0];
const sigNode = call.arguments.nodes[0];
if (!sigNode) {
const dflt: IrExpr = { kind: "strLit", value: "SIGTERM", type: STRING, loc };
return { kind: "libCall", fn: "child.kill", args: [receiver, dflt], type: BOOL, loc };
@@ -281,7 +281,7 @@ export function lowerChildMethodCall(
// child.unref(): drops the child from the event loop's keep-alive set
// (the process may exit while the child runs — Node's semantics; the
// child is still reaped while the loop runs for other reasons).
if ((name === "unref" || name === "ref") && call.arguments.length === 0) {
if ((name === "unref" || name === "ref") && call.arguments.nodes.length === 0) {
const receiver = lowerer.lowerExpr(access.expression);
return {
kind: "libCall",
@@ -320,9 +320,9 @@ export function lowerChildStreamMethodCall(
if (!lowerer.isStdlibMember(access)) return null;
const name = access.name.text;
const loc = locOf(call);
if (name === "setEncoding" && call.arguments.length === 1) {
if (name === "setEncoding" && call.arguments.nodes.length === 1) {
const receiver = lowerer.lowerExprExpecting(access.expression, CHILDSTREAM_T);
const enc = bufEncoding(lowerer, "child stream setEncoding", call.arguments[0]!);
const enc = bufEncoding(lowerer, "child stream setEncoding", call.arguments.nodes[0]!);
return {
kind: "libCall",
fn: "stream.childSetEncoding",
@@ -331,13 +331,13 @@ export function lowerChildStreamMethodCall(
loc,
};
}
if ((name === "on" || name === "once") && call.arguments.length === 2) {
const evT = lowerer.typeOf(call.arguments[0]!);
if ((name === "on" || name === "once") && call.arguments.nodes.length === 2) {
const evT = lowerer.typeOf(call.arguments.nodes[0]!);
const event = evT.isStringLiteralType() ? evT.value : null;
if (event !== "data" && event !== "end") {
lowerer.noLowering(
`stream.${name}(${event === null ? "non-literal event" : `"${event}"`}, ...)`,
call.arguments[0]!,
call.arguments.nodes[0]!,
'"data" and "end" are the supported child-stream events (as literals)',
);
}
@@ -349,7 +349,7 @@ export function lowerChildStreamMethodCall(
);
}
const receiver = lowerer.lowerExpr(access.expression);
const cb = lowerer.lowerExpr(call.arguments[1]!);
const cb = lowerer.lowerExpr(call.arguments.nodes[1]!);
const once: IrExpr = { kind: "boolLit", value: name === "once", type: BOOL, loc };
if (
cb.type.kind !== "func" ||
@@ -358,7 +358,7 @@ export function lowerChildStreamMethodCall(
) {
lowerer.unsupported(
"SC1090",
call.arguments[1]!,
call.arguments.nodes[1]!,
event === "data"
? "data listeners with more than one parameter or a return value (use (chunk) or ())"
: "end listeners with parameters or a return value (use ())",
@@ -381,7 +381,7 @@ export function lowerChildStreamMethodCall(
) {
lowerer.unsupported(
"SC1090",
call.arguments[1]!,
call.arguments.nodes[1]!,
`data listeners whose parameter is not 'Buffer', 'string', or a Buffer-armed union (got '${lowerer.fmt(param)}')`,
);
}
@@ -417,15 +417,15 @@ export function lowerChildWriterMethodCall(
const loc = locOf(call);
if (name === "write") {
if (call.arguments.length !== 1 || call.arguments.some(ts.isSpreadElement)) {
if (call.arguments.nodes.length !== 1 || call.arguments.nodes.some(ts.isSpreadElement)) {
lowerer.noLowering(
`child stdin write with ${call.arguments.length} arguments`,
`child stdin write with ${call.arguments.nodes.length} arguments`,
call,
"write(string | Uint8Array) with no encoding or callback is supported",
);
}
const receiver = lowerer.lowerExprExpecting(access.expression, CHILDWRITER_T);
const data = lowerer.lowerExpr(call.arguments[0]!);
const data = lowerer.lowerExpr(call.arguments.nodes[0]!);
if (data.type.kind === "string") {
return { kind: "libCall", fn: "writer.writeString", args: [receiver, data], type: BOOL, loc };
}
@@ -434,12 +434,12 @@ export function lowerChildWriterMethodCall(
}
lowerer.noLowering(
`child stdin write of '${lowerer.fmt(data.type)}'`,
call.arguments[0]!,
call.arguments.nodes[0]!,
"write one string, Buffer, or Uint8Array value (narrow unions first)",
);
}
if ((name === "end" || name === "destroy") && call.arguments.length === 0) {
if ((name === "end" || name === "destroy") && call.arguments.nodes.length === 0) {
if (!ts.isExpressionStatement(call.parent)) {
lowerer.unsupported(
"SC1090",
@@ -457,13 +457,13 @@ export function lowerChildWriterMethodCall(
};
}
if ((name === "on" || name === "once") && call.arguments.length === 2) {
const eventType = lowerer.typeOf(call.arguments[0]!);
if ((name === "on" || name === "once") && call.arguments.nodes.length === 2) {
const eventType = lowerer.typeOf(call.arguments.nodes[0]!);
const event = eventType.isStringLiteralType() ? eventType.value : null;
if (event !== "drain" && event !== "finish" && event !== "error") {
lowerer.noLowering(
`child stdin ${name}(${event === null ? "non-literal event" : `"${event}"`}, ...)`,
call.arguments[0]!,
call.arguments.nodes[0]!,
'"drain", "finish", and "error" are the supported child stdin events',
);
}
@@ -471,7 +471,7 @@ export function lowerChildWriterMethodCall(
lowerer.unsupported("SC1090", call, "chaining child stdin listener registration");
}
const receiver = lowerer.lowerExprExpecting(access.expression, CHILDWRITER_T);
const cb = lowerer.lowerExpr(call.arguments[1]!);
const cb = lowerer.lowerExpr(call.arguments.nodes[1]!);
const maxParams = event === "error" ? 1 : 0;
if (
cb.type.kind !== "func" ||
@@ -480,7 +480,7 @@ export function lowerChildWriterMethodCall(
) {
lowerer.unsupported(
"SC1090",
call.arguments[1]!,
call.arguments.nodes[1]!,
event === "error"
? "error listeners with at most one Error parameter and no return value"
: `${event} listeners with no parameters or return value`,
@@ -493,7 +493,7 @@ export function lowerChildWriterMethodCall(
) {
lowerer.unsupported(
"SC1090",
call.arguments[1]!,
call.arguments.nodes[1]!,
"child stdin error listeners whose parameter is Error",
);
}
@@ -48,15 +48,15 @@ export function lowerExecSyncCall(
shell: boolean,
loc: SrcLoc,
): IrExpr {
if (expr.arguments.some(ts.isSpreadElement)) {
if (expr.arguments.nodes.some(ts.isSpreadElement)) {
lowerer.noLowering(`${shell ? "execSync" : "execFileSync"} with a spread call`, expr);
}
const cmd = lowerer.lowerExprExpecting(expr.arguments[0]!, STRING);
const cmd = lowerer.lowerExprExpecting(expr.arguments.nodes[0]!, STRING);
// execSync: /bin/sh -c <command>; execFileSync: file + its args list.
let argvExpr: IrExpr;
let optsNode: ts.Expression | undefined;
if (shell) {
if (expr.arguments.length > 2) {
if (expr.arguments.nodes.length > 2) {
lowerer.noLowering(
"execSync with this argument shape",
expr,
@@ -69,17 +69,17 @@ export function lowerExecSyncCall(
type: arrayOf(STRING),
loc,
};
optsNode = expr.arguments[1];
optsNode = expr.arguments.nodes[1];
} else {
if (expr.arguments.length > 3) {
if (expr.arguments.nodes.length > 3) {
lowerer.noLowering(
"execFileSync with this argument shape",
expr,
"the supported form is execFileSync(file, args?, options?)",
);
}
argvExpr = lowerChildArgsArg(lowerer, expr.arguments[1], loc);
optsNode = expr.arguments[2];
argvExpr = lowerChildArgsArg(lowerer, expr.arguments.nodes[1], loc);
optsNode = expr.arguments.nodes[2];
}
// The shell command itself is /bin/sh; the "command" string rides as
// the argv[1] the display formatter reads.
@@ -186,8 +186,8 @@ export function lowerExecSyncCall(
modeOf(t.value, 2);
return;
}
if (ts.isArrayLiteralExpression(node) && node.elements.length === 3) {
node.elements.forEach((el, i) => {
if (ts.isArrayLiteralExpression(node) && node.elements.nodes.length === 3) {
node.elements.nodes.forEach((el, i) => {
const et = lowerer.typeOf(el);
if (!et.isStringLiteralType()) {
lowerer.noLowering(
@@ -206,7 +206,7 @@ export function lowerExecSyncCall(
);
};
for (const p of optsNode.properties) {
for (const p of optsNode.properties.nodes) {
// The conditional-spread idiom carrying `env` — the openssl-runner
// shape: `...(c ? { env: { ...process.env, ...extra } } : {})`
// (either orientation). The condition evaluates ONCE and picks
@@ -31,8 +31,8 @@ export function lowerChildArgsArg(
loc: SrcLoc,
): IrExpr {
if (!node) return { kind: "arrayLit", elems: [], type: arrayOf(STRING), loc };
if (ts.isArrayLiteralExpression(node) && !node.elements.some(ts.isSpreadElement)) {
const elems = node.elements.map((el) => lowerer.lowerExprExpecting(el, STRING));
if (ts.isArrayLiteralExpression(node) && !node.elements.nodes.some(ts.isSpreadElement)) {
const elems = node.elements.nodes.map((el) => lowerer.lowerExprExpecting(el, STRING));
return { kind: "arrayLit", elems, type: arrayOf(STRING), loc: locOf(node) };
}
return lowerer.lowerExprExpecting(node, arrayOf(STRING));
@@ -92,16 +92,16 @@ const NODE_SIGNAL_NAMES = new Set([
* fences by name. The bare `{ encoding: "utf8" }` shape keeps its
* historical cp.spawnSync lowering. */
export function lowerSpawnSyncCall(lowerer: Lowerer, expr: ts.CallExpression, loc: SrcLoc): IrExpr {
if (expr.arguments.length > 3 || expr.arguments.some(ts.isSpreadElement)) {
if (expr.arguments.nodes.length > 3 || expr.arguments.nodes.some(ts.isSpreadElement)) {
lowerer.noLowering(
"spawnSync with this argument shape",
expr,
"the supported form is spawnSync(command, args?, options?)",
);
}
const cmd = lowerer.lowerExprExpecting(expr.arguments[0]!, STRING);
const argv = lowerChildArgsArg(lowerer, expr.arguments[1], loc);
const optsNode = expr.arguments[2];
const cmd = lowerer.lowerExprExpecting(expr.arguments.nodes[0]!, STRING);
const argv = lowerChildArgsArg(lowerer, expr.arguments.nodes[1], loc);
const optsNode = expr.arguments.nodes[2];
let timeout: IrExpr = numLit(0, loc);
let killSignal: IrExpr = { kind: "strLit", value: "", type: STRING, loc };
@@ -152,8 +152,8 @@ export function lowerSpawnSyncCall(lowerer: Lowerer, expr: ts.CallExpression, lo
modeOf(t.value, 2);
return;
}
if (ts.isArrayLiteralExpression(node) && node.elements.length === 3) {
node.elements.forEach((el, i) => {
if (ts.isArrayLiteralExpression(node) && node.elements.nodes.length === 3) {
node.elements.nodes.forEach((el, i) => {
const et = lowerer.typeOf(el);
if (!et.isStringLiteralType()) {
lowerer.noLowering("spawnSync stdio tuple entries beyond string literals", el);
@@ -182,7 +182,7 @@ export function lowerSpawnSyncCall(lowerer: Lowerer, expr: ts.CallExpression, lo
'stdio takes a "pipe"/"ignore"/"inherit" literal (or a value typed as a union of those), or a 3-tuple of literals',
);
};
for (const p of optsNode.properties) {
for (const p of optsNode.properties.nodes) {
const m = optionMember(p);
if (!m) {
lowerer.noLowering(
@@ -280,21 +280,21 @@ export function lowerSpawnSyncCall(lowerer: Lowerer, expr: ts.CallExpression, lo
* `windowsHide` (a POSIX no-op). The bare `{ stdio: "ignore" }` shape
* keeps its historical cp.spawn lowering. */
export function lowerSpawnCall(lowerer: Lowerer, expr: ts.CallExpression, loc: SrcLoc): IrExpr {
if (expr.arguments.length > 3 || expr.arguments.some(ts.isSpreadElement)) {
if (expr.arguments.nodes.length > 3 || expr.arguments.nodes.some(ts.isSpreadElement)) {
lowerer.noLowering(
"spawn with this argument shape",
expr,
'the supported form is spawn(command, args?, { stdio: "pipe" | "ignore" | "inherit", detached?, env?, cwd? })',
);
}
const cmd = lowerer.lowerExprExpecting(expr.arguments[0]!, STRING);
const cmd = lowerer.lowerExprExpecting(expr.arguments.nodes[0]!, STRING);
let argsNode: ts.Expression | undefined;
let optsNode: ts.Expression | undefined;
if (expr.arguments.length === 3) {
argsNode = expr.arguments[1];
optsNode = expr.arguments[2];
} else if (expr.arguments.length === 2) {
const second = expr.arguments[1]!;
if (expr.arguments.nodes.length === 3) {
argsNode = expr.arguments.nodes[1];
optsNode = expr.arguments.nodes[2];
} else if (expr.arguments.nodes.length === 2) {
const second = expr.arguments.nodes[1]!;
const secondType = lowerer.typeOf(second);
const mapped = lowerer.mapTypeOf(secondType);
if (
@@ -338,7 +338,7 @@ export function lowerSpawnCall(lowerer: Lowerer, expr: ts.CallExpression, loc: S
let plain = true; // exactly { stdio: "ignore" }: the historical libCall
if (optsNode && ts.isObjectLiteralExpression(optsNode)) {
for (const p of optsNode.properties) {
for (const p of optsNode.properties.nodes) {
// The conditional-spread idiom `...(isWindows ? {} : { detached:
// true })` (either orientation): the one carried member supported
// is `detached` with a boolean literal — the platform-conditional
@@ -393,7 +393,7 @@ export function lowerSpawnCall(lowerer: Lowerer, expr: ts.CallExpression, loc: S
// The 3-tuple form: every slot accepts a literal, including
// "pipe" (child.stdin/stdout/stderr); stdout/stderr also accept
// a number-typed fd (an openSync result — dup2'd into the child).
if (ts.isArrayLiteralExpression(m.value) && m.value.elements.length === 3) {
if (ts.isArrayLiteralExpression(m.value) && m.value.elements.nodes.length === 3) {
const slot = (el: ts.Expression, which: 0 | 1 | 2): void => {
const t = lowerer.typeOf(el);
if (t.isStringLiteralType()) {
@@ -434,7 +434,7 @@ export function lowerSpawnCall(lowerer: Lowerer, expr: ts.CallExpression, loc: S
errFd = fd;
}
};
m.value.elements.forEach((el, i) => slot(el, i as 0 | 1 | 2));
m.value.elements.nodes.forEach((el, i) => slot(el, i as 0 | 1 | 2));
sawStdio = true;
plain = false;
break;
@@ -512,7 +512,7 @@ export function lowerSpawnCall(lowerer: Lowerer, expr: ts.CallExpression, loc: S
}
if (shellEnabled && argsNode !== undefined) {
const shellArgs = stripTypeCasts(argsNode);
if (!ts.isArrayLiteralExpression(shellArgs) || shellArgs.elements.length !== 0) {
if (!ts.isArrayLiteralExpression(shellArgs) || shellArgs.elements.nodes.length !== 0) {
lowerer.noLowering(
"spawn with shell enabled and separate arguments",
argsNode,
@@ -551,9 +551,9 @@ export function lowerSpawnCall(lowerer: Lowerer, expr: ts.CallExpression, loc: S
* Node's default JSON IPC channel is always present. */
export function lowerForkCall(lowerer: Lowerer, expr: ts.CallExpression, loc: SrcLoc): IrExpr {
if (
expr.arguments.length < 1 ||
expr.arguments.length > 3 ||
expr.arguments.some(ts.isSpreadElement)
expr.arguments.nodes.length < 1 ||
expr.arguments.nodes.length > 3 ||
expr.arguments.nodes.some(ts.isSpreadElement)
) {
lowerer.noLowering(
"fork with this argument shape",
@@ -561,24 +561,24 @@ export function lowerForkCall(lowerer: Lowerer, expr: ts.CallExpression, loc: Sr
"use fork(staticModulePath[, stringArgs][, inlineOptions])",
);
}
const targetPath = staticForkModulePath(lowerer.program, expr.arguments[0]!);
const targetPath = staticForkModulePath(lowerer.program, expr.arguments.nodes[0]!);
const targetId =
targetPath === null ? undefined : lowerer.forkTargetIdByPath.get(tsgoPath(resolve(targetPath)));
if (targetId === undefined) {
lowerer.noLowering(
"fork with a runtime-valued module path",
expr.arguments[0]!,
expr.arguments.nodes[0]!,
'resolve a relative worker with new URL("./worker.ts", import.meta.url) or fileURLToPath(...) and bind it to a const if needed',
);
}
let argsNode: ts.Expression | undefined;
let optsNode: ts.Expression | undefined;
if (expr.arguments.length === 3) {
argsNode = expr.arguments[1];
optsNode = expr.arguments[2];
} else if (expr.arguments.length === 2) {
const second = expr.arguments[1]!;
if (expr.arguments.nodes.length === 3) {
argsNode = expr.arguments.nodes[1];
optsNode = expr.arguments.nodes[2];
} else if (expr.arguments.nodes.length === 2) {
const second = expr.arguments.nodes[1]!;
const secondType = lowerer.typeOf(second);
const mapped = lowerer.mapTypeOf(secondType);
if (
@@ -622,7 +622,7 @@ export function lowerForkCall(lowerer: Lowerer, expr: ts.CallExpression, loc: Sr
};
if (optsNode && ts.isObjectLiteralExpression(optsNode)) {
for (const property of optsNode.properties) {
for (const property of optsNode.properties.nodes) {
const member = optionMember(property);
if (!member) {
lowerer.noLowering(
@@ -651,14 +651,14 @@ export function lowerForkCall(lowerer: Lowerer, expr: ts.CallExpression, loc: Sr
break;
case "stdio": {
if (ts.isArrayLiteralExpression(member.value)) {
if (member.value.elements.length !== 4) {
if (member.value.elements.nodes.length !== 4) {
lowerer.noLowering(
"fork with this stdio tuple",
member.value,
'use [stdin, stdout, stderr, "ipc"] with exactly four literal entries',
);
}
const ipc = member.value.elements[3]!;
const ipc = member.value.elements.nodes[3]!;
const ipcType = lowerer.typeOf(ipc);
if (!ipcType.isStringLiteralType() || ipcType.value !== "ipc") {
lowerer.noLowering(
@@ -667,9 +667,9 @@ export function lowerForkCall(lowerer: Lowerer, expr: ts.CallExpression, loc: Sr
'the fourth stdio entry must be "ipc"',
);
}
inMode = stdioMode(member.value.elements[0]!, "stdin");
outMode = stdioMode(member.value.elements[1]!, "stdout");
errMode = stdioMode(member.value.elements[2]!, "stderr");
inMode = stdioMode(member.value.elements.nodes[0]!, "stdin");
outMode = stdioMode(member.value.elements.nodes[1]!, "stdout");
errMode = stdioMode(member.value.elements.nodes[2]!, "stderr");
} else {
const mode = stdioMode(member.value, "scalar");
inMode = outMode = errMode = mode;
@@ -743,20 +743,20 @@ export function lowerExecFileCall(
): IrExpr {
const what = shell ? "exec" : "execFile";
if (
expr.arguments.some(ts.isSpreadElement) ||
expr.arguments.length < 2 ||
expr.arguments.length > (shell ? 3 : 4)
expr.arguments.nodes.some(ts.isSpreadElement) ||
expr.arguments.nodes.length < 2 ||
expr.arguments.nodes.length > (shell ? 3 : 4)
) {
lowerer.noLowering(
`${what} with ${expr.arguments.length} arguments`,
`${what} with ${expr.arguments.nodes.length} arguments`,
expr,
shell
? "the supported callback forms are exec(command, callback) and exec(command, { encoding: 'utf8', maxBuffer: N }, callback)"
: "the supported callback forms are execFile(file, callback), execFile(file, args, callback), and execFile(file, args, { encoding: 'utf8', maxBuffer: N }, callback)",
);
}
const first = lowerer.lowerExprExpecting(expr.arguments[0]!, STRING);
const callbackNode = expr.arguments[expr.arguments.length - 1]!;
const first = lowerer.lowerExprExpecting(expr.arguments.nodes[0]!, STRING);
const callbackNode = expr.arguments.nodes[expr.arguments.nodes.length - 1]!;
// exec: /bin/sh -c <command>, execSync's lowering (the runtime reads the
// command line back from argv[1] for failure messages); execFile: the
// file and its args list.
@@ -768,9 +768,15 @@ export function lowerExecFileCall(
type: arrayOf(STRING),
loc,
}
: lowerChildArgsArg(lowerer, expr.arguments.length >= 3 ? expr.arguments[1] : undefined, loc);
: lowerChildArgsArg(
lowerer,
expr.arguments.nodes.length >= 3 ? expr.arguments.nodes[1] : undefined,
loc,
);
const optionsNode =
expr.arguments.length === (shell ? 3 : 4) ? expr.arguments[shell ? 1 : 2] : undefined;
expr.arguments.nodes.length === (shell ? 3 : 4)
? expr.arguments.nodes[shell ? 1 : 2]
: undefined;
if (optionsNode) {
const options = optionsNode;
if (!ts.isObjectLiteralExpression(options)) {
@@ -792,7 +798,7 @@ export function lowerExecFileCall(
].includes(node.operatorToken.kind) &&
pureNumber(node.left) &&
pureNumber(node.right));
for (const property of options.properties) {
for (const property of options.properties.nodes) {
const member = optionMember(property);
if (!member)
lowerer.noLowering(
@@ -7,6 +7,7 @@ import { stripTypeCasts } from "./arguments.js";
import { streamClassAliasDecl } from "../lower-stream.js";
import { zlibClassName, zlibStreamModeOf } from "./compression.js";
import { stdlibGlobalNameOf } from "../surfaces.js";
import { RUNTIME_ERROR_CLASSES } from "../../../ir/ir.js";
/** A builtin class an expression names at compile time: the error
* classes, node:events EventEmitter, the node:stream classes (each a
@@ -92,7 +93,7 @@ export function builtinClassAliasOwnsNoSlot(lowerer: Lowerer, symbol: ts.Symbol)
if (builtinClassAliasOf(lowerer, symbol) === null) return false;
const binding =
symbol.flags & ts.SymbolFlags.Alias ? lowerer.checker.getAliasedSymbol(symbol) : symbol;
const decl = lowerer.checker.valueDeclarationOf(binding);
const decl = binding.valueDeclaration;
if (decl === undefined || !ts.isVariableDeclaration(decl)) return false;
if (builtinMemberRequireDecl(decl.name, decl.initializer)) return false;
if (streamClassAliasDecl(lowerer, decl.name, decl.initializer)) return false;
@@ -123,6 +124,60 @@ export function registerBuiltinClassAlias(
return symbol !== undefined && builtinClassAliasOwnsNoSlot(lowerer, symbol);
}
/** The runtime-provided class (EventEmitter, a stream class, an error
* class) `expr` names, when values of it construct (valueCtorParams), or
* null. */
export function runtimeClassValueOf(lowerer: Lowerer, expr: ts.Expression): ClassInfo | null {
const ref = builtinClassRefOf(lowerer, expr);
if (ref === null || ref.kind === "zlib" || ref.kind === "collection") return null;
return ref.info.valueCtorParams !== undefined ? ref.info : null;
}
/** The runtime-provided class whose static side `type` is (`typeof
* EventEmitter`, `typeof Readable`, `ErrorConstructor`), when values of
* it construct, or null. Instance types have no construct signatures and
* answer null. A union of such static sides (`[Readable, Writable]`'s
* element type) is their nearest common ancestor's value when every arm
* constructs through the same parameters: a slot of the ancestor holds
* any of them. */
export function runtimeClassValueOfType(lowerer: Lowerer, type: ts.Type): string | null {
if (type.isUnionType()) {
const arms: ClassInfo[] = [];
for (const part of ts.constituentTypes(type)) {
const name = runtimeClassValueOfType(lowerer, part);
const info = name !== null ? lowerer.classes.get(name) : undefined;
if (!info) return null;
arms.push(info);
}
const ancestor = commonRuntimeAncestor(arms);
if (ancestor === null) return null;
return arms.every((arm) => lowerer.ctorAbiEquals(arm, ancestor)) ? ancestor.def.name : null;
}
if (lowerer.checker.getConstructSignatures(type).length === 0) return null;
const symbol = type.getSymbol();
if (!symbol || !lowerer.isStdlibSymbol(symbol)) return null;
let info = lowerer.builtinEmitterInfoOf(symbol) ?? lowerer.builtinStreamInfoOf(symbol);
if (!info && symbol.name.endsWith("Constructor")) {
for (const [irName, rec] of RUNTIME_ERROR_CLASSES) {
if (`${rec.lib}Constructor` === symbol.name) info = lowerer.classes.get(irName) ?? null;
}
}
return info?.valueCtorParams !== undefined ? info.def.name : null;
}
/** The nearest class every one of `infos` is or extends, when it is a
* runtime class whose values construct, or null. */
function commonRuntimeAncestor(infos: ClassInfo[]): ClassInfo | null {
for (let candidate = infos[0] ?? null; candidate; candidate = candidate.base) {
const holdsAll = infos.every((info) => {
for (let c: ClassInfo | null = info; c; c = c.base) if (c === candidate) return true;
return false;
});
if (holdsAll) return candidate.def.runtime && candidate.valueCtorParams ? candidate : null;
}
return null;
}
/** True when two references name the same builtin class. */
export function sameBuiltinClass(a: BuiltinClassRef, b: BuiltinClassRef): boolean {
if (a.kind === "zlib") return b.kind === "zlib" && a.mode === b.mode;
@@ -134,7 +189,7 @@ export function sameBuiltinClass(a: BuiltinClassRef, b: BuiltinClassRef): boolea
* can name a class (an identifier or a property access), or null. */
function constInitializerOf(lowerer: Lowerer, symbol: ts.Symbol): ts.Expression | null {
if ((symbol.flags & ts.SymbolFlags.BlockScopedVariable) === 0) return null;
const decl = lowerer.checker.valueDeclarationOf(symbol);
const decl = symbol.valueDeclaration;
if (
decl === undefined ||
!ts.isVariableDeclaration(decl) ||
@@ -138,9 +138,7 @@ export function zlibStreamModeOf(
symbol.flags & ts.SymbolFlags.Alias ? lowerer.checker.getAliasedSymbol(symbol) : symbol;
const mode = ZLIB_STREAM_MODES.get(target.name);
if (mode === undefined) return null;
return lowerer.checker
.declarationsOf(target)
.some((d) => ts.isClassDeclaration(d) && isZlibFallbackDecl(lowerer, d))
return target.declarations.some((d) => ts.isClassDeclaration(d) && isZlibFallbackDecl(lowerer, d))
? mode
: null;
}
@@ -161,15 +159,13 @@ function isZlibFallbackDecl(lowerer: Lowerer, d: ts.Node): boolean {
export function isZlibStreamMember(lowerer: Lowerer, access: ts.PropertyAccessExpression): boolean {
const symbol = lowerer.checker.getSymbolAtLocation(access.name);
if (!symbol) return false;
return lowerer.checker
.declarationsOf(symbol)
.some(
(d) =>
d.parent !== undefined &&
ts.isClassDeclaration(d.parent) &&
d.parent.name?.text === "ZlibBase" &&
isZlibFallbackDecl(lowerer, d.parent),
);
return symbol.declarations.some(
(d) =>
d.parent !== undefined &&
ts.isClassDeclaration(d.parent) &&
d.parent.name?.text === "ZlibBase" &&
isZlibFallbackDecl(lowerer, d.parent),
);
}
/** `stream.bytesWritten` on a zlib stream. */
@@ -197,7 +193,7 @@ export function lowerZlibStreamMethodCall(
): IrExpr {
const loc = locOf(call);
const member = access.name.text;
const args = call.arguments;
const args = call.arguments.nodes;
if (args.some(ts.isSpreadElement)) lowerer.noLowering(`${member} with spread arguments`, call);
const isFunc = (node: ts.Expression): boolean =>
lowerer.mapTypeOf(lowerer.typeOf(node))?.kind === "func";
@@ -304,25 +300,25 @@ export function lowerPromisifiedZlibCall(
member: string,
loc: SrcLoc,
): IrExpr {
if (expr.arguments.some(ts.isSpreadElement)) {
if (expr.arguments.nodes.some(ts.isSpreadElement)) {
lowerer.noLowering(`the promisified zlib.${member} with spread arguments`, expr);
}
if (expr.arguments.length !== 1 && expr.arguments.length !== 2) {
if (expr.arguments.nodes.length !== 1 && expr.arguments.nodes.length !== 2) {
lowerer.noLowering(
`the promisified zlib.${member} with ${expr.arguments.length} arguments`,
`the promisified zlib.${member} with ${expr.arguments.nodes.length} arguments`,
expr,
`the supported forms are ${member}Async(data) and ${member}Async(data, options)`,
);
}
const input = zlibInput(lowerer, expr.arguments[0]!, loc);
const input = zlibInput(lowerer, expr.arguments.nodes[0]!, loc);
return {
kind: "libCall",
fn: input.dyn ? "zlib.promiseDyn" : "zlib.promise",
args: [
numLit(ZLIB_ONE_SHOT_MODES[member]!, loc),
input.value,
expr.arguments[1] !== undefined
? zlibOptionsValue(lowerer, expr.arguments[1], `zlib.${member}`, loc)
expr.arguments.nodes[1] !== undefined
? zlibOptionsValue(lowerer, expr.arguments.nodes[1], `zlib.${member}`, loc)
: dynUndefinedExpr(loc),
],
type: { kind: "promise", inner: BYTES_U8 },
@@ -346,19 +342,26 @@ export function lowerZlibModuleCall(
bi: { module: string; member: string },
loc: SrcLoc,
): IrExpr {
if (expr.arguments.some(ts.isSpreadElement)) {
if (expr.arguments.nodes.some(ts.isSpreadElement)) {
lowerer.noLowering(`zlib.${bi.member} with spread arguments`, expr);
}
const streamMode = ZLIB_STREAM_FACTORIES[bi.member];
if (streamMode !== undefined) {
return lowerZlibStreamNew(lowerer, expr.arguments, streamMode, `zlib.${bi.member}`, expr, loc);
return lowerZlibStreamNew(
lowerer,
expr.arguments.nodes,
streamMode,
`zlib.${bi.member}`,
expr,
loc,
);
}
const syncFn = ZLIB_SYNC_FNS[bi.member];
if (syncFn !== undefined) {
if (expr.arguments.length === 0 || expr.arguments.length > 2) {
lowerer.noLowering(`${bi.member} with ${expr.arguments.length} arguments`, expr);
if (expr.arguments.nodes.length === 0 || expr.arguments.nodes.length > 2) {
lowerer.noLowering(`${bi.member} with ${expr.arguments.nodes.length} arguments`, expr);
}
const input = zlibInput(lowerer, expr.arguments[0]!, loc);
const input = zlibInput(lowerer, expr.arguments.nodes[0]!, loc);
if (input.dyn) {
return {
kind: "libCall",
@@ -366,8 +369,8 @@ export function lowerZlibModuleCall(
args: [
numLit(ZLIB_ONE_SHOT_MODES[bi.member]!, loc),
input.value,
expr.arguments[1] !== undefined
? zlibOptionsValue(lowerer, expr.arguments[1], `zlib.${bi.member}`, loc)
expr.arguments.nodes[1] !== undefined
? zlibOptionsValue(lowerer, expr.arguments.nodes[1], `zlib.${bi.member}`, loc)
: dynUndefinedExpr(loc),
],
type: BYTES_U8,
@@ -375,15 +378,15 @@ export function lowerZlibModuleCall(
};
}
if (
expr.arguments.length === 2 &&
expr.arguments.nodes.length === 2 &&
(bi.member === "deflateSync" || bi.member === "deflateRawSync" || bi.member === "gzipSync")
) {
const options = expr.arguments[1]!;
const options = expr.arguments.nodes[1]!;
// A lone literal level keeps the direct compression entry (build-time
// compression in the native emitters uses it); other options take the
// runtime option ladder below.
if (ts.isObjectLiteralExpression(options) && options.properties.length === 1) {
const prop = options.properties[0]!;
if (ts.isObjectLiteralExpression(options) && options.properties.nodes.length === 1) {
const prop = options.properties.nodes[0]!;
if (
ts.isPropertyAssignment(prop) &&
(ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name)) &&
@@ -416,14 +419,14 @@ export function lowerZlibModuleCall(
}
}
}
if (expr.arguments.length === 2) {
if (expr.arguments.nodes.length === 2) {
return {
kind: "libCall",
fn: "zlib.syncOpts",
args: [
numLit(ZLIB_ONE_SHOT_MODES[bi.member]!, loc),
input.value,
zlibOptionsValue(lowerer, expr.arguments[1]!, `zlib.${bi.member}`, loc),
zlibOptionsValue(lowerer, expr.arguments.nodes[1]!, `zlib.${bi.member}`, loc),
],
type: BYTES_U8,
loc,
@@ -439,19 +442,19 @@ export function lowerZlibModuleCall(
}
const callbackFn = ZLIB_CALLBACK_FNS[bi.member];
if (callbackFn !== undefined) {
if (expr.arguments.length !== 2 && expr.arguments.length !== 3) {
if (expr.arguments.nodes.length !== 2 && expr.arguments.nodes.length !== 3) {
lowerer.noLowering(
`${bi.member} with ${expr.arguments.length} arguments`,
`${bi.member} with ${expr.arguments.nodes.length} arguments`,
expr,
`${bi.member}(data, callback) and ${bi.member}(data, options, callback) are supported`,
);
}
const input = zlibInput(lowerer, expr.arguments[0]!, loc);
const input = zlibInput(lowerer, expr.arguments.nodes[0]!, loc);
const dynData = input.dyn;
if (expr.arguments.length === 3 || dynData) {
if (expr.arguments.nodes.length === 3 || dynData) {
// (data, options, callback): Node validates the options before the
// callback and the input, so the runtime ladder runs first.
const withOptions = expr.arguments.length === 3;
const withOptions = expr.arguments.nodes.length === 3;
return {
kind: "libCall",
fn: dynData ? "zlib.cbOptsDyn" : "zlib.cbOpts",
@@ -459,9 +462,9 @@ export function lowerZlibModuleCall(
numLit(ZLIB_ONE_SHOT_MODES[bi.member]!, loc),
input.value,
withOptions
? zlibOptionsValue(lowerer, expr.arguments[1]!, `zlib.${bi.member}`, loc)
? zlibOptionsValue(lowerer, expr.arguments.nodes[1]!, `zlib.${bi.member}`, loc)
: dynUndefinedExpr(loc),
errorFirstBytesCallback(lowerer, expr.arguments[withOptions ? 2 : 1]!, "zlib"),
errorFirstBytesCallback(lowerer, expr.arguments.nodes[withOptions ? 2 : 1]!, "zlib"),
],
type: VOID,
loc,
@@ -470,15 +473,15 @@ export function lowerZlibModuleCall(
return {
kind: "libCall",
fn: callbackFn,
args: [input.value, errorFirstBytesCallback(lowerer, expr.arguments[1]!, "zlib")],
args: [input.value, errorFirstBytesCallback(lowerer, expr.arguments.nodes[1]!, "zlib")],
type: VOID,
loc,
};
}
if (bi.member === "crc32") {
if (expr.arguments.length < 1 || expr.arguments.length > 2) {
if (expr.arguments.nodes.length < 1 || expr.arguments.nodes.length > 2) {
lowerer.noLowering(
`zlib.crc32 with ${expr.arguments.length} arguments`,
`zlib.crc32 with ${expr.arguments.nodes.length} arguments`,
expr,
"crc32(data[, initialValue]) is supported",
);
@@ -487,9 +490,9 @@ export function lowerZlibModuleCall(
kind: "libCall",
fn: "zlib.crc32",
args: [
lowerBuiltinByteInput(lowerer, expr.arguments[0]!, loc, "zlib"),
expr.arguments[1]
? lowerer.lowerExprExpecting(expr.arguments[1]!, F64)
lowerBuiltinByteInput(lowerer, expr.arguments.nodes[0]!, loc, "zlib"),
expr.arguments.nodes[1]
? lowerer.lowerExprExpecting(expr.arguments.nodes[1]!, F64)
: { kind: "numLit", value: 0, type: F64, loc },
],
type: F64,
@@ -1,11 +1,11 @@
import * as ts from "../../ts7/adapter.js";
import { type Lowerer, own } from "../lowerer.js";
import { canonicalBuiltinModule, locOf, requireSpecOf } from "../../program.js";
import { canonicalBuiltinModule, isJsSourceFile, locOf, requireSpecOf } from "../../program.js";
import { builtinConstLit } from "../surfaces.js";
import { HTTP2_CONSTANTS } from "../http2-constants.js";
import { CRYPTO_CONSTANTS } from "../crypto-tables.js";
import { ZLIB_CONSTANTS } from "../zlib-constants.js";
import { F64, type IrExpr } from "../../../ir/ir.js";
import { DYN, F64, type IrExpr, STRING, type SrcLoc } from "../../../ir/ir.js";
import { builtinImportOf } from "./module-bindings.js";
/** The builtin modules whose `constants` object bakes as literals at
@@ -88,7 +88,7 @@ export function builtinConstantsDestructureDecl(
const module = builtinConstantsModuleOf(lowerer, init);
if (module === null) return false;
const table = BUILTIN_CONSTANTS_TABLES[module]!.table;
return nameNode.elements.every(
return nameNode.elements.nodes.every(
(el) =>
!el.dotDotDotToken &&
el.initializer === undefined &&
@@ -106,7 +106,7 @@ export function builtinConstantsDestructureDecl(
* its baked literal value. Null for every other binding. */
export function builtinConstantBindingOf(lowerer: Lowerer, ident: ts.Identifier): IrExpr | null {
const symbol = lowerer.checker.getSymbolAtLocation(ident);
const decl = symbol ? lowerer.checker.declarationsOf(symbol)[0] : undefined;
const decl = symbol ? symbol.declarations[0] : undefined;
if (!decl || !ts.isBindingElement(decl) || decl.dotDotDotToken || decl.initializer) return null;
if (!ts.isObjectBindingPattern(decl.parent)) return null;
const varDecl = decl.parent.parent;
@@ -147,12 +147,30 @@ export function lowerFsConstantsProperty(
COPYFILE_FICLONE_FORCE: 4,
};
const value = own(MODES, expr.name.text);
if (value === undefined) {
lowerer.noLowering(
`fs.constants.${expr.name.text}`,
expr,
"F_OK, R_OK, W_OK, X_OK, and COPYFILE_* are the lowered constants",
);
}
if (value === undefined) return fsConstantMemberRead(expr);
return { kind: "numLit", value, type: F64, loc: locOf(expr) };
}
/** fs.constants as a value: the target's null-prototype constants object,
* built by the runtime from its own libc's macros. */
export function fsConstantsObject(loc: SrcLoc): IrExpr {
return { kind: "libCall", fn: "fs.constants", args: [], type: DYN, loc };
}
/** `fs.constants.O_RDONLY` (any member the access-mode literals do not
* cover): a read of the runtime object, so a constant the target lacks
* reads as undefined in JavaScript and fails the number check in
* TypeScript, whose declarations promise every name. */
export function fsConstantMemberRead(expr: ts.PropertyAccessExpression): IrExpr {
const loc = locOf(expr);
const read: IrExpr = {
kind: "dynKeyGet",
value: fsConstantsObject(loc),
key: { kind: "strLit", value: expr.name.text, type: STRING, loc },
type: DYN,
loc,
};
return isJsSourceFile(expr.getSourceFile())
? read
: { kind: "dynCheck", value: read, type: F64, loc };
}
@@ -47,17 +47,17 @@ export function lowerCryptoComposedCall(
// "hex"/"base64" encoding. Anything else falls through (null): bare
// randomBytes lowers to a real Buffer through the crypto table, and
// the .toString rides the ordinary Buffer method lowering.
if (recv.arguments.length !== 1) return null;
const encNode = call.arguments[0];
if (recv.arguments.nodes.length !== 1) return null;
const encNode = call.arguments.nodes[0];
const encT = encNode ? lowerer.typeOf(encNode) : undefined;
if (
call.arguments.length !== 1 ||
call.arguments.nodes.length !== 1 ||
!encT?.isStringLiteralType() ||
(encT.value !== "hex" && encT.value !== "base64")
) {
return null;
}
const size = lowerer.lowerExprExpecting(recv.arguments[0]!, F64);
const size = lowerer.lowerExprExpecting(recv.arguments.nodes[0]!, F64);
const enc = lowerer.lowerExprExpecting(encNode!, STRING);
return {
kind: "libCall",
@@ -96,28 +96,30 @@ function lowerHashDigestChain(
: null;
if (!bi || bi.module !== "crypto" || bi.member !== "createHash") return null;
const loc = locOf(call);
const algT = chCall.arguments.length === 1 ? lowerer.typeOf(chCall.arguments[0]!) : undefined;
const algT =
chCall.arguments.nodes.length === 1 ? lowerer.typeOf(chCall.arguments.nodes[0]!) : undefined;
if (!algT?.isStringLiteralType() || (algT.value !== "sha256" && algT.value !== "sha1"))
return null;
if (updateCall.arguments.length !== 1) {
if (updateCall.arguments.nodes.length !== 1) {
return null;
}
const encT = call.arguments.length === 1 ? lowerer.typeOf(call.arguments[0]!) : undefined;
const encT =
call.arguments.nodes.length === 1 ? lowerer.typeOf(call.arguments.nodes[0]!) : undefined;
if (!encT?.isStringLiteralType() || (encT.value !== "hex" && encT.value !== "base64")) {
return null;
}
// alg and enc are proven literals (fenced above), so lowering them
// out of source position observes nothing; the data lowers between
// them in its own source order.
const alg = lowerer.lowerExprExpecting(chCall.arguments[0]!, STRING);
const alg = lowerer.lowerExprExpecting(chCall.arguments.nodes[0]!, STRING);
// The data picks the runtime entry by its static type, the
// fileURLToPath convention: strings hash their UTF-8 bytes (Node's
// default input encoding), Buffers/typed arrays hash their bytes.
const dataNode = updateCall.arguments[0]!;
const dataNode = updateCall.arguments.nodes[0]!;
const dataIr = lowerer.mapTypeOf(lowerer.typeOf(dataNode));
if (dataIr?.kind === "bytes") {
const data = cryptoByteView(lowerer, dataNode, lowerer.lowerExpr(dataNode));
const enc = lowerer.lowerExprExpecting(call.arguments[0]!, STRING);
const enc = lowerer.lowerExprExpecting(call.arguments.nodes[0]!, STRING);
return {
kind: "libCall",
fn: "crypto.hashDigestBytes",
@@ -128,7 +130,7 @@ function lowerHashDigestChain(
}
if (dataIr?.kind === "string") {
const data = lowerer.lowerExprExpecting(dataNode, STRING);
const enc = lowerer.lowerExprExpecting(call.arguments[0]!, STRING);
const enc = lowerer.lowerExprExpecting(call.arguments.nodes[0]!, STRING);
return {
kind: "libCall",
fn: "crypto.hashDigestStr",
@@ -257,10 +259,11 @@ export function lowerCryptoModuleCall(
loc: SrcLoc,
): IrExpr | null {
if (bi.module !== "crypto") return null;
const args = expr.arguments;
const args = expr.arguments.nodes;
if (args.some(ts.isSpreadElement)) {
lowerer.noLowering(`crypto.${bi.member} with spread arguments`, expr);
}
if (bi.member === "getRandomValues") return lowerCryptoGetRandomValues(lowerer, expr);
if (bi.member === "randomBytes") {
if (args.length === 1) return null;
if (args.length !== 2) {
@@ -465,8 +468,8 @@ export function lowerCryptoModuleCall(
let options = args[2];
// An options literal holding only a literal string-digest
// outputEncoding is that encoding; evaluating it observes nothing.
if (options && ts.isObjectLiteralExpression(options) && options.properties.length === 1) {
const property = options.properties[0]!;
if (options && ts.isObjectLiteralExpression(options) && options.properties.nodes.length === 1) {
const property = options.properties.nodes[0]!;
if (
ts.isPropertyAssignment(property) &&
ts.isIdentifier(property.name) &&
@@ -820,8 +823,8 @@ export function lowerCryptoModuleCall(
};
const list = own(LISTS, bi.member);
if (bi.member !== "getFips" && list === undefined) return null;
if (expr.arguments.length !== 0) {
lowerer.noLowering(`crypto.${bi.member} with ${expr.arguments.length} arguments`, expr);
if (expr.arguments.nodes.length !== 0) {
lowerer.noLowering(`crypto.${bi.member} with ${expr.arguments.nodes.length} arguments`, expr);
}
if (bi.member === "getFips") {
return { kind: "numLit", value: 0, type: F64, loc };
@@ -876,7 +879,7 @@ export function isCipherType(lowerer: Lowerer, type: ts.Type): boolean {
return (
!!symbol &&
CIPHER_TYPE_NAMES.has(symbol.name) &&
lowerer.checker.declarationsOf(symbol).some((d) => lowerer.isStdlibFile(d.getSourceFile()))
symbol.declarations.some((d) => lowerer.isStdlibFile(d.getSourceFile()))
);
}
@@ -916,7 +919,7 @@ export function lowerCipherMethodCall(
"update(), final(), setAAD(), setAutoPadding(), getAuthTag(), and setAuthTag() are supported; cipher streams are not",
);
}
if (call.arguments.some(ts.isSpreadElement))
if (call.arguments.nodes.some(ts.isSpreadElement))
lowerer.noLowering(`Cipheriv/Decipheriv.${name} with spread arguments`, call);
const loc = locOf(call);
return {
@@ -924,7 +927,7 @@ export function lowerCipherMethodCall(
recv: lowerer.lowerExprExpecting(access.expression, DYN),
method: name,
calleeName: v8CalleeText(access),
args: call.arguments.map((arg) => lowerer.lowerExprExpecting(arg, DYN)),
args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)),
type: DYN,
loc,
};
@@ -946,9 +949,9 @@ export function lowerCryptoHashMethodCall(
// Hmac has no copy(): Node reads the member as undefined and throws a
// TypeError once the arguments are evaluated.
if (receiverType.kind === "cryptoHmac" && name === "copy") {
if (call.arguments.some(ts.isSpreadElement))
if (call.arguments.nodes.some(ts.isSpreadElement))
lowerer.noLowering("Hmac.copy with spread arguments", call);
const evaluated = [access.expression, ...call.arguments].map((node): IrStmt => ({
const evaluated = [access.expression, ...call.arguments.nodes].map((node): IrStmt => ({
kind: "exprStmt",
expr: lowerer.lowerExpr(node),
loc,
@@ -965,19 +968,19 @@ export function lowerCryptoHashMethodCall(
const receiver = (): IrExpr => lowerer.lowerExprExpecting(access.expression, receiverType);
const hash = receiverType.kind === "cryptoHash";
const label = hash ? "Hash" : "Hmac";
if (call.arguments.some(ts.isSpreadElement))
if (call.arguments.nodes.some(ts.isSpreadElement))
lowerer.noLowering(`${label}.${name} with spread arguments`, call);
if (name === "update") {
if (call.arguments.length < 1 || call.arguments.length > 2) {
if (call.arguments.nodes.length < 1 || call.arguments.nodes.length > 2) {
lowerer.noLowering(
`${label}.update with ${call.arguments.length} arguments`,
`${label}.update with ${call.arguments.nodes.length} arguments`,
call,
"update(data[, inputEncoding]) is supported",
);
}
const target = receiver();
const dataNode = call.arguments[0]!;
const encodingNode = call.arguments[1];
const dataNode = call.arguments.nodes[0]!;
const encodingNode = call.arguments.nodes[1];
const dataType = lowerer.mapTypeOf(lowerer.typeOf(dataNode));
// Indexed reads can retain optional storage after a nullish fallback
// or narrowing. Use the proven argument type through a checked coercion.
@@ -1032,14 +1035,14 @@ export function lowerCryptoHashMethodCall(
};
}
if (name === "digest") {
if (call.arguments.length > 1) {
if (call.arguments.nodes.length > 1) {
lowerer.noLowering(
`${label}.digest with ${call.arguments.length} arguments`,
`${label}.digest with ${call.arguments.nodes.length} arguments`,
call,
"digest([outputEncoding]) is supported",
);
}
const encodingNode = call.arguments[0];
const encodingNode = call.arguments.nodes[0];
if (encodingNode === undefined) {
return {
kind: "libCall",
@@ -1078,13 +1081,13 @@ export function lowerCryptoHashMethodCall(
};
}
if (name === "copy" && hash) {
if (call.arguments.length > 1)
if (call.arguments.nodes.length > 1)
lowerer.noLowering(
`Hash.copy with ${call.arguments.length} arguments`,
`Hash.copy with ${call.arguments.nodes.length} arguments`,
call,
"copy([options]) is supported",
);
if (call.arguments.length === 0)
if (call.arguments.nodes.length === 0)
return {
kind: "libCall",
fn: "crypto.hashCopy",
@@ -1095,7 +1098,7 @@ export function lowerCryptoHashMethodCall(
return {
kind: "libCall",
fn: "crypto.hashCopyOpts",
args: [receiver(), lowerer.lowerExprExpecting(call.arguments[0]!, DYN)],
args: [receiver(), lowerer.lowerExprExpecting(call.arguments.nodes[0]!, DYN)],
type: CRYPTOHASH_T,
loc,
};
@@ -1138,7 +1141,7 @@ export function lowerPromisifiedCryptoCall(
member: string,
loc: SrcLoc,
): IrExpr {
const args = expr.arguments;
const args = expr.arguments.nodes;
if (args.some(ts.isSpreadElement)) {
lowerer.noLowering(`the promisified crypto.${member} with spread arguments`, expr);
}
@@ -1282,3 +1285,49 @@ export function lowerPromisifiedCryptoCall(
"KeyObject",
);
}
/** `crypto.getRandomValues(view)` — the WebCrypto global, node:crypto's
* export and `webcrypto.getRandomValues`: a typed view fills in place and
* returns itself at its own type (float views, DataViews and views over
* 65536 bytes throw Node's DOMExceptions at run time); any other value
* takes the checked form. Null for other arities (the member fence keeps
* them). */
export function lowerCryptoGetRandomValues(
lowerer: Lowerer,
call: ts.CallExpression,
): IrExpr | null {
if (call.arguments.nodes.length !== 1 || ts.isSpreadElement(call.arguments.nodes[0]!))
return null;
const loc = locOf(call);
const value = lowerer.lowerExpr(call.arguments.nodes[0]!);
if (value.type.kind === "bytes")
return { kind: "libCall", fn: "crypto.getRandomValues", args: [value], type: value.type, loc };
return {
kind: "libCall",
fn: "crypto.getRandomValuesDyn",
args: [lowerer.coerceInto(call.arguments.nodes[0]!, value, DYN)],
type: DYN,
loc,
};
}
/** Whether `receiver` names a WebCrypto Crypto object: the `crypto`
* global (also through globalThis) or node:crypto's `webcrypto`. */
export function isWebCryptoReceiver(lowerer: Lowerer, receiver: ts.Expression): boolean {
if (ts.isIdentifier(receiver)) {
if (receiver.text === "crypto" && lowerer.isStdlibGlobal(receiver, "crypto")) return true;
const imported = lowerer.builtinImportOf(receiver);
return imported?.module === "crypto" && imported.member === "webcrypto";
}
if (ts.isPropertyAccessExpression(receiver) && !receiver.questionDotToken) {
if (
receiver.name.text === "crypto" &&
ts.isIdentifier(receiver.expression) &&
lowerer.isStdlibGlobal(receiver.expression, "globalThis")
)
return true;
const member = lowerer.builtinMemberOf(receiver);
return member?.module === "crypto" && member.member === "webcrypto";
}
return false;
}
@@ -33,8 +33,14 @@ const DATE_GETTER_FNS: Readonly<Record<string, IrLibFn>> = {
getTimezoneOffset: "date.getTimezoneOffset",
};
const DATE_TEXT_FNS: ReadonlyMap<string, IrLibFn> = new Map([
["toString", "date.toString"],
["toDateString", "date.toDateString"],
["toTimeString", "date.toTimeString"],
]);
const DATE_METHOD_HINT =
"getTime(), valueOf(), toISOString(), toUTCString(), toJSON(), the local/UTC calendar getters, and getTimezoneOffset() are supported; Date setters and locale/string formatters have no lowering";
"getTime(), valueOf(), toISOString(), toUTCString(), toJSON(), toString(), toDateString(), toTimeString(), the local/UTC calendar getters, and getTimezoneOffset() are supported; Date setters and locale formatters have no lowering";
/** The Date slice: statics plus read-only Date values backed by one
* TimeClip'd millisecond scalar. Construction lives in lowerNew;
@@ -47,8 +53,8 @@ export function lowerDateCall(
if (call.questionDotToken || access.questionDotToken) return null;
const loc = locOf(call);
if (lowerer.stdlibGlobalMember(access, "Date") === "now") {
if (call.arguments.length !== 0) {
lowerer.noLowering(`Date.now with ${call.arguments.length} arguments`, call);
if (call.arguments.nodes.length !== 0) {
lowerer.noLowering(`Date.now with ${call.arguments.nodes.length} arguments`, call);
}
return { kind: "libCall", fn: "date.now", args: [], type: F64, loc };
}
@@ -56,10 +62,10 @@ export function lowerDateCall(
// new Date(dateString).getTime(); keep their NaN and TimeClip behavior
// identical for the ISO timestamps used by CLI applications.
if (lowerer.stdlibGlobalMember(access, "Date") === "parse") {
if (call.arguments.length !== 1 || ts.isSpreadElement(call.arguments[0]!)) {
lowerer.noLowering(`Date.parse with ${call.arguments.length} arguments`, call);
if (call.arguments.nodes.length !== 1 || ts.isSpreadElement(call.arguments.nodes[0]!)) {
lowerer.noLowering(`Date.parse with ${call.arguments.nodes.length} arguments`, call);
}
const input = lowerer.lowerExprExpecting(call.arguments[0]!, STRING);
const input = lowerer.lowerExprExpecting(call.arguments.nodes[0]!, STRING);
return { kind: "libCall", fn: "date.parse", args: [input], type: F64, loc };
}
// Date.UTC(year[, month[, date[, hours[, minutes[, seconds[, ms]]]]]]):
@@ -69,16 +75,16 @@ export function lowerDateCall(
// argument to number, so the seven-f64 ABI is exact.
if (lowerer.stdlibGlobalMember(access, "Date") === "UTC") {
if (
call.arguments.length < 1 ||
call.arguments.length > 7 ||
call.arguments.some((a) => ts.isSpreadElement(a))
call.arguments.nodes.length < 1 ||
call.arguments.nodes.length > 7 ||
call.arguments.nodes.some((a) => ts.isSpreadElement(a))
) {
lowerer.noLowering(`Date.UTC with ${call.arguments.length} arguments`, call);
lowerer.noLowering(`Date.UTC with ${call.arguments.nodes.length} arguments`, call);
}
const defaults = [0, 0, 1, 0, 0, 0, 0]; // year is always present (arity ≥ 1)
const args: IrExpr[] = [];
for (let i = 0; i < 7; i++) {
const a = call.arguments[i];
const a = call.arguments.nodes[i];
args.push(
a !== undefined
? lowerer.lowerExprExpecting(a, F64)
@@ -91,7 +97,7 @@ export function lowerDateCall(
if (!lowerer.isStdlibMember(access)) return null;
const name = access.name.text;
if (name === "toJSON") return lowerDateToJson(lowerer, call, access);
if (call.arguments.length !== 0) {
if (call.arguments.nodes.length !== 0) {
lowerer.noLowering(`Date.prototype.${name} with arguments`, call, DATE_METHOD_HINT);
}
const raw = lowerer.lowerExpr(access.expression);
@@ -120,6 +126,10 @@ export function lowerDateCall(
if (fn !== undefined) {
return { kind: "libCall", fn, args: [receiver], type: F64, loc };
}
const textFn = DATE_TEXT_FNS.get(name);
if (textFn !== undefined) {
return { kind: "libCall", fn: textFn, args: [receiver], type: STRING, loc };
}
lowerer.noLowering(
`Date.prototype.${name}`,
call,
@@ -137,8 +147,8 @@ function lowerDateToJson(
access: ts.PropertyAccessExpression,
): IrExpr {
const loc = locOf(call);
if (call.arguments.length > 1 || call.arguments.some((a) => ts.isSpreadElement(a)))
lowerer.noLowering(`Date.prototype.toJSON with ${call.arguments.length} arguments`, call);
if (call.arguments.nodes.length > 1 || call.arguments.nodes.some((a) => ts.isSpreadElement(a)))
lowerer.noLowering(`Date.prototype.toJSON with ${call.arguments.nodes.length} arguments`, call);
const raw = lowerer.lowerExpr(access.expression);
const receiver: IrExpr =
raw.type.kind === "dyn"
@@ -148,7 +158,7 @@ function lowerDateToJson(
lowerer.badType(access.expression, lowerer.typeOf(access.expression));
const date = lowerer.declareHiddenLocal("%date", receiver.type);
const stmts: IrStmt[] = [{ kind: "varDecl", localId: date.id, init: receiver, loc }];
const key = call.arguments[0];
const key = call.arguments.nodes[0];
if (key) stmts.push({ kind: "exprStmt", expr: lowerer.lowerExpr(key), loc });
const value: IrExpr = { kind: "varRef", localId: date.id, type: receiver.type, loc };
const time: IrExpr = { kind: "libCall", fn: "date.getTime", args: [value], type: F64, loc };
@@ -22,7 +22,7 @@ export function lowerDisposableStackNew(lowerer: Lowerer, expr: ts.NewExpression
if (!ts.isIdentifier(expr.expression) || expr.expression.text !== "DisposableStack") return null;
const symbol = lowerer.resolveValueSymbol(expr.expression);
if (!symbol || !lowerer.isStdlibSymbol(symbol)) return null;
if ((expr.arguments ?? []).length !== 0) return null;
if ((expr.arguments?.nodes ?? []).length !== 0) return null;
return { kind: "libCall", fn: "disposable.stackNew", args: [], type: DYN, loc: locOf(expr) };
}
@@ -49,8 +49,8 @@ export function lowerDisposableStackUseCall(
access: ts.PropertyAccessExpression,
): IrExpr | null {
if (call.questionDotToken || access.questionDotToken) return null;
if (access.name.text !== "use" || call.arguments.length !== 1) return null;
const argNode = call.arguments[0]!;
if (access.name.text !== "use" || call.arguments.nodes.length !== 1) return null;
const argNode = call.arguments.nodes[0]!;
if (ts.isSpreadElement(argNode)) return null;
if (!isStdlibDisposableStack(lowerer, access.expression)) return null;
const valueT = lowerer.mapTypeOf(lowerer.typeOf(argNode));
@@ -78,12 +78,12 @@ export function lowerProcessEnvGet(
}
export function lowerProcessLoadEnvFile(lowerer: Lowerer, call: ts.CallExpression): IrExpr {
if (call.arguments.length > 1 || call.arguments.some(ts.isSpreadElement)) {
if (call.arguments.nodes.length > 1 || call.arguments.nodes.some(ts.isSpreadElement)) {
lowerer.noLowering("process.loadEnvFile with spread or extra arguments", call);
}
const loc = locOf(call);
const path: IrExpr = call.arguments[0]
? lowerer.lowerExprExpecting(call.arguments[0], DYN)
const path: IrExpr = call.arguments.nodes[0]
? lowerer.lowerExprExpecting(call.arguments.nodes[0], DYN)
: dynUndefinedExpr(loc);
const load: IrExpr = {
kind: "libCall",
@@ -19,9 +19,9 @@ export function isExecExceptionType(lowerer: Lowerer, t: ts.Type): boolean {
const sym = t.getSymbol();
return (
sym?.name === "ExecException" &&
lowerer.checker
.declarationsOf(sym)
.some((d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(d.getSourceFile()))
sym.declarations.some(
(d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(d.getSourceFile()),
)
);
}
@@ -209,8 +209,8 @@ export function lowerErrorIsErrorCall(
): IrExpr | null {
if (call.questionDotToken || access.questionDotToken) return null;
if (lowerer.stdlibGlobalMember(access, "Error") !== "isError") return null;
if (call.arguments.length !== 1) return null;
const argNode = call.arguments[0]!;
if (call.arguments.nodes.length !== 1) return null;
const argNode = call.arguments.nodes[0]!;
const loc = locOf(call);
const caughtLocal = lowerer.caughtLocalOf(argNode);
if (caughtLocal) {
@@ -38,12 +38,12 @@ export function lowerFsCallbackCall(
bi: { module: string; member: string },
loc: SrcLoc,
): IrExpr {
if (expr.arguments.some(ts.isSpreadElement))
if (expr.arguments.nodes.some(ts.isSpreadElement))
lowerer.noLowering("filesystem callback call with spread arguments", expr);
const timestamp = ["utimes", "futimes", "lutimes"].includes(bi.member);
const args: IrExpr = {
kind: "dynArrLit",
elems: expr.arguments.map((arg, index) =>
elems: expr.arguments.nodes.map((arg, index) =>
timestamp && (index === 1 || index === 2)
? lowerFsTimestampValue(lowerer, arg, loc)
: lowerer.lowerExprExpecting(arg, DYN),
@@ -71,11 +71,11 @@ export function lowerFsLinkOrStatfsCall(
const symbolic = bi.member === "symlinkSync" || bi.member === "symlink";
const reading = bi.member === "readlinkSync" || bi.member === "readlink";
const arity = symbolic ? 3 : 2;
if (expr.arguments.length > arity || expr.arguments.some(ts.isSpreadElement))
if (expr.arguments.nodes.length > arity || expr.arguments.nodes.some(ts.isSpreadElement))
lowerer.noLowering(`${name} with this argument shape`, expr);
const args = Array.from({ length: arity }, (_, index) =>
expr.arguments[index]
? lowerer.lowerExprExpecting(expr.arguments[index]!, DYN)
expr.arguments.nodes[index]
? lowerer.lowerExprExpecting(expr.arguments.nodes[index]!, DYN)
: dynUndefinedExpr(loc),
);
if (reading) {
@@ -117,11 +117,11 @@ export function lowerFsCpCall(
loc: SrcLoc,
name: string,
): IrExpr {
if (expr.arguments.length > 3 || expr.arguments.some(ts.isSpreadElement))
if (expr.arguments.nodes.length > 3 || expr.arguments.nodes.some(ts.isSpreadElement))
lowerer.noLowering(`${name} with this argument shape`, expr);
const args = [0, 1, 2].map((index) =>
expr.arguments[index]
? lowerer.lowerExprExpecting(expr.arguments[index]!, DYN)
expr.arguments.nodes[index]
? lowerer.lowerExprExpecting(expr.arguments.nodes[index]!, DYN)
: dynUndefinedExpr(loc),
);
return { kind: "libCall", fn: fn.fn, args, type: fn.result, loc };
@@ -134,18 +134,18 @@ export function lowerFsTimestampCall(
loc: SrcLoc,
name: string,
): IrExpr {
if (expr.arguments.length > 3 || expr.arguments.some(ts.isSpreadElement))
if (expr.arguments.nodes.length > 3 || expr.arguments.nodes.some(ts.isSpreadElement))
lowerer.noLowering(`${name} with this argument shape`, expr);
const path = expr.arguments[0]
? lowerer.lowerExprExpecting(expr.arguments[0], DYN)
const path = expr.arguments.nodes[0]
? lowerer.lowerExprExpecting(expr.arguments.nodes[0], DYN)
: dynUndefinedExpr(loc);
return {
kind: "libCall",
fn: fn.fn,
args: [
path,
lowerFsTimestampValue(lowerer, expr.arguments[1], loc),
lowerFsTimestampValue(lowerer, expr.arguments[2], loc),
lowerFsTimestampValue(lowerer, expr.arguments.nodes[1], loc),
lowerFsTimestampValue(lowerer, expr.arguments.nodes[2], loc),
],
type: fn.result,
loc,
@@ -163,14 +163,14 @@ export function lowerFsVectorOrTruncateCall(
const vector = bi.member !== "ftruncateSync";
const required = vector ? 2 : 1;
if (
expr.arguments.length < required ||
expr.arguments.length > required + 1 ||
expr.arguments.some(ts.isSpreadElement)
expr.arguments.nodes.length < required ||
expr.arguments.nodes.length > required + 1 ||
expr.arguments.nodes.some(ts.isSpreadElement)
)
lowerer.noLowering(`${name} with this argument shape`, expr);
const args = [lowerer.lowerExprExpecting(expr.arguments[0]!, F64)];
if (vector) args.push(lowerer.lowerExprExpecting(expr.arguments[1]!, arrayOf(BYTES_U8)));
const optional = expr.arguments[required];
const args = [lowerer.lowerExprExpecting(expr.arguments.nodes[0]!, F64)];
if (vector) args.push(lowerer.lowerExprExpecting(expr.arguments.nodes[1]!, arrayOf(BYTES_U8)));
const optional = expr.arguments.nodes[required];
args.push(
optional
? lowerBuiltinOptionalDefault(lowerer, optional, F64, numLit(vector ? -1 : 0, loc), vector)
@@ -187,7 +187,7 @@ export function lowerFsNumericOpenCall(
expr: ts.CallExpression,
loc: SrcLoc,
): IrExpr {
if (expr.arguments.length > 3 || expr.arguments.some(ts.isSpreadElement)) {
if (expr.arguments.nodes.length > 3 || expr.arguments.nodes.some(ts.isSpreadElement)) {
lowerer.noLowering("openSync with numeric flags and this argument shape", expr);
}
const bits: Record<string, number | undefined> = {
@@ -223,17 +223,17 @@ export function lowerFsNumericOpenCall(
}
return null;
};
const mask = flagsOf(expr.arguments[1]!);
const mask = flagsOf(expr.arguments.nodes[1]!);
if (mask === null) {
lowerer.noLowering(
"openSync with computed numeric flags",
expr.arguments[1]!,
expr.arguments.nodes[1]!,
"use an inline bitwise OR of fs.constants.O_RDONLY/O_WRONLY/O_RDWR/O_CREAT/O_EXCL/O_NOFOLLOW/O_NONBLOCK/O_TRUNC/O_APPEND",
);
}
const path = lowerer.lowerExprExpecting(expr.arguments[0]!, STRING);
const mode = expr.arguments[2]
? lowerer.lowerExprExpecting(expr.arguments[2]!, F64)
const path = lowerer.lowerExprExpecting(expr.arguments.nodes[0]!, STRING);
const mode = expr.arguments.nodes[2]
? lowerer.lowerExprExpecting(expr.arguments.nodes[2]!, F64)
: ({ kind: "numLit", value: 0o666, type: F64, loc } satisfies IrExpr);
return {
kind: "libCall",
@@ -254,24 +254,24 @@ export function lowerFsPromisesOpenCall(
loc: SrcLoc,
): IrExpr {
if (
expr.arguments.length < 1 ||
expr.arguments.length > 3 ||
expr.arguments.some(ts.isSpreadElement)
expr.arguments.nodes.length < 1 ||
expr.arguments.nodes.length > 3 ||
expr.arguments.nodes.some(ts.isSpreadElement)
) {
lowerer.noLowering(
`fs.promises.open with ${expr.arguments.length} arguments`,
`fs.promises.open with ${expr.arguments.nodes.length} arguments`,
expr,
"use open(path[, stringFlags[, numericMode]])",
);
}
const path = lowerer.lowerExprExpecting(expr.arguments[0]!, STRING);
const path = lowerer.lowerExprExpecting(expr.arguments.nodes[0]!, STRING);
const defaultFlags: IrExpr = { kind: "strLit", value: "r", type: STRING, loc };
const defaultMode: IrExpr = { kind: "numLit", value: 0o666, type: F64, loc };
const flags = expr.arguments[1]
? lowerBuiltinOptionalDefault(lowerer, expr.arguments[1]!, STRING, defaultFlags)
const flags = expr.arguments.nodes[1]
? lowerBuiltinOptionalDefault(lowerer, expr.arguments.nodes[1]!, STRING, defaultFlags)
: defaultFlags;
const mode = expr.arguments[2]
? lowerBuiltinOptionalDefault(lowerer, expr.arguments[2]!, F64, defaultMode)
const mode = expr.arguments.nodes[2]
? lowerBuiltinOptionalDefault(lowerer, expr.arguments.nodes[2]!, F64, defaultMode)
: defaultMode;
return {
kind: "libCall",
@@ -292,16 +292,16 @@ export function lowerFsRenameCallbackCall(
expr: ts.CallExpression,
loc: SrcLoc,
): IrExpr {
if (expr.arguments.length !== 3 || expr.arguments.some(ts.isSpreadElement)) {
if (expr.arguments.nodes.length !== 3 || expr.arguments.nodes.some(ts.isSpreadElement)) {
lowerer.noLowering(
`rename with ${expr.arguments.length} argument${expr.arguments.length === 1 ? "" : "s"}`,
`rename with ${expr.arguments.nodes.length} argument${expr.arguments.nodes.length === 1 ? "" : "s"}`,
expr,
"the supported form is rename(oldPath, newPath, callback)",
);
}
const oldPath = lowerer.lowerExprExpecting(expr.arguments[0]!, STRING);
const newPath = lowerer.lowerExprExpecting(expr.arguments[1]!, STRING);
let callback = lowerer.lowerExpr(expr.arguments[2]!);
const oldPath = lowerer.lowerExprExpecting(expr.arguments.nodes[0]!, STRING);
const newPath = lowerer.lowerExprExpecting(expr.arguments.nodes[1]!, STRING);
let callback = lowerer.lowerExpr(expr.arguments.nodes[2]!);
// JS/checkJs callback values may arrive as checked-dynamic callables.
// Adapt them to the one error-first slot; the runtime passes a dyn
// Error on failure and null on success through the emitted thunk.
@@ -310,7 +310,7 @@ export function lowerFsRenameCallbackCall(
kind: "dynCheck",
value: callback,
type: funcOf([DYN], VOID),
loc: locOf(expr.arguments[2]!),
loc: locOf(expr.arguments.nodes[2]!),
};
}
let callbackOk = callback.type.kind === "func" && callback.type.params.length <= 1;
@@ -337,7 +337,7 @@ export function lowerFsRenameCallbackCall(
if (!callbackOk) {
lowerer.unsupported(
"SC1090",
expr.arguments[2]!,
expr.arguments.nodes[2]!,
"fs.rename callbacks must accept at most one Error | null parameter",
);
}
@@ -345,7 +345,7 @@ export function lowerFsRenameCallbackCall(
// void callback is expected; Node ignores that value. Normalize the
// closure to the runtime's void callback ABI after validating its
// error-first parameter shape.
callback = voidizedCallback(lowerer, callback, locOf(expr.arguments[2]!));
callback = voidizedCallback(lowerer, callback, locOf(expr.arguments.nodes[2]!));
return {
kind: "libCall",
fn: "fs.renameCb",
@@ -366,17 +366,17 @@ export function lowerFsReadSyncCall(
const supported =
"use readSync(fd, buffer), readSync(fd, buffer, { offset?, length?, position? }), or readSync(fd, buffer, offset, length[, position]); bigint positions and non-literal options objects have no lowering";
if (
expr.arguments.length < 2 ||
expr.arguments.length > 5 ||
expr.arguments.some(ts.isSpreadElement)
expr.arguments.nodes.length < 2 ||
expr.arguments.nodes.length > 5 ||
expr.arguments.nodes.some(ts.isSpreadElement)
) {
lowerer.noLowering(
`readSync with ${expr.arguments.length} argument${expr.arguments.length === 1 ? "" : "s"}`,
`readSync with ${expr.arguments.nodes.length} argument${expr.arguments.nodes.length === 1 ? "" : "s"}`,
expr,
supported,
);
}
if (expr.arguments.length === 2) {
if (expr.arguments.nodes.length === 2) {
return lowerFsSyncBufferWindow(
lowerer,
expr,
@@ -386,8 +386,8 @@ export function lowerFsReadSyncCall(
supported,
);
}
if (expr.arguments.length === 3) {
const optionsNode = expr.arguments[2]!;
if (expr.arguments.nodes.length === 3) {
const optionsNode = expr.arguments.nodes[2]!;
let valueNode = optionsNode;
while (ts.isParenthesizedExpression(valueNode)) valueNode = valueNode.expression;
if (ts.isObjectLiteralExpression(valueNode) || valueNode.kind === ts.SyntaxKind.NullKeyword) {
@@ -403,12 +403,12 @@ export function lowerFsReadSyncCall(
lowerer.noLowering("readSync with a non-literal options argument", optionsNode, supported);
}
const args: IrExpr[] = [
lowerer.lowerExprExpecting(expr.arguments[0]!, F64),
lowerer.lowerExprExpecting(expr.arguments[1]!, BYTES_U8),
lowerer.lowerExprExpecting(expr.arguments[2]!, F64),
lowerer.lowerExprExpecting(expr.arguments[3]!, F64),
lowerer.lowerExprExpecting(expr.arguments.nodes[0]!, F64),
lowerer.lowerExprExpecting(expr.arguments.nodes[1]!, BYTES_U8),
lowerer.lowerExprExpecting(expr.arguments.nodes[2]!, F64),
lowerer.lowerExprExpecting(expr.arguments.nodes[3]!, F64),
];
const positionNode = expr.arguments[4];
const positionNode = expr.arguments.nodes[4];
let positionValueNode = positionNode;
while (positionValueNode && ts.isParenthesizedExpression(positionValueNode)) {
positionValueNode = positionValueNode.expression;
@@ -441,14 +441,14 @@ export function lowerFsWriteSyncCall(
): IrExpr {
const supported =
'use writeSync(fd, buffer[, offset[, length[, position]]]), writeSync(fd, buffer, { offset?, length?, position? }), or writeSync(fd, string[, position[, "utf8"]]); non-literal options objects, bigint positions, and other encodings have no lowering';
if (expr.arguments.some(ts.isSpreadElement) || expr.arguments.length < 2) {
if (expr.arguments.nodes.some(ts.isSpreadElement) || expr.arguments.nodes.length < 2) {
lowerer.noLowering(
`writeSync with ${expr.arguments.length} argument${expr.arguments.length === 1 ? "" : "s"}`,
`writeSync with ${expr.arguments.nodes.length} argument${expr.arguments.nodes.length === 1 ? "" : "s"}`,
expr,
supported,
);
}
const dataNode = expr.arguments[1]!;
const dataNode = expr.arguments.nodes[1]!;
const dataType = lowerer.mapTypeOf(lowerer.typeOf(dataNode));
const position = (node: ts.Expression | undefined): IrExpr => {
let valueNode = node;
@@ -474,7 +474,7 @@ export function lowerFsWriteSyncCall(
"byte writes take Uint8Array/Buffer data",
);
}
if (expr.arguments.length === 2) {
if (expr.arguments.nodes.length === 2) {
return lowerFsSyncBufferWindow(
lowerer,
expr,
@@ -484,8 +484,8 @@ export function lowerFsWriteSyncCall(
supported,
);
}
if (expr.arguments.length === 3) {
const thirdNode = expr.arguments[2]!;
if (expr.arguments.nodes.length === 3) {
const thirdNode = expr.arguments.nodes[2]!;
let valueNode = thirdNode;
while (ts.isParenthesizedExpression(valueNode)) valueNode = valueNode.expression;
if (ts.isObjectLiteralExpression(valueNode) || valueNode.kind === ts.SyntaxKind.NullKeyword) {
@@ -514,37 +514,37 @@ export function lowerFsWriteSyncCall(
supported,
);
}
if (expr.arguments.length !== 4 && expr.arguments.length !== 5) {
if (expr.arguments.nodes.length !== 4 && expr.arguments.nodes.length !== 5) {
lowerer.noLowering(
`writeSync of Buffer data with ${expr.arguments.length} arguments`,
`writeSync of Buffer data with ${expr.arguments.nodes.length} arguments`,
expr,
supported,
);
}
const fd = lowerer.lowerExprExpecting(expr.arguments[0]!, F64);
const fd = lowerer.lowerExprExpecting(expr.arguments.nodes[0]!, F64);
return {
kind: "libCall",
fn: "fs.writeSync",
args: [
fd,
lowerer.lowerExprExpecting(dataNode, BYTES_U8),
lowerer.lowerExprExpecting(expr.arguments[2]!, F64),
lowerer.lowerExprExpecting(expr.arguments[3]!, F64),
position(expr.arguments[4]),
lowerer.lowerExprExpecting(expr.arguments.nodes[2]!, F64),
lowerer.lowerExprExpecting(expr.arguments.nodes[3]!, F64),
position(expr.arguments.nodes[4]),
],
type: F64,
loc,
};
}
if (dataType?.kind === "string") {
if (expr.arguments.length > 4) {
if (expr.arguments.nodes.length > 4) {
lowerer.noLowering(
`writeSync of string data with ${expr.arguments.length} arguments`,
`writeSync of string data with ${expr.arguments.nodes.length} arguments`,
expr,
supported,
);
}
const encNode = expr.arguments[3];
const encNode = expr.arguments.nodes[3];
let enc: IrExpr = { kind: "strLit", value: "utf8", type: STRING, loc };
if (encNode !== undefined) {
const encType = lowerer.typeOf(encNode);
@@ -559,11 +559,16 @@ export function lowerFsWriteSyncCall(
// a call/getter whose type is the accepted literal cannot vanish.
enc = lowerer.lowerExprExpecting(encNode, STRING);
}
const fd = lowerer.lowerExprExpecting(expr.arguments[0]!, F64);
const fd = lowerer.lowerExprExpecting(expr.arguments.nodes[0]!, F64);
return {
kind: "libCall",
fn: "fs.writeStrSync",
args: [fd, lowerer.lowerExprExpecting(dataNode, STRING), position(expr.arguments[2]), enc],
args: [
fd,
lowerer.lowerExprExpecting(dataNode, STRING),
position(expr.arguments.nodes[2]),
enc,
],
type: F64,
loc,
};
@@ -585,22 +590,22 @@ export function lowerFsReadDescriptorCall(
expr: ts.CallExpression,
loc: SrcLoc,
): IrExpr {
const fd = lowerer.lowerExprExpecting(expr.arguments[0]!, F64);
if (expr.arguments.length === 1) {
const fd = lowerer.lowerExprExpecting(expr.arguments.nodes[0]!, F64);
if (expr.arguments.nodes.length === 1) {
return { kind: "libCall", fn: "fs.readFdSyncBytes", args: [fd], type: BYTES_U8, loc };
}
const encT = lowerer.typeOf(expr.arguments[1]!);
const encT = lowerer.typeOf(expr.arguments.nodes[1]!);
if (
expr.arguments.length !== 2 ||
expr.arguments.nodes.length !== 2 ||
!(encT.isStringLiteralType() && (encT.value === "utf8" || encT.value === "utf-8"))
) {
lowerer.noLowering(
`readFileSync(fd) with a non-"utf8" encoding`,
expr.arguments[1] ?? expr,
expr.arguments.nodes[1] ?? expr,
'only utf8 reads are supported: readFileSync(fd, "utf8")',
);
}
const enc = lowerer.lowerExprExpecting(expr.arguments[1]!, STRING);
const enc = lowerer.lowerExprExpecting(expr.arguments.nodes[1]!, STRING);
return { kind: "libCall", fn: "fs.readFdSync", args: [fd, enc], type: STRING, loc };
}
@@ -620,8 +625,9 @@ export function lowerFsEncodedRead(
descriptor: boolean,
loc: SrcLoc,
): IrExpr | null {
if (expr.arguments.length !== 2 || expr.arguments.some(ts.isSpreadElement)) return null;
const options = expr.arguments[1]!;
if (expr.arguments.nodes.length !== 2 || expr.arguments.nodes.some(ts.isSpreadElement))
return null;
const options = expr.arguments.nodes[1]!;
const optionsType = lowerer.mapTypeOf(lowerer.typeOf(options));
if (optionsType?.kind !== "string" && optionsType?.kind !== "record") return null;
if (optionsType.kind === "record") {
@@ -634,7 +640,7 @@ export function lowerFsEncodedRead(
return null;
}
const sourceType = descriptor ? F64 : STRING;
const source = lowerer.lowerExprExpecting(expr.arguments[0]!, sourceType);
const source = lowerer.lowerExprExpecting(expr.arguments.nodes[0]!, sourceType);
const encoding: IrExpr =
optionsType.kind === "string"
? lowerer.lowerExprExpecting(options, STRING)
@@ -674,10 +680,11 @@ export function lowerFsPromisesEncodedRead(
expr: ts.CallExpression,
loc: SrcLoc,
): IrExpr | null {
if (expr.arguments.length !== 2 || expr.arguments.some(ts.isSpreadElement)) return null;
if (expr.arguments.nodes.length !== 2 || expr.arguments.nodes.some(ts.isSpreadElement))
return null;
const result = lowerer.mapTypeOf(lowerer.typeOf(expr));
if (result?.kind !== "promise" || result.inner.kind !== "string") return null;
const options = expr.arguments[1]!;
const options = expr.arguments.nodes[1]!;
const optionsTsType = lowerer.typeOf(options);
if (
optionsTsType.isStringLiteralType() &&
@@ -695,7 +702,7 @@ export function lowerFsPromisesEncodedRead(
)
return null;
}
const path = lowerer.lowerExprExpecting(expr.arguments[0]!, STRING);
const path = lowerer.lowerExprExpecting(expr.arguments.nodes[0]!, STRING);
const encoding: IrExpr =
optionsType.kind === "string"
? lowerer.lowerExprExpecting(options, STRING)
@@ -729,15 +736,15 @@ export function lowerFsOpendirCall(
loc: SrcLoc,
): IrExpr {
if (
expr.arguments.length < 1 ||
expr.arguments.length > 2 ||
expr.arguments.some(ts.isSpreadElement)
expr.arguments.nodes.length < 1 ||
expr.arguments.nodes.length > 2 ||
expr.arguments.nodes.some(ts.isSpreadElement)
) {
lowerer.noLowering(`${member} with ${expr.arguments.length} arguments`, expr);
lowerer.noLowering(`${member} with ${expr.arguments.nodes.length} arguments`, expr);
}
const path = lowerer.lowerExprExpecting(expr.arguments[0]!, STRING);
const path = lowerer.lowerExprExpecting(expr.arguments.nodes[0]!, STRING);
let bufferSize: IrExpr = { kind: "numLit", value: 32, type: F64, loc };
const optsNode = expr.arguments[1];
const optsNode = expr.arguments.nodes[1];
if (optsNode && !(ts.isIdentifier(optsNode) && optsNode.text === "undefined")) {
const fence = (): never =>
lowerer.noLowering(
@@ -746,7 +753,7 @@ export function lowerFsOpendirCall(
"pass an object literal with a numeric bufferSize and a utf8 encoding; recursive walks and other encodings have no lowering",
);
if (!ts.isObjectLiteralExpression(optsNode)) fence();
for (const property of (optsNode as ts.ObjectLiteralExpression).properties) {
for (const property of (optsNode as ts.ObjectLiteralExpression).properties.nodes) {
const option = optionMember(property);
if (!option) fence();
else if (option.name === "bufferSize")
@@ -775,7 +782,7 @@ export function lowerFsStreamCreate(
): IrExpr {
return fsStreamCreateExpr(
lowerer,
expr.arguments,
expr.arguments.nodes,
member === "createReadStream",
`fs.${member}`,
expr,
@@ -795,7 +802,7 @@ export function lowerFsMkdirSyncCall(
expr: ts.CallExpression,
loc: SrcLoc,
): IrExpr {
const optsNode = expr.arguments[1]!;
const optsNode = expr.arguments.nodes[1]!;
const options = parseMkdirOptions(optsNode);
if (!options) {
lowerer.noLowering(
@@ -805,7 +812,7 @@ export function lowerFsMkdirSyncCall(
);
}
const { recursive, modeNode } = options;
const path = lowerer.lowerExprExpecting(expr.arguments[0]!, STRING);
const path = lowerer.lowerExprExpecting(expr.arguments.nodes[0]!, STRING);
const mode: IrExpr | null = modeNode ? lowerer.lowerExprExpecting(modeNode, F64) : null;
if (!recursive) {
return mode
@@ -833,7 +840,7 @@ export function lowerFsPromisesMkdirCall(
expr: ts.CallExpression,
loc: SrcLoc,
): IrExpr {
const optsNode = expr.arguments[1]!;
const optsNode = expr.arguments.nodes[1]!;
const options = parseMkdirOptions(optsNode);
if (!options) {
lowerer.noLowering(
@@ -843,7 +850,7 @@ export function lowerFsPromisesMkdirCall(
);
}
const { recursive, modeNode } = options;
const path = lowerer.lowerExprExpecting(expr.arguments[0]!, STRING);
const path = lowerer.lowerExprExpecting(expr.arguments.nodes[0]!, STRING);
const mode: IrExpr | null = modeNode ? lowerer.lowerExprExpecting(modeNode, F64) : null;
const type: IrType = { kind: "promise", inner: VOID };
if (!recursive) {
@@ -866,18 +873,18 @@ export function lowerFsPromisesMkdirCall(
// historical rmOptsSync lowering unchanged.
export function lowerFsRemoveCall(lowerer: Lowerer, expr: ts.CallExpression, loc: SrcLoc): IrExpr {
const opts = literalBoolOptions(
expr.arguments[1]!,
expr.arguments.nodes[1]!,
["recursive", "force"],
["maxRetries", "retryDelay"],
);
if (opts === null) {
lowerer.noLowering(
"rmSync with an options argument beyond { recursive, force, maxRetries, retryDelay }",
expr.arguments[1]!,
expr.arguments.nodes[1]!,
"the recursive/force flags must be boolean literals; maxRetries/retryDelay are numbers; other options have no lowering",
);
}
const path = lowerer.lowerExprExpecting(expr.arguments[0]!, STRING);
const path = lowerer.lowerExprExpecting(expr.arguments.nodes[0]!, STRING);
const recursive = boolLit(opts.bools["recursive"] === true, loc);
const force = boolLit(opts.bools["force"] === true, loc);
@@ -916,7 +923,7 @@ export function lowerFsWriteBytesCall(
bi: { module: string; member: string },
loc: SrcLoc,
): IrExpr | null {
const dataIr = lowerer.mapTypeOf(lowerer.typeOf(expr.arguments[1]!));
const dataIr = lowerer.mapTypeOf(lowerer.typeOf(expr.arguments.nodes[1]!));
if (dataIr?.kind === "union") {
const arms = lowerer.unions.get(dataIr.unionId)?.arms;
if (
@@ -930,12 +937,12 @@ export function lowerFsWriteBytesCall(
if (dataIr.elem !== "u8") {
lowerer.noLowering(
`${bi.member} of '${lowerer.fmt(dataIr)}' data`,
expr.arguments[1]!,
expr.arguments.nodes[1]!,
"byte writes take Uint8Array/Buffer data",
);
}
const path = lowerer.lowerExprExpecting(expr.arguments[0]!, STRING);
const data = lowerer.lowerExprExpecting(expr.arguments[1]!, BYTES_U8);
const path = lowerer.lowerExprExpecting(expr.arguments.nodes[0]!, STRING);
const data = lowerer.lowerExprExpecting(expr.arguments.nodes[1]!, BYTES_U8);
if (bi.module === "fs/promises") {
return {
kind: "libCall",
@@ -963,7 +970,7 @@ export function lowerFsWriteOptionsCall(
loc: SrcLoc,
promiseWriteOptions: boolean,
): IrExpr {
const optsNode = expr.arguments[2]!;
const optsNode = expr.arguments.nodes[2]!;
const operation = promiseWriteOptions ? "fs.promises.writeFile" : "writeFileSync";
const plainFn: IrLibFn = promiseWriteOptions ? "fsp.writeFile" : "fs.writeFileSync";
const modeFn: IrLibFn = promiseWriteOptions ? "fsp.writeFileMode" : "fs.writeFileModeSync";
@@ -974,8 +981,8 @@ export function lowerFsWriteOptionsCall(
// values in object-literal order before issuing the write; otherwise a
// call/getter statically typed as the accepted utf8 literal can vanish.
const finishWrite = (optionValues: WriteOptionValue[]): IrExpr => {
const path = lowerer.lowerExprExpecting(expr.arguments[0]!, STRING);
const data = lowerer.lowerExprExpecting(expr.arguments[1]!, STRING);
const path = lowerer.lowerExprExpecting(expr.arguments.nodes[0]!, STRING);
const data = lowerer.lowerExprExpecting(expr.arguments.nodes[1]!, STRING);
const pathLocal = lowerer.declareHiddenLocal("%writePath", STRING);
const dataLocal = lowerer.declareHiddenLocal("%writeData", STRING);
const stmts: IrStmt[] = [
@@ -1021,7 +1028,7 @@ export function lowerFsWriteOptionsCall(
if (
t.isStringLiteralType() &&
(t.value === "utf8" || t.value === "utf-8") &&
lowerer.mapTypeOf(lowerer.typeOf(expr.arguments[1]!))?.kind === "string"
lowerer.mapTypeOf(lowerer.typeOf(expr.arguments.nodes[1]!))?.kind === "string"
) {
return finishWrite([{ kind: "effect", value: lowerer.lowerExprExpecting(optsNode, STRING) }]);
}
@@ -1029,7 +1036,7 @@ export function lowerFsWriteOptionsCall(
const optionValues: WriteOptionValue[] = [];
let ok = ts.isObjectLiteralExpression(optsNode);
if (ok) {
for (const p of (optsNode as ts.ObjectLiteralExpression).properties) {
for (const p of (optsNode as ts.ObjectLiteralExpression).properties.nodes) {
const m = optionMember(p);
if (!m) {
ok = false;
@@ -1066,7 +1073,7 @@ export function lowerFsWriteOptionsCall(
}
}
}
if (!ok || lowerer.mapTypeOf(lowerer.typeOf(expr.arguments[1]!))?.kind !== "string") {
if (!ok || lowerer.mapTypeOf(lowerer.typeOf(expr.arguments.nodes[1]!))?.kind !== "string") {
lowerer.noLowering(
`${operation} with 3 arguments`,
optsNode,
@@ -1089,20 +1096,20 @@ export function lowerFsReadPathCall(
// non-string paths throw ERR_INVALID_ARG_TYPE where the dynCheck
// throws its path-annotated TypeError. The fd forms (a number
// argument) and literal-utf8 reads keep their existing lowerings.
const pathV = lowerer.lowerExpr(expr.arguments[0]!);
const pathV = lowerer.lowerExpr(expr.arguments.nodes[0]!);
if (pathV.type.kind === "string" || pathV.type.kind === "dyn") {
const pathArg: IrExpr =
pathV.type.kind === "dyn" ? { kind: "dynCheck", value: pathV, type: STRING, loc } : pathV;
if (expr.arguments.length === 1) {
if (expr.arguments.nodes.length === 1) {
return { kind: "libCall", fn: "fs.readFileSyncBuf", args: [pathArg], type: BYTES_U8, loc };
}
// Runtime nullish and empty encodings read Buffers, supported names
// read strings, and invalid names throw ERR_INVALID_ARG_VALUE. A
// non-dyn encoding falls through to the literal-utf8 lowering
// below (the discarded probe IR never emits).
const encT = lowerer.typeOf(expr.arguments[1]!);
const encT = lowerer.typeOf(expr.arguments.nodes[1]!);
if (!(encT.isStringLiteralType() && (encT.value === "utf8" || encT.value === "utf-8"))) {
const enc = lowerer.lowerExpr(expr.arguments[1]!);
const enc = lowerer.lowerExpr(expr.arguments.nodes[1]!);
if (enc.type.kind === "dyn") {
return {
kind: "libCall",
@@ -1125,7 +1132,7 @@ function parseMkdirOptions(
if (!ts.isObjectLiteralExpression(node)) return null;
let recursive = false;
let modeNode: ts.Expression | null = null;
for (const property of node.properties) {
for (const property of node.properties.nodes) {
const member = optionMember(property);
if (!member) return null;
if (member.name === "recursive") {
@@ -48,8 +48,8 @@ export function lowerFsSyncBufferWindow(
source: FsSyncBufferWindowSource,
supported: string,
): IrExpr {
const fdValue = lowerer.lowerExprExpecting(expr.arguments[0]!, F64);
const bufferValue = lowerer.lowerExprExpecting(expr.arguments[1]!, BYTES_U8);
const fdValue = lowerer.lowerExprExpecting(expr.arguments.nodes[0]!, F64);
const bufferValue = lowerer.lowerExprExpecting(expr.arguments.nodes[1]!, BYTES_U8);
const fdLocal = lowerer.declareHiddenLocal("%fsSyncFd", F64);
const bufferLocal = lowerer.declareHiddenLocal("%fsSyncBuffer", BYTES_U8);
const stmts: IrStmt[] = [
@@ -88,7 +88,7 @@ export function lowerFsSyncBufferWindow(
if (!ts.isObjectLiteralExpression(source.node)) {
lowerer.noLowering(`${fn} with a non-literal options argument`, source.node, supported);
}
for (const property of (source.node as ts.ObjectLiteralExpression).properties) {
for (const property of (source.node as ts.ObjectLiteralExpression).properties.nodes) {
const member = optionMember(property);
if (member === null) {
lowerer.noLowering(`${fn} with a non-plain options member`, property, supported);
@@ -189,11 +189,12 @@ export function lowerFsToUnixTimestampCall(
loc: SrcLoc,
): IrExpr | null {
if (bi.module !== "fs" || bi.member !== "_toUnixTimestamp") return null;
if (expr.arguments.length !== 1 || ts.isSpreadElement(expr.arguments[0]!)) return null;
if (expr.arguments.nodes.length !== 1 || ts.isSpreadElement(expr.arguments.nodes[0]!))
return null;
return {
kind: "libCall",
fn: "fs.toUnixTimestamp",
args: [lowerFsTimestampValue(lowerer, expr.arguments[0], loc)],
args: [lowerFsTimestampValue(lowerer, expr.arguments.nodes[0], loc)],
type: F64,
loc,
};
@@ -208,7 +209,7 @@ function opendirLowers(lowerer: Lowerer, args: readonly ts.Expression[]): boolea
const options = args[1];
if (!options || (ts.isIdentifier(options) && options.text === "undefined")) return true;
if (!ts.isObjectLiteralExpression(options)) return false;
return options.properties.every((property) => {
return options.properties.nodes.every((property) => {
const option = optionMember(property);
if (!option) return false;
if (option.name === "bufferSize")
@@ -250,7 +251,7 @@ export function lowerFsLadderCall(
}
if (bi.module !== "fs" && bi.module !== "fs/promises") return null;
if (!isJsSourceFile(expr.getSourceFile())) return null;
const args = expr.arguments;
const args = expr.arguments.nodes;
if (args.some(ts.isSpreadElement)) return null;
// Every ladder argument crosses as a dyn value; an argument that
// cannot leaves the historical fence in place.
@@ -304,12 +305,12 @@ export function lowerFsLadderCall(
ts.isIdentifier(pathNode.expression) &&
pathNode.expression.text === "URL" &&
lowerer.mapTypeOf(lowerer.typeOf(pathNode))?.kind === "url" &&
pathNode.arguments?.length === 1 &&
ts.isStringLiteral(pathNode.arguments[0]!)
pathNode.arguments?.nodes.length === 1 &&
ts.isStringLiteral(pathNode.arguments.nodes[0]!)
) {
let parsed: URL | null = null;
try {
parsed = new URL(pathNode.arguments[0]!.text);
parsed = new URL(pathNode.arguments.nodes[0]!.text);
} catch {
parsed = null;
}
@@ -512,8 +513,8 @@ export function lowerStringOrBytesWrite(
kind: "call",
callee: helper,
args: [
lowerer.lowerExprExpecting(call.arguments[0]!, STRING),
lowerer.lowerExprExpecting(call.arguments[1]!, dataType),
lowerer.lowerExprExpecting(call.arguments.nodes[0]!, STRING),
lowerer.lowerExprExpecting(call.arguments.nodes[1]!, dataType),
],
type: VOID,
loc,
@@ -648,7 +649,8 @@ export function lowerFileHandleMethodCall(
};
if (name === "sync" || name === "datasync") {
if (call.arguments.length !== 0) lowerer.noLowering(`FileHandle.${name} with arguments`, call);
if (call.arguments.nodes.length !== 0)
lowerer.noLowering(`FileHandle.${name} with arguments`, call);
return {
kind: "libCall",
fn: name === "sync" ? "fileHandle.sync" : "fileHandle.datasync",
@@ -658,34 +660,34 @@ export function lowerFileHandleMethodCall(
};
}
if (name === "truncate" || name === "chmod") {
if (call.arguments.length > 1 || (name === "chmod" && call.arguments.length !== 1))
lowerer.noLowering(`FileHandle.${name} with ${call.arguments.length} arguments`, call);
if (call.arguments.nodes.length > 1 || (name === "chmod" && call.arguments.nodes.length !== 1))
lowerer.noLowering(`FileHandle.${name} with ${call.arguments.nodes.length} arguments`, call);
return {
kind: "libCall",
fn: name === "truncate" ? "fileHandle.truncate" : "fileHandle.chmod",
args: [receiver(), num(call.arguments[0], 0).value],
args: [receiver(), num(call.arguments.nodes[0], 0).value],
type: promise(VOID),
loc,
};
}
if (name === "utimes") {
if (call.arguments.length !== 2 || call.arguments.some(ts.isSpreadElement))
if (call.arguments.nodes.length !== 2 || call.arguments.nodes.some(ts.isSpreadElement))
lowerer.noLowering("FileHandle.utimes with this argument shape", call);
return {
kind: "libCall",
fn: "fileHandle.utimes",
args: [
receiver(),
lowerFsTimestampValue(lowerer, call.arguments[0], loc),
lowerFsTimestampValue(lowerer, call.arguments[1], loc),
lowerFsTimestampValue(lowerer, call.arguments.nodes[0], loc),
lowerFsTimestampValue(lowerer, call.arguments.nodes[1], loc),
],
type: promise(VOID),
loc,
};
}
if (name === "readv" || name === "writev") {
if (call.arguments.length < 1 || call.arguments.length > 2)
lowerer.noLowering(`FileHandle.${name} with ${call.arguments.length} arguments`, call);
if (call.arguments.nodes.length < 1 || call.arguments.nodes.length > 2)
lowerer.noLowering(`FileHandle.${name} with ${call.arguments.nodes.length} arguments`, call);
const type = lowerer.mapTypeOf(lowerer.typeOf(call));
if (type?.kind !== "promise" || type.inner.kind !== "record")
lowerer.badType(call, lowerer.typeOf(call));
@@ -694,15 +696,16 @@ export function lowerFileHandleMethodCall(
fn: name === "readv" ? "fileHandle.readv" : "fileHandle.writev",
args: [
receiver(),
lowerer.lowerExprExpecting(call.arguments[0]!, arrayOf(BYTES_U8)),
num(call.arguments[1], -1).value,
lowerer.lowerExprExpecting(call.arguments.nodes[0]!, arrayOf(BYTES_U8)),
num(call.arguments.nodes[1], -1).value,
],
type,
loc,
};
}
if (name === "close" || name === "stat") {
if (call.arguments.length !== 0) lowerer.noLowering(`FileHandle.${name} with arguments`, call);
if (call.arguments.nodes.length !== 0)
lowerer.noLowering(`FileHandle.${name} with arguments`, call);
return {
kind: "libCall",
fn: name === "close" ? "fileHandle.close" : "fileHandle.stat",
@@ -713,12 +716,12 @@ export function lowerFileHandleMethodCall(
}
if (name === "readFile") {
if (call.arguments.length > 1) {
lowerer.noLowering(`FileHandle.readFile with ${call.arguments.length} arguments`, call);
if (call.arguments.nodes.length > 1) {
lowerer.noLowering(`FileHandle.readFile with ${call.arguments.nodes.length} arguments`, call);
}
const type = lowerer.mapTypeOf(lowerer.typeOf(call));
if (type?.kind !== "promise") lowerer.badType(call, lowerer.typeOf(call));
const encoding = utf8(call.arguments[0]);
const encoding = utf8(call.arguments.nodes[0]);
if (typeEquals(type.inner, BYTES_U8)) {
return {
kind: "libCall",
@@ -739,14 +742,14 @@ export function lowerFileHandleMethodCall(
}
if (name === "writeFile" || name === "appendFile") {
if (call.arguments.length < 1 || call.arguments.length > 2) {
lowerer.noLowering(`FileHandle.${name} with ${call.arguments.length} arguments`, call);
if (call.arguments.nodes.length < 1 || call.arguments.nodes.length > 2) {
lowerer.noLowering(`FileHandle.${name} with ${call.arguments.nodes.length} arguments`, call);
}
const dataNode = call.arguments[0]!;
const dataNode = call.arguments.nodes[0]!;
const dataT = lowerer.mapTypeOf(lowerer.typeOf(dataNode));
// Evaluate a supplied utf8 encoding even for Buffer data, matching
// Node's argument order; it does not affect the bytes.
const encoding = utf8(call.arguments[1]);
const encoding = utf8(call.arguments.nodes[1]);
if (dataT?.kind === "string") {
return {
kind: "libCall",
@@ -773,10 +776,10 @@ export function lowerFileHandleMethodCall(
}
if (name === "read" || name === "write") {
const firstNode = call.arguments[0];
const firstNode = call.arguments.nodes[0];
const firstT = firstNode ? lowerer.mapTypeOf(lowerer.typeOf(firstNode)) : undefined;
const secondT = call.arguments[1]
? lowerer.mapTypeOf(lowerer.typeOf(call.arguments[1]!))
const secondT = call.arguments.nodes[1]
? lowerer.mapTypeOf(lowerer.typeOf(call.arguments.nodes[1]!))
: undefined;
const readOptions =
name === "read" &&
@@ -784,7 +787,7 @@ export function lowerFileHandleMethodCall(
const bufferOptions =
firstT?.kind === "bytes" && firstT.elem === "u8" && secondT?.kind === "record";
if (readOptions || bufferOptions) {
if (call.arguments.length > (bufferOptions ? 2 : 1))
if (call.arguments.nodes.length > (bufferOptions ? 2 : 1))
lowerer.noLowering(`FileHandle.${name} options with extra arguments`, call);
const type = lowerer.mapTypeOf(lowerer.typeOf(call));
if (type?.kind !== "promise" || type.inner.kind !== "record")
@@ -799,7 +802,7 @@ export function lowerFileHandleMethodCall(
let buffer = bufferOptions
? stage(lowerer.lowerExprExpecting(firstNode!, BYTES_U8), "%fhWindowBuffer")
: undefined;
const optionsNode = bufferOptions ? call.arguments[1] : firstNode;
const optionsNode = bufferOptions ? call.arguments.nodes[1] : firstNode;
const options = optionsNode
? stage(lowerer.lowerExpr(optionsNode), "%fhWindowOptions")
: undefined;
@@ -858,21 +861,21 @@ export function lowerFileHandleMethodCall(
}
if (name === "read") {
if (call.arguments.length < 1 || call.arguments.length > 4) {
if (call.arguments.nodes.length < 1 || call.arguments.nodes.length > 4) {
lowerer.noLowering(
`FileHandle.read with ${call.arguments.length} arguments`,
`FileHandle.read with ${call.arguments.nodes.length} arguments`,
call,
"use read(buffer[, offset[, length[, position]]])",
);
}
const buffer = lowerer.lowerExprExpecting(call.arguments[0]!, BYTES_U8);
const buffer = lowerer.lowerExprExpecting(call.arguments.nodes[0]!, BYTES_U8);
const type = lowerer.mapTypeOf(lowerer.typeOf(call));
if (type?.kind !== "promise" || type.inner.kind !== "record") {
lowerer.badType(call, lowerer.typeOf(call));
}
const offset = num(call.arguments[1], 0);
const length = num(call.arguments[2], -1);
const position = num(call.arguments[3], -1);
const offset = num(call.arguments.nodes[1], 0);
const length = num(call.arguments.nodes[2], -1);
const position = num(call.arguments.nodes[3], -1);
return {
kind: "libCall",
fn: "fileHandle.read",
@@ -883,19 +886,19 @@ export function lowerFileHandleMethodCall(
}
if (name === "write") {
if (call.arguments.length < 1 || call.arguments.length > 4) {
lowerer.noLowering(`FileHandle.write with ${call.arguments.length} arguments`, call);
if (call.arguments.nodes.length < 1 || call.arguments.nodes.length > 4) {
lowerer.noLowering(`FileHandle.write with ${call.arguments.nodes.length} arguments`, call);
}
const dataNode = call.arguments[0]!;
const dataNode = call.arguments.nodes[0]!;
const dataT = lowerer.mapTypeOf(lowerer.typeOf(dataNode));
const type = lowerer.mapTypeOf(lowerer.typeOf(call));
if (type?.kind !== "promise" || type.inner.kind !== "record") {
lowerer.badType(call, lowerer.typeOf(call));
}
if (dataT?.kind === "bytes" && dataT.elem === "u8") {
const offset = num(call.arguments[1], 0);
const length = num(call.arguments[2], -1);
const position = num(call.arguments[3], -1);
const offset = num(call.arguments.nodes[1], 0);
const length = num(call.arguments.nodes[2], -1);
const position = num(call.arguments.nodes[3], -1);
return {
kind: "libCall",
fn: "fileHandle.writeBytes",
@@ -912,14 +915,14 @@ export function lowerFileHandleMethodCall(
};
}
if (dataT?.kind === "string") {
if (call.arguments.length > 3) {
if (call.arguments.nodes.length > 3) {
lowerer.noLowering(
`FileHandle.write(string) with ${call.arguments.length} arguments`,
`FileHandle.write(string) with ${call.arguments.nodes.length} arguments`,
call,
'use write(string[, position[, "utf8"]])',
);
}
const position = num(call.arguments[1], -1);
const position = num(call.arguments.nodes[1], -1);
return {
kind: "libCall",
fn: "fileHandle.writeStr",
@@ -927,7 +930,7 @@ export function lowerFileHandleMethodCall(
receiver(),
lowerer.lowerExprExpecting(dataNode, STRING),
position.value,
utf8(call.arguments[2]),
utf8(call.arguments.nodes[2]),
],
type,
loc,
@@ -960,7 +963,7 @@ function lowerFileHandleStreamCall(
receiver: IrExpr,
loc: SrcLoc,
): IrExpr {
const args = call.arguments;
const args = call.arguments.nodes;
if (args.some(ts.isSpreadElement) || args.length > 1) {
lowerer.noLowering(`FileHandle.${name} with ${args.length} arguments`, call);
}
@@ -1021,7 +1024,7 @@ export function lowerDirMethodCall(
if (!lowerer.isStdlibMember(access)) return null;
const name = access.name.text;
const loc = locOf(call);
if (DIR_METHODS.has(name) && call.arguments.length === 0) {
if (DIR_METHODS.has(name) && call.arguments.nodes.length === 0) {
const receiver = lowerer.lowerExprExpecting(access.expression, DIR_T);
if (name === "closeSync")
return { kind: "libCall", fn: "dir.closeSync", args: [receiver], type: VOID, loc };
@@ -1093,7 +1096,7 @@ export function lowerStatsMethodCall(
if (!lowerer.isStdlibMember(access)) return null;
const name = access.name.text;
const fn = Object.hasOwn(STATS_TYPE_TESTS, name) ? STATS_TYPE_TESTS[name] : undefined;
if (fn && call.arguments.length === 0) {
if (fn && call.arguments.nodes.length === 0) {
const receiver = lowerer.lowerExpr(access.expression);
return { kind: "libCall", fn, args: [receiver], type: BOOL, loc: locOf(call) };
}
@@ -1120,9 +1123,9 @@ export function lowerStatsMethodCall(
* alive until watcher.close(). */
export function lowerFsWatchCall(lowerer: Lowerer, expr: ts.CallExpression, loc: SrcLoc): IrExpr {
if (
expr.arguments.length < 1 ||
expr.arguments.length > 3 ||
expr.arguments.some(ts.isSpreadElement)
expr.arguments.nodes.length < 1 ||
expr.arguments.nodes.length > 3 ||
expr.arguments.nodes.some(ts.isSpreadElement)
) {
lowerer.noLowering(
"fs.watch with this argument shape",
@@ -1130,16 +1133,17 @@ export function lowerFsWatchCall(lowerer: Lowerer, expr: ts.CallExpression, loc:
"the supported forms are watch(path[, options][, listener])",
);
}
const hasOptions = expr.arguments.length >= 2 && ts.isObjectLiteralExpression(expr.arguments[1]!);
if (expr.arguments.length === 3 && !hasOptions) {
const hasOptions =
expr.arguments.nodes.length >= 2 && ts.isObjectLiteralExpression(expr.arguments.nodes[1]!);
if (expr.arguments.nodes.length === 3 && !hasOptions) {
lowerer.noLowering(
"fs.watch with a non-literal options argument",
expr.arguments[1]!,
expr.arguments.nodes[1]!,
"pass the options as an object literal: watch(path, { recursive?, persistent?, encoding? }, listener)",
);
}
if (hasOptions) {
for (const prop of (expr.arguments[1] as ts.ObjectLiteralExpression).properties) {
for (const prop of (expr.arguments.nodes[1] as ts.ObjectLiteralExpression).properties.nodes) {
if (!ts.isPropertyAssignment(prop) && !ts.isShorthandPropertyAssignment(prop)) {
lowerer.noLowering(
"fs.watch options with computed keys or spreads",
@@ -1202,14 +1206,14 @@ export function lowerFsWatchCall(lowerer: Lowerer, expr: ts.CallExpression, loc:
// An undocumented key, dropped like Node drops it.
}
}
const path = lowerer.lowerExprExpecting(expr.arguments[0]!, STRING);
const path = lowerer.lowerExprExpecting(expr.arguments.nodes[0]!, STRING);
const args: IrExpr[] = [path];
const listenerArg = hasOptions
? expr.arguments.length === 3
? expr.arguments[2]!
? expr.arguments.nodes.length === 3
? expr.arguments.nodes[2]!
: null
: expr.arguments.length === 2
? expr.arguments[1]!
: expr.arguments.nodes.length === 2
? expr.arguments.nodes[1]!
: null;
if (listenerArg !== null) {
const cb = lowerer.lowerExpr(listenerArg);
@@ -1253,7 +1257,7 @@ export function lowerWatcherMethodCall(
if (!lowerer.isStdlibMember(access)) return null;
const name = access.name.text;
const loc = locOf(call);
if (name === "close" && call.arguments.length === 0) {
if (name === "close" && call.arguments.nodes.length === 0) {
const receiver = lowerer.lowerExpr(access.expression);
return { kind: "libCall", fn: "watcher.close", args: [receiver], type: VOID, loc };
}
@@ -1291,7 +1295,7 @@ export function lowerFsReaddirTypesCall(
): IrExpr {
const operation = promiseForm ? "fs.promises.readdir" : "readdirSync";
const spelling = promiseForm ? "readdir" : "readdirSync";
const optsNode = call.arguments[1]!;
const optsNode = call.arguments.nodes[1]!;
if (!ts.isObjectLiteralExpression(optsNode)) {
lowerer.noLowering(
`${operation} with a non-literal options argument`,
@@ -1300,7 +1304,7 @@ export function lowerFsReaddirTypesCall(
);
}
let sawWithFileTypes = false;
for (const p of optsNode.properties) {
for (const p of optsNode.properties.nodes) {
const m = optionMember(p);
if (!m) {
lowerer.noLowering(
@@ -1372,7 +1376,7 @@ export function lowerFsReaddirTypesCall(
) {
fence();
}
const path = lowerer.lowerExprExpecting(call.arguments[0]!, STRING);
const path = lowerer.lowerExprExpecting(call.arguments.nodes[0]!, STRING);
return {
kind: "libCall",
fn: promiseForm ? "fsp.readdirTypes" : "fs.readdirTypesSync",
@@ -1400,8 +1404,8 @@ export function lowerFsStatOptionsCall(
): IrExpr {
const spelling = noFollow ? "lstatSync" : "statSync";
const plainFn: IrLibFn = noFollow ? "fs.lstatSync" : "fs.statSync";
const optsNode = call.arguments[1]!;
const path = lowerer.lowerExprExpecting(call.arguments[0]!, STRING);
const optsNode = call.arguments.nodes[1]!;
const path = lowerer.lowerExprExpecting(call.arguments.nodes[0]!, STRING);
// The path binds first, then the options' effects, then the stat runs.
const plainAfter = (effect: IrExpr | null): IrExpr => {
if (effect === null) return { kind: "libCall", fn: plainFn, args: [path], type: STATS_T, loc };
@@ -1481,7 +1485,7 @@ export function lowerFsStatOptionsCall(
lowerer.noLowering(`${spelling} with this options value`, optsNode, "pass an object literal");
}
}
for (const p of ts.isObjectLiteralExpression(optsNode) ? optsNode.properties : []) {
for (const p of ts.isObjectLiteralExpression(optsNode) ? optsNode.properties.nodes : []) {
const m = optionMember(p);
if (!m) {
lowerer.noLowering(
@@ -1578,7 +1582,7 @@ export function lowerDirentMethodCall(
lowerer.checker.getSymbolAtLocation(access.name),
);
}
if (call.arguments.length !== 0) lowerer.noLowering(`Dirent.${name} with arguments`, call);
if (call.arguments.nodes.length !== 0) lowerer.noLowering(`Dirent.${name} with arguments`, call);
const dtype: IrExpr = {
kind: "recordGet",
obj: receiver,

Some files were not shown because too many files have changed in this diff Show More