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//!   - `isProxy` is always `false` — there is no `Proxy` in node-js.
17//!   - No boxed primitives exist (`Number(x)` yields a primitive, never an
18//!     object wrapper), so `isBoxedPrimitive` and the `is{Number,String,…}Object`
19//!     family are all `false`.
20//!   - `isArgumentsObject` is `false`: `arguments` is materialised as a plain
21//!     array (see `host.rs`), indistinguishable from any other array here.
22//!   - No `SharedArrayBuffer`/`DataView`/`BigInt64Array`, module-namespace, or
23//!     external objects → those predicates are `false`.
24
25use crate::host::{with_host, JsObj};
26use fusevm::Value;
27
28pub const METHODS: &[&str] = &[
29    "isDate",
30    "isRegExp",
31    "isMap",
32    "isSet",
33    "isWeakMap",
34    "isWeakSet",
35    "isPromise",
36    "isArrayBuffer",
37    "isSharedArrayBuffer",
38    "isAnyArrayBuffer",
39    "isTypedArray",
40    "isDataView",
41    "isUint8Array",
42    "isUint8ClampedArray",
43    "isUint16Array",
44    "isUint32Array",
45    "isInt8Array",
46    "isInt16Array",
47    "isInt32Array",
48    "isFloat32Array",
49    "isFloat64Array",
50    "isBigInt64Array",
51    "isBigUint64Array",
52    "isAsyncFunction",
53    "isGeneratorFunction",
54    "isGeneratorObject",
55    "isProxy",
56    "isNativeError",
57    "isBoxedPrimitive",
58    "isArgumentsObject",
59    "isNumberObject",
60    "isStringObject",
61    "isBooleanObject",
62    "isSymbolObject",
63    "isBigIntObject",
64    "isModuleNamespaceObject",
65    "isExternal",
66    "isArrayBufferView",
67    "isCryptoKey",
68    "isKeyObject",
69    "isFloat16Array",
70    "isMapIterator",
71    "isSetIterator",
72];
73
74pub fn call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
75    let v = args.first().cloned().unwrap_or(Value::Undef);
76    let b = |x: bool| Some(Ok(Value::Bool(x)));
77    match method {
78        "isDate" => b(super::native_tag(&v).as_deref() == Some("Date")),
79        "isRegExp" => b(with_host(|h| matches!(h.get(&v), Some(JsObj::RegExp(_))))),
80        "isMap" => b(with_host(|h| {
81            matches!(h.get(&v), Some(JsObj::Map { weak: false, .. }))
82        })),
83        "isSet" => b(with_host(|h| {
84            matches!(h.get(&v), Some(JsObj::Set { weak: false, .. }))
85        })),
86        "isWeakMap" => b(with_host(|h| {
87            matches!(h.get(&v), Some(JsObj::Map { weak: true, .. }))
88        })),
89        "isWeakSet" => b(with_host(|h| {
90            matches!(h.get(&v), Some(JsObj::Set { weak: true, .. }))
91        })),
92        "isPromise" => b(with_host(|h| {
93            matches!(h.get(&v), Some(JsObj::Promise { .. }))
94        })),
95
96        // `ArrayBuffer` is a `@@native`-tagged byte container; node-js has no
97        // `SharedArrayBuffer`, so `isAnyArrayBuffer` collapses onto it.
98        "isArrayBuffer" => b(super::native_tag(&v).as_deref() == Some("ArrayBuffer")),
99        "isSharedArrayBuffer" => b(false),
100        "isAnyArrayBuffer" => b(super::native_tag(&v).as_deref() == Some("ArrayBuffer")),
101        "isDataView" => b(false),
102
103        // Typed arrays carry `@@native = "TypedArray"` + a `@@kind`; a Node
104        // `Buffer` is a `Uint8Array` subclass, so it answers to both `isTypedArray`
105        // and `isUint8Array`.
106        "isTypedArray" => b(ta_kind(&v).is_some()),
107        "isUint8Array" => b(ta_kind(&v).as_deref() == Some("Uint8Array")),
108        "isUint8ClampedArray" => b(ta_kind(&v).as_deref() == Some("Uint8ClampedArray")),
109        "isUint16Array" => b(ta_kind(&v).as_deref() == Some("Uint16Array")),
110        "isUint32Array" => b(ta_kind(&v).as_deref() == Some("Uint32Array")),
111        "isInt8Array" => b(ta_kind(&v).as_deref() == Some("Int8Array")),
112        "isInt16Array" => b(ta_kind(&v).as_deref() == Some("Int16Array")),
113        "isInt32Array" => b(ta_kind(&v).as_deref() == Some("Int32Array")),
114        "isFloat32Array" => b(ta_kind(&v).as_deref() == Some("Float32Array")),
115        "isFloat64Array" => b(ta_kind(&v).as_deref() == Some("Float64Array")),
116        // No BigInt-backed typed arrays in node-js.
117        "isBigInt64Array" => b(false),
118        "isBigUint64Array" => b(false),
119
120        "isAsyncFunction" => b(func_flag(&v, FuncFlag::Async)),
121        "isGeneratorFunction" => b(func_flag(&v, FuncFlag::Generator)),
122        "isGeneratorObject" => b(with_host(|h| {
123            matches!(h.get(&v), Some(JsObj::Generator { .. }))
124        })),
125
126        // No Proxy in node-js; there is nothing that could report `true`.
127        "isProxy" => b(false),
128        // Reuse the vetted prototype-chain walk: any instance whose chain reaches
129        // `Error.prototype` is a native error.
130        "isNativeError" => b(is_native_error(&v)),
131
132        // node-js never boxes primitives, so every wrapper-object predicate is
133        // structurally `false`.
134        "isBoxedPrimitive" | "isNumberObject" | "isStringObject" | "isBooleanObject"
135        | "isSymbolObject" | "isBigIntObject" => b(false),
136
137        // `arguments` is a plain array here (indistinguishable from any array),
138        // and there are no module-namespace / external (N-API) objects.
139        "isArgumentsObject" | "isModuleNamespaceObject" | "isExternal" => b(false),
140
141        // A "view" over an `ArrayBuffer`: any typed array (a `Buffer` counts, being
142        // a `Uint8Array` subclass). node-js has no `DataView`, so views == typed
143        // arrays exactly.
144        "isArrayBufferView" => b(ta_kind(&v).is_some()),
145        // node-js has no `Float16Array` kind (no `@@kind` ever reports it).
146        "isFloat16Array" => b(ta_kind(&v).as_deref() == Some("Float16Array")),
147        // No WebCrypto `CryptoKey` / `KeyObject` heap kinds exist here.
148        "isCryptoKey" | "isKeyObject" => b(false),
149        // node-js iterators are a single generic `JsObj::Iter` with no Map/Set
150        // brand, so a genuine `map.entries()` cannot be told apart from any other
151        // iterator. Reporting `false` avoids false positives on array/string
152        // iterators (the common case); the positive case is a known limitation.
153        "isMapIterator" | "isSetIterator" => b(false),
154
155        _ => None,
156    }
157}
158
159/// The typed-array kind of `v` (`"Uint8Array"`/…/`"Float64Array"`), or `None` if
160/// it is not a typed array. A native `Buffer` reports `Uint8Array` (Node models
161/// `Buffer` as a `Uint8Array` subclass).
162fn ta_kind(v: &Value) -> Option<String> {
163    match super::native_tag(v).as_deref() {
164        Some("TypedArray") => with_host(|h| match h.get(v) {
165            Some(JsObj::Object(p)) => p.get("@@kind").map(|k| h.str_of(k)),
166            _ => None,
167        }),
168        Some("Buffer") => Some("Uint8Array".to_string()),
169        _ => None,
170    }
171}
172
173/// Which `FuncDef` flag a function predicate is asking about.
174enum FuncFlag {
175    Async,
176    Generator,
177}
178
179/// True if `v` is a closure whose template carries the requested flag. Extract the
180/// `def_id` first (immutable borrow), then read the shared `funcs` table — never
181/// nesting two `with_host` borrows.
182fn func_flag(v: &Value, flag: FuncFlag) -> bool {
183    let def_id = with_host(|h| match h.get(v) {
184        Some(JsObj::Func(f)) => Some(f.def_id),
185        _ => None,
186    });
187    with_host(|h| {
188        def_id
189            .and_then(|id| h.funcs.get(id))
190            .map(|d| match flag {
191                FuncFlag::Async => d.is_async,
192                FuncFlag::Generator => d.is_generator,
193            })
194            .unwrap_or(false)
195    })
196}
197
198/// True if `v`'s prototype chain reaches `Error.prototype` — i.e. it is an
199/// instance of one of the built-in error constructors.
200fn is_native_error(v: &Value) -> bool {
201    if !matches!(v, Value::Obj(_)) {
202        return false;
203    }
204    let err_ctor = with_host(|h| h.alloc(JsObj::Builtin("Error".into())));
205    crate::host::instance_of(v, &err_ctor).unwrap_or(false)
206}