Skip to content

Commit 3c8018c

Browse files
TheLartiansPerryvw
authored andcommitted
Add support for union accessors (#344)
* check unions for accessors * add test case * update test * handle setters * update tests * consistent indentation
1 parent 851205b commit 3c8018c

File tree

4 files changed

+66
-5
lines changed

4 files changed

+66
-5
lines changed

src/Errors.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,4 +116,9 @@ export class TSTLErrors {
116116
+ "the TupleReturn decorator.",
117117
node);
118118
}
119+
120+
public static UnsupportedUnionAccessor = (node: ts.Node) => {
121+
return new TranspileError(`Unsupported union of accessor with non-accessor types.`,
122+
node);
123+
}
119124
}

src/TSHelper.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -251,11 +251,22 @@ export class TSHelper {
251251
}
252252
}
253253

254-
public static hasGetAccessor(node: ts.Node, checker: ts.TypeChecker): boolean {
254+
public static typeHasGetAccessor(type: ts.Type, name: ts.__String, checker: ts.TypeChecker): boolean | undefined {
255+
if (type.isUnion()) {
256+
if (type.types.some(t => this.typeHasGetAccessor(t, name, checker))) {
257+
// undefined if only a subset of types implements the accessor
258+
return type.types.every(t => this.typeHasGetAccessor(t, name, checker)) ? true : undefined;
259+
}
260+
return false;
261+
}
262+
return this.forTypeOrAnySupertype(type, checker, t => this.hasExplicitGetAccessor(t, name));
263+
}
264+
265+
public static hasGetAccessor(node: ts.Node, checker: ts.TypeChecker): boolean | undefined {
255266
if (ts.isPropertyAccessExpression(node)) {
256267
const name = node.name.escapedText;
257268
const type = checker.getTypeAtLocation(node.expression);
258-
return this.forTypeOrAnySupertype(type, checker, t => this.hasExplicitGetAccessor(t, name));
269+
return this.typeHasGetAccessor(type, name, checker);
259270
}
260271
return false;
261272
}
@@ -267,11 +278,22 @@ export class TSHelper {
267278
}
268279
}
269280

281+
public static typeHasSetAccessor(type: ts.Type, name: ts.__String, checker: ts.TypeChecker): boolean | undefined {
282+
if (type.isUnion()) {
283+
if (type.types.some(t => this.typeHasSetAccessor(t, name, checker))) {
284+
// undefined if only a subset of types implements the accessor
285+
return type.types.every(t => this.typeHasSetAccessor(t, name, checker)) ? true : undefined;
286+
}
287+
return false;
288+
}
289+
return this.forTypeOrAnySupertype(type, checker, t => this.hasExplicitSetAccessor(t, name));
290+
}
291+
270292
public static hasSetAccessor(node: ts.Node, checker: ts.TypeChecker): boolean {
271293
if (ts.isPropertyAccessExpression(node)) {
272294
const name = node.name.escapedText;
273295
const type = checker.getTypeAtLocation(node.expression);
274-
return this.forTypeOrAnySupertype(type, checker, t => this.hasExplicitSetAccessor(t, name));
296+
return this.typeHasSetAccessor(type, name, checker);
275297
}
276298
return false;
277299
}

src/Transpiler.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -965,8 +965,11 @@ export abstract class LuaTranspiler {
965965
}
966966

967967
public transpileAssignment(node: ts.BinaryExpression, lhs: string, rhs: string): string {
968-
if (tsHelper.hasSetAccessor(node.left, this.checker)) {
968+
const hasSetAccessor = tsHelper.hasSetAccessor(node.left, this.checker);
969+
if (hasSetAccessor) {
969970
return this.transpileSetAccessor(node.left as ts.PropertyAccessExpression, rhs);
971+
} else if (hasSetAccessor === undefined) {
972+
throw TSTLErrors.UnsupportedUnionAccessor(node);
970973
}
971974

972975
// Validate assignment
@@ -1611,8 +1614,11 @@ export abstract class LuaTranspiler {
16111614
public transpilePropertyAccessExpression(node: ts.PropertyAccessExpression): string {
16121615
const property = node.name.text;
16131616

1614-
if (tsHelper.hasGetAccessor(node, this.checker)) {
1617+
const hasGetAccessor = tsHelper.hasGetAccessor(node, this.checker);
1618+
if (hasGetAccessor) {
16151619
return this.transpileGetAccessor(node);
1620+
} else if (hasGetAccessor === undefined) {
1621+
throw TSTLErrors.UnsupportedUnionAccessor(node);
16161622
}
16171623

16181624
// Check for primitive types to override

test/unit/expressions.spec.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,34 @@ export class ExpressionTests {
317317
Expect(result).toBe(expected);
318318
}
319319

320+
@TestCase("return x.value;", 1)
321+
@TestCase("x.value = 3; return x.value;", 3)
322+
@Test("Union accessors")
323+
public unionAccessors(expression: string, expected: any): void {
324+
const source = `class A{ get value(){ return this.v || 1; } set value(v){ this.v = v; } v: number; }
325+
class B{ get value(){ return this.v || 2; } set value(v){ this.v = v; } v: number; }
326+
let x: A|B = new A();
327+
${expression}`;
328+
329+
const lua = util.transpileString(source);
330+
const result = util.executeLua(lua);
331+
Expect(result).toBe(expected);
332+
}
333+
334+
@TestCase("x.value = 3;")
335+
@TestCase("return x.value;")
336+
@Test("Unsupported Union accessors")
337+
public unsupportedUnionAccessors(expression: string): void {
338+
const source = `class A{ get value(){ return 1; } }
339+
class B{ value:number = 3; }
340+
let x: A|B = new A();
341+
${expression}`;
342+
Expect(() => { util.transpileString(source); }).toThrowError(
343+
TranspileError,
344+
"Unsupported union of accessor with non-accessor types."
345+
);
346+
}
347+
320348
@TestCase("i++", 10)
321349
@TestCase("i--", 10)
322350
@TestCase("++i", 11)

0 commit comments

Comments
 (0)