mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-08 08:44:37 +08:00
State the empty-array arm rule once
The three sites that give an empty `[]` literal a home — a union slot, a contextual union, and the `??` default — each carried their own copy of the rule and each tested it slightly differently (element count here, a `never[]` own type there), so a later fix could drift one from the others. emptyArrayLiteralArm states it once: an empty literal has no element to select an arm by, so it builds as the destination's first array arm. Every site keeps the lowering it had. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
887aa774e0
commit
eabcbdb533
@@ -38,6 +38,28 @@ import { arrayValueStore, primitiveElement } from "../array-values.js";
|
||||
import { classGeneratorIterable } from "../class-iterables.js";
|
||||
import { iteratorCanStep } from "../iterator-consumption.js";
|
||||
|
||||
/** The arm an EMPTY `[]` literal builds as: `arms`' first array arm, or
|
||||
* null when the literal is not empty (an element selects the arm) or no
|
||||
* arm is an array.
|
||||
*
|
||||
* An empty literal carries no element of its own — tsc infers `never[]`,
|
||||
* whose f64-element mapping is the uninhabited element's representation,
|
||||
* not one of the destination's arms — and every empty array is the same
|
||||
* value whichever array family stores it, so several array arms raise no
|
||||
* ambiguity. This is the ONE statement of that rule: a union slot, a
|
||||
* contextual union, and the `??` default all ask it. A lone array slot
|
||||
* states the same thing without asking. */
|
||||
export function emptyArrayLiteralArm(
|
||||
node: ts.Expression,
|
||||
arms: readonly IrType[],
|
||||
): (IrType & { kind: "array" }) | null {
|
||||
let value = node;
|
||||
while (ts.isParenthesizedExpression(value)) value = value.expression;
|
||||
if (!ts.isArrayLiteralExpression(value) || value.elements.nodes.length > 0) return null;
|
||||
const arm = arms.find((candidate) => candidate.kind === "array");
|
||||
return (arm as (IrType & { kind: "array" }) | undefined) ?? null;
|
||||
}
|
||||
|
||||
/** JavaScript: whether `expr` is or holds an existing checked-dynamic
|
||||
* object — a binding stored as a dyn value whose checker type is an
|
||||
* object, or an array or object literal with such an element. Building
|
||||
@@ -206,16 +228,12 @@ export function lowerArrayLiteral(
|
||||
mapped = arrayOf(unitOnlyUnion(lowerer.unions));
|
||||
// An EMPTY literal in a union slot (`const t: string[] | undefined =
|
||||
// []`, the `[]` default argument against `string[] | undefined`) has
|
||||
// no useful own type — tsc infers `never[]`, whose f64-element
|
||||
// mapping is a representation for the uninhabited, not the slot's
|
||||
// arm. Build the literal as the union's FIRST array arm and let the
|
||||
// slot's coercion wrap it: an empty array is the same value whichever
|
||||
// array family stores it, so several array arms raise no ambiguity —
|
||||
// the rule a lone array slot already states.
|
||||
if (ctxUnion && expr.elements.nodes.length === 0) {
|
||||
// no useful own type: it builds as the union's first array arm (the
|
||||
// emptyArrayLiteralArm rule) and the slot's coercion wraps it.
|
||||
if (ctxUnion) {
|
||||
const def = lowerer.unions.get(ctxUnion.unionId);
|
||||
const arrayArms = def?.arms.filter((a) => a.kind === "array") ?? [];
|
||||
if (arrayArms.length >= 1) mapped = arrayArms[0]!;
|
||||
const arm = emptyArrayLiteralArm(expr, def?.arms ?? []);
|
||||
if (arm) mapped = arm;
|
||||
}
|
||||
// A NON-EMPTY literal whose own type has no static home (the JS dyn
|
||||
// fallback — a null/dyn mapping, or an inference that degraded to a
|
||||
|
||||
@@ -73,6 +73,7 @@ import { nsWritableTarget } from "../lower-namespaces.js";
|
||||
import { arrayValueStore, tryLowerBytesReadNumberEquality } from "../array-values.js";
|
||||
import { cjsExportMemberWriteGlobalOf, cjsInFileExportGlobalOf } from "../cjs-exports-object.js";
|
||||
import { lowerUnionFieldWrite } from "./union-fields.js";
|
||||
import { emptyArrayLiteralArm } from "./array-literals.js";
|
||||
import { lowerAbstractEquality } from "../abstract-equality.js";
|
||||
import { lowerInExpression } from "./relational.js";
|
||||
import { lowerDynTypeofTest, lowerErrorCodeTypeofTest } from "./typeof.js";
|
||||
@@ -380,19 +381,6 @@ function untypedCollectionFallback(lowerer: Lowerer, node: ts.Expression): boole
|
||||
});
|
||||
}
|
||||
|
||||
/** An empty `[]` literal whose own type is `never[]` (no contextual array
|
||||
* type supplied one): the `??` default of an optional array read. */
|
||||
function emptyNeverArrayLiteral(lowerer: Lowerer, node: ts.Expression): boolean {
|
||||
let value = node;
|
||||
while (ts.isParenthesizedExpression(value)) value = value.expression;
|
||||
if (!ts.isArrayLiteralExpression(value) || value.elements.nodes.length > 0) return false;
|
||||
const own = lowerer.typeOf(value);
|
||||
return (
|
||||
lowerer.checker.isArrayType(own) &&
|
||||
(lowerer.checker.getTypeArguments(own as ts.TypeReference)[0]!.flags & ts.TypeFlags.Never) !== 0
|
||||
);
|
||||
}
|
||||
|
||||
export function lowerNullishPair(
|
||||
lowerer: Lowerer,
|
||||
expr: ts.BinaryExpression,
|
||||
@@ -445,21 +433,21 @@ export function lowerNullishPair(
|
||||
return { kind: "nullish", left, right, type: rest[0]!, loc };
|
||||
}
|
||||
// An EMPTY array-literal default (`''.match(re) ?? []`) has no element of
|
||||
// its own: tsc infers `never[]`, whose f64-element mapping the whole
|
||||
// expression keeps as a second array arm (`RegExpMatchArray | number[]`),
|
||||
// a result no single array slot can receive. It adopts the left's present
|
||||
// array arm instead — the empty-literal rule the union slot and the
|
||||
// ternary arm already state — so `?? []` answers the left's array type.
|
||||
// A JavaScript `[]` is `any[]` and keeps the dynamic path.
|
||||
// its own, so it builds as the left's array arm (the emptyArrayLiteralArm
|
||||
// rule the union slot and the contextual union share) and the whole
|
||||
// expression answers the left's array type instead of the checker's
|
||||
// `never[]`-flavored second arm. A JavaScript `[]` is `any[]` and keeps
|
||||
// the dynamic path.
|
||||
if (
|
||||
rest.length === 1 &&
|
||||
rest[0]!.kind === "array" &&
|
||||
!isJsSourceFile(ts.getSourceFileOfNode(expr.right)) &&
|
||||
emptyNeverArrayLiteral(lowerer, expr.right)
|
||||
!isJsSourceFile(ts.getSourceFileOfNode(expr.right))
|
||||
) {
|
||||
const right = lowerer.lowerExprExpecting(expr.right, rest[0]!);
|
||||
if (typeEquals(right.type, rest[0]!))
|
||||
return { kind: "nullish", left, right, type: rest[0]!, loc };
|
||||
const arm = emptyArrayLiteralArm(expr.right, rest);
|
||||
if (arm) {
|
||||
const right = lowerer.lowerExprExpecting(expr.right, arm);
|
||||
if (typeEquals(right.type, arm)) return { kind: "nullish", left, right, type: arm, loc };
|
||||
}
|
||||
}
|
||||
let type = lowerer.irTypeOf(expr);
|
||||
// A runtime-optional read can reach the default even when declarations
|
||||
|
||||
@@ -781,6 +781,7 @@ import { lowerRecordFieldCall, lowerObjectMethodCall } from "./lower-calls.js";
|
||||
import { fenceCrossBlockNsRef, nsPathPrefix } from "./lower-namespaces.js";
|
||||
import { isSafeToRepeat } from "./expressions/evaluation-safety.js";
|
||||
import { partialAssertionFields } from "./partial-assertions.js";
|
||||
import { emptyArrayLiteralArm } from "./expressions/array-literals.js";
|
||||
import { lowerTernary } from "./lower-exprs.js";
|
||||
import {
|
||||
maybePromiseReturnStmt,
|
||||
@@ -14044,15 +14045,10 @@ export class Lowerer {
|
||||
);
|
||||
}
|
||||
}
|
||||
// An EMPTY literal carries no element to select an arm by: tsc
|
||||
// infers `never[]`, whose f64-element mapping is the uninhabited
|
||||
// element's representation, not one of the slot's arms. Every empty
|
||||
// array is the same value whichever array arm stores it, so the
|
||||
// literal is built as the union's FIRST array arm — the empty
|
||||
// literal rule a lone array slot already states, and the same one
|
||||
// the ternary arm and the `?? []` default apply.
|
||||
if (!arm && arms.length > 1 && x.elements.nodes.length === 0)
|
||||
arm = arms.find((candidate) => candidate.kind === "array");
|
||||
// An EMPTY literal carries no element to select an arm by: it
|
||||
// builds as the union's first array arm (the emptyArrayLiteralArm
|
||||
// rule the contextual union and the `?? []` default share).
|
||||
if (!arm) arm = emptyArrayLiteralArm(x, arms) ?? undefined;
|
||||
if (arm) {
|
||||
const built = this.lowerArrayLiteral(
|
||||
x,
|
||||
|
||||
Reference in New Issue
Block a user