Skip to content

Commit f744a37

Browse files
committed
typeof now uses lib function, with optimized path for comparisons
1 parent d30f7ed commit f744a37

4 files changed

Lines changed: 92 additions & 15 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: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2655,6 +2655,22 @@ export class LuaTransformer {
26552655
}
26562656
}
26572657

2658+
protected transformTypeOfLiteralComparison(
2659+
typeOfExpression: ts.TypeOfExpression,
2660+
comparedExpression: tstl.StringLiteral,
2661+
operator: ts.BinaryOperator,
2662+
tsOriginal: ts.Node
2663+
): ExpressionVisitResult {
2664+
if (comparedExpression.value === "object") {
2665+
comparedExpression.value = "table";
2666+
} else if (comparedExpression.value === "undefined") {
2667+
comparedExpression.value = "nil";
2668+
}
2669+
const innerExpression = this.transformExpression(typeOfExpression.expression);
2670+
const typeCall = tstl.createCallExpression(tstl.createIdentifier("type"), [innerExpression], typeOfExpression);
2671+
return this.transformBinaryOperation(typeCall, comparedExpression, operator, tsOriginal);
2672+
}
2673+
26582674
public transformBinaryExpression(expression: ts.BinaryExpression): ExpressionVisitResult {
26592675
// Check if this is an assignment token, then handle accordingly
26602676

@@ -2673,14 +2689,15 @@ export class LuaTransformer {
26732689
const rhs = this.transformExpression(expression.right);
26742690

26752691
// Transpile operators
2676-
switch (expression.operatorToken.kind) {
2692+
const operator = expression.operatorToken.kind;
2693+
switch (operator) {
26772694
case ts.SyntaxKind.AmpersandToken:
26782695
case ts.SyntaxKind.BarToken:
26792696
case ts.SyntaxKind.CaretToken:
26802697
case ts.SyntaxKind.LessThanLessThanToken:
26812698
case ts.SyntaxKind.GreaterThanGreaterThanToken:
26822699
case ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken:
2683-
return this.transformBinaryBitOperation(expression, lhs, rhs, expression.operatorToken.kind);
2700+
return this.transformBinaryBitOperation(expression, lhs, rhs, operator);
26842701
case ts.SyntaxKind.PlusToken:
26852702
case ts.SyntaxKind.AmpersandAmpersandToken:
26862703
case ts.SyntaxKind.BarBarToken:
@@ -2689,6 +2706,7 @@ export class LuaTransformer {
26892706
case ts.SyntaxKind.AsteriskAsteriskToken:
26902707
case ts.SyntaxKind.SlashToken:
26912708
case ts.SyntaxKind.PercentToken:
2709+
return this.transformBinaryOperation(lhs, rhs, operator, expression);
26922710
case ts.SyntaxKind.GreaterThanToken:
26932711
case ts.SyntaxKind.GreaterThanEqualsToken:
26942712
case ts.SyntaxKind.LessThanToken:
@@ -2697,7 +2715,13 @@ export class LuaTransformer {
26972715
case ts.SyntaxKind.EqualsEqualsEqualsToken:
26982716
case ts.SyntaxKind.ExclamationEqualsToken:
26992717
case ts.SyntaxKind.ExclamationEqualsEqualsToken:
2700-
return this.transformBinaryOperation(lhs, rhs, expression.operatorToken.kind, expression);
2718+
// Custom handling for 'typeof(foo) === "type"'
2719+
if (ts.isTypeOfExpression(expression.left) && tstl.isStringLiteral(rhs)) {
2720+
return this.transformTypeOfLiteralComparison(expression.left, rhs, operator, expression);
2721+
} else if (ts.isTypeOfExpression(expression.right) && tstl.isStringLiteral(lhs)) {
2722+
return this.transformTypeOfLiteralComparison(expression.right, lhs, operator, expression);
2723+
}
2724+
return this.transformBinaryOperation(lhs, rhs, operator, expression);
27012725
case ts.SyntaxKind.EqualsToken:
27022726
return this.transformAssignmentExpression(expression);
27032727
case ts.SyntaxKind.InKeyword:
@@ -2736,7 +2760,7 @@ export class LuaTransformer {
27362760
);
27372761

27382762
default:
2739-
throw TSTLErrors.UnsupportedKind("binary operator", expression.operatorToken.kind, expression);
2763+
throw TSTLErrors.UnsupportedKind("binary operator", operator, expression);
27402764
}
27412765
}
27422766

@@ -4489,16 +4513,7 @@ export class LuaTransformer {
44894513

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

45044519
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: 50 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,52 @@ 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+
if (typeof val ${operator} "${compareTo}") {
160+
return "TRUE";
161+
} else {
162+
return "FALSE";
163+
}`;
164+
165+
expect(util.transpileAndExecute(code)).toBe(expectResult);
166+
});
167+
168+
test.each([
169+
{ expression: "{}", operator: "===", compareTo: "object", expectResult: "TRUE" },
170+
{ expression: "{}", operator: "!==", compareTo: "object", expectResult: "FALSE" },
171+
{ expression: "{}", operator: "==", compareTo: "object", expectResult: "TRUE" },
172+
{ expression: "{}", operator: "!=", compareTo: "object", expectResult: "FALSE" },
173+
{ expression: "{}", operator: "<=", compareTo: "object", expectResult: "TRUE" },
174+
{ expression: "{}", operator: "<", compareTo: "object", expectResult: "FALSE" },
175+
{ expression: "undefined", operator: "===", compareTo: "undefined", expectResult: "TRUE" },
176+
{ expression: "() => {}", operator: "===", compareTo: "function", expectResult: "TRUE" },
177+
{ expression: "1", operator: "===", compareTo: "number", expectResult: "TRUE" },
178+
{ expression: "true", operator: "===", compareTo: "boolean", expectResult: "TRUE" },
179+
{ expression: `"foo"`, operator: "===", compareTo: "string", expectResult: "TRUE" },
180+
])("typeof non-literal comparison (%p)", ({ expression, operator, compareTo, expectResult }) => {
181+
const code = `
182+
let val = ${expression};
183+
let compareTo = "${compareTo}";
184+
if (typeof val ${operator} compareTo) {
185+
return "TRUE";
186+
} else {
187+
return "FALSE";
188+
}`;
189+
190+
expect(util.transpileAndExecute(code)).toBe(expectResult);
191+
});

0 commit comments

Comments
 (0)