Repository navigation
Let constructor functions inherit from EventEmitter and the stream classes - #1512
Merged
Merged
Conversation
…asses Bundled npm code (glob, yazl, from2, multistream) subclasses Node's classes with constructor functions: util.inherits(Ctor, EventEmitter) and EventEmitter.call(this). util.inherits received an identity token and threw ERR_INVALID_ARG_TYPE, EventEmitter.call(this) and EventEmitter.prototype were refused, and `this instanceof Ctor` against a function was refused. A new runtime unit (scr_dyn_builtin_class.c, linked by the new dynBuiltinClass feature) provides EventEmitter, the legacy Stream and the stream classes as untyped constructor functions with Node's prototype chain. EventEmitter's prototype ports lib/events.js over the object's own _events, _eventsCount and _maxListeners properties; Stream adds the legacy pipe. util.inherits, Base.call/apply(this, ...), Base.prototype, instanceof on untyped values and JavaScript value reads of these classes use them. Constructing over a stream class throws SC2020 where it runs. instanceof against a compiled constructor function compares its prototype with the value's prototype chain. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…f alone, and clean up a pipe once
- EventEmitter.defaultMaxListeners on the untyped constructor is Node's
accessor over the default the typed EventEmitter reads: assignments
validate as validateNumber(arg, 'defaultMaxListeners', 0) does, and
getMaxListeners() of emitters without their own limit answers the new
value.
- dyn.builtinInstanceOf and dyn.functionInstanceOf link the
constructor-function unit and the units it calls (dyn-invoke, inspect,
symbol, emitter), so a program whose only use is `this instanceof F`
links.
- The legacy pipe's cleanup runs once: when the destination emits
'close' from end(), the cleanup still queued on the source's 'end'
finds the listeners removed and emit('end') answers true.
euphemism
pushed a commit
to euphemism/scriptc
that referenced
this pull request
Oct 20, 2026
…e headers The backend can emit scr_wasm_memory_construct, scr_wasm_table_construct, scr_wasm_global_construct, scr_wasm_construct_without_new, scr_wasm_module_imports, scr_wasm_module_exports, scr_wasm_module_custom_sections and scr_wasm_value_of (vercel-labs#1410), and scr_stream_dyn_native, scr_stream_pipe_dyn and scr_stream_unpipe_dyn (vercel-labs#1524). Their prototypes were in scr_wasm.c or nowhere, so the runtime ABI guard failed. They are now in scr_wasm.h and scr_stream.h, and scr_wasm.c no longer repeats the header's declarations. The speculative-hoisting test relied on `v instanceof Num` in a JavaScript function failing, which constructor-function instanceof now compiles (vercel-labs#1512). Its hoisted function reads the prototype through Reflect.getPrototypeOf instead, which still fences.
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.
Stacked on #1479 (
opt/builtin-base-class-values). Review that first.Summary
Bundled npm code builds subclasses of Node's classes the pre-class way, with constructor functions:
glob, yazl, from2 and multistream all do this at module load.
util.inheritsreceived an identity token for the builtin class and threw Node'sERR_INVALID_ARG_TYPE("superCtor.prototype must be of type object"),EventEmitter.call(this)andEventEmitter.prototypewere refused, andthis instanceof Globwas refused because the right-hand side is a function rather than a class.#1479 made ES classes that extend these bases usable as values. Those are compiled classes with a native emitter or stream inside. A constructor function's
new Glob()builds an ordinary untyped object instead, so the base has to work on such an object.Untyped constructors for the builtin bases. A new runtime unit,
scr_dyn_builtin_class.c, provides EventEmitter, the legacyStreamand the five stream classes as untyped constructor functions, one per thread. Their prototypes are chained as in Node (PassThrough→Transform→Duplex→Readable→Stream→EventEmitter, andWritable→Stream). The compiler uses them where a constructor function meets its base:util.inherits(Ctor, Base)andCtor.super_;Base.call(this, …)andBase.apply(this, …);Base.prototype, soObject.setPrototypeOf(Ctor.prototype, Base.prototype)works too;x instanceof Baseon an untyped value;EventEmitteror a stream class (a destructuredrequirebinding orstream.Readable), which used to be the identity token.EventEmitter on an untyped object.
EventEmitter.call(this)runs Node'sEventEmitter.init. The prototype's methods are a port oflib/events.jsthat keeps the listener state where Node keeps it, in the object's own_events,_eventsCountand_maxListenersproperties plus thekShapeModeandkCapturesymbols. This coverson/addListener,prependListener,once,prependOnceListener,off/removeListener,removeAllListeners,emit(including the unhandled'error'rule andERR_UNHANDLED_ERROR),listeners,rawListeners,listenerCount,eventNames,setMaxListeners,getMaxListeners, thenewListenerandremoveListenerevents, the leak warning, and the staticEventEmitter.listenerCount. The legacyStreamadds Node's legacypipe.Stream bases.
util.inherits(Ctor, stream.Readable)and the other stream classes link the prototypes, so module initialization andinstanceofwork. CallingReadable.call(this, opts)(or another stream class) on a constructor function's receiver throwsSC2020where it runs. Native stream state on an untyped object is the next step.instanceof a constructor function.
x instanceof Ctorin JavaScript, whereCtoris a compiled function, comparesCtor.prototypewith the value's prototype chain. This is theif (!(this instanceof Ctor)) return new Ctor(…)guard.The unit links only into programs that use these values, behind a new
dynBuiltinClassruntime feature. It also needs the inspect, dyn-invoke and symbol units. Programs that don't use these values are unchanged.Evidence
6453-constructor-function-emitter.cjs: a two-level constructor-function hierarchy over EventEmitter throughutil.inheritsandObject.setPrototypeOf, the call-without-newguard, the listener API, once and prepend ordering, unhandled'error'with an Error and with a string,newListener/removeListenerevents,removeAllListeners,setMaxListenersrange and listener type errors, the leak warning, andutil.inspectof an instance.6454-constructor-function-stream-bases.cjs: the legacyStreambase and itspipebetween two such streams (with and withoutend: false), plusutil.inheritsonto all five stream classes withsuper_andinstanceofalong the chain.5977-builtin-class-aliases.cjsstoresEventEmitterandPassThroughin an untyped array; those values are now the untyped constructors instead of identity tokens, with the same output.5977-builtin-class-aliases.cjs, which is intended: it storesEventEmitterandPassThroughin an untyped array, so it now links the new unit (+9.4% binary size for that program) and holds real constructor functions where it held identity tokens. Its output is unchanged.test:ts7 --baselines-only, the diagnostics, npm and inheritance suites, the runtime-pack and feature unit tests,self-hosting-runtime-features.test.tsand the self-hosting IR build are clean.Limitations
These are documented on the limitations page:
captureRejectionsandevents.errorMonitorlisteners are not supported on these objects.Readable,Writable,Duplex,TransformorPassThroughrefuses at run time.#1487 adds a compile-time refusal for
util.inheritsonto any runtime-provided class. When both land, EventEmitter and the stream classes should take this path and other runtime classes keep that refusal; the two touch the same lines inlower-inspect.ts.The value of
require("stream")itself (the legacy constructor as a module object) is unchanged.util.inheritsand.callthrough it work, but other uses of it as a value do not.🤖 Generated with Claude Code