Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/Errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,9 @@ export class TSTLErrors {
+ "the TupleReturn decorator.",
node);
}

public static UnsupportedUnionAccessor = (node: ts.Node) => {
return new TranspileError(`Unsupported union of accessor with non-accessor types.`,
node);
}
}
28 changes: 25 additions & 3 deletions src/TSHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,11 +245,22 @@ export class TSHelper {
}
}

public static hasGetAccessor(node: ts.Node, checker: ts.TypeChecker): boolean {
public static typeHasGetAccessor(type: ts.Type, name: ts.__String, checker: ts.TypeChecker): boolean | undefined {
if (type.isUnion()) {
if (type.types.some(t => this.typeHasGetAccessor(t, name, checker))) {
// undefined if only a subset of types implements the accessor
return type.types.every(t => this.typeHasGetAccessor(t, name, checker)) ? true : undefined;
}
return false;
}
return this.forTypeOrAnySupertype(type, checker, t => this.hasExplicitGetAccessor(t, name));
}

public static hasGetAccessor(node: ts.Node, checker: ts.TypeChecker): boolean | undefined {
if (ts.isPropertyAccessExpression(node)) {
const name = node.name.escapedText;
const type = checker.getTypeAtLocation(node.expression);
return this.forTypeOrAnySupertype(type, checker, t => this.hasExplicitGetAccessor(t, name));
return this.typeHasGetAccessor(type, name, checker);
}
return false;
}
Expand All @@ -261,11 +272,22 @@ export class TSHelper {
}
}

public static typeHasSetAccessor(type: ts.Type, name: ts.__String, checker: ts.TypeChecker): boolean | undefined {
if (type.isUnion()) {
if (type.types.some(t => this.typeHasSetAccessor(t, name, checker))) {
// undefined if only a subset of types implements the accessor
return type.types.every(t => this.typeHasSetAccessor(t, name, checker)) ? true : undefined;
}
return false;
}
return this.forTypeOrAnySupertype(type, checker, t => this.hasExplicitSetAccessor(t, name));
}

public static hasSetAccessor(node: ts.Node, checker: ts.TypeChecker): boolean {
if (ts.isPropertyAccessExpression(node)) {
const name = node.name.escapedText;
const type = checker.getTypeAtLocation(node.expression);
return this.forTypeOrAnySupertype(type, checker, t => this.hasExplicitSetAccessor(t, name));
return this.typeHasSetAccessor(type, name, checker);
}
return false;
}
Expand Down
10 changes: 8 additions & 2 deletions src/Transpiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -965,8 +965,11 @@ export abstract class LuaTranspiler {
}

public transpileAssignment(node: ts.BinaryExpression, lhs: string, rhs: string): string {
if (tsHelper.hasSetAccessor(node.left, this.checker)) {
const hasSetAccessor = tsHelper.hasSetAccessor(node.left, this.checker);
if (hasSetAccessor) {
return this.transpileSetAccessor(node.left as ts.PropertyAccessExpression, rhs);
} else if (hasSetAccessor === undefined) {
throw TSTLErrors.UnsupportedUnionAccessor(node);
}

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

if (tsHelper.hasGetAccessor(node, this.checker)) {
const hasGetAccessor = tsHelper.hasGetAccessor(node, this.checker);
if (hasGetAccessor) {
return this.transpileGetAccessor(node);
} else if (hasGetAccessor === undefined) {
throw TSTLErrors.UnsupportedUnionAccessor(node);
}

// Check for primitive types to override
Expand Down
28 changes: 28 additions & 0 deletions test/unit/expressions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,34 @@ export class ExpressionTests {
Expect(result).toBe(expected);
}

@TestCase("return x.value;", 1)
@TestCase("x.value = 3; return x.value;", 3)
@Test("Union accessors")
public unionAccessors(expression: string, expected: any): void {
const source = `class A{ get value(){ return this.v || 1; } set value(v){ this.v = v; } v: number; }
class B{ get value(){ return this.v || 2; } set value(v){ this.v = v; } v: number; }
let x: A|B = new A();
${expression}`;

const lua = util.transpileString(source);
const result = util.executeLua(lua);
Expect(result).toBe(expected);
}

@TestCase("x.value = 3;")
@TestCase("return x.value;")
@Test("Unsupported Union accessors")
public unsupportedUnionAccessors(expression: string): void {
const source = `class A{ get value(){ return 1; } }
class B{ value:number = 3; }
let x: A|B = new A();
${expression}`;
Expect(() => { util.transpileString(source); }).toThrowError(
TranspileError,
"Unsupported union of accessor with non-accessor types."
);
}

@TestCase("i++", 10)
@TestCase("i--", 10)
@TestCase("++i", 11)
Expand Down