mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-31 07:23:06 +08:00
Merge main
This commit is contained in:
@@ -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.
|
||||
|
||||
Name a new corpus program after what it tests, without a number: lowercase words joined by hyphens, such as `tests/corpus/intersection-restated-members.ts`, or a directory such as `tests/corpus/<name>/main.ts` for a multi-file program. Parallel changes kept taking the same next number; with names, two changes collide only when they add the same name, which git reports as a conflict. Leave existing numbered programs as they are: never rename or renumber them, and name a new program even when it extends a numbered family.
|
||||
|
||||
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.
|
||||
|
||||
@@ -14,7 +14,7 @@ A class can extend `require("events")` directly.
|
||||
Other immutable aliases of table-backed Node builtins support direct calls, but cannot be passed elsewhere.
|
||||
`util.promisify` supports compile-time conversions of `child_process.execFile`, `child_process.exec`, `setTimeout` and `setImmediate` (to the `timers/promises` functions, as in Node), `stream.finished` and `stream.pipeline` (to the `stream/promises` functions, as in Node), the `zlib` callback one-shots `deflate`, `inflate`, `deflateRaw`, `inflateRaw`, `gzip`, `gunzip` and `unzip` (with or without options), the `crypto` callback functions `randomBytes`, `pbkdf2`, `scrypt`, `hkdf`, `randomFill`, `randomInt` and `generateKey`, `dns.lookup` (settling with `{ address, family }`, or the address array with `all: true`) and `dns.lookupService` (settling with `{ hostname, service }`), and the `fs` callback functions that have an `fs/promises` counterpart (`access`, `appendFile`, `chmod`, `chown`, `copyFile`, `lchown`, `link`, `lstat`, `lutimes`, `mkdir`, `mkdtemp`, `readFile`, `readdir`, `readlink`, `realpath`, `rename`, `rm`, `rmdir`, `stat`, `statfs`, `symlink`, `truncate`, `unlink`, `utimes`, and `writeFile`); calls through the converted function lower as the `fs/promises` call.
|
||||
|
||||
`util.promisify` over an untyped (`any`) function returns a native wrapper that follows Node's callback rules and carries the original's `name` and `length`.
|
||||
`util.promisify` over an untyped function (a value typed `any`, a function whose parameters are untyped, or any other target in JavaScript) returns a native wrapper that follows Node's callback rules and carries the original's `name` and `length`, and throws Node's `ERR_INVALID_ARG_TYPE` for values that are not functions.
|
||||
Such a function with other own properties, `util.promisify.custom` among them, throws `SC2020` where `util.promisify` runs, and the wrapper does not report Node's DEP0174 warning when the original returns a promise.
|
||||
|
||||
`util.styleText` supports standard formats and aliases, `#RGB` and `#RRGGBB` hex colors, arrays of formats, nested ANSI resets, and explicit color enabling with `{ validateStream: false }`.
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
`console.log`, `info`, and `debug` write to stdout; `error` and `warn` write to stderr.
|
||||
They accept `unknown`, printing strings verbatim and other values through native `util.inspect`.
|
||||
Direct calls support global `console` and `node:console` imports.
|
||||
Console output, `util.format`, and `util.formatWithOptions` with a format string known only at run time, spread arguments, or an options object format their arguments at run time.
|
||||
There, `%s` of a function or of a class instance whose class defines `toString` and `%o` of an object throw `SC2020`.
|
||||
`util.inspect` with `colors`, with option values known only at run time, or in the `inspect(value, showHidden, depth, colors)` form renders the value's untyped view in the runtime with Node's styles.
|
||||
Colored output of errors, promises, regular expressions, `DataView`, `ArrayBuffer`, weak collections, iterators, and other native objects throws `SC2020`.
|
||||
Inspect options other than `depth`, `colors`, `customInspect`, `compact` (a number or `false`), `breakLength`, and `sorted` (a boolean) throw `SC2020` when they are set to a value other than Node's default.
|
||||
`util.inspect` calls `[util.inspect.custom]` methods of classes and objects with Node's depth, options, and `util.inspect` arguments, and `console.dir` and `customInspect: false` skip them.
|
||||
A class instance with such a method that is held as an untyped value throws `SC2020` when it is inspected, and a class whose subclasses declare the method without declaring it itself is refused.
|
||||
Stored global console values and detached output methods retain identity and accept ordinary arguments.
|
||||
Stored methods do not support format specifiers with substitution arguments.
|
||||
Other stored console APIs, custom `Console` instances, and console mutation are unsupported.
|
||||
|
||||
@@ -21382,9 +21382,9 @@
|
||||
"anchor": "err_invalid_mime_syntax",
|
||||
"nodeStability": null,
|
||||
"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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -23520,9 +23520,9 @@
|
||||
"anchor": "err_throttled",
|
||||
"nodeStability": null,
|
||||
"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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -71946,9 +71946,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 call shapes accepted by the compiler lowering.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -71969,9 +71969,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 call shapes accepted by the compiler lowering.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -71992,9 +71992,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -72015,9 +72015,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -72038,9 +72038,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -72061,9 +72061,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -72176,9 +72176,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -72199,9 +72199,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -72222,9 +72222,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -72245,9 +72245,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -72268,9 +72268,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -72291,9 +72291,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -72314,9 +72314,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -72337,9 +72337,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -72360,9 +72360,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -72636,9 +72636,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -72751,9 +72751,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -72774,9 +72774,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -72797,9 +72797,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 call shapes accepted by the compiler lowering.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -73027,9 +73027,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -73050,9 +73050,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -73395,9 +73395,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -25806,8 +25806,12 @@
|
||||
"anchor": "dep0044-utilisarray",
|
||||
"nodeStability": null,
|
||||
"static": {
|
||||
"status": "not-applicable",
|
||||
"evidence": "compiler-chapter-policy:deprecations"
|
||||
"status": "partial",
|
||||
"evidence": "surface-manifest:node-builtin.util.isArray",
|
||||
"tests": [
|
||||
"tests/corpus/7700-util-deprecated-helpers.cjs",
|
||||
"tests/corpus/7701-util-deprecated-helpers.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -26094,8 +26098,12 @@
|
||||
"anchor": "dep0060-util_extend",
|
||||
"nodeStability": null,
|
||||
"static": {
|
||||
"status": "not-applicable",
|
||||
"evidence": "compiler-chapter-policy:deprecations"
|
||||
"status": "partial",
|
||||
"evidence": "surface-manifest:node-builtin.util._extend",
|
||||
"tests": [
|
||||
"tests/corpus/7700-util-deprecated-helpers.cjs",
|
||||
"tests/corpus/7701-util-deprecated-helpers.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -32763,6 +32771,8 @@
|
||||
"tests/corpus/6702-ecdh-errors.js",
|
||||
"tests/corpus/6708-dh-errors.js",
|
||||
"tests/corpus/7440-tls-server-add-context.ts",
|
||||
"tests/corpus/7707-util-mime-type.cjs",
|
||||
"tests/corpus/7711-util-throttle.cjs",
|
||||
"tests/corpus/7714-zlib-brotli-compress.cjs",
|
||||
"tests/corpus/caught-system-error-fields.ts",
|
||||
"tests/corpus/error-stack-frames.ts",
|
||||
@@ -36688,8 +36698,11 @@
|
||||
"anchor": "err_invalid_mime_syntax",
|
||||
"nodeStability": null,
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:errors"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:errors.ERR_INVALID_MIME_SYNTAX",
|
||||
"tests": [
|
||||
"tests/corpus/7707-util-mime-type.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -38826,8 +38839,11 @@
|
||||
"anchor": "err_throttled",
|
||||
"nodeStability": null,
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:errors"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:errors.ERR_THROTTLED",
|
||||
"tests": [
|
||||
"tests/corpus/7711-util-throttle.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -101568,6 +101584,25 @@
|
||||
"tests/corpus/5835-util-format-integer-specifiers.ts",
|
||||
"tests/corpus/5884-inspect-layout-options.ts",
|
||||
"tests/corpus/6428-promisify-callback-functions.ts",
|
||||
"tests/corpus/7520-util-format-runtime.ts",
|
||||
"tests/corpus/7521-util-format-runtime.cjs",
|
||||
"tests/corpus/7522-promisify-untyped.cjs",
|
||||
"tests/corpus/7523-promisify-untyped.ts",
|
||||
"tests/corpus/7524-inspect-custom.ts",
|
||||
"tests/corpus/7525-inspect-custom.cjs",
|
||||
"tests/corpus/7526-inspect-options-runtime.ts",
|
||||
"tests/corpus/7700-util-deprecated-helpers.cjs",
|
||||
"tests/corpus/7701-util-deprecated-helpers.ts",
|
||||
"tests/corpus/7702-util-signal-exit-code.cjs",
|
||||
"tests/corpus/7703-util-aborted.cjs",
|
||||
"tests/corpus/7704-util-aborted.ts",
|
||||
"tests/corpus/7705-util-diff.cjs",
|
||||
"tests/corpus/7706-util-diff.ts",
|
||||
"tests/corpus/7707-util-mime-type.cjs",
|
||||
"tests/corpus/7708-util-mime-type.ts",
|
||||
"tests/corpus/7710-util-debounce.cjs",
|
||||
"tests/corpus/7711-util-throttle.cjs",
|
||||
"tests/corpus/7712-util-debounce-throttle.ts",
|
||||
"tests/corpus/7747-text-codec-untyped-slot.js",
|
||||
"tests/corpus/node24-deep-equality-properties.js",
|
||||
"tests/corpus/node24-deep-equality.cjs",
|
||||
@@ -101654,8 +101689,12 @@
|
||||
"inherited": false
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:util"
|
||||
"status": "partial",
|
||||
"evidence": "surface-manifest:node-builtin.util._extend",
|
||||
"tests": [
|
||||
"tests/corpus/7700-util-deprecated-helpers.cjs",
|
||||
"tests/corpus/7701-util-deprecated-helpers.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -101677,8 +101716,12 @@
|
||||
"inherited": false
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:util"
|
||||
"status": "partial",
|
||||
"evidence": "surface-manifest:node-builtin.util.isArray",
|
||||
"tests": [
|
||||
"tests/corpus/7700-util-deprecated-helpers.cjs",
|
||||
"tests/corpus/7701-util-deprecated-helpers.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -101700,8 +101743,12 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:util"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:util.util.MIMEType",
|
||||
"tests": [
|
||||
"tests/corpus/7707-util-mime-type.cjs",
|
||||
"tests/corpus/7708-util-mime-type.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -101723,8 +101770,12 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:util"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:util.mime.toString",
|
||||
"tests": [
|
||||
"tests/corpus/7707-util-mime-type.cjs",
|
||||
"tests/corpus/7708-util-mime-type.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -101746,8 +101797,12 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:util"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:util.mime.toJSON",
|
||||
"tests": [
|
||||
"tests/corpus/7707-util-mime-type.cjs",
|
||||
"tests/corpus/7708-util-mime-type.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -101769,8 +101824,12 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:util"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:util.MIMEType.parse",
|
||||
"tests": [
|
||||
"tests/corpus/7707-util-mime-type.cjs",
|
||||
"tests/corpus/7708-util-mime-type.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -101884,8 +101943,12 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:util"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:util.util.MIMEParams",
|
||||
"tests": [
|
||||
"tests/corpus/7707-util-mime-type.cjs",
|
||||
"tests/corpus/7708-util-mime-type.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -101907,8 +101970,12 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:util"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:util.mimeParams.delete",
|
||||
"tests": [
|
||||
"tests/corpus/7707-util-mime-type.cjs",
|
||||
"tests/corpus/7708-util-mime-type.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -101930,8 +101997,12 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:util"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:util.mimeParams.entries",
|
||||
"tests": [
|
||||
"tests/corpus/7707-util-mime-type.cjs",
|
||||
"tests/corpus/7708-util-mime-type.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -101953,8 +102024,12 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:util"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:util.mimeParams.get",
|
||||
"tests": [
|
||||
"tests/corpus/7707-util-mime-type.cjs",
|
||||
"tests/corpus/7708-util-mime-type.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -101976,8 +102051,12 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:util"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:util.mimeParams.has",
|
||||
"tests": [
|
||||
"tests/corpus/7707-util-mime-type.cjs",
|
||||
"tests/corpus/7708-util-mime-type.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -101999,8 +102078,12 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:util"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:util.mimeParams.keys",
|
||||
"tests": [
|
||||
"tests/corpus/7707-util-mime-type.cjs",
|
||||
"tests/corpus/7708-util-mime-type.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -102022,8 +102105,12 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:util"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:util.mimeParams.set",
|
||||
"tests": [
|
||||
"tests/corpus/7707-util-mime-type.cjs",
|
||||
"tests/corpus/7708-util-mime-type.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -102045,8 +102132,12 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:util"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:util.mimeParams.values",
|
||||
"tests": [
|
||||
"tests/corpus/7707-util-mime-type.cjs",
|
||||
"tests/corpus/7708-util-mime-type.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -102068,8 +102159,12 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:util"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:util.mimeParams",
|
||||
"tests": [
|
||||
"tests/corpus/7707-util-mime-type.cjs",
|
||||
"tests/corpus/7708-util-mime-type.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -102468,8 +102563,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:util"
|
||||
"status": "partial",
|
||||
"evidence": "surface-manifest:node-builtin.util.convertProcessSignalToExitCode",
|
||||
"tests": [
|
||||
"tests/corpus/7702-util-signal-exit-code.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -102603,8 +102701,12 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:util"
|
||||
"status": "partial",
|
||||
"evidence": "surface-manifest:node-builtin.util.debounce",
|
||||
"tests": [
|
||||
"tests/corpus/7710-util-debounce.cjs",
|
||||
"tests/corpus/7712-util-debounce-throttle.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -102626,8 +102728,12 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:util"
|
||||
"status": "partial",
|
||||
"evidence": "surface-manifest:node-builtin.util.throttle",
|
||||
"tests": [
|
||||
"tests/corpus/7711-util-throttle.cjs",
|
||||
"tests/corpus/7712-util-debounce-throttle.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -102649,8 +102755,12 @@
|
||||
"inherited": false
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:util"
|
||||
"status": "partial",
|
||||
"evidence": "surface-manifest:node-builtin.util.diff",
|
||||
"tests": [
|
||||
"tests/corpus/7705-util-diff.cjs",
|
||||
"tests/corpus/7706-util-diff.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -102677,7 +102787,9 @@
|
||||
"tests": [
|
||||
"tests/corpus/1636-util-format.ts",
|
||||
"tests/corpus/2440-console-inspect-args.ts",
|
||||
"tests/corpus/5835-util-format-integer-specifiers.ts"
|
||||
"tests/corpus/5835-util-format-integer-specifiers.ts",
|
||||
"tests/corpus/7520-util-format-runtime.ts",
|
||||
"tests/corpus/7521-util-format-runtime.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
@@ -102705,7 +102817,9 @@
|
||||
"tests": [
|
||||
"tests/corpus/1636-util-format.ts",
|
||||
"tests/corpus/2440-console-inspect-args.ts",
|
||||
"tests/corpus/5835-util-format-integer-specifiers.ts"
|
||||
"tests/corpus/5835-util-format-integer-specifiers.ts",
|
||||
"tests/corpus/7520-util-format-runtime.ts",
|
||||
"tests/corpus/7521-util-format-runtime.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
@@ -102896,7 +103010,8 @@
|
||||
"tests/corpus/1632-inspect-records.ts",
|
||||
"tests/corpus/1637-inspect-dyn.ts",
|
||||
"tests/corpus/2485-inspect-circular-refs.ts",
|
||||
"tests/corpus/5884-inspect-layout-options.ts"
|
||||
"tests/corpus/5884-inspect-layout-options.ts",
|
||||
"tests/corpus/7526-inspect-options-runtime.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
@@ -102919,8 +103034,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:util"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:util.util.inspect",
|
||||
"tests": [
|
||||
"tests/corpus/7526-inspect-options-runtime.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -102942,8 +103060,12 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:util"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:util.custom",
|
||||
"tests": [
|
||||
"tests/corpus/7524-inspect-custom.ts",
|
||||
"tests/corpus/7525-inspect-custom.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "ancestor"
|
||||
},
|
||||
@@ -103260,6 +103382,8 @@
|
||||
"tests/fixtures/node-types/promisify-dns.ts",
|
||||
"tests/corpus/6428-promisify-callback-functions.ts",
|
||||
"tests/fixtures/node-types/promisify-callback-functions.ts",
|
||||
"tests/corpus/7522-promisify-untyped.cjs",
|
||||
"tests/corpus/7523-promisify-untyped.ts",
|
||||
"tests/harness/project-config.test.ts"
|
||||
]
|
||||
},
|
||||
@@ -103475,8 +103599,12 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:util"
|
||||
"status": "partial",
|
||||
"evidence": "surface-manifest:node-builtin.util.aborted",
|
||||
"tests": [
|
||||
"tests/corpus/7703-util-aborted.cjs",
|
||||
"tests/corpus/7704-util-aborted.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
|
||||
@@ -187,6 +187,13 @@
|
||||
"node-builtin.util.callbackify": ["tests/corpus/5190-util-callbackify.cjs", "tests/corpus/5191-util-callbackify.ts", "tests/fixtures/node-types/util-callables.ts"],
|
||||
"node-builtin.util.debuglog": ["tests/corpus/5192-util-debuglog.cjs", "tests/corpus/5193-util-debuglog.ts", "tests/fixtures/node-types/util-callables.ts"],
|
||||
"node-builtin.util.debug": ["tests/corpus/5192-util-debuglog.cjs", "tests/corpus/5193-util-debuglog.ts"],
|
||||
"node-builtin.util.isArray": ["tests/corpus/7700-util-deprecated-helpers.cjs", "tests/corpus/7701-util-deprecated-helpers.ts"],
|
||||
"node-builtin.util._extend": ["tests/corpus/7700-util-deprecated-helpers.cjs", "tests/corpus/7701-util-deprecated-helpers.ts"],
|
||||
"node-builtin.util.convertProcessSignalToExitCode": ["tests/corpus/7702-util-signal-exit-code.cjs"],
|
||||
"node-builtin.util.aborted": ["tests/corpus/7703-util-aborted.cjs", "tests/corpus/7704-util-aborted.ts"],
|
||||
"node-builtin.util.diff": ["tests/corpus/7705-util-diff.cjs", "tests/corpus/7706-util-diff.ts"],
|
||||
"node-builtin.util.debounce": ["tests/corpus/7710-util-debounce.cjs", "tests/corpus/7712-util-debounce-throttle.ts"],
|
||||
"node-builtin.util.throttle": ["tests/corpus/7711-util-throttle.cjs", "tests/corpus/7712-util-debounce-throttle.ts"],
|
||||
"node-builtin.util": ["tests/corpus/2440-console-inspect-args.ts", "tests/corpus/2912-promisify-fs-readfile.ts"],
|
||||
"node-builtin.worker_threads": ["tests/corpus/957-builtins-namespace.ts", "tests/corpus/native-worker-messages.ts", "tests/corpus/native-worker-references.ts"],
|
||||
"node-builtin.zlib": ["tests/corpus/1404-zlib-crypto-bytes.ts", "tests/corpus/2921-zlib-callbacks.ts", "tests/corpus/3115-zlib-static-compression-levels.ts", "tests/corpus/5370-zlib-stream-roundtrip.ts", "tests/corpus/5371-zlib-stream-chunking.cjs", "tests/corpus/5373-zlib-stream-options.cjs", "tests/corpus/5378-zlib-sync-options.cjs", "tests/corpus/5379-zlib-callback-options.ts", "tests/corpus/5415-zlib-dynamic-inputs.cjs"],
|
||||
@@ -253,9 +260,11 @@
|
||||
{"chapter": "worker_threads", "symbols": ["resourceLimits"], "status": "partial", "evidence": ["tests/corpus/5806-worker-resource-limits.ts"]},
|
||||
{"chapter": "worker_threads", "symbols": ["BroadcastChannel extends EventTarget", "broadcastChannel.close", "broadcastChannel.postMessage", "broadcastChannel.ref", "broadcastChannel.unref", "onmessage"], "status": "partial", "evidence": ["tests/corpus/5800-broadcast-channel.ts", "tests/corpus/5801-broadcast-channel.cjs", "tests/corpus/5802-broadcast-channel-workers.ts"]},
|
||||
{"chapter": "util", "symbols": ["util.types.isAnyArrayBuffer", "util.types.isArrayBuffer", "util.types.isArrayBufferView", "util.types.isDataView", "util.types.isTypedArray", "util.types.isUint8Array", "util.types.isUint8ClampedArray", "util.types.isUint16Array", "util.types.isUint32Array", "util.types.isInt8Array", "util.types.isInt16Array", "util.types.isInt32Array", "util.types.isFloat32Array", "util.types.isFloat64Array", "util.types.isMap", "util.types.isSet", "util.types.isWeakMap", "util.types.isWeakSet", "util.types.isRegExp", "util.types.isNativeError", "util.types.isPromise", "util.types.isProxy", "util.types.isAsyncFunction", "util.types.isGeneratorFunction", "util.types.isGeneratorObject"], "status": "partial", "evidence": ["tests/corpus/node24-util-types.ts", "tests/corpus/node24-util-types.js", "tests/corpus/node24-util-types.cjs", "tests/fixtures/node-types/util-types.ts"]},
|
||||
{"chapter": "util", "symbols": ["util.format", "util.formatWithOptions"], "status": "partial", "evidence": ["tests/corpus/1636-util-format.ts", "tests/corpus/2440-console-inspect-args.ts", "tests/corpus/5835-util-format-integer-specifiers.ts"]},
|
||||
{"chapter": "util", "symbols": ["util.format", "util.formatWithOptions"], "status": "partial", "evidence": ["tests/corpus/1636-util-format.ts", "tests/corpus/2440-console-inspect-args.ts", "tests/corpus/5835-util-format-integer-specifiers.ts", "tests/corpus/7520-util-format-runtime.ts", "tests/corpus/7521-util-format-runtime.cjs"]},
|
||||
{"chapter": "util", "symbols": ["util.types.isAnyArrayBuffer", "util.types.isArrayBuffer", "util.types.isArrayBufferView", "util.types.isDataView", "util.types.isTypedArray", "util.types.isUint8Array", "util.types.isUint8ClampedArray", "util.types.isUint16Array", "util.types.isUint32Array", "util.types.isInt8Array", "util.types.isInt16Array", "util.types.isInt32Array", "util.types.isFloat32Array", "util.types.isFloat64Array", "util.types.isMap", "util.types.isSet", "util.types.isWeakMap", "util.types.isWeakSet", "util.types.isRegExp", "util.types.isNativeError", "util.types.isPromise", "util.types.isProxy", "util.types.isAsyncFunction", "util.types.isGeneratorFunction", "util.types.isGeneratorObject", "util.types.isDate", "util.types.isSharedArrayBuffer", "util.types.isMapIterator", "util.types.isSetIterator", "util.types.isKeyObject", "util.types.isNumberObject", "util.types.isStringObject", "util.types.isBooleanObject", "util.types.isBigIntObject", "util.types.isSymbolObject", "util.types.isBoxedPrimitive", "util.types.isBigInt64Array", "util.types.isBigUint64Array", "util.types.isFloat16Array", "util.types.isCryptoKey", "util.types.isExternal"], "status": "partial", "evidence": ["tests/corpus/5831-util-types-brands.ts", "tests/corpus/5832-util-types-brands-dynamic.js", "tests/corpus/node24-util-types.ts", "tests/corpus/node24-util-types.js", "tests/corpus/node24-util-types.cjs", "tests/fixtures/node-types/util-types.ts"]},
|
||||
{ "chapter": "util", "signatures": ["`util.inspect(object[, options])`"], "status": "partial", "evidence": ["tests/corpus/1630-inspect-scalars.ts", "tests/corpus/1632-inspect-records.ts", "tests/corpus/1637-inspect-dyn.ts", "tests/corpus/2485-inspect-circular-refs.ts", "tests/corpus/5884-inspect-layout-options.ts"] },
|
||||
{ "chapter": "util", "signatures": ["`util.inspect(object[, options])`"], "status": "partial", "evidence": ["tests/corpus/1630-inspect-scalars.ts", "tests/corpus/1632-inspect-records.ts", "tests/corpus/1637-inspect-dyn.ts", "tests/corpus/2485-inspect-circular-refs.ts", "tests/corpus/5884-inspect-layout-options.ts", "tests/corpus/7526-inspect-options-runtime.ts"] },
|
||||
{ "chapter": "util", "signatures": ["`util.inspect(object[, showHidden[, depth[, colors]]])`"], "status": "partial", "evidence": ["tests/corpus/7526-inspect-options-runtime.ts"] },
|
||||
{ "chapter": "util", "signatures": ["`custom` Type: {symbol} that can be used to declare custom inspect functions."], "status": "partial", "evidence": ["tests/corpus/7524-inspect-custom.ts", "tests/corpus/7525-inspect-custom.cjs"] },
|
||||
{ "chapter": "util", "symbols": ["util.styleText"], "status": "partial", "evidence": ["tests/corpus/node24-style-text.ts", "tests/corpus/node24-style-text-validation.js", "tests/corpus/node24-style-text-environment.js", "tests/corpus/node24-style-text.cjs", "tests/fixtures/node-types/style-text.ts"] },
|
||||
{ "chapter": "util", "symbols": ["util.isDeepStrictEqual"], "status": "partial", "evidence": ["tests/corpus/node24-deep-equality.ts", "tests/corpus/node24-deep-equality.cjs", "tests/corpus/node24-deep-equality-properties.js", "tests/fixtures/node-types/deep-equality.ts", "packages/compiler/test/deep-equality.test.ts"] },
|
||||
{ "chapter": "deprecations", "symbols": ["util.toUSVString"], "status": "not-applicable" },
|
||||
@@ -265,6 +274,10 @@
|
||||
{ "chapter": "buffer", "symbols": ["buffer.isAscii", "buffer.isUtf8", "buffer.transcode"], "status": "partial", "evidence": ["tests/corpus/node24-buffer-encoding.ts", "tests/corpus/node24-buffer-encoding-errors.ts", "tests/fixtures/node-types/text-encoding.ts"] },
|
||||
{ "chapter": "util", "parent": "Class: `util.TextEncoder`", "signatures": ["`textEncoder.encodeInto(src, dest)`", "`encoding` Type: {string}"], "status": "partial", "evidence": ["tests/corpus/node24-text-encode-into.ts", "tests/corpus/node24-text-codec-properties.ts", "tests/fixtures/node-types/text-encoding.ts"] },
|
||||
{ "chapter": "util", "parent": "Class: `util.TextDecoder`", "signatures": ["`encoding` Type: {string}", "`fatal` Type: {boolean}", "`ignoreBOM` Type: {boolean}"], "status": "partial", "evidence": ["tests/corpus/node24-text-codec-properties.ts", "tests/fixtures/node-types/text-encoding.ts"] },
|
||||
{ "chapter": "util", "symbols": ["util.MIMEType", "util.MIMEParams", "MIMEType.parse", "mime.toString", "mime.toJSON", "mimeParams.delete", "mimeParams.entries", "mimeParams.get", "mimeParams.has", "mimeParams.keys", "mimeParams.set", "mimeParams.values"], "status": "partial", "evidence": ["tests/corpus/7707-util-mime-type.cjs", "tests/corpus/7708-util-mime-type.ts"] },
|
||||
{ "chapter": "util", "signatures": ["`mimeParams[Symbol.iterator]()`"], "status": "partial", "evidence": ["tests/corpus/7707-util-mime-type.cjs", "tests/corpus/7708-util-mime-type.ts"] },
|
||||
{ "chapter": "errors", "symbols": ["ERR_INVALID_MIME_SYNTAX"], "status": "partial", "evidence": ["tests/corpus/7707-util-mime-type.cjs"] },
|
||||
{ "chapter": "errors", "symbols": ["ERR_THROTTLED"], "status": "partial", "evidence": ["tests/corpus/7711-util-throttle.cjs"] },
|
||||
{ "chapter": "util", "symbols": ["util.parseEnv"], "status": "partial", "evidence": ["tests/corpus/node24-env-parse.ts", "tests/corpus/node24-env-parse-errors.ts", "tests/corpus/node24-env-imports.cjs", "tests/fixtures/node-types/env-files.ts", "tests/harness/project-config.test.ts"] },
|
||||
{ "chapter": "process", "symbols": ["process.memoryUsage.rss"], "status": "partial", "evidence": ["tests/corpus/5210-process-memory-usage.ts"] },
|
||||
{ "chapter": "perf_hooks", "symbols": ["performance.now"], "status": "partial", "evidence": ["tests/corpus/2427-perf-hooks-now.ts", "tests/corpus/2446-performance-global.ts"] },
|
||||
@@ -409,7 +422,7 @@
|
||||
{ "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/corpus/6428-promisify-callback-functions.ts", "tests/fixtures/node-types/promisify-callback-functions.ts", "tests/harness/project-config.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/corpus/6428-promisify-callback-functions.ts", "tests/fixtures/node-types/promisify-callback-functions.ts", "tests/corpus/7522-promisify-untyped.cjs", "tests/corpus/7523-promisify-untyped.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"] },
|
||||
{ "chapter": "zlib", "signatures": ["Class: `zlib.Deflate`", "Class: `zlib.Inflate`", "Class: `zlib.Gzip`", "Class: `zlib.Gunzip`", "Class: `zlib.DeflateRaw`", "Class: `zlib.InflateRaw`", "Class: `zlib.Unzip`", "Class: `zlib.ZlibBase`", "`zlib.close([callback])`", "`zlib.flush([kind, ]callback)`", "`zlib.params(level, strategy, callback)`", "`zlib.reset()`", "`bytesWritten` Type: {number}", "Class: `Options`"], "status": "partial", "evidence": ["tests/corpus/5370-zlib-stream-roundtrip.ts", "tests/corpus/5371-zlib-stream-chunking.cjs", "tests/corpus/5372-zlib-stream-errors.cjs", "tests/corpus/5373-zlib-stream-options.cjs", "tests/corpus/5374-zlib-stream-flush-params.ts", "tests/corpus/5375-zlib-stream-promises.ts", "tests/corpus/5377-zlib-stream-backpressure.cjs"] },
|
||||
{ "chapter": "zlib", "signatures": ["Class: `zlib.BrotliCompress`", "Class: `BrotliOptions`"], "status": "partial", "evidence": ["tests/corpus/7714-zlib-brotli-compress.cjs"] },
|
||||
|
||||
+131
-5
@@ -28,6 +28,85 @@ declare module "util" {
|
||||
): string;
|
||||
export function parseEnv(content: string): { [key: string]: string | undefined };
|
||||
export function inherits(constructor: unknown, superConstructor: unknown): void;
|
||||
export class MIMEType {
|
||||
constructor(input: string | { toString: () => string });
|
||||
static parse(input: string | { toString: () => string }): MIMEType | null;
|
||||
type: string;
|
||||
subtype: string;
|
||||
readonly essence: string;
|
||||
readonly params: MIMEParams;
|
||||
toString(): string;
|
||||
toJSON(): string;
|
||||
}
|
||||
export class MIMEParams {
|
||||
delete(name: string): void;
|
||||
entries(): IterableIterator<[name: string, value: string]>;
|
||||
get(name: string): string | null;
|
||||
has(name: string): boolean;
|
||||
keys(): IterableIterator<string>;
|
||||
set(name: string, value: string): void;
|
||||
values(): IterableIterator<string>;
|
||||
toString(): string;
|
||||
toJSON(): string;
|
||||
[Symbol.iterator](): IterableIterator<[name: string, value: string]>;
|
||||
}
|
||||
export type DiffEntry = [operation: -1 | 0 | 1, value: string];
|
||||
export function diff(
|
||||
actual: string | readonly string[],
|
||||
expected: string | readonly string[],
|
||||
): DiffEntry[];
|
||||
export interface DebounceOptions {
|
||||
leading?: boolean;
|
||||
rejectOnCancel?: boolean;
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
/** Every call answers a promise of the result of the call it waits for. */
|
||||
export interface DebouncedFunction<T extends (...args: any[]) => any> {
|
||||
(...args: Parameters<T>): Promise<Awaited<ReturnType<T>>>;
|
||||
cancel(reason?: unknown): void;
|
||||
flush(): void;
|
||||
ref(): this;
|
||||
unref(): this;
|
||||
readonly pending: Promise<Awaited<ReturnType<T>>> | null;
|
||||
readonly pendingCount: number;
|
||||
}
|
||||
export function debounce<T extends (...args: any[]) => any>(
|
||||
fn: T,
|
||||
wait: number,
|
||||
options?: DebounceOptions,
|
||||
): DebouncedFunction<T>;
|
||||
export interface ThrottleOptions {
|
||||
concurrency?: number;
|
||||
maxPending?: number;
|
||||
overflow?: "queue" | "drop";
|
||||
signal?: AbortSignal;
|
||||
strict?: boolean;
|
||||
}
|
||||
export interface ThrottledFunction<T extends (...args: any[]) => any> {
|
||||
(...args: Parameters<T>): Promise<Awaited<ReturnType<T>>>;
|
||||
cancel(reason?: unknown): void;
|
||||
hasImmediateCapacity(): boolean;
|
||||
ref(): this;
|
||||
unref(): this;
|
||||
readonly pending: Promise<Awaited<ReturnType<T>>> | null;
|
||||
readonly pendingCount: number;
|
||||
readonly activeCount: number;
|
||||
}
|
||||
export function throttle<T extends (...args: any[]) => any>(
|
||||
fn: T,
|
||||
limit: number,
|
||||
interval: number,
|
||||
options?: ThrottleOptions,
|
||||
): ThrottledFunction<T>;
|
||||
/** Resolves with the abort event once `signal` aborts, or with undefined
|
||||
* when it already has. */
|
||||
export function aborted(signal: AbortSignal, resource: any): Promise<unknown>;
|
||||
/** 128 plus the number of the signal, the exit code of a process it terminates. */
|
||||
export function convertProcessSignalToExitCode(signalCode: string): number;
|
||||
/** @deprecated Since v4.0.0 - Use `Array.isArray()` instead. */
|
||||
export function isArray(object: unknown): object is unknown[];
|
||||
/** @deprecated Since v6.0.0 - Use `Object.assign()` instead. Answers target itself. */
|
||||
export function _extend<T extends object>(target: T, source: object | null | undefined): T;
|
||||
export interface DeprecateOptions {
|
||||
modifyPrototype?: boolean;
|
||||
}
|
||||
@@ -85,7 +164,13 @@ declare module "util" {
|
||||
export type TextDecoder = globalThis.TextDecoder;
|
||||
export var TextEncoder: typeof globalThis.TextEncoder;
|
||||
export var TextDecoder: typeof globalThis.TextDecoder;
|
||||
/* stream.pipeline's overload comes first: its __promisify__ member keeps
|
||||
/* An untyped function: an `any` value, or a function whose parameters
|
||||
* are untyped (JavaScript's `(value, callback) => ...`), the only
|
||||
* parameters that accept this object type. The promisified function is
|
||||
* untyped too. It comes first because such functions match every other
|
||||
* overload. */
|
||||
export function promisify(fn: (...args: { readonly "scriptc:untyped": true }[]) => unknown): any;
|
||||
/* stream.pipeline's overload comes next: its __promisify__ member keeps
|
||||
* every other target out of it. */
|
||||
export function promisify(
|
||||
fn: typeof import("node:stream").pipeline,
|
||||
@@ -206,15 +291,56 @@ declare module "util" {
|
||||
): (address: string, port: number) => Promise<{ hostname: string; service: string }>;
|
||||
/* Declared surface without full lowering (the type-level tolerance
|
||||
* story: guarded/diagnostic-path code must TYPECHECK; a reached use
|
||||
* without a lowering fences at its site). inspect's options carry the
|
||||
* documented knobs through an index signature. */
|
||||
* without a lowering fences at its site). */
|
||||
export interface InspectOptions {
|
||||
showHidden?: boolean | undefined;
|
||||
depth?: number | null | undefined;
|
||||
colors?: boolean | undefined;
|
||||
customInspect?: boolean | undefined;
|
||||
showProxy?: boolean | undefined;
|
||||
maxArrayLength?: number | null | undefined;
|
||||
maxStringLength?: number | null | undefined;
|
||||
breakLength?: number | undefined;
|
||||
compact?: boolean | number | undefined;
|
||||
sorted?: boolean | ((a: string, b: string) => number) | undefined;
|
||||
getters?: "get" | "set" | boolean | undefined;
|
||||
numericSeparator?: boolean | undefined;
|
||||
}
|
||||
export type Style =
|
||||
| "special"
|
||||
| "number"
|
||||
| "bigint"
|
||||
| "boolean"
|
||||
| "undefined"
|
||||
| "null"
|
||||
| "string"
|
||||
| "symbol"
|
||||
| "date"
|
||||
| "regexp"
|
||||
| "module";
|
||||
export interface InspectOptionsStylized extends InspectOptions {
|
||||
stylize(text: string, styleType: Style): string;
|
||||
}
|
||||
export type CustomInspectFunction = (
|
||||
depth: number,
|
||||
options: InspectOptionsStylized,
|
||||
inspect: typeof import("node:util").inspect,
|
||||
) => any;
|
||||
export function inspect(
|
||||
object: unknown,
|
||||
options?: { depth?: number | null; colors?: boolean; [k: string]: unknown },
|
||||
showHidden?: boolean,
|
||||
depth?: number | null,
|
||||
color?: boolean,
|
||||
): string;
|
||||
export function inspect(object: unknown, options?: InspectOptions): string;
|
||||
export namespace inspect {
|
||||
/* The key of an object's custom inspect function:
|
||||
* Symbol.for('nodejs.util.inspect.custom'). */
|
||||
const custom: unique symbol;
|
||||
}
|
||||
export function format(format?: unknown, ...args: unknown[]): string;
|
||||
export function formatWithOptions(
|
||||
inspectOptions: { depth?: number | null; colors?: boolean; [k: string]: unknown },
|
||||
inspectOptions: InspectOptions,
|
||||
format?: unknown,
|
||||
...args: unknown[]
|
||||
): string;
|
||||
|
||||
@@ -33,6 +33,8 @@ export function executableLinkFeatures(mod: IrModule): NativeLinkFeatures {
|
||||
...(features.declAdopt ? { declAdopt: true } : {}),
|
||||
...(features.dynForAwait ? { dynForAwait: true } : {}),
|
||||
...(features.eventsOnceSignal ? { eventsOnceSignal: true } : {}),
|
||||
...(features.mime ? { mime: true } : {}),
|
||||
...(features.utilTiming ? { utilTiming: true } : {}),
|
||||
...(features.dynBuffer ? { dynBuffer: true } : {}),
|
||||
...(features.builtinValues ? { builtinValues: true } : {}),
|
||||
events: features.processEvents,
|
||||
|
||||
@@ -50,6 +50,7 @@ import {
|
||||
matchIntegerBytesForLoop,
|
||||
matchIntegerCountedForLoop,
|
||||
matchIntegerPairedForLoop,
|
||||
steppedLoopCounter,
|
||||
} from "../../ir/integer-loops.js";
|
||||
import { emitByteWindowGuard, matchByteWindow } from "./byte-windows.js";
|
||||
import {
|
||||
@@ -1218,7 +1219,24 @@ export function emitStmtBody(emitter: LlEmitter, s: IrStmt): void {
|
||||
// has no exit besides its own bound, which LLVM can vectorize. A typed
|
||||
// array's length is fixed, so each block can take it as its limit when
|
||||
// the loop does not assign the array's binding.
|
||||
//
|
||||
// Inside another loop of this function, which polls at least once
|
||||
// every WORKER_POLL_BLOCK of its own iterations, a loop needs less:
|
||||
// - a loop whose counters' ranges bound its runs, and those of the
|
||||
// loops in its body, to WORKER_POLL_BLOCK^2 needs no poll;
|
||||
// - an innermost counted loop is strip-mined, typed arrays or not,
|
||||
// and does not poll after its last block. A run of at most
|
||||
// WORKER_POLL_BLOCK iterations then never polls, and the countdown
|
||||
// leaves its body. A run of at most WORKER_UNPOLLED_RUN iterations
|
||||
// is one block, polled once as it leaves.
|
||||
const enclosed =
|
||||
emitter.mod.workers === true &&
|
||||
emitter.jumpTargets.some((target) => target.kind === "loop");
|
||||
const bounded =
|
||||
enclosed && boundedLoopRuns(emitter, s) <= WORKER_POLL_BLOCK * WORKER_POLL_BLOCK;
|
||||
const innermost = enclosed && !containsLoop(s.body);
|
||||
const stripMined =
|
||||
!bounded &&
|
||||
capturedLets.length === 0 &&
|
||||
integerSlot !== null &&
|
||||
((countedLoop !== null &&
|
||||
@@ -1226,16 +1244,21 @@ export function emitStmtBody(emitter: LlEmitter, s: IrStmt): void {
|
||||
Math.abs(countedLoop.step) * WORKER_POLL_BLOCK <= 2 ** 52) ||
|
||||
(integerLoop !== null && integerArrayLoop === null && integerLoop.stableReceiver)) &&
|
||||
emitter.workerLoopPolls(s) &&
|
||||
loopAccessesTypedArrays(s);
|
||||
const workerBudget = stripMined ? null : emitter.workerLoopBudget(s);
|
||||
(loopAccessesTypedArrays(s) || (countedLoop !== null && innermost));
|
||||
const workerBudget = stripMined || bounded ? null : emitter.workerLoopBudget(s);
|
||||
// One slot for every copy of the loop: a speculative copy resumes an
|
||||
// iteration in another copy's body, which then reads this bound.
|
||||
let blockBound: string | null = null;
|
||||
let runSlot: string | null = null;
|
||||
if (stripMined) {
|
||||
blockBound = B.slot();
|
||||
B.entryAllocas.push(
|
||||
`${blockBound} = alloca ${countedLoop ? "i64" : emitter.sizeType} ; cancellation poll block bound`,
|
||||
);
|
||||
if (innermost) {
|
||||
runSlot = B.slot();
|
||||
B.entryAllocas.push(`${runSlot} = alloca i1 ; cancellation poll after the run`);
|
||||
}
|
||||
}
|
||||
freshenBindings();
|
||||
emitter.emitLoopCopies(s, capturedLets.length === 0, le, (exit) => {
|
||||
@@ -1271,7 +1294,7 @@ export function emitStmtBody(emitter: LlEmitter, s: IrStmt): void {
|
||||
return len;
|
||||
},
|
||||
};
|
||||
emitStripMinedCountedLoop(emitter, s, counter, blockBound!, exit);
|
||||
emitStripMinedCountedLoop(emitter, s, counter, blockBound!, exit, runSlot);
|
||||
return;
|
||||
}
|
||||
const lc = B.newLabel("loop.c");
|
||||
@@ -2628,6 +2651,60 @@ function emitInt32KindLoop(
|
||||
/** Iterations between cancellation polls of a strip-mined counted loop. */
|
||||
const WORKER_POLL_BLOCK = 64;
|
||||
|
||||
/** The longest run of a strip-mined loop inside another loop that polls
|
||||
* only as it leaves. */
|
||||
const WORKER_UNPOLLED_RUN = 16384;
|
||||
|
||||
/** Whether a step of `step` from every value in `range` gives a different
|
||||
* value. Beyond 2^53 a double can stop changing (`j + 1 === j` from 2^53),
|
||||
* so a counter there can run without end inside a range of a few values. */
|
||||
function exactSteps(range: { min: number; max: number }, step: number): boolean {
|
||||
const size = Math.abs(step);
|
||||
return range.min >= size - 2 ** 53 && range.max <= 2 ** 53 - size;
|
||||
}
|
||||
|
||||
/** At most how many times a `for` loop and the loops in its body run their
|
||||
* bodies per entry, from the counters' proven ranges: its iterations times
|
||||
* one plus the nested loops' runs. Infinity when a count has no bound, or
|
||||
* depends on a run-time guard (the guard's slow copy steps a double). */
|
||||
function boundedLoopRuns(emitter: LlEmitter, loop: IrStmt): number {
|
||||
if (loop.kind !== "for") return Infinity;
|
||||
const counted = matchIntegerCountedForLoop(
|
||||
loop,
|
||||
emitter.numericLocals,
|
||||
emitter.integerRanges,
|
||||
LOOPS.of(emitter).unaliasedArrays,
|
||||
);
|
||||
let trips = Infinity;
|
||||
if (counted && !counted.guarded && exactSteps(counted.range, counted.step))
|
||||
trips = Math.floor((counted.range.max - counted.range.min) / Math.abs(counted.step)) + 1;
|
||||
else if (!counted) {
|
||||
// Each iteration closes the gap between the two counters by at least two.
|
||||
const paired = matchIntegerPairedForLoop(loop, emitter.numericLocals, emitter.integerRanges);
|
||||
if (paired && !paired.guarded && exactSteps(paired.range, 1))
|
||||
trips = Math.floor((paired.range.max - paired.range.min) / 2) + 1;
|
||||
}
|
||||
if (trips === Infinity) {
|
||||
// A counter with a proven range where the condition reads it, such as
|
||||
// one compared with a module constant.
|
||||
const stepped = steppedLoopCounter(loop, emitter.currentLocals);
|
||||
const range = stepped && emitter.integerRanges.get(stepped.ref);
|
||||
if (stepped && range && exactSteps(range, stepped.step))
|
||||
trips = Math.floor((range.max - range.min) / Math.abs(stepped.step)) + 1;
|
||||
}
|
||||
if (!(trips <= WORKER_POLL_BLOCK * WORKER_POLL_BLOCK)) return Infinity;
|
||||
let nested = 0;
|
||||
const visit = (s: IrStmt): boolean => {
|
||||
if (s.kind === "while" || s.kind === "doWhile" || s.kind === "for" || s.kind === "forOf") {
|
||||
nested += boundedLoopRuns(emitter, s);
|
||||
return nested !== Infinity;
|
||||
}
|
||||
return everyStmtChild(s, () => true, visit);
|
||||
};
|
||||
if (!loop.body.every(visit)) return Infinity;
|
||||
return trips * (1 + nested);
|
||||
}
|
||||
|
||||
/** The counter of a strip-mined loop: its slot, the LLVM integer type and
|
||||
* signedness of its values, its step, the counters stepped beside it, and
|
||||
* the limit, emitted in the outer header before each block. */
|
||||
@@ -2645,13 +2722,20 @@ interface StripMinedCounter {
|
||||
* tests the counter against the loop's limit, each block runs the body up
|
||||
* to the nearer of the limit and WORKER_POLL_BLOCK steps on, and the poll
|
||||
* follows the block. It polls as often as the budget it replaces, and the
|
||||
* inner loop's only exit besides `break` and `return` is its bound. */
|
||||
* inner loop's only exit besides `break` and `return` is its bound.
|
||||
*
|
||||
* With `runSlot` (for a loop that another loop of the function encloses), a
|
||||
* block that ends at the limit leaves the loop without polling, and a run
|
||||
* of at most WORKER_UNPOLLED_RUN iterations is one block, polled once as
|
||||
* it leaves when it took more than WORKER_POLL_BLOCK iterations. `runSlot`
|
||||
* holds whether the current block is such a run. */
|
||||
function emitStripMinedCountedLoop(
|
||||
emitter: LlEmitter,
|
||||
s: Extract<IrStmt, { kind: "for" }>,
|
||||
counter: StripMinedCounter,
|
||||
boundSlot: string,
|
||||
exit: string,
|
||||
runSlot: string | null,
|
||||
): void {
|
||||
const B = emitter.B;
|
||||
const { slot, type, signed, step } = counter;
|
||||
@@ -2684,7 +2768,27 @@ function emitStripMinedCountedLoop(
|
||||
`${stepped} = call ${type} @${saturating}(${type} ${start}, ${type} ${step * WORKER_POLL_BLOCK})`,
|
||||
);
|
||||
B.line(`${bound} = call ${type} @${nearer}(${type} ${stepped}, ${type} ${limit})`);
|
||||
B.line(`store ${type} ${bound}, ptr ${boundSlot}`);
|
||||
if (runSlot === null) B.line(`store ${type} ${bound}, ptr ${boundSlot}`);
|
||||
else {
|
||||
// The steps left to the limit: positive, as the header tested.
|
||||
const left = B.tmp(),
|
||||
whole = B.tmp(),
|
||||
long = B.tmp(),
|
||||
polled = B.tmp(),
|
||||
end = B.tmp();
|
||||
const stride = Math.abs(step);
|
||||
B.line(
|
||||
ascending
|
||||
? `${left} = sub ${wrap} ${type} ${limit}, ${start}`
|
||||
: `${left} = sub ${wrap} ${type} ${start}, ${limit}`,
|
||||
);
|
||||
B.line(`${whole} = icmp ule ${type} ${left}, ${stride * WORKER_UNPOLLED_RUN}`);
|
||||
B.line(`${long} = icmp ugt ${type} ${left}, ${stride * WORKER_POLL_BLOCK}`);
|
||||
B.line(`${polled} = and i1 ${whole}, ${long}`);
|
||||
B.line(`${end} = select i1 ${whole}, ${type} ${limit}, ${type} ${bound}`);
|
||||
B.line(`store ${type} ${end}, ptr ${boundSlot}`);
|
||||
B.line(`store i1 ${polled}, ptr ${runSlot}`);
|
||||
}
|
||||
B.br(bc);
|
||||
B.startBlock(bc);
|
||||
const index = B.tmp();
|
||||
@@ -2693,7 +2797,8 @@ function emitStripMinedCountedLoop(
|
||||
B.line(`${index} = load ${type}, ptr ${slot}`);
|
||||
B.line(`${end} = load ${type}, ptr ${boundSlot}`);
|
||||
B.line(`${inBlock} = icmp ${compare} ${type} ${index}, ${end}`);
|
||||
B.condBr(inBlock, lb, poll);
|
||||
const after = runSlot === null ? poll : B.newLabel("loop.end");
|
||||
B.condBr(inBlock, lb, after);
|
||||
B.startBlock(lb);
|
||||
emitter.jumpTargets.push({
|
||||
kind: "loop",
|
||||
@@ -2716,11 +2821,45 @@ function emitStripMinedCountedLoop(
|
||||
B.line(`store ${type} ${next}, ptr ${stepSlot}`);
|
||||
}
|
||||
B.br(bc);
|
||||
if (runSlot !== null) {
|
||||
// A speculative copy may enter the body without passing the header, so
|
||||
// the limit is read again here.
|
||||
B.startBlock(after);
|
||||
const at = B.tmp();
|
||||
const more = B.tmp();
|
||||
const run = B.tmp();
|
||||
const done = B.newLabel("loop.done");
|
||||
const last = B.newLabel("loop.poll.run");
|
||||
B.line(`${at} = load ${type}, ptr ${slot}`);
|
||||
B.line(`${more} = icmp ${compare} ${type} ${at}, ${counter.limit()}`);
|
||||
B.condBr(more, poll, done);
|
||||
B.startBlock(done);
|
||||
B.line(`${run} = load i1, ptr ${runSlot}`);
|
||||
B.condBr(run, last, exit);
|
||||
B.startBlock(last);
|
||||
emitter.emitWorkerPoll();
|
||||
B.br(exit);
|
||||
}
|
||||
B.startBlock(poll);
|
||||
emitter.emitWorkerPoll();
|
||||
B.br(lc);
|
||||
}
|
||||
|
||||
/** Whether `body` holds a loop outside its closures. */
|
||||
function containsLoop(body: IrStmt[]): boolean {
|
||||
const inStmt = (s: IrStmt): boolean =>
|
||||
s.kind === "while" ||
|
||||
s.kind === "doWhile" ||
|
||||
s.kind === "for" ||
|
||||
s.kind === "forOf" ||
|
||||
!everyStmtChild(
|
||||
s,
|
||||
() => true,
|
||||
(child) => !inStmt(child),
|
||||
);
|
||||
return body.some(inStmt);
|
||||
}
|
||||
|
||||
function enterLoopHeader(emitter: LlEmitter, header: string): void {
|
||||
const dispatch = emitter.speculationHeader;
|
||||
emitter.speculationHeader = null;
|
||||
|
||||
@@ -1151,6 +1151,7 @@ export function emitLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValue {
|
||||
case "punycode":
|
||||
case "constants":
|
||||
case "promise":
|
||||
case "mime":
|
||||
return host.emitGenericLibCall(e);
|
||||
case "test":
|
||||
return emitTestLibCall(host, e);
|
||||
|
||||
@@ -86,6 +86,17 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"util.inherits": "scr_util_inherits",
|
||||
"util.inheritsPrototype": "scr_util_inherits_prototype",
|
||||
"util.deprecate": "scr_util_deprecate",
|
||||
"util.isArrayDeprecated": "scr_util_is_array_deprecated",
|
||||
"util.extend": "scr_util_extend",
|
||||
"util.signalExitCode": "scr_util_signal_exit_code",
|
||||
"util.aborted": "scr_util_aborted",
|
||||
"util.diff": "scr_util_diff",
|
||||
"util.debounce": "scr_util_debounce",
|
||||
"util.throttle": "scr_util_throttle",
|
||||
"mime.typeNew": "scr_mime_type_new",
|
||||
"mime.typeParse": "scr_mime_type_parse",
|
||||
"mime.paramsNew": "scr_mime_params_new",
|
||||
"mime.is": "scr_mime_is",
|
||||
"util.callbackify": "scr_util_callbackify",
|
||||
"util.promisifyDyn": "scr_util_promisify",
|
||||
"util.debuglog": "scr_util_debuglog",
|
||||
@@ -1082,6 +1093,26 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"dyn.generatorThis": "scr_gen_receiver",
|
||||
"insp.dyn": "scr_insp_dyn",
|
||||
"insp.dynS": "scr_insp_dyn_s",
|
||||
"insp.format": "scr_insp_format_pack",
|
||||
"insp.formatPrimitives": "scr_insp_format_primitives",
|
||||
"insp.formatList": "scr_insp_format_list",
|
||||
"insp.formatWithOptions": "scr_insp_format_with_options",
|
||||
"insp.inspect": "scr_insp_inspect",
|
||||
"insp.customEnabled": "scr_insp_custom_enabled",
|
||||
"insp.registerCustom": "scr_insp_register_custom",
|
||||
"insp.customInspect": "scr_insp_custom_inspect",
|
||||
"insp.depthKind": "scr_insp_depth_kind",
|
||||
"insp.customDepth": "scr_insp_custom_depth",
|
||||
"insp.customOptions": "scr_insp_custom_options",
|
||||
"insp.inspectFunction": "scr_insp_inspect_function",
|
||||
"insp.customText": "scr_insp_custom_text",
|
||||
"insp.customIsSelf": "scr_insp_custom_is_self",
|
||||
"insp.customResult": "scr_insp_custom_result",
|
||||
"insp.customCallBegin": "scr_insp_custom_call_begin",
|
||||
"insp.customCallEnd": "scr_insp_custom_call_end",
|
||||
"insp.formatSpecifies": "scr_insp_format_specifies",
|
||||
"insp.formatSpecifiesDyn": "scr_insp_format_specifies_dyn",
|
||||
"insp.dynFormatS": "scr_insp_dyn_format_s",
|
||||
"insp.dynHidden": "scr_insp_dyn_hidden",
|
||||
"fs.readFileSyncDyn": "scr_fs_read_file_sync_dyn",
|
||||
"fs.readFileEncoded": "scr_fs_read_file_encoded",
|
||||
@@ -1742,9 +1773,14 @@ export const USES_TIMERS_LIB_FNS = new Set<string>([
|
||||
"buffer.deprecation",
|
||||
// The wrappers schedule warnings and callbacks on the tick queue.
|
||||
"util.deprecate",
|
||||
"util.isArrayDeprecated",
|
||||
"util.extend",
|
||||
"util.callbackify",
|
||||
"util.promisifyDyn",
|
||||
"util.debuglog",
|
||||
// The wrapped calls arm timers and settle promises.
|
||||
"util.debounce",
|
||||
"util.throttle",
|
||||
"worker.new",
|
||||
"worker.parentPort",
|
||||
"worker.channel",
|
||||
|
||||
@@ -110,8 +110,8 @@ export function loopTestsEveryIteration(loop: Loop, tested: (call: IrExpr) => bo
|
||||
/** True when the body of `loop`, outside its nested loops and closures,
|
||||
* reads or writes a typed-array element: the loops whose cancellation poll
|
||||
* the emitter moves between blocks of iterations, so that LLVM can
|
||||
* vectorize them. Other loops keep the budget in their body, which costs
|
||||
* less in a loop of a few iterations. */
|
||||
* vectorize them. Outside another loop, other loops keep the budget in
|
||||
* their body, which costs less in a loop of a few iterations. */
|
||||
export function loopAccessesTypedArrays(loop: Loop): boolean {
|
||||
const inExpr = (e: IrExpr): boolean => {
|
||||
if (e.kind === "bytesIntrinsic" && e.method === "get") return true;
|
||||
|
||||
@@ -61,6 +61,10 @@ export interface NativeLinkFeatures {
|
||||
/** events.once with options: scr_events_once_signal.c. Absent (not
|
||||
* false) for programs without it. */
|
||||
eventsOnceSignal?: boolean;
|
||||
/** util.MIMEType and util.MIMEParams: scr_mime.c. */
|
||||
mime?: boolean;
|
||||
/** util.debounce and util.throttle: scr_util_timing.c. */
|
||||
utilTiming?: boolean;
|
||||
/** Buffer.prototype methods called on checked-dynamic values link
|
||||
* scr_bytes_dyn.c; absent (false) for programs without them. */
|
||||
dynBuffer?: boolean;
|
||||
|
||||
@@ -131,6 +131,10 @@ export interface CcOptions {
|
||||
/** events.once with options (events.onceSignal): compiles
|
||||
* scr_events_once_signal.c. */
|
||||
eventsOnceSignal?: boolean;
|
||||
/** util.MIMEType and util.MIMEParams: compiles scr_mime.c. */
|
||||
mime?: boolean;
|
||||
/** util.debounce and util.throttle: compiles scr_util_timing.c. */
|
||||
utilTiming?: boolean;
|
||||
/** Buffer.prototype methods called on checked-dynamic values:
|
||||
* compiles scr_bytes_dyn.c. */
|
||||
dynBuffer?: boolean;
|
||||
|
||||
@@ -249,6 +249,8 @@ const RUNTIME_UNITS: readonly RuntimeUnit[] = [
|
||||
// events.once with options registers through the emitter and
|
||||
// event-target units.
|
||||
unit((u) => u.opts.eventsOnceSignal === true, "scr_events_once_signal.c"),
|
||||
unit((u) => u.opts.mime === true, "scr_mime.c"),
|
||||
unit((u) => u.opts.utilTiming === true, "scr_util_timing.c"),
|
||||
// The checked-dynamic HANDLE support unit (listener gate + runtime
|
||||
// adapter closures): every referencing unit is one of the emitter or
|
||||
// net families (http implies net), so handle-free binaries keep
|
||||
|
||||
@@ -89,6 +89,8 @@ export interface RuntimeFeatureSet extends NativeLinkFeatures {
|
||||
declAdopt: boolean;
|
||||
dynForAwait: boolean;
|
||||
eventsOnceSignal: boolean;
|
||||
mime: boolean;
|
||||
utilTiming: boolean;
|
||||
dynBuffer: boolean;
|
||||
builtinValues: boolean;
|
||||
warningReplace: boolean;
|
||||
@@ -289,6 +291,8 @@ export function effectiveRuntimeFeatures(
|
||||
declAdopt: features.declAdopt === true,
|
||||
dynForAwait: features.dynForAwait === true,
|
||||
eventsOnceSignal: features.eventsOnceSignal === true,
|
||||
mime: features.mime === true,
|
||||
utilTiming: features.utilTiming === true,
|
||||
dynBuffer: features.dynBuffer === true,
|
||||
builtinValues: features.builtinValues === true,
|
||||
warningReplace: features.warningReplace === true,
|
||||
|
||||
@@ -76,6 +76,8 @@ export interface EarlyExecutableNativeFeatures {
|
||||
declAdopt?: boolean;
|
||||
dynForAwait?: boolean;
|
||||
eventsOnceSignal?: boolean;
|
||||
mime?: boolean;
|
||||
utilTiming?: boolean;
|
||||
dynBuffer?: boolean;
|
||||
builtinValues?: boolean;
|
||||
events: boolean;
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
import * as ts from "../../ts7/adapter.js";
|
||||
import { type Lowerer } from "../lowerer.js";
|
||||
import { locOf } from "../../program.js";
|
||||
import { dynUndefinedExpr, numLit } from "../../../ir/build.js";
|
||||
import { BOOL, DYN, type IrExpr, type IrLibFn } from "../../../ir/ir.js";
|
||||
import { builtinImportOf } from "./module-bindings.js";
|
||||
|
||||
/* node:util's MIMEType and MIMEParams are native checked-dynamic handles
|
||||
* (scr_mime.c): both types map to dyn, their getters and setters are
|
||||
* handle property reads and writes, their methods dispatch through the
|
||||
* handle, and these helpers lower the uses a dyn value cannot answer by
|
||||
* itself (construction, MIMEType.parse and instanceof). */
|
||||
|
||||
type MimeClass = "MIMEType" | "MIMEParams";
|
||||
|
||||
/** The MIMEType or MIMEParams class in any node:util spelling. */
|
||||
export function mimeClassOf(lowerer: Lowerer, node: ts.Expression): MimeClass | null {
|
||||
const builtin = ts.isIdentifier(node)
|
||||
? builtinImportOf(lowerer, node)
|
||||
: ts.isPropertyAccessExpression(node) && !node.questionDotToken
|
||||
? lowerer.builtinMemberOf(node)
|
||||
: null;
|
||||
if (builtin?.module !== "util") return null;
|
||||
return builtin.member === "MIMEType" || builtin.member === "MIMEParams" ? builtin.member : null;
|
||||
}
|
||||
|
||||
/** The class of a stdlib MIMEType- or MIMEParams-typed value. */
|
||||
function mimeValueClass(lowerer: Lowerer, node: ts.Expression): MimeClass | null {
|
||||
const type = lowerer.typeOf(node);
|
||||
const sym = type.getAliasSymbol() ?? type.getSymbol();
|
||||
if (!sym) return null;
|
||||
const name = ts.symbolName(sym);
|
||||
if (name !== "MIMEType" && name !== "MIMEParams") return null;
|
||||
return lowerer.isStdlibSymbol(sym) ? name : null;
|
||||
}
|
||||
|
||||
/** The call of `fn` over the first `count` arguments, as checked-dynamic
|
||||
* values (missing ones as undefined). Every argument evaluates in order
|
||||
* before the call, as in JavaScript: with surplus arguments, the used ones
|
||||
* are held in hidden locals while the others evaluate. */
|
||||
function mimeCall(
|
||||
lowerer: Lowerer,
|
||||
fn: IrLibFn,
|
||||
nodes: readonly ts.Expression[],
|
||||
count: number,
|
||||
loc: ReturnType<typeof locOf>,
|
||||
): IrExpr {
|
||||
const values = nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN));
|
||||
if (values.length <= count) {
|
||||
const args = [...values];
|
||||
while (args.length < count) args.push(dynUndefinedExpr(loc));
|
||||
return { kind: "libCall", fn, args, type: DYN, loc };
|
||||
}
|
||||
const locals = values.slice(0, count).map((value) => ({
|
||||
value,
|
||||
local: lowerer.declareHiddenLocal("%mimeArg", DYN),
|
||||
}));
|
||||
return {
|
||||
kind: "seqExpr",
|
||||
stmts: [
|
||||
...locals.map(({ value, local }) => ({
|
||||
kind: "varDecl" as const,
|
||||
localId: local.id,
|
||||
init: value,
|
||||
loc,
|
||||
})),
|
||||
...values.slice(count).map((arg) => ({ kind: "exprStmt" as const, expr: arg, loc: arg.loc })),
|
||||
],
|
||||
result: {
|
||||
kind: "libCall",
|
||||
fn,
|
||||
args: locals.map(({ local }): IrExpr => ({
|
||||
kind: "varRef",
|
||||
localId: local.id,
|
||||
type: DYN,
|
||||
loc,
|
||||
})),
|
||||
type: DYN,
|
||||
loc,
|
||||
},
|
||||
type: DYN,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
|
||||
/** `new MIMEType(string[, noThrow])` and `new MIMEParams()`. */
|
||||
export function lowerMimeNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr | null {
|
||||
const cls = mimeClassOf(lowerer, expr.expression);
|
||||
if (cls === null) return null;
|
||||
const nodes = expr.arguments?.nodes ?? [];
|
||||
if (nodes.some(ts.isSpreadElement)) return null;
|
||||
const loc = locOf(expr);
|
||||
const fn: IrLibFn = cls === "MIMEType" ? "mime.typeNew" : "mime.paramsNew";
|
||||
return mimeCall(lowerer, fn, nodes, cls === "MIMEType" ? 2 : 0, loc);
|
||||
}
|
||||
|
||||
const INSTANCE_METHODS: Record<MimeClass, ReadonlySet<string>> = {
|
||||
MIMEType: new Set(["toString", "toJSON"]),
|
||||
MIMEParams: new Set([
|
||||
"get",
|
||||
"has",
|
||||
"set",
|
||||
"delete",
|
||||
"entries",
|
||||
"keys",
|
||||
"values",
|
||||
"toString",
|
||||
"toJSON",
|
||||
]),
|
||||
};
|
||||
|
||||
/** MIMEType.parse(), and the instance methods, which the handle answers
|
||||
* as methods, so they dispatch through dynInvoke rather than as a member
|
||||
* read and a call. */
|
||||
export function lowerMimeCall(lowerer: Lowerer, call: ts.CallExpression): IrExpr | null {
|
||||
if (call.questionDotToken || !ts.isPropertyAccessExpression(call.expression)) return null;
|
||||
const access = call.expression;
|
||||
if (access.questionDotToken || call.arguments.nodes.some(ts.isSpreadElement)) return null;
|
||||
const name = access.name.text;
|
||||
const loc = locOf(call);
|
||||
if (name === "parse" && mimeClassOf(lowerer, access.expression) === "MIMEType") {
|
||||
return mimeCall(lowerer, "mime.typeParse", call.arguments.nodes, 1, loc);
|
||||
}
|
||||
const cls = mimeValueClass(lowerer, access.expression);
|
||||
if (cls === null || !INSTANCE_METHODS[cls].has(name)) return null;
|
||||
return {
|
||||
kind: "dynInvoke",
|
||||
recv: lowerer.lowerExprExpecting(access.expression, DYN),
|
||||
method: name,
|
||||
calleeName: ts.getTextOfNode(access),
|
||||
args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)),
|
||||
type: DYN,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
|
||||
/** Reports SC2020 when `access` reads a static member of MIMEType or
|
||||
* MIMEParams (`MIMEType.prototype`, `MIMEType.name`); null otherwise.
|
||||
* JavaScript sources hold either class as an identity token, which has no
|
||||
* members, so the read would answer undefined. MIMEType.parse calls lower
|
||||
* in lowerMimeCall and never reach the member read. */
|
||||
export function fenceMimeStaticRead(lowerer: Lowerer, access: ts.PropertyAccessExpression): null {
|
||||
const cls = mimeClassOf(lowerer, access.expression);
|
||||
if (cls !== null) lowerer.noLowering(`util.${cls}.${access.name.text}`, access);
|
||||
return null;
|
||||
}
|
||||
|
||||
/** `v instanceof MIMEType` / `v instanceof MIMEParams`. */
|
||||
export function lowerMimeInstanceof(lowerer: Lowerer, expr: ts.BinaryExpression): IrExpr | null {
|
||||
const cls = mimeClassOf(lowerer, expr.right);
|
||||
if (cls === null) return null;
|
||||
const loc = locOf(expr);
|
||||
const value = lowerer.coerceInto(expr.left, lowerer.lowerExpr(expr.left), DYN);
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "mime.is",
|
||||
args: [value, numLit(cls === "MIMEType" ? 0 : 1, loc)],
|
||||
type: BOOL,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
|
||||
/** `params`, `params.keys()`, `params.values()` or `params.entries()` over a
|
||||
* MIMEParams: its checked-dynamic view iterates the live entry list, like
|
||||
* URLSearchParams (isSearchParamsIterable). */
|
||||
export function isMimeParamsIterable(lowerer: Lowerer, node: ts.Expression): boolean {
|
||||
while (ts.isParenthesizedExpression(node)) node = node.expression;
|
||||
if (mimeValueClass(lowerer, node) === "MIMEParams") return true;
|
||||
if (
|
||||
!ts.isCallExpression(node) ||
|
||||
node.arguments.nodes.length !== 0 ||
|
||||
!ts.isPropertyAccessExpression(node.expression)
|
||||
)
|
||||
return false;
|
||||
const name = node.expression.name.text;
|
||||
return (
|
||||
(name === "keys" || name === "values" || name === "entries") &&
|
||||
mimeValueClass(lowerer, node.expression.expression) === "MIMEParams"
|
||||
);
|
||||
}
|
||||
@@ -29,11 +29,13 @@ import { checkFsUrlPaths, isFsPathParam, lowerFallibleFsUrlCall, lowerFsPath } f
|
||||
import { lowerBufferEncodingCall } from "./text-codecs.js";
|
||||
import { lowerUtilTypeCall } from "./util-types.js";
|
||||
import { InternalCompilerError } from "../../../errors.js";
|
||||
import { dynUndefinedExpr, varRef } from "../../../ir/build.js";
|
||||
import * as ts from "../../ts7/adapter.js";
|
||||
import { type Lowerer } from "../lowerer.js";
|
||||
import { type BuiltinModuleFn } from "../surfaces.js";
|
||||
import { type ParamShape } from "../call-signatures.js";
|
||||
import { BYTES_U8, F64, type IrExpr, STRING, type SrcLoc, arrayOf } from "../../../ir/ir.js";
|
||||
import { BYTES_U8, DYN, F64, type IrExpr, STRING, type SrcLoc, arrayOf } from "../../../ir/ir.js";
|
||||
import { typeKey } from "../../type-mapper.js";
|
||||
import { lowerProcessLoadEnvFile } from "./environment.js";
|
||||
import { lowerFsWatchCall, lowerFsReaddirTypesCall, lowerFsStatOptionsCall } from "./filesystem.js";
|
||||
import { lowerZlibModuleCall } from "./compression.js";
|
||||
@@ -386,7 +388,65 @@ export function lowerBuiltinModuleCall(
|
||||
const lowered = lowerFsReadPathCall(lowerer, expr, loc);
|
||||
if (lowered) return lowered;
|
||||
}
|
||||
const required = fn.params.length - (fn.defaults?.length ?? 0);
|
||||
// util._extend over a typed target: the writes go through its
|
||||
// checked-dynamic view, and the call answers the target itself, as Node
|
||||
// does, when the result type is the target's.
|
||||
if (
|
||||
bi.module === "util" &&
|
||||
bi.member === "_extend" &&
|
||||
expr.arguments.nodes.length >= 1 &&
|
||||
!expr.arguments.nodes.some(ts.isSpreadElement)
|
||||
) {
|
||||
const target = lowerer.lowerExpr(expr.arguments.nodes[0]!);
|
||||
const resultType = lowerer.mapTypeOf(lowerer.typeOf(expr));
|
||||
if (
|
||||
target.type.kind !== "dyn" &&
|
||||
resultType !== null &&
|
||||
typeKey(resultType) === typeKey(target.type)
|
||||
) {
|
||||
const held = lowerer.declareHiddenLocal("%extendTarget", target.type);
|
||||
const ref = varRef(held.id, target.type, loc);
|
||||
const source = expr.arguments.nodes[1]
|
||||
? lowerer.lowerExprExpecting(expr.arguments.nodes[1], DYN)
|
||||
: dynUndefinedExpr(loc);
|
||||
return {
|
||||
kind: "seqExpr",
|
||||
stmts: [
|
||||
{ kind: "varDecl", localId: held.id, init: target, loc },
|
||||
{
|
||||
kind: "exprStmt",
|
||||
expr: {
|
||||
kind: "libCall",
|
||||
fn: "util.extend",
|
||||
args: [lowerer.coerceToExpected(ref, DYN), source],
|
||||
type: DYN,
|
||||
loc,
|
||||
},
|
||||
loc,
|
||||
},
|
||||
],
|
||||
result: ref,
|
||||
type: target.type,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
}
|
||||
// Trailing checked-dynamic parameters whose value adapter defaults to
|
||||
// undefined may be omitted: the call passes undefined, as Node sees it.
|
||||
let undefinedTail = 0;
|
||||
if (!fn.defaults && fn.valueParams) {
|
||||
for (let i = fn.valueParams.length - 1; i >= 0; i--) {
|
||||
const param = fn.valueParams[i]!;
|
||||
if (
|
||||
param.mode !== "optional" ||
|
||||
param.defaultValue !== undefined ||
|
||||
fn.params[i]?.kind !== "dyn"
|
||||
)
|
||||
break;
|
||||
undefinedTail++;
|
||||
}
|
||||
}
|
||||
const required = fn.params.length - (fn.defaults?.length ?? undefinedTail);
|
||||
const hasSpread = expr.arguments.nodes.some(ts.isSpreadElement);
|
||||
if (
|
||||
bi.module === "url" &&
|
||||
@@ -466,12 +526,9 @@ export function lowerBuiltinModuleCall(
|
||||
: {
|
||||
type,
|
||||
mode: "omittable",
|
||||
callDefault: {
|
||||
kind: "strLit",
|
||||
value: fn.defaults![i - required]!,
|
||||
type: STRING,
|
||||
loc,
|
||||
},
|
||||
callDefault: fn.defaults
|
||||
? { kind: "strLit", value: fn.defaults[i - required]!, type: STRING, loc }
|
||||
: dynUndefinedExpr(loc),
|
||||
},
|
||||
);
|
||||
args = lowerer.completeArgs(expr.arguments.nodes, shapes, loc, expr);
|
||||
@@ -482,7 +539,11 @@ export function lowerBuiltinModuleCall(
|
||||
: lowerer.lowerExprExpecting(a, fn.params[i]),
|
||||
);
|
||||
for (let i = args.length; i < fn.params.length; i++) {
|
||||
const dflt = fn.defaults![i - required]!;
|
||||
if (!fn.defaults) {
|
||||
args.push(dynUndefinedExpr(loc));
|
||||
continue;
|
||||
}
|
||||
const dflt = fn.defaults[i - required]!;
|
||||
args.push({ kind: "strLit", value: dflt, type: STRING, loc });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { keyedHelperName } from "../stable-names.js";
|
||||
import * as ts from "../../ts7/adapter.js";
|
||||
import { type Lowerer } from "../lowerer.js";
|
||||
import { varRef } from "../../../ir/build.js";
|
||||
import { dynUndefinedExpr, varRef } from "../../../ir/build.js";
|
||||
import { isJsSourceFile } from "../../program.js";
|
||||
import {
|
||||
BOOL,
|
||||
DYN,
|
||||
@@ -92,12 +93,22 @@ function callbackShape(
|
||||
return { fnT, resultT, args, callbackT, callbackParamT: callbackParamT!, inner, error };
|
||||
}
|
||||
|
||||
/** Whether `promisify(fn)`'s one argument is typed `any`: the function is
|
||||
* only known at run time, so the promisified function is built there. */
|
||||
/** Whether `promisify(fn)`'s one argument is only known at run time, so
|
||||
* the promisified function is built there: a value typed `any`, a function
|
||||
* with untyped parameters (the declarations type that call `any`), or any
|
||||
* argument in JavaScript, whose inferred types do not describe the
|
||||
* function Node receives (it validates the argument and reads its own
|
||||
* properties). */
|
||||
export function untypedPromisifyArgument(lowerer: Lowerer, call: ts.CallExpression): boolean {
|
||||
if (call.questionDotToken || call.arguments.nodes.length !== 1) return false;
|
||||
const argNode = call.arguments.nodes[0]!;
|
||||
return !ts.isSpreadElement(argNode) && (lowerer.typeOf(argNode).flags & ts.TypeFlags.Any) !== 0;
|
||||
if (call.questionDotToken || call.arguments.nodes.length > 1) return false;
|
||||
const argNode = call.arguments.nodes[0];
|
||||
if (argNode !== undefined && ts.isSpreadElement(argNode)) return false;
|
||||
if (isJsSourceFile(ts.getSourceFileOfNode(call))) return true;
|
||||
if (argNode === undefined) return false;
|
||||
return (
|
||||
(lowerer.typeOf(argNode).flags & ts.TypeFlags.Any) !== 0 ||
|
||||
(lowerer.typeOf(call).flags & ts.TypeFlags.Any) !== 0
|
||||
);
|
||||
}
|
||||
|
||||
/** The callback shape of a `promisify(fn)` call, or null. */
|
||||
@@ -144,10 +155,11 @@ export function lowerCallbackPromisify(
|
||||
if (shape === null) {
|
||||
// An untyped (`any`) function: Node's promisify at run time.
|
||||
if (!untypedPromisifyArgument(lowerer, call)) return null;
|
||||
const argNode = call.arguments.nodes[0];
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "util.promisifyDyn",
|
||||
args: [lowerer.lowerExprExpecting(call.arguments.nodes[0]!, DYN)],
|
||||
args: [argNode ? lowerer.lowerExprExpecting(argNode, DYN) : dynUndefinedExpr(loc)],
|
||||
type: DYN,
|
||||
loc,
|
||||
};
|
||||
|
||||
@@ -57,6 +57,95 @@ function emptyTupleSpread(lowerer: Lowerer, node: ts.Expression): boolean {
|
||||
return restTupleElementTypes(lowerer.typeOf(operand), lowerer.typeCtx)?.length === 0;
|
||||
}
|
||||
|
||||
/** A call's arguments as one checked-dynamic array, built in source order
|
||||
* into a fresh hidden local: each argument converts to dyn, and a spread
|
||||
* contributes the values its iteration yields (holes as undefined).
|
||||
* `lowerSpread` lowers a spread's operand; the default lowers it here. */
|
||||
export function dynArgumentPack(
|
||||
lowerer: Lowerer,
|
||||
argNodes: readonly ts.Expression[],
|
||||
loc: SrcLoc,
|
||||
options: {
|
||||
/** Pre-lowered values pushed before the arguments. */
|
||||
leading?: readonly IrExpr[] | undefined;
|
||||
blame?: ts.Node | undefined;
|
||||
lowerSpread?: (arg: ts.SpreadElement) => IrExpr;
|
||||
} = {},
|
||||
): { stmts: IrStmt[]; pack: () => IrExpr } {
|
||||
const blame = options.blame ?? argNodes[0]!;
|
||||
const lowerSpread =
|
||||
options.lowerSpread ?? ((arg: ts.SpreadElement) => lowerer.lowerExpr(arg.expression));
|
||||
const pack = lowerer.declareHiddenLocal("%spreadArgs", DYN);
|
||||
const ref = (): IrExpr => ({ kind: "varRef", localId: pack.id, type: DYN, loc });
|
||||
const stmts: IrStmt[] = [
|
||||
{
|
||||
kind: "varDecl",
|
||||
localId: pack.id,
|
||||
init: { kind: "dynArrLit", elems: [], type: DYN, loc },
|
||||
loc,
|
||||
},
|
||||
];
|
||||
const push = (value: IrExpr, fn: "dyn.packPush" | "dyn.packPushSpreadIter") => {
|
||||
stmts.push({
|
||||
kind: "exprStmt",
|
||||
expr: { kind: "libCall", fn, args: [ref(), value], type: VOID, loc: value.loc },
|
||||
loc: value.loc,
|
||||
});
|
||||
};
|
||||
for (const value of options.leading ?? [])
|
||||
push(lowerer.coerceInto(blame, value, DYN), "dyn.packPush");
|
||||
const spreads = argNodes.filter(ts.isSpreadElement).length;
|
||||
argNodes.forEach((arg, index) => {
|
||||
if (!ts.isSpreadElement(arg)) {
|
||||
push(lowerer.lowerExprExpecting(arg, DYN), "dyn.packPush");
|
||||
return;
|
||||
}
|
||||
let value = lowerSpread(arg);
|
||||
if (value.type.kind === "set") {
|
||||
value = {
|
||||
kind: "setIntrinsic",
|
||||
method: "toArray",
|
||||
receiver: value,
|
||||
args: [],
|
||||
type: arrayOf(value.type.elem),
|
||||
loc: value.loc,
|
||||
};
|
||||
} else if (value.type.kind === "object") {
|
||||
value = lowerer.classIteratorDrainCall(value, locOf(arg)) ?? value;
|
||||
}
|
||||
if (value.type.kind === "array") {
|
||||
// Argument iteration materializes holes as present undefined
|
||||
// before conversion to the dense checked-dynamic argument pack.
|
||||
value = {
|
||||
kind: "arrayLit",
|
||||
elems: [value],
|
||||
spreads: [0],
|
||||
type: value.type,
|
||||
loc: value.loc,
|
||||
};
|
||||
}
|
||||
value = checkedIterableSpread(lowerer, value, ts.getTextOfNode(arg.expression), loc);
|
||||
if (spreads === 1 && index === argNodes.length - 1) {
|
||||
stmts.push({
|
||||
kind: "exprStmt",
|
||||
expr: {
|
||||
kind: "libCall",
|
||||
fn: "dyn.packPushSpread",
|
||||
args: [
|
||||
ref(),
|
||||
value,
|
||||
{ kind: "strLit", value: ts.getTextOfNode(arg.expression), type: STRING, loc },
|
||||
],
|
||||
type: VOID,
|
||||
loc,
|
||||
},
|
||||
loc,
|
||||
});
|
||||
} else push(value, "dyn.packPushSpreadIter");
|
||||
});
|
||||
return { stmts, pack: ref };
|
||||
}
|
||||
|
||||
export function completeArgs(
|
||||
lowerer: Lowerer,
|
||||
argNodes: readonly ts.Expression[],
|
||||
@@ -97,72 +186,10 @@ export function completeArgs(
|
||||
// Runtime-length spreads determine the complete argument list before
|
||||
// any parameter default runs. Build it once in source order, then
|
||||
// extract the fixed native ABI slots; missing elements are undefined.
|
||||
const pack = lowerer.declareHiddenLocal("%spreadArgs", DYN);
|
||||
const ref = (): IrExpr => ({ kind: "varRef", localId: pack.id, type: DYN, loc });
|
||||
const stmts: IrStmt[] = [
|
||||
{
|
||||
kind: "varDecl",
|
||||
localId: pack.id,
|
||||
init: { kind: "dynArrLit", elems: [], type: DYN, loc },
|
||||
loc,
|
||||
},
|
||||
];
|
||||
const push = (value: IrExpr, fn: "dyn.packPush" | "dyn.packPushSpreadIter") => {
|
||||
stmts.push({
|
||||
kind: "exprStmt",
|
||||
expr: { kind: "libCall", fn, args: [ref(), value], type: VOID, loc: value.loc },
|
||||
loc: value.loc,
|
||||
});
|
||||
};
|
||||
for (const value of leading ?? []) push(lowerer.coerceInto(blame, value, DYN), "dyn.packPush");
|
||||
const spreads = argNodes.filter(ts.isSpreadElement).length;
|
||||
argNodes.forEach((arg, index) => {
|
||||
if (!ts.isSpreadElement(arg)) {
|
||||
push(lowerer.lowerExprExpecting(arg, DYN), "dyn.packPush");
|
||||
return;
|
||||
}
|
||||
let value = lowerSpread(arg);
|
||||
if (value.type.kind === "set") {
|
||||
value = {
|
||||
kind: "setIntrinsic",
|
||||
method: "toArray",
|
||||
receiver: value,
|
||||
args: [],
|
||||
type: arrayOf(value.type.elem),
|
||||
loc: value.loc,
|
||||
};
|
||||
} else if (value.type.kind === "object") {
|
||||
value = lowerer.classIteratorDrainCall(value, locOf(arg)) ?? value;
|
||||
}
|
||||
if (value.type.kind === "array") {
|
||||
// Argument iteration materializes holes as present undefined
|
||||
// before conversion to the dense checked-dynamic argument pack.
|
||||
value = {
|
||||
kind: "arrayLit",
|
||||
elems: [value],
|
||||
spreads: [0],
|
||||
type: value.type,
|
||||
loc: value.loc,
|
||||
};
|
||||
}
|
||||
value = checkedIterableSpread(lowerer, value, ts.getTextOfNode(arg.expression), loc);
|
||||
if (spreads === 1 && index === argNodes.length - 1) {
|
||||
stmts.push({
|
||||
kind: "exprStmt",
|
||||
expr: {
|
||||
kind: "libCall",
|
||||
fn: "dyn.packPushSpread",
|
||||
args: [
|
||||
ref(),
|
||||
value,
|
||||
{ kind: "strLit", value: ts.getTextOfNode(arg.expression), type: STRING, loc },
|
||||
],
|
||||
type: VOID,
|
||||
loc,
|
||||
},
|
||||
loc,
|
||||
});
|
||||
} else push(value, "dyn.packPushSpreadIter");
|
||||
const { stmts, pack: ref } = dynArgumentPack(lowerer, argNodes, loc, {
|
||||
leading,
|
||||
blame,
|
||||
lowerSpread,
|
||||
});
|
||||
const args = shapes.map((shape, index): IrExpr => {
|
||||
if (shape.mode === "arguments") return ref();
|
||||
|
||||
@@ -44,6 +44,7 @@ import {
|
||||
} from "../collection-ingestion.js";
|
||||
import { strCharsCall } from "./array-construction.js";
|
||||
import { arrayElementRead } from "./array-iteration.js";
|
||||
import { isMimeParamsIterable } from "../builtins/mime.js";
|
||||
import { isSearchParamsIterable } from "../builtins/url.js";
|
||||
import { classGeneratorIterable } from "../class-iterables.js";
|
||||
import { generatorDrain, lowerCollectionIteratorValue } from "../iterator-adapters.js";
|
||||
@@ -1271,8 +1272,8 @@ export function lowerMapSeedNew(
|
||||
);
|
||||
}
|
||||
// new Map(sp) / new Map(sp.entries()): the checked-dynamic view's live
|
||||
// pairs seed the map in order.
|
||||
if (isSearchParamsIterable(lowerer, argNode)) {
|
||||
// pairs seed the map in order, and so do a MIMEParams's entries.
|
||||
if (isSearchParamsIterable(lowerer, argNode) || isMimeParamsIterable(lowerer, argNode)) {
|
||||
return mapFromSeedValue(lowerer, lowerer.lowerExprExpecting(argNode, DYN), mapT);
|
||||
}
|
||||
const input = collectionInput(lowerer, argNode);
|
||||
|
||||
@@ -29,6 +29,7 @@ import { lowerTernary } from "./narrowing.js";
|
||||
import { lowerCollectionSpread } from "../containers/collection-methods.js";
|
||||
import { strCharsCall } from "../containers/array-construction.js";
|
||||
import { generatorDrain } from "../iterator-adapters.js";
|
||||
import { isMimeParamsIterable } from "../builtins/mime.js";
|
||||
import { isSearchParamsIterable } from "../builtins/url.js";
|
||||
import { dynUndefinedExpr, varRef } from "../../../ir/build.js";
|
||||
import { arrayValueStore, primitiveElement } from "../array-values.js";
|
||||
@@ -543,7 +544,9 @@ export function lowerArrayLiteral(
|
||||
// `[...sp]` and `[...sp.entries()]` over a URLSearchParams: its
|
||||
// checked-dynamic view iterates the live pair list, and the drained
|
||||
// [name, value] pairs convert to the literal's tuple elements.
|
||||
const searchParamsSource = isSearchParamsIterable(lowerer, el.expression);
|
||||
const searchParamsSource =
|
||||
isSearchParamsIterable(lowerer, el.expression) ||
|
||||
isMimeParamsIterable(lowerer, el.expression);
|
||||
if (src.type.kind === "searchParams") src = lowerer.coerceInto(el.expression, src, DYN);
|
||||
// A native checked iterable can supply scalar elements even when
|
||||
// the checker inferred a typed array from its producer. Drain once
|
||||
|
||||
@@ -1551,6 +1551,9 @@ export function lowerBinary(lowerer: Lowerer, expr: ts.BinaryExpression): IrExpr
|
||||
reference.kind === "map" ||
|
||||
reference.kind === "set" ||
|
||||
reference.kind === "union" ||
|
||||
// A promise of dyn boxes its own promise, so identity holds;
|
||||
// other promise types box a fresh adapter.
|
||||
(reference.kind === "promise" && reference.inner.kind === "dyn") ||
|
||||
DYN_HANDLE_KINDS.has(reference.kind)) &&
|
||||
lowerer.dynConvertible(reference)
|
||||
) {
|
||||
|
||||
@@ -34,7 +34,11 @@ import {
|
||||
} from "../../type-mapper.js";
|
||||
import { isSafeToRepeat } from "./evaluation-safety.js";
|
||||
import { tryLowerExpression } from "./try-lower-expression.js";
|
||||
import { fenceSymbolFieldCopy } from "../symbol-fields.js";
|
||||
import {
|
||||
classSymbolKeyOf,
|
||||
fenceSymbolFieldCopy,
|
||||
symbolFieldDisplayName,
|
||||
} from "../symbol-fields.js";
|
||||
import { planUnionRetag } from "../union-retag.js";
|
||||
import { jsHoldsDynObject, runtimeOptionalStorageOperand } from "../lower-exprs.js";
|
||||
import { builtinGlobalValue } from "./property-access.js";
|
||||
@@ -197,7 +201,19 @@ function propNameText(lowerer: Lowerer, name: ts.PropertyName): string {
|
||||
* (NamedEvaluation): an identifier, string or numeric key, or a computed
|
||||
* key that folds to one string. Null for other keys. */
|
||||
function propertyFunctionName(lowerer: Lowerer, name: ts.PropertyName): string | null {
|
||||
if (ts.isComputedPropertyName(name)) return literalComputedKey(lowerer, name);
|
||||
if (ts.isComputedPropertyName(name)) {
|
||||
const literal = literalComputedKey(lowerer, name);
|
||||
if (literal !== null) return literal;
|
||||
// A symbol key names the function `[description]`.
|
||||
let key = name.expression;
|
||||
while (ts.isParenthesizedExpression(key)) key = key.expression;
|
||||
const symbol = classSymbolKeyOf(lowerer, key);
|
||||
if (symbol) return `[${symbolFieldDisplayName(symbol.fieldName).slice("Symbol(".length, -1)}]`;
|
||||
const wellKnown = ts.isPropertyAccessExpression(key)
|
||||
? lowerer.stdlibGlobalMember(key, "Symbol")
|
||||
: null;
|
||||
return wellKnown ? `[Symbol.${wellKnown}]` : null;
|
||||
}
|
||||
if (ts.isNumericLiteral(name)) return String(Number(name.text));
|
||||
if (ts.isIdentifier(name) || ts.isStringLiteral(name)) return name.text;
|
||||
return null;
|
||||
|
||||
@@ -9,6 +9,7 @@ import * as ts from "../../ts7/adapter.js";
|
||||
import { lowerHistogramProperty } from "../builtins/performance.js";
|
||||
import { staticFunctionNameRead } from "../static-function-names.js";
|
||||
import { typedArgumentsArity } from "../typed-arguments.js";
|
||||
import { INSPECT_CUSTOM_DESCRIPTION, isInspectCustomAccess } from "../symbol-fields.js";
|
||||
import { isThisParameter } from "../call-signatures.js";
|
||||
import { argumentCountSlot, contextualCallCount, usedAsValue } from "../argument-counts.js";
|
||||
import {
|
||||
@@ -64,6 +65,7 @@ import {
|
||||
} from "../../../ir/ir.js";
|
||||
import { lowerNavigatorMember } from "../builtins/navigator.js";
|
||||
import { lowerX509PublicKey } from "../builtins/x509.js";
|
||||
import { fenceMimeStaticRead } from "../builtins/mime.js";
|
||||
import { NODE_COMPAT_VERSION } from "../builtins/process.js";
|
||||
import {
|
||||
inlineNamespaceRequireOf,
|
||||
@@ -338,6 +340,16 @@ export function lowerPropertyAccessExpr(
|
||||
// Cloudflare probe) folds like `navigator.userAgent`.
|
||||
const navigator = lowerNavigatorMember(lowerer, expr, NODE_COMPAT_VERSION);
|
||||
if (navigator) return navigator;
|
||||
// util.inspect.custom: Symbol.for('nodejs.util.inspect.custom').
|
||||
if (isInspectCustomAccess(lowerer, expr)) {
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "sym.for",
|
||||
args: [{ kind: "strLit", value: INSPECT_CUSTOM_DESCRIPTION, type: STRING, loc }],
|
||||
type: SYMBOL_T,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
{
|
||||
const member = lowerBufferPrototypeMember(lowerer, expr);
|
||||
if (member) return member;
|
||||
@@ -934,6 +946,7 @@ export function lowerPropertyAccessExpr(
|
||||
// prototype that no lowering above claimed keeps the SC2020 fence
|
||||
// rather than reading undefined through the field and dynamic reads.
|
||||
fenceBuiltinConstructorStaticRead(lowerer, expr) ??
|
||||
fenceMimeStaticRead(lowerer, expr) ??
|
||||
lowerer.lowerFieldRead(expr);
|
||||
// A union-typed field read narrows like an identifier when the
|
||||
// checker has narrowed this use to one arm.
|
||||
|
||||
@@ -39,6 +39,7 @@ import { lowerReadlineInstanceof } from "../builtins/readline.js";
|
||||
import { lowerKeyObjectInstanceof } from "../builtins/key-object.js";
|
||||
import { lowerCryptoKeyInstanceof } from "../builtins/webcrypto.js";
|
||||
import { lowerNetAddressInstanceof } from "../builtins/net-address.js";
|
||||
import { lowerMimeInstanceof } from "../builtins/mime.js";
|
||||
import { typedArrayHasMember } from "../builtins/typed-array-members.js";
|
||||
import { fenceEarlyNsMemberRef, nsMemberIdentOf } from "../lower-namespaces.js";
|
||||
import { maybePromiseInstanceOf } from "../maybe-promise.js";
|
||||
@@ -246,6 +247,8 @@ export function lowerInstanceOf(lowerer: Lowerer, expr: ts.BinaryExpression, loc
|
||||
if (cryptoKey) return cryptoKey;
|
||||
const netAddress = lowerNetAddressInstanceof(lowerer, expr);
|
||||
if (netAddress) return netAddress;
|
||||
const mime = lowerMimeInstanceof(lowerer, expr);
|
||||
if (mime) return mime;
|
||||
const readlineInterface = lowerReadlineInstanceof(lowerer, expr);
|
||||
if (readlineInterface) return readlineInterface;
|
||||
}
|
||||
|
||||
@@ -284,6 +284,28 @@ import { lowerRecordInheritedCall } from "./record-inherited.js";
|
||||
|
||||
const ZLIB_STREAM_METHODS: ReadonlySet<string> = new Set(["flush", "params", "close", "reset"]);
|
||||
|
||||
/** The node:util member a `(0, member)` callee names, or null. */
|
||||
function indirectUtilCalleeOf(
|
||||
lowerer: Lowerer,
|
||||
callee: ts.Expression,
|
||||
): { module: string; member: string } | null {
|
||||
let e = callee;
|
||||
while (ts.isParenthesizedExpression(e)) e = e.expression;
|
||||
if (
|
||||
!ts.isBinaryExpression(e) ||
|
||||
e.operatorToken.kind !== ts.SyntaxKind.CommaToken ||
|
||||
!ts.isNumericLiteral(e.left)
|
||||
)
|
||||
return null;
|
||||
const target = e.right;
|
||||
const bi = ts.isIdentifier(target)
|
||||
? lowerer.builtinImportOf(target)
|
||||
: ts.isPropertyAccessExpression(target) && !target.questionDotToken
|
||||
? lowerer.builtinMemberOf(target)
|
||||
: null;
|
||||
return bi?.module === "util" ? bi : null;
|
||||
}
|
||||
|
||||
/** `Object.defineProperty(target, ...)`, `Object.defineProperties`,
|
||||
* `Reflect.defineProperty`, and `target.__defineGetter__/__defineSetter__`
|
||||
* over a target that may hold an Error can install an accessor over an
|
||||
@@ -377,6 +399,14 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr {
|
||||
const codeGeneration = codeGenerationCalleeOf(lowerer, expr.expression);
|
||||
if (codeGeneration !== null) return lowerCodeGenerationCall(lowerer, expr, codeGeneration);
|
||||
}
|
||||
// `(0, util_1.inspect)(value)`: TypeScript's CommonJS output calls a
|
||||
// named import through a comma expression, which leaves the call's
|
||||
// receiver undefined. node:util's functions do not read their receiver,
|
||||
// so the call lowers as the direct one.
|
||||
{
|
||||
const util = indirectUtilCalleeOf(lowerer, expr.expression);
|
||||
if (util) return lowerBuiltinCall(lowerer, expr, util, loc);
|
||||
}
|
||||
// The deprecated Buffer(...) call (containers/bytes.ts). The checker
|
||||
// types it `any`, so the bytes cross as a checked-dynamic value, which
|
||||
// keeps the Buffer brand for inspection.
|
||||
|
||||
@@ -163,6 +163,7 @@ import {
|
||||
} from "./lower-event-emitter.js";
|
||||
import { declSymbolOf } from "./lower-modules.js";
|
||||
import {
|
||||
INSPECT_CUSTOM_MEMBER,
|
||||
classSymbolKeyOf,
|
||||
classSymbolKeyOfSymbol,
|
||||
symbolFieldDisplayName,
|
||||
@@ -4454,6 +4455,9 @@ export function collectClassShapeInner(
|
||||
...(methods.size > 0 ? { methods: [...methods.keys()] } : {}),
|
||||
...((): { prototypeNames?: string[] } => {
|
||||
const names = classPrototypeNames(decl);
|
||||
// A [util.inspect.custom] method, by its reserved slot name: the
|
||||
// checked-dynamic inspect walk refuses instances that have one.
|
||||
if (methods.has(INSPECT_CUSTOM_MEMBER)) names.push(INSPECT_CUSTOM_MEMBER);
|
||||
return names.length > 0 ? { prototypeNames: names } : {};
|
||||
})(),
|
||||
...(abstractClass ? { abstract: true as const } : {}),
|
||||
|
||||
@@ -14,7 +14,15 @@ import { boolLit, dynUndefinedExpr, numLit, strLit, varRef } from "../../ir/buil
|
||||
import { locOf } from "../program.js";
|
||||
import { type ConsoleMethodMember } from "./builtins/console.js";
|
||||
import { jsFuncNameOf, type Lowerer } from "./lowerer.js";
|
||||
import { lowerConsoleInspectArg, lowerFormatCall, lowerInspectValue } from "./lower-inspect.js";
|
||||
import {
|
||||
heldFormatArgs,
|
||||
lowerConsoleInspectArg,
|
||||
lowerFormatCall,
|
||||
lowerInspectValue,
|
||||
lowerRuntimeFormat,
|
||||
runtimeFormatString,
|
||||
formatStringConverts,
|
||||
} from "./lower-inspect.js";
|
||||
import { streamSidesOf } from "./lower-stream.js";
|
||||
|
||||
/** node:console Console instances: a native handle (the checked-dynamic
|
||||
@@ -358,9 +366,32 @@ export function lowerConsoleOutput(
|
||||
) {
|
||||
return consoleWrite(stdout, [lowerFormatCall(lowerer, expr, loc, false)], loc);
|
||||
}
|
||||
const args = expr.arguments.nodes.map((a) =>
|
||||
lowerConsoleArg(lowerer, a, lowerer.lowerExpr(a), surface, loc),
|
||||
);
|
||||
const nodes = expr.arguments.nodes;
|
||||
// Spread arguments and format strings that may hold a substitution at
|
||||
// run time go through the runtime formatter. A string first argument
|
||||
// without a '%' keeps the space-joined write below.
|
||||
if (nodes.some(ts.isSpreadElement))
|
||||
return consoleWrite(stdout, [lowerRuntimeFormat(lowerer, nodes, loc)], loc);
|
||||
const runtime =
|
||||
nodes.length > 1 && !isStringLiteralLike(first) ? runtimeFormatString(lowerer, first!) : null;
|
||||
if (runtime === "other" && !formatStringConverts(lowerer, first!))
|
||||
return consoleWrite(stdout, [lowerRuntimeFormat(lowerer, nodes, loc)], loc);
|
||||
if (runtime !== null) {
|
||||
const held = heldFormatArgs(lowerer, nodes, loc);
|
||||
if (!held.joinable) return seq(held.stmts, consoleWrite(stdout, [held.format()], loc), loc);
|
||||
const joined = held.args.map((arg) =>
|
||||
lowerConsoleArg(lowerer, arg.node, arg.ref(), surface, loc),
|
||||
);
|
||||
const write: IrStmt = {
|
||||
kind: "if",
|
||||
cond: held.specifies,
|
||||
then: [{ kind: "exprStmt", expr: consoleWrite(stdout, [held.format()], loc), loc }],
|
||||
else_: [{ kind: "exprStmt", expr: consoleWrite(stdout, joined, loc), loc }],
|
||||
loc,
|
||||
};
|
||||
return seq([...held.stmts, write], noop(loc), loc);
|
||||
}
|
||||
const args = nodes.map((a) => lowerConsoleArg(lowerer, a, lowerer.lowerExpr(a), surface, loc));
|
||||
return consoleWrite(stdout, args, loc);
|
||||
}
|
||||
|
||||
|
||||
@@ -167,6 +167,23 @@ function declareFunctionArguments(
|
||||
}
|
||||
}
|
||||
|
||||
/** The type parameters a lambda without its own takes from a generic
|
||||
* contextual signature, unbound in the current scope. */
|
||||
function contextualTypeParameterSymbols(
|
||||
lowerer: Lowerer,
|
||||
node: ts.ArrowFunction | ts.FunctionExpression,
|
||||
): ts.Symbol[] {
|
||||
const signatures = lowerer.checker.getCallSignatures(lowerer.typeOf(node));
|
||||
if (signatures.length !== 1) return [];
|
||||
const symbols: ts.Symbol[] = [];
|
||||
for (const parameter of signatures[0]!.getTypeParameters()) {
|
||||
const symbol = parameter.getSymbol();
|
||||
if (!symbol || lowerer.typeParamBindings?.has(symbol)) return [];
|
||||
symbols.push(symbol);
|
||||
}
|
||||
return symbols;
|
||||
}
|
||||
|
||||
/** Lifts an arrow function / function expression / nested declaration /
|
||||
* object-literal shorthand method to a module-level function and yields
|
||||
* the `closure` expression creating it. */
|
||||
@@ -202,6 +219,31 @@ export function lowerLambda(
|
||||
lowerer.typeParamBindings = previous;
|
||||
}
|
||||
}
|
||||
// A lambda contextually typed by a GENERIC signature (`send: (m) => …`
|
||||
// for `send<T>(m: Msg<T>): string`) takes that signature's type
|
||||
// parameters as its own; it lowers once, erased, like a generic lambda.
|
||||
if (
|
||||
(ts.isArrowFunction(node) || ts.isFunctionExpression(node)) &&
|
||||
!node.typeParameters?.nodes.length &&
|
||||
!lowerer.erasingLambdas.has(node)
|
||||
) {
|
||||
const contextual = contextualTypeParameterSymbols(lowerer, node);
|
||||
if (contextual.length > 0) {
|
||||
const previous = lowerer.typeParamBindings;
|
||||
const bindings = new Map(previous ?? []);
|
||||
for (const symbol of contextual) bindings.set(symbol, DYN);
|
||||
lowerer.typeParamBindings = bindings;
|
||||
lowerer.erasingLambdas.add(node);
|
||||
lowerer.erasingDepth++;
|
||||
try {
|
||||
return lowerLambda(lowerer, node, checkedReceiver);
|
||||
} finally {
|
||||
lowerer.erasingDepth--;
|
||||
lowerer.erasingLambdas.delete(node);
|
||||
lowerer.typeParamBindings = previous;
|
||||
}
|
||||
}
|
||||
}
|
||||
const prepared =
|
||||
ts.isArrowFunction(node) || ts.isFunctionExpression(node)
|
||||
? lowerer.preparedClassFactories.get(node)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -97,6 +97,7 @@ import {
|
||||
createRequireProgramModuleDecl,
|
||||
} from "./builtins/module-bindings.js";
|
||||
import { isPromisifyCall, registerBuiltinCallableAlias } from "./builtins/callable-aliases.js";
|
||||
import { customInspectRegistrations } from "./lower-inspect.js";
|
||||
import { registerBuiltinClassAlias } from "./builtins/class-values.js";
|
||||
import { stripTypeCasts } from "./builtins/arguments.js";
|
||||
import { isWebCryptoMemberType } from "./builtins/webcrypto.js";
|
||||
@@ -1973,18 +1974,21 @@ export function collectGlobals(
|
||||
// symbol registers HERE, before any use site can lower, and the
|
||||
// statement lowering skips the declaration by the same check. A
|
||||
// bad promisify target reports here (recorded once; the statement
|
||||
// lowering poisons too, like every collectGlobals failure).
|
||||
// lowering poisons too, like every collectGlobals failure). Targets
|
||||
// that promisify to a function value (typed callback-last functions
|
||||
// and untyped functions) keep an ordinary global.
|
||||
if (
|
||||
ts.isIdentifier(decl.name) &&
|
||||
decl.initializer &&
|
||||
isPromisifyCall(lowerer, decl.initializer)
|
||||
) {
|
||||
let registered = true;
|
||||
try {
|
||||
lowerer.registerPromisifiedBuiltinDecl(decl.name, decl.initializer);
|
||||
registered = lowerer.registerPromisifiedBuiltinDecl(decl.name, decl.initializer);
|
||||
} catch (e) {
|
||||
if (!(e instanceof PoisonError)) throw e;
|
||||
}
|
||||
continue;
|
||||
if (registered) continue;
|
||||
}
|
||||
// `const inspect = require("util").inspect` at file scope: a
|
||||
// named import in const clothing (builtinImportOf resolves uses)
|
||||
@@ -3549,6 +3553,9 @@ export function buildMain(lowerer: Lowerer): IrFunction {
|
||||
loc,
|
||||
});
|
||||
}
|
||||
// Classes with custom inspect methods register their compiled calls for
|
||||
// the runtime's inspect walk before any module runs, in every thread.
|
||||
body.unshift(...customInspectRegistrations(lowerer, loc));
|
||||
// Node's startup refusal (a resolution the graph carries that Node
|
||||
// rejects — preflight's Node-order resolution walk — or a module-LINK
|
||||
// SyntaxError from either named-import checker): the graph is refused
|
||||
|
||||
@@ -69,6 +69,7 @@ import { isDiffieHellmanType, lowerDiffieHellmanCall } from "./builtins/dh.js";
|
||||
import { fenceWebCryptoMember, lowerWebCryptoCall } from "./builtins/webcrypto.js";
|
||||
import { valueStreamOf } from "./builtins/hash-stream.js";
|
||||
import { lowerNetAddressCall, lowerNetAddressNew } from "./builtins/net-address.js";
|
||||
import { lowerMimeCall, lowerMimeNew } from "./builtins/mime.js";
|
||||
import { dynUndefinedExpr, nodeThrowExpr, numLit, varRef } from "../../ir/build.js";
|
||||
import { iteratorSlotAdapter } from "./iterator-adapters.js";
|
||||
import { iterableSlotValue } from "./iterable-values.js";
|
||||
@@ -177,7 +178,7 @@ import {
|
||||
import { DeferredModuleInitializers } from "./deferred-module-initializers.js";
|
||||
import { finalizeClassMethodValues } from "./class-method-values.js";
|
||||
import { finalizePrivateBrands } from "./private-brands.js";
|
||||
import { type ClassSymbolKey, tableKeyFieldType } from "./symbol-fields.js";
|
||||
import { type ClassSymbolKey, INSPECT_CUSTOM_MEMBER, tableKeyFieldType } from "./symbol-fields.js";
|
||||
import type { HttpClientFnBinding } from "./lower-server.js";
|
||||
export type { WidthLift } from "./width-lift.js";
|
||||
export { newFnCtx, type FnCtx } from "./function-context.js";
|
||||
@@ -8689,6 +8690,19 @@ export class Lowerer {
|
||||
this.constructedClasses.add(name.slice(1, -".constructor".length));
|
||||
for (const [owner, methods] of this.virtualEdges)
|
||||
for (const method of methods) this.noteVirtualEdge(owner, method);
|
||||
// An instance's [util.inspect.custom] method runs from the runtime
|
||||
// inspect walk (customInspectRegistrations), which no IR edge shows.
|
||||
for (
|
||||
let c = this.classes.get(name.slice(1, -".constructor".length)) ?? null;
|
||||
c;
|
||||
c = c.base
|
||||
) {
|
||||
const custom = `%${c.def.name}.${INSPECT_CUSTOM_MEMBER}`;
|
||||
if (units.has(custom)) {
|
||||
this.onEdge?.(custom);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
// Class EXPRESSIONS collect while init bodies lower (below): their
|
||||
@@ -13850,6 +13864,7 @@ export class Lowerer {
|
||||
lowerEcdhCall(this, expr) ??
|
||||
lowerDiffieHellmanCall(this, expr) ??
|
||||
lowerNetAddressCall(this, expr) ??
|
||||
lowerMimeCall(this, expr) ??
|
||||
lowerFetchElementMethodCall(this, expr) ??
|
||||
lowerDynamicImportCall(this, expr) ??
|
||||
lowerCall(this, expr)
|
||||
@@ -14087,6 +14102,7 @@ export class Lowerer {
|
||||
lowerResponseNew(this, expr) ??
|
||||
lowerBlobNew(this, expr) ??
|
||||
lowerNetAddressNew(this, expr) ??
|
||||
lowerMimeNew(this, expr) ??
|
||||
lowerStaticReadableStreamNew(this, expr) ??
|
||||
lowerStaticTransformStreamNew(this, expr) ??
|
||||
lowerConsoleNew(this, expr);
|
||||
@@ -14531,6 +14547,10 @@ export class Lowerer {
|
||||
const assertion = assertFunctionValue(bi, loc);
|
||||
if (assertion) return assertion;
|
||||
if (bi.module === "util" && bi.member === "types") return utilTypesModuleValue(this, loc);
|
||||
// util.inspect: the function custom inspect methods receive, one
|
||||
// identity per thread.
|
||||
if (bi.module === "util" && bi.member === "inspect")
|
||||
return { kind: "libCall", fn: "insp.inspectFunction", args: [], type: DYN, loc };
|
||||
if (bi.module === "util/types" && builtinModuleFnOf(this, bi.module, bi.member))
|
||||
return lowerUtilTypeValue(this, bi.member, loc);
|
||||
if (bi.module === "util" && bi.member === "getSystemErrorMap")
|
||||
|
||||
@@ -1740,7 +1740,8 @@ function buildBuiltinModuleFns(): BuiltinModuleFnTable {
|
||||
// lowerVarDecl / collectGlobals BEFORE any call lowering runs — and
|
||||
// inspect/format/formatWithOptions plus parseArgs through the util spoke
|
||||
// (lower-inspect.ts: per-type synthesized traversal helpers,
|
||||
// compile-time format strings, checked-dynamic config/result for
|
||||
// compile-time format strings and the runtime formatter for the others,
|
||||
// checked-dynamic config/result for
|
||||
// parseArgs), which both dispatch paths try before the generic table.
|
||||
// parseArgs is tabled as well so the generated surface manifest records
|
||||
// the dedicated static member; the spoke owns its optional arity and
|
||||
@@ -1767,6 +1768,67 @@ function buildBuiltinModuleFns(): BuiltinModuleFnTable {
|
||||
],
|
||||
},
|
||||
parseArgs: { fn: "util.parseArgs", params: [DYN], result: DYN },
|
||||
aborted: {
|
||||
fn: "util.aborted",
|
||||
params: [DYN, DYN],
|
||||
result: DYN,
|
||||
valueParams: [
|
||||
{ mode: "optional", type: DYN, defaultValue: undefined },
|
||||
{ mode: "optional", type: DYN, defaultValue: undefined },
|
||||
],
|
||||
},
|
||||
diff: {
|
||||
fn: "util.diff",
|
||||
params: [DYN, DYN],
|
||||
result: DYN,
|
||||
valueParams: [
|
||||
{ mode: "optional", type: DYN, defaultValue: undefined },
|
||||
{ mode: "optional", type: DYN, defaultValue: undefined },
|
||||
],
|
||||
},
|
||||
debounce: {
|
||||
fn: "util.debounce",
|
||||
params: [DYN, DYN, DYN],
|
||||
result: DYN,
|
||||
valueParams: [
|
||||
{ mode: "optional", type: DYN, defaultValue: undefined },
|
||||
{ mode: "optional", type: DYN, defaultValue: undefined },
|
||||
{ mode: "optional", type: DYN, defaultValue: undefined },
|
||||
],
|
||||
},
|
||||
throttle: {
|
||||
fn: "util.throttle",
|
||||
params: [DYN, DYN, DYN, DYN],
|
||||
result: DYN,
|
||||
valueParams: [
|
||||
{ mode: "optional", type: DYN, defaultValue: undefined },
|
||||
{ mode: "optional", type: DYN, defaultValue: undefined },
|
||||
{ mode: "optional", type: DYN, defaultValue: undefined },
|
||||
{ mode: "optional", type: DYN, defaultValue: undefined },
|
||||
],
|
||||
},
|
||||
convertProcessSignalToExitCode: {
|
||||
fn: "util.signalExitCode",
|
||||
params: [DYN],
|
||||
result: F64,
|
||||
valueParams: [{ mode: "optional", type: DYN, defaultValue: undefined }],
|
||||
},
|
||||
// The deprecated helpers report DEP0044 and DEP0060 on their first call.
|
||||
isArray: {
|
||||
fn: "util.isArrayDeprecated",
|
||||
params: [DYN],
|
||||
result: BOOL,
|
||||
valueParams: [{ mode: "optional", type: DYN, defaultValue: undefined }],
|
||||
},
|
||||
_extend: {
|
||||
fn: "util.extend",
|
||||
params: [DYN, DYN],
|
||||
result: DYN,
|
||||
valueParams: [
|
||||
{ mode: "optional", type: DYN, defaultValue: undefined },
|
||||
{ mode: "optional", type: DYN, defaultValue: undefined },
|
||||
],
|
||||
},
|
||||
parseEnv: { fn: "util.parseEnv", params: [DYN], result: DYN },
|
||||
getSystemErrorName: {
|
||||
fn: "util.getSystemErrorName",
|
||||
|
||||
@@ -76,7 +76,36 @@ export function fenceSymbolFieldCopy(
|
||||
}
|
||||
}
|
||||
|
||||
/** The registry name util.inspect.custom denotes. */
|
||||
export const INSPECT_CUSTOM_DESCRIPTION = "nodejs.util.inspect.custom";
|
||||
/** The method slot of a class's `[util.inspect.custom]` member. */
|
||||
export const INSPECT_CUSTOM_MEMBER = `${FIELD_PREFIX}Symbol(${INSPECT_CUSTOM_DESCRIPTION})`;
|
||||
|
||||
/** `inspect.custom` over node:util's inspect: a named import, `util.inspect`
|
||||
* or the CommonJS `util_1.inspect`. */
|
||||
export function isInspectCustomAccess(lowerer: Lowerer, expr: ts.Expression): boolean {
|
||||
while (ts.isParenthesizedExpression(expr)) expr = expr.expression;
|
||||
if (!ts.isPropertyAccessExpression(expr) || expr.questionDotToken) return false;
|
||||
if (!ts.isIdentifier(expr.name) || expr.name.text !== "custom") return false;
|
||||
let target = expr.expression;
|
||||
while (ts.isParenthesizedExpression(target)) target = target.expression;
|
||||
const bi = ts.isIdentifier(target)
|
||||
? lowerer.builtinImportOf(target)
|
||||
: ts.isPropertyAccessExpression(target) && !target.questionDotToken
|
||||
? lowerer.builtinMemberOf(target)
|
||||
: null;
|
||||
return bi?.module === "util" && bi.member === "inspect";
|
||||
}
|
||||
|
||||
export function classSymbolKeyOf(lowerer: Lowerer, key: ts.Expression): ClassSymbolKey | null {
|
||||
// util.inspect.custom is the registered Symbol.for(INSPECT_CUSTOM_DESCRIPTION),
|
||||
// one identity with the program's own Symbol.for calls of that name.
|
||||
if (isInspectCustomAccess(lowerer, key)) {
|
||||
let e = key;
|
||||
while (ts.isParenthesizedExpression(e)) e = e.expression;
|
||||
const sym = lowerer.checker.getSymbolAtLocation((e as ts.PropertyAccessExpression).name);
|
||||
return sym ? symbolKeyOf(lowerer, sym, true, INSPECT_CUSTOM_DESCRIPTION) : null;
|
||||
}
|
||||
const wellKnown = wellKnownSymbolKeyOf(lowerer, key);
|
||||
if (wellKnown !== undefined) return wellKnown;
|
||||
if (
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
isDynTypedRefType,
|
||||
BOOL,
|
||||
bytesOf,
|
||||
canBoxFuncIntoDyn,
|
||||
canConvertToDyn,
|
||||
canDynCheckTo,
|
||||
CHILD_T,
|
||||
@@ -717,6 +718,12 @@ export class UnionRegistry {
|
||||
return id !== undefined && this.pendingRec.has(id);
|
||||
}
|
||||
|
||||
/** True when `unionId` is a recursive placeholder whose arms are not
|
||||
* known yet (its outer frame is still mapping, or failed). */
|
||||
isPendingPlaceholder(unionId: string): boolean {
|
||||
return this.pendingRec.has(unionId);
|
||||
}
|
||||
|
||||
/** Completes a recursive placeholder with its canonical arm list and
|
||||
* registers the structural key (first writer wins, like shapes). */
|
||||
finalizeRecursive(t: ts.Type, arms: IrType[], discriminant?: IrUnionDiscriminant): string {
|
||||
@@ -3029,6 +3036,30 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null {
|
||||
// spawn consumes a presence-sensitive option bag, including optional
|
||||
// stdio and env values. Keep it checked-dynamic for native normalization.
|
||||
if (isStdlibInterface("SpawnOptions")) return DYN;
|
||||
// util.inspect as a value (a custom inspect method's third parameter is
|
||||
// `typeof inspect`): the runtime's inspect function, called through the
|
||||
// checked-dynamic boundary.
|
||||
if (
|
||||
psym !== undefined &&
|
||||
ts.symbolName(psym) === "inspect" &&
|
||||
psym.declarations.some(
|
||||
(d) =>
|
||||
ts.isFunctionDeclaration(d) &&
|
||||
ctx.isStdlibFile(ts.getSourceFileOfNode(d)) &&
|
||||
isDeclaredInAmbientModule(d, "util"),
|
||||
)
|
||||
)
|
||||
return DYN;
|
||||
// util.inspect's options bag, which inspect and formatWithOptions read
|
||||
// at run time (its sorted option can be a comparator function), and the
|
||||
// options custom inspect functions receive.
|
||||
if (
|
||||
(isStdlibInterface("InspectOptions") || isStdlibInterface("InspectOptionsStylized")) &&
|
||||
psym!.declarations.some(
|
||||
(d) => ts.isInterfaceDeclaration(d) && isDeclaredInAmbientModule(d, "util"),
|
||||
)
|
||||
)
|
||||
return DYN;
|
||||
// dns.LookupAllOptions (`all: true`, the overload whose callback takes
|
||||
// an address list): a lookup option bag dns.lookup reads at run time.
|
||||
// It rides checked-dynamic storage, so the lookup function type that
|
||||
@@ -3092,11 +3123,14 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null {
|
||||
if (!elem) return null;
|
||||
return genResultRecord(elem, VOID, ctx.shapes, ctx.unions);
|
||||
}
|
||||
// node:net's SocketAddress and BlockList: native handles (scr_net.c)
|
||||
// whose getters and methods dispatch through the handle.
|
||||
// node:net's SocketAddress and BlockList (scr_net.c) and node:util's
|
||||
// MIMEType and MIMEParams (scr_mime.c): native handles whose getters and
|
||||
// methods dispatch through the handle.
|
||||
if (
|
||||
((psym !== undefined && ts.symbolName(psym) === "SocketAddress") ||
|
||||
(psym !== undefined && ts.symbolName(psym) === "BlockList")) &&
|
||||
(psym !== undefined && ts.symbolName(psym) === "BlockList") ||
|
||||
(psym !== undefined && ts.symbolName(psym) === "MIMEType") ||
|
||||
(psym !== undefined && ts.symbolName(psym) === "MIMEParams")) &&
|
||||
psym.declarations.some(
|
||||
(d) =>
|
||||
(ts.isClassDeclaration(d) || ts.isInterfaceDeclaration(d)) &&
|
||||
@@ -3105,6 +3139,18 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null {
|
||||
) {
|
||||
return DYN;
|
||||
}
|
||||
// The functions node:util's debounce and throttle return
|
||||
// (scr_util_timing.c): native functions with their own members and
|
||||
// getters.
|
||||
if (
|
||||
psym !== undefined &&
|
||||
(ts.symbolName(psym) === "DebouncedFunction" || ts.symbolName(psym) === "ThrottledFunction") &&
|
||||
psym.declarations.some(
|
||||
(d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)),
|
||||
)
|
||||
) {
|
||||
return DYN;
|
||||
}
|
||||
// WebCrypto: the crypto global and its subtle object are dynamic
|
||||
// objects, CryptoKey values are native handles (scr_webcrypto.c), and
|
||||
// the algorithm dictionaries the methods take and keys report are plain
|
||||
@@ -5335,8 +5381,19 @@ function mapCallSignature(sig: ts.Signature, ctx: TypeMapperCtx): IrType | null
|
||||
if (pt) pt = jsOpenObjectType(decl, pt, ctx.shapes, ctx.unions);
|
||||
// Belt and braces for non-strict type worlds: an optional param's
|
||||
// ABI slot is always the undefined-armed union (strictNullChecks
|
||||
// already spells it that way; arm it here if the world didn't).
|
||||
if (optional && pt && pt.kind !== "void" && pt.kind !== "dyn") {
|
||||
// already spells it that way; arm it here if the world didn't). A
|
||||
// back-reference to a union still being mapped (`options?: { then?:
|
||||
// (ctx: Sub) => void }` where Sub extends the interface declaring the
|
||||
// method) is a recursive placeholder with no arms yet; when the checker
|
||||
// spelled the parameter `X | undefined`, the placeholder receives that
|
||||
// undefined arm when its frame finishes.
|
||||
const pendingOptional =
|
||||
pt?.kind === "union" &&
|
||||
ctx.unions.isPendingPlaceholder(pt.unionId) &&
|
||||
ts
|
||||
.constituentTypes(checker.getTypeOfSymbol(p))
|
||||
.some((part) => (part.flags & ts.TypeFlags.Undefined) !== 0);
|
||||
if (optional && pt && pt.kind !== "void" && pt.kind !== "dyn" && !pendingOptional) {
|
||||
const armed = withUndefinedArm(pt, ctx.unions);
|
||||
if (!armed) return null;
|
||||
pt = armed;
|
||||
@@ -8398,6 +8455,16 @@ function dynSubsumableUnionArm(arm: IrType, ctx: TypeMapperCtx): boolean {
|
||||
// `string | URL | Request`, fetch's own input type under @types/node.
|
||||
case "url":
|
||||
return true;
|
||||
// A function the checked-dynamic tree can box (each parameter checks
|
||||
// in, the result converts out) is held by reference there, and
|
||||
// `typeof x === "function"` narrows it back out: `Progress | AbortSignal`
|
||||
// beside fetch's handles.
|
||||
case "func":
|
||||
return canBoxFuncIntoDyn(
|
||||
arm,
|
||||
(id) => ctx.shapes.get(id),
|
||||
(id) => ctx.unions.get(id),
|
||||
);
|
||||
case "record":
|
||||
case "array":
|
||||
// `T | Promise<T>` with T bound to `unknown`: the checked-dynamic value
|
||||
|
||||
@@ -650,6 +650,8 @@ async function compileExecutableNative(
|
||||
...(features.declAdopt ? { declAdopt: true } : {}),
|
||||
...(features.dynForAwait ? { dynForAwait: true } : {}),
|
||||
...(features.eventsOnceSignal ? { eventsOnceSignal: true } : {}),
|
||||
...(features.mime ? { mime: true } : {}),
|
||||
...(features.utilTiming ? { utilTiming: true } : {}),
|
||||
...(features.dynBuffer ? { dynBuffer: true } : {}),
|
||||
...(features.builtinValues ? { builtinValues: true } : {}),
|
||||
events: features.events,
|
||||
|
||||
@@ -185,6 +185,16 @@ export const MAY_THROW_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
|
||||
"num.toStringRadix",
|
||||
"num.toStringRadixF64",
|
||||
"insp.jsonDyn",
|
||||
// The runtime formatter calls toString, valueOf and toJSON, and refuses
|
||||
// renderings it has no lowering for.
|
||||
"insp.format",
|
||||
"insp.formatList",
|
||||
"insp.formatWithOptions",
|
||||
"insp.inspect",
|
||||
// The options object reads the caller's options (getters included), and
|
||||
// the checked-dynamic walk calls custom inspect functions.
|
||||
"insp.customOptions",
|
||||
"insp.customResult",
|
||||
// A checked-dynamic value the inspector does not render (a promise, a
|
||||
// URL, a RegExp) throws the 'not supported yet' Error.
|
||||
"insp.dyn",
|
||||
@@ -599,6 +609,13 @@ export const MAY_THROW_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
|
||||
"util.inherits",
|
||||
"util.inheritsPrototype",
|
||||
"util.deprecate",
|
||||
"util.extend",
|
||||
"util.signalExitCode",
|
||||
"util.diff",
|
||||
"util.debounce",
|
||||
"util.throttle",
|
||||
"mime.typeNew",
|
||||
"mime.typeParse",
|
||||
"util.callbackify",
|
||||
"util.promisifyDyn",
|
||||
"util.debuglog",
|
||||
|
||||
@@ -1156,6 +1156,17 @@ function buildLibFnSignatures() {
|
||||
"util.inherits": { argTypes: [DYN, DYN], result: VOID },
|
||||
"util.inheritsPrototype": { argTypes: [DYN, DYN, DYN], result: VOID },
|
||||
"util.deprecate": { argTypes: [DYN, DYN, DYN, DYN], result: DYN },
|
||||
"util.isArrayDeprecated": { argTypes: [DYN], result: BOOL },
|
||||
"util.extend": { argTypes: [DYN, DYN], result: DYN },
|
||||
"util.signalExitCode": { argTypes: [DYN], result: F64 },
|
||||
"util.aborted": { argTypes: [DYN, DYN], result: DYN },
|
||||
"util.diff": { argTypes: [DYN, DYN], result: DYN },
|
||||
"util.debounce": { argTypes: [DYN, DYN, DYN], result: DYN },
|
||||
"util.throttle": { argTypes: [DYN, DYN, DYN, DYN], result: DYN },
|
||||
"mime.typeNew": { argTypes: [DYN, DYN], result: DYN },
|
||||
"mime.typeParse": { argTypes: [DYN], result: DYN },
|
||||
"mime.paramsNew": { argTypes: [], result: DYN },
|
||||
"mime.is": { argTypes: [DYN, F64], result: BOOL },
|
||||
"util.callbackify": { argTypes: [DYN], result: DYN },
|
||||
/** util.promisify over an untyped function value: a native dyn function
|
||||
* whose calls answer a dyn promise the appended callback settles. */
|
||||
@@ -4600,12 +4611,54 @@ function buildLibFnSignatures() {
|
||||
"insp.errorCause": { argTypes: [null, F64, F64], result: STRING },
|
||||
"insp.dyn": { argTypes: [DYN, F64, F64], result: STRING },
|
||||
"insp.dynS": { argTypes: [DYN, F64], result: STRING },
|
||||
/** util.format over a dyn array of its arguments, and
|
||||
* util.formatWithOptions(options, pack): the runtime formatter for format
|
||||
* strings known at run time and spread arguments. */
|
||||
"insp.format": { argTypes: [DYN], result: STRING },
|
||||
/** insp.format over arguments that are all strings, numbers, booleans,
|
||||
* null or undefined, without the checked-dynamic walk. */
|
||||
"insp.formatPrimitives": { argTypes: [arrayOf(DYN)], result: STRING },
|
||||
"insp.formatList": { argTypes: [arrayOf(DYN)], result: STRING },
|
||||
"insp.formatWithOptions": { argTypes: [DYN, DYN], result: STRING },
|
||||
/** util.inspect(value, options, depth, colors) with the call's argument
|
||||
* count: the runtime reads the options and renders the checked-dynamic
|
||||
* view. */
|
||||
"insp.inspect": { argTypes: [DYN, DYN, DYN, DYN, F64], result: STRING },
|
||||
/** A [util.inspect.custom] method's call: whether ctx.customInspect is
|
||||
* set, its depth argument (the remaining depth at recursion r of depth
|
||||
* d), its options object and util.inspect, then its result rendered: a
|
||||
* string re-indented at the current level, or a checked-dynamic value
|
||||
* (customIsSelf tests for the instance itself). customInspect sets the
|
||||
* flag for the renders that follow and answers the previous value, and
|
||||
* depthKind does the same for how an unbounded depth was given (0
|
||||
* Infinity, 1 null, 2 undefined). */
|
||||
"insp.customEnabled": { argTypes: [], result: BOOL },
|
||||
/** Registers a class's compiled custom inspect function under its
|
||||
* instances' typed-ref type key, for the checked-dynamic walk. */
|
||||
"insp.registerCustom": { argTypes: [STRING, DYN], result: VOID },
|
||||
"insp.customInspect": { argTypes: [BOOL], result: BOOL },
|
||||
"insp.depthKind": { argTypes: [F64], result: F64 },
|
||||
"insp.customDepth": { argTypes: [F64, F64], result: DYN },
|
||||
"insp.customOptions": { argTypes: [F64], result: DYN },
|
||||
"insp.inspectFunction": { argTypes: [], result: DYN },
|
||||
"insp.customText": { argTypes: [STRING], result: STRING },
|
||||
"insp.customIsSelf": { argTypes: [DYN, DYN], result: BOOL },
|
||||
"insp.customResult": { argTypes: [DYN, F64, F64], result: STRING },
|
||||
/** Around the method's call: the inspect and format calls it makes run
|
||||
* in a fresh inspect context with the default options. begin answers the
|
||||
* token end takes; end also discards what a call that threw left. */
|
||||
"insp.customCallBegin": { argTypes: [], result: F64 },
|
||||
"insp.customCallEnd": { argTypes: [F64], result: VOID },
|
||||
/** Whether a runtime format string has a '%' before its last character. */
|
||||
"insp.formatSpecifies": { argTypes: [STRING], result: BOOL },
|
||||
"insp.formatSpecifiesDyn": { argTypes: [DYN], result: BOOL },
|
||||
"insp.dynFormatS": { argTypes: [DYN, F64], result: STRING },
|
||||
/** util.format's %o over a checked-dynamic value: inspect with
|
||||
* showHidden. Kinds whose hidden entries have no rendering throw the
|
||||
* 'not supported yet' Error. */
|
||||
"insp.dynHidden": { argTypes: [DYN, F64], result: STRING },
|
||||
"insp.begin": { argTypes: [F64], result: VOID },
|
||||
/** util.inspect's breakLength, compact (0 for false) and sorted for the
|
||||
/** util.inspect's breakLength, compact (-1 for false) and sorted for the
|
||||
* renders that follow; the call site restores 80, 3 and false afterwards. */
|
||||
"insp.layout": { argTypes: [F64, F64, BOOL], result: VOID },
|
||||
"insp.entry": { argTypes: [STRING, BOOL], result: VOID },
|
||||
|
||||
@@ -575,6 +575,19 @@ const DYN_READ_ONLY = new Set<string>([
|
||||
"buffer.poolFromDynRange",
|
||||
"insp.dyn",
|
||||
"insp.dynS",
|
||||
// util.format and util.inspect over checked values: the inspect walk and
|
||||
// the format conversions read them; insp.registerCustom keeps a class's
|
||||
// custom inspect thunk in the runtime's registry.
|
||||
"insp.customIsSelf",
|
||||
"insp.customResult",
|
||||
"insp.format",
|
||||
"insp.formatList",
|
||||
"insp.formatPrimitives",
|
||||
"insp.formatSpecifiesDyn",
|
||||
"insp.formatWithOptions",
|
||||
"insp.inspect",
|
||||
"insp.registerCustom",
|
||||
"insp.dynFormatS",
|
||||
"insp.dynHidden",
|
||||
// The dynamic-tier operators run their dyn twins (scr_js_value.c); the
|
||||
// conversions move a value between dyn and jsval, and js.toF64 is
|
||||
|
||||
@@ -618,6 +618,28 @@ function writesLocal(body: IrStmt[], localId: string): boolean {
|
||||
});
|
||||
}
|
||||
|
||||
/** The counter of a `for` loop that its condition reads and its update
|
||||
* steps by a constant, when nothing else in the loop writes it: each test
|
||||
* of the condition then sees a different value. The local must not be
|
||||
* boxed (a closure could write it). */
|
||||
export function steppedLoopCounter(
|
||||
stmt: IrStmt & { kind: "for" },
|
||||
locals: ReadonlyMap<string, IrLocal>,
|
||||
): { ref: IrExpr & { kind: "varRef" }; step: number } | null {
|
||||
const cond = stmt.cond;
|
||||
if (cond?.kind !== "bin") return null;
|
||||
for (const side of [cond.left, cond.right]) {
|
||||
if (side.kind !== "varRef") continue;
|
||||
const local = locals.get(side.localId);
|
||||
if (local === undefined || local.boxed) continue;
|
||||
const step = constantStep(stmt.update, side.localId);
|
||||
if (step === null || writesLocal(stmt.body, side.localId)) continue;
|
||||
if (writesLocal([{ kind: "exprStmt", expr: cond, loc: cond.loc }], side.localId)) continue;
|
||||
return { ref: side, step };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function isUnitIncrement(update: IrStmt | null, localId: string): boolean {
|
||||
// The frontend normally lowers `i++` to `i = i + 1` before backend
|
||||
// emission. Accept the incDec form too so this analysis remains valid for
|
||||
|
||||
@@ -103,6 +103,11 @@ const DYN_ASYNC_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
|
||||
"punycode.warn",
|
||||
// DEP0005 for the deprecated Buffer constructor.
|
||||
"buffer.deprecation",
|
||||
// util.aborted answers a checked-dynamic promise, and so do the
|
||||
// functions util.debounce and util.throttle return.
|
||||
"util.aborted",
|
||||
"util.debounce",
|
||||
"util.throttle",
|
||||
"timers.immediatePromise",
|
||||
// The performance timeline (scr_perf.c, linked with this unit for its
|
||||
// MaxPerformanceEntryBufferExceededWarning).
|
||||
@@ -144,8 +149,11 @@ const DYN_ASYNC_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
|
||||
"util.promisifyDyn",
|
||||
"util.debuglog",
|
||||
"util.deprecate",
|
||||
"util.diff",
|
||||
"util.extend",
|
||||
"util.inherits",
|
||||
"util.inheritsPrototype",
|
||||
"util.isArrayDeprecated",
|
||||
"util.styleText",
|
||||
]);
|
||||
|
||||
@@ -174,8 +182,11 @@ const UTIL_CALLABLE_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
|
||||
"util.callbackify",
|
||||
"util.debuglog",
|
||||
"util.deprecate",
|
||||
"util.diff",
|
||||
"util.extend",
|
||||
"util.inherits",
|
||||
"util.inheritsPrototype",
|
||||
"util.isArrayDeprecated",
|
||||
]);
|
||||
|
||||
/** Entry points that link the regex unit: the regex and glob matchers (fs.glob's walk compiles its patterns with the glob matcher), and readline's terminal unit (scr_readline_tty.c), which measures with the regex unit's stripVTControlCharacters. */
|
||||
@@ -997,9 +1008,20 @@ const RUNTIME_FEATURE_RULES = {
|
||||
(mod.classes ?? []).some(
|
||||
(c) => c.name === RUNTIME_EVENT_TARGET_CLASS || c.name === RUNTIME_EVENT_CLASS,
|
||||
),
|
||||
fns: ["events.addAbortListener", ...ABORT_SIGNAL_LIB_FNS],
|
||||
// util.debounce and util.throttle register options.signal's listener.
|
||||
fns: [
|
||||
"events.addAbortListener",
|
||||
"util.aborted",
|
||||
"util.debounce",
|
||||
"util.throttle",
|
||||
...ABORT_SIGNAL_LIB_FNS,
|
||||
],
|
||||
prefixes: ["event."],
|
||||
}),
|
||||
/** util.MIMEType and util.MIMEParams (scr_mime.c). */
|
||||
mime: rule({ prefixes: ["mime."] }),
|
||||
/** util.debounce and util.throttle (scr_util_timing.c). */
|
||||
utilTiming: rule({ fns: ["util.debounce", "util.throttle"] }),
|
||||
/** events.once(emitter, name, { signal }) (scr_events_once_signal.c). */
|
||||
eventsOnceSignal: rule({ fns: ["events.onceSignal"] }),
|
||||
fetch: rule({ prefixes: ["fetch."] }),
|
||||
@@ -1218,6 +1240,8 @@ function noRuntimeFeatures(): RuntimeFeatures {
|
||||
http: false,
|
||||
inspect: false,
|
||||
legacyTextDecoder: false,
|
||||
mime: false,
|
||||
utilTiming: false,
|
||||
mockTimers: false,
|
||||
net: false,
|
||||
netLookup: false,
|
||||
|
||||
@@ -83,6 +83,8 @@ test.skipIf(process.platform !== "linux" && process.platform !== "darwin")(
|
||||
declAdopt: false,
|
||||
dynForAwait: false,
|
||||
eventsOnceSignal: false,
|
||||
mime: false,
|
||||
utilTiming: false,
|
||||
dynBuffer: false,
|
||||
builtinValues: false,
|
||||
warningReplace: false,
|
||||
|
||||
@@ -2482,12 +2482,36 @@
|
||||
"status": "static",
|
||||
"note": "recognized module (bare and node:-prefixed specifiers)"
|
||||
},
|
||||
{
|
||||
"id": "node-builtin.util._extend",
|
||||
"kind": "node-builtin",
|
||||
"name": "util._extend",
|
||||
"status": "static"
|
||||
},
|
||||
{
|
||||
"id": "node-builtin.util.aborted",
|
||||
"kind": "node-builtin",
|
||||
"name": "util.aborted",
|
||||
"status": "static"
|
||||
},
|
||||
{
|
||||
"id": "node-builtin.util.callbackify",
|
||||
"kind": "node-builtin",
|
||||
"name": "util.callbackify",
|
||||
"status": "static"
|
||||
},
|
||||
{
|
||||
"id": "node-builtin.util.convertProcessSignalToExitCode",
|
||||
"kind": "node-builtin",
|
||||
"name": "util.convertProcessSignalToExitCode",
|
||||
"status": "static"
|
||||
},
|
||||
{
|
||||
"id": "node-builtin.util.debounce",
|
||||
"kind": "node-builtin",
|
||||
"name": "util.debounce",
|
||||
"status": "static"
|
||||
},
|
||||
{
|
||||
"id": "node-builtin.util.debug",
|
||||
"kind": "node-builtin",
|
||||
@@ -2506,6 +2530,12 @@
|
||||
"name": "util.deprecate",
|
||||
"status": "static"
|
||||
},
|
||||
{
|
||||
"id": "node-builtin.util.diff",
|
||||
"kind": "node-builtin",
|
||||
"name": "util.diff",
|
||||
"status": "static"
|
||||
},
|
||||
{
|
||||
"id": "node-builtin.util.getSystemErrorMap",
|
||||
"kind": "node-builtin",
|
||||
@@ -2530,6 +2560,12 @@
|
||||
"name": "util.inherits",
|
||||
"status": "static"
|
||||
},
|
||||
{
|
||||
"id": "node-builtin.util.isArray",
|
||||
"kind": "node-builtin",
|
||||
"name": "util.isArray",
|
||||
"status": "static"
|
||||
},
|
||||
{
|
||||
"id": "node-builtin.util.isDeepStrictEqual",
|
||||
"kind": "node-builtin",
|
||||
@@ -2568,6 +2604,12 @@
|
||||
"name": "util.styleText",
|
||||
"status": "static"
|
||||
},
|
||||
{
|
||||
"id": "node-builtin.util.throttle",
|
||||
"kind": "node-builtin",
|
||||
"name": "util.throttle",
|
||||
"status": "static"
|
||||
},
|
||||
{
|
||||
"id": "node-builtin.util.toUSVString",
|
||||
"kind": "node-builtin",
|
||||
|
||||
@@ -136,3 +136,111 @@ test("worker programs poll counted loops over typed arrays between blocks of ite
|
||||
await rm(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("worker programs poll no loop whose proven ranges bound its runs inside another loop", async () => {
|
||||
const dir = await mkdtemp(join(tmpdir(), "scriptc-bounded-loop-polls-"));
|
||||
try {
|
||||
const entry = join(dir, "bounded.ts");
|
||||
await writeFile(
|
||||
entry,
|
||||
[
|
||||
'import { isMainThread, Worker } from "node:worker_threads";',
|
||||
"const LIMIT = 50;",
|
||||
"export function rows(cr: Float64Array, n: number): number {",
|
||||
" let t = 0;",
|
||||
" for (let r = 0; r < n; r++)",
|
||||
" for (let i = 0; i < 8; i += 2) {",
|
||||
" const c = cr[i]!;",
|
||||
" for (let j = 0; j < LIMIT; j++) {",
|
||||
" t = (t * 31 + j + c) % 1000003;",
|
||||
" if (t === 7) break;",
|
||||
" }",
|
||||
" }",
|
||||
" return t;",
|
||||
"}",
|
||||
"export function open(cr: Float64Array, n: number, k: number): number {",
|
||||
" let t = 0;",
|
||||
" for (let r = 0; r < n; r++)",
|
||||
" for (let i = 0; i < 8; i += 2) {",
|
||||
" const c = cr[i]!;",
|
||||
" for (let j = 0; j < k; j++) {",
|
||||
" t = (t * 31 + j + c) % 1000003;",
|
||||
" if (t === 7) break;",
|
||||
" }",
|
||||
" }",
|
||||
" return t;",
|
||||
"}",
|
||||
"export function wide(n: number): number {",
|
||||
" let t = 0;",
|
||||
" for (let r = 0; r < n; r++) for (let j = 0; j < 5000; j++) t = (t * 31 + j) % 1000003;",
|
||||
" return t;",
|
||||
"}",
|
||||
"export function exact(n: number): number {",
|
||||
" let t = 0;",
|
||||
" for (let r = 0; r < n; r++)",
|
||||
" for (let j = 9007199254740900; j < 9007199254740950; j++) t = (t + (j % 7)) % 1000003;",
|
||||
" return t;",
|
||||
"}",
|
||||
"export function stalled(n: number): number {",
|
||||
" let t = 0;",
|
||||
" for (let r = 0; r < n; r++)",
|
||||
" for (let j = 9007199254740990; j < 9007199254741002; j++) t = (t + (j % 7)) % 1000003;",
|
||||
" return t;",
|
||||
"}",
|
||||
"export function single(): number {",
|
||||
" let t = 0;",
|
||||
" for (let j = 0; j < 50; j++) t = (t * 31 + j) % 1000003;",
|
||||
" return t;",
|
||||
"}",
|
||||
"const cr = new Float64Array(8).fill(0.5);",
|
||||
'const k = Number(process.argv[2] ?? "50");',
|
||||
"console.log(rows(cr, 3), open(cr, 3, k), wide(2), exact(2), stalled(0), single());",
|
||||
"if (!isMainThread) new Worker(new URL(import.meta.url));",
|
||||
].join("\n"),
|
||||
);
|
||||
const outPath = join(dir, "bounded.ir.json");
|
||||
const result = await compile(entry, { outPath, outDir: dir, outputKind: "ir" });
|
||||
if (!result.ok) throw new Error(result.diagnostics.map((d) => d.message).join("\n"));
|
||||
const ll = emitLlvmModule(deserializeModule(await readFile(outPath, "utf8")));
|
||||
const body = (name: string): string =>
|
||||
[
|
||||
...ll.matchAll(
|
||||
new RegExp(
|
||||
`define internal [^\\n]+ @sc_b?f_${name}(?:\\.i32)?\\([^\\n]*\\n([\\s\\S]*?)\\n\\}`,
|
||||
"g",
|
||||
),
|
||||
),
|
||||
]
|
||||
.map((match) => match[1]!)
|
||||
.join("\n");
|
||||
const polls = (text: string): number => text.match(/@scr_exc_flag/g)?.length ?? 0;
|
||||
// The i and j loops of `rows` run at most 4 × (1 + 50) bodies: only the
|
||||
// r loop polls, and the i loop over the typed array is not strip-mined.
|
||||
const rows = body("rows");
|
||||
expect(rows).not.toMatch(/^loop\.poll\d+:$/m);
|
||||
expect(polls(rows)).toBeGreaterThan(0);
|
||||
// With a limit known only at run time the inner loops keep their polls.
|
||||
// The innermost one is strip-mined, and a block that ends at the limit
|
||||
// leaves it without polling: only a block that ends short of it polls.
|
||||
const open = body("open");
|
||||
expect(polls(open)).toBeGreaterThan(polls(rows));
|
||||
expect(open).toMatch(/^loop\.poll\d+:$/m);
|
||||
const ends = [...open.matchAll(/^loop\.end\d+:$[\s\S]*?^\S/gm)].map((match) => match[0]);
|
||||
expect(ends.length).toBeGreaterThan(0);
|
||||
for (const end of ends) expect(end).toMatch(/br i1 %\w+, label %loop\.poll\d+, label %/);
|
||||
// A run of up to 16384 iterations is one block, polled as it leaves when
|
||||
// it took more than 64.
|
||||
expect(open).toMatch(/^loop\.poll\.run\d+:$/m);
|
||||
// Steps of 1 below 2^53 are exact, so 50 runs near it are bounded. From
|
||||
// 2^53, `j + 1 === j`: a counter ranging up to it can run without end
|
||||
// and keeps its poll.
|
||||
expect(polls(body("exact"))).toBe(polls(body("single")));
|
||||
expect(polls(body("stalled"))).toBeGreaterThan(polls(body("exact")));
|
||||
// A nest over the bound, or a loop no other loop encloses, polls.
|
||||
expect(polls(body("wide"))).toBeGreaterThan(0);
|
||||
expect(body("wide")).toMatch(/^loop\.poll\d+:$/m);
|
||||
expect(polls(body("single"))).toBeGreaterThan(0);
|
||||
} finally {
|
||||
await rm(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7520-util-format-runtime.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7521-util-format-runtime.cjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7522-promisify-untyped.cjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7523-promisify-untyped.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7524-inspect-custom.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7525-inspect-custom.cjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7526-inspect-options-runtime.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7536-inspect-custom-errors.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7537-inspect-custom-errors.cjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7538-inspect-custom-prototypes.mjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7700-util-deprecated-helpers.cjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7701-util-deprecated-helpers.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7702-util-signal-exit-code.cjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7703-util-aborted.cjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7704-util-aborted.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7705-util-diff.cjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7706-util-diff.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7707-util-mime-type.cjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7708-util-mime-type.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7709-function-own-properties.cjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7710-util-debounce.cjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7711-util-throttle.cjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7712-util-debounce-throttle.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/8009-normalize-hangul-jungseong-o-yae.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/8120-inspect-custom-nested.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/8121-inspect-custom-nested.cjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/8122-inspect-custom-runtime-only.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/8125-inspect-custom-throws.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/8126-inspect-custom-throws.cjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/dyn-union-function-arms.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/generic-member-lambda-impls.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/native-worker-terminate-bounded-loops.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/recursive-optional-callback-params.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/diagnostics/inspect-custom-fences.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -93,6 +93,8 @@ export const OPTIONAL_RUNTIME_SOURCES = [
|
||||
["scr_events_emitter.c", "emitter"],
|
||||
// events.once with options registers through the emitter and event-target units.
|
||||
["scr_events_once_signal.c", "eventsOnceSignal"],
|
||||
["scr_mime.c", "mime"],
|
||||
["scr_util_timing.c", "utilTiming"],
|
||||
["scr_dyn_handle.c", any("emitter", "netEffective")],
|
||||
["scr_symbol.c", "symbol"],
|
||||
["scr_bigint_assert.c", all("assert", "bigint")],
|
||||
|
||||
@@ -523,7 +523,11 @@ ScrStr *scr_console_dir_dyn(ScrDyn *value, double depth) {
|
||||
scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020");
|
||||
return scr_str_new("", 0);
|
||||
}
|
||||
return scr_insp_dyn(value, 0, depth);
|
||||
/* console.dir is inspect with customInspect: false. */
|
||||
bool custom = scr_insp_custom_inspect(false);
|
||||
ScrStr *out = scr_insp_dyn(value, 0, depth);
|
||||
scr_insp_custom_inspect(custom);
|
||||
return out;
|
||||
}
|
||||
|
||||
/* ── console.table ───────────────────────────────────────────────────
|
||||
|
||||
@@ -186,6 +186,8 @@ typedef enum {
|
||||
X(MATCH_SOURCE, 55) /* a match result's pending metadata (scr_regex.c) */ \
|
||||
X(MESSAGE_EVENT, 50) /* ScrEvent with data — BroadcastChannel's 'message' */ \
|
||||
X(MESSAGE_PORT, 36) \
|
||||
X(MIME_PARAMS, 81) /* util.MIMEParams (scr_mime.c) */ \
|
||||
X(MIME_TYPE, 82) /* util.MIMEType (scr_mime.c) */ \
|
||||
X(MODULE_NAMESPACE, 79) /* module namespace object read through its exports (scr_module_namespace.c) */ \
|
||||
X(NET_BLOCK_LIST, 48) /* net.BlockList (scr_net.c) */ \
|
||||
X(NET_SERVER, 3) /* ScrNetServer — net.Server (http.Server rides the same handle) */ \
|
||||
@@ -961,6 +963,10 @@ ScrDyn *scr_dyn_open_iterator(const ScrDyn *value, int *kind, bool generators);
|
||||
* for-of protocol reads it. +1, or NULL with the error pending. */
|
||||
ScrDyn *scr_dyn_iterator_result_get(const ScrDyn *result, const char *key, size_t len);
|
||||
void scr_dyn_install_iterator_symbol(ScrDyn *key);
|
||||
/* Symbol.for('nodejs.util.inspect.custom'): the symbol unit installs it
|
||||
* when the registry creates it; NULL (borrowed) until then. */
|
||||
void scr_dyn_install_inspect_custom_symbol(ScrDyn *key);
|
||||
ScrDyn *scr_dyn_inspect_custom_symbol(void);
|
||||
void scr_dyn_install_has_instance_symbol(ScrDyn *key);
|
||||
void scr_dyn_install_async_iterator_symbol(ScrDyn *key);
|
||||
void scr_dyn_install_async_dispose_symbol(ScrDyn *key);
|
||||
|
||||
@@ -1962,3 +1962,60 @@ ScrPromise *scr_tp_scheduler_wait_signal(double ms, ScrPromise *value, bool ref,
|
||||
ScrPromise *scr_tp_set_immediate_signal(ScrPromise *value, bool ref, ScrDyn *signal) {
|
||||
return scr_tp_signal(0, value, ref, signal, true);
|
||||
}
|
||||
|
||||
/* ── util.aborted(signal, resource) ────────────────────────────────────
|
||||
* Node's async function: the signal must be an AbortSignal and the
|
||||
* resource an object (functions and arrays included), or the promise
|
||||
* rejects with ERR_INVALID_ARG_TYPE. An aborted signal gives a promise of
|
||||
* undefined; otherwise a once 'abort' listener that resists
|
||||
* stopImmediatePropagation resolves it with the event. The answer adopts
|
||||
* that promise, as an async function's result does. */
|
||||
static ScrDyn *scr_util_aborted_listener(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
|
||||
ScrPromise *inner = scr_box_get_ref(closure->caps[0]);
|
||||
/* scr_promise_resolve_dyn takes the value's reference. */
|
||||
scr_promise_resolve_dyn(inner, scr_dyn_retain(argc ? args[0] : scr_dyn_undefined()));
|
||||
scr_promise_release(inner);
|
||||
return scr_dyn_retain(scr_dyn_undefined());
|
||||
}
|
||||
|
||||
ScrDyn *scr_util_aborted(ScrDyn *signal, ScrDyn *resource) {
|
||||
ScrPromise *outer = scr_promise_new();
|
||||
bool aborted = false;
|
||||
ScrEventTargetCore *core = scr_et_signal_core_fn ? scr_et_signal_core_fn(signal, &aborted) : NULL;
|
||||
bool object = resource->kind == SCR_DYN_OBJ || resource->kind == SCR_DYN_ARR || resource->kind == SCR_DYN_FUNC ||
|
||||
resource->kind == SCR_DYN_TYPED_REF || resource->kind == SCR_DYN_HANDLE ||
|
||||
resource->kind == SCR_DYN_BYTES || resource->kind == SCR_DYN_PROMISE ||
|
||||
resource->kind == SCR_DYN_PROXY;
|
||||
if (!core) {
|
||||
static const char message[] = "signal is not of type AbortSignal.";
|
||||
scr_throw_error_msg_code(SCR_ERR_TYPE, message, sizeof message - 1, "ERR_INVALID_ARG_TYPE");
|
||||
} else if (!object) {
|
||||
scr_dyn_arg_type_fail("resource", "of type object", resource);
|
||||
}
|
||||
if (scr_exc_pending()) {
|
||||
scr_promise_reject_pending(outer);
|
||||
ScrDyn *rejected = scr_dyn_new_promise(outer);
|
||||
scr_promise_release(outer);
|
||||
return rejected;
|
||||
}
|
||||
ScrPromise *inner = scr_promise_new();
|
||||
if (aborted) {
|
||||
scr_promise_fulfill_void(inner);
|
||||
} else {
|
||||
ScrClosure *closure = scr_closure_new(NULL, 1);
|
||||
closure->caps[0] = scr_box_new_obj(scr_promise_retain_v, scr_promise_release_v, NULL);
|
||||
scr_box_set_ref(closure->caps[0], scr_promise_retain(inner));
|
||||
ScrDyn *listener = scr_dyn_new_func(closure, scr_util_aborted_listener, 1, "native:function", "");
|
||||
ScrStr *abort = scr_str_new("abort", 5);
|
||||
bool duplicate;
|
||||
(void)scr_et_append(core, abort, listener, SCR_ET_ONCE | SCR_ET_RESIST, &duplicate);
|
||||
scr_str_release(abort);
|
||||
scr_dyn_release(listener);
|
||||
}
|
||||
ScrDyn *adopted = scr_dyn_new_promise(inner);
|
||||
scr_promise_release(inner);
|
||||
scr_promise_resolve_dyn(outer, adopted); /* moves the promise box in */
|
||||
ScrDyn *result = scr_dyn_new_promise(outer);
|
||||
scr_promise_release(outer);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -106,6 +106,8 @@ typedef struct {
|
||||
extern SCR_TL const ScrEtBroadcastOps *scr_et_broadcast_ops;
|
||||
void scr_et_broadcast_install(void);
|
||||
|
||||
/* util.aborted(signal, resource): +1 dyn promise; argument errors reject it. */
|
||||
ScrDyn *scr_util_aborted(ScrDyn *signal, ScrDyn *resource);
|
||||
/* events.addAbortListener(signal, listener): +1 disposable, or NULL. */
|
||||
ScrDyn *scr_events_add_abort_listener(ScrDyn *signal, ScrDyn *listener);
|
||||
/* timers/promises setTimeout(delay, value, options) and setImmediate(value,
|
||||
|
||||
+1283
-36
File diff suppressed because it is too large
Load Diff
@@ -51,9 +51,58 @@ ScrStr *scr_insp_typed_array(ScrBytes *b, double recurse, double depth);
|
||||
ScrStr *scr_insp_bytes(ScrBytes *b, double recurse, double depth);
|
||||
/* format's %s / rest-args twin: dyn strings pass verbatim. */
|
||||
ScrStr *scr_insp_dyn_s(ScrDyn *d, double depth);
|
||||
/* util.format over checked-dynamic arguments (formatWithOptionsInternal):
|
||||
* the options an inspect of each argument takes, and the API name the
|
||||
* SC2020 refusals name. These entries call program code, so they answer
|
||||
* NULL with the exception pending when it throws. */
|
||||
typedef struct ScrInspFormatOptions {
|
||||
const char *surface;
|
||||
double depth; /* Infinity for null, undefined and NaN */
|
||||
/* The depth as the options gave it, which custom inspect functions
|
||||
* receive when the depth is unbounded: 0 a number (depth_number,
|
||||
* Infinity or NaN), 1 null, 2 undefined. */
|
||||
int depth_kind;
|
||||
double depth_number;
|
||||
bool colors;
|
||||
bool custom_inspect;
|
||||
double break_length;
|
||||
double compact; /* -1 for false */
|
||||
bool sorted;
|
||||
/* The options object, when it holds keys inspect does not read: custom
|
||||
* inspect functions receive them (borrowed; NULL otherwise). */
|
||||
const ScrDyn *user_options;
|
||||
} ScrInspFormatOptions;
|
||||
void scr_insp_format_defaults(ScrInspFormatOptions *o, const char *surface);
|
||||
/* Reads inspect's options from an options object into o; false with the
|
||||
* SC2020 refusal pending for values that have no lowering. */
|
||||
bool scr_insp_format_read_options(const ScrDyn *options, ScrInspFormatOptions *o);
|
||||
ScrStr *scr_insp_format(ScrDyn *const *args, size_t argc, const ScrInspFormatOptions *o);
|
||||
/* util.format(...pack) and util.formatWithOptions(options, ...pack) over a
|
||||
* dyn array of the arguments (+1). */
|
||||
ScrStr *scr_insp_format_pack(ScrDyn *pack);
|
||||
/* util.format over a native list of checked values, the compiled calls'
|
||||
* held arguments (+1). */
|
||||
struct ScrArr;
|
||||
ScrStr *scr_insp_format_list(struct ScrArr *list);
|
||||
/* scr_insp_format_list when every argument is a string, number, boolean,
|
||||
* null or undefined, without the checked-dynamic walk (+1). */
|
||||
ScrStr *scr_insp_format_primitives(struct ScrArr *list);
|
||||
ScrStr *scr_insp_format_with_options(ScrDyn *options, ScrDyn *pack);
|
||||
/* util.inspect(value, options, depth, colors) over checked-dynamic
|
||||
* arguments with Node's legacy-argument rules; argc is the call's
|
||||
* argument count (+1, or NULL with the exception pending). */
|
||||
ScrStr *scr_insp_inspect(ScrDyn *value, ScrDyn *options, ScrDyn *depth, ScrDyn *colors,
|
||||
double argc);
|
||||
/* Whether a format string has a '%' before its last character, the only
|
||||
* strings whose substitutions can change the output. */
|
||||
bool scr_insp_format_specifies(ScrStr *first);
|
||||
bool scr_insp_format_specifies_dyn(ScrDyn *first);
|
||||
/* format's %s over a dyn value: scr_insp_dyn_s, except that values whose
|
||||
* class defines its own toString print String(value). */
|
||||
ScrStr *scr_insp_dyn_format_s(ScrDyn *d, double depth);
|
||||
ScrStr *scr_insp_dyn_hidden(ScrDyn *d, double depth); /* %o: showHidden */
|
||||
void scr_insp_begin(double recurse);
|
||||
/* util.inspect's breakLength, compact (0 for false) and sorted for the
|
||||
/* util.inspect's breakLength, compact (-1 for false) and sorted for the
|
||||
* renders that follow; the call site restores the defaults (80, 3, false)
|
||||
* afterwards. */
|
||||
void scr_insp_layout(double break_length, double compact, bool sorted);
|
||||
@@ -80,6 +129,38 @@ ScrStr *scr_insp_end(ScrStr *base, ScrStr *b0, ScrStr *b1, double recurse,
|
||||
* caller's level (+1; borrows both). */
|
||||
void scr_insp_nested_begin(void);
|
||||
ScrStr *scr_insp_nested_end(ScrStr *prefix, ScrStr *inner);
|
||||
/* nested_end's restore without its text: the caller's context returns. */
|
||||
void scr_insp_nested_restore(void);
|
||||
/* ctx.customInspect for the renders that follow; answers the previous
|
||||
* value for the caller to restore. */
|
||||
bool scr_insp_custom_inspect(bool enabled);
|
||||
/* Around a custom inspect function's call: begin gives the calls it makes
|
||||
* a fresh inspect context with the default options and answers a token;
|
||||
* end(token) restores the caller's, also after calls that threw. */
|
||||
double scr_insp_custom_call_begin(void);
|
||||
void scr_insp_custom_call_end(double token);
|
||||
/* How the unbounded depth of the renders that follow was given, for custom
|
||||
* inspect functions: 0 Infinity, 1 null, 2 undefined. Answers the
|
||||
* previous kind for the caller to restore. */
|
||||
double scr_insp_depth_kind(double kind);
|
||||
/* A custom inspect function's string result re-indented at the current
|
||||
* level (+1; borrows text). */
|
||||
ScrStr *scr_insp_custom_text(ScrStr *text);
|
||||
/* The compiled [util.inspect.custom] calls: ctx.customInspect, the depth
|
||||
* argument at recursion `recurse` of `depth` and the options object (+1
|
||||
* dyn values; options NULL with an exception pending), util.inspect as a
|
||||
* function value (+1), whether a dyn result is the instance itself, and a
|
||||
* dyn result's rendering at the instance's level (+1). */
|
||||
bool scr_insp_custom_enabled(void);
|
||||
/* Registers a class's compiled custom inspect function under the type key
|
||||
* of its instances' typed refs: fn(self, depth, options, inspect) answers
|
||||
* the method's result (borrows both). */
|
||||
void scr_insp_register_custom(ScrStr *type_key, ScrDyn *fn);
|
||||
ScrDyn *scr_insp_custom_depth(double recurse, double depth);
|
||||
ScrDyn *scr_insp_custom_options(double depth);
|
||||
ScrDyn *scr_insp_inspect_function(void);
|
||||
bool scr_insp_custom_is_self(ScrDyn *result, ScrDyn *self);
|
||||
ScrStr *scr_insp_custom_result(ScrDyn *result, double recurse, double depth);
|
||||
/* Mark the inspector's state before a top-level render, and return to the
|
||||
* mark when the render ends, normally or by an inspection refusal. */
|
||||
void scr_insp_mark(void);
|
||||
|
||||
@@ -184,6 +184,24 @@ bool scr_dyn_is_async_dispose_symbol(const ScrDyn *key) {
|
||||
key->v.symbol.value == scr_async_dispose_symbol->v.symbol.value;
|
||||
}
|
||||
|
||||
/* Symbol.for('nodejs.util.inspect.custom'), installed by the symbol unit
|
||||
* when the program creates it: the key util.inspect reads custom inspect
|
||||
* functions under. */
|
||||
static SCR_TL ScrDyn *scr_inspect_custom_symbol;
|
||||
|
||||
static void scr_inspect_custom_symbol_cleanup(void) {
|
||||
scr_dyn_release(scr_inspect_custom_symbol);
|
||||
scr_inspect_custom_symbol = NULL;
|
||||
}
|
||||
|
||||
void scr_dyn_install_inspect_custom_symbol(ScrDyn *key) {
|
||||
if (scr_inspect_custom_symbol) return;
|
||||
scr_inspect_custom_symbol = scr_dyn_retain(key);
|
||||
scr_atexit(scr_inspect_custom_symbol_cleanup);
|
||||
}
|
||||
|
||||
ScrDyn *scr_dyn_inspect_custom_symbol(void) { return scr_inspect_custom_symbol; }
|
||||
|
||||
static void scr_to_primitive_symbol_cleanup(void) {
|
||||
scr_dyn_release(scr_to_primitive_symbol);
|
||||
scr_to_primitive_symbol = NULL;
|
||||
@@ -6797,11 +6815,12 @@ static ScrDyn *scr_dyn_fn_properties(const ScrDyn *function) {
|
||||
owner->props = scr_box_retain(function->v.fn.class_obj->static_data);
|
||||
if (!owner->props) {
|
||||
ScrDyn *table = scr_dyn_new_obj();
|
||||
/* V8's own-key order: length, then name. */
|
||||
scr_dyn_obj_set(table, "length", 6, scr_dyn_new_num((double)function->v.fn.arity));
|
||||
const char *name = function->v.fn.name ? function->v.fn.name : "";
|
||||
ScrStr *name_string = scr_str_new(name, strlen(name));
|
||||
scr_dyn_obj_set(table, "name", 4, scr_dyn_new_str(name_string));
|
||||
scr_str_release(name_string);
|
||||
scr_dyn_obj_set(table, "length", 6, scr_dyn_new_num((double)function->v.fn.arity));
|
||||
for (size_t i = 0; i < table->v.obj.len; i++) {
|
||||
table->v.obj.entries[i].writable = false;
|
||||
table->v.obj.entries[i].enumerable = false;
|
||||
@@ -10388,7 +10407,8 @@ ScrStr *scr_dyn_to_string(const ScrDyn *d, const ScrStr *enc) {
|
||||
if (scr_dyn_error_encoding(d) && !scr_dyn_property_owner(d, "toString", 8)) return scr_dyn_error_string(d);
|
||||
return scr_str_new("[object Object]", 15);
|
||||
case SCR_DYN_HANDLE:
|
||||
if (d->v.handle.tag == SCR_DYNH_REGEXP || d->v.handle.tag == SCR_DYNH_URL || d->v.handle.tag == SCR_DYNH_DATE) {
|
||||
if (d->v.handle.tag == SCR_DYNH_REGEXP || d->v.handle.tag == SCR_DYNH_URL || d->v.handle.tag == SCR_DYNH_DATE ||
|
||||
d->v.handle.tag == SCR_DYNH_MIME_TYPE || d->v.handle.tag == SCR_DYNH_MIME_PARAMS) {
|
||||
ScrDyn *text = scr_dyn_handle_ops_of(d)->invoke(d->v.handle.ptr, (ScrDyn *)d, "toString", NULL, 0, "RegExp.toString");
|
||||
if (!text) return NULL;
|
||||
ScrStr *out = scr_str_retain(text->v.str);
|
||||
@@ -13823,7 +13843,9 @@ static ScrDyn *scr_json_to_json_at(ScrDyn *value, const ScrStr *key, size_t inde
|
||||
if (scr_exc_pending()) { scr_dyn_release(view); scr_dyn_release(value); return NULL; }
|
||||
if (inspected->kind == SCR_DYN_HANDLE && (inspected->v.handle.tag == SCR_DYNH_DATE || inspected->v.handle.tag == SCR_DYNH_URL ||
|
||||
inspected->v.handle.tag == SCR_DYNH_NET_SOCKET_ADDRESS ||
|
||||
inspected->v.handle.tag == SCR_DYNH_NET_BLOCK_LIST)) {
|
||||
inspected->v.handle.tag == SCR_DYNH_NET_BLOCK_LIST ||
|
||||
inspected->v.handle.tag == SCR_DYNH_MIME_TYPE ||
|
||||
inspected->v.handle.tag == SCR_DYNH_MIME_PARAMS)) {
|
||||
const ScrDynHandleOps *ops = scr_dyn_handle_ops_of(inspected);
|
||||
ScrDyn *converted = ops->invoke(inspected->v.handle.ptr, value, "toJSON", NULL, 0, "toJSON");
|
||||
scr_dyn_release(value);
|
||||
|
||||
@@ -11588,8 +11588,8 @@ double scr_date_get_timezone_offset(double ms) {
|
||||
* from local fields reads those fields back. */
|
||||
|
||||
/* The host zone's offset (local - UTC) in ms at UTC instant `ms` (an
|
||||
* integral time value within about ±8.65e15). */
|
||||
static bool scr_date_offset_at(double ms, double *out) {
|
||||
* integral time value within about ±8.65e15), from the host's localtime. */
|
||||
static bool scr_date_offset_at_uncached(double ms, double *out) {
|
||||
long long cms = (long long)ms;
|
||||
double secd = (double)(cms / 1000 - (cms % 1000 < 0));
|
||||
struct tm tmv;
|
||||
@@ -11611,6 +11611,60 @@ static bool scr_date_offset_at(double ms, double *out) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Offsets at UTC midnights, for scr_date_offset_at. A day whose two
|
||||
* midnights have the same offset has that offset throughout, given at most
|
||||
* one transition per day (scr_date_local_to_utc already takes transitions
|
||||
* to be at least a day apart). Each local setter asks for three to five
|
||||
* offsets around one time value, and consecutive ones land on the same or
|
||||
* neighbouring days, so most answers come from here instead of a
|
||||
* localtime(3) call: glibc takes its zone lock in each and, for dates past
|
||||
* the zone file's last transition, parses the zone's rule string again. A
|
||||
* change of process.env.TZ starts a new generation. */
|
||||
#define SCR_DATE_MIDNIGHTS 64
|
||||
typedef struct {
|
||||
long long day;
|
||||
double offset;
|
||||
bool valid;
|
||||
} ScrDateMidnight;
|
||||
typedef struct {
|
||||
bool valid;
|
||||
unsigned generation;
|
||||
ScrDateMidnight entries[SCR_DATE_MIDNIGHTS];
|
||||
} ScrDateMidnightCache;
|
||||
static SCR_TL ScrDateMidnightCache scr_date_midnight_cache;
|
||||
|
||||
static bool scr_date_midnight_offset(ScrDateMidnightCache *c, long long day, double *out) {
|
||||
ScrDateMidnight *e = &c->entries[(unsigned long long)day % SCR_DATE_MIDNIGHTS];
|
||||
if (e->valid && e->day == day) {
|
||||
*out = e->offset;
|
||||
return true;
|
||||
}
|
||||
if (!scr_date_offset_at_uncached((double)day * 86400000.0, out)) return false;
|
||||
e->valid = true;
|
||||
e->day = day;
|
||||
e->offset = *out;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool scr_date_offset_at(double ms, double *out) {
|
||||
ScrDateMidnightCache *c = &scr_date_midnight_cache;
|
||||
unsigned generation = scr_date_tz_generation;
|
||||
if (!c->valid || c->generation != generation) {
|
||||
memset(c, 0, sizeof *c);
|
||||
c->valid = true;
|
||||
c->generation = generation;
|
||||
}
|
||||
long long cms = (long long)ms;
|
||||
long long day = cms / 86400000 - (cms % 86400000 < 0);
|
||||
double start, end;
|
||||
if (scr_date_midnight_offset(c, day, &start) && scr_date_midnight_offset(c, day + 1, &end) &&
|
||||
start == end) {
|
||||
*out = start;
|
||||
return true;
|
||||
}
|
||||
return scr_date_offset_at_uncached(ms, out);
|
||||
}
|
||||
|
||||
static volatile const double scr_date_day_ms = 86400000.0;
|
||||
|
||||
static double scr_date_local_to_utc(double t) {
|
||||
|
||||
@@ -0,0 +1,603 @@
|
||||
/* node:util MIMEType and MIMEParams (Node's lib/internal/mime.js) as
|
||||
* native handles of the checked-dynamic tree. The parser scans UTF-16 code
|
||||
* units, as Node's string searches do, so error offsets match; every value
|
||||
* that survives validation is ASCII (types, subtypes, parameter names) or
|
||||
* Latin-1 (parameter values). MIMEParams keeps Map semantics: insertion
|
||||
* order, a set of an existing name keeps its place, and a deleted entry
|
||||
* leaves a gap that live iterators skip. A separate unit, linked only by
|
||||
* programs that construct these classes. */
|
||||
#include "scr_runtime.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct {
|
||||
size_t rc;
|
||||
ScrStr **keys;
|
||||
ScrStr **values; /* NULL for a deleted entry */
|
||||
size_t len, cap;
|
||||
} ScrMimeParams;
|
||||
|
||||
typedef struct {
|
||||
size_t rc;
|
||||
ScrStr *type;
|
||||
ScrStr *subtype;
|
||||
ScrDyn *params; /* the MIMEParams handle box, owned */
|
||||
} ScrMimeType;
|
||||
|
||||
static void scr_mime_oom(void) { scr_trap("scriptc: out of memory\n"); }
|
||||
|
||||
/* ── code units ─────────────────────────────────────────────────────── */
|
||||
|
||||
typedef struct {
|
||||
uint16_t *units;
|
||||
size_t len;
|
||||
} ScrMimeUnits;
|
||||
|
||||
static ScrMimeUnits scr_mime_units(ScrStr *text) {
|
||||
ScrMimeUnits out;
|
||||
out.len = (size_t)scr_str_utf16_len(text);
|
||||
out.units = malloc((out.len ? out.len : 1) * sizeof *out.units);
|
||||
if (!out.units) scr_mime_oom();
|
||||
for (size_t i = 0; i < out.len; i++) out.units[i] = (uint16_t)scr_str_char_code_at(text, (double)i);
|
||||
return out;
|
||||
}
|
||||
|
||||
static bool scr_mime_ws(uint16_t c) { return c == '\r' || c == '\n' || c == '\t' || c == ' '; }
|
||||
|
||||
static bool scr_mime_token(uint16_t c) {
|
||||
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')) return true;
|
||||
return c != 0 && c < 0x80 && strchr("!#$%&'*+-.^_`|~", (char)c) != NULL;
|
||||
}
|
||||
|
||||
static bool scr_mime_quoted(uint16_t c) {
|
||||
return c == '\t' || (c >= 0x20 && c <= 0x7E) || (c >= 0x80 && c <= 0xFF);
|
||||
}
|
||||
|
||||
/* The index of the first unit of [from, to) outside the token set, as an
|
||||
* offset from `from`, or -1. */
|
||||
static long scr_mime_invalid_token(const uint16_t *u, size_t from, size_t to) {
|
||||
for (size_t i = from; i < to; i++)
|
||||
if (!scr_mime_token(u[i])) return (long)(i - from);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* A string of units that are all at most 0xFF, lowered (ASCII only) when
|
||||
* `lower`. +1. */
|
||||
static ScrStr *scr_mime_latin1(const uint16_t *u, size_t from, size_t to, bool lower) {
|
||||
ScrJsonBuf b;
|
||||
scr_jb_init(&b);
|
||||
for (size_t i = from; i < to; i++) {
|
||||
uint16_t c = u[i];
|
||||
if (lower && c >= 'A' && c <= 'Z') c = (uint16_t)(c + ('a' - 'A'));
|
||||
if (c < 0x80) {
|
||||
scr_jb_putc(&b, (char)c);
|
||||
} else {
|
||||
char pair[2] = {(char)(0xC0 | (c >> 6)), (char)(0x80 | (c & 0x3F))};
|
||||
scr_jb_write(&b, pair, 2);
|
||||
}
|
||||
}
|
||||
return scr_jb_finish(&b);
|
||||
}
|
||||
|
||||
/* ERR_INVALID_MIME_SYNTAX(production, str, invalidIndex). */
|
||||
static void scr_mime_syntax_fail(const char *production, const ScrStr *str, long index) {
|
||||
ScrJsonBuf b;
|
||||
scr_jb_init(&b);
|
||||
scr_jb_puts(&b, "The MIME syntax for a ");
|
||||
scr_jb_puts(&b, production);
|
||||
scr_jb_puts(&b, " in \"");
|
||||
scr_jb_put_str(&b, str);
|
||||
scr_jb_puts(&b, "\" is invalid");
|
||||
if (index != -1) {
|
||||
char at[32];
|
||||
snprintf(at, sizeof at, " at %ld", index);
|
||||
scr_jb_puts(&b, at);
|
||||
}
|
||||
ScrStr *message = scr_jb_finish(&b);
|
||||
scr_throw_error_msg_code(SCR_ERR_TYPE, message->data, message->len, "ERR_INVALID_MIME_SYNTAX");
|
||||
scr_str_release(message);
|
||||
}
|
||||
|
||||
/* ── MIMEParams ─────────────────────────────────────────────────────── */
|
||||
|
||||
static ScrMimeParams *scr_mime_params_alloc(void) {
|
||||
ScrMimeParams *p = calloc(1, sizeof *p);
|
||||
if (!p) scr_mime_oom();
|
||||
p->rc = 1;
|
||||
return p;
|
||||
}
|
||||
|
||||
static void *scr_mime_params_retain(void *h) {
|
||||
((ScrMimeParams *)h)->rc++;
|
||||
return h;
|
||||
}
|
||||
|
||||
static void scr_mime_params_release(void *h) {
|
||||
ScrMimeParams *p = h;
|
||||
if (!p || --p->rc > 0) return;
|
||||
for (size_t i = 0; i < p->len; i++) {
|
||||
scr_str_release(p->keys[i]);
|
||||
scr_str_release(p->values[i]);
|
||||
}
|
||||
free(p->keys);
|
||||
free(p->values);
|
||||
free(p);
|
||||
}
|
||||
|
||||
static long scr_mime_params_find(const ScrMimeParams *p, const ScrStr *key) {
|
||||
for (size_t i = 0; i < p->len; i++)
|
||||
if (p->values[i] && scr_str_eq((ScrStr *)p->keys[i], (ScrStr *)key)) return (long)i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Map.prototype.set: an existing name keeps its place. Both borrowed. */
|
||||
static void scr_mime_params_put(ScrMimeParams *p, ScrStr *key, ScrStr *value) {
|
||||
long at = scr_mime_params_find(p, key);
|
||||
if (at >= 0) {
|
||||
scr_str_release(p->values[at]);
|
||||
p->values[at] = scr_str_retain(value);
|
||||
return;
|
||||
}
|
||||
if (p->len == p->cap) {
|
||||
p->cap = p->cap ? p->cap * 2 : 4;
|
||||
p->keys = realloc(p->keys, p->cap * sizeof *p->keys);
|
||||
p->values = realloc(p->values, p->cap * sizeof *p->values);
|
||||
if (!p->keys || !p->values) scr_mime_oom();
|
||||
}
|
||||
p->keys[p->len] = scr_str_retain(key);
|
||||
p->values[p->len] = scr_str_retain(value);
|
||||
p->len++;
|
||||
}
|
||||
|
||||
/* Node's MIMEParams#parse over the text after the subtype's ';'. */
|
||||
static void scr_mime_params_parse(ScrMimeParams *p, const uint16_t *u, size_t n) {
|
||||
size_t end = n;
|
||||
while (end > 0 && scr_mime_ws(u[end - 1])) end--;
|
||||
size_t pos = 0;
|
||||
while (pos < end) {
|
||||
while (pos < n && scr_mime_ws(u[pos])) pos++;
|
||||
size_t name_end = pos;
|
||||
while (name_end < n && u[name_end] != ';' && u[name_end] != '=') name_end++;
|
||||
size_t name_from = pos;
|
||||
pos = name_end;
|
||||
if (pos < end) {
|
||||
uint16_t c = u[pos];
|
||||
pos++;
|
||||
if (c == ';') continue;
|
||||
}
|
||||
if (pos >= end) break;
|
||||
uint16_t *value = NULL;
|
||||
size_t value_len = 0;
|
||||
if (u[pos] == '"') {
|
||||
pos++;
|
||||
/* QUOTED_VALUE_PATTERN: escaped units, a trailing backslash (group 1)
|
||||
* and the closing quote (group 2). */
|
||||
size_t i = pos;
|
||||
bool trailing_backslash = false, closed = false;
|
||||
while (i < n) {
|
||||
if (u[i] == '\\') {
|
||||
if (i + 1 == n) {
|
||||
trailing_backslash = true;
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
bool pair = u[i + 1] >= 0xD800 && u[i + 1] <= 0xDBFF && i + 2 < n && u[i + 2] >= 0xDC00 && u[i + 2] <= 0xDFFF;
|
||||
i += pair ? 3 : 2;
|
||||
} else if (u[i] != '"') {
|
||||
i++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i < n && u[i] == '"' && !trailing_backslash) {
|
||||
closed = true;
|
||||
i++;
|
||||
}
|
||||
size_t match = i - pos;
|
||||
size_t inside = trailing_backslash || closed ? match - 1 : match;
|
||||
value = malloc((inside + 2) * sizeof *value);
|
||||
if (!value) scr_mime_oom();
|
||||
/* removeBackslashes, then the unmatched backslash back at the end. */
|
||||
size_t k;
|
||||
for (k = 0; inside > 0 && k < inside - 1; k++) {
|
||||
uint16_t c = u[pos + k];
|
||||
if (c == '\\') {
|
||||
k++;
|
||||
value[value_len++] = u[pos + k];
|
||||
} else {
|
||||
value[value_len++] = c;
|
||||
}
|
||||
}
|
||||
if (inside > 0 && k == inside - 1) value[value_len++] = u[pos + k];
|
||||
if (trailing_backslash) value[value_len++] = '\\';
|
||||
pos += match;
|
||||
} else {
|
||||
size_t value_end = pos;
|
||||
while (value_end < n && u[value_end] != ';') value_end++;
|
||||
size_t trimmed = value_end;
|
||||
while (trimmed > pos && scr_mime_ws(u[trimmed - 1])) trimmed--;
|
||||
size_t raw = value_end - pos;
|
||||
if (trimmed == pos) {
|
||||
pos += raw;
|
||||
continue;
|
||||
}
|
||||
value_len = trimmed - pos;
|
||||
value = malloc(value_len * sizeof *value);
|
||||
if (!value) scr_mime_oom();
|
||||
memcpy(value, u + pos, value_len * sizeof *value);
|
||||
pos += raw;
|
||||
}
|
||||
bool valid_name = name_end > name_from && scr_mime_invalid_token(u, name_from, name_end) == -1;
|
||||
bool valid_value = true;
|
||||
for (size_t i = 0; i < value_len && valid_value; i++) valid_value = scr_mime_quoted(value[i]);
|
||||
if (valid_name && valid_value) {
|
||||
ScrStr *key = scr_mime_latin1(u, name_from, name_end, true);
|
||||
if (scr_mime_params_find(p, key) < 0) {
|
||||
ScrStr *text = scr_mime_latin1(value, 0, value_len, false);
|
||||
scr_mime_params_put(p, key, text);
|
||||
scr_str_release(text);
|
||||
}
|
||||
scr_str_release(key);
|
||||
}
|
||||
free(value);
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
|
||||
/* The `encode` step of MIMEParams#toString. */
|
||||
static void scr_mime_encode_value(ScrJsonBuf *b, const ScrStr *value) {
|
||||
if (value->len == 0) {
|
||||
scr_jb_puts(b, "\"\"");
|
||||
return;
|
||||
}
|
||||
bool plain = true;
|
||||
for (size_t i = 0; i < value->len && plain; i++) {
|
||||
unsigned char c = (unsigned char)value->data[i];
|
||||
plain = c < 0x80 && scr_mime_token(c);
|
||||
}
|
||||
if (plain) {
|
||||
scr_jb_put_str(b, value);
|
||||
return;
|
||||
}
|
||||
scr_jb_putc(b, '"');
|
||||
for (size_t i = 0; i < value->len; i++) {
|
||||
char c = value->data[i];
|
||||
if (c == '"' || c == '\\') scr_jb_putc(b, '\\');
|
||||
scr_jb_putc(b, c);
|
||||
}
|
||||
scr_jb_putc(b, '"');
|
||||
}
|
||||
|
||||
static ScrStr *scr_mime_params_to_string(const ScrMimeParams *p) {
|
||||
ScrJsonBuf b;
|
||||
scr_jb_init(&b);
|
||||
bool first = true;
|
||||
for (size_t i = 0; i < p->len; i++) {
|
||||
if (!p->values[i]) continue;
|
||||
if (!first) scr_jb_putc(&b, ';');
|
||||
first = false;
|
||||
scr_jb_put_str(&b, p->keys[i]);
|
||||
scr_jb_putc(&b, '=');
|
||||
scr_mime_encode_value(&b, p->values[i]);
|
||||
}
|
||||
return scr_jb_finish(&b);
|
||||
}
|
||||
|
||||
/* ToString(name), lowered: `toASCIILower(`${name}`)`. +1 or NULL pending. */
|
||||
static ScrStr *scr_mime_name_arg(ScrDyn *const *args, size_t argc) {
|
||||
ScrStr *raw = scr_dyn_string_coerce_js(argc ? args[0] : scr_dyn_undefined());
|
||||
if (!raw) return NULL;
|
||||
ScrJsonBuf b;
|
||||
scr_jb_init(&b);
|
||||
for (size_t i = 0; i < raw->len; i++) {
|
||||
char c = raw->data[i];
|
||||
scr_jb_putc(&b, c >= 'A' && c <= 'Z' ? (char)(c + ('a' - 'A')) : c);
|
||||
}
|
||||
scr_str_release(raw);
|
||||
return scr_jb_finish(&b);
|
||||
}
|
||||
|
||||
static ScrDyn *scr_mime_not_a_function(const char *what) {
|
||||
ScrJsonBuf b;
|
||||
scr_jb_init(&b);
|
||||
scr_jb_puts(&b, what);
|
||||
scr_jb_puts(&b, " is not a function");
|
||||
scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&b));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static ScrDyn *scr_mime_params_step(void *h, double *index, unsigned selection) {
|
||||
ScrMimeParams *p = h;
|
||||
size_t i = (size_t)*index;
|
||||
while (i < p->len && !p->values[i]) i++;
|
||||
bool done = i >= p->len;
|
||||
ScrDyn *value;
|
||||
if (done) {
|
||||
value = scr_dyn_retain(scr_dyn_undefined());
|
||||
*index = (double)p->len;
|
||||
} else {
|
||||
if (selection == 2) {
|
||||
value = scr_dyn_new_arr();
|
||||
scr_dyn_arr_push(value, scr_dyn_new_str(p->keys[i]));
|
||||
scr_dyn_arr_push(value, scr_dyn_new_str(p->values[i]));
|
||||
} else {
|
||||
value = scr_dyn_new_str(selection == 1 ? p->keys[i] : p->values[i]);
|
||||
}
|
||||
*index = (double)(i + 1);
|
||||
}
|
||||
ScrDyn *result = scr_dyn_new_obj();
|
||||
scr_dyn_obj_set(result, "value", 5, value);
|
||||
scr_dyn_obj_set(result, "done", 4, scr_dyn_new_bool(done));
|
||||
return result;
|
||||
}
|
||||
|
||||
static ScrDyn *scr_mime_params_invoke(void *h, ScrDyn *self, const char *method, ScrDyn *const *args, size_t argc,
|
||||
const char *what) {
|
||||
ScrMimeParams *p = h;
|
||||
if (!strcmp(method, "entries") || !strcmp(method, "keys") || !strcmp(method, "values"))
|
||||
return scr_dyn_native_handle_iterator(self, !strcmp(method, "entries") ? 2 : !strcmp(method, "keys") ? 1 : 0);
|
||||
if (!strcmp(method, "toString") || !strcmp(method, "toJSON")) {
|
||||
ScrStr *text = scr_mime_params_to_string(p);
|
||||
ScrDyn *result = scr_dyn_new_str(text);
|
||||
scr_str_release(text);
|
||||
return result;
|
||||
}
|
||||
bool get = !strcmp(method, "get"), has = !strcmp(method, "has"), del = !strcmp(method, "delete");
|
||||
bool set = !strcmp(method, "set");
|
||||
if (!get && !has && !del && !set) return scr_mime_not_a_function(what);
|
||||
ScrStr *name = scr_mime_name_arg(args, argc);
|
||||
if (!name) return NULL;
|
||||
ScrDyn *result = NULL;
|
||||
long at = scr_mime_params_find(p, name);
|
||||
if (get) {
|
||||
result = at >= 0 ? scr_dyn_new_str(p->values[at]) : scr_dyn_new_null();
|
||||
} else if (has) {
|
||||
result = scr_dyn_new_bool(at >= 0);
|
||||
} else if (del) {
|
||||
if (at >= 0) {
|
||||
scr_str_release(p->values[at]);
|
||||
p->values[at] = NULL;
|
||||
}
|
||||
result = scr_dyn_retain(scr_dyn_undefined());
|
||||
} else {
|
||||
ScrStr *value = scr_dyn_string_coerce_js(argc > 1 ? args[1] : scr_dyn_undefined());
|
||||
if (value) {
|
||||
ScrMimeUnits nu = scr_mime_units(name);
|
||||
long bad_name = nu.len == 0 ? -1 : scr_mime_invalid_token(nu.units, 0, nu.len);
|
||||
free(nu.units);
|
||||
ScrMimeUnits vu = scr_mime_units(value);
|
||||
long bad_value = -1;
|
||||
for (size_t i = 0; i < vu.len && bad_value == -1; i++)
|
||||
if (!scr_mime_quoted(vu.units[i])) bad_value = (long)i;
|
||||
free(vu.units);
|
||||
if (name->len == 0 || bad_name != -1) {
|
||||
scr_mime_syntax_fail("parameter name", name, bad_name);
|
||||
} else if (bad_value != -1) {
|
||||
scr_mime_syntax_fail("parameter value", value, bad_value);
|
||||
} else {
|
||||
scr_mime_params_put(p, name, value);
|
||||
result = scr_dyn_retain(scr_dyn_undefined());
|
||||
}
|
||||
scr_str_release(value);
|
||||
}
|
||||
}
|
||||
scr_str_release(name);
|
||||
return result;
|
||||
}
|
||||
|
||||
static ScrDyn *scr_mime_empty_view(void *h) {
|
||||
(void)h;
|
||||
return scr_dyn_new_obj();
|
||||
}
|
||||
|
||||
/* A method read as a value (`params.get` without a call) has no dyn
|
||||
* function value here: it throws the "not supported yet" Error; other
|
||||
* names answer undefined. */
|
||||
static ScrDyn *scr_mime_member_get(const char *cls, const char *const *methods, const char *key, size_t len) {
|
||||
for (size_t i = 0; methods[i]; i++) {
|
||||
if (strlen(methods[i]) != len || memcmp(methods[i], key, len) != 0) continue;
|
||||
char message[128];
|
||||
int n = snprintf(message, sizeof message, "'%s.prototype.%s' as a value is not supported yet", cls, methods[i]);
|
||||
scr_throw_error_msg(SCR_ERR_ERROR, message, (size_t)n);
|
||||
return NULL;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static ScrDyn *scr_mime_params_get(void *h, const char *key, size_t len) {
|
||||
(void)h;
|
||||
static const char *const methods[] = {"delete", "entries", "get", "has", "keys", "set", "values", "toString", "toJSON", NULL};
|
||||
return scr_mime_member_get("MIMEParams", methods, key, len);
|
||||
}
|
||||
|
||||
static ScrDyn *scr_mime_params_box(ScrMimeParams *p) {
|
||||
static const ScrDynHandleOps ops = {
|
||||
.cls = "MIMEParams",
|
||||
.retain = scr_mime_params_retain,
|
||||
.release = scr_mime_params_release,
|
||||
.invoke = scr_mime_params_invoke,
|
||||
.get = scr_mime_params_get,
|
||||
.iter_step = scr_mime_params_step,
|
||||
.inspect_view = scr_mime_empty_view,
|
||||
.inspect_view_empty = true,
|
||||
};
|
||||
scr_dyn_handle_install(SCR_DYNH_MIME_PARAMS, &ops);
|
||||
ScrDyn *box = scr_dyn_new_handle(p, SCR_DYNH_MIME_PARAMS);
|
||||
scr_mime_params_release(p);
|
||||
return box;
|
||||
}
|
||||
|
||||
/* new util.MIMEParams(): an empty parameter map. +1. */
|
||||
ScrDyn *scr_mime_params_new(void) { return scr_mime_params_box(scr_mime_params_alloc()); }
|
||||
|
||||
/* ── MIMEType ───────────────────────────────────────────────────────── */
|
||||
|
||||
static void *scr_mime_type_retain(void *h) {
|
||||
((ScrMimeType *)h)->rc++;
|
||||
return h;
|
||||
}
|
||||
|
||||
static void scr_mime_type_release(void *h) {
|
||||
ScrMimeType *t = h;
|
||||
if (!t || --t->rc > 0) return;
|
||||
scr_str_release(t->type);
|
||||
scr_str_release(t->subtype);
|
||||
scr_dyn_release(t->params);
|
||||
free(t);
|
||||
}
|
||||
|
||||
/* The setters' validation: a non-empty token, lowered. +1 or NULL pending. */
|
||||
static ScrStr *scr_mime_part(const char *production, const ScrDyn *value) {
|
||||
ScrStr *text = scr_dyn_string_coerce_js(value);
|
||||
if (!text) return NULL;
|
||||
ScrMimeUnits u = scr_mime_units(text);
|
||||
long bad = scr_mime_invalid_token(u.units, 0, u.len);
|
||||
ScrStr *out = NULL;
|
||||
if (u.len == 0 || bad != -1) scr_mime_syntax_fail(production, text, bad);
|
||||
else out = scr_mime_latin1(u.units, 0, u.len, true);
|
||||
free(u.units);
|
||||
scr_str_release(text);
|
||||
return out;
|
||||
}
|
||||
|
||||
static ScrStr *scr_mime_type_to_string(const ScrMimeType *t) {
|
||||
ScrJsonBuf b;
|
||||
scr_jb_init(&b);
|
||||
scr_jb_put_str(&b, t->type);
|
||||
scr_jb_putc(&b, '/');
|
||||
scr_jb_put_str(&b, t->subtype);
|
||||
ScrStr *params = scr_mime_params_to_string(t->params->v.handle.ptr);
|
||||
if (params->len) {
|
||||
scr_jb_putc(&b, ';');
|
||||
scr_jb_put_str(&b, params);
|
||||
}
|
||||
scr_str_release(params);
|
||||
return scr_jb_finish(&b);
|
||||
}
|
||||
|
||||
static ScrDyn *scr_mime_type_get(void *h, const char *key, size_t len) {
|
||||
ScrMimeType *t = h;
|
||||
if (len == 4 && !memcmp(key, "type", 4)) return scr_dyn_new_str(t->type);
|
||||
if (len == 7 && !memcmp(key, "subtype", 7)) return scr_dyn_new_str(t->subtype);
|
||||
if (len == 6 && !memcmp(key, "params", 6)) return scr_dyn_retain(t->params);
|
||||
if (len == 7 && !memcmp(key, "essence", 7)) {
|
||||
ScrJsonBuf b;
|
||||
scr_jb_init(&b);
|
||||
scr_jb_put_str(&b, t->type);
|
||||
scr_jb_putc(&b, '/');
|
||||
scr_jb_put_str(&b, t->subtype);
|
||||
ScrStr *text = scr_jb_finish(&b);
|
||||
ScrDyn *out = scr_dyn_new_str(text);
|
||||
scr_str_release(text);
|
||||
return out;
|
||||
}
|
||||
static const char *const methods[] = {"toString", "toJSON", NULL};
|
||||
return scr_mime_member_get("MIMEType", methods, key, len);
|
||||
}
|
||||
|
||||
static bool scr_mime_type_set(void *h, const char *key, size_t len, const ScrDyn *value) {
|
||||
ScrMimeType *t = h;
|
||||
bool type = len == 4 && !memcmp(key, "type", 4), subtype = len == 7 && !memcmp(key, "subtype", 7);
|
||||
if (!type && !subtype) return false;
|
||||
ScrStr *part = scr_mime_part(type ? "type" : "subtype", value);
|
||||
if (!part) return true;
|
||||
ScrStr **slot = type ? &t->type : &t->subtype;
|
||||
scr_str_release(*slot);
|
||||
*slot = part;
|
||||
return true;
|
||||
}
|
||||
|
||||
static ScrDyn *scr_mime_type_invoke(void *h, ScrDyn *self, const char *method, ScrDyn *const *args, size_t argc,
|
||||
const char *what) {
|
||||
(void)self; (void)args; (void)argc;
|
||||
if (strcmp(method, "toString") && strcmp(method, "toJSON")) return scr_mime_not_a_function(what);
|
||||
ScrStr *text = scr_mime_type_to_string(h);
|
||||
ScrDyn *result = scr_dyn_new_str(text);
|
||||
scr_str_release(text);
|
||||
return result;
|
||||
}
|
||||
|
||||
/* new MIMEType(string, noThrow) and MIMEType.parse(string): NULL with the
|
||||
* error pending, or (parse) NULL with nothing pending for invalid input. */
|
||||
static ScrDyn *scr_mime_type_make(const ScrDyn *input, bool no_throw) {
|
||||
ScrStr *text = scr_dyn_string_coerce_js(input);
|
||||
if (!text) return NULL;
|
||||
ScrMimeUnits u = scr_mime_units(text);
|
||||
size_t n = u.len, pos = 0;
|
||||
while (pos < n && scr_mime_ws(u.units[pos])) pos++;
|
||||
size_t type_end = pos;
|
||||
while (type_end < n && u.units[type_end] != '/') type_end++;
|
||||
bool slash = type_end < n;
|
||||
long bad_type = scr_mime_invalid_token(u.units, pos, type_end);
|
||||
ScrDyn *result = NULL;
|
||||
if (type_end == pos || bad_type != -1 || !slash) {
|
||||
if (!no_throw) scr_mime_syntax_fail("type", text, bad_type);
|
||||
goto done;
|
||||
}
|
||||
size_t type_from = pos;
|
||||
pos = type_end + 1;
|
||||
size_t sub_end = pos;
|
||||
while (sub_end < n && u.units[sub_end] != ';') sub_end++;
|
||||
size_t sub_from = pos;
|
||||
size_t trimmed = sub_end;
|
||||
while (trimmed > sub_from && scr_mime_ws(u.units[trimmed - 1])) trimmed--;
|
||||
pos = sub_end < n ? sub_end + 1 : sub_end;
|
||||
long bad_sub = scr_mime_invalid_token(u.units, sub_from, trimmed);
|
||||
if (trimmed == sub_from || bad_sub != -1) {
|
||||
if (!no_throw) scr_mime_syntax_fail("subtype", text, bad_sub);
|
||||
goto done;
|
||||
}
|
||||
ScrMimeType *t = calloc(1, sizeof *t);
|
||||
if (!t) scr_mime_oom();
|
||||
t->rc = 1;
|
||||
t->type = scr_mime_latin1(u.units, type_from, type_end, true);
|
||||
t->subtype = scr_mime_latin1(u.units, sub_from, trimmed, true);
|
||||
ScrMimeParams *p = scr_mime_params_alloc();
|
||||
scr_mime_params_parse(p, u.units + pos, n - pos);
|
||||
t->params = scr_mime_params_box(p);
|
||||
static const ScrDynHandleOps ops = {
|
||||
.cls = "MIMEType",
|
||||
.retain = scr_mime_type_retain,
|
||||
.release = scr_mime_type_release,
|
||||
.invoke = scr_mime_type_invoke,
|
||||
.get = scr_mime_type_get,
|
||||
.set = scr_mime_type_set,
|
||||
.inspect_view = scr_mime_empty_view,
|
||||
.inspect_view_empty = true,
|
||||
};
|
||||
scr_dyn_handle_install(SCR_DYNH_MIME_TYPE, &ops);
|
||||
result = scr_dyn_new_handle(t, SCR_DYNH_MIME_TYPE);
|
||||
scr_mime_type_release(t);
|
||||
done:
|
||||
free(u.units);
|
||||
scr_str_release(text);
|
||||
return result;
|
||||
}
|
||||
|
||||
/* new util.MIMEType(string[, noThrowSymbol]): a second argument that is not
|
||||
* null or undefined throws ERR_ILLEGAL_CONSTRUCTOR (Node's internal
|
||||
* kNoThrow symbol is unreachable from programs). +1 or NULL pending. */
|
||||
ScrDyn *scr_mime_type_new(ScrDyn *input, ScrDyn *no_throw) {
|
||||
if (no_throw->kind != SCR_DYN_UNDEF && no_throw->kind != SCR_DYN_NULL) {
|
||||
static const char message[] = "Illegal constructor";
|
||||
scr_throw_error_msg_code(SCR_ERR_TYPE, message, sizeof message - 1, "ERR_ILLEGAL_CONSTRUCTOR");
|
||||
return NULL;
|
||||
}
|
||||
return scr_mime_type_make(input, false);
|
||||
}
|
||||
|
||||
/* MIMEType.parse(string): the MIMEType, or null for invalid input. +1, or
|
||||
* NULL with a conversion error pending. */
|
||||
ScrDyn *scr_mime_type_parse(ScrDyn *input) {
|
||||
ScrDyn *result = scr_mime_type_make(input, true);
|
||||
if (result || scr_exc_pending()) return result;
|
||||
return scr_dyn_new_null();
|
||||
}
|
||||
|
||||
/* `value instanceof MIMEType` (kind 0) or `MIMEParams` (1). */
|
||||
bool scr_mime_is(const ScrDyn *value, double kind) {
|
||||
return value->kind == SCR_DYN_HANDLE &&
|
||||
value->v.handle.tag == (kind == 0 ? SCR_DYNH_MIME_TYPE : SCR_DYNH_MIME_PARAMS);
|
||||
}
|
||||
@@ -1701,3 +1701,33 @@ ScrDyn *scr_fs_constants_object(void) {
|
||||
scr_atexit(scr_fs_constants_clear);
|
||||
return scr_dyn_retain(o);
|
||||
}
|
||||
|
||||
/* util.convertProcessSignalToExitCode(signalCode): 128 plus the number of
|
||||
* the os.constants.signals entry, and validateOneOf's
|
||||
* ERR_INVALID_ARG_VALUE, listing every name, for anything else. NaN with
|
||||
* the exception pending. */
|
||||
double scr_util_signal_exit_code(const ScrDyn *code) {
|
||||
ScrDyn *signals = scr_dyn_new_obj();
|
||||
scr_os_signal_constants(signals);
|
||||
const ScrDyn *number =
|
||||
code->kind == SCR_DYN_STR ? scr_dyn_obj_get(signals, code->v.str->data, code->v.str->len) : NULL;
|
||||
double result = NAN;
|
||||
if (number && number->kind == SCR_DYN_NUM) {
|
||||
result = 128 + number->v.num;
|
||||
} else {
|
||||
ScrJsonBuf reason;
|
||||
scr_jb_init(&reason);
|
||||
scr_jb_puts(&reason, "must be one of: ");
|
||||
for (size_t i = 0; i < signals->v.obj.len; i++) {
|
||||
if (i > 0) scr_jb_puts(&reason, ", ");
|
||||
scr_jb_putc(&reason, '\'');
|
||||
scr_jb_write(&reason, signals->v.obj.entries[i].key, signals->v.obj.entries[i].key_len);
|
||||
scr_jb_putc(&reason, '\'');
|
||||
}
|
||||
ScrStr *text = scr_jb_finish(&reason);
|
||||
scr_dyn_arg_value_fail("signalCode", text->data, code);
|
||||
scr_str_release(text);
|
||||
}
|
||||
scr_dyn_release(signals);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,9 @@ ScrStr *scr_os_hostname(void);
|
||||
ScrStr *scr_os_endianness(void);
|
||||
double scr_os_freemem(void);
|
||||
double scr_os_uptime(void);
|
||||
/* util.convertProcessSignalToExitCode: 128 plus the signal number, or NaN
|
||||
* with ERR_INVALID_ARG_VALUE pending (scr_os.c). */
|
||||
double scr_util_signal_exit_code(const struct ScrDyn *code);
|
||||
ScrArr *scr_os_loadavg(void);
|
||||
/* os.getPriority(pid): validateInt32, then the scheduling priority;
|
||||
* failures throw Node's RangeError or SystemError (ERR_SYSTEM_ERROR). */
|
||||
|
||||
@@ -66,6 +66,24 @@ void scr_util_inherits_prototype(struct ScrDyn *ctor, struct ScrDyn *super_ctor,
|
||||
struct ScrDyn *super_prototype);
|
||||
struct ScrDyn *scr_util_deprecate(struct ScrDyn *fn, struct ScrDyn *msg, struct ScrDyn *code,
|
||||
struct ScrDyn *options);
|
||||
/* util.isArray and util._extend, with Node's DEP0044 and DEP0060 on the
|
||||
* first call (scr_util_callable.c). _extend answers target +1. */
|
||||
bool scr_util_is_array_deprecated(const struct ScrDyn *value);
|
||||
struct ScrDyn *scr_util_extend(struct ScrDyn *target, struct ScrDyn *source);
|
||||
/* util.diff(actual, expected): Node's operation list (+1), or NULL with
|
||||
* ERR_INVALID_ARG_TYPE pending. */
|
||||
struct ScrDyn *scr_util_diff(struct ScrDyn *actual, struct ScrDyn *expected);
|
||||
/* util.MIMEType and util.MIMEParams (scr_mime.c): +1 handles, or NULL
|
||||
* with the error pending; parse answers null for invalid input. */
|
||||
struct ScrDyn *scr_mime_type_new(struct ScrDyn *input, struct ScrDyn *no_throw);
|
||||
struct ScrDyn *scr_mime_type_parse(struct ScrDyn *input);
|
||||
struct ScrDyn *scr_mime_params_new(void);
|
||||
bool scr_mime_is(const struct ScrDyn *value, double kind);
|
||||
/* util.debounce(fn, wait, options) and util.throttle(fn, limit, interval,
|
||||
* options) (scr_util_timing.c): the wrapping function (+1), or NULL with
|
||||
* the validation error pending. */
|
||||
struct ScrDyn *scr_util_debounce(struct ScrDyn *fn, struct ScrDyn *wait, struct ScrDyn *options);
|
||||
struct ScrDyn *scr_util_throttle(struct ScrDyn *fn, struct ScrDyn *limit, struct ScrDyn *interval, struct ScrDyn *options);
|
||||
struct ScrDyn *scr_util_callbackify(struct ScrDyn *original);
|
||||
struct ScrDyn *scr_util_promisify(struct ScrDyn *original);
|
||||
struct ScrDyn *scr_util_debuglog(struct ScrDyn *section, struct ScrDyn *callback);
|
||||
|
||||
@@ -2418,6 +2418,57 @@ static void *scr_normalize_realloc(void *opaque, void *ptr, size_t size) {
|
||||
return realloc(ptr, size);
|
||||
}
|
||||
|
||||
/* unicode_normalize, except that U+11A7 never composes. libunicode composes
|
||||
* an LV syllable with U+11A7 as a trailing consonant (those are U+11A8 to
|
||||
* U+11C2), which drops it: "\uAC00\u11A7" became "\uAC00". U+11A7 is a
|
||||
* class-0 vowel that composes with nothing, so for the composing forms the
|
||||
* code points between occurrences of it normalize on their own and it is
|
||||
* kept as it is, as in Node. */
|
||||
static int scr_unicode_normalize(uint32_t **pdst, const uint32_t *src, int len,
|
||||
UnicodeNormalizationEnum mode) {
|
||||
int at = 0;
|
||||
if (mode == UNICODE_NFC || mode == UNICODE_NFKC)
|
||||
while (at < len && src[at] != 0x11A7) at++;
|
||||
else
|
||||
at = len;
|
||||
if (at == len) return unicode_normalize(pdst, src, len, mode, NULL, scr_normalize_realloc);
|
||||
uint32_t *out = NULL;
|
||||
size_t count = 0, cap = 0;
|
||||
int start = 0;
|
||||
for (int i = 0; i <= len; i++) {
|
||||
if (i < len && src[i] != 0x11A7) continue;
|
||||
uint32_t *part = NULL;
|
||||
int n = 0;
|
||||
if (i > start) {
|
||||
n = unicode_normalize(&part, src + start, i - start, mode, NULL, scr_normalize_realloc);
|
||||
if (n < 0) {
|
||||
free(out);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
size_t need = count + (size_t)n + 1;
|
||||
if (need > cap) {
|
||||
size_t grown = cap ? cap * 2 : 16;
|
||||
if (grown < need) grown = need;
|
||||
uint32_t *bigger = realloc(out, grown * sizeof *out);
|
||||
if (!bigger) {
|
||||
free(part);
|
||||
free(out);
|
||||
return -1;
|
||||
}
|
||||
out = bigger;
|
||||
cap = grown;
|
||||
}
|
||||
if (n > 0) memcpy(out + count, part, (size_t)n * sizeof *out);
|
||||
count += (size_t)n;
|
||||
free(part);
|
||||
if (i < len) out[count++] = 0x11A7;
|
||||
start = i + 1;
|
||||
}
|
||||
*pdst = out;
|
||||
return (int)count;
|
||||
}
|
||||
|
||||
ScrStr *scr_str_normalize(const ScrStr *s, const ScrStr *form) {
|
||||
UnicodeNormalizationEnum mode;
|
||||
if (form->len == 3 && !memcmp(form->data, "NFC", 3)) mode = UNICODE_NFC;
|
||||
@@ -2442,7 +2493,7 @@ ScrStr *scr_str_normalize(const ScrStr *s, const ScrStr *form) {
|
||||
offset += step;
|
||||
}
|
||||
uint32_t *output = NULL;
|
||||
int count = unicode_normalize(&output, input, length, mode, NULL, scr_normalize_realloc);
|
||||
int count = scr_unicode_normalize(&output, input, length, mode);
|
||||
free(input);
|
||||
if (count < 0) scr_regex_oom();
|
||||
ScrJsonBuf buffer;
|
||||
@@ -2455,7 +2506,7 @@ ScrStr *scr_str_normalize(const ScrStr *s, const ScrStr *form) {
|
||||
uint32_t *scr_nfc_code_points(const uint32_t *input, size_t length, size_t *out_len) {
|
||||
if (length > INT32_MAX) scr_regex_oom();
|
||||
uint32_t *output = NULL;
|
||||
int count = unicode_normalize(&output, input, (int)length, UNICODE_NFC, NULL, scr_normalize_realloc);
|
||||
int count = scr_unicode_normalize(&output, input, (int)length, UNICODE_NFC);
|
||||
if (count < 0) scr_regex_oom();
|
||||
*out_len = (size_t)count;
|
||||
return output;
|
||||
|
||||
@@ -150,6 +150,11 @@ ScrSym *scr_sym_for(ScrStr *key) {
|
||||
s->reg_key = scr_str_retain(key);
|
||||
s->reg_next = g_sym_registry;
|
||||
g_sym_registry = scr_sym_retain(s);
|
||||
if (key->len == 26 && !memcmp(key->data, "nodejs.util.inspect.custom", 26)) {
|
||||
ScrDyn *custom = scr_dyn_new_symbol(s);
|
||||
scr_dyn_install_inspect_custom_symbol(custom);
|
||||
scr_dyn_release(custom);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
* and reported on the next tick (scr_warning_emit_deferred), which also
|
||||
* applies process.noDeprecation and process.throwDeprecation. debuglog
|
||||
* matches sections against NODE_DEBUG as the process started and writes
|
||||
* Node's "SECTION PID: message" lines to stderr. All arguments are
|
||||
* borrowed. */
|
||||
* Node's "SECTION PID: message" lines, formatted by scr_insp_format, to
|
||||
* stderr. All arguments are borrowed. */
|
||||
#include "scr_runtime.h"
|
||||
|
||||
#include <stdio.h>
|
||||
@@ -365,6 +365,66 @@ ScrDyn *scr_util_deprecate(ScrDyn *fn, ScrDyn *msg, ScrDyn *code, ScrDyn *option
|
||||
return deprecated;
|
||||
}
|
||||
|
||||
/* ── util.isArray and util._extend ─────────────────────────────────────
|
||||
* Node wraps both with its internal deprecate(): the first call while
|
||||
* deprecation warnings are on reports DEP0044 or DEP0060 on the next tick,
|
||||
* unless a util.deprecate wrapper already reported that code. */
|
||||
static SCR_TL bool uc_is_array_warned = false;
|
||||
static SCR_TL bool uc_extend_warned = false;
|
||||
|
||||
static bool uc_internal_deprecation(bool *warned, const char *message, const char *code) {
|
||||
if (scr_process_deprecation_flag_on(0) || *warned) return true;
|
||||
*warned = true;
|
||||
if (!uc_codes_warned) {
|
||||
uc_codes_warned = scr_dyn_new_obj_null_proto();
|
||||
scr_atexit(uc_codes_warned_teardown);
|
||||
}
|
||||
size_t code_len = strlen(code);
|
||||
if (scr_dyn_obj_get(uc_codes_warned, code, code_len)) return true;
|
||||
ScrStr *text = scr_str_new(message, strlen(message));
|
||||
ScrDyn *msg = scr_dyn_new_str(text);
|
||||
scr_str_release(text);
|
||||
ScrStr *key = scr_str_new(code, code_len);
|
||||
bool ok = uc_emit(msg, "DeprecationWarning", key);
|
||||
if (ok) scr_dyn_obj_set(uc_codes_warned, code, code_len, scr_dyn_new_bool(true));
|
||||
scr_str_release(key);
|
||||
scr_dyn_release(msg);
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool scr_util_is_array_deprecated(const ScrDyn *value) {
|
||||
if (!uc_internal_deprecation(&uc_is_array_warned,
|
||||
"The `util.isArray` API is deprecated. Please use `Array.isArray()` instead.",
|
||||
"DEP0044"))
|
||||
return false;
|
||||
return value->kind == SCR_DYN_ARR || scr_dyn_ref_is_array(value);
|
||||
}
|
||||
|
||||
/* util._extend(target, source): source's own enumerable string keys,
|
||||
* assigned to target from the last key to the first; a source that is
|
||||
* null or not an object leaves target as it is. +1. */
|
||||
ScrDyn *scr_util_extend(ScrDyn *target, ScrDyn *source) {
|
||||
if (!uc_internal_deprecation(&uc_extend_warned,
|
||||
"The `util._extend` API is deprecated. Please use Object.assign() instead.",
|
||||
"DEP0060"))
|
||||
return NULL;
|
||||
bool object = source->kind == SCR_DYN_OBJ || source->kind == SCR_DYN_ARR || source->kind == SCR_DYN_TYPED_REF ||
|
||||
source->kind == SCR_DYN_HANDLE || source->kind == SCR_DYN_BYTES || source->kind == SCR_DYN_PROMISE ||
|
||||
source->kind == SCR_DYN_PROXY;
|
||||
if (!object) return scr_dyn_retain(target);
|
||||
ScrDyn *keys = scr_dyn_obj_keys(source);
|
||||
if (!keys) return NULL;
|
||||
for (size_t i = keys->v.arr.len; i-- > 0 && !scr_exc_pending();) {
|
||||
ScrDyn *key = keys->v.arr.items[i];
|
||||
ScrDyn *value = scr_dyn_reflect_get(source, key, source);
|
||||
if (!value) break;
|
||||
scr_dyn_key_set(target, key->v.str, value);
|
||||
scr_dyn_release(value);
|
||||
}
|
||||
scr_dyn_release(keys);
|
||||
return scr_exc_pending() ? NULL : scr_dyn_retain(target);
|
||||
}
|
||||
|
||||
/* ── util.callbackify ──────────────────────────────────────────────────
|
||||
* The wrapper pops the callback, binds it to the call's receiver and
|
||||
* calls original; the promise's settlement schedules the callback on the
|
||||
@@ -533,163 +593,6 @@ ScrDyn *scr_util_callbackify(ScrDyn *original) {
|
||||
return callbackified;
|
||||
}
|
||||
|
||||
/* ── util.format over dyn arguments ────────────────────────────────────
|
||||
* formatWithOptionsInternal for debuglog's lines: the %-substitutions
|
||||
* (%s %d %i %f %j %o %O %c %%) with Node's argument cursor, then the
|
||||
* remaining arguments space-joined, strings verbatim and everything else
|
||||
* inspected. With terminal colors, primitives take inspect's default
|
||||
* styles; colored object renderings have no lowering. */
|
||||
static bool uc_primitive(const ScrDyn *value) {
|
||||
return !uc_object(value);
|
||||
}
|
||||
|
||||
static ScrStr *uc_inspect(ScrDyn *value, double depth, bool colors) {
|
||||
if (!colors) return scr_insp_dyn(value, 0, depth);
|
||||
if (!uc_primitive(value)) {
|
||||
uc_fence("util.debuglog output of objects on a color terminal has no lowering");
|
||||
return NULL;
|
||||
}
|
||||
const char *open = "\x1b[33m", *close = "\x1b[39m";
|
||||
if (value->kind == SCR_DYN_UNDEF) open = "\x1b[90m";
|
||||
else if (value->kind == SCR_DYN_NULL) open = "\x1b[1m", close = "\x1b[22m";
|
||||
else if (value->kind == SCR_DYN_STR || value->kind == SCR_DYN_SYMBOL) open = "\x1b[32m";
|
||||
ScrStr *text = scr_insp_dyn(value, 0, depth);
|
||||
ScrJsonBuf b;
|
||||
scr_jb_init(&b);
|
||||
scr_jb_puts(&b, open);
|
||||
scr_jb_put_str(&b, text);
|
||||
scr_jb_puts(&b, close);
|
||||
scr_str_release(text);
|
||||
return scr_jb_finish(&b);
|
||||
}
|
||||
|
||||
/* hasBuiltInToString's negative answer for checked objects: an own or
|
||||
* inherited toString the program defined. Symbol-keyed members (where
|
||||
* Symbol.toPrimitive would also count) are reported as unknown. */
|
||||
static int uc_custom_to_string(const ScrDyn *value) {
|
||||
for (const ScrDyn *current = value; current && current->kind == SCR_DYN_OBJ; current = current->prototype) {
|
||||
if (current->symbol_properties) return -1;
|
||||
const ScrDyn *member = scr_dyn_obj_get(current, "toString", 8);
|
||||
if (member) return member->kind == SCR_DYN_FUNC;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ScrStr *uc_format_s(ScrDyn *value) {
|
||||
if (value->kind == SCR_DYN_STR) return scr_str_retain(value->v.str);
|
||||
if (value->kind == SCR_DYN_NUM) return scr_insp_f64(value->v.num);
|
||||
if (value->kind == SCR_DYN_FUNC) {
|
||||
uc_fence("util.debuglog %s of a function prints its source text, which a compiled binary does not keep");
|
||||
return NULL;
|
||||
}
|
||||
if (value->kind == SCR_DYN_OBJ && scr_dyn_obj_get(value, "%error", 6) == NULL) {
|
||||
int custom = uc_custom_to_string(value);
|
||||
if (custom < 0) {
|
||||
uc_fence("util.debuglog %s of objects with symbol-keyed members has no lowering");
|
||||
return NULL;
|
||||
}
|
||||
if (custom) return scr_dyn_string_coerce_js(value);
|
||||
}
|
||||
return scr_insp_dyn_s(value, 0);
|
||||
}
|
||||
|
||||
static ScrStr *uc_format_number(ScrDyn *value, int spec) {
|
||||
if (value->kind == SCR_DYN_BIGINT && spec != 'f') return scr_bigint_inspect(value->v.bigint);
|
||||
if (value->kind == SCR_DYN_SYMBOL) return scr_str_new("NaN", 3);
|
||||
double number;
|
||||
if (spec == 'd') {
|
||||
if (value->kind == SCR_DYN_BIGINT) return scr_bigint_inspect(value->v.bigint);
|
||||
if (!scr_dyn_number_coerce_js(value, &number)) return NULL;
|
||||
} else {
|
||||
ScrStr *text = scr_dyn_string_coerce_js(value);
|
||||
if (!text) return NULL;
|
||||
number = spec == 'i' ? scr_parse_int(text, 0) : scr_parse_float(text);
|
||||
scr_str_release(text);
|
||||
}
|
||||
return scr_insp_f64(number);
|
||||
}
|
||||
|
||||
static ScrStr *uc_format(ScrDyn *const *args, size_t argc, bool colors) {
|
||||
if (argc == 0) return scr_str_new("", 0);
|
||||
ScrJsonBuf out;
|
||||
scr_jb_init(&out);
|
||||
size_t a = 0;
|
||||
const char *join = "";
|
||||
if (args[0]->kind == SCR_DYN_STR) {
|
||||
const ScrStr *first = args[0]->v.str;
|
||||
if (argc == 1) {
|
||||
scr_str_release(scr_jb_finish(&out));
|
||||
return scr_str_retain((ScrStr *)first);
|
||||
}
|
||||
const char *text = first->data;
|
||||
size_t last = 0;
|
||||
for (size_t i = 0; first->len > 0 && i < first->len - 1; i++) {
|
||||
if (text[i] != '%') continue;
|
||||
int next = (unsigned char)text[++i];
|
||||
if (a + 1 != argc) {
|
||||
ScrStr *piece = NULL;
|
||||
switch (next) {
|
||||
case 's': piece = uc_format_s(args[++a]); break;
|
||||
case 'j': piece = scr_util_format_j(args[++a]); break;
|
||||
case 'd':
|
||||
case 'i':
|
||||
case 'f': piece = uc_format_number(args[++a], next); break;
|
||||
case 'O': piece = uc_inspect(args[++a], 2, colors); break;
|
||||
case 'o':
|
||||
if (!uc_primitive(args[a + 1])) {
|
||||
uc_fence("util.debuglog %o of objects renders hidden properties, which have no lowering; use %O");
|
||||
break;
|
||||
}
|
||||
piece = uc_inspect(args[++a], 4, colors);
|
||||
break;
|
||||
case 'c':
|
||||
a += 1;
|
||||
piece = scr_str_new("", 0);
|
||||
break;
|
||||
case '%':
|
||||
scr_jb_write(&out, text + last, i - last);
|
||||
last = i + 1;
|
||||
continue;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
if (!piece) {
|
||||
scr_str_release(scr_jb_finish(&out));
|
||||
return NULL;
|
||||
}
|
||||
if (last != i - 1) scr_jb_write(&out, text + last, i - 1 - last);
|
||||
scr_jb_put_str(&out, piece);
|
||||
scr_str_release(piece);
|
||||
last = i + 1;
|
||||
} else if (next == '%') {
|
||||
scr_jb_write(&out, text + last, i - last);
|
||||
last = i + 1;
|
||||
}
|
||||
}
|
||||
if (last != 0) {
|
||||
a++;
|
||||
join = " ";
|
||||
if (last < first->len) scr_jb_write(&out, text + last, first->len - last);
|
||||
}
|
||||
}
|
||||
for (; a < argc; a++) {
|
||||
scr_jb_puts(&out, join);
|
||||
if (args[a]->kind == SCR_DYN_STR) {
|
||||
scr_jb_put_str(&out, args[a]->v.str);
|
||||
} else {
|
||||
ScrStr *piece = uc_inspect(args[a], 2, colors);
|
||||
if (!piece) {
|
||||
scr_str_release(scr_jb_finish(&out));
|
||||
return NULL;
|
||||
}
|
||||
scr_jb_put_str(&out, piece);
|
||||
scr_str_release(piece);
|
||||
}
|
||||
join = " ";
|
||||
}
|
||||
return scr_jb_finish(&out);
|
||||
}
|
||||
|
||||
/* ── util.debuglog ─────────────────────────────────────────────────────
|
||||
* The logger defers its NODE_DEBUG test to its first call or `enabled`
|
||||
* read. The first call also selects the section's shared implementation
|
||||
@@ -755,7 +658,10 @@ static bool uc_debug_test(const ScrStr *section) {
|
||||
static ScrDyn *uc_debug_write(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
|
||||
bool colors = scr_getenv("FORCE_COLOR") ? scr_util_terminal_colors()
|
||||
: scr_process_is_tty(2) && scr_util_terminal_colors();
|
||||
ScrStr *message = uc_format(args, argc, colors);
|
||||
ScrInspFormatOptions options;
|
||||
scr_insp_format_defaults(&options, "util.debuglog");
|
||||
options.colors = colors;
|
||||
ScrStr *message = scr_insp_format(args, argc, &options);
|
||||
if (!message) return NULL;
|
||||
ScrDyn *set = uc_cap(closure, 0);
|
||||
char pid[64];
|
||||
@@ -912,3 +818,158 @@ ScrDyn *scr_util_debuglog(ScrDyn *section, ScrDyn *callback) {
|
||||
}
|
||||
return logger;
|
||||
}
|
||||
|
||||
/* ── util.diff(actual, expected) ───────────────────────────────────────
|
||||
* Node's diff over strings (one entry per UTF-16 code unit) or arrays of
|
||||
* strings: identical values answer an empty array before validation;
|
||||
* otherwise each value must be a string or an array of strings
|
||||
* (ERR_INVALID_ARG_TYPE naming `actual`, `actual[i]`, ...), and the answer
|
||||
* is myersDiff's operation list reversed: [0, item] for kept items,
|
||||
* [1, item] for items of actual and [-1, item] for items of expected. */
|
||||
typedef struct {
|
||||
ScrStr **items;
|
||||
size_t len;
|
||||
} UcDiffSeq;
|
||||
|
||||
static void uc_diff_seq_free(UcDiffSeq *seq) {
|
||||
for (size_t i = 0; i < seq->len; i++) scr_str_release(seq->items[i]);
|
||||
free(seq->items);
|
||||
seq->items = NULL;
|
||||
seq->len = 0;
|
||||
}
|
||||
|
||||
static bool uc_diff_input(const ScrDyn *value, const char *name, UcDiffSeq *out) {
|
||||
out->items = NULL;
|
||||
out->len = 0;
|
||||
if (value->kind == SCR_DYN_STR) {
|
||||
ScrStr *text = value->v.str;
|
||||
size_t len = (size_t)scr_str_utf16_len(text);
|
||||
out->items = malloc((len ? len : 1) * sizeof *out->items);
|
||||
if (!out->items) scr_trap("scriptc: out of memory\n");
|
||||
for (size_t i = 0; i < len; i++) out->items[out->len++] = scr_str_char_at(text, (double)i);
|
||||
return true;
|
||||
}
|
||||
ScrDyn *array = value->kind == SCR_DYN_TYPED_REF && scr_dyn_ref_is_array(value)
|
||||
? scr_dyn_typed_ref_materialize(value)
|
||||
: value->kind == SCR_DYN_ARR ? scr_dyn_retain((ScrDyn *)value) : NULL;
|
||||
if (!array) {
|
||||
scr_dyn_arg_type_fail(name, "of type string", value);
|
||||
return false;
|
||||
}
|
||||
size_t len = array->v.arr.len;
|
||||
out->items = malloc((len ? len : 1) * sizeof *out->items);
|
||||
if (!out->items) scr_trap("scriptc: out of memory\n");
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
const ScrDyn *item = array->v.arr.presence && !array->v.arr.presence[i] ? scr_dyn_undefined()
|
||||
: array->v.arr.items[i];
|
||||
if (item->kind != SCR_DYN_STR) {
|
||||
char element[64];
|
||||
snprintf(element, sizeof element, "%s[%zu]", name, i);
|
||||
scr_dyn_arg_type_fail(element, "of type string", item);
|
||||
scr_dyn_release(array);
|
||||
uc_diff_seq_free(out);
|
||||
return false;
|
||||
}
|
||||
out->items[out->len++] = scr_str_retain(item->v.str);
|
||||
}
|
||||
scr_dyn_release(array);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void uc_diff_push(ScrDyn *result, int op, ScrStr *item) {
|
||||
ScrDyn *entry = scr_dyn_new_arr();
|
||||
scr_dyn_arr_push(entry, scr_dyn_new_num((double)op));
|
||||
scr_dyn_arr_push(entry, scr_dyn_new_str(item));
|
||||
scr_dyn_arr_push(result, entry);
|
||||
}
|
||||
|
||||
ScrDyn *scr_util_diff(ScrDyn *actual, ScrDyn *expected) {
|
||||
if (scr_dyn_strict_eq(actual, expected)) return scr_dyn_new_arr();
|
||||
UcDiffSeq a, e;
|
||||
if (!uc_diff_input(actual, "actual", &a)) return NULL;
|
||||
if (!uc_diff_input(expected, "expected", &e)) {
|
||||
uc_diff_seq_free(&a);
|
||||
return NULL;
|
||||
}
|
||||
size_t max = a.len + e.len;
|
||||
if (max > 2147483647u) {
|
||||
uc_diff_seq_free(&a);
|
||||
uc_diff_seq_free(&e);
|
||||
char message[160];
|
||||
int n = snprintf(message, sizeof message,
|
||||
"The value of \"myersDiff input size\" is out of range. It must be < 2^31. Received %zu", max);
|
||||
scr_throw_error_msg_code(SCR_ERR_RANGE, message, (size_t)n, "ERR_OUT_OF_RANGE");
|
||||
return NULL;
|
||||
}
|
||||
/* Two distinct empty sequences: Node's myersDiff reads past its
|
||||
* one-element state, finds no end and answers undefined, which the
|
||||
* ArrayPrototypeReverse of util.diff rejects. The trace below would be read
|
||||
* out of bounds. */
|
||||
if (max == 0) {
|
||||
uc_diff_seq_free(&a);
|
||||
uc_diff_seq_free(&e);
|
||||
static const char message[] = "Cannot convert undefined or null to object";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
|
||||
return NULL;
|
||||
}
|
||||
size_t width = 2 * max + 1;
|
||||
int32_t *v = calloc(width, sizeof *v);
|
||||
int32_t **trace = malloc((max + 1) * sizeof *trace);
|
||||
if (!v || !trace) scr_trap("scriptc: out of memory\n");
|
||||
size_t levels = 0;
|
||||
bool done = false;
|
||||
for (size_t level = 0; level <= max && !done; level++) {
|
||||
trace[levels] = malloc(width * sizeof **trace);
|
||||
if (!trace[levels]) scr_trap("scriptc: out of memory\n");
|
||||
memcpy(trace[levels], v, width * sizeof *v);
|
||||
levels++;
|
||||
long d = (long)level;
|
||||
for (long k = -d; k <= d; k += 2) {
|
||||
size_t offset = (size_t)(k + (long)max);
|
||||
int32_t previous = offset > 0 ? v[offset - 1] : 0;
|
||||
int32_t next = offset + 1 < width ? v[offset + 1] : 0;
|
||||
long x = k == -d || (k != d && previous < next) ? next : (long)previous + 1;
|
||||
long y = x - k;
|
||||
while ((size_t)x < a.len && y >= 0 && (size_t)y < e.len && scr_str_eq(a.items[x], e.items[y])) {
|
||||
x++;
|
||||
y++;
|
||||
}
|
||||
v[offset] = (int32_t)x;
|
||||
if ((size_t)x >= a.len && y >= 0 && (size_t)y >= e.len) {
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* backtrack: the operations from the end, which the answer reverses. */
|
||||
ScrDyn *reversed = scr_dyn_new_arr();
|
||||
long x = (long)a.len, y = (long)e.len;
|
||||
for (long level = (long)levels - 1; level >= 0; level--) {
|
||||
const int32_t *row = trace[level];
|
||||
long k = x - y;
|
||||
size_t offset = (size_t)(k + (long)max);
|
||||
long prev_k;
|
||||
if (k == -level || (k != level && row[offset - 1] < row[offset + 1])) prev_k = k + 1;
|
||||
else prev_k = k - 1;
|
||||
long prev_x = row[(size_t)(prev_k + (long)max)];
|
||||
long prev_y = prev_x - prev_k;
|
||||
while (x > prev_x && y > prev_y) {
|
||||
uc_diff_push(reversed, 0, a.items[x - 1]);
|
||||
x--;
|
||||
y--;
|
||||
}
|
||||
if (level > 0) {
|
||||
if (x > prev_x) uc_diff_push(reversed, 1, a.items[--x]);
|
||||
else uc_diff_push(reversed, -1, e.items[--y]);
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < levels; i++) free(trace[i]);
|
||||
free(trace);
|
||||
free(v);
|
||||
ScrDyn *result = scr_dyn_new_arr();
|
||||
for (size_t i = reversed->v.arr.len; i-- > 0;) scr_dyn_arr_push(result, scr_dyn_retain(reversed->v.arr.items[i]));
|
||||
scr_dyn_release(reversed);
|
||||
uc_diff_seq_free(&a);
|
||||
uc_diff_seq_free(&e);
|
||||
return result;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,13 +16,16 @@
|
||||
* [--seeds 1,2,3] [--jobs 8] [--entry <file.ts>]... [--out <dir>]
|
||||
* [--fail-on names|content]
|
||||
*
|
||||
* --stride and --offset sample the top-level corpus programs by number, or by
|
||||
* a hash of the name of a program without one.
|
||||
*
|
||||
* Needs a built compiler (pnpm build). Writes <out>/results.json and prints
|
||||
* a ranking of fields by the programs whose IR they change. With --fail-on,
|
||||
* it exits with status 1 when some program's IR differs between orders:
|
||||
* in any way (names), or beyond names and ids (content). */
|
||||
import { spawn } from "node:child_process";
|
||||
import { createHash } from "node:crypto";
|
||||
import { mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
||||
import { mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
||||
import { cpus, tmpdir } from "node:os";
|
||||
import { basename, join, resolve } from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
@@ -306,12 +309,25 @@ if (values["fail-on"] !== undefined && !["names", "content"].includes(values["fa
|
||||
const out = resolve(values.out);
|
||||
mkdirSync(out, { recursive: true });
|
||||
const seeds = values.seeds.split(",").filter(Boolean);
|
||||
/** A top-level corpus program's sampling key: its number, or, for a program
|
||||
* named without one, a stable hash of its name (FNV-1a). */
|
||||
const programKey = (name) =>
|
||||
/^\d+-/.test(name)
|
||||
? Number.parseInt(name, 10)
|
||||
: [...name].reduce((h, c) => Math.imul(h ^ c.charCodeAt(0), 16777619) >>> 0, 2166136261);
|
||||
/** Numbered programs by number, then the named ones by name. */
|
||||
const programOrder = (a, b) =>
|
||||
/^\d+-/.test(a) !== /^\d+-/.test(b)
|
||||
? /^\d+-/.test(a)
|
||||
? -1
|
||||
: 1
|
||||
: programKey(a) - programKey(b) || (a < b ? -1 : a > b ? 1 : 0);
|
||||
const corpus = join(root, "tests/corpus");
|
||||
const programs = [
|
||||
...readdirSync(corpus)
|
||||
.filter((name) => /^\d+-.*\.ts$/.test(name))
|
||||
.filter((name) => Number.parseInt(name, 10) % Number(values.stride) === Number(values.offset))
|
||||
.sort((a, b) => Number.parseInt(a, 10) - Number.parseInt(b, 10))
|
||||
.filter((name) => /^[\w-]+\.ts$/.test(name) && statSync(join(corpus, name)).isFile())
|
||||
.filter((name) => programKey(name) % Number(values.stride) === Number(values.offset))
|
||||
.sort(programOrder)
|
||||
.slice(0, Number(values.limit))
|
||||
.map((name) => join(corpus, name)),
|
||||
...values.entry.map((entry) => resolve(entry)),
|
||||
|
||||
@@ -7,8 +7,9 @@
|
||||
* Builds an instrumented native compiler for this Linux host, runs it on the
|
||||
* training builds below, and merges the raw profiles it writes into one
|
||||
* sparse indexed profile. The training builds are tsc-ts (a cold `speed`
|
||||
* build, then a rebuild after an edit), every seventh corpus program at the
|
||||
* default optimization and a further slice in `dev`, and the runtime
|
||||
* build, then a rebuild after an edit), every seventh top-level corpus
|
||||
* program (by number, or by a hash of the name of a program without one) at
|
||||
* the default optimization and a further slice in `dev`, and the runtime
|
||||
* benchmark workloads. Run it on a quiet Linux x64 host after changes that
|
||||
* move much compiler code; a stale profile only loses part of its gain. A
|
||||
* refresh takes about 11 minutes on 16 cores.
|
||||
@@ -126,16 +127,30 @@ async function pool(tasks) {
|
||||
return passed;
|
||||
}
|
||||
|
||||
const programNumber = (name) => Number.parseInt(name, 10);
|
||||
/** A top-level corpus program's sampling key: its number, or, for a program
|
||||
* named without one, a stable hash of its name (FNV-1a). */
|
||||
const programKey = (name) =>
|
||||
/^\d+-/.test(name)
|
||||
? Number.parseInt(name, 10)
|
||||
: [...name].reduce((h, c) => Math.imul(h ^ c.charCodeAt(0), 16777619) >>> 0, 2166136261);
|
||||
/** Numbered programs by number, then the named ones by name. */
|
||||
const programOrder = (a, b) =>
|
||||
/^\d+-/.test(a) !== /^\d+-/.test(b)
|
||||
? /^\d+-/.test(a)
|
||||
? -1
|
||||
: 1
|
||||
: programKey(a) - programKey(b) || (a < b ? -1 : a > b ? 1 : 0);
|
||||
const corpus = readdirSync(join(root, "tests/corpus"))
|
||||
.filter((name) => /^\d+-.*\.ts$/.test(name))
|
||||
.sort((a, b) => programNumber(a) - programNumber(b));
|
||||
.filter(
|
||||
(name) => /^[\w-]+\.ts$/.test(name) && statSync(join(root, "tests/corpus", name)).isFile(),
|
||||
)
|
||||
.sort(programOrder);
|
||||
const training = [
|
||||
...corpus
|
||||
.filter((name) => programNumber(name) % 7 === 0)
|
||||
.filter((name) => programKey(name) % 7 === 0)
|
||||
.map((name) => ({ file: join(root, "tests/corpus", name), optimization: "release" })),
|
||||
...corpus
|
||||
.filter((name) => programNumber(name) % 14 === 7)
|
||||
.filter((name) => programKey(name) % 14 === 7)
|
||||
.map((name) => ({ file: join(root, "tests/corpus", name), optimization: "dev" })),
|
||||
...readdirSync(join(root, "benchmarks/runtime/workloads"))
|
||||
.filter((name) => name.endsWith(".ts"))
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
// util.format, util.formatWithOptions and console output with format
|
||||
// strings known only at run time, spread arguments and options objects:
|
||||
// every substitution, the argument cursor, %% and %c, missing and extra
|
||||
// arguments, the space-joined form of strings without a '%', and Node's
|
||||
// inspectOptions validation.
|
||||
import * as util from "node:util";
|
||||
|
||||
class Point {
|
||||
x: number;
|
||||
y: number;
|
||||
constructor(x: number, y: number) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
interface Row {
|
||||
id: number;
|
||||
tags: string[];
|
||||
nested: { deep: { deeper: { deepest: number } } };
|
||||
}
|
||||
|
||||
const formats: string[] = [
|
||||
"%s and %s",
|
||||
"%d|%i|%f",
|
||||
"%j",
|
||||
"%O / %o",
|
||||
"100%% of %s",
|
||||
"%c%s%c",
|
||||
"no specifiers",
|
||||
"trailing %",
|
||||
"%x %s",
|
||||
"%s",
|
||||
];
|
||||
const row: Row = { id: 7, tags: ["a", "b"], nested: { deep: { deeper: { deepest: 1 } } } };
|
||||
|
||||
for (const format of formats) {
|
||||
console.log(util.format(format, "first", 42, row));
|
||||
console.log(util.format(format, 3.5, "12.75"));
|
||||
console.log(util.format(format));
|
||||
}
|
||||
|
||||
function pick(i: number): string {
|
||||
return ["%s=%d", "%j", "%O", "plain"][i % 4]!;
|
||||
}
|
||||
for (let i = 0; i < 4; i++) {
|
||||
console.log(util.format(pick(i), new Point(1, 2), -0, [1, 2, 3]));
|
||||
console.log(pick(i), new Map([["k", new Point(3, 4)]]), new Set([1n, 2n]));
|
||||
console.error(pick(i), "to stderr", null, undefined);
|
||||
}
|
||||
|
||||
const label: string = process.argv.length > 99 ? "" : "count: %d of %s";
|
||||
console.log(label, 5, "five", "extra");
|
||||
console.log(util.format(label, 10n, Symbol("sym")));
|
||||
console.log(util.format(label, new Point(0, 0), [new Point(5, 6)]));
|
||||
console.log(util.format(pick(0), { toString: "not a function" }, true));
|
||||
|
||||
// Arguments that are all strings, numbers, booleans, null or undefined.
|
||||
const prim: string = process.argv.length > 99 ? "" : "%s|%d|%i|%f|%j|%O|%o|%c|%%";
|
||||
console.log(prim, -0, "12.5", -0, true, 'q"uote', "s", 3, "css", "rest", null, undefined);
|
||||
console.log(prim, NaN, null, undefined, "0x10", Infinity, NaN, false);
|
||||
console.log(util.format(prim, "a\nb", " 7 ", "-3.9e2", "", -Infinity, 'it\'s', 1e21));
|
||||
console.log(util.format(label, 2.5));
|
||||
|
||||
// A union first argument formats only when it holds a string.
|
||||
const maybe: string | number = process.argv.length > 99 ? 1 : "%s/%s";
|
||||
const notString: string | number = process.argv.length > 99 ? "%s" : 9;
|
||||
console.log(util.format(maybe, "u", "v"));
|
||||
console.log(util.format(notString, "u", "v"));
|
||||
console.log(maybe, "w", "x");
|
||||
|
||||
// An unknown first argument.
|
||||
const loose: unknown = process.argv.length > 99 ? 0 : "%s-%i";
|
||||
console.log(util.format(loose, "a", "7.9"));
|
||||
console.log(loose, "b", 3.2);
|
||||
|
||||
// Spread arguments.
|
||||
const values: unknown[] = ["%s + %s = %d", 1, 2, 3, "tail"];
|
||||
console.log(util.format(...values));
|
||||
console.log(util.format("%s|%s", ...values));
|
||||
console.log(...values);
|
||||
console.log("prefix", ...new Set(["x", "y"]), "suffix");
|
||||
console.error(...[1, "two", [3]]);
|
||||
console.log(util.format(...([] as string[])));
|
||||
|
||||
// formatWithOptions with an options object.
|
||||
const deep = { a: { b: { c: { d: { e: 1 } } } } };
|
||||
const depth = process.argv.length > 99 ? 1 : 0;
|
||||
console.log(util.formatWithOptions({ depth }, "%O", deep));
|
||||
console.log(util.formatWithOptions({ depth: null }, deep));
|
||||
console.log(util.formatWithOptions({ depth: undefined }, "x", deep));
|
||||
console.log(util.formatWithOptions({ depth: 0 }, "%s|%O", deep, deep));
|
||||
console.log(util.formatWithOptions({ compact: false }, "%s", "v", { a: 1, b: [1, 2] }));
|
||||
console.log(util.formatWithOptions({ breakLength: 20 }, { alpha: 1, beta: 2, gamma: 3 }));
|
||||
console.log(util.formatWithOptions({ sorted: true }, "%O", { b: 1, a: 2, c: { z: 1, y: 2 } }));
|
||||
console.log(util.formatWithOptions({ colors: true }, "%s %d %O", "s", 2, 3), "|");
|
||||
console.log(util.formatWithOptions({ colors: true }, 1, "plain", null, undefined, true, 4n));
|
||||
const options: util.InspectOptions = { depth: 1, compact: 1 };
|
||||
console.log(util.formatWithOptions(options, "%O", deep));
|
||||
console.log(util.formatWithOptions(options, ...values));
|
||||
console.log(util.formatWithOptions([] as unknown as util.InspectOptions, "%s", 1));
|
||||
const invalid: unknown[] = [null, 1, "x", undefined, () => {}];
|
||||
for (const bad of invalid) {
|
||||
try {
|
||||
util.formatWithOptions(bad as util.InspectOptions, "a");
|
||||
} catch (error) {
|
||||
const e = error as NodeJS.ErrnoException;
|
||||
console.log(e.name, e.code, e.message);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user