Skip to content

Commit 2835c51

Browse files
authored
typeof improvements (#625)
* typeof now uses lib function, with optimized path for comparisons * simplified tests * refactored transformBinaryExpression a bit
1 parent 59acb58 commit 2835c51

4 files changed

Lines changed: 112 additions & 24 deletions

File tree

src/LuaLib.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export enum LuaLibFeature {
5555
StringStartsWith = "StringStartsWith",
5656
Symbol = "Symbol",
5757
SymbolRegistry = "SymbolRegistry",
58+
TypeOf = "TypeOf",
5859
}
5960

6061
const luaLibDependencies: { [lib in LuaLibFeature]?: LuaLibFeature[] } = {

src/LuaTransformer.ts

Lines changed: 57 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2657,10 +2657,42 @@ export class LuaTransformer {
26572657
}
26582658
}
26592659

2660+
protected transformTypeOfLiteralComparison(
2661+
typeOfExpression: ts.TypeOfExpression,
2662+
comparedExpression: tstl.StringLiteral,
2663+
operator: ts.BinaryOperator,
2664+
tsOriginal: ts.Node
2665+
): ExpressionVisitResult {
2666+
if (comparedExpression.value === "object") {
2667+
comparedExpression.value = "table";
2668+
} else if (comparedExpression.value === "undefined") {
2669+
comparedExpression.value = "nil";
2670+
}
2671+
const innerExpression = this.transformExpression(typeOfExpression.expression);
2672+
const typeCall = tstl.createCallExpression(tstl.createIdentifier("type"), [innerExpression], typeOfExpression);
2673+
return this.transformBinaryOperation(typeCall, comparedExpression, operator, tsOriginal);
2674+
}
2675+
2676+
protected transformComparisonExpression(expression: ts.BinaryExpression): ExpressionVisitResult {
2677+
const left = this.transformExpression(expression.left);
2678+
const right = this.transformExpression(expression.right);
2679+
const operator = expression.operatorToken.kind;
2680+
2681+
// Custom handling for 'typeof(foo) === "type"'
2682+
if (ts.isTypeOfExpression(expression.left) && tstl.isStringLiteral(right)) {
2683+
return this.transformTypeOfLiteralComparison(expression.left, right, operator, expression);
2684+
} else if (ts.isTypeOfExpression(expression.right) && tstl.isStringLiteral(left)) {
2685+
return this.transformTypeOfLiteralComparison(expression.right, left, operator, expression);
2686+
}
2687+
2688+
return this.transformBinaryOperation(left, right, operator, expression);
2689+
}
2690+
26602691
public transformBinaryExpression(expression: ts.BinaryExpression): ExpressionVisitResult {
2661-
// Check if this is an assignment token, then handle accordingly
2692+
const operator = expression.operatorToken.kind;
26622693

2663-
const [isCompound, replacementOperator] = tsHelper.isBinaryAssignmentToken(expression.operatorToken.kind);
2694+
// Check if this is an assignment token, then handle accordingly
2695+
const [isCompound, replacementOperator] = tsHelper.isBinaryAssignmentToken(operator);
26642696
if (isCompound && replacementOperator) {
26652697
return this.transformCompoundAssignmentExpression(
26662698
expression,
@@ -2671,26 +2703,27 @@ export class LuaTransformer {
26712703
);
26722704
}
26732705

2674-
const lhs = this.transformExpression(expression.left);
2675-
const rhs = this.transformExpression(expression.right);
2676-
26772706
// Transpile operators
2678-
switch (expression.operatorToken.kind) {
2707+
switch (operator) {
26792708
case ts.SyntaxKind.AmpersandToken:
26802709
case ts.SyntaxKind.BarToken:
26812710
case ts.SyntaxKind.CaretToken:
26822711
case ts.SyntaxKind.LessThanLessThanToken:
26832712
case ts.SyntaxKind.GreaterThanGreaterThanToken:
26842713
case ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken:
2685-
return this.transformBinaryBitOperation(expression, lhs, rhs, expression.operatorToken.kind);
26862714
case ts.SyntaxKind.PlusToken:
26872715
case ts.SyntaxKind.AmpersandAmpersandToken:
26882716
case ts.SyntaxKind.BarBarToken:
26892717
case ts.SyntaxKind.MinusToken:
26902718
case ts.SyntaxKind.AsteriskToken:
26912719
case ts.SyntaxKind.AsteriskAsteriskToken:
26922720
case ts.SyntaxKind.SlashToken:
2693-
case ts.SyntaxKind.PercentToken:
2721+
case ts.SyntaxKind.PercentToken: {
2722+
const lhs = this.transformExpression(expression.left);
2723+
const rhs = this.transformExpression(expression.right);
2724+
return this.transformBinaryOperation(lhs, rhs, operator, expression);
2725+
}
2726+
26942727
case ts.SyntaxKind.GreaterThanToken:
26952728
case ts.SyntaxKind.GreaterThanEqualsToken:
26962729
case ts.SyntaxKind.LessThanToken:
@@ -2699,19 +2732,26 @@ export class LuaTransformer {
26992732
case ts.SyntaxKind.EqualsEqualsEqualsToken:
27002733
case ts.SyntaxKind.ExclamationEqualsToken:
27012734
case ts.SyntaxKind.ExclamationEqualsEqualsToken:
2702-
return this.transformBinaryOperation(lhs, rhs, expression.operatorToken.kind, expression);
2735+
return this.transformComparisonExpression(expression);
2736+
27032737
case ts.SyntaxKind.EqualsToken:
27042738
return this.transformAssignmentExpression(expression);
2705-
case ts.SyntaxKind.InKeyword:
2739+
2740+
case ts.SyntaxKind.InKeyword: {
2741+
const lhs = this.transformExpression(expression.left);
2742+
const rhs = this.transformExpression(expression.right);
27062743
const indexExpression = tstl.createTableIndexExpression(rhs, lhs);
27072744
return tstl.createBinaryExpression(
27082745
indexExpression,
27092746
tstl.createNilLiteral(),
27102747
tstl.SyntaxKind.InequalityOperator,
27112748
expression
27122749
);
2750+
}
27132751

2714-
case ts.SyntaxKind.InstanceOfKeyword:
2752+
case ts.SyntaxKind.InstanceOfKeyword: {
2753+
const lhs = this.transformExpression(expression.left);
2754+
const rhs = this.transformExpression(expression.right);
27152755
const rhsType = this.checker.getTypeAtLocation(expression.right);
27162756
const decorators = tsHelper.getCustomDecorators(rhsType, this.checker);
27172757

@@ -2729,16 +2769,19 @@ export class LuaTransformer {
27292769
}
27302770

27312771
return this.transformLuaLibFunction(LuaLibFeature.InstanceOf, expression, lhs, rhs);
2772+
}
27322773

2733-
case ts.SyntaxKind.CommaToken:
2774+
case ts.SyntaxKind.CommaToken: {
2775+
const rhs = this.transformExpression(expression.right);
27342776
return this.createImmediatelyInvokedFunctionExpression(
27352777
this.statementVisitResultToArray(this.transformExpressionStatement(expression.left)),
27362778
rhs,
27372779
expression
27382780
);
2781+
}
27392782

27402783
default:
2741-
throw TSTLErrors.UnsupportedKind("binary operator", expression.operatorToken.kind, expression);
2784+
throw TSTLErrors.UnsupportedKind("binary operator", operator, expression);
27422785
}
27432786
}
27442787

@@ -4491,16 +4534,7 @@ export class LuaTransformer {
44914534

44924535
public transformTypeOfExpression(expression: ts.TypeOfExpression): ExpressionVisitResult {
44934536
const innerExpression = this.transformExpression(expression.expression);
4494-
const typeFunctionIdentifier = tstl.createIdentifier("type");
4495-
const typeCall = tstl.createCallExpression(typeFunctionIdentifier, [innerExpression]);
4496-
const tableString = tstl.createStringLiteral("table");
4497-
const objectString = tstl.createStringLiteral("object");
4498-
const condition = tstl.createBinaryExpression(typeCall, tableString, tstl.SyntaxKind.EqualityOperator);
4499-
const andClause = tstl.createBinaryExpression(condition, objectString, tstl.SyntaxKind.AndOperator);
4500-
4501-
return tstl.createParenthesizedExpression(
4502-
tstl.createBinaryExpression(andClause, tstl.cloneNode(typeCall), tstl.SyntaxKind.OrOperator, expression)
4503-
);
4537+
return this.transformLuaLibFunction(LuaLibFeature.TypeOf, expression, innerExpression);
45044538
}
45054539

45064540
public transformSpreadElement(expression: ts.SpreadElement): ExpressionVisitResult {

src/lualib/TypeOf.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
declare function type(this: void, value: unknown): string;
2+
3+
function __TS__TypeOf(this: void, value: unknown): string {
4+
const luaType = type(value);
5+
if (luaType === "table") {
6+
return "object";
7+
} else if (luaType === "nil") {
8+
return "undefined";
9+
} else {
10+
return luaType;
11+
}
12+
}

test/unit/typechecking.spec.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ test("typeof function", () => {
4040
test.each(["null", "undefined"])("typeof undefined (%p)", inp => {
4141
const result = util.transpileAndExecute(`return typeof ${inp};`);
4242

43-
expect(result).toBe("nil");
43+
expect(result).toBe("undefined");
4444
});
4545

4646
test("instanceof", () => {
@@ -140,3 +140,44 @@ test("instanceof Symbol.hasInstance", () => {
140140

141141
expect(result).toBe(true);
142142
});
143+
144+
test.each([
145+
{ expression: "{}", operator: "===", compareTo: "object", expectResult: true },
146+
{ expression: "{}", operator: "!==", compareTo: "object", expectResult: false },
147+
{ expression: "{}", operator: "==", compareTo: "object", expectResult: true },
148+
{ expression: "{}", operator: "!=", compareTo: "object", expectResult: false },
149+
{ expression: "{}", operator: "<=", compareTo: "object", expectResult: true },
150+
{ expression: "{}", operator: "<", compareTo: "object", expectResult: false },
151+
{ expression: "undefined", operator: "===", compareTo: "undefined", expectResult: true },
152+
{ expression: "() => {}", operator: "===", compareTo: "function", expectResult: true },
153+
{ expression: "1", operator: "===", compareTo: "number", expectResult: true },
154+
{ expression: "true", operator: "===", compareTo: "boolean", expectResult: true },
155+
{ expression: `"foo"`, operator: "===", compareTo: "string", expectResult: true },
156+
])("typeof literal comparison (%p)", ({ expression, operator, compareTo, expectResult }) => {
157+
const code = `
158+
let val = ${expression};
159+
return typeof val ${operator} "${compareTo}";`;
160+
161+
expect(util.transpileAndExecute(code)).toBe(expectResult);
162+
});
163+
164+
test.each([
165+
{ expression: "{}", operator: "===", compareTo: "object", expectResult: true },
166+
{ expression: "{}", operator: "!==", compareTo: "object", expectResult: false },
167+
{ expression: "{}", operator: "==", compareTo: "object", expectResult: true },
168+
{ expression: "{}", operator: "!=", compareTo: "object", expectResult: false },
169+
{ expression: "{}", operator: "<=", compareTo: "object", expectResult: true },
170+
{ expression: "{}", operator: "<", compareTo: "object", expectResult: false },
171+
{ expression: "undefined", operator: "===", compareTo: "undefined", expectResult: true },
172+
{ expression: "() => {}", operator: "===", compareTo: "function", expectResult: true },
173+
{ expression: "1", operator: "===", compareTo: "number", expectResult: true },
174+
{ expression: "true", operator: "===", compareTo: "boolean", expectResult: true },
175+
{ expression: `"foo"`, operator: "===", compareTo: "string", expectResult: true },
176+
])("typeof non-literal comparison (%p)", ({ expression, operator, compareTo, expectResult }) => {
177+
const code = `
178+
let val = ${expression};
179+
let compareTo = "${compareTo}";
180+
return typeof val ${operator} compareTo;`;
181+
182+
expect(util.transpileAndExecute(code)).toBe(expectResult);
183+
});

0 commit comments

Comments
 (0)