Commit Graph
3704 Commits
Author SHA1 Message Date
Chris Tate 4060ea51d4 Expect generic protocol dispatch to compile
A class behind an interface whose method is generic now dispatches to
the method's erased instance, so the boundary test compiles the program
instead of expecting the old refusal.

Fixes #905
Co-authored-by: Konstantin Mikheev <8560582+konmik@users.noreply.github.com>
2026-10-17 19:11:33 -05:00
Chris Tate 2e7a37c0be Allow overrides that narrow a union return type
An override whose return type is part of the base method's union return
(`Meta` for `Meta | undefined`, `number` for `number | undefined`, or a
subclass of one of the union's class arms) was refused with SC1090,
although TypeScript accepts it. The override now returns through the
base's union ABI, so calls through the base type receive the value
unchanged, and calls resolved to the override narrow the result back to
its declared type.
2026-10-17 19:06:47 -05:00
Chris Tate aa0f0fb62e Compile JavaScript classes like TypeScript classes when their layout or JSDoc types allow it (#1232) 2026-10-17 19:04:37 -05:00
Chris Tate 8bb3fa1077 Call valueOf and Symbol.toPrimitive on values held as any
`value.valueOf()` on a dynamic receiver was refused at compile time, a
class's own toString was not reached by `value.toString()`, and classes
could not declare a `[Symbol.toPrimitive]` method.

- `value.valueOf()` lowers to dyn.valueOf, which answers each kind's own
  valueOf: a primitive itself, a Date its time value, an object with a
  valueOf member that member's result, and any other object itself. A
  null-prototype dictionary throws "<spelling> is not a function", and
  null and undefined throw Node's property-read TypeError.
- When a boxed class declares valueOf or toString, `value.valueOf()` and
  `value.toString(argument)` call the class's method for its instances
  and keep the runtime call for every other value.
- Classes can declare `[Symbol.toPrimitive](hint)`. The dynamic
  conversions call it with "default" for `+` and `==`, "number" for the
  numeric operators and conversions, and "string" for template literals,
  String() and property keys, and throw Node's TypeError when it returns
  an object.
2026-10-17 19:01:59 -05:00
Chris Tate 9fd5c8a73f Widen ASCII to UTF-16 sixteen bytes per step in Buffer.write
Buffer.write(text, "utf16le") zero-extended an ASCII run eight bytes per
step with shifts and masks in 64-bit words. Where the target has vector
unpacks (SSE2 on x86-64, NEON on AArch64) it now widens sixteen bytes
per step: one load, a test of the high bits, and two unpacked stores.
The word loop stays for the remaining bytes and for other targets. A
scanner that fills a Uint16Array of code units this way, as tsc-ts does
once per source file, spends about half as long in the copy.
2026-10-17 18:59:01 -05:00
Chris Tate 50d101f612 Record corpus costs for the worker programs and order 5805's reads
The worker series adds 16 corpus programs without measured costs in
tests/harness/corpus-costs.json, which dropped the measured share below
shard.test.ts's 0.9. Their plain and san costs are local timings of each
program's differential and llvm-differential cases, scaled by the median
ratio between the recorded and local timings of four programs timed in
the same run (001-hello, 1000-json-stringify-basics,
5022-lifetime-region-workers, 5164-worker-field-callback-receiver).

5805 read its second message right after the first arrived; under load
the parent's second postMessage could still be in flight, so the read
answered undefined. The worker now waits on a shared flag that the
parent sets after posting its first three messages. The output is
unchanged.
2026-10-17 18:45:15 -05:00
Chris Tate 42906482b6 Compile JavaScript classes like TypeScript classes when their layout or JSDoc types allow it
Fixes #934
Co-authored-by: Alan Leal <33952+alantriesagain@users.noreply.github.com>
2026-10-17 18:44:30 -05:00
Chris Tate 95b0e2132d Call generic members through receivers that are not statically known
Generic arrow properties and generic methods now have an erased runtime
value, their instance with every type parameter bound to unknown. Calls
through a round trip via any, a function result, an array element or a
class-backed interface call that value and convert the result to the
call's type, as JavaScript does. Direct calls on a known definition keep
their monomorphized path.

Fixes #905
Co-authored-by: Konstantin Mikheev <8560582+konmik@users.noreply.github.com>
2026-10-17 18:36:45 -05:00
Chris Tate 5e020e9ea6 Regenerate the surface manifest for the new util.types brand checks 2026-10-17 18:25:15 -05:00
Chris Tate 7c90c8e3a1 Pool keep-alive sockets in http Agents and use globalAgent for default requests 2026-10-17 18:23:08 -05:00
Chris Tate 7980eb4345 Match Node 24 in events and timers/promises (#1227) 2026-10-17 18:11:06 -05:00
Chris Tate 1ad47f1f77 Match Node 24 in util.format, util.types and util.inspect
- util.format %d and %i format bigint, symbol, null and undefined arguments
- util.format %j prints [Circular] for circular arguments
- util.types answers the remaining brand checks
- util.inspect prints WeakMap, WeakSet, Date and numeric typed array values as Node does
- util.inspect lowers the breakLength, compact and sorted options
2026-10-17 17:58:40 -05:00
Chris Tate 2ba6bfa9cd Merge main 2026-10-17 17:57:22 -05:00
Chris Tate 0580460c46 Match Node 24 in URLSearchParams, querystring, string_decoder and os
- URLSearchParams iterators compile outside a for-of head
- URLSearchParams can be built from [string, string] tuple pairs
- querystring.stringify writes bigint values
- StringDecoder accepts an encoding known only at run time
- os.setPriority is lowered
2026-10-17 17:54:52 -05:00
Chris Tate 96a03aec71 Call factory-defined static methods with their receiver class (#1226) 2026-10-17 17:50:47 -05:00
Chris Tate 6ae3eb5cc8 Match Node 24 in events and timers/promises
- The EventEmitter leak warning reaches process 'warning' listeners on the next tick
- The events listener-count and max-listener helpers work over EventEmitter instances
- timers/promises setTimeout, setImmediate and scheduler.wait accept an AbortSignal
- timers/promises scheduler.wait and scheduler.yield are lowered
2026-10-17 17:48:42 -05:00
Chris Tate a5675b2279 Call factory-defined static methods with their receiver class
A static method that a function-local JavaScript class defines at
runtime and a program class inherits (Animal.extend("Dog")) is now
called directly with this bound to the receiver class when it captures
nothing from the factory's scope, so the subclass it builds has a static
base and constructs like Node.

Fixes #938
Co-authored-by: phall <25888173+phall1@users.noreply.github.com>
2026-10-17 17:31:30 -05:00
Chris Tate 1783196f60 Resolve host names in net.connect (#1224) 2026-10-17 17:17:13 -05:00
Chris Tate a3b2c24b23 Document the wasm32-wasi runtime pack and fix the SC3003 hint (#1223) 2026-10-17 17:13:16 -05:00
Chris Tate a4a2472bd6 Lower Error.isError (#1221) 2026-10-17 17:01:01 -05:00
Chris Tate c9de7a351d Read .groups on matchAll rows passed to array callbacks (#1222) 2026-10-17 16:55:56 -05:00
Chris Tate 8bc797779a Group Sets, Maps and generators with Object.groupBy and Map.groupBy (#1220) 2026-10-17 16:49:35 -05:00
Malte UblandClaude Opus 5.5 714ef5aa09 Prove array lengths stable in index loops and version dense array loops
Index loops over arrays left the counter a double in several common
shapes, and every element access at the counter tested the array's
hole-free prefix on each pass:

- A syntactic proof (ir/array-length-stability.ts) shows that a loop never
  changes an array's length: the body runs no user code, never rebinds the
  array or lets it escape, and only resizes local arrays that no other
  binding can hold. Element writes at a counter kept below the length are
  accepted.
- Counted loops take a stable `a.length` as their limit, and an element
  access at the counter makes an unversioned counted loop worthwhile, so
  strided (`i += 2`) and descending (`i = a.length - 1; i >= 0`) loops get
  an integer counter.
- Array lengths bound while-loop and for-loop counters by their range,
  which needs no stability proof.
- A loop whose array length is stable and whose indexed arrays keep their
  storage runs as two copies. One test before the loop (the length is at
  most 2^31 - 1 and at most each indexed array's `filled` bound) selects a
  fast copy whose element accesses at the counter skip the prefix test and
  whose counter is an int32. LLVM can vectorize that copy.
- Loop counters whose ranges fit in 32 bits convert to double from their
  low half, so an inlined callee's index conversion folds away.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 14:48:38 -07:00
Chris Tate e02834f078 Use asserted key types in the method replacement census (#1218) 2026-10-17 16:43:49 -05:00
Chris Tate 55005ef73d Resolve host names in net.connect
net.connect and net.createConnection dialed only IP literals and
"localhost"; any other host name failed with getaddrinfo ENOTFOUND
without a lookup, so names served by /etc/hosts or a container's DNS
(such as Docker service names) did not connect. The dial now resolves
the name with getaddrinfo using Node's hints, orders the addresses as
Node's autoSelectFamily does, and tries them in order.

Fixes #1156
Co-authored-by: Quim T. <26749475+espetro@users.noreply.github.com>
2026-10-17 16:42:55 -05:00
Chris Tate 89a6991278 Expose Promise.prototype methods on promises in untyped bindings (#1219) 2026-10-17 16:41:39 -05:00
Chris Tate 7689d4b148 Merge main 2026-10-17 16:39:51 -05:00
Chris Tate 8fbc1d9b77 Document the wasm32-wasi runtime pack and fix the SC3003 hint
The WebAssembly and platform docs now say that wasm32-wasi builds need
zig and the @scriptc/runtime-wasm32-wasi package where the target is
introduced. A missing cross-target runtime pack still reports SC3003
with the package name and version; its hint now says that scriptc
includes the runtime pack for its own platform only, instead of
suggesting a reinstall with optional dependencies.

Fixes #717
Co-authored-by: guest271314 <4174848+guest271314@users.noreply.github.com>
2026-10-17 16:39:20 -05:00
Chris Tate 69fe16ac0f Keep the identity of checked-dynamic objects placed in JavaScript literals (#1207) 2026-10-17 16:37:43 -05:00
Chris Tate b7eb8255f9 Read .groups on matchAll rows passed to array callbacks
m.groups on a match row compiles when the row's regex is statically
known. The row can now also be the element parameter of an inline
callback over a matchAll drain (map, forEach, filter, find, findLast,
some, every, flatMap) or Array.from's mapping function over s.matchAll(re).
In JavaScript sources such a parameter holds the row as a checked-dynamic
value without its groups, and the read is refused instead of reading
undefined.
2026-10-17 16:35:27 -05:00
Chris Tate f1a4b364fb Report program type errors instead of npm package refusals (#1216) 2026-10-17 16:32:09 -05:00
Chris Tate adc2190bd6 Widen Result<T, never> and Result<never, E> unions (#1213) 2026-10-17 16:28:15 -05:00
Chris Tate 983058a3a7 Lower Error.isError
The ESNext error lib is now part of the type world, and Error.isError(value)
compiles. A catch binding tests its payload against the Error class, an
unknown value tests the checked-dynamic error encoding, and a union tests its
runtime tag against the arms whose class extends Error. Other values answer
statically: true for instances of Error and its subclasses, false for
everything else, including objects that only have an error's shape. The
argument is always evaluated. Unions with unknown arms are refused.
2026-10-17 16:26:42 -05:00
Chris Tate ed380babe9 Treat type-asserted values as present in the optional-value analysis (#1214) 2026-10-17 16:24:44 -05:00
Chris Tate 1f04c0b544 Group Sets, Maps and generators with Object.groupBy and Map.groupBy
Object.groupBy and Map.groupBy accepted only arrays. They now also take a
Set (its elements), a Map (its [key, value] entries) and a synchronous
generator. Sets and Maps are walked live, as forEach walks them, and a
generator is pulled one element per callback call and closed when the
callback throws. In JavaScript sources, unannotated callback parameters
take the element and its index.
2026-10-17 16:23:24 -05:00
Chris Tate 6ce668b213 Compile switch statements over object values (#1211) 2026-10-17 16:21:43 -05:00
Chris Tate a1d5d9e1b9 Expose Promise.prototype methods on promises in untyped bindings
Reading then, catch or finally from a promise held in a JavaScript or
checked-dynamic binding now answers the shared method values, so
typeof checks, thenable detection and "then" in p match Node. Calling a
method value uses its receiver; a receiver that is not a promise throws
Node's incompatible-receiver TypeError.

Fixes #978
Co-authored-by: Quim T. <26749475+espetro@users.noreply.github.com>
2026-10-17 16:18:56 -05:00
Chris Tate b91882d684 Use asserted key types in the method replacement census
An element write whose key is asserted to a number, such as
`slots[at as number] = x` or `slots[at!] = x`, was judged by the type of
the key before the assertion. A `number | undefined` key counted as a key
that can name any string, so calls of every method in the program went
through the checked property lookup. The census now reads the type of the
key with its assertions, so these writes only mark methods with numeric
names.

Fixes #1148
Co-authored-by: Alan Leal <33952+alantriesagain@users.noreply.github.com>
2026-10-17 16:15:37 -05:00
Chris Tate 4e979b2c7e Lower Promise.try with arguments for the callback (#1212) 2026-10-17 16:15:20 -05:00
Chris Tate 1f999c7cf0 Print BlockList address rules in a platform-independent order (#1210) 2026-10-17 16:12:37 -05:00
Chris Tate 07aece4362 Expect the typed-callbacks npm program to match Node
Its package puts a cyclic object (cycle.self = cycle) in an array literal
with other values. The literal used to convert that checked-dynamic
object into a recursive record type and crashed; it now builds as a
checked-dynamic array that holds the object itself, so the program runs
as under Node and is no longer a known divergence.
2026-10-17 16:11:51 -05:00
Chris Tate 6b5adcf6e5 Store unknown and object class fields in checked-dynamic storage (#1208) 2026-10-17 16:08:57 -05:00
Chris Tate b666b6b1b1 Lower filter, find, findIndex, some and every on tuples (#1209) 2026-10-17 16:08:26 -05:00
Chris Tate 2f2f26d0d5 Report program type errors instead of npm package refusals
When the program still fails to typecheck against a refused package's own
declarations, the type errors are the program's. Report them at their
locations instead of an SC2013 refusal for every imported package.

Fixes #1155
Co-authored-by: Quim T. <26749475+espetro@users.noreply.github.com>
2026-10-17 16:06:08 -05:00
Chris Tate d7623938c0 Keep declared methods of generic class instances after a computed-key write
When a write with a computed string key can replace any method, calls of
every method name read the receiver's property bag without checking for
the key, and the bag falls back to the class's prototype data. Generic
class instantiations, mixin classes and their subclasses have no prototype
data, so their methods read as undefined and calls threw "x.status is not
a function". Reads on these classes now check for an own property and
otherwise use the declared method.

Fixes #963
Co-authored-by: Quim T. <26749475+espetro@users.noreply.github.com>
2026-10-17 16:03:33 -05:00
Chris Tate e161dc4446 Treat type-asserted values as present in the optional-value analysis
`hot(xs[i] as number)` and `hot(stack.pop() as number)` counted as
possibly undefined, so `hot(n: number)` took a boxed `number | undefined`
parameter and callers boxed every number they passed. An assertion to a
type without undefined already checks the value and throws a TypeError
for undefined, so its result is now treated as present, like `x!`.

Fixes #985
Co-authored-by: Tim Kendall <1427378+timkendall@users.noreply.github.com>
2026-10-17 16:00:54 -05:00
Chris Tate d00ccbe89e Widen Result<T, never> and Result<never, E> unions
A union arm whose object type has a required property of type never has
no values. When a conversion's source checker type proves an arm
uninhabited, the re-tag traps on that arm instead of requiring a matching
destination layout, so Result<T, never> widens into Result<T, E>.

Fixes #1170
Co-authored-by: BaoLei <50226652+blurname@users.noreply.github.com>
2026-10-17 15:58:16 -05:00
Malte UblandClaude Opus 5.5 250097dd32 Represent unions of records as the record pointer itself
A union of two or more record arms and at most two unit arms (the
discriminated unions of object literals behind Result, token, shape and
AST types) now holds the record pointer instead of a reference-counted
%ScrUnion box, like nullable unions already do. NULL and the existing
null sentinel encode the unit arms.

Every record shape that is an arm of such a union carries its
module-wide shape number in a hidden i32 after the reference count; the
arm of a value is that word mapped through a per-union tag helper, which
LLVM folds into the consumer's switch. Records share one retain; release
and trace dispatch on the arm, and every arm of a union with a traced arm
carries a collector header. Wraps move the record, retypes between such
unions reuse the pointer, and equality is identity. Record field indices
come from the shape's field base.

Unions over records that are also arms of runtime-inspected unions (Map
keys, Set elements), library builds, lifetime-region programs and debug
builds keep boxes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 13:57:35 -07:00
Chris Tate cd977bf200 Lower Promise.try with arguments for the callback
Promise.try(f, ...args) evaluates the arguments at the call and passes
them to f, which still runs synchronously; its result, a returned
promise or a synchronous throw settle the returned promise as in the
one-argument form. A callback with more parameters than arguments, or
an arguments list with a spread, is refused.
2026-10-17 15:55:37 -05:00
Chris Tate 6f4b569cb6 Compile switch statements over object values
A switch whose discriminant is an object, array, record, function or
other reference value was refused. It now lowers like the union and
checked-dynamic switches: the discriminant is evaluated once and each
case expression, in source order and only until one matches, is compared
with it by the same strict-equality lowering as ===, which compares
references by identity. The === and !== lowering is now a function the
switch lowering shares.
2026-10-17 15:52:58 -05:00