mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-02 08:35:07 +08:00
fix: accept readonly tuples as Set seeds (#384)
- Accept supported readonly tuples in static Set constructors while preserving single evaluation and insertion order. - Add differential coverage and refresh constructor diagnostics. Fixes #373 Co-authored-by: Hagege Ruben <20857346+HagegeR@users.noreply.github.com>
This commit is contained in:
co-authored by
Hagege Ruben
parent
e12856c1b4
commit
cc44afb4ee
@@ -5323,12 +5323,11 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr {
|
||||
}
|
||||
lowerer.badType(expr, tsType);
|
||||
}
|
||||
// `new Set<T>()`: Map's sibling. The SEEDED form lowers for any
|
||||
// T[]-typed argument — literal or variable, T already a legal
|
||||
// element type — as construct + bulk add (duplicates collapse,
|
||||
// insertion order preserved, exactly JS). Non-array seeds (another
|
||||
// Set, general iterables) keep the fence. Unsupported element types
|
||||
// are named specifically.
|
||||
// `new Set<T>()`: Map's sibling. The SEEDED form lowers for arrays
|
||||
// and fixed tuples of legal elements — literal or variable — as
|
||||
// construct + bulk add (duplicates collapse, insertion order
|
||||
// preserved, exactly JS). Other iterables keep the fence.
|
||||
// Unsupported element types are named specifically.
|
||||
// `new WeakMap()` / `new WeakSet()` in JAVASCRIPT sources: no weak
|
||||
// container exists in the value model, but harness code constructs
|
||||
// one unconditionally and touches it only on paths tests don't
|
||||
@@ -5379,6 +5378,24 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr {
|
||||
// Any other lowered kind falls through to the named fence
|
||||
// below — never a mistyped seed into the validator.
|
||||
}
|
||||
// Fixed tuples use record storage, not T[] storage. Snapshot
|
||||
// their legal elements into an array before bulk insertion;
|
||||
// the helper takes the tuple as one argument, so an effectful
|
||||
// seed expression is evaluated exactly once.
|
||||
if (argIr?.kind === "record") {
|
||||
const shape = lowerer.shapes.get(argIr.shapeId);
|
||||
if (shape?.tuple && shape.fields.every((field) => typeEquals(field.type, mapped.elem))) {
|
||||
const seedType = { kind: "array" as const, elem: mapped.elem };
|
||||
const helper = lowerer.tupleArrayWidthHelper(argIr.shapeId, seedType, loc);
|
||||
if (helper) {
|
||||
const tuple = lowerer.lowerExpr(argNode);
|
||||
if (typeEquals(tuple.type, argIr)) {
|
||||
const seed: IrExpr = { kind: "call", callee: helper, args: [tuple], type: seedType, loc };
|
||||
return { kind: "setNew", seed, type: mapped, loc };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// JavaScript's identity-Set idiom: `new Set([setTimeout, atob,
|
||||
@@ -5410,7 +5427,7 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr {
|
||||
lowerer.noLowering(
|
||||
"new Set(values)",
|
||||
expr,
|
||||
"construct the Set empty and add() each value — only an array of " +
|
||||
"construct the Set empty and add() each value — only an array or fixed tuple of " +
|
||||
"already-legal elements (string or number) seeds a Set",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6002,8 +6002,9 @@ export class Lowerer {
|
||||
tupleArrayWidthHelper(fromId: string, toT: IrType & { kind: "array" }, loc: SrcLoc): string | null {
|
||||
const from = this.shapes.get(fromId);
|
||||
if (!from || !from.tuple) return null;
|
||||
const fields = [...from.fields].sort((a, b) => Number(a.name) - Number(b.name));
|
||||
const lifts: WidthLift[] = [];
|
||||
for (const f of from.fields) {
|
||||
for (const f of fields) {
|
||||
const lift = this.widthLiftPlan(f.type, toT.elem);
|
||||
if (!lift) return null;
|
||||
lifts.push(lift);
|
||||
@@ -6025,7 +6026,7 @@ export class Lowerer {
|
||||
kind: "return",
|
||||
value: {
|
||||
kind: "arrayLit",
|
||||
elems: from.fields.map((f, i) =>
|
||||
elems: fields.map((f, i) =>
|
||||
this.applyWidthLift(
|
||||
lifts[i]!,
|
||||
{ kind: "recordGet", obj: t, shapeId: fromId, field: f.name, type: f.type, loc },
|
||||
|
||||
@@ -6510,6 +6510,12 @@
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/2947-set-readonly-seeds.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/2947-set-readonly-seeds.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/300-if-else.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/300-if-else.ts"
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
const words = ["pear", "apple", "pear"] as const;
|
||||
const named = new Set<string>(words);
|
||||
console.log(named.size, named.has("apple"), [...named].join(","));
|
||||
|
||||
const inferred = new Set(words);
|
||||
console.log(inferred.size, inferred.has("pear"), [...inferred].join(","));
|
||||
|
||||
const readonlyWords: readonly string[] = ["north", "south", "north"];
|
||||
const fromReadonlyArray = new Set(readonlyWords);
|
||||
console.log(fromReadonlyArray.size, [...fromReadonlyArray].join(","));
|
||||
|
||||
const numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 3] as const;
|
||||
const fromTuple = new Set<number>(numbers);
|
||||
console.log(fromTuple.size, [...fromTuple].join(","));
|
||||
|
||||
let seedCalls = 0;
|
||||
function makeSeed() {
|
||||
seedCalls++;
|
||||
return ["once", "twice", "once"] as const;
|
||||
}
|
||||
const fromCall = new Set<string>(makeSeed());
|
||||
console.log(seedCalls, fromCall.size, [...fromCall].join(","));
|
||||
|
||||
const direct = new Set(["red", "blue", "red"] as const);
|
||||
console.log(direct.size, [...direct].join(","));
|
||||
@@ -5,7 +5,7 @@ sets.ts:8:16 - error SC2020: 'new Set(values)' is part of the standard library t
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
9 |
|
||||
|
||||
hint: construct the Set empty and add() each value — only an array of already-legal elements (string or number) seeds a Set
|
||||
hint: construct the Set empty and add() each value — only an array or fixed tuple of already-legal elements (string or number) seeds a Set
|
||||
|
||||
sets.ts:12:7 - error SC2009: values of type 'Set<boolean>' cannot be compiled: the Set shape is supported, but elements are limited to numbers and strings — 'boolean' is outside that domain
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ stdlib-fence.ts:30:12 - error SC2020: 'new Set(values)' is part of the standard
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~
|
||||
31 | // (new Error / TypeError / RangeError / SyntaxError now LOWER — the error
|
||||
|
||||
hint: construct the Set empty and add() each value — only an array of already-legal elements (string or number) seeds a Set
|
||||
hint: construct the Set empty and add() each value — only an array or fixed tuple of already-legal elements (string or number) seeds a Set
|
||||
|
||||
stdlib-fence.ts:34:7 - error SC2020: 'AggregateError' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
|
||||
Reference in New Issue
Block a user