mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-01 07:33:12 +08:00
Clone Maps and Sets in structuredClone
This commit is contained in:
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/structured-clone-collections.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -15623,6 +15623,73 @@ typedef struct ScrScParent {
|
||||
const struct ScrScParent *up;
|
||||
} ScrScParent;
|
||||
|
||||
static ScrDyn *scr_sc_clone(const ScrDyn *v, const ScrScParent *up, ScrDynRegion *region);
|
||||
|
||||
static const char scr_sc_cycle_message[] =
|
||||
"structuredClone of cyclic values (the checked-dynamic tree cannot represent cycles) is not supported yet";
|
||||
|
||||
/* The clone a handle's clone hook runs inside: its parent chain and region,
|
||||
* so a Map or Set clones its entries with the cycle check and region of the
|
||||
* value that holds it. */
|
||||
static SCR_TL struct {
|
||||
const ScrScParent *up;
|
||||
ScrDynRegion *region;
|
||||
/* scr_sc_clone, reached through the context so that the Map and Set
|
||||
* handle ops do not link the structured-clone code into programs that
|
||||
* never clone (volatile, so the call is not resolved at compile time). */
|
||||
ScrDyn *(*volatile clone)(const ScrDyn *v, const ScrScParent *up, ScrDynRegion *region);
|
||||
} scr_sc_nested;
|
||||
|
||||
/* A Map or Set clones entry by entry, cloning each key and value. The copy
|
||||
* keeps the source's storage kinds and typed slot adapters, so a typed
|
||||
* collection checks back into its own type; a typed slot converts the
|
||||
* cloned value into a fresh element. The Map and Set handle ops install it
|
||||
* as their clone hook. */
|
||||
static ScrMap *scr_sc_clone_collection(ScrMap *source, bool is_map) {
|
||||
const ScrScParent *up = scr_sc_nested.up;
|
||||
ScrDynRegion *region = scr_sc_nested.region;
|
||||
ScrDyn *(*clone)(const ScrDyn *, const ScrScParent *, ScrDynRegion *) = scr_sc_nested.clone;
|
||||
ScrMap *out = scr_map_new((ScrMapKeyKind)source->key_kind, (ScrMapValKind)source->val_kind, source->ops);
|
||||
out->dyn_ops = source->dyn_ops;
|
||||
scr_map_iter_enter(source);
|
||||
for (double i = 0; i < scr_map_iter_count(source); i++) {
|
||||
if (!scr_map_iter_live(source, i)) continue;
|
||||
ScrDyn *key = scr_map_dyn_key(source, i);
|
||||
ScrDyn *key_copy = clone(key, up, region);
|
||||
scr_dyn_release(key);
|
||||
ScrDyn *value_copy = NULL;
|
||||
if (key_copy && is_map) {
|
||||
ScrDyn *value = scr_map_dyn_value(source, i);
|
||||
value_copy = clone(value, up, region);
|
||||
scr_dyn_release(value);
|
||||
}
|
||||
if (key_copy && (value_copy || !is_map)) scr_map_dyn_set(out, key_copy, value_copy, !is_map);
|
||||
scr_dyn_release(key_copy);
|
||||
scr_dyn_release(value_copy);
|
||||
if (scr_exc_pending()) break;
|
||||
}
|
||||
scr_map_iter_exit(source);
|
||||
if (scr_exc_pending()) {
|
||||
scr_map_release(out);
|
||||
return NULL;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static ScrDyn *scr_native_map_clone(void *ptr) {
|
||||
ScrMap *copy = scr_sc_clone_collection(ptr, true);
|
||||
ScrDyn *result = copy ? scr_dyn_native_map(copy) : NULL;
|
||||
scr_map_release(copy);
|
||||
return result;
|
||||
}
|
||||
|
||||
static ScrDyn *scr_native_set_clone(void *ptr) {
|
||||
ScrMap *copy = scr_sc_clone_collection(ptr, false);
|
||||
ScrDyn *result = copy ? scr_dyn_native_set(copy) : NULL;
|
||||
scr_map_release(copy);
|
||||
return result;
|
||||
}
|
||||
|
||||
/* The clone is a fresh tree (shared inputs are copied per path, cycles
|
||||
* throw), so it is tagged like a parse result (ScrDynRegion); a leaf the
|
||||
* region may not hold clears it. */
|
||||
@@ -15740,7 +15807,25 @@ static ScrDyn *scr_sc_clone(const ScrDyn *v, const ScrScParent *up, ScrDynRegion
|
||||
case SCR_DYN_HANDLE:
|
||||
if (scr_dyn_handle_ops_of(v)->clone) {
|
||||
region->intact = false; /* a handle is not a leaf the region may hold */
|
||||
return scr_dyn_handle_ops_of(v)->clone(v->v.handle.ptr);
|
||||
/* A handle that holds values (a Map or Set) clones them with this
|
||||
* value as their parent; one that holds its own storage is cut off
|
||||
* by its parent's identity. */
|
||||
for (const ScrScParent *p = up; p != NULL; p = p->up) {
|
||||
if (p->node->kind == SCR_DYN_HANDLE && p->node->v.handle.ptr == v->v.handle.ptr) {
|
||||
scr_throw_error_msg(SCR_ERR_ERROR, scr_sc_cycle_message, sizeof scr_sc_cycle_message - 1);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
ScrScParent self = {v, up};
|
||||
const ScrScParent *saved_up = scr_sc_nested.up;
|
||||
ScrDynRegion *saved_region = scr_sc_nested.region;
|
||||
scr_sc_nested.up = &self;
|
||||
scr_sc_nested.region = region;
|
||||
scr_sc_nested.clone = scr_sc_clone;
|
||||
ScrDyn *out = scr_dyn_handle_ops_of(v)->clone(v->v.handle.ptr);
|
||||
scr_sc_nested.up = saved_up;
|
||||
scr_sc_nested.region = saved_region;
|
||||
return out;
|
||||
}
|
||||
/* fall through */
|
||||
case SCR_DYN_FUNC:
|
||||
@@ -19144,6 +19229,7 @@ ScrDyn *scr_dyn_native_set(ScrMap *value) {
|
||||
.iter_pack = &scr_native_set_pack,
|
||||
/* Entries live in internal slots: JSON.stringify writes {}, as Node. */
|
||||
.json_empty_object = true,
|
||||
.clone = &scr_native_set_clone,
|
||||
};
|
||||
scr_dyn_handle_install(SCR_DYNH_SET, &ops);
|
||||
return scr_dyn_new_handle(value, SCR_DYNH_SET);
|
||||
@@ -19261,6 +19347,7 @@ ScrDyn *scr_dyn_native_map(ScrMap *value) {
|
||||
.iter_pack = &scr_native_map_pack,
|
||||
/* Entries live in internal slots: JSON.stringify writes {}, as Node. */
|
||||
.json_empty_object = true,
|
||||
.clone = &scr_native_map_clone,
|
||||
};
|
||||
scr_dyn_handle_install(SCR_DYNH_MAP, &ops);
|
||||
return scr_dyn_new_handle(value, SCR_DYNH_MAP);
|
||||
|
||||
@@ -406,7 +406,9 @@ export const ICU_CPP_NAMESPACE = "6icu_78";
|
||||
* spells `6icu_78` wherever the name occurs — as the function's own
|
||||
* namespace (`_ZN6icu_78...`), in its parameter types, in vtables, typeinfo
|
||||
* and guard variables, or in a libc++ instantiation over an ICU type. No
|
||||
* symbol outside the archive carries either mark.
|
||||
* symbol outside the archive carries either mark, except emitted program
|
||||
* functions (`sc_` names), whose numbered helpers can end in `_78`
|
||||
* (`sc_f__x25_dyn_class_property_78`); the suffix test skips them.
|
||||
*
|
||||
* scr_intl.c's own functions are deliberately not tested by name. The
|
||||
* engine-free Intl paths share their prefix (`scr_intl_num_format_en_us` in
|
||||
@@ -420,7 +422,9 @@ export function icuSymbols(names) {
|
||||
return [...new Set(names)]
|
||||
.filter(
|
||||
(name) =>
|
||||
name.endsWith("_78") || name.includes(ICU_CPP_NAMESPACE) || name.startsWith("icudt78"),
|
||||
(name.endsWith("_78") && !name.startsWith("sc_")) ||
|
||||
name.includes(ICU_CPP_NAMESPACE) ||
|
||||
name.startsWith("icudt78"),
|
||||
)
|
||||
.sort();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
// structuredClone of Maps and Sets, alone and in fields of typed records:
|
||||
// keys and values are cloned, the copy is a new collection of the same
|
||||
// type, and Dates, typed arrays and nested records in the same records
|
||||
// clone too.
|
||||
const m = new Map([
|
||||
["a", 1],
|
||||
["b", 2],
|
||||
]);
|
||||
const mc = structuredClone(m);
|
||||
mc.set("c", 3);
|
||||
console.log(mc.get("a"), mc.size, m.size, mc !== m, [...mc.keys()].join(","));
|
||||
console.log(structuredClone({ m }).m.get("b"));
|
||||
|
||||
const s = new Set([1, 2, 3]);
|
||||
const sc = structuredClone(s);
|
||||
sc.delete(1);
|
||||
console.log(sc.has(2), sc.size, s.size, [...sc].join(","));
|
||||
console.log(structuredClone({ s }).s.has(3));
|
||||
|
||||
const d = new Date(86400000);
|
||||
console.log(structuredClone({ d }).d.toISOString(), structuredClone(d).getTime());
|
||||
|
||||
const t = new Uint8Array([1, 2, 3]);
|
||||
console.log(structuredClone({ t }).t[2], structuredClone(t).length);
|
||||
|
||||
// Record and array values are cloned, not shared.
|
||||
type Item = { id: number; tags: string[] };
|
||||
const items = new Map<string, Item>([["x", { id: 7, tags: ["red"] }]]);
|
||||
const itemsCopy = structuredClone(items);
|
||||
itemsCopy.get("x")!.tags.push("blue");
|
||||
console.log(items.get("x")!.tags.join(","), itemsCopy.get("x")!.tags.join(","), itemsCopy.get("x")!.id);
|
||||
|
||||
// Number, boolean and string keys keep their types.
|
||||
const byNumber = new Map<number, string>([
|
||||
[1, "one"],
|
||||
[-0, "zero"],
|
||||
]);
|
||||
const byNumberCopy = structuredClone(byNumber);
|
||||
console.log(byNumberCopy.get(1), byNumberCopy.get(0), byNumberCopy.has(2));
|
||||
const flags = new Set<boolean>([true]);
|
||||
console.log(structuredClone(flags).has(true), structuredClone(flags).has(false));
|
||||
|
||||
// Collections nested in collections and records.
|
||||
type Outer = { inner: { m: Map<string, number>; when: Date }; tags: Set<string>; bytes: Float64Array };
|
||||
const outer: Outer = {
|
||||
inner: { m: new Map([["x", 5]]), when: new Date(0) },
|
||||
tags: new Set(["t"]),
|
||||
bytes: new Float64Array([1.5]),
|
||||
};
|
||||
const outerCopy = structuredClone(outer);
|
||||
console.log(
|
||||
outerCopy.inner.m.get("x"),
|
||||
outerCopy.inner.when.getTime(),
|
||||
outerCopy.tags.has("t"),
|
||||
outerCopy.bytes[0],
|
||||
outerCopy.inner.m !== outer.inner.m,
|
||||
);
|
||||
const groups = new Map<string, Set<number>>([["evens", new Set([2, 4])]]);
|
||||
const groupsCopy = structuredClone(groups);
|
||||
groupsCopy.get("evens")!.add(6);
|
||||
console.log(groups.get("evens")!.size, groupsCopy.get("evens")!.size);
|
||||
|
||||
// An empty collection, and an entry that cannot be cloned.
|
||||
console.log(structuredClone(new Map<string, number>()).size, structuredClone(new Set<string>()).size);
|
||||
const handlers = new Map<string, () => void>([["run", () => {}]]);
|
||||
try {
|
||||
structuredClone(handlers);
|
||||
console.log("cloned");
|
||||
} catch (error) {
|
||||
console.log((error as Error).name);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 4900, "san": 55300 }
|
||||
@@ -36,6 +36,9 @@ const NON_ICU_SYMBOLS = [
|
||||
"scr_intl_segmenter_refusing",
|
||||
"scr_dyn_call",
|
||||
"scr_string_concat",
|
||||
// Emitted program functions are numbered, and a number can be 78.
|
||||
"sc_f__x25_dyn_class_property_78",
|
||||
"sc_bf__x25_arr_loop_78",
|
||||
];
|
||||
|
||||
describe("icuSymbols", () => {
|
||||
|
||||
Reference in New Issue
Block a user