mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-28 06:53:42 +08:00
Park typed collection iterators on the entry they read
An early exit from a loop over an iterator-typed Map or Set method parks the iterator with a second yield of the current entry. That yield read the entry again by its index, which aborted with a map iteration error when the loop had deleted the entry. Each step now reads the entry once into a local that both yields answer. `entries()` takes the typed walk only for a slot whose yield type is the key and value pair record. Other yield types, such as `IterableIterator<any>` or `<unknown>`, keep the checked adapter instead of failing to compile.
This commit is contained in:
@@ -84,6 +84,26 @@ function collectionIteratorSlotValue(
|
||||
collT.kind === "map"
|
||||
? { kind: "mapIntrinsic", method: m, receiver: coll, args: [index], type: t, loc }
|
||||
: { kind: "setIntrinsic", method: "iterKey", receiver: coll, args: [index], type: t, loc };
|
||||
// entries() answers [key, value] pairs: only a slot whose yield type is
|
||||
// that pair as a record of the two fields takes the typed walk; a tuple
|
||||
// or other pair type keeps the checked adapter.
|
||||
if (method === "entries") {
|
||||
const pair =
|
||||
expected.yieldT.kind === "record" ? lowerer.shapes.get(expected.yieldT.shapeId) : undefined;
|
||||
const field = (name: string): IrType | undefined =>
|
||||
pair?.fields.find((f) => f.name === name)?.type;
|
||||
const first = field("0");
|
||||
const second = field("1");
|
||||
if (
|
||||
pair === undefined ||
|
||||
pair.fields.length !== 2 ||
|
||||
first === undefined ||
|
||||
second === undefined ||
|
||||
!typeEquals(first, keyT) ||
|
||||
!typeEquals(second, valueT)
|
||||
)
|
||||
return null;
|
||||
}
|
||||
const entry = (): IrExpr =>
|
||||
method === "keys" || (collT.kind === "set" && method === "values")
|
||||
? read("iterKey", keyT)
|
||||
@@ -99,9 +119,13 @@ function collectionIteratorSlotValue(
|
||||
loc,
|
||||
};
|
||||
if (!typeEquals(entry().type, expected.yieldT)) return null;
|
||||
// The entry is read once per step and kept: the parking yield of an
|
||||
// early exit answers it again rather than rereading a slot the loop may
|
||||
// have deleted.
|
||||
const current = varRef("current.0", expected.yieldT, loc);
|
||||
const suspend = (): IrStmt => ({
|
||||
kind: "exprStmt",
|
||||
expr: { kind: "yieldExpr", value: entry(), type: resumeType(expected.nextT), loc },
|
||||
expr: { kind: "yieldExpr", value: current, type: resumeType(expected.nextT), loc },
|
||||
loc,
|
||||
});
|
||||
const finish: IrStmt = {
|
||||
@@ -120,6 +144,7 @@ function collectionIteratorSlotValue(
|
||||
{ id: "native.0", name: "native", type: DYN, mutable: false },
|
||||
{ id: "index.0", name: "index", type: F64, mutable: true },
|
||||
{ id: "resumed.0", name: "resumed", type: BOOL, mutable: true },
|
||||
{ id: "current.0", name: "current", type: expected.yieldT, mutable: false },
|
||||
],
|
||||
body: [
|
||||
{
|
||||
@@ -173,6 +198,7 @@ function collectionIteratorSlotValue(
|
||||
loc,
|
||||
},
|
||||
setResumed(false, loc),
|
||||
{ kind: "varDecl", localId: "current.0", init: entry(), loc },
|
||||
{
|
||||
kind: "tryCatch",
|
||||
tryBody: [suspend(), setResumed(true, loc)],
|
||||
|
||||
@@ -56,3 +56,54 @@ const ordered = [...newest(listed)];
|
||||
console.log(JSON.stringify(ordered), ordered[0] === listed[1]);
|
||||
const step = newest(listed).next();
|
||||
console.log(step.done, JSON.stringify(step.value));
|
||||
|
||||
// Deleting the current entry before leaving a loop over an iterator-typed
|
||||
// method: the iterator parks after that entry and continues with the next.
|
||||
class Store {
|
||||
readonly rows = new Map<string, number>();
|
||||
readonly tags = new Set<string>();
|
||||
values(): IterableIterator<number> {
|
||||
return this.rows.values();
|
||||
}
|
||||
keys(): IterableIterator<string> {
|
||||
return this.rows.keys();
|
||||
}
|
||||
tagValues(): IterableIterator<string> {
|
||||
return this.tags.values();
|
||||
}
|
||||
anyPairs(): IterableIterator<any> {
|
||||
return this.rows.entries();
|
||||
}
|
||||
unknownPairs(): IterableIterator<unknown> {
|
||||
return this.rows.entries();
|
||||
}
|
||||
tagAny(): IterableIterator<any> {
|
||||
return this.tags.entries();
|
||||
}
|
||||
}
|
||||
const store = new Store();
|
||||
for (const [k, v] of [["a", 1], ["b", 2], ["c", 3]] as const) store.rows.set(k, v);
|
||||
for (const t of ["x", "y", "z"]) store.tags.add(t);
|
||||
console.log(JSON.stringify([...store.anyPairs()]), JSON.stringify([...store.unknownPairs()]));
|
||||
console.log(JSON.stringify([...store.tagAny()]));
|
||||
const values = store.values();
|
||||
for (const v of values) {
|
||||
if (v === 2) {
|
||||
store.rows.delete("b");
|
||||
break;
|
||||
}
|
||||
}
|
||||
console.log([...values].join(), [...store.rows.keys()].join());
|
||||
for (const k of store.keys()) {
|
||||
store.rows.delete(k);
|
||||
break;
|
||||
}
|
||||
console.log([...store.rows.keys()].join());
|
||||
const tags = store.tagValues();
|
||||
for (const t of tags) {
|
||||
if (t === "y") {
|
||||
store.tags.delete("y");
|
||||
break;
|
||||
}
|
||||
}
|
||||
console.log([...tags].join(), [...store.tags].join());
|
||||
|
||||
Reference in New Issue
Block a user