X Tutup
Skip to content

Commit 3dc393e

Browse files
authored
Improve expression statement handling and language extensions calls (#1201)
* Improve expression statement handling and language extensions calls as expression. * Changes from PR feedback * Add more test cases
1 parent 55a3076 commit 3dc393e

File tree

12 files changed

+209
-289
lines changed

12 files changed

+209
-289
lines changed

src/transformation/builtins/index.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,17 @@ export function transformBuiltinCallExpression(
6060
node: ts.CallExpression,
6161
isOptionalCall: boolean
6262
): lua.Expression | undefined {
63+
const unsupportedOptionalCall = () => {
64+
context.diagnostics.push(unsupportedBuiltinOptionalCall(node));
65+
return lua.createNilLiteral();
66+
};
6367
const expressionType = context.checker.getTypeAtLocation(node.expression);
6468
if (ts.isIdentifier(node.expression) && isStandardLibraryType(context, expressionType, undefined)) {
6569
// TODO:
6670
checkForLuaLibType(context, expressionType);
6771
const result = transformGlobalCall(context, node);
6872
if (result) {
73+
if (isOptionalCall) return unsupportedOptionalCall();
6974
return result;
7075
}
7176
}
@@ -75,14 +80,8 @@ export function transformBuiltinCallExpression(
7580
return;
7681
}
7782

78-
assume<PropertyCallExpression>(node);
79-
8083
const isOptionalAccess = expression.questionDotToken;
81-
const unsupportedOptionalCall = () => {
82-
context.diagnostics.push(unsupportedBuiltinOptionalCall(node));
83-
return lua.createNilLiteral();
84-
};
85-
84+
assume<PropertyCallExpression>(node);
8685
// If the function being called is of type owner.func, get the type of owner
8786
const ownerType = context.checker.getTypeAtLocation(expression.expression);
8887

src/transformation/utils/diagnostics.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -128,14 +128,6 @@ export const invalidTableExtensionUse = createErrorDiagnosticFactory(
128128
"This function must be called directly and cannot be referred to."
129129
);
130130

131-
export const invalidTableDeleteExpression = createErrorDiagnosticFactory(
132-
"Table delete extension can only be called as a stand-alone statement. It cannot be used as an expression in another statement."
133-
);
134-
135-
export const invalidTableSetExpression = createErrorDiagnosticFactory(
136-
"Table set extension can only be called as a stand-alone statement. It cannot be used as an expression in another statement."
137-
);
138-
139131
export const annotationRemoved = createErrorDiagnosticFactory(
140132
(kind: AnnotationKind) =>
141133
`'@${kind}' has been removed and will no longer have any effect.` +

src/transformation/visitors/call.ts

Lines changed: 9 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -10,26 +10,11 @@ import { isValidLuaIdentifier } from "../utils/safe-names";
1010
import { isExpressionWithEvaluationEffect } from "../utils/typescript";
1111
import { transformElementAccessArgument } from "./access";
1212
import { isMultiReturnCall, shouldMultiReturnCallBeWrapped } from "./language-extensions/multi";
13-
import { isOperatorMapping, transformOperatorMappingExpression } from "./language-extensions/operators";
14-
import {
15-
isTableDeleteCall,
16-
isTableGetCall,
17-
isTableHasCall,
18-
isTableSetCall,
19-
transformTableDeleteExpression,
20-
transformTableGetExpression,
21-
transformTableHasExpression,
22-
transformTableSetExpression,
23-
} from "./language-extensions/table";
24-
import {
25-
annotationRemoved,
26-
invalidTableDeleteExpression,
27-
invalidTableSetExpression,
28-
unsupportedBuiltinOptionalCall,
29-
} from "../utils/diagnostics";
13+
import { annotationRemoved } from "../utils/diagnostics";
3014
import { moveToPrecedingTemp, transformExpressionList } from "./expression-list";
3115
import { transformInPrecedingStatementScope } from "../utils/preceding-statements";
32-
import { transformOptionalChain, getOptionalContinuationData } from "./optional-chaining";
16+
import { getOptionalContinuationData, transformOptionalChain } from "./optional-chaining";
17+
import { transformLanguageExtensionCallExpression } from "./language-extensions";
3318

3419
export type PropertyCallExpression = ts.CallExpression & { expression: ts.PropertyAccessExpression };
3520

@@ -234,52 +219,16 @@ export const transformCallExpression: FunctionVisitor<ts.CallExpression> = (node
234219
: undefined;
235220
const wrapResultInTable = isMultiReturnCall(context, node) && shouldMultiReturnCallBeWrapped(context, node);
236221

237-
const builtinResult = transformBuiltinCallExpression(context, node, optionalContinuation !== undefined);
238-
if (builtinResult) {
239-
if (optionalContinuation) {
240-
context.diagnostics.push(unsupportedBuiltinOptionalCall(node));
241-
}
242-
return wrapResultInTable ? wrapInTable(builtinResult) : builtinResult;
243-
}
244-
245222
if (isTupleReturnCall(context, node)) {
246223
context.diagnostics.push(annotationRemoved(node, AnnotationKind.TupleReturn));
247224
}
248225

249-
if (isOperatorMapping(context, node)) {
250-
if (optionalContinuation) {
251-
context.diagnostics.push(unsupportedBuiltinOptionalCall(node));
252-
return lua.createNilLiteral();
253-
}
254-
return transformOperatorMappingExpression(context, node);
255-
}
256-
257-
if (isTableDeleteCall(context, node)) {
258-
context.diagnostics.push(invalidTableDeleteExpression(node));
259-
context.addPrecedingStatements(transformTableDeleteExpression(context, node));
260-
return lua.createNilLiteral();
261-
}
262-
263-
if (isTableGetCall(context, node)) {
264-
if (optionalContinuation) {
265-
context.diagnostics.push(unsupportedBuiltinOptionalCall(node));
266-
return lua.createNilLiteral();
267-
}
268-
return transformTableGetExpression(context, node);
269-
}
270-
271-
if (isTableHasCall(context, node)) {
272-
if (optionalContinuation) {
273-
context.diagnostics.push(unsupportedBuiltinOptionalCall(node));
274-
return lua.createNilLiteral();
275-
}
276-
return transformTableHasExpression(context, node);
277-
}
278-
279-
if (isTableSetCall(context, node)) {
280-
context.diagnostics.push(invalidTableSetExpression(node));
281-
context.addPrecedingStatements(transformTableSetExpression(context, node));
282-
return lua.createNilLiteral();
226+
const builtinOrExtensionResult =
227+
transformBuiltinCallExpression(context, node, optionalContinuation !== undefined) ??
228+
transformLanguageExtensionCallExpression(context, node, optionalContinuation !== undefined);
229+
if (builtinOrExtensionResult) {
230+
// unsupportedOptionalCall diagnostic already present
231+
return wrapResultInTable ? wrapInTable(builtinOrExtensionResult) : builtinOrExtensionResult;
283232
}
284233

285234
if (ts.isPropertyAccessExpression(node.expression)) {
Lines changed: 24 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,10 @@
11
import * as ts from "typescript";
22
import * as lua from "../../LuaAST";
3-
import { FunctionVisitor } from "../context";
3+
import { FunctionVisitor, tempSymbolId, TransformationContext } from "../context";
44
import { transformBinaryExpressionStatement } from "./binary-expression";
5-
import {
6-
isTableDeleteCall,
7-
isTableSetCall,
8-
transformTableDeleteExpression,
9-
transformTableSetExpression,
10-
} from "./language-extensions/table";
115
import { transformUnaryExpressionStatement } from "./unary-expression";
12-
import { transformVoidExpressionStatement } from "./void";
136

147
export const transformExpressionStatement: FunctionVisitor<ts.ExpressionStatement> = (node, context) => {
15-
const expression = node.expression;
16-
17-
if (ts.isCallExpression(expression) && isTableDeleteCall(context, expression)) {
18-
return transformTableDeleteExpression(context, expression);
19-
}
20-
21-
if (ts.isCallExpression(expression) && isTableSetCall(context, expression)) {
22-
return transformTableSetExpression(context, expression);
23-
}
24-
25-
if (ts.isVoidExpression(expression)) {
26-
return transformVoidExpressionStatement(expression, context);
27-
}
28-
298
const unaryExpressionResult = transformUnaryExpressionStatement(context, node);
309
if (unaryExpressionResult) {
3110
return unaryExpressionResult;
@@ -36,9 +15,27 @@ export const transformExpressionStatement: FunctionVisitor<ts.ExpressionStatemen
3615
return binaryExpressionResult;
3716
}
3817

39-
const result = context.transformExpression(expression);
40-
return lua.isCallExpression(result) || lua.isMethodCallExpression(result)
41-
? lua.createExpressionStatement(result)
42-
: // Assign expression statements to dummy to make sure they're legal Lua
43-
lua.createVariableDeclarationStatement(lua.createAnonymousIdentifier(), result);
18+
return transformExpressionToStatement(context, node.expression);
4419
};
20+
21+
export function transformExpressionToStatement(
22+
context: TransformationContext,
23+
expression: ts.Expression
24+
): lua.Statement | undefined {
25+
const result = context.transformExpression(expression);
26+
27+
const isTempVariable = lua.isIdentifier(result) && result.symbolId === tempSymbolId;
28+
if (isTempVariable) {
29+
return undefined;
30+
}
31+
// "synthetic": no side effects and no original source
32+
const isSyntheticExpression = (lua.isIdentifier(result) || lua.isLiteral(result)) && result.line === undefined;
33+
if (isSyntheticExpression) {
34+
return undefined;
35+
}
36+
if (lua.isCallExpression(result) || lua.isMethodCallExpression(result)) {
37+
return lua.createExpressionStatement(result);
38+
}
39+
// Assign expression statements to dummy to make sure they're legal Lua
40+
return lua.createVariableDeclarationStatement(lua.createAnonymousIdentifier(), result);
41+
}

src/transformation/visitors/identifier.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as ts from "typescript";
22
import * as lua from "../../LuaAST";
33
import { transformBuiltinIdentifierExpression } from "../builtins";
44
import { createPromiseIdentifier, isPromiseClass } from "../builtins/promise";
5-
import { FunctionVisitor, TransformationContext } from "../context";
5+
import { FunctionVisitor, tempSymbolId, TransformationContext } from "../context";
66
import { AnnotationKind, isForRangeType } from "../utils/annotations";
77
import {
88
invalidMultiFunctionUse,
@@ -25,7 +25,7 @@ import { isOptionalContinuation } from "./optional-chaining";
2525

2626
export function transformIdentifier(context: TransformationContext, identifier: ts.Identifier): lua.Identifier {
2727
if (isOptionalContinuation(identifier)) {
28-
return lua.createIdentifier(identifier.text);
28+
return lua.createIdentifier(identifier.text, undefined, tempSymbolId);
2929
}
3030

3131
if (isMultiFunctionNode(context, identifier)) {
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { TransformationContext } from "../../context";
2+
import * as ts from "typescript";
3+
import * as lua from "../../../LuaAST";
4+
import { transformOperatorMappingExpression } from "./operators";
5+
import { transformTableExtensionCall } from "./table";
6+
7+
export function transformLanguageExtensionCallExpression(
8+
context: TransformationContext,
9+
node: ts.CallExpression,
10+
isOptionalCall: boolean
11+
): lua.Expression | undefined {
12+
const operatorMapping = transformOperatorMappingExpression(context, node, isOptionalCall);
13+
if (operatorMapping) {
14+
return operatorMapping;
15+
}
16+
const tableCall = transformTableExtensionCall(context, node, isOptionalCall);
17+
if (tableCall) {
18+
return tableCall;
19+
}
20+
}

src/transformation/visitors/language-extensions/operators.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import * as extensions from "../../utils/language-extensions";
55
import { assert } from "../../../utils";
66
import { getFunctionTypeForCall } from "../../utils/typescript";
77
import { LuaTarget } from "../../../CompilerOptions";
8-
import { unsupportedForTarget } from "../../utils/diagnostics";
8+
import { unsupportedBuiltinOptionalCall, unsupportedForTarget } from "../../utils/diagnostics";
99

1010
const binaryOperatorMappings = new Map<extensions.ExtensionKind, lua.BinaryOperator>([
1111
[extensions.ExtensionKind.AdditionOperatorType, lua.SyntaxKind.AdditionOperator],
@@ -82,10 +82,15 @@ export function isOperatorMapping(context: TransformationContext, node: ts.CallE
8282

8383
export function transformOperatorMappingExpression(
8484
context: TransformationContext,
85-
node: ts.CallExpression
86-
): lua.Expression {
85+
node: ts.CallExpression,
86+
isOptionalCall: boolean
87+
): lua.Expression | undefined {
8788
const extensionKind = getOperatorMapExtensionKindForCall(context, node);
88-
assert(extensionKind);
89+
if (!extensionKind) return undefined;
90+
if (isOptionalCall) {
91+
context.diagnostics.push(unsupportedBuiltinOptionalCall(node));
92+
return lua.createNilLiteral();
93+
}
8994

9095
const isBefore53 =
9196
context.luaTarget === LuaTarget.Lua51 ||

0 commit comments

Comments
 (0)
X Tutup