mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-27 06:43:19 +08:00
Build Sets, Maps and weak collections from checked-dynamic iterables
`new Set(x)`, `new Map(x)`, `new WeakSet(x)`, `new WeakMap(x)` and `Object.fromEntries(x)` now accept any checked-dynamic iterable, as in Node: a native iterator (`map.keys()`, `set.values()`), a compiled generator, an object with a Symbol.iterator method, or a Set. Before, a Set seed that was not an array or a string threw "is not iterable", and a Map, weak collection or Object.fromEntries seed that was a generator or a collection other than a Map refused with SC2020. The runtime drains native iterators, packs arrays, strings and collections, and steps every other iterable through its iterator (scr_dyn_seed_values). A program that builds these from an untyped seed registers the compiled generators' next and return methods with the runtime, as destructuring rest already did, so a generator seed can be stepped. The seed's values are all taken before the first one is added, so when an entry of a Map seed is not an object the TypeError comes after the iterable is exhausted; Node closes the iterator at that entry. The limitations page says so.
This commit is contained in:
@@ -7,4 +7,6 @@
|
||||
They support `.next()`, `for...of`, array spreads, and mapper-less `Array.from`.
|
||||
They also support the iterator helper methods, such as `.map()` and `.take()`.
|
||||
Detached collection methods are unsupported.
|
||||
Constructing a Map, Set, WeakMap or WeakSet, or calling `Object.fromEntries`, with a checked-dynamic iterable (a native iterator, a generator, or an object with a `Symbol.iterator` method) takes all of its values before adding the first one.
|
||||
When an entry of a Map seed is not an object, the TypeError comes after the iterable is exhausted; Node closes the iterator at that entry.
|
||||
The Set composition methods (`union`, `intersection`, `difference`, `symmetricDifference`, `isSubsetOf`, `isSupersetOf`, `isDisjointFrom`) accept a Set of the same element type or a Map keyed by it; other set-like arguments are unsupported.
|
||||
|
||||
@@ -11,6 +11,18 @@ import { everyStmtList } from "../../ir/traverse.js";
|
||||
import { toPrimitiveHint } from "./class-dynamic-dispatch.js";
|
||||
import { GENERATOR_STEP_METHODS, HELPER_METHODS } from "./generator-dynamic-dispatch.js";
|
||||
|
||||
/** Runtime entry points that step a checked-dynamic iterable's iterator:
|
||||
* destructuring rest, and the collection constructors' and
|
||||
* Object.fromEntries' seeds. */
|
||||
const RUNTIME_STEP_LIB_FNS: ReadonlySet<string> = new Set([
|
||||
"dyn.destructureRest",
|
||||
"dyn.nativeSetNew",
|
||||
"dyn.mapSeedEntries",
|
||||
"dyn.fromEntries",
|
||||
"weakMap.new",
|
||||
"weakSet.new",
|
||||
]);
|
||||
|
||||
/** What one function's body holds for the class dispatch pass and its
|
||||
* caught-value, generator and class construction sub-passes, collected in
|
||||
* one walk. Lists keep the order the walk meets their entries. */
|
||||
@@ -186,7 +198,7 @@ function scanSites(statements: IrStmt[]): FunctionSites {
|
||||
expr.key.kind === "strLit" &&
|
||||
HELPER_METHODS.has(expr.key.value)) ||
|
||||
expr.kind === "dynIterN" ||
|
||||
(expr.kind === "libCall" && expr.fn === "dyn.destructureRest")
|
||||
(expr.kind === "libCall" && RUNTIME_STEP_LIB_FNS.has(expr.fn))
|
||||
)
|
||||
sites.runtimeSteps = true;
|
||||
switch (expr.kind) {
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6926-dynamic-collection-seeds.mjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6927-dynamic-collection-seeds.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -1577,6 +1577,20 @@ ScrDyn *scr_dyn_iter_pack(const ScrDyn *src, const ScrStr *msg) {
|
||||
|
||||
static ScrDyn *scr_native_iterator_drain(const ScrDyn *src);
|
||||
|
||||
/* The values a collection constructor's iterable seed gives, in order: +1
|
||||
* array, or NULL with the exception pending. A native iterator (a Map's
|
||||
* keys(), a Set's values()) drains, which runs no program code; arrays,
|
||||
* strings, typed arrays and collections pack; other iterables (a
|
||||
* generator, an object with a Symbol.iterator method) step their
|
||||
* iterator to the end, so their values are all taken before the
|
||||
* constructor adds the first one. */
|
||||
static ScrDyn *scr_dyn_seed_values(const ScrDyn *src) {
|
||||
if (src->kind == SCR_DYN_HANDLE &&
|
||||
(src->v.handle.tag == SCR_DYNH_ITERATOR || scr_dyn_handle_ops_of(src)->iter_step))
|
||||
return scr_native_iterator_drain(src);
|
||||
return scr_dyn_destructure_rest(src, NULL);
|
||||
}
|
||||
|
||||
/* The for-of-over-dyn pack accessors: the emitted index loop drives them
|
||||
* over a scr_dyn_iter_pack result (ARR by construction — the defensive
|
||||
* arms cover nothing reachable from that lowering). Never throw. */
|
||||
@@ -1592,18 +1606,10 @@ ScrDyn *scr_dyn_map_seed_entries(const ScrDyn *src) {
|
||||
if (scr_exc_pending()) return NULL;
|
||||
}
|
||||
/* A native iterator (sp.entries(), a Map's entries()) or a handle that
|
||||
* steps through live entries (URLSearchParams): producing its entries
|
||||
* runs no program code, so draining them first is what the consumer
|
||||
* observes. */
|
||||
if (src->kind == SCR_DYN_HANDLE &&
|
||||
(src->v.handle.tag == SCR_DYNH_ITERATOR || scr_dyn_handle_ops_of(src)->iter_step))
|
||||
return scr_native_iterator_drain(src);
|
||||
if (src->kind == SCR_DYN_HANDLE || src->kind == SCR_DYN_TYPED_REF) {
|
||||
static const char message[] = "new Map(entries) over native non-array iterables has no lowering";
|
||||
scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020");
|
||||
return NULL;
|
||||
}
|
||||
return scr_dyn_iter_pack(src, NULL);
|
||||
* steps through live entries (URLSearchParams) drains; a Set, a
|
||||
* generator or another iterable gives its values in order
|
||||
* (scr_dyn_seed_values). */
|
||||
return scr_dyn_seed_values(src);
|
||||
}
|
||||
|
||||
ScrDyn *scr_dyn_map_seed_entry(const ScrDyn *entry) {
|
||||
@@ -14836,7 +14842,7 @@ ScrDyn *scr_dyn_native_set(ScrMap *value) {
|
||||
ScrDyn *scr_dyn_native_set_new(const ScrDyn *seed) {
|
||||
ScrDyn *items = NULL;
|
||||
if (seed->kind != SCR_DYN_NULL && seed->kind != SCR_DYN_UNDEF) {
|
||||
items = scr_dyn_iter_pack(seed, NULL);
|
||||
items = scr_dyn_seed_values(seed);
|
||||
if (!items) return NULL;
|
||||
}
|
||||
ScrMap *set = scr_map_new(SCR_MAP_KEY_DYN, SCR_MAP_VAL_F64, &scr_map_dyn_set_ops);
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
// Set, Map, WeakSet and Object.fromEntries over checked-dynamic iterables:
|
||||
// native iterators, generators, objects with a Symbol.iterator method and
|
||||
// collections, as seeds that reach the runtime untyped.
|
||||
function* pairs() { yield ["g", 1]; yield ["h", 2]; }
|
||||
function* values() { yield 1; yield 2; yield 2; }
|
||||
const iterable = { *[Symbol.iterator]() { yield ["i", 3]; } };
|
||||
const valueIterable = { *[Symbol.iterator]() { yield "a"; yield "b"; } };
|
||||
function attempt(label, make) {
|
||||
try { console.log(label, make()); } catch (e) { console.log(label, "threw", e.name, e.message); }
|
||||
}
|
||||
function viaSet(x) { return new Set(x).size; }
|
||||
function viaMap(x) { return [...new Map(x).keys()].join(); }
|
||||
function viaWeakSet(x) { return new WeakSet(x).has(key); }
|
||||
const key = {};
|
||||
const m = new Map([["a", 1], ["b", 2]]);
|
||||
attempt("set keys()", () => viaSet(m.keys()));
|
||||
attempt("set values()", () => viaSet(new Set([1, 2]).values()));
|
||||
attempt("set generator", () => viaSet(values()));
|
||||
attempt("set iterable", () => viaSet(valueIterable));
|
||||
attempt("set string", () => viaSet("abca"));
|
||||
attempt("set number", () => viaSet(5));
|
||||
attempt("map generator", () => viaMap(pairs()));
|
||||
attempt("map iterable", () => viaMap(iterable));
|
||||
attempt("map set", () => viaMap(new Set([["s", 1]])));
|
||||
attempt("map entries()", () => viaMap(m.entries()));
|
||||
attempt("map bad entry", () => viaMap([1]));
|
||||
attempt("weakset generator", () => viaWeakSet((function* () { yield key; })()));
|
||||
attempt("fromEntries generator", () => JSON.stringify(Object.fromEntries(pairs())));
|
||||
@@ -0,0 +1,9 @@
|
||||
// Set and Map seeds held as any: native iterators and generators.
|
||||
function* values(): Generator<number> { yield 1; yield 2; yield 2; }
|
||||
function* pairs(): Generator<[string, number]> { yield ["a", 1]; yield ["b", 2]; }
|
||||
const m = new Map<string, number>([["x", 1], ["y", 2]]);
|
||||
const anyKeys: any = m.keys();
|
||||
const anyGen: any = values();
|
||||
const anyPairs: any = pairs();
|
||||
console.log(new Set(anyKeys).size, new Set(anyGen).size, new Map(anyPairs).size);
|
||||
console.log(new Set(values()).size, new Map(pairs()).get("b"), new Set(m.values()).size);
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 3813, "san": 3374 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 2436, "san": 2273 }
|
||||
Reference in New Issue
Block a user