diff --git a/packages/compiler/src/frontend/lowering/containers/array-methods.ts b/packages/compiler/src/frontend/lowering/containers/array-methods.ts index 44182fbda..3ab2e84e2 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-methods.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-methods.ts @@ -361,6 +361,21 @@ export function lowerArrayMethodCall( if (probe.type.kind === "array") { receiverIr = probe.type; probedUntyped = true; + } else if ( + probe.type.kind === "dyn" && + !isJsSourceFile(ts.getSourceFileOfNode(access)) && + lowerer.isStdlibMember(access) + ) { + // An `unknown[]` (or `any[]`) array stored checked-dynamic — a + // generic body instantiated at `unknown`, a spread of unknown + // elements: the method runs on the runtime array (dynInvoke, JS + // exact), and a typed result is checked like any member read. + const dispatched = lowerDynDispatchMethodCall(lowerer, call, access, probe, true); + if (!dispatched) return null; + const result = lowerer.mapTypeOf(lowerer.typeOf(call)); + return result && result.kind !== "void" && result.kind !== "dyn" + ? lowerer.coerceInto(call, dispatched, result) + : dispatched; } } if (receiverIr?.kind === "union") { diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index d18761f98..bb006f530 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -4261,6 +4261,7 @@ const DYN_DISPATCH_METHODS = new Set([ "shift", "unshift", "slice", + "splice", "at", "indexOf", "lastIndexOf", diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7402-unknown-array-methods.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7402-unknown-array-methods.ts.json new file mode 100644 index 000000000..db33da675 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7402-unknown-array-methods.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7402-unknown-array-methods.ts" + ], + "diags": [] +} diff --git a/tests/corpus/7402-unknown-array-methods.ts b/tests/corpus/7402-unknown-array-methods.ts new file mode 100644 index 000000000..e374dd40f --- /dev/null +++ b/tests/corpus/7402-unknown-array-methods.ts @@ -0,0 +1,14 @@ +// Methods on arrays of unknown elements, which are stored checked-dynamic: +// direct `unknown[]` bindings and generic bodies instantiated at `unknown`. +const values: unknown[] = [1, "two", 3, { four: 4 }]; +const removed = values.splice(1, 2); +console.log(values, removed, removed.length); +values.splice(1, 0, "inserted", null); +console.log(values, values.indexOf(null), values.includes("inserted")); +console.log(values.slice(1).reverse(), values.join("|")); + +function takeAt(items: readonly T[], index: number): T[] { + const pool = [...items]; + return pool.splice(index, 1); +} +console.log(takeAt([10, 20, 30], 1), takeAt([true, "x", 7], 2)); diff --git a/tests/harness/corpus-costs/7402-unknown-array-methods.ts.json b/tests/harness/corpus-costs/7402-unknown-array-methods.ts.json new file mode 100644 index 000000000..9eb118813 --- /dev/null +++ b/tests/harness/corpus-costs/7402-unknown-array-methods.ts.json @@ -0,0 +1 @@ +{ "plain": 2600, "san": 20000 }