Repository navigation
Compile process.send presence, process.exitCode reads, wider stream hooks, void race entries and promisify of callbacks - #1429
Merged
Merged
Conversation
…itCode reads, wider stream hooks, void race entries, RequestOptions and promisify of callbacks - `if (process.send)`, `Boolean(process.send)`, `!process.send` and `typeof process.send` answer whether the process has an IPC channel (a fork channel this binary opened, or NODE_CHANNEL_FD from a Node.js parent). The same holds for process.disconnect. - In a program without fork targets, `process.send?.(...)` answers undefined, and a direct call throws Node's TypeError when there is no channel. A Node.js parent's channel has no lowering, so a call fences when it runs. `process.send!(...)` lowers like the direct call. - process.exitCode reads answer the assigned code, or undefined. Only programs that read it record assignments, including process.exit(code) before the 'exit' listeners run. Other programs keep the untracked write. - A stream subclass hook may declare a supertype of Node's argument (`_write(chunk: Buffer | string, ...)`): the wrapper takes Node's Buffer and widens it. - Promise.race accepts a Promise<void> entry when the result holds undefined. - http.RequestOptions and https.RequestOptions as declared types ride checked-dynamic storage, like SpawnOptions. - util.promisify over a typed callback-last function value (a bound method, a declaration, an arrow) builds a native promisified function. A truthy error rejects, the value fulfills, and a synchronous throw rejects. - The bundled Node declarations gain the callback-only generic promisify overloads. Before this, `promisify(obj.close.bind(obj))` matched crypto.randomFill's overload and reported "Expected 1-3 arguments". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ules A .ts or .tsx file with import syntax is an ES module under Node's strip-types rule, which has no __dirname or __filename. TypeScript projects are commonly shipped as CommonJS bundles where both exist, and @types/node declares them for every file. scriptc now compiles them in such files to the module's source path, the same compile-time constants as in CommonJS and the same strings as import.meta.dirname and import.meta.filename. JavaScript ES modules keep the fence. The limitations page documents the difference. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A class that extends Writable, Readable, Duplex or Transform from node:stream compiled only against the bundled Node declarations. With @types/node in the project, which nests the classes in the module's `internal` namespace, the base was refused as "not declared in the program". The recognition now accepts either declaration of the stream module's classes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Resolve the conflicts with main's process.features value, the WebCrypto dynamic types, the NODE_COMPAT_VERSION import and the symbol.declarations API. Renumber this branch's corpus programs to 6420-6429, write the new limitations partial one sentence per line, and regenerate the compatibility outputs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main split lower-exprs.ts into expression families; the typeof and __dirname changes move to expressions/typeof.ts and expressions/identifiers.ts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`util.promisify(client.close.bind(client))` over `close(callback?: (error?: Error) => void)` was refused because the callback parameter is `callback | undefined`. The promisified function now takes the union's function arm and passes the callback into the union. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The read used to defer a refusal, which kept the corpus program from
compiling statically. It now throws Node's catchable ReferenceError
("__dirname is not defined"), and typeof answers "undefined" as in
Node.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The promisified executor typed its call of the function as void, so a function with a non-void return type failed to compile with SC9001. The call now has the function's return type, and its value is discarded, as in Node.
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A set of small Node surface gaps that commonly sit on a service's startup path. Each one was a compile refusal; all now compile, and the forms that still cannot work keep a named diagnostic.
process.send presence
Node defines
process.sendandprocess.disconnectonly when the process has an IPC channel. A read whose value only feeds a truthiness test (a condition,!,Boolean(...), an operand of&&/||) andtypeof process.sendnow answer that presence: a channel this fork child opened, or a non-emptyNODE_CHANNEL_FDfrom a Node.js parent, the variable Node itself tests at startup. The test is built from the existing environment read (plusscr_process_ipc_openin programs with fork targets), so it links no new runtime code. Other value uses ofprocess.sendkeep the SC2020 fence.In a program without fork targets this binary never opens a channel, so:
process.send?.(...)skips its arguments and answersundefinedwithout a channel, like Node;process.send(...)/process.send!(...)evaluate their arguments and throw Node'sTypeError: process.send is not a function;Programs with fork targets keep their existing send lowering.
process.exitCode reads
process.exitCodenow readsundefineduntil something assigns it, then the assigned code.process.exit(code)assigns it before the'exit'listeners run, andprocess.exit()without an argument keeps it. To tell an unset code from0, assignments must be recorded, so only programs that read the property use the recording writer (process.setExitCodeTracked); all other programs keep the untracked write and compile to identical code.Stream hooks that declare a supertype
A stream subclass hook may now declare a supertype of what Node passes (a union holding Node's argument type). The wrapper takes Node's Buffer and widens it into the declared parameter. This covers
_write,_transform,_final,_destroyand_read._writevhas no lowering at all and stays fenced, and the option-callback form (new Writable({ write(chunk: Buffer | string) {} })) keeps the exact-type rule.Stream classes declared by @types/node
class X extends Writable(and Readable, Duplex, Transform) fromnode:streamcompiled only against the bundled declarations. With @types/node in the project, which nests the classes in the module'sinternalnamespace, the base was refused as "extending classes not declared in the program". The recognition now accepts either declaration of the stream module's classes. A new node-types fixture runs a Writable with a widened chunk, a Transform and an erroring Writable under the pinned @types/node.Promise.race with a Promise entry
Promise.race([poll(), exited()])overPromise<T | undefined>andPromise<void>was refused because the checker foldsvoidinto the result'sundefinedarm. APromise<void>entry now fulfills the race with that arm (a new branch in the race adapter).Promise.all,Promise.anyandPromise.allSettledhave a different, stricter restriction (one shared promise type; a heterogeneous array literal is the tuple overload) and are unchanged.http.RequestOptions as a declared type
http.RequestOptionsandhttps.RequestOptionsas declared types ride checked-dynamic storage, likeSpawnOptions.http.request(options)with a non-literal options value still fences (the options must be an object literal), as before.util.promisify over typed callback functions
util.promisify(fn)over a typed callback-last function value (a bound method, a function declaration, an arrow) now builds a native promisified function: anewPromisewhose executor callsfnwith the arguments and a callback. A truthy error rejects, the value fulfills, and a synchronous throw rejects, as in Node. An optional callback parameter (close(callback?: (error?: Error) => void)) works too: the call passes the callback into the union. The callback's error parameter must be a union of an Error class andnull/undefined; other callback shapes keep the "util.promisify of this target" refusal, whose hint now names this form.The bundled Node declarations had no generic promisify overloads, so a callback-only function matched crypto.randomFill's overload and reported "Expected 1-3 arguments, but got 0". They now carry @types/node's two callback-only overloads, placed before the targets that would capture them. The overloads with leading arguments cannot be added: TypeScript's subtype pass then prefers them over the builtin targets (
promisify(execFile)would lose its result type), so those forms type-check only with @types/node. The node-types fixture covers them.__dirname and __filename in TypeScript ES modules
A
.ts/.tsxfile with import syntax is an ES module under Node's strip-types rule, which has no__dirname/__filename; scriptc refused them there. TypeScript projects are commonly built as CommonJS bundles (tsc, esbuild, tsx) where both exist, and @types/node declares them for every file. They now compile in such files to the module's source path, the same compile-time constants as in CommonJS and the same strings asimport.meta.dirname/import.meta.filename. This is on by default and needs no flag: the only programs that observe a difference are ones that rely on the ReferenceError or ontypeof __dirname === "undefined", which TypeScript projects targeting a CommonJS build do not. JavaScript ES modules keep the fence. The limitations page documents the difference.Evidence
Corpus programs (Node and native, both lanes):
6420-process-send-presence.ts: every truthiness position,typeof,?.without a channel, the direct call's TypeError.6421-process-send-presence-fork/: presence in a fork child and its parent, the guarded send.6422-process-exitcode-read.ts: unset,0, assignment,??default, invalid code (RangeError keeps the old code),process.exit(7)seen by the exit listener (exit 7).6423-process-exitcode-default.ts: the exit status followsexitCodeon a natural exit (exit 2).6424-process-exit-keeps-exitcode.ts:process.exit()keepsexitCode(exit 5).6425-stream-hook-wider-chunk.ts: Writable/Transform hooks declaringBuffer | string,_final, an erroring_write.6426-promise-race-void-entry.ts: either entry first, an already settled void entry,number | void, a rejection.6427-http-request-options-type.ts: declared options with spread and a typed parameter.6428-promisify-callback-functions.ts: bound methods (one with an optional callback), a declaration, an arrow, rejection, a synchronous throw, asynchronous settlement order.6429-esm-js-dirname.mjs: a JavaScript ES module still has no__dirname.Harness:
tests/harness/esm-path-globals.test.ts:__dirname/__filenamein a two-module TypeScript ESM program; the oracle runs a twin withimport.meta.dirname/import.meta.filenameat the same paths.tests/harness/project-config.test.ts: promisify with leading arguments and stream subclasses under @types/node.boot-path-node-surface-fences.ts(a storedprocess.send, a non-supertype chunk, a promisify target with a non-Error callback).Gates:
scripts/zero-cost-gate.mjs --corpus=all): PASS on the final tree against main 4bbf31b: 3249 programs identical in IR, LLVM and release executables (bench workloads, the self-hosted compiler, every TypeScript and JavaScript corpus program); compile time bench geomean +0.14% [−1.03%, +1.17%], self-hosted +0.45% (one pair), both within tolerance.test:ts7 --baselines-only(new baselines only; existing ones unchanged),pnpm manifestandpnpm node-compat(evidence rows only),node-compat:check.packages/compiler/src/native/cli.ts: no diagnostics.While building the self-hosting check, the first version of the promisify helper built its parameter lists with
IrType[].map((type, i) => …). That made the self-hosted compiler typeIrParamelements as possiblyundefinedprogram-wide, and an unrelated{ ...param, owned: true }spread then failed. The helper now builds the lists withpush. The root cause in the self-hosted flow analysis is worth a separate look.🤖 Generated with Claude Code