Skip to content

Commit 466124e

Browse files
committed
feat: add IterableWeakMap
1 parent c2294aa commit 466124e

File tree

6 files changed

+303
-1
lines changed

6 files changed

+303
-1
lines changed

.vscode/settings.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
"editor.defaultFormatter": "denoland.vscode-deno",
77
"cSpell.words": [
88
"iwset",
9+
"weakref",
10+
"wmap",
911
"wset"
1012
]
1113
}

iterable_weak_map.test.ts

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
import { assert, assertEquals, assertFalse } from "testing/asserts.ts";
2+
import { IterableWeakMap } from "./iterable_weak_map.ts";
3+
4+
function* iterate<T>(items: T[]): IterableIterator<T> {
5+
for (const item of items) {
6+
yield item;
7+
}
8+
}
9+
10+
Deno.test("IterableWeakMap, constructor", () => {
11+
assertEquals(new IterableWeakMap().size, 0);
12+
assertEquals(new IterableWeakMap(undefined).size, 0);
13+
assertEquals(new IterableWeakMap(null).size, 0);
14+
15+
assertEquals(new IterableWeakMap([]).size, 0);
16+
assertEquals(new IterableWeakMap(iterate([])).size, 0);
17+
18+
assertEquals(new IterableWeakMap([[{}, 1]]).size, 1);
19+
assertEquals(new IterableWeakMap(iterate([[{}, 1] as const])).size, 1);
20+
});
21+
22+
Deno.test("IterableWeakMap, comparison Map, WeakMap", () => {
23+
const map = new Map();
24+
const wmap = new WeakMap();
25+
const iwmap = new IterableWeakMap();
26+
27+
assertEquals(map.size, 0);
28+
// assertEquals(wmap.size, 0);
29+
assertEquals(iwmap.size, 0);
30+
31+
const obj1 = {};
32+
const obj2 = {};
33+
34+
assertEquals(map.get(obj1), undefined);
35+
assertEquals(wmap.get(obj1), undefined);
36+
assertEquals(iwmap.get(obj1), undefined);
37+
38+
assertEquals(map.set(obj1, "1"), map);
39+
assertEquals(wmap.set(obj1, "1"), wmap);
40+
assertEquals(iwmap.set(obj1, "1"), iwmap);
41+
42+
assertEquals(map.get(obj1), "1");
43+
assertEquals(wmap.get(obj1), "1");
44+
assertEquals(iwmap.get(obj1), "1");
45+
46+
assertEquals(map.size, 1);
47+
// assertEquals(wmap.size, 0);
48+
assertEquals(iwmap.size, 1);
49+
50+
// add the same object again
51+
assertEquals(map.set(obj1, "2"), map);
52+
assertEquals(wmap.set(obj1, "2"), wmap);
53+
assertEquals(iwmap.set(obj1, "2"), iwmap);
54+
55+
assertEquals(map.get(obj1), "2");
56+
assertEquals(wmap.get(obj1), "2");
57+
assertEquals(iwmap.get(obj1), "2");
58+
59+
assertEquals(map.size, 1);
60+
// assertEquals(wmap.size, 0);
61+
assertEquals(iwmap.size, 1);
62+
63+
assertEquals(map.set(obj2, true), map);
64+
assertEquals(wmap.set(obj2, true), wmap);
65+
assertEquals(iwmap.set(obj2, true), iwmap);
66+
67+
assertEquals(map.size, 2);
68+
// assertEquals(wmap.size, 0);
69+
assertEquals(iwmap.size, 2);
70+
71+
assert(map.has(obj1));
72+
assert(wmap.has(obj1));
73+
assert(iwmap.has(obj1));
74+
75+
// delete the object
76+
assert(map.delete(obj1));
77+
assert(wmap.delete(obj1));
78+
assert(iwmap.delete(obj1));
79+
80+
assertEquals(map.size, 1);
81+
// assertEquals(wmap.size, 0);
82+
assertEquals(iwmap.size, 1);
83+
84+
assertFalse(map.delete(obj1));
85+
assertFalse(wmap.delete(obj1));
86+
assertFalse(iwmap.delete(obj1));
87+
88+
assertEquals(map.size, 1);
89+
// assertEquals(wmap.size, 0);
90+
assertEquals(iwmap.size, 1);
91+
92+
assertFalse(map.has(obj1));
93+
assertFalse(wmap.has(obj1));
94+
assertFalse(iwmap.has(obj1));
95+
96+
assertEquals(map.get(obj1), undefined);
97+
assertEquals(wmap.get(obj1), undefined);
98+
assertEquals(iwmap.get(obj1), undefined);
99+
100+
assertFalse(map.clear());
101+
// assertFalse(wmap.clear());
102+
assertFalse(iwmap.clear());
103+
104+
assertEquals(map.size, 0);
105+
// assertEquals(wmap.size, 0);
106+
assertEquals(iwmap.size, 0);
107+
108+
assertEquals(map.toString(), "[object Map]");
109+
assertEquals(wmap.toString(), "[object WeakMap]");
110+
assertEquals(iwmap.toString(), "[object IterableWeakMap]");
111+
});
112+
113+
Deno.test("IterableWeakMap, iterable", () => {
114+
const tuples: [{}, number][] = [[{}, 1], [{}, 2], [{}, 3]];
115+
116+
const maps = [
117+
new Map(tuples),
118+
new IterableWeakMap(tuples),
119+
];
120+
121+
for (const map of maps) {
122+
assertEquals(map.size, 3);
123+
124+
{
125+
let i = 0;
126+
map.forEach(function (this: unknown, k, v, s) {
127+
assert(this === undefined);
128+
assert(k === tuples[i][1]);
129+
assert(v === tuples[i][0]);
130+
assert(s === map);
131+
i++;
132+
});
133+
assertFalse(i === 0);
134+
}
135+
{
136+
let i = 0;
137+
map.forEach(function (this: unknown, k, v, s) {
138+
assert(this === ":D");
139+
assert(k === tuples[i][1]);
140+
assert(v === tuples[i][0]);
141+
assert(s === map);
142+
i++;
143+
}, ":D");
144+
assertFalse(i === 0);
145+
}
146+
{
147+
let i = 0;
148+
for (const [k, v] of map) {
149+
assert(k === tuples[i][0]);
150+
assert(v === tuples[i++][1]);
151+
}
152+
assertFalse(i === 0);
153+
}
154+
{
155+
let i = 0;
156+
for (const k of map.keys()) {
157+
assert(k === tuples[i++][0]);
158+
}
159+
assertFalse(i === 0);
160+
}
161+
{
162+
let i = 0;
163+
for (const v of map.values()) {
164+
assert(v === tuples[i++][1]);
165+
}
166+
assertFalse(i === 0);
167+
}
168+
{
169+
let i = 0;
170+
for (const [k, v] of map.entries()) {
171+
assert(k === tuples[i][0]);
172+
assert(v === tuples[i++][1]);
173+
}
174+
assertFalse(i === 0);
175+
}
176+
}
177+
});
178+
179+
Deno.test("IterableWeakMap, garbage collect", async () => {
180+
let removedCount = 0;
181+
let insertedCount = 0;
182+
const register = new FinalizationRegistry(() => {
183+
removedCount++;
184+
});
185+
186+
const map = new IterableWeakMap();
187+
for (let i = 0; removedCount < 100; i++) {
188+
await new Promise((resolve) => setTimeout(resolve, 16));
189+
const data = {};
190+
map.set(data, i);
191+
register.register(data, i);
192+
insertedCount++;
193+
}
194+
195+
assertEquals(insertedCount - removedCount, map.size);
196+
assertEquals([...map].length, map.size);
197+
});

iterable_weak_map.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// deno-lint-ignore-file ban-types
2+
3+
import { IterableWeakSet } from "./iterable_weak_set.ts";
4+
5+
export class IterableWeakMap<K extends object, V>
6+
implements WeakMap<K, V>, Map<K, V> {
7+
#weakMap = new WeakMap<K, V>();
8+
#set = new IterableWeakSet<K>();
9+
10+
constructor(entries?: readonly (readonly [K, V])[] | null);
11+
constructor(iterable: Iterable<readonly [K, V]>);
12+
constructor(iterable?: Iterable<readonly [K, V]> | null) {
13+
for (const [key, value] of iterable ?? []) {
14+
this.set(key, value);
15+
}
16+
}
17+
18+
get size(): number {
19+
return this.#set.size;
20+
}
21+
22+
clear(): void {
23+
for (const key of this.#set) {
24+
this.delete(key);
25+
}
26+
}
27+
28+
delete(key: K): boolean {
29+
const ref = this.#weakMap.get(key);
30+
if (ref) {
31+
this.#weakMap.delete(key);
32+
this.#set.delete(key);
33+
return true;
34+
}
35+
return false;
36+
}
37+
38+
get(key: K): V | undefined {
39+
return this.#weakMap.get(key);
40+
}
41+
42+
has(key: K): boolean {
43+
return this.#weakMap.has(key);
44+
}
45+
46+
set(key: K, value: V): this {
47+
this.#weakMap.set(key, value);
48+
this.#set.add(key);
49+
return this;
50+
}
51+
52+
get [Symbol.toStringTag]() {
53+
return "IterableWeakMap";
54+
}
55+
56+
forEach(
57+
callbackfn: (value: V, key: K, map: Map<K, V>) => void,
58+
thisArg?: unknown,
59+
): void {
60+
for (const [key, value] of this[Symbol.iterator]()) {
61+
callbackfn.call(thisArg, value, key, this);
62+
}
63+
}
64+
65+
*[Symbol.iterator](): IterableIterator<[K, V]> {
66+
for (const key of this.#set) {
67+
yield [key, this.#weakMap.get(key)!];
68+
}
69+
}
70+
71+
entries(): IterableIterator<[K, V]> {
72+
return this[Symbol.iterator]();
73+
}
74+
75+
keys(): IterableIterator<K> {
76+
return this.#set[Symbol.iterator]();
77+
}
78+
79+
*values(): IterableIterator<V> {
80+
for (const key of this.#set) {
81+
yield this.#weakMap.get(key)!;
82+
}
83+
}
84+
}

iterable_weak_set.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,24 @@
11
import { assert, assertEquals, assertFalse } from "testing/asserts.ts";
22
import { IterableWeakSet } from "./iterable_weak_set.ts";
33

4+
function* iterate<T>(items: T[]): IterableIterator<T> {
5+
for (const item of items) {
6+
yield item;
7+
}
8+
}
9+
10+
Deno.test("IterableWeakSet, constructor", () => {
11+
assertEquals(new IterableWeakSet().size, 0);
12+
assertEquals(new IterableWeakSet(undefined).size, 0);
13+
assertEquals(new IterableWeakSet(null).size, 0);
14+
15+
assertEquals(new IterableWeakSet([]).size, 0);
16+
assertEquals(new IterableWeakSet(iterate([])).size, 0);
17+
18+
assertEquals(new IterableWeakSet([{}]).size, 1);
19+
assertEquals(new IterableWeakSet(iterate([{}])).size, 1);
20+
});
21+
422
Deno.test("IterableWeakSet, comparison Set, WeakSet", () => {
523
const set = new Set();
624
const wset = new WeakSet();

iterable_weak_set.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export class IterableWeakSet<T extends object> implements WeakSet<T>, Set<T> {
99

1010
constructor(values?: readonly T[] | null);
1111
constructor(iterable: Iterable<T>);
12-
constructor(iterable: Iterable<T> | readonly T[] | null = null) {
12+
constructor(iterable: Iterable<T> | null = null) {
1313
for (const value of iterable ?? []) {
1414
this.add(value);
1515
}

mod.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export { IterableWeakSet } from "./iterable_weak_set.ts";
2+
export { IterableWeakMap } from "./iterable_weak_map.ts";

0 commit comments

Comments
 (0)