mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-02 00:25:34 +08:00
Expand static Array flat, splice, and join lowering (#460)
* Expand static Array flattening, splice insertion, and join coercion * Refresh Array diagnostic and TypeScript 7 baselines
This commit is contained in:
@@ -1901,6 +1901,17 @@ function emitContainerExpr(
|
||||
const cnt = e.args[1] ? emitter.emitExpr(e.args[1]).name : "INFINITY";
|
||||
return emitter.newTemp(e.type, `scr_arr_splice(${r.name}, ${start.name}, ${cnt})`);
|
||||
}
|
||||
case "spliceInsert": {
|
||||
const start = emitter.emitExpr(e.args[0]!);
|
||||
const count = emitter.emitExpr(e.args[1]!);
|
||||
const items = emitter.emitExpr(e.args[2]!);
|
||||
return emitter.newTemp(e.type, `scr_arr_splice_insert(${r.name}, ${start.name}, ${count.name}, ${items.name})`);
|
||||
}
|
||||
case "flatCopy":
|
||||
case "flatOne": {
|
||||
const out = emitter.emitExpr(e.args[0]!);
|
||||
return emitter.newTemp(e.type, `scr_arr_flat_copy(${r.name}, ${out.name}, ${method === "flatOne" ? "true" : "false"})`);
|
||||
}
|
||||
default: {
|
||||
const _exhaustive: never = method;
|
||||
void _exhaustive;
|
||||
|
||||
@@ -443,6 +443,23 @@ export function emitArrIntrinsic(host: LlvmEmitterContext, e: IrExpr & { kind: "
|
||||
B.line(`${t} = call ptr @scr_arr_splice(ptr ${r.name}, double ${start.name}, double ${cnt})`);
|
||||
return host.own({ name: t, type: e.type });
|
||||
}
|
||||
case "spliceInsert": {
|
||||
const start = host.emitExpr(e.args[0]!);
|
||||
const count = host.emitExpr(e.args[1]!);
|
||||
const items = host.emitExpr(e.args[2]!);
|
||||
host.declare(`declare ptr @scr_arr_splice_insert(ptr, double, double, ptr)`);
|
||||
const t = B.tmp();
|
||||
B.line(`${t} = call ptr @scr_arr_splice_insert(ptr ${r.name}, double ${start.name}, double ${count.name}, ptr ${items.name})`);
|
||||
return host.own({ name: t, type: e.type });
|
||||
}
|
||||
case "flatCopy":
|
||||
case "flatOne": {
|
||||
const out = host.emitExpr(e.args[0]!);
|
||||
host.declare(`declare ptr @scr_arr_flat_copy(ptr, ptr, i1)`);
|
||||
const t = B.tmp();
|
||||
B.line(`${t} = call ptr @scr_arr_flat_copy(ptr ${r.name}, ptr ${out.name}, i1 ${method === "flatOne" ? 1 : 0})`);
|
||||
return host.own({ name: t, type: e.type });
|
||||
}
|
||||
default: {
|
||||
const _exhaustive: never = method;
|
||||
void _exhaustive;
|
||||
|
||||
@@ -16,8 +16,8 @@ import { buildArraySortFn } from "./lower-array-sort.js";
|
||||
import { arrayIndexPresent, arrayValueRead, arrayValueStore, arrayValueType, currentArrayIndexPresent } from "./array-values.js";
|
||||
import { typeKey } from "../type-mapper.js";
|
||||
import { WidthLift } from "./lowerer.js";
|
||||
import { boolLit, countedFor, numLit, varRef } from "../../ir/build.js";
|
||||
import { lowerPositionArgument, positionNumber } from "./optional-arguments.js";
|
||||
import { boolLit, countedFor, numLit, strLit, varRef } from "../../ir/build.js";
|
||||
import { defaultAfterUndefined, lowerPositionArgument, lowerStaticallyUndefinedArgument, positionNumber } from "./optional-arguments.js";
|
||||
import { lowerArrayCopyWithin, lowerArrayFill } from "./array-indexed-mutation.js";
|
||||
|
||||
function primitivePositionType(lowerer: Lowerer, type: IrType): boolean {
|
||||
@@ -85,6 +85,72 @@ function lowerArrayValueItems(
|
||||
return { kind: "seqExpr", stmts: body, result: outRef, type: arrType, loc };
|
||||
}
|
||||
|
||||
function lowerArraySpreadItems(
|
||||
lowerer: Lowerer,
|
||||
nodes: readonly ts.Expression[],
|
||||
elem: IrType,
|
||||
arrType: IrType & { kind: "array" },
|
||||
loc: SrcLoc,
|
||||
): IrExpr {
|
||||
const out = lowerer.declareHiddenLocal("%arrayItems", arrType);
|
||||
const outRef = varRef(out.id, arrType, loc);
|
||||
const length = (): IrExpr => ({ kind: "arrIntrinsic", method: "length", receiver: outRef, args: [], type: F64, loc });
|
||||
const body: IrStmt[] = [
|
||||
{ kind: "varDecl", localId: out.id, init: { kind: "arrayLit", elems: [], type: arrType, loc }, loc },
|
||||
];
|
||||
for (const node of nodes) {
|
||||
if (ts.isSpreadElement(node)) {
|
||||
const source = lowerer.lowerExpr(node.expression);
|
||||
if (!typeEquals(source.type, arrType)) {
|
||||
lowerer.noLowering(`Array insertion spread from '${lowerer.fmt(source.type)}'`, node);
|
||||
}
|
||||
body.push({
|
||||
kind: "exprStmt",
|
||||
expr: { kind: "arrIntrinsic", method: "pushSpread", receiver: outRef, args: [source], type: F64, loc },
|
||||
loc,
|
||||
});
|
||||
} else {
|
||||
const value = lowerer.lowerExpr(node);
|
||||
const stored = lowerer.runtimeOptionalWidening(value.type, elem) !== null
|
||||
? value : lowerer.coerceInto(node, value, elem);
|
||||
body.push(arrayValueStore(lowerer, outRef, length(), stored, elem, loc));
|
||||
}
|
||||
}
|
||||
return { kind: "seqExpr", stmts: body, result: outRef, type: arrType, loc };
|
||||
}
|
||||
|
||||
function lowerArrayJoinSeparator(lowerer: Lowerer, node: ts.Expression | undefined, loc: SrcLoc): IrExpr {
|
||||
const comma = strLit(",", loc);
|
||||
if (!node) return comma;
|
||||
const undefinedValue = lowerStaticallyUndefinedArgument(lowerer, node);
|
||||
if (undefinedValue) return defaultAfterUndefined(undefinedValue, comma);
|
||||
const value = lowerer.lowerExpr(node);
|
||||
if (value.type.kind === "nullT") return defaultAfterUndefined(value, strLit("null", loc));
|
||||
if (value.type.kind === "union") {
|
||||
const undefinedTag = lowerer.armTag(value.type.unionId, UNDEFINED_T);
|
||||
if (undefinedTag >= 0) {
|
||||
const local = lowerer.declareHiddenLocal("%joinSeparator", value.type);
|
||||
const ref = varRef(local.id, value.type, loc);
|
||||
return {
|
||||
kind: "seqExpr",
|
||||
stmts: [{ kind: "varDecl", localId: local.id, init: value, loc }],
|
||||
result: {
|
||||
kind: "ternary",
|
||||
cond: { kind: "unionIsTag", unionId: value.type.unionId, tag: undefinedTag, value: ref, negated: false, type: BOOL, loc },
|
||||
then: comma,
|
||||
else_: lowerer.ensureString(ref, node),
|
||||
type: STRING, loc,
|
||||
},
|
||||
type: STRING, loc,
|
||||
};
|
||||
}
|
||||
}
|
||||
if (value.type.kind === "dyn" || value.type.kind === "jsval") {
|
||||
lowerer.noLowering(".join separator with a runtime-dependent undefined value", node);
|
||||
}
|
||||
return lowerer.ensureString(value, node);
|
||||
}
|
||||
|
||||
/** Callback-driven array producers bypass mapType's ordinary T[] mapping:
|
||||
* the callback is lowered first, then the helper's result array is built
|
||||
* directly from its IR return type. Fence element kinds ScrArr cannot hold
|
||||
@@ -343,9 +409,6 @@ function fenceProducedArrayElem(lowerer: Lowerer, node: ts.Node, producer: strin
|
||||
};
|
||||
}
|
||||
if (name === "toSpliced") {
|
||||
if (call.arguments.some(ts.isSpreadElement)) {
|
||||
lowerer.unsupported("SC1090", call, "spread arguments to Array.toSpliced");
|
||||
}
|
||||
const receiver = lowerer.lowerExpr(access.expression);
|
||||
const start = lowerArrayPosition(lowerer, call.arguments[0], numLit(0, loc), "array toSpliced start");
|
||||
const deleteCount = lowerArrayPosition(
|
||||
@@ -355,12 +418,15 @@ function fenceProducedArrayElem(lowerer: Lowerer, node: ts.Node, producer: strin
|
||||
"array toSpliced deleteCount",
|
||||
);
|
||||
const itemNodes = call.arguments.slice(2);
|
||||
const itemProbes = itemNodes.map((arg) => tryLowerExpression(lowerer, arg));
|
||||
const hasSpread = itemNodes.some(ts.isSpreadElement);
|
||||
const itemProbes = hasSpread ? [] : itemNodes.map((arg) => tryLowerExpression(lowerer, arg));
|
||||
const statefulItems = itemProbes.some((probe) =>
|
||||
probe !== null && lowerer.runtimeOptionalWidening(probe.type, elem) !== null);
|
||||
const items: IrExpr = statefulItems
|
||||
? lowerArrayValueItems(lowerer, itemNodes.map((arg) => lowerer.lowerExpr(arg)), elem, receiverIr, loc)
|
||||
: {
|
||||
const items: IrExpr = hasSpread
|
||||
? lowerArraySpreadItems(lowerer, itemNodes, elem, receiverIr, loc)
|
||||
: statefulItems
|
||||
? lowerArrayValueItems(lowerer, itemNodes.map((arg) => lowerer.lowerExpr(arg)), elem, receiverIr, loc)
|
||||
: {
|
||||
kind: "arrayLit",
|
||||
elems: itemNodes.map((arg) => lowerer.coerceInto(arg, lowerer.lowerExpr(arg), elem)),
|
||||
type: receiverIr,
|
||||
@@ -375,18 +441,18 @@ function fenceProducedArrayElem(lowerer: Lowerer, node: ts.Node, producer: strin
|
||||
loc,
|
||||
};
|
||||
}
|
||||
if (name === "flat") return lowerArrayFlatCall(lowerer, call, access, receiverIr);
|
||||
|
||||
// The lib declares wider call forms than the lowered surface —
|
||||
// join's separator is optional, and the predicate/mapping HOFs take
|
||||
// a thisArg. Each unlowered form is fenced
|
||||
// The lib declares wider call forms than the lowered surface — the
|
||||
// predicate/mapping HOFs take a thisArg. Each unlowered form is fenced
|
||||
// per site (SC2020), never silently truncated to the supported
|
||||
// arguments. push/unshift lower every declared form (variadic, 0 args
|
||||
// included — Node returns the unchanged length); reduce/reduceRight
|
||||
// lower both declared forms (with and without an initial value).
|
||||
const arity = {
|
||||
push: [0, Number.MAX_SAFE_INTEGER], unshift: [0, Number.MAX_SAFE_INTEGER], pop: [0, 0], indexOf: [1, 2], lastIndexOf: [1, 2], includes: [0, 2], join: [1, 1],
|
||||
push: [0, Number.MAX_SAFE_INTEGER], unshift: [0, Number.MAX_SAFE_INTEGER], pop: [0, 0], indexOf: [1, 2], lastIndexOf: [1, 2], includes: [0, 2], join: [0, 1],
|
||||
concat: [0, Number.MAX_SAFE_INTEGER],
|
||||
slice: [0, 2], shift: [0, 0], splice: [0, 2], at: [0, 1],
|
||||
slice: [0, 2], shift: [0, 0], splice: [0, Number.MAX_SAFE_INTEGER], at: [0, 1],
|
||||
map: [1, 1], filter: [1, 1], forEach: [1, 1], find: [1, 1], findIndex: [1, 1], some: [1, 1],
|
||||
findLast: [1, 1], findLastIndex: [1, 1],
|
||||
every: [1, 1], flatMap: [1, 1], reduce: [1, 2], reduceRight: [1, 2],
|
||||
@@ -397,11 +463,7 @@ function fenceProducedArrayElem(lowerer: Lowerer, node: ts.Node, producer: strin
|
||||
];
|
||||
if (call.arguments.length < arity[0]! || call.arguments.length > arity[1]!) {
|
||||
const hint =
|
||||
name === "splice"
|
||||
? "the removal forms lower — splice(start, deleteCount?); to insert, build a new array with slice and push"
|
||||
: name === "join"
|
||||
? 'pass the separator explicitly: join(",")'
|
||||
: name === "map" || name === "filter" || name === "forEach" ||
|
||||
name === "map" || name === "filter" || name === "forEach" ||
|
||||
name === "find" || name === "some" || name === "every" || name === "flatMap"
|
||||
? "the thisArg parameter has no lowering — use an arrow function"
|
||||
: undefined;
|
||||
@@ -578,11 +640,29 @@ function fenceProducedArrayElem(lowerer: Lowerer, node: ts.Node, producer: strin
|
||||
}
|
||||
if (name === "splice") {
|
||||
const receiver = lowerer.lowerExpr(access.expression);
|
||||
const args = [
|
||||
const args: IrExpr[] = [
|
||||
lowerArrayPosition(lowerer, call.arguments[0], numLit(0, loc), "array splice start"),
|
||||
lowerArrayPosition(lowerer, call.arguments[1], numLit(call.arguments.length === 1 ? Infinity : 0, loc), "array splice deleteCount"),
|
||||
];
|
||||
return { kind: "arrIntrinsic", method: "splice", receiver, args, type: receiverIr, loc };
|
||||
if (call.arguments.length <= 2) {
|
||||
return { kind: "arrIntrinsic", method: "splice", receiver, args, type: receiverIr, loc };
|
||||
}
|
||||
const itemNodes = call.arguments.slice(2);
|
||||
const hasSpread = itemNodes.some(ts.isSpreadElement);
|
||||
const itemProbes = hasSpread ? [] : itemNodes.map((arg) => tryLowerExpression(lowerer, arg));
|
||||
const statefulItems = itemProbes.some((probe) =>
|
||||
probe !== null && lowerer.runtimeOptionalWidening(probe.type, elem) !== null);
|
||||
const items: IrExpr = hasSpread
|
||||
? lowerArraySpreadItems(lowerer, itemNodes, elem, receiverIr, loc)
|
||||
: statefulItems
|
||||
? lowerArrayValueItems(lowerer, itemNodes.map((arg) => lowerer.lowerExpr(arg)), elem, receiverIr, loc)
|
||||
: {
|
||||
kind: "arrayLit",
|
||||
elems: itemNodes.map((arg) => lowerer.coerceInto(arg, lowerer.lowerExpr(arg), elem)),
|
||||
type: receiverIr,
|
||||
loc,
|
||||
};
|
||||
return { kind: "arrIntrinsic", method: "spliceInsert", receiver, args: [...args, items], type: receiverIr, loc };
|
||||
}
|
||||
if (name === "shift") {
|
||||
// JS shift exactly: undefined on an empty array, else the first
|
||||
@@ -626,7 +706,7 @@ function fenceProducedArrayElem(lowerer: Lowerer, node: ts.Node, producer: strin
|
||||
);
|
||||
}
|
||||
const receiver = lowerer.lowerExpr(access.expression);
|
||||
const sep = lowerer.lowerExpr(call.arguments[0]!);
|
||||
const sep = lowerArrayJoinSeparator(lowerer, call.arguments[0], loc);
|
||||
return { kind: "arrIntrinsic", method: "join", receiver, args: [sep], type: STRING, loc };
|
||||
}
|
||||
if (name === "map" || name === "filter" || name === "forEach") {
|
||||
@@ -644,6 +724,82 @@ function fenceProducedArrayElem(lowerer: Lowerer, node: ts.Node, producer: strin
|
||||
return lowerArrayReduceCall(lowerer, call, access, name as "reduce" | "reduceRight", elem);
|
||||
}
|
||||
|
||||
function literalFlatDepth(value: IrExpr): number | null {
|
||||
if (value.kind === "numLit") return value.value;
|
||||
if (value.kind === "strLit") return Number(value.value);
|
||||
if (value.kind === "boolLit") return value.value ? 1 : 0;
|
||||
if (value.kind === "unitLit") return value.unit === "undefined" ? 1 : 0;
|
||||
if (value.kind === "unary" && value.op === "-") {
|
||||
const operand = literalFlatDepth(value.operand);
|
||||
return operand === null ? null : -operand;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function lowerArrayFlatCall(
|
||||
lowerer: Lowerer,
|
||||
call: ts.CallExpression,
|
||||
access: ts.PropertyAccessExpression,
|
||||
receiverType: IrType & { kind: "array" },
|
||||
): IrExpr {
|
||||
const loc = locOf(call);
|
||||
if (call.arguments.length > 1 || call.arguments.some(ts.isSpreadElement)) {
|
||||
lowerer.noLowering(`.flat with ${call.arguments.length} arguments`, call);
|
||||
}
|
||||
const elemCouldNest = (elem: IrType): boolean =>
|
||||
elem.kind === "array" || elem.kind === "jsval" || elem.kind === "dyn" ||
|
||||
(elem.kind === "union" && (lowerer.unions.get(elem.unionId)?.arms.some((arm) =>
|
||||
arm.kind === "array" || arm.kind === "jsval" || arm.kind === "dyn") ?? true));
|
||||
if (!elemCouldNest(receiverType.elem) && call.arguments[0]) {
|
||||
const receiver = lowerer.lowerExpr(access.expression);
|
||||
const depth = lowerArrayPosition(lowerer, call.arguments[0], numLit(1, loc), "array flat depth");
|
||||
const local = lowerer.declareHiddenLocal("%flatReceiver", receiverType);
|
||||
const ref = varRef(local.id, receiverType, loc);
|
||||
return {
|
||||
kind: "seqExpr",
|
||||
stmts: [
|
||||
{ kind: "varDecl", localId: local.id, init: receiver, loc },
|
||||
{ kind: "exprStmt", expr: depth, loc },
|
||||
],
|
||||
result: {
|
||||
kind: "arrIntrinsic", method: "flatCopy", receiver: ref,
|
||||
args: [{ kind: "arrayLit", elems: [], type: receiverType, loc }],
|
||||
type: receiverType, loc,
|
||||
},
|
||||
type: receiverType, loc,
|
||||
};
|
||||
}
|
||||
const depth = call.arguments[0] ? literalFlatDepth(lowerer.lowerExpr(call.arguments[0])) : 1;
|
||||
if (depth === null) lowerer.noLowering(".flat with a nonconstant depth", call);
|
||||
let remaining = Number.isNaN(depth) || depth <= 0 ? 0 : Math.trunc(depth);
|
||||
let result: IrExpr = lowerer.lowerExpr(access.expression);
|
||||
let currentType = receiverType;
|
||||
let flattened = false;
|
||||
while (remaining > 0) {
|
||||
const elem = currentType.elem;
|
||||
if (elem.kind !== "array" && elemCouldNest(elem)) {
|
||||
lowerer.noLowering(".flat over elements with runtime-dependent array shape", call);
|
||||
}
|
||||
if (elem.kind !== "array") break;
|
||||
result = {
|
||||
kind: "arrIntrinsic", method: "flatOne", receiver: result,
|
||||
args: [{ kind: "arrayLit", elems: [], type: elem, loc }],
|
||||
type: elem, loc,
|
||||
};
|
||||
currentType = elem;
|
||||
remaining--;
|
||||
flattened = true;
|
||||
}
|
||||
if (!flattened) {
|
||||
result = {
|
||||
kind: "arrIntrinsic", method: "flatCopy", receiver: result,
|
||||
args: [{ kind: "arrayLit", elems: [], type: currentType, loc }],
|
||||
type: currentType, loc,
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** `indexOf` and `includes` normally use the runtime's typed search helpers,
|
||||
* but an indexed array read is a `T | undefined` value. Keep that union
|
||||
* through a small lowered loop so the needle's missing state is observable:
|
||||
|
||||
@@ -314,6 +314,7 @@ export const ARRAY_METHODS = new Set([
|
||||
"every",
|
||||
"at",
|
||||
"flatMap",
|
||||
"flat",
|
||||
"reduce",
|
||||
"reduceRight",
|
||||
"indexOf",
|
||||
|
||||
@@ -1592,6 +1592,12 @@ export type IrArrIntrinsicMethod =
|
||||
| "slice"
|
||||
| "shift"
|
||||
| "splice"
|
||||
/** One dense copy pass, or one level of flattening into an empty typed
|
||||
* result array. The supplied result is borrowed and returned retained. */
|
||||
| "flatCopy"
|
||||
| "flatOne"
|
||||
/** Mutating splice with evaluated insertion items; returns removed slots. */
|
||||
| "spliceInsert"
|
||||
| "reverse"
|
||||
/** ES2023 copying methods. `toSpliced` receives [start, deleteCount,
|
||||
* itemsArray], with omitted arguments completed by the frontend;
|
||||
|
||||
@@ -2798,12 +2798,22 @@ function validateFunction(
|
||||
? { argTypes: [F64], result: e.receiver.type }
|
||||
: e.method === "splice"
|
||||
? { argTypes: [F64, F64], result: e.receiver.type }
|
||||
: e.method === "spliceInsert"
|
||||
? { argTypes: [F64, F64, e.receiver.type], result: e.receiver.type }
|
||||
: e.method === "flatCopy" || e.method === "flatOne"
|
||||
? { argTypes: [e.type], result: e.type }
|
||||
: e.method === "shift"
|
||||
? { argTypes: [], result: e.type } // union-checked below
|
||||
: { argTypes: [], result: F64 }; // length
|
||||
if (e.method === "getNumber" && elem.kind !== "f64") {
|
||||
err(`arrIntrinsic getNumber requires f64 elements, got ${elem.kind}`, e.loc);
|
||||
}
|
||||
if (e.method === "flatCopy" && !typeEquals(e.type, e.receiver.type)) {
|
||||
err("arrIntrinsic flatCopy result must match its receiver", e.loc);
|
||||
}
|
||||
if (e.method === "flatOne" && (elem.kind !== "array" || !typeEquals(e.type, elem))) {
|
||||
err("arrIntrinsic flatOne result must match the nested array type", e.loc);
|
||||
}
|
||||
if (
|
||||
e.method === "join" &&
|
||||
elem.kind !== "f64" && elem.kind !== "string" && elem.kind !== "bool" &&
|
||||
|
||||
@@ -2164,6 +2164,12 @@
|
||||
"name": "Array.prototype.findLastIndex",
|
||||
"status": "static"
|
||||
},
|
||||
{
|
||||
"id": "stdlib.array.flat",
|
||||
"kind": "stdlib",
|
||||
"name": "Array.prototype.flat",
|
||||
"status": "static"
|
||||
},
|
||||
{
|
||||
"id": "stdlib.array.flatMap",
|
||||
"kind": "stdlib",
|
||||
|
||||
@@ -7087,12 +7087,30 @@
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/3049-array-flat-depth.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/3049-array-flat-depth.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/305-truthiness.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/305-truthiness.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/3050-array-splice-insert.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/3050-array-splice-insert.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/3051-array-join-default.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/3051-array-join-default.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/400-fib.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/400-fib.ts"
|
||||
|
||||
@@ -1142,6 +1142,54 @@ ScrArr *scr_arr_splice(ScrArr *a, double start, double deleteCount) {
|
||||
return out;
|
||||
}
|
||||
|
||||
/* Insert the evaluated arguments at the original start position. The first
|
||||
* splice moves removed slots out, and the second moves the remaining tail
|
||||
* out. Appending items follows array iteration (a hole becomes undefined);
|
||||
* appending the tail follows indexed copying (holes stay holes). */
|
||||
ScrArr *scr_arr_splice_insert(ScrArr *a, double start, double deleteCount,
|
||||
const ScrArr *items) {
|
||||
double len = (double)a->len;
|
||||
double s0 = isnan(start) ? 0 : trunc(start);
|
||||
if (s0 < 0) s0 += len;
|
||||
size_t from = s0 <= 0 ? 0 : s0 >= len ? a->len : (size_t)s0;
|
||||
double avail = len - (double)from;
|
||||
double d0 = isnan(deleteCount) ? 0 : trunc(deleteCount);
|
||||
size_t n = d0 <= 0 ? 0 : d0 >= avail ? (size_t)avail : (size_t)d0;
|
||||
if (items->len > SCR_ARR_MAX_LENGTH - (a->len - n)) scr_arr_oom();
|
||||
ScrArr *removed = scr_arr_splice(a, start, deleteCount);
|
||||
ScrArr *tail = scr_arr_splice(a, (double)from, INFINITY);
|
||||
scr_arr_push_spread(a, items);
|
||||
scr_arr_concat_copy(a, tail);
|
||||
scr_arr_release(tail);
|
||||
return removed;
|
||||
}
|
||||
|
||||
/* FlattenIntoArray for static arrays: a dense copy with depth zero, or one
|
||||
* level over array elements. The frontend supplies an empty result of the
|
||||
* correct element kind; inner holes are skipped and present undefined stays
|
||||
* present. This function borrows both inputs and returns a retained result. */
|
||||
ScrArr *scr_arr_flat_copy(const ScrArr *a, ScrArr *out, bool flatten) {
|
||||
for (size_t i = 0; i < a->len; i++) {
|
||||
uint64_t slot;
|
||||
uint8_t state = scr_arr_state_at(a, i, &slot);
|
||||
if (state == SCR_ARR_HOLE) continue;
|
||||
if (flatten && state == SCR_ARR_VALUE) {
|
||||
const ScrArr *inner = (const ScrArr *)scr_slot_to_ptr(slot);
|
||||
for (size_t j = 0; j < inner->len; j++) {
|
||||
if (scr_arr_state_at(inner, j, NULL) == SCR_ARR_HOLE) continue;
|
||||
if (out->len == SCR_ARR_MAX_LENGTH) scr_arr_oom();
|
||||
size_t at = out->len++;
|
||||
scr_arr_copy_index(out, at, inner, j);
|
||||
}
|
||||
} else {
|
||||
if (out->len == SCR_ARR_MAX_LENGTH) scr_arr_oom();
|
||||
size_t at = out->len++;
|
||||
scr_arr_copy_index(out, at, a, i);
|
||||
}
|
||||
}
|
||||
return scr_arr_retain(out);
|
||||
}
|
||||
|
||||
/* ── indexOf / includes ────────────────────────────────────────────────
|
||||
* indexOf uses JS strict equality (===): NaN never matches (NaN !== NaN),
|
||||
* -0 matches 0 (C == agrees on both). includes uses SameValueZero: the one
|
||||
|
||||
@@ -1087,6 +1087,9 @@ uint8_t scr_arr_shift_state(ScrArr *a, uint64_t *slot_out);
|
||||
* to the end). Returns the removed elements in order as a fresh +1 array,
|
||||
* ownership MOVED out of the receiver. Borrows a. */
|
||||
ScrArr *scr_arr_splice(ScrArr *a, double start, double deleteCount);
|
||||
ScrArr *scr_arr_splice_insert(ScrArr *a, double start, double deleteCount,
|
||||
const ScrArr *items);
|
||||
ScrArr *scr_arr_flat_copy(const ScrArr *a, ScrArr *out, bool flatten);
|
||||
|
||||
/* indexOf: first index whose element strictly equals (JS ===) the needle,
|
||||
* or -1. Per element kind: f64 by value (NaN never matches — NaN !== NaN;
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
const sparse = [1, , 3];
|
||||
const copied = sparse.flat(0);
|
||||
console.log(JSON.stringify(copied), copied.length, 1 in copied, 1 in sparse);
|
||||
|
||||
const rows = [[1, , 3], , [4, 5]] as number[][];
|
||||
const one = rows.flat();
|
||||
console.log(JSON.stringify(one), one.length, 1 in one);
|
||||
|
||||
const nested = [[[1], [2, , 3]], [[4]]];
|
||||
console.log(JSON.stringify(nested.flat()));
|
||||
console.log(JSON.stringify(nested.flat(2)));
|
||||
console.log(JSON.stringify(nested.flat(Infinity)));
|
||||
console.log(JSON.stringify(nested.flat(0)));
|
||||
console.log(JSON.stringify(nested.flat(-2)));
|
||||
|
||||
const first = { value: 1 };
|
||||
const second = { value: 2 };
|
||||
const records = [[first], [second]];
|
||||
const flattened = records.flat();
|
||||
console.log(flattened[0] === first, flattened[1] === second, records[0]![0] === first);
|
||||
|
||||
const words = [["a", "b"], ["c"]];
|
||||
console.log(words.flat().join("|"), words.flat(0).length);
|
||||
console.log(JSON.stringify([7, , 9].flat()), JSON.stringify([7, , 9]));
|
||||
|
||||
const scalar = [1, , 3] as number[];
|
||||
function depthArg(value: number): number { console.log("depth", value); return value; }
|
||||
console.log(JSON.stringify(scalar.flat(depthArg(5))), JSON.stringify(scalar.flat(depthArg(-1))));
|
||||
@@ -0,0 +1,36 @@
|
||||
const values = [0, 1, 2, 3];
|
||||
console.log(JSON.stringify(values.splice(1, 0, 8, 9)), JSON.stringify(values));
|
||||
console.log(JSON.stringify(values.splice(-2, 1, 6)), JSON.stringify(values));
|
||||
console.log(JSON.stringify(values.splice(99, 4, 10)), JSON.stringify(values));
|
||||
console.log(JSON.stringify(values.splice(-99, 99, 11, 12)), JSON.stringify(values));
|
||||
|
||||
const sparse = [1, , 3, 4];
|
||||
const removed = sparse.splice(1, 1, 9, 10);
|
||||
console.log(JSON.stringify(removed), removed.length, 0 in removed, JSON.stringify(sparse), 3 in sparse);
|
||||
const more = [1, , 3, 4];
|
||||
console.log(JSON.stringify(more.splice(1, 0, 8)), JSON.stringify(more), 2 in more);
|
||||
|
||||
const nullable: (number | undefined)[] = [1, 2];
|
||||
nullable.splice(1, 0, undefined);
|
||||
console.log(JSON.stringify(nullable), 1 in nullable, nullable[1] === undefined);
|
||||
|
||||
const first = { value: 1 };
|
||||
const second = { value: 2 };
|
||||
const records = [first, second];
|
||||
const old = records.splice(1, 1, first, second);
|
||||
console.log(old[0] === second, records[0] === first, records[1] === first, records[2] === second);
|
||||
|
||||
const events: string[] = [];
|
||||
const ordered = [1, 2, 3];
|
||||
function receiver(): number[] { events.push("receiver"); return ordered; }
|
||||
function numberArg(label: string, value: number): number { events.push(label); return value; }
|
||||
const taken = receiver().splice(numberArg("start", 1), numberArg("count", 1), numberArg("first", 7), numberArg("last", 8));
|
||||
console.log(events.join("|"), JSON.stringify(taken), JSON.stringify(ordered));
|
||||
|
||||
const spread = [7, , 9] as number[];
|
||||
const destination = [1, 2, 3];
|
||||
console.log(JSON.stringify(destination.splice(1, 1, 6, ...spread, 10)), JSON.stringify(destination), 3 in destination);
|
||||
const self = [1, 2, 3];
|
||||
console.log(JSON.stringify(self.splice(1, 0, ...self)), JSON.stringify(self));
|
||||
const original = [1, 2, 3];
|
||||
console.log(JSON.stringify(original.toSpliced(1, 1, 6, ...spread, 10)), JSON.stringify(original));
|
||||
@@ -0,0 +1,17 @@
|
||||
console.log([1, 2, 3].join(), ["a", "b"].join(), [true, false].join());
|
||||
console.log([1, 2].join(undefined), [1, 2].join(void 0));
|
||||
console.log([1, 2].join("|"), [1].join(), ([] as number[]).join());
|
||||
|
||||
let calls = 0;
|
||||
function missing(): undefined { calls++; return undefined; }
|
||||
console.log([5, 6].join(missing()), calls);
|
||||
|
||||
// @ts-ignore JavaScript accepts a numeric separator.
|
||||
console.log([1, 2, 3].join(0));
|
||||
// @ts-ignore JavaScript accepts a boolean separator.
|
||||
console.log([1, 2].join(false));
|
||||
// @ts-ignore JavaScript accepts a null separator.
|
||||
console.log([1, 2].join(null));
|
||||
|
||||
function optionalSeparator(usePipe: boolean): string | undefined { return usePipe ? "|" : undefined; }
|
||||
console.log([1, 2].join(optionalSeparator(true)), [1, 2].join(optionalSeparator(false)));
|
||||
@@ -48,7 +48,7 @@ const localized = (1234.5).toLocaleString();
|
||||
// no fenced form remains to pin.)
|
||||
const nums = [1, 2, 3];
|
||||
const found = nums.indexOf(2, 1); // the fromIndex overload lowers
|
||||
const joined = nums.join();
|
||||
const joined = nums.join(); // the omitted separator lowers
|
||||
nums.forEach((x) => console.log(x), { unused: true }); // thisArg stays fenced
|
||||
const pos = "abc".includes("b", 1); // the position form lowers now (indexOf's clamp)
|
||||
const rev = JSON.parse("1", (_k, v) => v);
|
||||
|
||||
@@ -224,18 +224,9 @@ stdlib-fence.ts:46:19 - error SC2020: 'Number.prototype.toLocaleString without a
|
||||
|
||||
hint: the default locale is the host environment's, which a compiled binary cannot carry — pass it explicitly: x.toLocaleString("en-US")
|
||||
|
||||
stdlib-fence.ts:51:16 - error SC2020: '.join with 0 arguments' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
50 | const found = nums.indexOf(2, 1); // the fromIndex overload lowers
|
||||
51 | const joined = nums.join();
|
||||
| ^~~~~~~~~~~
|
||||
52 | nums.forEach((x) => console.log(x), { unused: true }); // thisArg stays fenced
|
||||
|
||||
hint: pass the separator explicitly: join(",")
|
||||
|
||||
stdlib-fence.ts:52:1 - error SC2020: '.forEach with 2 arguments' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
51 | const joined = nums.join();
|
||||
51 | const joined = nums.join(); // the omitted separator lowers
|
||||
52 | nums.forEach((x) => console.log(x), { unused: true }); // thisArg stays fenced
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
53 | const pos = "abc".includes("b", 1); // the position form lowers now (indexOf's clamp)
|
||||
|
||||
@@ -46,6 +46,8 @@
|
||||
"test/built-ins/Array/prototype/indexOf/15.4.4.14-9-b-ii-1.js",
|
||||
"test/built-ins/Array/prototype/indexOf/15.4.4.14-9-b-ii-4.js",
|
||||
"test/built-ins/Array/prototype/indexOf/fromindex-zero-conversion.js",
|
||||
"test/built-ins/Array/prototype/join/S15.4.4.5_A1.2_T1.js",
|
||||
"test/built-ins/Array/prototype/join/S15.4.4.5_A1.2_T2.js",
|
||||
"test/built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-1.js",
|
||||
"test/built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-2.js",
|
||||
"test/built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-3.js",
|
||||
@@ -55,6 +57,7 @@
|
||||
"test/built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-7.js",
|
||||
"test/built-ins/Array/prototype/lastIndexOf/fromindex-zero-conversion.js",
|
||||
"test/built-ins/Array/prototype/splice/15.4.4.12-9-a-1.js",
|
||||
"test/built-ins/Array/prototype/splice/S15.4.4.12_A6.1_T1.js",
|
||||
"test/built-ins/Array/prototype/splice/called_with_one_argument.js",
|
||||
"test/built-ins/BigInt/prototype/toString/default-radix.js",
|
||||
"test/built-ins/Math/abs/S15.8.2.1_A1.js",
|
||||
@@ -228,6 +231,8 @@
|
||||
"test/built-ins/Array/prototype/indexOf/15.4.4.14-9-b-ii-1.js": "db248a7b40b4285a0331fa7eaee0a78e6a9af4e57294c9ce9e89fe392845ca2e",
|
||||
"test/built-ins/Array/prototype/indexOf/15.4.4.14-9-b-ii-4.js": "07b115c3b90dfb5bcb9e38990fd1db57720c4ac423e052082828ed8ccb6b4214",
|
||||
"test/built-ins/Array/prototype/indexOf/fromindex-zero-conversion.js": "ababfd2ad12b4099f9d2108ba6e58ff33feb358522f3bbd4ddde5144cbf0bc7f",
|
||||
"test/built-ins/Array/prototype/join/S15.4.4.5_A1.2_T1.js": "eab57335e972d5f6e5b0e62a6c951ca38ecbc76c3068a7306e6295d4c35219b3",
|
||||
"test/built-ins/Array/prototype/join/S15.4.4.5_A1.2_T2.js": "ea0b6435d60901e5eee67e0657bbf699726fc73adf4f0a1a5ae17f7aa841dfc1",
|
||||
"test/built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-1.js": "58e752c5eda47f23bdc281f13ac7f3d3df37a0051b0ccd194198987796c8a841",
|
||||
"test/built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-2.js": "70cf040ba435efad144dcdbab8dc29547174ea3af9a7a5750eb0b0939110f497",
|
||||
"test/built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-3.js": "448016548d98ed3c4cefb81f0d995cd12e3d9d13fd90a5d119dd072108979419",
|
||||
@@ -237,6 +242,7 @@
|
||||
"test/built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-7.js": "5afb53c527ddb49da7a505af67cd59ad49d8b47d5af9280ed0e5578672a94c40",
|
||||
"test/built-ins/Array/prototype/lastIndexOf/fromindex-zero-conversion.js": "8231b818adf4123a4e5c9b65206b0b72984894b825319da669b99f820c75f963",
|
||||
"test/built-ins/Array/prototype/splice/15.4.4.12-9-a-1.js": "6e6cd215339a848aef80163023c1201e9c502af76d4dcc62915bf6f868d9523a",
|
||||
"test/built-ins/Array/prototype/splice/S15.4.4.12_A6.1_T1.js": "9c1bd5c44cdba0a240fbd9476c450047bcdc3c809a6bc1faf1c2ef62542beac4",
|
||||
"test/built-ins/Array/prototype/splice/called_with_one_argument.js": "719fd6f8bf400aacefa5a0499b69495384e831668463d42960ff44d399f9f74e",
|
||||
"test/built-ins/BigInt/prototype/toString/default-radix.js": "d901ef7e3fab5b8c528cb60a12c80145a6c5b7fbd02f829fe657259eddbe711a",
|
||||
"test/built-ins/Math/abs/S15.8.2.1_A1.js": "c5b508cdc6130c8670e8ac6d822ca93e5dbead7566543cb65b793602b7203d7c",
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
// Copyright 2009 the Sputnik authors. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
|
||||
/*---
|
||||
info: If separator is undefined, a single comma is used as the separator
|
||||
esid: sec-array.prototype.join
|
||||
description: Checking this use new Array() and []
|
||||
---*/
|
||||
|
||||
var x = new Array(0, 1, 2, 3);
|
||||
if (x.join() !== "0,1,2,3") {
|
||||
throw new Test262Error('#1: x = new Array(0,1,2,3); x.join() === "0,1,2,3". Actual: ' + (x.join()));
|
||||
}
|
||||
|
||||
x = [];
|
||||
x[0] = 0;
|
||||
x[3] = 3;
|
||||
if (x.join() !== "0,,,3") {
|
||||
throw new Test262Error('#2: x = []; x[0] = 0; x[3] = 3; x.join() === "0,,,3". Actual: ' + (x.join()));
|
||||
}
|
||||
|
||||
x = [];
|
||||
x[0] = 0;
|
||||
if (x.join() !== "0") {
|
||||
throw new Test262Error('#3: x = []; x[0] = 0; x.join() === "0". Actual: ' + (x.join()));
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
// Copyright 2009 the Sputnik authors. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
|
||||
/*---
|
||||
info: If separator is undefined, a single comma is used as the separator
|
||||
esid: sec-array.prototype.join
|
||||
description: Checking this use new Array() and []
|
||||
---*/
|
||||
|
||||
var x = new Array(0, 1, 2, 3);
|
||||
if (x.join(undefined) !== "0,1,2,3") {
|
||||
throw new Test262Error('#1: x = new Array(0,1,2,3); x.join(undefined) === "0,1,2,3". Actual: ' + (x.join(undefined)));
|
||||
}
|
||||
|
||||
x = [];
|
||||
x[0] = 0;
|
||||
x[3] = 3;
|
||||
if (x.join(undefined) !== "0,,,3") {
|
||||
throw new Test262Error('#2: x = []; x[0] = 0; x[3] = 3; x.join(undefined) === "0,,,3". Actual: ' + (x.join(undefined)));
|
||||
}
|
||||
|
||||
x = [];
|
||||
x[0] = 0;
|
||||
if (x.join(undefined) !== "0") {
|
||||
throw new Test262Error('#3: x = []; x[0] = 0; x.join(undefined) === "0". Actual: ' + (x.join(undefined)));
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
// Copyright 2014 Ecma International. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
|
||||
/*---
|
||||
info: Array.prototype.splice sets `length` on `this`
|
||||
esid: sec-array.prototype.splice
|
||||
description: Array.prototype.splice sets `length` on Array
|
||||
---*/
|
||||
|
||||
var a = [0, 1, 2];
|
||||
|
||||
a.splice(1, 2, 4);
|
||||
|
||||
if (a.length !== 2) {
|
||||
throw new Test262Error("Expected a.length === 2, actually " + a.length);
|
||||
}
|
||||
|
||||
if (a[0] !== 0) {
|
||||
throw new Test262Error("Expected a[0] === 0, actually " + a[0]);
|
||||
}
|
||||
|
||||
if (a[1] !== 4) {
|
||||
throw new Test262Error("Expected a[1] === 4, actually " + a[1]);
|
||||
}
|
||||
Reference in New Issue
Block a user