mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-10 01:56:15 +08:00
Run Array.prototype's generic methods over array-like receivers
Array.prototype.push.call({length: 1, 0: "x"}, "y") and its siblings
answered "not supported yet": the checked-dynamic array dispatch only ran
the methods of a receiver that is a real array, though the algorithms are
generic in the language. PR #2505 implemented them inside scr_dyn_invoke.c,
which every program that links that unit then differed on (373 DIFFERS,
108 runtime-only programs), so it was withheld.
The algorithms now live in their own gated unit, scr_dyn_array_like.c,
reached through a distinct libCall (dyn.arrayLikeProtoCall) that only a
JavaScript source emits. A native array receiver, or a live typed-ref array,
delegates to the invoke unit's dispatch, so the array path keeps one
implementation and its exact bytes. Methods: concat, copyWithin, fill,
flat, flatMap, join, pop, push, reverse, shift, splice, toReversed,
toSorted, toSpliced, unshift, with.
Each algorithm starts with `? ToObject(this value)`, so a nullish
receiver is a TypeError before any length is read — the check the invoke
unit answers for its own receivers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
e19fa7a6b1
commit
6cea7a348f
@@ -2854,6 +2854,30 @@ function immediateObjectTagReceiver(
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Array.prototype methods whose `.<name>.call(receiver, ...)` form the
|
||||
* array-like unit runs generically when `receiver` is not a native array:
|
||||
* the rest of the prototype's methods besides the invoke unit's own set
|
||||
* (at, slice, indexOf, lastIndexOf, includes and the higher-order
|
||||
* callbacks). */
|
||||
const ARRAY_LIKE_BORROW_METHODS = new Set([
|
||||
"concat",
|
||||
"copyWithin",
|
||||
"fill",
|
||||
"flat",
|
||||
"flatMap",
|
||||
"join",
|
||||
"pop",
|
||||
"push",
|
||||
"reverse",
|
||||
"shift",
|
||||
"splice",
|
||||
"toReversed",
|
||||
"toSorted",
|
||||
"toSpliced",
|
||||
"unshift",
|
||||
"with",
|
||||
]);
|
||||
|
||||
const ARRAY_BORROW_METHODS = new Set([
|
||||
"at",
|
||||
"every",
|
||||
@@ -2890,7 +2914,9 @@ function lowerArrayPrototypeBorrowCall(
|
||||
const method = access.expression;
|
||||
if (
|
||||
method.questionDotToken ||
|
||||
!ARRAY_BORROW_METHODS.has(method.name.text) ||
|
||||
!(
|
||||
ARRAY_BORROW_METHODS.has(method.name.text) || ARRAY_LIKE_BORROW_METHODS.has(method.name.text)
|
||||
) ||
|
||||
!ts.isPropertyAccessExpression(method.expression) ||
|
||||
method.expression.questionDotToken ||
|
||||
method.expression.name.text !== "prototype" ||
|
||||
@@ -2905,7 +2931,12 @@ function lowerArrayPrototypeBorrowCall(
|
||||
const args = call.arguments.nodes.slice(1).map((arg) => lowerer.lowerExprExpecting(arg, DYN));
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "dyn.arrayProtoCall",
|
||||
// The array-like unit runs an extended method's algorithm over a
|
||||
// receiver that is not a native array and delegates the rest to the
|
||||
// invoke unit's dispatch.
|
||||
fn: ARRAY_LIKE_BORROW_METHODS.has(method.name.text)
|
||||
? "dyn.arrayLikeProtoCall"
|
||||
: "dyn.arrayProtoCall",
|
||||
args: [
|
||||
receiver,
|
||||
{ kind: "strLit", value: method.name.text, type: STRING, loc },
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/js-array-proto-array-likes.js"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// Array.prototype's methods are generic: they run over any object with a
|
||||
// length, reading and writing ordinary properties. A call on such an
|
||||
// object used to answer "not supported yet" for every method other than
|
||||
// at/slice/indexOf/includes and the callback methods.
|
||||
const resolved = [];
|
||||
const report = (label, value) => resolved.push(label + '=' + value);
|
||||
|
||||
const box = { length: 3, 0: 'a', 1: 'b', 2: 'c' };
|
||||
report('join', Array.prototype.join.call(box, '-'));
|
||||
report('push', Array.prototype.push.call(box, 'd'));
|
||||
report('after-push', Array.prototype.join.call(box, ''));
|
||||
report('pop', Array.prototype.pop.call(box));
|
||||
report('length', box.length);
|
||||
report('shift', Array.prototype.shift.call(box));
|
||||
report('unshift', Array.prototype.unshift.call(box, 'x', 'y'));
|
||||
report('after-unshift', Array.prototype.join.call(box, ','));
|
||||
report('reverse', Array.prototype.join.call(Array.prototype.reverse.call(box), '/'));
|
||||
report('concat', Array.prototype.concat.call(box, ['z']).join('+'));
|
||||
report('slice', Array.prototype.slice.call(box).join('|'));
|
||||
|
||||
const filler = { length: 4, 0: 1, 1: 2, 2: 3, 3: 4 };
|
||||
report('fill', Array.prototype.join.call(Array.prototype.fill.call(filler, 0, 1, 3), ''));
|
||||
report('copyWithin', Array.prototype.join.call(Array.prototype.copyWithin.call(filler, 0, 2), ''));
|
||||
|
||||
const spliced = { length: 4, 0: 'a', 1: 'b', 2: 'c', 3: 'd' };
|
||||
report('splice', Array.prototype.splice.call(spliced, 1, 2, 'X').join(''));
|
||||
report('after-splice', Array.prototype.join.call(spliced, ''));
|
||||
report('splice-none', Array.prototype.splice.call(spliced).length);
|
||||
|
||||
const ordered = { length: 3, 0: 3, 1: 1, 2: 2 };
|
||||
report('toSorted', Array.prototype.toSorted.call(ordered).join(''));
|
||||
report('toReversed', Array.prototype.toReversed.call(ordered).join(''));
|
||||
report('toSpliced', Array.prototype.toSpliced.call(ordered, 1, 1).join(''));
|
||||
report('with', Array.prototype.with.call(ordered, 0, 9).join(''));
|
||||
report('with-negative', Array.prototype.with.call(ordered, -1, 9).join(''));
|
||||
report('unsorted', Array.prototype.join.call(ordered, ''));
|
||||
|
||||
const nested = { length: 3, 0: [1, 2], 1: 3, 2: [4, [5]] };
|
||||
report('flat', Array.prototype.flat.call(nested, 1).join(''));
|
||||
report('flat-deep', Array.prototype.flat.call(nested, 2).join(''));
|
||||
report('flatMap', Array.prototype.flatMap.call(nested, (value) => [value, value]).length);
|
||||
|
||||
// Holes and absent indexes stay holes.
|
||||
const sparse = { length: 3, 0: 'a', 2: 'c' };
|
||||
const copied = Array.prototype.toReversed.call(sparse);
|
||||
report('holes', '1' in copied ? 'present' : 'absent');
|
||||
report('holes-join', copied.join(''));
|
||||
|
||||
// Writes beyond the receiver's length extend it, as the length assignment
|
||||
// at the end of push/splice does.
|
||||
const extended = { length: 1, 0: 'only' };
|
||||
Array.prototype.push.call(extended, 'next');
|
||||
report('extended', JSON.stringify(extended));
|
||||
|
||||
// Every one of the algorithms starts with `? ToObject(this value)`, so a
|
||||
// nullish receiver is a TypeError before any length is read.
|
||||
const attempt = (label, run) => {
|
||||
try {
|
||||
report(label, 'no-throw ' + JSON.stringify(run()));
|
||||
} catch (error) {
|
||||
report(label, error.constructor.name + ': ' + error.message);
|
||||
}
|
||||
};
|
||||
attempt('null-join', () => Array.prototype.join.call(null));
|
||||
attempt('undefined-push', () => Array.prototype.push.call(undefined, 1));
|
||||
attempt('null-copyWithin', () => Array.prototype.copyWithin.call(null, 0, 0));
|
||||
attempt('undefined-fill', () => Array.prototype.fill.call(undefined, 0));
|
||||
attempt('null-toReversed', () => Array.prototype.toReversed.call(null));
|
||||
attempt('undefined-with', () => Array.prototype.with.call(undefined, 0, 1));
|
||||
attempt('null-splice', () => Array.prototype.splice.call(null, 0, 1));
|
||||
|
||||
console.log(resolved.join('\n'));
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 3300, "san": 5400 }
|
||||
Reference in New Issue
Block a user