Skip to content

Commit a246177

Browse files
ark120202Perryvw
authored andcommitted
Weak collections (#406)
* Add weak collections * Remove runtime weak reference type checks * Fix weak collections tests with enabled diagnostics
1 parent afb65e2 commit a246177

File tree

8 files changed

+327
-3
lines changed

8 files changed

+327
-3
lines changed

src/LuaLib.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ export enum LuaLibFeature {
2323
Iterator = "Iterator",
2424
Map = "Map",
2525
Set = "Set",
26+
WeakMap = "WeakMap",
27+
WeakSet = "WeakSet",
2628
StringReplace = "StringReplace",
2729
StringSplit = "StringSplit",
2830
StringConcat = "StringConcat",
@@ -33,6 +35,8 @@ const luaLibDependencies: {[lib in LuaLibFeature]?: LuaLibFeature[]} = {
3335
Iterator: [LuaLibFeature.Symbol],
3436
Map: [LuaLibFeature.InstanceOf, LuaLibFeature.Iterator, LuaLibFeature.Symbol],
3537
Set: [LuaLibFeature.InstanceOf, LuaLibFeature.Iterator, LuaLibFeature.Symbol],
38+
WeakMap: [LuaLibFeature.InstanceOf, LuaLibFeature.Iterator, LuaLibFeature.Symbol],
39+
WeakSet: [LuaLibFeature.InstanceOf, LuaLibFeature.Iterator, LuaLibFeature.Symbol],
3640
};
3741

3842
export class LuaLib {

src/LuaTransformer.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3541,6 +3541,12 @@ export class LuaTransformer {
35413541
case "Set":
35423542
this.importLuaLibFeature(LuaLibFeature.Set);
35433543
return;
3544+
case "WeakMap":
3545+
this.importLuaLibFeature(LuaLibFeature.WeakMap);
3546+
return;
3547+
case "WeakSet":
3548+
this.importLuaLibFeature(LuaLibFeature.WeakSet);
3549+
return;
35443550
}
35453551
}
35463552
}

src/lualib/Set.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class Set<TValue> {
2626
const arr = other as TValue[];
2727
this.size = arr.length;
2828
for (const value of arr) {
29-
this.items[value as any] = true as any;
29+
this.items[value as any] = true;
3030
}
3131
}
3232
}

src/lualib/WeakMap.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
declare function setmetatable<T extends object>(obj: T, metatable: any): T;
2+
3+
class WeakMap<TKey extends object, TValue> {
4+
private items: {[key: string]: TValue}; // Type of key is actually TKey
5+
6+
constructor(other: Iterable<[TKey, TValue]> | Array<[TKey, TValue]>) {
7+
this.items = {};
8+
setmetatable(this.items, { __mode: 'k' });
9+
10+
if (other) {
11+
const iterable = other as Iterable<[TKey, TValue]>;
12+
if (iterable[Symbol.iterator]) {
13+
// Iterate manually because WeakMap is compiled with ES5 which doesn't support Iterables in for...of
14+
const iterator = iterable[Symbol.iterator]();
15+
while (true) {
16+
const result = iterator.next();
17+
if (result.done) {
18+
break;
19+
}
20+
const value: [TKey, TValue] = result.value; // Ensures index is offset when tuple is accessed
21+
this.set(value[0], value[1]);
22+
}
23+
} else {
24+
for (const kvp of other as Array<[TKey, TValue]>) {
25+
this.items[kvp[0] as any] = kvp[1];
26+
}
27+
}
28+
}
29+
}
30+
31+
public delete(key: TKey): boolean {
32+
const contains = this.has(key);
33+
this.items[key as any] = undefined;
34+
return contains;
35+
}
36+
37+
public get(key: TKey): TValue {
38+
return this.items[key as any];
39+
}
40+
41+
public has(key: TKey): boolean {
42+
return this.items[key as any] !== undefined;
43+
}
44+
45+
public set(key: TKey, value: TValue): WeakMap<TKey, TValue> {
46+
this.items[key as any] = value;
47+
return this;
48+
}
49+
}

src/lualib/WeakSet.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
declare function setmetatable<T extends object>(obj: T, metatable: any): T;
2+
3+
class WeakSet<TValue extends object> {
4+
private items: {[key: string]: boolean}; // Key type is actually TValue
5+
6+
constructor(other: Iterable<TValue> | TValue[]) {
7+
this.items = {};
8+
setmetatable(this.items, { __mode: 'k' });
9+
10+
if (other) {
11+
const iterable = other as Iterable<TValue>;
12+
if (iterable[Symbol.iterator]) {
13+
// Iterate manually because WeakSet is compiled with ES5 which doesn't support Iterables in for...of
14+
const iterator = iterable[Symbol.iterator]();
15+
while (true) {
16+
const result = iterator.next();
17+
if (result.done) {
18+
break;
19+
}
20+
this.add(result.value);
21+
}
22+
} else {
23+
for (const value of other as TValue[]) {
24+
this.items[value as any] = true;
25+
}
26+
}
27+
}
28+
}
29+
30+
public add(value: TValue): WeakSet<TValue> {
31+
this.items[value as any] = true;
32+
return this;
33+
}
34+
35+
public delete(value: TValue): boolean {
36+
const contains = this.has(value);
37+
this.items[value as any] = undefined;
38+
return contains;
39+
}
40+
41+
public has(value: TValue): boolean {
42+
return this.items[value as any] === true;
43+
}
44+
}

test/src/util.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,14 +84,14 @@ export function transpileAndExecute(
8484
tsStr: string,
8585
compilerOptions?: CompilerOptions,
8686
luaHeader?: string,
87-
tsHeader?: string
87+
tsHeader?: string,
88+
ignoreDiagnosticsOverride = process.argv[2] === "--ignoreDiagnostics"
8889
): any
8990
{
9091
const wrappedTsString = `declare function JSONStringify(p: any): string;
9192
${tsHeader ? tsHeader : ""}
9293
function __runTest(): any {${tsStr}}`;
9394

94-
const ignoreDiagnosticsOverride = process.argv[2] === "--ignoreDiagnostics";
9595
const lua = `${luaHeader ? luaHeader : ""}
9696
${transpileString(wrappedTsString, compilerOptions, ignoreDiagnosticsOverride)}
9797
return __runTest();`;

test/unit/lualib/weakMap.spec.ts

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import { Expect, Test } from "alsatian";
2+
import * as util from "../../src/util";
3+
4+
export class WeakMapTests {
5+
private initRefsTs = `let ref = {};
6+
let ref2 = () => {};`;
7+
8+
@Test("weakMap constructor")
9+
public weakMapConstructor(): void
10+
{
11+
const result = util.transpileAndExecute(this.initRefsTs + `
12+
let mymap = new WeakMap([[ref, 1]]);
13+
return mymap.get(ref);
14+
`);
15+
16+
Expect(result).toBe(1);
17+
}
18+
19+
@Test("weakMap iterable constructor")
20+
public weakMapIterableConstructor(): void
21+
{
22+
const result = util.transpileAndExecute(this.initRefsTs + `
23+
let mymap = new WeakMap([[ref, 1], [ref2, 2]]);
24+
return mymap.has(ref) && mymap.has(ref2);
25+
`);
26+
27+
Expect(result).toBe(true);
28+
}
29+
30+
@Test("weakMap iterable constructor map")
31+
public weakMapIterableConstructor2(): void
32+
{
33+
const result = util.transpileAndExecute(this.initRefsTs + `
34+
let mymap = new WeakMap(new Map([[ref, 1], [ref2, 2]]));
35+
return mymap.has(ref) && mymap.has(ref2);
36+
`);
37+
38+
Expect(result).toBe(true);
39+
}
40+
41+
@Test("weakMap delete")
42+
public weakMapDelete(): void
43+
{
44+
const contains = util.transpileAndExecute(this.initRefsTs + `
45+
let mymap = new WeakMap([[ref, true], [ref2, true]]);
46+
mymap.delete(ref2);
47+
return mymap.has(ref) && !mymap.has(ref2);
48+
`);
49+
50+
Expect(contains).toBe(true);
51+
}
52+
53+
@Test("weakMap get")
54+
public weakMapGet(): void
55+
{
56+
const result = util.transpileAndExecute(this.initRefsTs + `
57+
let mymap = new WeakMap([[ref, 1], [{}, 2]]);
58+
return mymap.get(ref);
59+
`);
60+
61+
Expect(result).toBe(1);
62+
}
63+
64+
@Test("weakMap get missing")
65+
public weakMapGetMissing(): void
66+
{
67+
const result = util.transpileAndExecute(this.initRefsTs + `
68+
let mymap = new WeakMap([[{}, true]]);
69+
return mymap.get({});
70+
`);
71+
72+
Expect(result).toBe(undefined);
73+
}
74+
75+
@Test("weakMap has")
76+
public weakMapHas(): void
77+
{
78+
const contains = util.transpileAndExecute(this.initRefsTs + `
79+
let mymap = new WeakMap([[ref, true]]);
80+
return mymap.has(ref);
81+
`);
82+
83+
Expect(contains).toBe(true);
84+
}
85+
86+
@Test("weakMap has false")
87+
public weakMapHasFalse(): void
88+
{
89+
const contains = util.transpileAndExecute(this.initRefsTs + `
90+
let mymap = new WeakMap([[ref, true]]);
91+
return mymap.has(ref2);
92+
`);
93+
94+
Expect(contains).toBe(false);
95+
}
96+
97+
@Test("weakMap has null")
98+
public weakMapHasNull(): void
99+
{
100+
const contains = util.transpileAndExecute(this.initRefsTs + `
101+
let mymap = new WeakMap([[{}, true]]);
102+
return mymap.has(null);
103+
`);
104+
105+
Expect(contains).toBe(false);
106+
}
107+
108+
@Test("weakMap set")
109+
public weakMapSet(): void
110+
{
111+
const init = this.initRefsTs + `
112+
let mymap = new WeakMap();
113+
mymap.set(ref, 5);
114+
`;
115+
116+
const has = util.transpileAndExecute(init + `return mymap.has(ref);`);
117+
Expect(has).toBe(true);
118+
119+
const value = util.transpileAndExecute(init + `return mymap.get(ref)`);
120+
Expect(value).toBe(5);
121+
}
122+
123+
@Test("weakMap has no map features")
124+
public weakMapHasNoMapFeatures(): void
125+
{
126+
const transpileAndExecute = (tsStr: string) => util.transpileAndExecute(tsStr, undefined, undefined, undefined, true);
127+
Expect(transpileAndExecute(`return new WeakMap().size`)).toBe(undefined);
128+
Expect(() => transpileAndExecute(`new WeakMap().clear()`)).toThrow();
129+
Expect(() => transpileAndExecute(`new WeakMap().keys()`)).toThrow();
130+
Expect(() => transpileAndExecute(`new WeakMap().values()`)).toThrow();
131+
Expect(() => transpileAndExecute(`new WeakMap().entries()`)).toThrow();
132+
Expect(() => transpileAndExecute(`new WeakMap().forEach(() => {})`)).toThrow();
133+
}
134+
}

test/unit/lualib/weakSet.spec.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { Expect, Test } from "alsatian";
2+
import * as util from "../../src/util";
3+
4+
export class WeakSetTests {
5+
private initRefsTs = `let ref = {};
6+
let ref2 = () => {};`;
7+
8+
@Test("weakSet constructor")
9+
public weakSetConstructor(): void
10+
{
11+
const result = util.transpileAndExecute(this.initRefsTs + `
12+
let myset = new WeakSet([ref]);
13+
return myset.has(ref)
14+
`);
15+
16+
Expect(result).toBe(true);
17+
}
18+
19+
@Test("weakSet iterable constructor")
20+
public weakSetIterableConstructor(): void
21+
{
22+
const result = util.transpileAndExecute(this.initRefsTs + `
23+
let myset = new WeakSet([ref, ref2]);
24+
return myset.has(ref) && myset.has(ref2);
25+
`);
26+
27+
Expect(result).toBe(true);
28+
}
29+
30+
@Test("weakSet iterable constructor set")
31+
public weakSetIterableConstructorSet(): void
32+
{
33+
const result = util.transpileAndExecute(this.initRefsTs + `
34+
let myset = new WeakSet(new Set([ref, ref2]));
35+
return myset.has(ref) && myset.has(ref2);
36+
`);
37+
38+
Expect(result).toBe(true);
39+
}
40+
41+
@Test("weakSet add")
42+
public weakSetAdd(): void
43+
{
44+
const result = util.transpileAndExecute(this.initRefsTs + `
45+
let myset = new WeakSet();
46+
myset.add(ref);
47+
return myset.has(ref);
48+
`);
49+
50+
Expect(result).toBe(true);
51+
}
52+
53+
@Test("weakSet add different references")
54+
public weakSetAddDifferentReferences(): void
55+
{
56+
const result = util.transpileAndExecute(this.initRefsTs + `
57+
let myset = new WeakSet();
58+
myset.add({});
59+
return myset.has({});
60+
`);
61+
62+
Expect(result).toBe(false);
63+
}
64+
65+
@Test("weakSet delete")
66+
public weakSetDelete(): void
67+
{
68+
const contains = util.transpileAndExecute(this.initRefsTs + `
69+
let myset = new WeakSet([ref, ref2]);
70+
myset.delete(ref);
71+
return myset.has(ref2) && !myset.has(ref);
72+
`);
73+
Expect(contains).toBe(true);
74+
}
75+
76+
@Test("weakSet has no set features")
77+
public weakSetHasNoSetFeatures(): void
78+
{
79+
const transpileAndExecute = (tsStr: string) => util.transpileAndExecute(tsStr, undefined, undefined, undefined, true);
80+
Expect(transpileAndExecute(`return new WeakSet().size`)).toBe(undefined);
81+
Expect(() => transpileAndExecute(`new WeakSet().clear()`)).toThrow();
82+
Expect(() => transpileAndExecute(`new WeakSet().keys()`)).toThrow();
83+
Expect(() => transpileAndExecute(`new WeakSet().values()`)).toThrow();
84+
Expect(() => transpileAndExecute(`new WeakSet().entries()`)).toThrow();
85+
Expect(() => transpileAndExecute(`new WeakSet().forEach(() => {})`)).toThrow();
86+
}
87+
}

0 commit comments

Comments
 (0)