Commit Graph
443 Commits
Author SHA1 Message Date
Malte UblandClaude Opus 5.5 1c19d6613d Compile the deprecated punycode and constants builtin modules
- node:punycode: encode, decode, toASCII, toUnicode, ucs2.decode,
  ucs2.encode and version, ported from the punycode.js 2.1.0 Node bundles
  (scr_url.c), with its RangeErrors. DEP0040 follows Node: a static ES
  import reports before any module evaluates and before the program can
  register a 'warning' listener; require() reports on the next tick, once
  for both spellings, not when a dependency under node_modules loads the
  module first, and not under process.noDeprecation.
- node:constants: the frozen object Node builds from os.constants'
  dlopen, errno, priority and signals groups, fs.constants (node_constants.cc
  order and guards) and crypto.constants (scr_os.c); its bindings read it as
  a checked-dynamic value.

These unblock tr46 (whatwg-url, node-fetch@2) and graceful-fs (fs-extra).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 06:23:56 -07:00
Malte UblandClaude Opus 5.5 e79380ce17 Name the runtime package in npm refusals and add transitive --npm-static
- A package typed only by an @types twin was judged under the twin's name
  and refused with "its import resolves to '@types/x/index.d.ts', not to
  runtime JavaScript". Selection now resolves the runtime package, and the
  refusal names it with the --npm-static option that compiles it anyway.
- Refusals the declaration and shipped-JS heuristics make carry the same
  remedy as a diagnostic hint; a dependency of a named package suggests
  the transitive form.
- `--npm-static=pkg...` (and `"pkg..."` in a library profile) selects the
  package and every package its files import, nested require() calls
  included, without the eligibility heuristics.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:39:21 -07:00
Malte UblandClaude Opus 5.5 d1639298ff Erase only import type edges, following Node's type stripping
Node runs TypeScript with verbatimModuleSyntax semantics: `import type` and
`export type … from` vanish, while `import { type A }` and
`export { type A } from` keep an empty clause and still load their module.

- Package selection skipped nothing: `import type { X } from "type-fest"`
  selected type-fest and failed with SC2013, and type imports pulled whole
  packages into the compile. Erased edges no longer select packages.
- Files reached only through erased edges are no longer program modules,
  so their imports (a `node:vm` import, say) no longer fence the build.
- Inline type-only clauses are real module edges again (their modules'
  side effects ran under Node but not in the binary); a cycle closed by
  one binds nothing and is admitted like a side-effect import.
- Interfaces, type aliases and type literals from an installed package's
  declarations map structurally, so values the program builds with a
  type-only package's types compile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:39:16 -07:00
Chris Tate dfd19cbea6 Give socket 'data' listeners strings after setEncoding
After socket.setEncoding("utf8"), Node passes strings to 'data'
listeners, while @types/node types the chunk as a Buffer. An inline
listener whose chunk is typed Buffer (annotated or inferred) received a
Buffer. When a program can call setEncoding on a socket, such listeners
now take the chunk as a checked dynamic value, so console.log, typeof,
string methods and concatenation behave as in Node, and Buffer-only
methods throw Node's TypeError. Programs without setEncoding on a socket
compile to the same IR as before.
2026-10-17 20:11:58 -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 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 a3b2c24b23 Document the wasm32-wasi runtime pack and fix the SC3003 hint (#1223) 2026-10-17 17:13:16 -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 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 6470c870ef Store unknown and object class fields in checked-dynamic storage
TypeScript class fields declared `unknown` or `object`, also with `null`
or `undefined` or as arrays of those, were refused with SC1090. They now
use the checked-dynamic storage that JavaScript class fields without a
declared type already use. This covers instance fields, static fields and
parameter properties. Fields declared `any` stay refused.

Fixes #1147
Co-authored-by: Alan Leal <33952+alantriesagain@users.noreply.github.com>
2026-10-17 15:47:37 -05:00
Chris Tate 4018a1d957 Compile async functions that suspend only at their awaits to stackless coroutines (#1204) 2026-10-17 15:38:36 -05:00
Chris Tate 0b7e777689 Support net.SocketAddress and net.BlockList (#1200) 2026-10-17 14:56:25 -05:00
Chris Tate 0db6d27c7c Support net.SocketAddress and net.BlockList
net.SocketAddress and net.BlockList were refused at compile time. They
are now native checked-dynamic handles in scr_net.c, so their getters
and methods also work on untyped values, and they match Node 24:

- new SocketAddress(options) checks the options in Node's order
  (ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_VALUE for the family,
  ERR_SOCKET_BAD_PORT, ERR_OUT_OF_RANGE for the flow label) and throws
  ERR_INVALID_ADDRESS for an address that does not parse. parse() reads
  URL.parse(`http://${input}`)'s hostname and port and answers undefined
  on failure; isSocketAddress(), toJSON() and util.inspect match.
- BlockList takes address, range, subnet and CIDR rules (addAddress,
  addAddresses, addRange, addSubnet, addCIDR, addCIDRs), removes them
  (removeAddress, removeRange, removeSubnet, removeCIDR, clear) and lists
  them in Node's order: IPv4 addresses, IPv6 addresses, subnets, then
  ranges, each newest first, with a repeated address kept once.
- check() compares across families through the IPv4-mapped IPv6 form,
  as Node's SocketAddress::compare and is_in_network do, and answers
  false for a string that does not parse in the given family.
- rules, size, toJSON(), fromJSON() (Node's rule patterns), isBlockList(),
  PRIVATE_RANGES, instanceof and util.inspect match, with the validation
  errors of lib/internal/blocklist.js.

Corpus 5659 and the node-types fixture net-blocklist.ts cover both
classes.

The compatibility registry lists the BlockList methods and
SocketAddress.parse as supported, and both classes as partial: their
structured clone and worker transfer forms are not modeled.
2026-10-17 14:32:06 -05:00
Malte UblandClaude Opus 5.5 24c54fc5d3 Add Int32[] element contracts
`Int32` from @scriptc/types now also annotates the element type of a
declared number array (`Int32[]`, `readonly Int32[]`, `Array<Int32>`,
`ReadonlyArray<Int32>`) on parameters, variables, class instance fields
and function results. The contract promises that every element stored
into the array, through any reference, is an int32 by construction.

contracts/int32-elements.ts checks it over a whole-program flow graph of
the places array references live (locals, parameters, globals, results,
class field families, record fields, container element slots), with one
node per allocation and per opaque origin. Allocations that can reach an
annotated slot are constrained and every element store into an array that
may be constrained must be range-proven an int32 (SC7002); element copies
constrain their sources by fixpoint; arrays from or escaping to code the
IR does not show refuse the contract (SC7003). Slots only constrained
arrays reach are recorded as IrContracts.int32Elements.

Element reads that find a value through those slots carry the int32
range, `getNumber` reads are integer-or-NaN operands of wrapping sums, and
ToInt32 of any integer-or-NaN operand is branch-free. Holes, missing
indices and length changes keep their JavaScript behavior.

Integer kernels over a 2^20-element array: `(s + a[i]) | 0` 1086 -> 175 ms,
`(Math.imul(h, 31) + a[i]) | 0` 602 -> 150 ms, prefix sums 575 -> 155 ms;
programs without the annotation compile to identical binaries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 12:08:06 -07:00
Malte UblandClaude Opus 5.5 74a9cfb8cc Compile async functions that suspend only at their awaits to stackless coroutines
Native async bodies whose only suspension points are their own awaits now
compile to LLVM switched coroutines, the lowering WASI async bodies and
native synchronous generators already use. A suspended call keeps a heap
frame of the values live across its awaits instead of a fiber with its own
stack. Bodies that reach other suspension points (async generator
consumption, dyn awaits, node:test subtests), module evaluation bodies,
dyn results, worker and library programs, and Windows keep their fibers.

The emitter selects bodies by emitting them as usual first: that text may
suspend only through await runtime calls and await-call entries, and may
call no function that parks. The coroutine form is then emitted and
checked again. Each await prepares its continuation in the runtime (a
pending promise's waiter list, or one READY-queue turn, continuing in place
exactly where a fiber's hop would) and extracts the settled result after
resuming. The eager prefix runs on the spawner's stack and moves to a fresh
stack near the stack guard, so deep async recursion keeps working.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 11:29:05 -07:00
Chris Tate 8620fa533a Keep record views of class instances only where a copy is observable (#1188) 2026-10-17 12:30:36 -05:00
Chris Tate 23056949da Keep readline's terminal cursor on character boundaries, and merge main 2026-10-17 11:06:19 -05:00
Chris Tate c5d5170e1a Keep record views of class instances only where a copy is observable
A structural type that receives class instances became a checked view
for all of its values, even when nothing could tell a copy from the
original. In tsc-ts this turned ResolvedProjectReference, the redirect
parameter of every module resolution, into a checked-dynamic value.

markIdentityFlows still finds every flow, then keeps a type marked only
when the program can observe the difference:

- values of the type are written through, compared by identity (also
  inside generic functions), used as Set or Map keys or array search
  needles, or passed to whole-object observers or any-value parameters;
- a field the type exposes is written on the source class after
  construction, or is an accessor;
- its values flow into another kept view or class-backed protocol.

Other types stay native records.
2026-10-17 10:34:06 -05:00
Chris Tate a904a552d7 Add the concurrency test option (#1180) 2026-10-17 09:37:02 -05:00
Chris Tate b429d4ca6f Merge pull request #1108 from vercel-labs/ctate/add-messagechannel-receivemessageonport-5d9f36
Add MessageChannel, receiveMessageOnPort and MessagePort transfer
2026-10-17 09:35:15 -05:00
Chris Tate 4b7ff49da8 Support createHash and createHmac values as streams (#1120) 2026-10-17 08:47:55 -05:00
Chris Tate 3fadf25b02 Merge main 2026-10-17 08:38:10 -05:00
Chris Tate d5034758fb Merge main 2026-10-17 03:33:06 -05:00
Chris Tate 53023c615d Merge main 2026-10-17 03:30:55 -05:00
Chris Tate b37c9491fe Fix or refuse Node 24 builtin forms that ran with wrong output
- JavaScript calls of Promise.withResolvers' resolve/reject through the
  record no longer fail with an internal compiler error.
- new RegExp with the d or v flag is refused (SC1120) for literal flags
  and throws a named error for computed flags, instead of SyntaxError.
- JavaScript .groups reads on matches whose regex is not traceable are
  refused like TypeScript's, instead of reading undefined.
- Iterator helpers on the string iterator are refused instead of
  failing at run time.
2026-10-17 03:29:53 -05:00
Chris Tate 05cb9bd609 Add the concurrency test option
node:test changes, matching Node 24.22:

- { concurrency } on describe, suite, test, it and t.test sets how many
  subtests run at once: a number, true for os.availableParallelism() - 1,
  or false for 1. Subtests inherit the parent's value.
- Above 1, each subtest runs on its own fiber. Subtests start in
  declaration order as slots free up, and t.test and test() calls inside a
  concurrent test start the subtest at once; t.test's promise settles when
  it finishes. The parent finishes after all of them.
- Results, diagnostics and failures are reported in declaration order.
- A concurrent subtest waits after its beforeEach hooks, and a concurrent
  suite before its children, for as many turns as Node's runner does, so
  sibling bodies interleave as they do there.
- A concurrency that is not an integer from 1 to 4294967295 throws Node's
  ERR_OUT_OF_RANGE RangeError when the test or suite is created.
- Nested test() calls and hooks inside a concurrent subtest attach to the
  subtest that runs on the calling fiber.

The concurrency fixture compares the reporter output, the order of the
bodies' side effects, and the range errors with Node, plain and with
SCRIPTC_SAN=1.
2026-10-17 03:28:15 -05:00
Chris Tate 0c4056fb0e Add the plan and timeout test options (#1165) 2026-10-17 03:22:28 -05:00
Chris Tate 26aeeb4c6f Lower util.promisify over the zlib callback one-shots (#1179) 2026-10-17 03:10:14 -05:00
Chris Tate 2896e94b20 Merge main 2026-10-17 02:59:21 -05:00
Chris Tate 7dcb1e6c4e Lower util.promisify over the zlib callback one-shots
promisify(zlib.deflate) and the other callback one-shots (inflate,
deflateRaw, inflateRaw, gzip, gunzip, unzip) over a named or namespace
import of node:zlib now lower. A call through the binding,
gzipAsync(data, options?), runs the callback form's work on the worker
pool and returns a promise that settles where the callback would run:
fulfilled with the Buffer, or rejected with the callback form's Error
(errno, code and message). An invalid option or input rejects the promise
instead of throwing, as the throw inside Node's promisify wrapper does.

The runtime adds scr_zlib_codec_promise and its checked-dynamic twin,
which share the callback forms' options ladder and worker-pool operation
and settle a pending promise in place of the callback. The fallback
declarations gain the matching util.promisify overload.
2026-10-17 02:46:20 -05:00
Chris Tate bc0636de7b Merge main 2026-10-17 02:35:31 -05:00
Chris Tate 0777ad7223 Merge main 2026-10-17 02:33:12 -05:00
Chris Tate 5c39821726 Merge main 2026-10-17 02:30:43 -05:00
Chris Tate 6fa1f8ce01 Lower util.promisify(stream.finished) and util.promisify(stream.pipeline) (#1177) 2026-10-17 02:19:12 -05:00
Chris Tate f61e5dc938 Add readline's terminal mode, cursor helpers and emitKeypressEvents 2026-10-17 02:10:27 -05:00
Chris Tate 3e83b4a5c7 Lower util.promisify(stream.finished) and util.promisify(stream.pipeline)
Node's util.promisify returns the custom promisified forms of
stream.finished and stream.pipeline, which are stream/promises' functions
of the same name. A const binding of promisify over the node:stream named
imports or namespace members now registers that stream/promises member
as the projection, so calls through the binding lower as the
stream/promises call and accept the stream arguments and options it
accepts.

The fallback declarations gain the matching util.promisify overloads.
pipeline's overload comes first: its (...streams: unknown[]) => unknown
type is assignable to every other target's parameter type.
2026-10-17 01:51:57 -05:00
Chris Tate f7ad1f96a4 Lower Array.fromAsync over arrays
The ESNext array lib is now part of the type world, and Array.fromAsync(source,
mapFn?) compiles when the source is an array of values or of promises. Each
call lowers to an async helper with the spec's loop: elements are awaited
through the async-from-sync iterator, with the same microtask turns as a
`for await` loop over an array, and a mapping function's result is awaited
before it is stored. A throwing or rejecting mapping function and a rejected
element close the iterator before the returned promise rejects, on the turn
Node rejects it. Async generator sources, a thisArg, and arrays that mix
values and promises are refused.
2026-10-17 01:47:40 -05:00
Chris Tate 6d4843ea99 Dispose Disposable-typed values and expose SuppressedError's error and suppressed
`using` and `await using` now accept object literals and other values typed
Disposable, AsyncDisposable or unknown. The declaration reads the value's
[Symbol.dispose] method ([Symbol.asyncDispose] first for `await using`) and
throws Node's TypeErrors for values that are not objects and for missing or
non-callable methods; null and undefined dispose nothing. Scope exit calls the
method with the value as `this`, and `await using` awaits the result of an
async method but not of the sync fallback. A class instance reached through
such a value throws an SC2020 error, because its dynamic view has no methods.

SuppressedError is its own error class: a failing disposal over a pending
error throws one whose `error` is the disposal error and whose `suppressed` is
the pending error, both non-enumerable as in Node. `new SuppressedError(error,
suppressed, message?)` and `instanceof SuppressedError` compile, and
`extends SuppressedError` is refused.
2026-10-16 22:36:18 -05:00
Chris Tate d7a5568726 Revert "Lower child_process.exec's callback forms" (#1161) 2026-10-16 22:05:36 -05:00
Chris Tate dfac9a950c Merge main 2026-10-16 21:33:56 -05:00
Chris Tate 53f22f7054 Merge main 2026-10-16 21:31:59 -05:00
Chris Tate db94b2d257 Add fs.cpSync, fs.cp and fs/promises cp
fs.cpSync, fs.cp and fs/promises cp compile with every option:
dereference, errorOnExist, filter, force, mode, preserveTimestamps,
recursive and verbatimSymlinks. They follow Node's two implementations.
cpSync follows cp-sync.js over the fs binding: copy errors are plain
Errors with a code, std::error_code failures throw an ErrnoException,
and links inside a copied directory point at their canonical targets.
fs.cp and fs/promises cp follow cp.js, whose copy errors are
SystemErrors with code, info, errno, syscall and path.

Options and paths validate in Node's order with Node's errors. filter
runs for the top-level paths and each entry in Node's order; an async
filter must return a promise that is already settled, and a pending one
throws SC2020. The callback and promise forms complete the copy before
the callback runs or the promise settles. Windows builds throw SC2020.

The copy lives in the new scr_fs_cp.c. Directory walks keep their frames
on the heap, so deep trees also copy on a fiber's stack. File contents
copy with sendfile on Linux, and COPYFILE_FICLONE uses FICLONE
where it exists. ScrError gains the SystemError context and the
ErrnoException key order, with matching fields in the compiler's Error
layout. The stored fs.cp callback uses the same implementation.

Corpus 5567-5571 cover the sync, callback and promise forms, errors,
symlinks and 1500-level trees.
2026-10-16 21:06:22 -05:00
Chris Tate d8c2b0f96d Lower child_process.exec's callback forms (#1161) 2026-10-16 20:57:36 -05:00
Chris Tate e2c34f381b Add the plan and timeout test options 2026-10-16 20:55:26 -05:00
Chris Tate 6f4334be00 Accept Map arguments in the Set composition methods (#1164) 2026-10-16 20:51:52 -05:00
Chris Tate 2ed22d38a4 Merge main 2026-10-16 20:32:43 -05:00
Chris Tate d53c1b8ba0 Merge main 2026-10-16 20:30:22 -05:00
Chris Tate 58842aa39b Accept Map arguments in the Set composition methods
union, intersection, difference, symmetricDifference, isSubsetOf,
isSupersetOf and isDisjointFrom now compile with a Map argument whose key type
is the Set's element type, as well as with a Set. A Map's set record reads its
keys, and its size, has and keys are the builtins, so the helper loops walk
and test the Map's keys in the order the spec gives. Other set-like arguments
are still refused.
2026-10-16 20:28:04 -05:00
Chris Tate 9681454556 Lower util.promisify(setTimeout) and util.promisify(setImmediate)
Node's util.promisify returns the custom promisified form of setTimeout
and setImmediate, which is timers/promises' function of the same name.
A const binding of promisify over the global functions, the node:timers
named imports or the node:timers namespace members now registers that
timers/promises member as the projection, so calls through the binding
lower as the timers/promises call: they fulfill with the given value
(undefined without one) after the delay or in the check phase.

The fallback declarations gain the matching util.promisify overloads.
2026-10-16 20:22:19 -05:00
Chris Tate cef618b553 Lower child_process.exec's callback forms
exec(command, callback) and exec(command, { encoding, maxBuffer },
callback) lower like execFile's callback slice. The command line runs
with /bin/sh -c, as execSync runs it, the call returns the live
ChildProcess, and the error-first callback receives the captured utf8
stdout and stderr. A non-zero exit, including the shell's 127 for a
missing command, reports Node's "Command failed: <command>\n<stderr>":
scr_exec_file takes a shell flag so the message shows the command line.

Under @types/node, child_process.ExecException maps to Error, as
ExecFileException does. Its code member is the child's numeric exit
status, which the runtime error's string code slot does not hold, so
reading it, testing it with `in`, or testing its typeof is a named
refusal.
2026-10-16 20:14:33 -05:00