Skip to content

Commit b63bf86

Browse files
committed
Fix optimized vararg spread with casts
Also fixes for other outer expressions.
1 parent 2df7047 commit b63bf86

File tree

5 files changed

+70
-22
lines changed

5 files changed

+70
-22
lines changed

src/transformation/visitors/errors.ts

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -44,31 +44,38 @@ export const transformTryStatement: FunctionVisitor<ts.TryStatement> = (statemen
4444
const catchParameter = statement.catchClause.variableDeclaration
4545
? transformIdentifier(context, statement.catchClause.variableDeclaration.name as ts.Identifier)
4646
: undefined;
47-
const catchParameters = () => (catchParameter ? [lua.cloneIdentifier(catchParameter)] : []);
48-
47+
const catchFunction = lua.createFunctionExpression(
48+
catchBlock,
49+
catchParameter ? [lua.cloneIdentifier(catchParameter)] : []
50+
);
4951
const catchIdentifier = lua.createIdentifier("____catch");
50-
const catchFunction = lua.createFunctionExpression(catchBlock, catchParameters());
5152
result.push(lua.createVariableDeclarationStatement(catchIdentifier, catchFunction));
5253

54+
const hasReturn = tryScope.functionReturned ?? catchScope.functionReturned;
55+
5356
const tryReturnIdentifiers = [tryResultIdentifier]; // ____try
54-
if (returnedIdentifier) {
55-
tryReturnIdentifiers.push(returnedIdentifier); // ____returned or catch variable
56-
if (tryScope.functionReturned || catchScope.functionReturned) {
57+
if (hasReturn || statement.catchClause.variableDeclaration) {
58+
tryReturnIdentifiers.push(returnedIdentifier); // ____returned
59+
if (hasReturn) {
5760
tryReturnIdentifiers.push(returnValueIdentifier); // ____returnValue
5861
returnCondition = lua.cloneIdentifier(returnedIdentifier);
5962
}
6063
}
6164
result.push(lua.createVariableDeclarationStatement(tryReturnIdentifiers, tryCall));
6265

63-
// Wrap catch in function if try or catch has return
64-
const catchCall = lua.createCallExpression(catchIdentifier, [lua.cloneIdentifier(returnedIdentifier)]);
65-
const catchAssign = lua.createAssignmentStatement(
66-
[lua.cloneIdentifier(returnedIdentifier), lua.cloneIdentifier(returnValueIdentifier)],
67-
catchCall
66+
const catchCall = lua.createCallExpression(
67+
catchIdentifier,
68+
statement.catchClause.variableDeclaration ? [lua.cloneIdentifier(returnedIdentifier)] : []
6869
);
70+
const catchCallStatement = hasReturn
71+
? lua.createAssignmentStatement(
72+
[lua.cloneIdentifier(returnedIdentifier), lua.cloneIdentifier(returnValueIdentifier)],
73+
catchCall
74+
)
75+
: lua.createExpressionStatement(catchCall);
6976

7077
const notTryCondition = lua.createUnaryExpression(tryResultIdentifier, lua.SyntaxKind.NotOperator);
71-
result.push(lua.createIfStatement(notTryCondition, lua.createBlock([catchAssign])));
78+
result.push(lua.createIfStatement(notTryCondition, lua.createBlock([catchCallStatement])));
7279
} else if (tryScope.functionReturned) {
7380
// try with return, but no catch
7481
// returnedIdentifier = lua.createIdentifier("____returned");

src/transformation/visitors/spread.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,15 @@ import { isMultiReturnCall } from "./language-extensions/multi";
1616
import { annotationRemoved } from "../utils/diagnostics";
1717
import { isGlobalVarargConstant } from "./language-extensions/vararg";
1818

19+
function skipOuterExpressionParents(node: ts.Node) {
20+
while (ts.isOuterExpression(node)) {
21+
node = node.parent;
22+
}
23+
return node;
24+
}
25+
1926
export function isOptimizedVarArgSpread(context: TransformationContext, symbol: ts.Symbol, identifier: ts.Identifier) {
20-
if (!ts.isSpreadElement(identifier.parent)) {
27+
if (!ts.isSpreadElement(skipOuterExpressionParents(identifier.parent))) {
2128
return false;
2229
}
2330

@@ -63,20 +70,21 @@ export function isOptimizedVarArgSpread(context: TransformationContext, symbol:
6370

6471
// TODO: Currently it's also used as an array member
6572
export const transformSpreadElement: FunctionVisitor<ts.SpreadElement> = (node, context) => {
66-
if (ts.isIdentifier(node.expression)) {
67-
if (isVarargType(context, node.expression)) {
73+
const tsInnerExpression = ts.skipOuterExpressions(node.expression);
74+
if (ts.isIdentifier(tsInnerExpression)) {
75+
if (isVarargType(context, tsInnerExpression)) {
6876
context.diagnostics.push(annotationRemoved(node, AnnotationKind.Vararg));
6977
}
70-
const symbol = context.checker.getSymbolAtLocation(node.expression);
71-
if (symbol && isOptimizedVarArgSpread(context, symbol, node.expression)) {
78+
const symbol = context.checker.getSymbolAtLocation(tsInnerExpression);
79+
if (symbol && isOptimizedVarArgSpread(context, symbol, tsInnerExpression)) {
7280
return lua.createDotsLiteral(node);
7381
}
7482
}
7583

7684
const innerExpression = context.transformExpression(node.expression);
77-
if (isMultiReturnCall(context, node.expression)) return innerExpression;
85+
if (isMultiReturnCall(context, tsInnerExpression)) return innerExpression;
7886

79-
const type = context.checker.getTypeAtLocation(node.expression);
87+
const type = context.checker.getTypeAtLocation(node.expression); // not ts-inner expression, in case of casts
8088
if (isArrayType(context, type)) {
8189
return createUnpackCall(context, innerExpression, node);
8290
}

src/typescript-internal.d.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { DiagnosticsProducingTypeChecker } from "./transformation/context";
2-
32
export {};
43

54
declare module "typescript" {
@@ -31,6 +30,13 @@ declare module "typescript" {
3130

3231
function transformJsx(context: TransformationContext): (x: SourceFile) => SourceFile;
3332

34-
function skipParentheses(node: Expression): Expression;
35-
function skipParentheses(node: Node): Node;
33+
export type OuterExpression =
34+
| ParenthesizedExpression
35+
| TypeAssertion
36+
| AsExpression
37+
| NonNullExpression
38+
| PartiallyEmittedExpression;
39+
40+
function skipOuterExpressions(node: Expression, kinds?: OuterExpressionKinds): Expression;
41+
export function isOuterExpression(node: Node, kinds?: OuterExpressionKinds): node is OuterExpression;
3642
}

test/unit/__snapshots__/spread.spec.ts.snap

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,21 @@ end
1717
return ____exports"
1818
`;
1919

20+
exports[`vararg spread optimization With cast 1`] = `
21+
"local ____exports = {}
22+
function ____exports.__main(self)
23+
local function pick(self, ...)
24+
local args = {...}
25+
return args[2]
26+
end
27+
local function test(self, ...)
28+
return pick(nil, ...)
29+
end
30+
return test(nil, \\"a\\", \\"b\\", \\"c\\")
31+
end
32+
return ____exports"
33+
`;
34+
2035
exports[`vararg spread optimization basic use 1`] = `
2136
"local ____exports = {}
2237
function ____exports.__main(self)

test/unit/spread.spec.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,18 @@ describe("vararg spread optimization", () => {
265265
.expectLuaToMatchSnapshot()
266266
.expectToMatchJsResult();
267267
});
268+
269+
test("With cast", () => {
270+
util.testFunction`
271+
function pick(...args: any[]) { return args[1]; }
272+
function test<F extends (...args: any)=>any>(...args: Parameters<F>) {
273+
return pick(...(args as any[]));
274+
}
275+
return test<(...args: string[])=>void>("a", "b", "c");
276+
`
277+
.expectLuaToMatchSnapshot()
278+
.expectToMatchJsResult();
279+
});
268280
});
269281

270282
describe("vararg spread de-optimization", () => {

0 commit comments

Comments
 (0)