From 68241234faa4715708010b75744bbfa2bb0cc40a Mon Sep 17 00:00:00 2001 From: Kenta Moriuchi Date: Tue, 19 Dec 2023 15:05:49 +0900 Subject: [PATCH] fix(console): inspect for `{Set,Map}Iterator` and `Weak{Set,Map}` (#21554) --- cli/tests/unit/console_test.ts | 47 ++++- ext/console/01_console.js | 176 +++++------------- ext/console/lib.rs | 97 +++++++++- ext/node/lib.rs | 6 - .../internal/console/constructor.mjs | 8 +- ext/node/polyfills/internal_binding/types.ts | 58 +----- ext/node/polyfills/internal_binding/util.ts | 2 - ext/node/polyfills/util.ts | 10 +- 8 files changed, 204 insertions(+), 200 deletions(-) diff --git a/cli/tests/unit/console_test.ts b/cli/tests/unit/console_test.ts index d6e2a5263d..2c8021c516 100644 --- a/cli/tests/unit/console_test.ts +++ b/cli/tests/unit/console_test.ts @@ -268,6 +268,14 @@ Deno.test(function consoleTestStringifyCircular() { "2018-12-10T02:26:59.002Z", ); assertEquals(stringify(new Set([1, 2, 3])), "Set(3) { 1, 2, 3 }"); + assertEquals( + stringify(new Set([1, 2, 3]).values()), + "[Set Iterator] { 1, 2, 3 }", + ); + assertEquals( + stringify(new Set([1, 2, 3]).entries()), + "[Set Entries] { [ 1, 1 ], [ 2, 2 ], [ 3, 3 ] }", + ); assertEquals( stringify( new Map([ @@ -277,6 +285,14 @@ Deno.test(function consoleTestStringifyCircular() { ), `Map(2) { 1 => "one", 2 => "two" }`, ); + assertEquals( + stringify(new Map([[1, "one"], [2, "two"]]).values()), + `[Map Iterator] { "one", "two" }`, + ); + assertEquals( + stringify(new Map([[1, "one"], [2, "two"]]).entries()), + `[Map Entries] { [ 1, "one" ], [ 2, "two" ] }`, + ); assertEquals(stringify(new WeakSet()), "WeakSet { }"); assertEquals(stringify(new WeakMap()), "WeakMap { }"); assertEquals(stringify(Symbol(1)), `Symbol("1")`); @@ -378,13 +394,16 @@ Deno.test(function consoleTestStringifyFunctionWithPrototypeRemoved() { assertEquals(stringify(f), "[Function (null prototype): f]"); const af = async function af() {}; Reflect.setPrototypeOf(af, null); - assertEquals(stringify(af), "[Function (null prototype): af]"); + assertEquals(stringify(af), "[AsyncFunction (null prototype): af]"); const gf = function* gf() {}; Reflect.setPrototypeOf(gf, null); - assertEquals(stringify(gf), "[Function (null prototype): gf]"); + assertEquals(stringify(gf), "[GeneratorFunction (null prototype): gf]"); const agf = async function* agf() {}; Reflect.setPrototypeOf(agf, null); - assertEquals(stringify(agf), "[Function (null prototype): agf]"); + assertEquals( + stringify(agf), + "[AsyncGeneratorFunction (null prototype): agf]", + ); }); Deno.test(function consoleTestStringifyFunctionWithProperties() { @@ -1002,6 +1021,26 @@ Deno.test(function consoleTestStringifyIterableWhenGrouped() { ); }); +Deno.test(function consoleTestIteratorValueAreNotConsumed() { + const setIterator = new Set([1, 2, 3]).values(); + assertEquals( + stringify(setIterator), + "[Set Iterator] { 1, 2, 3 }", + ); + assertEquals([...setIterator], [1, 2, 3]); +}); + +Deno.test(function consoleTestWeakSetAndWeakMapWithShowHidden() { + assertEquals( + stripColor(Deno.inspect(new WeakSet([{}]), { showHidden: true })), + "WeakSet { {} }", + ); + assertEquals( + stripColor(Deno.inspect(new WeakMap([[{}, "foo"]]), { showHidden: true })), + 'WeakMap { {} => "foo" }', + ); +}); + Deno.test(async function consoleTestStringifyPromises() { const pendingPromise = new Promise((_res, _rej) => {}); assertEquals(stringify(pendingPromise), "Promise { }"); @@ -2279,7 +2318,7 @@ Deno.test(function inspectWithPrototypePollution() { Deno.test(function inspectPromiseLike() { assertEquals( Deno.inspect(Object.create(Promise.prototype)), - "Promise { }", + "Promise {}", ); }); diff --git a/ext/console/01_console.js b/ext/console/01_console.js index 27203789f9..e5f5d8dcd8 100644 --- a/ext/console/01_console.js +++ b/ext/console/01_console.js @@ -10,7 +10,6 @@ const { ArrayBufferPrototypeGetByteLength, ArrayIsArray, ArrayPrototypeFill, - ArrayPrototypeConcat, ArrayPrototypeFilter, ArrayPrototypeFind, ArrayPrototypeForEach, @@ -75,7 +74,6 @@ const { ObjectPrototype, ObjectPrototypeIsPrototypeOf, ObjectPrototypePropertyIsEnumerable, - ObjectPrototypeToString, ObjectSetPrototypeOf, ObjectValues, Proxy, @@ -135,22 +133,7 @@ const { WeakMapPrototypeHas, WeakSetPrototypeHas, } = primordials; - -// supposed to be in node/internal_binding/util.ts -export function previewEntries(iter, isKeyValue) { - if (isKeyValue) { - // deno-lint-ignore prefer-primordials - const arr = [...iter]; - if (ArrayIsArray(arr[0]) && arr[0].length === 2) { - // deno-lint-ignore prefer-primordials - return [ArrayPrototypeConcat([], ...arr), true]; - } - return [arr, false]; - } else { - // deno-lint-ignore prefer-primordials - return [...iter]; - } -} +const ops = core.ops; let noColor = () => false; @@ -284,19 +267,15 @@ function isObjectLike(value) { return value !== null && typeof value === "object"; } -export function isAnyArrayBuffer(value) { - return isArrayBuffer(value) || isSharedArrayBuffer(value); +function isAnyArrayBuffer(value) { + return ops.op_is_any_arraybuffer(value); } -export function isArgumentsObject(value) { - return ( - isObjectLike(value) && - value[SymbolToStringTag] === undefined && - ObjectPrototypeToString(value) === "[object Arguments]" - ); +function isArgumentsObject(value) { + return ops.op_is_arguments_object(value); } -export function isArrayBuffer(value) { +function isArrayBuffer(value) { try { ArrayBufferPrototypeGetByteLength(value); return true; @@ -305,21 +284,11 @@ export function isArrayBuffer(value) { } } -export function isAsyncFunction(value) { - return ( - typeof value === "function" && - (value[SymbolToStringTag] === "AsyncFunction") - ); +function isAsyncFunction(value) { + return ops.op_is_async_function(value); } -export function isAsyncGeneratorFunction(value) { - return ( - typeof value === "function" && - (value[SymbolToStringTag] === "AsyncGeneratorFunction") - ); -} - -export function isBooleanObject(value) { +function isBooleanObject(value) { if (!isObjectLike(value)) { return false; } @@ -332,7 +301,7 @@ export function isBooleanObject(value) { } } -export function isBoxedPrimitive( +function isBoxedPrimitive( value, ) { return ( @@ -344,27 +313,22 @@ export function isBoxedPrimitive( ); } -export function isDataView(value) { +function isDataView(value) { return ( ArrayBufferIsView(value) && TypedArrayPrototypeGetSymbolToStringTag(value) === undefined ); } -export function isTypedArray(value) { +function isTypedArray(value) { return TypedArrayPrototypeGetSymbolToStringTag(value) !== undefined; } -export function isGeneratorFunction( - value, -) { - return ( - typeof value === "function" && - value[SymbolToStringTag] === "GeneratorFunction" - ); +function isGeneratorFunction(value) { + return ops.op_is_generator_function(value); } -export function isMap(value) { +function isMap(value) { try { MapPrototypeGetSize(value); return true; @@ -373,33 +337,19 @@ export function isMap(value) { } } -export function isMapIterator( - value, -) { - return ( - isObjectLike(value) && - value[SymbolToStringTag] === "Map Iterator" - ); +function isMapIterator(value) { + return ops.op_is_map_iterator(value); } -export function isModuleNamespaceObject( - value, -) { - return ( - isObjectLike(value) && - value[SymbolToStringTag] === "Module" - ); +function isModuleNamespaceObject(value) { + return ops.op_is_module_namespace_object(value); } -export function isNativeError(value) { - return ( - isObjectLike(value) && - value[SymbolToStringTag] === undefined && - ObjectPrototypeToString(value) === "[object Error]" - ); +function isNativeError(value) { + return ops.op_is_native_error(value); } -export function isNumberObject(value) { +function isNumberObject(value) { if (!isObjectLike(value)) { return false; } @@ -412,7 +362,7 @@ export function isNumberObject(value) { } } -export function isBigIntObject(value) { +function isBigIntObject(value) { if (!isObjectLike(value)) { return false; } @@ -425,21 +375,15 @@ export function isBigIntObject(value) { } } -export function isPromise(value) { - return ( - isObjectLike(value) && - value[SymbolToStringTag] === "Promise" - ); -} -export function isRegExp(value) { - return ( - isObjectLike(value) && - value[SymbolToStringTag] === undefined && - ObjectPrototypeToString(value) === "[object RegExp]" - ); +function isPromise(value) { + return ops.op_is_promise(value); } -export function isSet(value) { +function isRegExp(value) { + return ops.op_is_reg_exp(value); +} + +function isSet(value) { try { SetPrototypeGetSize(value); return true; @@ -448,27 +392,11 @@ export function isSet(value) { } } -export function isSetIterator( - value, -) { - return ( - isObjectLike(value) && - value[SymbolToStringTag] === "Set Iterator" - ); +function isSetIterator(value) { + return ops.op_is_set_iterator(value); } -export function isSharedArrayBuffer( - value, -) { - try { - getSharedArrayBufferByteLength(value); - return true; - } catch { - return false; - } -} - -export function isStringObject(value) { +function isStringObject(value) { if (!isObjectLike(value)) { return false; } @@ -481,7 +409,7 @@ export function isStringObject(value) { } } -export function isSymbolObject(value) { +function isSymbolObject(value) { if (!isObjectLike(value)) { return false; } @@ -494,7 +422,7 @@ export function isSymbolObject(value) { } } -export function isWeakMap( +function isWeakMap( value, ) { try { @@ -505,7 +433,7 @@ export function isWeakMap( } } -export function isWeakSet( +function isWeakSet( value, ) { try { @@ -793,9 +721,6 @@ function getFunctionBase(value, constructor, tag) { if (isAsyncFunction(value)) { type = `Async${type}`; } - if (isAsyncGeneratorFunction(value)) { - type = `AsyncGenerator${type}`; - } let base = `[${type}`; if (constructor === null) { base += " (null prototype)"; @@ -866,7 +791,7 @@ function formatRaw(ctx, value, recurseTimes, typedArray, proxyDetails) { const prefix = (constructor !== "Array" || tag !== "") ? getPrefix(constructor, tag, "Array", `(${value.length})`) : ""; - keys = core.ops.op_get_non_index_property_names(value, filter); + keys = ops.op_get_non_index_property_names(value, filter); braces = [`${prefix}[`, "]"]; if ( value.length === 0 && keys.length === 0 && protoProps === undefined @@ -1465,7 +1390,7 @@ function formatTypedArray( ) { const maxLength = MathMin(MathMax(0, ctx.maxArrayLength), length); const remaining = value.length - maxLength; - const output = new Array(maxLength); + const output = []; const elementFormatter = value.length > 0 && typeof value[0] === "number" ? formatNumber : formatBigInt; @@ -1508,7 +1433,7 @@ function getIteratorBraces(type, tag) { const iteratorRegExp = new SafeRegExp(" Iterator] {$"); function formatIterator(braces, ctx, value, recurseTimes) { - const { 0: entries, 1: isKeyValue } = previewEntries(value, true); + const { 0: entries, 1: isKeyValue } = ops.op_preview_entries(value, true); if (isKeyValue) { // Mark entry iterators as such. braces[0] = StringPrototypeReplace( @@ -1625,7 +1550,7 @@ function inspectError(value, ctx) { const hexSliceLookupTable = function () { const alphabet = "0123456789abcdef"; - const table = new Array(256); + const table = []; for (let i = 0; i < 16; ++i) { const i16 = i * 16; for (let j = 0; j < 16; ++j) { @@ -1689,14 +1614,7 @@ const PromiseState = { function formatPromise(ctx, value, recurseTimes) { let output; - let opResult; - // This op will fail for non-promises, but we get here for some promise-likes. - try { - opResult = core.getPromiseDetails(value); - } catch { - return [ctx.stylize("", "special")]; - } - const { 0: state, 1: result } = opResult; + const { 0: state, 1: result } = core.getPromiseDetails(value); if (state === PromiseState.Pending) { output = [ctx.stylize("", "special")]; } else { @@ -1717,12 +1635,12 @@ function formatWeakCollection(ctx) { } function formatWeakSet(ctx, value, recurseTimes) { - const entries = previewEntries(value); + const entries = ops.op_preview_entries(value, false); return formatSetIterInner(ctx, recurseTimes, entries, kWeak); } function formatWeakMap(ctx, value, recurseTimes) { - const entries = previewEntries(value); + const entries = ops.op_preview_entries(value, false); return formatMapIterInner(ctx, recurseTimes, entries, kWeak); } @@ -1843,7 +1761,7 @@ function formatNamespaceObject( value, recurseTimes, ) { - const output = new Array(keys.length); + const output = []; for (let i = 0; i < keys.length; i++) { try { output[i] = formatProperty( @@ -2062,7 +1980,7 @@ function groupArrayElements(ctx, output, value) { outputLength--; } const separatorSpace = 2; // Add 1 for the space and 1 for the separator. - const dataLen = new Array(outputLength); + const dataLen = []; // Calculate the total length of all output entries and the individual max // entries length of all output entries. We have to remove colors first, // otherwise the length would not be calculated properly. @@ -2176,7 +2094,7 @@ function formatMapIterInner( const len = entries.length / 2; const remaining = len - maxArrayLength; const maxLength = MathMin(maxArrayLength, len); - const output = new Array(maxLength); + const output = []; let i = 0; ctx.indentationLvl += 2; if (state === kWeak) { @@ -2227,7 +2145,7 @@ function formatSetIterInner( ) { const maxArrayLength = MathMax(ctx.maxArrayLength, 0); const maxLength = MathMin(maxArrayLength, entries.length); - const output = new Array(maxLength); + const output = []; ctx.indentationLvl += 2; for (let i = 0; i < maxLength; i++) { output[i] = formatValue(ctx, entries[i], recurseTimes); diff --git a/ext/console/lib.rs b/ext/console/lib.rs index a31470e080..5464da555f 100644 --- a/ext/console/lib.rs +++ b/ext/console/lib.rs @@ -1,8 +1,103 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +use deno_core::op2; +use deno_core::v8; use std::path::PathBuf; -deno_core::extension!(deno_console, esm = ["01_console.js"],); +deno_core::extension!( + deno_console, + ops = [ + op_is_any_arraybuffer, + op_is_arguments_object, + op_is_async_function, + op_is_generator_function, + op_is_generator_object, + op_is_map_iterator, + op_is_module_namespace_object, + op_is_native_error, + op_is_promise, + op_is_reg_exp, + op_is_set_iterator, + op_preview_entries, + ], + esm = ["01_console.js"], +); pub fn get_declaration() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("lib.deno_console.d.ts") } + +#[op2(fast)] +fn op_is_any_arraybuffer(value: &v8::Value) -> bool { + value.is_array_buffer() || value.is_shared_array_buffer() +} + +#[op2(fast)] +pub fn op_is_arguments_object(value: &v8::Value) -> bool { + value.is_arguments_object() +} + +#[op2(fast)] +pub fn op_is_async_function(value: &v8::Value) -> bool { + value.is_async_function() +} + +#[op2(fast)] +pub fn op_is_generator_function(value: &v8::Value) -> bool { + value.is_generator_function() +} + +#[op2(fast)] +pub fn op_is_generator_object(value: &v8::Value) -> bool { + value.is_generator_object() +} + +#[op2(fast)] +pub fn op_is_map_iterator(value: &v8::Value) -> bool { + value.is_map_iterator() +} + +#[op2(fast)] +pub fn op_is_module_namespace_object(value: &v8::Value) -> bool { + value.is_module_namespace_object() +} + +#[op2(fast)] +pub fn op_is_native_error(value: &v8::Value) -> bool { + value.is_native_error() +} + +#[op2(fast)] +pub fn op_is_promise(value: &v8::Value) -> bool { + value.is_promise() +} + +#[op2(fast)] +pub fn op_is_reg_exp(value: &v8::Value) -> bool { + value.is_reg_exp() +} + +#[op2(fast)] +pub fn op_is_set_iterator(value: &v8::Value) -> bool { + value.is_set_iterator() +} + +#[op2] +pub fn op_preview_entries<'s>( + scope: &mut v8::HandleScope<'s>, + object: &v8::Object, + slow_path: bool, +) -> v8::Local<'s, v8::Value> { + let (entries, is_key_value) = object.preview_entries(scope); + match entries { + None => v8::undefined(scope).into(), + Some(entries) => { + if !slow_path { + return entries.into(); + } + + let ret: [v8::Local; 2] = + [entries.into(), v8::Boolean::new(scope, is_key_value).into()]; + v8::Array::new_with_elements(scope, &ret).into() + } + } +} diff --git a/ext/node/lib.rs b/ext/node/lib.rs index dd3acd17cc..42a7527913 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -150,11 +150,6 @@ fn op_node_build_os() -> String { env!("TARGET").split('-').nth(2).unwrap().to_string() } -#[op2(fast)] -fn op_is_any_arraybuffer(value: &v8::Value) -> bool { - value.is_array_buffer() || value.is_shared_array_buffer() -} - #[op2(fast)] fn op_node_is_promise_rejected(value: v8::Local) -> bool { let Ok(promise) = v8::Local::::try_from(value) else { @@ -286,7 +281,6 @@ deno_core::extension!(deno_node, ops::os::op_node_os_username

, ops::os::op_geteuid

, op_node_build_os, - op_is_any_arraybuffer, op_node_is_promise_rejected, op_npm_process_state, ops::require::op_require_init_paths, diff --git a/ext/node/polyfills/internal/console/constructor.mjs b/ext/node/polyfills/internal/console/constructor.mjs index afa18bb97e..e9160cf700 100644 --- a/ext/node/polyfills/internal/console/constructor.mjs +++ b/ext/node/polyfills/internal/console/constructor.mjs @@ -4,6 +4,9 @@ // TODO(petamoriken): enable prefer-primordials for node polyfills // deno-lint-ignore-file prefer-primordials +import { core } from "ext:core/mod.js"; +const ops = core.ops; + // Mock trace for now const trace = () => {}; import { @@ -17,7 +20,6 @@ import { validateInteger, validateObject, } from "ext:deno_node/internal/validators.mjs"; -import { previewEntries } from "ext:deno_node/internal_binding/util.ts"; import { Buffer } from "node:buffer"; const { isBuffer } = Buffer; import { @@ -500,7 +502,7 @@ const consoleMethods = { let isKeyValue = false; let i = 0; if (mapIter) { - const res = previewEntries(tabularData, true); + const res = ops.op_preview_entries(tabularData, true); tabularData = res[0]; isKeyValue = res[1]; } @@ -535,7 +537,7 @@ const consoleMethods = { const setIter = isSetIterator(tabularData); if (setIter) { - tabularData = previewEntries(tabularData); + tabularData = ops.op_preview_entries(tabularData, false); } const setlike = setIter || mapIter || isSet(tabularData); diff --git a/ext/node/polyfills/internal_binding/types.ts b/ext/node/polyfills/internal_binding/types.ts index fe697f194e..1f0528b2f5 100644 --- a/ext/node/polyfills/internal_binding/types.ts +++ b/ext/node/polyfills/internal_binding/types.ts @@ -27,9 +27,6 @@ const { core } = globalThis.__bootstrap; const { ops } = core; -// https://tc39.es/ecma262/#sec-object.prototype.tostring -const _toString = Object.prototype.toString; - // https://tc39.es/ecma262/#sec-bigint.prototype.valueof const _bigIntValueOf = BigInt.prototype.valueOf; @@ -94,11 +91,7 @@ export function isAnyArrayBuffer( } export function isArgumentsObject(value: unknown): value is IArguments { - return ( - isObjectLike(value) && - value[Symbol.toStringTag] === undefined && - _toString.call(value) === "[object Arguments]" - ); + return ops.op_is_arguments_object(value); } export function isArrayBuffer(value: unknown): value is ArrayBuffer { @@ -113,11 +106,7 @@ export function isArrayBuffer(value: unknown): value is ArrayBuffer { export function isAsyncFunction( value: unknown, ): value is (...args: unknown[]) => Promise { - return ( - typeof value === "function" && - // @ts-ignore: function is a kind of object - value[Symbol.toStringTag] === "AsyncFunction" - ); + return ops.op_is_async_function(value); } // deno-lint-ignore ban-types @@ -166,18 +155,11 @@ export function isDate(value: unknown): value is Date { export function isGeneratorFunction( value: unknown, ): value is GeneratorFunction { - return ( - typeof value === "function" && - // @ts-ignore: function is a kind of object - value[Symbol.toStringTag] === "GeneratorFunction" - ); + return ops.op_is_generator_function(value); } export function isGeneratorObject(value: unknown): value is Generator { - return ( - isObjectLike(value) && - value[Symbol.toStringTag] === "Generator" - ); + return ops.op_is_generator_object(value); } export function isMap(value: unknown): value is Map { @@ -192,27 +174,17 @@ export function isMap(value: unknown): value is Map { export function isMapIterator( value: unknown, ): value is IterableIterator<[unknown, unknown]> { - return ( - isObjectLike(value) && - value[Symbol.toStringTag] === "Map Iterator" - ); + return ops.op_is_map_iterator(value); } export function isModuleNamespaceObject( value: unknown, ): value is Record { - return ( - isObjectLike(value) && - value[Symbol.toStringTag] === "Module" - ); + return ops.op_is_module_namespace_object(value); } export function isNativeError(value: unknown): value is Error { - return ( - isObjectLike(value) && - value[Symbol.toStringTag] === undefined && - _toString.call(value) === "[object Error]" - ); + return ops.op_is_native_error(value); } // deno-lint-ignore ban-types @@ -243,10 +215,7 @@ export function isBigIntObject(value: unknown): value is bigint { } export function isPromise(value: unknown): value is Promise { - return ( - isObjectLike(value) && - value[Symbol.toStringTag] === "Promise" - ); + return ops.op_is_promise(value); } export function isProxy( @@ -256,11 +225,7 @@ export function isProxy( } export function isRegExp(value: unknown): value is RegExp { - return ( - isObjectLike(value) && - value[Symbol.toStringTag] === undefined && - _toString.call(value) === "[object RegExp]" - ); + return ops.op_is_reg_exp(value); } export function isSet(value: unknown): value is Set { @@ -275,10 +240,7 @@ export function isSet(value: unknown): value is Set { export function isSetIterator( value: unknown, ): value is IterableIterator { - return ( - isObjectLike(value) && - value[Symbol.toStringTag] === "Set Iterator" - ); + return ops.op_is_set_iterator(value); } export function isSharedArrayBuffer( diff --git a/ext/node/polyfills/internal_binding/util.ts b/ext/node/polyfills/internal_binding/util.ts index 302b2d57ce..a90728564a 100644 --- a/ext/node/polyfills/internal_binding/util.ts +++ b/ext/node/polyfills/internal_binding/util.ts @@ -129,5 +129,3 @@ export function getOwnNonIndexProperties( } return result; } - -export { previewEntries } from "ext:deno_console/01_console.js"; diff --git a/ext/node/polyfills/util.ts b/ext/node/polyfills/util.ts index a77167253f..c84131f68a 100644 --- a/ext/node/polyfills/util.ts +++ b/ext/node/polyfills/util.ts @@ -109,15 +109,11 @@ export function isFunction(value: unknown): boolean { return typeof value === "function"; } -/** @deprecated Use util.types.RegExp() instead. */ -export function isRegExp(value: unknown): boolean { - return types.isRegExp(value); -} +/** @deprecated Use util.types.isRegExp() instead. */ +export const isRegExp = types.isRegExp; /** @deprecated Use util.types.isDate() instead. */ -export function isDate(value: unknown): boolean { - return types.isDate(value); -} +export const isDate = types.isDate; /** @deprecated - use `value === null || (typeof value !== "object" && typeof value !== "function")` instead. */ export function isPrimitive(value: unknown): boolean {