Builtins.typeOf() and Builtins.is() ?
- Stage: Unstaged
- Status: Withdrawn
- ECMAScript edition: —
- Synchronized: Aug 28, 2026
- 中文译文 · Source repository
This proposal introduces a new built-in object Builtin with typeOf() and is() methods to solve cross-realm type checking issues that arise with instanceof. It adds a \[\[Builtin\]\] internal slot and a @@builtin symbol to mark built-ins, allowing reliable type identification across realms and user-defined masquerading. The proposal also includes a Proxy.isProxy() method.
The README below comes from the upstream repository and may contain outdated stage or status metadata. Use the proposal details above as the current source of truth.
Builtin.is and Builtin.typeOf
Motivation
There are a number of situations where existing type checking using instanceof
can be problematic. For instance:
In this case, both statements return valid Date objects. However, because
the second is created in a separate realm, it is not recognized as a Date in
the current realm, despite operating appropriately in every other respect.
In other cases, instanceof does not provide adequate granularity, such as
checking if a given argument is an unsigned 16-bit integer vs. a signed 32-bit
integer.
This proposal introduces a new Builtin built-in object that exposes methods
that allow reliable cross-realm type checking for ECMAScript built-ins.
Prior art
Node.js has relied on such checks, in part, to reliably determine types for
debugging, inspection and display formatting purposes in the util.format()
and util.inspect() APIs. In addition, the is package on npm (which
implements similar type checks) currently has roughly 33k+ downloads per day.
Node.js can (and has) implement these functions in a host-specific manner as part of the Node.js API but the preference would be towards having these kind of type checks be a regular part of the language API.
For example:
Requirements
What is needed?
- Mechanism for reliably determining if any given object is a built-in or is an instance of a built-in, even across realms.
- Mechanism for reliably determining if objects from different realms correspond
to the same built-in (e.g.
Datefrom one realm is the same built-in asDatefrom a second realm). - Avoid introducing new, or changing existing, language syntax.
- Allow host environments to insert new built-ins.
- Allow user code objects to masquerade as built-ins.
Proposed API
Identifying an Object as a Built-in
An object is identified as a built-in using:
- A new
[[Builtin]]internal slot to mark built-ins - A new
@@builtinsymbol (Symbol.builtin) property whose value is a function whose default behavior is to provide the value of the[[Builtin]]internal slot.
[[Builtin]] internal slot
Intrinsic objects listed in the table below have a [[Builtin]] internal slot
with the given string value. Intrinsic objects not listed in the table do not
have the [[Builtin]] internal slot.
Note: Currently, intrinsic prototype objects such as %DatePrototype%
intentionally do not have a [[Builtin]] internal slot. The effect of this
is such that Builtin.typeOf(new Date()) would return 'Date',
Builtin.typeOf(Object.getPrototypeOf(new Date())) would return 'object',
despite %DatePrototype% being an intrinsic object. The justification for
this is that it is not yet clear if intrinsic prototype objects need to be
identifiable as built-ins.
In addition, all built-in non-constructor functions and methods have a
[[Builtin]] internal slot equal to the name of the function. These are used
to allow using Builtin.is() to determine if two function/method instances
represent the same intrinsic function or method.
For instance,
Symbol.builtin
The initial value of the @@builtin own property for all intrinsic objects
having a [[Builtin]] internal slot is the same function that returns the
value of the [[Builtin]] internal slot. Intrinsic objects that do not have
the [[Builtin]] internal slot do not have an initial value for the @@builtin
own property.
An object is detectable as a built-in if it has the @@builtin own property.
An object is detectable as an instance of a built-in if its constructor has a
@@builtin property as either an own or inherited property.
Setting the @@builtin property to a non-function value makes the object,
or instances of the object, no longer detectable as built-ins:
The @@builtin property has the attributes:
[[Configurable]]: true[[Enumerable]]: false[[Writable]]: true
Abstract Operations
GetBuiltinValue
The abstract operation GetBuiltinValue with argument object performs the
following steps:
- Let
fnbe? GetMethod(object, @@builtin). - If
fnisundefined, returnundefined. - Let
valuebe? Call(fn, object). - If
valueisundefined, returnundefined. - Return
? ToString(value).
GetOwnBuiltinValue
The abstract operation GetOwnBuiltinValue with argument object performs the
following steps:
- Let
hasPropertybe? HasOwnProperty(object, @@builtin). - If
hasPropertyisfalse, returnundefined. - Return
? GetBuiltinValue(object).
Builtin
The Builtin object is the %Builtin% intrinsic object and the initial
value of the Builtin property of the global object. The Builtin
object is an ordinary object.
The value of the [[Prototype]] internal slot of the Builtin object is
the intrinsic object %ObjectPrototype%.
The Builtin object is not a function object. It does not have a
[[Construct]] internal method; it is not possible to use the Builtin
object as a constructor with the new operator. The Builtin object also
does not have a [[Call]] internal method; it is not possible to invoke the
Builtin object as a function.
Builtin.is(value1, value2)
When called with arguments value1 and value2:
- If
Type(value1)is notObjectreturnfalse. - Let
V1be? GetOwnBuiltinValue(value1). - If
V1isundefined, returnfalse. - If
value2isundefined, returnfalse. - If
Type(value2)is notObject, returnfalse. - Let
V2be? GetOwnBuiltinValue(value2). - Let
samebe the result of performing Strict Equality ComparisonV1 === V2. - Return
same
The Builtin.is() function returns true if both of the given values have a
@@builtin own property function that each returns values that, after coercion
to a string, are strictly equal to one another. Otherwise, return false.
Note that user code may modify the @@builtin own property on any object:
By default, the Builtin.is() function will not throw an exception. It is
possible for Builtin.is() to throw if a user-provided @@builtin function
throws or returns a value that cannot be coerced to a string (e.g. Symbol
values).
Builtin.typeOf(arg)
When the typeOf() function is called with argument arg:
- If
Type(arg)isObject, then:- Let
Cbe? Get(arg, "constructor"). - If
Cis notundefined, then:- Let
Vbe? GetBuiltinValue(C). - If
Vis notundefined, returnV.
- Let
- Let
- Return
typeof arg.
For example:
By default, the Builtin.typeOf() function will not throw an exception. It is
possible for Builtin.typeOf() to throw if a user-provided @@builtin function
throws or returns a value that cannot be coerced to a string (e.g. Symbol
values).
Note: Because of the nature of Proxy instances, it is not possible for
Builtin.typeOf(proxyObj) to ever return 'Proxy'.
Proxy.isProxy(value)
Returns true if value is a Proxy exotic object, otherwise return false.
The Proxy.isProxy() function will not throw an exception.
Note: Due to the security issues around Proxy, host environments should be
allowed to provide an option for forcing Proxy.isProxy(value) to always
return false. For instance, Node.js could hypothetically provide a
command-line argument like --disable-isproxy.
Notes
-
Adding a new
%Builtin%intrinsic object can be avoided by adding functions to an existing intrinisic, for instanceObject.isBuiltin()orObject.typeOf(). -
Using
@@builtinmeans that any object can lie about being a built-in by setting the@@builtinown property to whatever value it wants. This is by design. Polyfills/shims and secure-realm code, for example, must be able to create builtins, remove them, or replace builtins that are noncompliant - as such, a shim (that runs before other code) must be able to create its own builtin replacement and truly masquerade as if it were the original builtin. -
Why have a separate
Proxy.isProxy()function? For the simple reason thatProxyobjects do not act like anything else. The use case justifyingProxy.isProxy()is that, when debugging, it can often be necessary to know if the an object of interest is a Proxy or not. -
The
Builtinproperty on theglobalobject is set initially to theBuiltinobject. This property has the attributes:[[Configurable]]: true[[Enumerable]]: true[[Writable]]: true
-
The
Builtin.is,Builtin.typeOf, andProxy.isProxyproperties have the attributes:[[Configurable]]: true[[Enumerable]]: true[[Writable]]: true
Examples
Because the value of @@builtin is a function, the original implementation can
be captured, cached, and restored later:
Note: The behavior of the initial @@builtin function is to return the value
of the this objects [[Builtin]] internal slot if one exists. Accordingly,
it is possible to grab a reference to the function once and use it on multiple
objects: