Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

errors: improve performance of determineSpecificType #49696

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions benchmark/error/determine-specific-type.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
'use strict';

const common = require('../common');

const bench = common.createBenchmark(main, {
n: [1e6],
v: [
'() => 1n',
'() => true',
'() => false',
'() => 2',
'() => +0',
'() => -0',
'() => NaN',
'() => Infinity',
'() => ""',
'() => "\'"',
'() => Symbol("foo")',
'() => function foo() {}',
'() => null',
'() => undefined',
'() => new Array()',
'() => new BigInt64Array()',
'() => new BigUint64Array()',
'() => new Int8Array()',
'() => new Int16Array()',
'() => new Int32Array()',
'() => new Float32Array()',
'() => new Float64Array()',
'() => new Uint8Array()',
'() => new Uint8ClampedArray()',
'() => new Uint16Array()',
'() => new Uint32Array()',
'() => new Date()',
'() => new Map()',
'() => new WeakMap()',
'() => new Object()',
'() => Promise.resolve("foo")',
'() => new Set()',
'() => new WeakSet()',
'() => ({ __proto__: null })',
],
}, {
flags: ['--expose-internals'],
});

function main({ n, v }) {
const {
determineSpecificType,
} = require('internal/errors');

const value = eval(v)();

bench.start();
for (let i = 0; i < n; ++i)
determineSpecificType(value);
bench.end(n);
}
61 changes: 46 additions & 15 deletions lib/internal/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const {
String,
StringPrototypeEndsWith,
StringPrototypeIncludes,
StringPrototypeIndexOf,
StringPrototypeSlice,
StringPrototypeSplit,
StringPrototypeStartsWith,
Expand Down Expand Up @@ -874,23 +875,53 @@ const genericNodeError = hideStackFrames(function genericNodeError(message, erro
* @returns {string}
*/
function determineSpecificType(value) {
if (value == null) {
return '' + value;
if (value === null) {
return 'null';
} else if (value === undefined) {
return 'undefined';
}
if (typeof value === 'function' && value.name) {
return `function ${value.name}`;
}
if (typeof value === 'object') {
if (value.constructor?.name) {
return `an instance of ${value.constructor.name}`;
}
return `${lazyInternalUtilInspect().inspect(value, { depth: -1 })}`;
}
let inspected = lazyInternalUtilInspect()
.inspect(value, { colors: false });
if (inspected.length > 28) { inspected = `${StringPrototypeSlice(inspected, 0, 25)}...`; }

return `type ${typeof value} (${inspected})`;
const type = typeof value;

switch (type) {
case 'bigint':
return `type bigint (${value}n)`;
case 'number':
if (value === 0) {
return 1 / value === -Infinity ? 'type number (-0)' : 'type number (0)';
} else if (value !== value) { // eslint-disable-line no-self-compare
return 'type number (NaN)';
} else if (value === Infinity) {
return 'type number (Infinity)';
} else if (value === -Infinity) {
return 'type number (-Infinity)';
}
return `type number (${value})`;
case 'boolean':
return value ? 'type boolean (true)' : 'type boolean (false)';
case 'symbol':
return `type symbol (${String(value)})`;
case 'function':
return `function ${value.name}`;
case 'object':
if (value.constructor && 'name' in value.constructor) {
return `an instance of ${value.constructor.name}`;
}
return `${lazyInternalUtilInspect().inspect(value, { depth: -1 })}`;
case 'string':
value.length > 28 && (value = `${StringPrototypeSlice(value, 0, 25)}...`);
if (StringPrototypeIndexOf(value, "'") === -1) {
return `type string ('${value}')`;
}
return `type string (${JSONStringify(value)})`;
default:
value = lazyInternalUtilInspect().inspect(value, { colors: false });
if (value.length > 28) {
value = `${StringPrototypeSlice(value, 0, 25)}...`;
}

return `type ${type} (${value})`;
}
}

/**
Expand Down
2 changes: 1 addition & 1 deletion test/common/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -815,7 +815,7 @@ function invalidArgTypeHelper(input) {
if (input == null) {
return ` Received ${input}`;
}
if (typeof input === 'function' && input.name) {
if (typeof input === 'function') {
return ` Received function ${input.name}`;
}
if (typeof input === 'object') {
Expand Down
61 changes: 61 additions & 0 deletions test/parallel/test-error-value-type-detection.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ strictEqual(
'type bigint (1n)',
);

strictEqual(
determineSpecificType(true),
'type boolean (true)',
);
strictEqual(
determineSpecificType(false),
'type boolean (false)',
Expand Down Expand Up @@ -42,6 +46,27 @@ strictEqual(
"type string ('')",
);

strictEqual(
determineSpecificType(''),
"type string ('')",
);
strictEqual(
determineSpecificType("''"),
"type string (\"''\")",
);
strictEqual(
determineSpecificType('Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor'),
"type string ('Lorem ipsum dolor sit ame...')",
);
strictEqual(
determineSpecificType("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor'"),
"type string ('Lorem ipsum dolor sit ame...')",
);
strictEqual(
determineSpecificType("Lorem' ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor"),
"type string (\"Lorem' ipsum dolor sit am...\")",
);

strictEqual(
determineSpecificType(Symbol('foo')),
'type symbol (Symbol(foo))',
Expand All @@ -52,6 +77,38 @@ strictEqual(
'function foo',
);

const implicitlyNamed = function() {}; // eslint-disable-line func-style
strictEqual(
determineSpecificType(implicitlyNamed),
'function implicitlyNamed',
);
strictEqual(
determineSpecificType(() => {}),
'function ',
);
function noName() {}
delete noName.name;
strictEqual(
noName.name,
'',
);
strictEqual(
determineSpecificType(noName),
'function ',
);

function * generatorFn() {}
strictEqual(
determineSpecificType(generatorFn),
'function generatorFn',
);

async function asyncFn() {}
strictEqual(
determineSpecificType(asyncFn),
'function asyncFn',
);

strictEqual(
determineSpecificType(null),
'null',
Expand Down Expand Up @@ -134,6 +191,10 @@ strictEqual(
determineSpecificType({}),
'an instance of Object',
);
strictEqual(
determineSpecificType(new Object()),
'an instance of Object',
);

strictEqual(
determineSpecificType(Promise.resolve('foo')),
Expand Down
2 changes: 1 addition & 1 deletion test/parallel/test-fs-readfile-error.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ assert.throws(
{
code: 'ERR_INVALID_ARG_TYPE',
message: 'The "path" argument must be of type string or an instance of ' +
'Buffer or URL. Received type function ([Function (anonymous)])',
'Buffer or URL. Received function ',
name: 'TypeError'
}
);