Commit Graph
3652 Commits
Author SHA1 Message Date
Chris Tate 30cf205e90 Run untyped pipeline function stages and later iterable stages
A pipeline stage that arrives as an untyped value now follows Node's
stage ladder for more shapes. As the first stage, a function is a source
function: the pipeline calls it with { signal } while it is set up and
pumps its result, which must be iterable (ERR_INVALID_RETURN_VALUE
otherwise), and a { readable, writable } pair pumps its readable side.

After the first stage, the pipeline call is lowered once per combination
of its first three untyped stages being functions or not, and a typeof
test picks the branch at run time. A function stage is called with the
previous stage and { signal }; before the last stage its result must be
async iterable, and as the last stage a promise resolves the pipeline's
value and an async iterable feeds the PassThrough pipeline() returns
(SCR_PIPE_DEST_DYN). Any other value that is not a stream becomes
Duplex.from of it, which the next stage reads from without the previous
stage being piped into it, and Duplex.from's ERR_INVALID_ARG_TYPE for
the values it rejects.

An untyped stage followed by an untyped function stage hands its value to
that stage through a cell created per pipeline call, so the function
receives the previous stage's own iterator, as Node's makeAsyncIterable
answers it, instead of an async generator over it.

Untyped web streams, pairs after the first stage, and function stages
that return a stream throw the not-supported error.

RUNTIME_ABI_VERSION goes from 45 to 46 for the new stream exports.
2026-10-25 18:10:32 -05:00
Chris Tate 56099ae21c Erase lambdas that implement a generic method through their context (#2274) 2026-10-25 18:07:47 -05:00
Chris Tate 27c00f6577 util: runtime format strings, inspect.custom and colors, untyped promisify (#1911) 2026-10-25 17:46:31 -05:00
Chris Tate b63dc67f9e Let boxable function arms ride a checked-dynamic union (#2269) 2026-10-25 17:44:23 -05:00
Chris Tate fad9cbdf57 Keep U+11A7 after a Hangul LV syllable in NFC and NFKC (#2266) 2026-10-25 17:31:45 -05:00
Chris Tate 8be991aeca Skip worker cancellation polls in short loops inside another loop (#2261) 2026-10-25 17:14:42 -05:00
Chris Tate 67c38b5361 Keep the whole union of an optional parameter that reaches a recursive placeholder (#2241) 2026-10-25 17:10:27 -05:00
Chris Tate f1a69d9bb0 Probe hashed number-key maps without normalizing integer keys (#2264) 2026-10-25 17:03:41 -05:00
Malte UblandClaude Opus 5.5 3375304945 Erase lambdas that implement a generic method through their context
A lambda with no type parameters of its own, contextually typed by a
generic signature (`send: (m) => …` for an interface's `send<T>(m:
Message<T>): string`), takes that signature's type parameters: its
parameter is `Message<T>`. A generic lambda written with its own type
parameters already lowers once, erased, with them bound to `unknown`, but
this one tried to map `Message<T>` with `T` unbound and was refused
(SC2009).

It now lowers the same way, erased, binding the contextual signature's
type parameters to `unknown`. Parameters bound in the enclosing scope
keep their bindings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 15:02:24 -07:00
Malte UblandClaude Opus 5.5 605e312e78 Let boxable function arms ride a checked-dynamic union
A union with a checked-dynamic arm (`unknown`, fetch's AbortSignal)
maps to the checked-dynamic value when every other arm is one the
checked-dynamic tree holds faithfully. Function arms were excluded, so
`Progress | AbortSignal | undefined` was refused (SC2009).

The tree boxes a function whose parameters check in and whose result
converts out by reference (canBoxFuncIntoDyn), and `typeof x ===
"function"` narrows it back out to the typed function. Such arms now
join the collapse. The corpus program checks calls, identity (`===`
against the original), and the other arm's members against Node.

pi's SessionManager.listAll takes `onProgressOrSignal?: SessionListProgress
| AbortSignal`. The refusal rejected the whole SessionManager class.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 15:02:19 -07:00
Chris Tate 5f76d41a71 Keep U+11A7 after a Hangul LV syllable in NFC and NFKC
libunicode composes an LV syllable with U+11A7 as if it were a trailing
consonant (U+11A8..U+11C2), which drops it: "가ᆧ".normalize()
gave "가" where Node gives "가ᆧ". 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. The guard sits in front of the vendored library (scr_unicode_normalize),
for String.prototype.normalize and the NFC step of IDNA mapping.

8009-normalize-hangul-jungseong-o-yae prints the code points of all four
forms for U+11A7 after LV and LVT syllables, conjoining jamo, other
starters and combining marks.
2026-10-25 16:57:11 -05:00
Chris Tate 8d485dcf5f Convert promise and function values into a union's arm of their kind (#2257) 2026-10-25 16:56:50 -05:00
Chris Tate 60c89fd456 Keep record values that subtype reduction folds into an inferred return type (#2069) 2026-10-25 16:50:31 -05:00
Chris Tate 3a900ed9b6 Keep the poll in nested loops whose counter can stop changing at 2^53
The range analysis caps a counter that steps by 1 at 2^53, where
`j + 1 === j`, so a loop such as `for (let j = 2 ** 53 - 2;
j < 2 ** 53 + 10; j++)` has a range of three values at its condition and
was taken for a loop of three runs. It never ends, and without its poll
worker.terminate() could not stop it. A loop's runs now count as bounded
only when a step from every value in its counter's range gives a
different value.
2026-10-25 16:38:27 -05:00
Chris Tate 35161d1883 Cross package values at their declared type where the JS literal type lacks members (#2262) 2026-10-25 16:38:00 -05:00
Chris Tate e3f02c73c9 Probe hashed number-key maps without normalizing integer keys
A number-key Map or Set read whose key has an integer view
(sc_map_entry_f64_int) fell back to the same hashed probe as any double
key, which tests for NaN and -0 before hashing. The fallback is now
sc_map_entry_f64_hashed_int, which takes the integer and converts it to
the key word directly: an integer is never NaN, and zero converts to +0.

Both hashed probes test the absent-key filter only before scanning a map
that keeps no bucket table. The filter keeps two of its 64 bits per key,
so it rejects few absent keys once a map has a bucket table, and the
probe's first bucket read answers those.

The map layout and the runtime lookup are unchanged. Corpus 8331 covers
integers the compiler tracks as integers on hashed maps and absent keys on
small bucket tables, and matches Node's output.
2026-10-25 16:32:27 -05:00
Chris Tate 474edacb4f Prefer the union arm that keeps every field of a converted record (#2259) 2026-10-25 16:26:24 -05:00
Chris Tate a9ea71df7d Compare unions that hold promises as untyped values
A promise converted into a union's promise arm is a new promise, so two
optional slots set to one promise each hold their own conversion, and a
value narrowed out of such a slot may be one too. Union equality compared
those arm payloads by pointer and answered false where Node answers true.

Any comparison between a union with a promise arm and a promise or another
such union now compares both sides as untyped values, where a converted
promise answers with its origin's identity.
2026-10-25 16:22:25 -05:00
Chris Tate d4a677525d Build the sanitized three.js nodes fixture in its own CI job (#2210) 2026-10-25 16:16:34 -05:00
Chris Tate ef14f0ae8a Merge main 2026-10-25 16:15:54 -05:00
Chris Tate 0ee74478fd Run paired row sums two columns per step (#2247) 2026-10-25 16:15:20 -05:00
Chris Tate a96fbcfb64 Refuse static member reads of MIMEType and MIMEParams in JavaScript
JavaScript sources hold node:util's MIMEType and MIMEParams as identity
tokens, so a read such as MIMEType.prototype or MIMEType.name answered
undefined where Node returns the class's member. Those reads now report
SC2020, as the TypeScript spelling does at compile time; in JavaScript
the refusal throws when the read runs.

The node24-mime-errors fixture reads MIMEType.prototype to take the
getters' descriptors, so its expected refusal is now SC2020.
2026-10-25 16:14:48 -05:00
Chris Tate 110396ee2d Replace a package's declaration files or supply its declarations with type patches (#2245) 2026-10-25 16:03:41 -05:00
Chris Tate 9cfeb2408f Count shorthand property reads of an initializer-less JavaScript let (#2252) 2026-10-25 15:54:08 -05:00
Chris Tate 11de08b931 Skip worker cancellation polls in short loops inside another loop
In a worker program, a loop inside another loop of the same function
needs less than its own budget, because the enclosing loop polls at
least once every 64 of its iterations:

- A loop whose counters' proven ranges bound its runs, and those of the
  loops in its body, to 64 x 64 emits no poll. This covers loops with
  constant bounds, such as mandelbrot's lane and iteration loops. The
  bound uses the counted and two-counter loop ranges, or the range of a
  counter that the update alone steps by a constant where the condition
  reads it. Loops whose counted form needs a run-time guard don't count.
- An innermost counted loop is strip-mined like a counted loop over
  typed arrays, whatever its body. A run of at most 64 iterations never
  polls, and the budget's countdown leaves the body. A run of at most
  16384 iterations is one block and polls once as it leaves. A longer
  run polls between blocks of 64, but not after its last block.

Between two polls there are then at most 64 iterations of the enclosing
loop, and in each of them at most 16384 unpolled iterations of each
strip-mined inner loop and 64 x 64 runs of each bounded nest. A loop
that no other loop of its function encloses polls as before.

New corpus program: native-worker-terminate-bounded-loops.ts.
2026-10-25 15:46:27 -05:00
Chris Tate 94446a086a Answer Object.prototype's methods on typed records (#2244) 2026-10-25 15:43:30 -05:00
Chris Tate 224e040aac Map generic alias casts inside generic bodies with the instance's arguments (#2227) 2026-10-25 15:32:14 -05:00
Chris Tate 677c550fdb Merge main 2026-10-25 15:28:35 -05:00
Chris Tate 20574b1932 Merge main 2026-10-25 15:18:27 -05:00
Chris Tate 8d5c85448f Close recursive record knots through erased generic method members (#2246) 2026-10-25 15:07:46 -05:00
Chris Tate f791c1d006 Address review feedback 2026-10-25 14:47:24 -05:00
Chris Tate 6b51aa883f Keep -0 row and column starts out of the paired row-sum nest
The paired nest reads its bounds masked to [0, 2^25 - 1], which turns a
-0 start into +0, and a term can tell the two apart (1 / i on the first
row). The bounds test now requires 1 / b > 0, which -0 fails, so such a
nest runs as written. Corpus 8355 adds row and column starts of -0 with
terms that see the sign.
2026-10-25 14:46:01 -05:00
Chris Tate 85df48f28f Report either decryption error of a wrong passphrase in corpus 8183 (#2243) 2026-10-25 14:44:25 -05:00
Chris Tate 68942a8a1c Convert Buffers to and from latin1 and hex text a word at a time (#2183) 2026-10-25 14:38:27 -05:00
Chris Tate 7d458303a6 Check type patch files again on a cached configuration, and supply .jsx entries
- The project configuration records the local files a type patch names,
  with whether each existed. A configuration read again from an unchanged
  package.json is reused only while every answer still holds, so creating
  or deleting a local declaration file between builds in one process
  validates again.
- A types patch supplies the declaration TypeScript probes for a .jsx
  JavaScript entry (x.d.ts for x.jsx), as it does for .js, .mjs and .cjs.
- Remove the two tests/fixtures/effect4/node_modules links the previous
  commit added; that directory is ignored and pnpm creates them.
2026-10-25 14:32:00 -05:00
Chris Tate 94d3b691ea Fix wrong npm declarations locally with type patches (#2238) 2026-10-25 14:31:10 -05:00
Chris Tate be0e13aa1e Run paired row sums two columns per step
A paired row-sum nest now also takes two columns per step: each row adds
the first column's term and then the second's, and an odd column count
runs its last column on its own, so every sum keeps its order of
additions. The nest runs paired when all four bounds are integers in
[0, 2^25 - 1]; it reads them masked to that range, which proves both
counters' ranges, so its counted loops need no run-time guard or second
copy of the body.

Corpus 8355 covers the column counts and bounds, and corpus 8356 covers
worker loops over shared views that run past a view's end or write
overlapping views of one buffer.
2026-10-25 14:28:23 -05:00
Chris Tate d477cd45cd Run global substitutions with a literal replacement through build-time replacers (#2240) 2026-10-25 14:19:37 -05:00
Chris Tate a5663bdd73 Iterate strings over their bytes (#2233) 2026-10-25 14:14:06 -05:00
Chris Tate f82f71e6c5 Iterate and read native arrays from untyped code without type-name scans (#2239) 2026-10-25 14:10:07 -05:00
Malte UblandClaude Opus 5.5 f4825e05a7 Replace a package's declaration files or supply its declarations with type patches
A scriptc.typePatches entry can now replace a declaration file of the
package (or of its @types twin, written @types/<name>/<path>) with a
local .d.ts ("files"), or give the package a declaration entry ("types").
A supplied entry answers the package's types file and the declarations
TypeScript probes beside its JavaScript entries, and exists for the
program host, the resolver and npm-static's untyped-package test, so a
package without declarations becomes a typed package. Declaration patches
still apply on top. A files target the installed copy does not ship is
SC8031.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 12:05:52 -07:00
Malte UblandClaude Opus 5.5 a2be7d8bc0 Answer Object.prototype's methods on typed records
Records inherit toString, toLocaleString, valueOf, hasOwnProperty,
isPrototypeOf and propertyIsEnumerable. Calls of these methods by name
run an own callable entry when the record's type allows one and the
record holds it, and otherwise the built-in with the record as receiver;
toLocaleString, valueOf and isPrototypeOf on records were refused, and
toString ignored an own entry. Keyed reads of records of functions answer
the built-in as a native function value when the key misses: bound to the
record when called at once, with an undefined `this` otherwise. The
backend fuses the read with its fallback, so hits cost nothing extra.
Records of other value types keep reading undefined for these names; the
limitations page says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 12:05:14 -07:00
Chris Tate 6000485a17 Report either decryption error of a wrong passphrase in corpus 8183
With a fresh key and IV each run, Node's OpenSSL now and then accepts
the padding of a wrong-passphrase decryption and rejects the decrypted
structure instead: ERR_OSSL_UNSUPPORTED rather than ERR_OSSL_BAD_DECRYPT,
in about 1 run in 100. The program now prints both as one decryption
error.
2026-10-25 14:04:10 -05:00
Chris Tate cab0968510 Keep holes and values written through unknown[] views in typed arrays (#1866) 2026-10-25 14:01:03 -05:00
Chris Tate a40e8f9b38 Read fixed encrypted keys in corpus 8167's wrong-passphrase cases (#2237) 2026-10-25 13:55:13 -05:00
Malte UblandClaude Opus 5.5 a4bc01bafb Count shorthand property reads of an initializer-less JavaScript let
An initializer-less JavaScript `let` whose writes all agree gets a scalar
slot when every read already has that flow type (inferredEvolvingLetType).
The read scan resolved each identifier with getSymbolAtLocation, which
answers the property symbol for a shorthand property name, so the
`width` in `return { width }` was not seen as a read. A function that
assigns the let on one path only (`let width; if (tty) width = 80;
return { width };`) then stored it in a number slot: the path that never
assigns returned 80 or 0 instead of undefined. The capture scan missed a
closure that reads the let through a shorthand the same way.

Both scans now resolve a shorthand name to the binding it reads, so such
a let keeps its checked-dynamic slot and reads undefined until assigned,
as in Node. TypeScript lets are unaffected (the inference applies to
JavaScript only).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 11:52:19 -07:00
Chris Tate e2bf699e81 Merge main 2026-10-25 13:51:46 -05:00
Malte UblandClaude Opus 5.5 177b2fde49 Cross package values at their declared type where the JS literal type lacks members
An opted-in package's JavaScript types its own values. The lowering types
a consumer's view of such a value from that JavaScript, and the
declaration boundary (decl-projection.ts) casts to the declared type only
where the JavaScript world answers `any`. A function that returns an
object literal has a precise JavaScript type, so it never crossed, even
where that literal type lacks members the declarations promise:

- `{ [key: string]: string }` over `{ size: "12px" }`: the consumer read
  `settings["font"] ?? "x"` through the literal's fixed key set. The
  checker types the miss `any` (a JavaScript literal), but the keyed read
  took the declared fields' common type, so the miss trapped with
  "record has no key" and aborted the process.
- an expando `out.max = ...` on a non-empty literal, which the JavaScript
  checker leaves out of the literal's type: reading `stats(xs).max` was
  refused with SC1090.

A package value whose JavaScript type is an object literal that lacks a
declared property or string index signature now crosses at its declared
type, so missing keys read undefined through the index signature and
expando members are read from the object. An index signature whose values
do not project (a package interface) keeps the value untyped (`as any`),
so its reads are the JavaScript reads. Only programs compiled with
--npm-static whose consumers read such values are rewritten.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 11:51:24 -07:00
Chris Tate dec6be6ebd Run global substitutions with a literal replacement through build-time replacers
A global replace or replaceAll of a regex literal called the compiled
matcher once per match: each call set up its frame and seek again, and the
runtime then copied the gap and the replacement. A substitution of a
literal inside the compiled subset with an ASCII replacement without $
patterns now gets a replacer built for that pattern and replacement. It
appends each gap and the replacement to the output and continues the
search after the match, as the runtime's loop does (one past an empty
match), without returning. Gaps of up to 16 bytes are one 16-byte copy,
and the search after a match is a 16-byte scan instead of a memchr call.

scr_regex_replace_with runs the replacer on an ASCII subject; a subject
outside ASCII keeps the runtime's loop. Plain-text patterns, which the
runtime searches as strings, get no replacer.

New corpus program: 8351-regex-literal-replacers.ts.
2026-10-25 13:35:04 -05:00
Chris Tate 0664361990 Decode string storage within its length in Buffer encodings (#2232) 2026-10-25 13:30:42 -05:00