Skip to content

Promisify untyped functions at run time - #1502

Merged
ctate merged 3 commits into
mainfrom
opt/promisify-dyn
Oct 21, 2026
Merged

ctate merged 3 commits into
mainfrom
opt/promisify-dyn

Conversation

@cramforce

@cramforce cramforce commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Summary

util.promisify(fn) needs the callback's shape to build a typed wrapper. When fn is typed any (a method of an untyped client, often obj.close.bind(obj)), there is no shape, and the call was refused.

An untyped argument now goes to a runtime wrapper, scr_util_promisify. It lives in its own runtime unit, scr_util_promisify.c, linked by a new utilPromisify feature together with the dyn-async and dyn-invoke units, so programs that don't promisify an untyped function are unchanged. The wrapper is an untyped function. Each call creates a promise, passes the call's receiver and arguments plus an error-first callback to fn, and settles once:

  • a truthy error rejects;
  • otherwise the first value after the error fulfills;
  • a synchronous throw rejects;
  • later callback calls are ignored.

This matches Node for plain functions. Node's promisify also copies the original's own property descriptors and prototype onto the wrapper and consults util.promisify.custom; the wrapper copies name and length, and an original with any other own property (util.promisify.custom among them) refuses with SC2020 where util.promisify runs, rather than returning a wrapper without them. The DEP0174 warning for an original that returns a promise is not reported. Both limits are on the limitations page. Typed functions keep the existing static wrapper, and their programs are unchanged.

Evidence

  • 6452-promisify-untyped-functions.ts: untyped methods promisified bound and unbound, an extra callback value dropped, error rejection, a callback called twice, a synchronous throw, and the call's receiver reaching the function. Node and native agree in both lanes.
  • The existing promisify and callbackify corpus programs pass in both lanes.
  • npm.test.ts: node24-util-adapters now runs its invalid-argument promisify scenarios as Node does and stops at an unrelated deferred Object.create refusal; its known state records that instead of the earlier promisify refusal.
  • Zero-cost gate against Compile process.send presence, process.exitCode reads, wider stream hooks, void race entries and promisify of callbacks #1429 (opt/bc-node-surface at 25e40af, which merges main 9d80738): PASS, 3293 programs identical (bench, self-hosted compiler, corpus) and 2 that fail on both sides; bench geomean −0.25% [−0.61%, +0.25%], self +1.16%, both within tolerance.
  • self-hosting-runtime-features.test.ts covers the new feature's detection, and the runtime-pack and executable-cache unit tests pass.
  • Lint, format, test:ts7 --baselines-only, the diagnostics suite and the self-hosting IR build are clean. contract-acyclic.ts fails in the diagnostics suite the same way on main.

🤖 Generated with Claude Code

util.promisify(fn) over a function typed any (an npm package method typed
by its JavaScript, a bound method of an untyped value) was refused,
because the promisified signature is only known at run time. A native
wrapper now calls the function with the call's receiver, its arguments
and a callback, and answers a promise: a truthy error rejects, otherwise
the first value fulfills, the first settlement wins, and a synchronous
throw rejects. util.promisify.custom functions are not consulted.

The wrapper lives in its own runtime unit (scr_util_promisify.c), linked
by the new utilPromisify feature, so other programs are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
scriptc Ready Ready Preview, v0 Oct 21, 2026 2:53am UTC

Comment thread packages/runtime/src/scr_util_promisify.c
Base automatically changed from opt/bc-node-surface to main October 20, 2026 07:18
Node's promisify copies the original's own property descriptors and
prototype onto the wrapper and consults util.promisify.custom. The
runtime wrapper now copies name and length and refuses, with SC2020 where
util.promisify runs, an original with any other own property, instead of
returning a wrapper that lacks them. The node24-util-adapters npm case
now gets past its promisify scenarios and stops at an unrelated deferred
Object.create refusal; its known state records that.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ctate
ctate merged commit e5af4de into main Oct 21, 2026
21 of 22 checks passed
@ctate
ctate deleted the opt/promisify-dyn branch October 21, 2026 02:54

This branch was successfully deployed

1 active deployment
Preview — 012fdb3f Deployed Oct 21, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants