Skip to main content

nodejs/stdlib/
util_types.rs

1//! Node `util.types` — runtime type-tag predicates.
2//!
3//! Every function here answers "what internal kind is this value?" without any
4//! user-observable coercion, mirroring Node's `require('node:util/types')`. Each
5//! predicate inspects the argument's `JsObj` heap variant or its hidden
6//! `@@native` tag and returns a plain boolean, so classification reuses the exact
7//! machinery the rest of the runtime already relies on:
8//!   - `Map`/`Set` carry a `weak` flag → the weak/non-weak split;
9//!   - `RegExp`/`Promise`/`Generator` are first-class heap variants;
10//!   - `Date`/`ArrayBuffer`/`TypedArray`/`Buffer` are plain objects wearing a
11//!     `@@native` tag (see `date.rs`/`typedarray.rs`/`buffer.rs`);
12//!   - async/generator functions are read off the shared `FuncDef` flags;
13//!   - `isNativeError` defers to the real `host::instance_of` against `Error`.
14//!
15//! Deviations from V8, kept honest (node-js is not V8):
16//!   - `isExternal` is `false`: there are no N-API external objects.
17//!   - `isArgumentsObject` is `false`: `arguments` is materialised as a plain
18//!     array (see `host.rs`), indistinguishable from any other array here.
19//!   - No `SharedArrayBuffer`/`DataView`/`BigInt64Array`, module-namespace, or
20//!     external objects → those predicates are `false`.
21
22use crate::host::{with_host, JsObj};
23use fusevm::Value;
24
25pub const METHODS: &[&str] = &[
26    "isDate",
27    "isRegExp",
28    "isMap",
29    "isSet",
30    "isWeakMap",
31    "isWeakSet",
32    "isPromise",
33    "isArrayBuffer",
34    "isSharedArrayBuffer",
35    "isAnyArrayBuffer",
36    "isTypedArray",
37    "isDataView",
38    "isUint8Array",
39    "isUint8ClampedArray",
40    "isUint16Array",
41    "isUint32Array",
42    "isInt8Array",
43    "isInt16Array",
44    "isInt32Array",
45    "isFloat32Array",
46    "isFloat64Array",
47    "isBigInt64Array",
48    "isBigUint64Array",
49    "isAsyncFunction",
50    "isGeneratorFunction",
51    "isGeneratorObject",
52    "isProxy",
53    "isNativeError",
54    "isBoxedPrimitive",
55    "isArgumentsObject",
56    "isNumberObject",
57    "isStringObject",
58    "isBooleanObject",
59    "isSymbolObject",
60    "isBigIntObject",
61    "isModuleNamespaceObject",
62    "isExternal",
63    "isArrayBufferView",
64    "isCryptoKey",
65    "isKeyObject",
66    "isFloat16Array",
67    "isMapIterator",
68    "isSetIterator",
69];
70
71pub fn call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
72    let v = args.first().cloned().unwrap_or(Value::Undef);
73    let b = |x: bool| Some(Ok(Value::Bool(x)));
74    match method {
75        "isDate" => b(super::native_tag(&v).as_deref() == Some("Date")),
76        "isRegExp" => b(with_host(|h| matches!(h.get(&v), Some(JsObj::RegExp(_))))),
77        "isMap" => b(with_host(|h| {
78            matches!(h.get(&v), Some(JsObj::Map { weak: false, .. }))
79        })),
80        "isSet" => b(with_host(|h| {
81            matches!(h.get(&v), Some(JsObj::Set { weak: false, .. }))
82        })),
83        "isWeakMap" => b(with_host(|h| {
84            matches!(h.get(&v), Some(JsObj::Map { weak: true, .. }))
85        })),
86        "isWeakSet" => b(with_host(|h| {
87            matches!(h.get(&v), Some(JsObj::Set { weak: true, .. }))
88        })),
89        "isPromise" => b(with_host(|h| {
90            matches!(h.get(&v), Some(JsObj::Promise { .. }))
91        })),
92
93        // `ArrayBuffer` is a `@@native`-tagged byte container; node-js has no
94        // `SharedArrayBuffer`, so `isAnyArrayBuffer` collapses onto it.
95        "isArrayBuffer" => b(super::native_tag(&v).as_deref() == Some("ArrayBuffer")),
96        "isSharedArrayBuffer" => b(false),
97        "isAnyArrayBuffer" => b(super::native_tag(&v).as_deref() == Some("ArrayBuffer")),
98        "isDataView" => b(super::native_tag(&v).as_deref() == Some("DataView")),
99
100        // Typed arrays carry `@@native = "TypedArray"` + a `@@kind`; a Node
101        // `Buffer` is a `Uint8Array` subclass, so it answers to both `isTypedArray`
102        // and `isUint8Array`.
103        "isTypedArray" => b(ta_kind(&v).is_some()),
104        "isUint8Array" => b(ta_kind(&v).as_deref() == Some("Uint8Array")),
105        "isUint8ClampedArray" => b(ta_kind(&v).as_deref() == Some("Uint8ClampedArray")),
106        "isUint16Array" => b(ta_kind(&v).as_deref() == Some("Uint16Array")),
107        "isUint32Array" => b(ta_kind(&v).as_deref() == Some("Uint32Array")),
108        "isInt8Array" => b(ta_kind(&v).as_deref() == Some("Int8Array")),
109        "isInt16Array" => b(ta_kind(&v).as_deref() == Some("Int16Array")),
110        "isInt32Array" => b(ta_kind(&v).as_deref() == Some("Int32Array")),
111        "isFloat32Array" => b(ta_kind(&v).as_deref() == Some("Float32Array")),
112        "isFloat64Array" => b(ta_kind(&v).as_deref() == Some("Float64Array")),
113        "isBigInt64Array" => b(ta_kind(&v).as_deref() == Some("BigInt64Array")),
114        "isBigUint64Array" => b(ta_kind(&v).as_deref() == Some("BigUint64Array")),
115
116        "isAsyncFunction" => b(func_flag(&v, FuncFlag::Async)),
117        "isGeneratorFunction" => b(func_flag(&v, FuncFlag::Generator)),
118        "isGeneratorObject" => b(with_host(|h| {
119            matches!(h.get(&v), Some(JsObj::Generator { .. }))
120        })),
121
122        "isProxy" => b(with_host(|h| h.kind_of(&v)) == Some(crate::host::ObjKind::Proxy)),
123        // Reuse the vetted prototype-chain walk: any instance whose chain reaches
124        // `Error.prototype` is a native error.
125        "isNativeError" => b(is_native_error(&v)),
126
127        // A wrapper object carries the primitive it boxes in a hidden slot.
128        "isBoxedPrimitive" => b(boxed_kind(&v).is_some()),
129        "isNumberObject" => b(boxed_kind(&v) == Some(Boxed::Number)),
130        "isStringObject" => b(boxed_kind(&v) == Some(Boxed::String)),
131        "isBooleanObject" => b(boxed_kind(&v) == Some(Boxed::Boolean)),
132        "isSymbolObject" => b(boxed_kind(&v) == Some(Boxed::Symbol)),
133        "isBigIntObject" => b(boxed_kind(&v) == Some(Boxed::BigInt)),
134
135        // There are no module-namespace / external (N-API) objects.
136        "isArgumentsObject" => b(crate::builtins::is_arguments(&v)),
137        "isModuleNamespaceObject" | "isExternal" => b(false),
138
139        // A "view" over an `ArrayBuffer`: any typed array (a `Buffer` counts,
140        // being a `Uint8Array` subclass) or a `DataView`.
141        "isArrayBufferView" => {
142            b(ta_kind(&v).is_some() || super::native_tag(&v).as_deref() == Some("DataView"))
143        }
144        // node-js has no `Float16Array` kind (no `@@kind` ever reports it).
145        "isFloat16Array" => b(ta_kind(&v).as_deref() == Some("Float16Array")),
146        // No WebCrypto `CryptoKey` / `KeyObject` heap kinds exist here.
147        "isCryptoKey" | "isKeyObject" => b(false),
148        // node-js iterators are a single generic `JsObj::Iter` with no Map/Set
149        // brand, so a genuine `map.entries()` cannot be told apart from any other
150        // iterator. Reporting `false` avoids false positives on array/string
151        // iterators (the common case); the positive case is a known limitation.
152        "isMapIterator" | "isSetIterator" => b(false),
153
154        _ => None,
155    }
156}
157
158/// Which primitive a wrapper object boxes.
159#[derive(PartialEq, Eq, Clone, Copy)]
160enum Boxed {
161    Number,
162    String,
163    Boolean,
164    Symbol,
165    BigInt,
166}
167
168/// The primitive `v` boxes, or `None` when it is not a wrapper object.
169///
170/// Wrapper objects keep the boxed value in a hidden `@@primitive` slot, so the
171/// predicate reads that rather than the object's shape.
172fn boxed_kind(v: &Value) -> Option<Boxed> {
173    let prim = crate::builtins::wrapped_primitive(v)?;
174    Some(with_host(|h| match h.get(&prim) {
175        Some(JsObj::Str(_)) => Boxed::String,
176        Some(JsObj::Symbol { .. }) => Boxed::Symbol,
177        Some(JsObj::BigInt(_)) => Boxed::BigInt,
178        _ => match prim {
179            Value::Bool(_) => Boxed::Boolean,
180            _ => Boxed::Number,
181        },
182    }))
183}
184
185/// The typed-array kind of `v` (`"Uint8Array"`/…/`"Float64Array"`), or `None` if
186/// it is not a typed array. A native `Buffer` reports `Uint8Array` (Node models
187/// `Buffer` as a `Uint8Array` subclass).
188fn ta_kind(v: &Value) -> Option<String> {
189    match super::native_tag(v).as_deref() {
190        Some("TypedArray") => with_host(|h| match h.get(v) {
191            Some(JsObj::Object(p)) => p.get("@@kind").map(|k| h.str_of(k)),
192            _ => None,
193        }),
194        Some("Buffer") => Some("Uint8Array".to_string()),
195        _ => None,
196    }
197}
198
199/// Which `FuncDef` flag a function predicate is asking about.
200enum FuncFlag {
201    Async,
202    Generator,
203}
204
205/// True if `v` is a closure whose template carries the requested flag. Extract the
206/// `def_id` first (immutable borrow), then read the shared `funcs` table — never
207/// nesting two `with_host` borrows.
208fn func_flag(v: &Value, flag: FuncFlag) -> bool {
209    let def_id = with_host(|h| match h.get(v) {
210        Some(JsObj::Func(f)) => Some(f.def_id),
211        _ => None,
212    });
213    with_host(|h| {
214        def_id
215            .and_then(|id| h.funcs.get(id))
216            .map(|d| match flag {
217                FuncFlag::Async => d.is_async,
218                FuncFlag::Generator => d.is_generator,
219            })
220            .unwrap_or(false)
221    })
222}
223
224/// True if `v`'s prototype chain reaches `Error.prototype` — i.e. it is an
225/// instance of one of the built-in error constructors.
226fn is_native_error(v: &Value) -> bool {
227    if !matches!(v, Value::Obj(_)) {
228        return false;
229    }
230    let err_ctor = with_host(|h| h.alloc(JsObj::Builtin("Error".into())));
231    crate::host::instance_of(v, &err_ctor).unwrap_or(false)
232}