mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-10 01:56:15 +08:00
concat read a receiver's length before appending it, flatMap called its callback without thisArg, and with, toReversed, toSorted and toSpliced checked indexes with HasProperty, which a proxy's has trap or a getter observes. Writes to a string receiver were discarded, though a String wrapper's indexes below its length and its length are read-only. - concat appends a receiver that is not an array as its first element without reading `length`; the array dispatch's concat spreads the arguments. - flatMap calls the callback with thisArg as `this`. - with, toReversed, toSorted and toSpliced read every index with Get alone and define it on the result, so a missing index becomes undefined. toReversed reads from the last index down, with never reads the index it replaces, and toSpliced without arguments returns a copy. - On a string receiver, a write to an index below the string's length or to `length` throws "Cannot assign to read only property '<key>' of object '[object String]'", and deleting such an index throws "Cannot delete property '<index>' of [object String]", as in Node. Writes to number, boolean, bigint and symbol receivers are still discarded with their wrapper. - fill, copyWithin and reverse return their receiver, which for a primitive is its wrapper object, and concat's result starts with it. A compiled program has no wrapper objects, so these calls on a primitive throw the "not supported yet" Error. - An array receiver held as an untyped value ran these methods through scr_dyn_array_proto_call, which implements none of them, and threw "not supported yet". It now runs them through the array dispatch.
153 lines
7.6 KiB
JavaScript
153 lines
7.6 KiB
JavaScript
// 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);
|
|
|
|
// toReversed defines every index on its result, so a hole becomes undefined.
|
|
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));
|
|
|
|
// A proxy over `target` that logs its string-keyed reads and its has checks.
|
|
const traced = (target) => {
|
|
const log = [];
|
|
const proxy = new Proxy(target, {
|
|
get(object, key, receiver) {
|
|
if (typeof key === 'string') log.push('get:' + key);
|
|
return Reflect.get(object, key, receiver);
|
|
},
|
|
has(object, key) {
|
|
log.push('has:' + String(key));
|
|
return Reflect.has(object, key);
|
|
},
|
|
});
|
|
return { proxy, log };
|
|
};
|
|
// concat appends a receiver that is not spreadable as one value, without
|
|
// reading its length.
|
|
const unread = traced({ length: 2, 0: 'a', 1: 'b' });
|
|
const joined = Array.prototype.concat.call(unread.proxy, [1]);
|
|
report('concat-length', joined.length);
|
|
report('concat-reads', unread.log.join(','));
|
|
|
|
// flatMap calls the callback with thisArg as `this`.
|
|
const tagged = { length: 2, 0: 'a', 1: 'b' };
|
|
report('flatMap-this', Array.prototype.flatMap.call(tagged, function (value) {
|
|
return [value, this.tag];
|
|
}, { tag: 't' }).join(''));
|
|
|
|
// A string's ToObject wrapper has read-only indexes below its length and a
|
|
// read-only length, so the mutating methods throw on them.
|
|
attempt('string-push', () => Array.prototype.push.call('ab', 'c'));
|
|
attempt('string-pop', () => Array.prototype.pop.call('ab'));
|
|
attempt('string-pop-empty', () => Array.prototype.pop.call(''));
|
|
attempt('string-shift', () => Array.prototype.shift.call('ab'));
|
|
attempt('string-unshift', () => Array.prototype.unshift.call('ab', 'x'));
|
|
attempt('string-unshift-none', () => Array.prototype.unshift.call('ab'));
|
|
attempt('string-reverse', () => Array.prototype.reverse.call('ab'));
|
|
attempt('string-fill', () => Array.prototype.fill.call('ab', 'x'));
|
|
attempt('string-copyWithin', () => Array.prototype.copyWithin.call('abc', 0, 1));
|
|
attempt('string-splice', () => Array.prototype.splice.call('abc', 1, 1));
|
|
attempt('string-splice-insert', () => Array.prototype.splice.call('', 0, 0, 'x'));
|
|
attempt('string-splice-none', () => Array.prototype.splice.call('abc'));
|
|
// Other primitives' wrappers carry no indexes and a writable length.
|
|
attempt('number-push', () => Array.prototype.push.call(5, 'x'));
|
|
attempt('boolean-pop', () => Array.prototype.pop.call(true));
|
|
|
|
// toReversed reads with Get alone, from the last index down.
|
|
const backwards = traced({ length: 3, 0: 'a', 2: 'c' });
|
|
report('toReversed', JSON.stringify(Array.prototype.toReversed.call(backwards.proxy)));
|
|
report('toReversed-reads', backwards.log.join(','));
|
|
|
|
// with never reads the replaced index, and reads every other one with Get.
|
|
const replaced = traced({ length: 3, 0: 'a', 2: 'c' });
|
|
report('with', JSON.stringify(Array.prototype.with.call(replaced.proxy, 0, 'z')));
|
|
report('with-reads', replaced.log.join(','));
|
|
|
|
// toSorted reads every index with Get, holes included.
|
|
const sorted = traced({ length: 3, 0: 'b', 2: 'a' });
|
|
report('toSorted', JSON.stringify(Array.prototype.toSorted.call(sorted.proxy)));
|
|
report('toSorted-reads', sorted.log.join(','));
|
|
|
|
// toSpliced without arguments copies the receiver; a missing index becomes
|
|
// an own undefined, read with Get alone.
|
|
const copiedOut = traced({ length: 3, 0: 'a', 2: 'c' });
|
|
const copy = Array.prototype.toSpliced.call(copiedOut.proxy);
|
|
report('toSpliced-none', JSON.stringify(copy) + ' ' + ('1' in copy));
|
|
report('toSpliced-none-reads', copiedOut.log.join(','));
|
|
const skipped = traced({ length: 4, 0: 'a', 3: 'd' });
|
|
report('toSpliced-holes', JSON.stringify(Array.prototype.toSpliced.call(skipped.proxy, 1, 1, 'X')));
|
|
report('toSpliced-reads', skipped.log.join(','));
|
|
|
|
// An array receiver held as an untyped value runs the array methods.
|
|
const either = (useArray) => (useArray ? ['p', 'q'] : { length: 0 });
|
|
const list = either(resolved.length > 0);
|
|
report('array-push', Array.prototype.push.call(list, 'r'));
|
|
report('array-reverse', Array.prototype.join.call(Array.prototype.reverse.call(list), ''));
|
|
report('array-concat', Array.prototype.concat.call(list, ['s'], 't').join(''));
|
|
report('array-toSpliced', Array.prototype.toSpliced.call(list, 0, 1).join(''));
|
|
report('array-with', Array.prototype.with.call(list, 0, 'z').join(''));
|
|
|
|
console.log(resolved.join('\n'));
|