From a8019503292b417a4e97b145e5aa95166ccdc662 Mon Sep 17 00:00:00 2001 From: Tom <26638278+tomblind@users.noreply.github.com> Date: Sat, 23 Feb 2019 07:14:59 -0700 Subject: [PATCH 1/3] tupleReturn improvements - fixed `@tupleReturn` on arrow functions - enabled inference of tupleReturn on arrow functions - preventing wrap+unpack when using spread operator on tupleReturn function --- src/LuaTransformer.ts | 22 +++++++++++++++----- src/TSHelper.ts | 43 ++++++++++++++++++++++++++++++++-------- test/unit/tuples.spec.ts | 41 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 13 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index c72debaeb..937b91a42 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -2972,7 +2972,15 @@ export class LuaTransformer { hasContext ? context : undefined ); - const body = ts.isBlock(node.body) ? node.body : ts.createBlock([ts.createReturn(node.body)]); + let body: ts.Block; + if (ts.isBlock(node.body)) { + body = node.body; + } else { + const ret = ts.createReturn(node.body); + body = ts.createBlock([ret]); + ret.parent = body; + body.parent = node.body.parent; + } const [transformedBody] = this.transformFunctionBody(node.parameters, body, spreadIdentifier); return tstl.createFunctionExpression( @@ -3053,9 +3061,10 @@ export class LuaTransformer { const isTupleReturnForward = node.parent && ts.isReturnStatement(node.parent) && tsHelper.isInTupleReturnFunction(node, this.checker); const isInDestructingAssignment = tsHelper.isInDestructingAssignment(node); + const isInSpread = node.parent && ts.isSpreadElement(node.parent); const returnValueIsUsed = node.parent && !ts.isExpressionStatement(node.parent); - const wrapResult = isTupleReturn && !isTupleReturnForward&& !isInDestructingAssignment - && returnValueIsUsed && !isLuaIterator; + const wrapResult = isTupleReturn && !isTupleReturnForward && !isInDestructingAssignment + && !isInSpread && returnValueIsUsed && !isLuaIterator; if (ts.isPropertyAccessExpression(node.expression)) { const result = this.transformPropertyCall(node); @@ -3707,8 +3716,11 @@ export class LuaTransformer { public transformSpreadElement(expression: ts.SpreadElement): ExpressionVisitResult { const innerExpression = this.transformExpression(expression.expression); - - return this.createUnpackCall(innerExpression, expression); + if (tsHelper.isTupleReturnCall(expression.expression, this.checker)) { + return innerExpression; + } else { + return this.createUnpackCall(innerExpression, expression); + } } public transformStringLiteral(literal: ts.StringLiteralLike): tstl.StringLiteral { diff --git a/src/TSHelper.ts b/src/TSHelper.ts index e67dfd969..347f165cb 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -187,7 +187,13 @@ export class TSHelper { public static isInTupleReturnFunction(node: ts.Node, checker: ts.TypeChecker): boolean { const declaration = TSHelper.findFirstNodeAbove(node, ts.isFunctionLike); if (declaration) { - const decorators = TSHelper.getCustomDecorators(checker.getTypeAtLocation(declaration), checker); + let functionType: ts.Type; + if (ts.isFunctionExpression(declaration) || ts.isArrowFunction(declaration)) { + functionType = TSHelper.inferAssignedType(declaration, checker); + } else { + functionType = checker.getTypeAtLocation(declaration); + } + const decorators = TSHelper.getCustomDecorators(functionType, checker); return decorators.has(DecoratorKind.TupleReturn); } else { return false; @@ -446,15 +452,36 @@ export class TSHelper { } else if (ts.isCallExpression(expression.parent)) { // Expression being passed as argument to a function - let i = expression.parent.arguments.indexOf(expression); - if (i >= 0) { + const argumentIndex = expression.parent.arguments.indexOf(expression); + if (argumentIndex >= 0) { const parentSignature = checker.getResolvedSignature(expression.parent); - const parentSignatureDeclaration = parentSignature.getDeclaration(); - if (parentSignatureDeclaration) { - if (this.getExplicitThisParameter(parentSignatureDeclaration)) { - ++i; + if (parentSignature.parameters.length > 0) { // In case function type is 'any' + const signatureIndex = Math.min(argumentIndex, parentSignature.parameters.length - 1); + let parameterType = checker.getTypeOfSymbolAtLocation( + parentSignature.parameters[signatureIndex], + expression + ); + if (TSHelper.isArrayType(parameterType, checker)) { + // Check for elipses argument + const parentSignatureDeclaration = parentSignature.getDeclaration(); + if (parentSignatureDeclaration) { + let declarationIndex = signatureIndex; + if (this.getExplicitThisParameter(parentSignatureDeclaration)) { + // Ignore 'this' parameter + ++declarationIndex; + } + const parameterDeclaration = parentSignatureDeclaration.parameters[declarationIndex]; + if ((parameterType.flags & ts.TypeFlags.Object) !== 0 + && ((parameterType as ts.ObjectType).objectFlags & ts.ObjectFlags.Reference) !== 0 + && parameterDeclaration.dotDotDotToken) + { + // Determine type from elipsis array/tuple type + const parameterTypeReference = (parameterType as ts.TypeReference); + parameterType = parameterTypeReference.typeArguments[argumentIndex - declarationIndex]; + } + } } - return checker.getTypeAtLocation(parentSignatureDeclaration.parameters[i]); + return parameterType; } } diff --git a/test/unit/tuples.spec.ts b/test/unit/tuples.spec.ts index 16f1bf935..817542c33 100644 --- a/test/unit/tuples.spec.ts +++ b/test/unit/tuples.spec.ts @@ -168,4 +168,45 @@ export class TupleTests { // Assert Expect(result).toBe(5); } + + @Test("Tuple Return on Arrow Function") + public tupleReturnOnArrowFunction(): void { + const code = + `const fn = /** @tupleReturn */ (s: string) => [s, "bar"]; + const [a, b] = fn("foo"); + return a + b;`; + const lua = util.transpileString(code); + Expect(lua).not.toContain("unpack"); + const result = util.executeLua(lua); + Expect(result).toBe("foobar"); + } + + @Test("Tuple Return Inference") + public tupleReturnInference(): void { + const code = + `/** @tupleReturn */ interface Fn { (s: string): [string, string] } + const fn: Fn = s => [s, "bar"]; + const [a, b] = fn("foo"); + return a + b;`; + const lua = util.transpileString(code); + Expect(lua).not.toContain("unpack"); + const result = util.executeLua(lua); + Expect(result).toBe("foobar"); + } + + @Test("Tuple Return in Spread") + public tupleReturnInSpread(): void { + const code = + `/** @tupleReturn */ function foo(): [string, string] { + return ["foo", "bar"]; + } + function bar(a: string, b: string) { + return a + b; + } + return bar(...foo());`; + const lua = util.transpileString(code); + Expect(lua).not.toContain("unpack"); + const result = util.executeLua(lua); + Expect(result).toBe("foobar"); + } } From 695a6875340b0379e32d80132e662de81b20da23 Mon Sep 17 00:00:00 2001 From: Tom <26638278+tomblind@users.noreply.github.com> Date: Sat, 23 Feb 2019 07:32:57 -0700 Subject: [PATCH 2/3] added tests for argument inference --- test/unit/tuples.spec.ts | 45 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/test/unit/tuples.spec.ts b/test/unit/tuples.spec.ts index 817542c33..796836c29 100644 --- a/test/unit/tuples.spec.ts +++ b/test/unit/tuples.spec.ts @@ -194,6 +194,51 @@ export class TupleTests { Expect(result).toBe("foobar"); } + @Test("Tuple Return Inference as Argument") + public tupleReturnInferenceAsArgument(): void { + const code = + `/** @tupleReturn */ interface Fn { (s: string): [string, string] } + function foo(fn: Fn) { + const [a, b] = fn("foo"); + return a + b; + } + return foo(s => [s, "bar"]);`; + const lua = util.transpileString(code); + Expect(lua).not.toContain("unpack"); + const result = util.executeLua(lua); + Expect(result).toBe("foobar"); + } + + @Test("Tuple Return Inference as Elipsis Argument") + public tupleReturnInferenceAsElipsisArgument(): void { + const code = + `/** @tupleReturn */ interface Fn { (s: string): [string, string] } + function foo(a: number, ...fn: Fn[]) { + const [a, b] = fn[0]("foo"); + return a + b; + } + return foo(7, s => [s, "bar"]);`; + const lua = util.transpileString(code); + Expect(lua).not.toContain("unpack"); + const result = util.executeLua(lua); + Expect(result).toBe("foobar"); + } + + @Test("Tuple Return Inference as Elipsis Tuple Argument") + public tupleReturnInferenceAsElipsisTupleArgument(): void { + const code = + `/** @tupleReturn */ interface Fn { (s: string): [string, string] } + function foo(a: number, ...fn: [number, Fn]) { + const [a, b] = fn[1]("foo"); + return a + b; + } + return foo(7, 17, s => [s, "bar"]);`; + const lua = util.transpileString(code); + Expect(lua).not.toContain("unpack"); + const result = util.executeLua(lua); + Expect(result).toBe("foobar"); + } + @Test("Tuple Return in Spread") public tupleReturnInSpread(): void { const code = From 55944e2e91e99f196beab7e03f323dbd38a11560 Mon Sep 17 00:00:00 2001 From: Tom <26638278+tomblind@users.noreply.github.com> Date: Sat, 23 Feb 2019 08:06:25 -0700 Subject: [PATCH 3/3] better name --- src/LuaTransformer.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 937b91a42..e04c594da 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -2976,9 +2976,9 @@ export class LuaTransformer { if (ts.isBlock(node.body)) { body = node.body; } else { - const ret = ts.createReturn(node.body); - body = ts.createBlock([ret]); - ret.parent = body; + const returnExpression = ts.createReturn(node.body); + body = ts.createBlock([returnExpression]); + returnExpression.parent = body; body.parent = node.body.parent; } const [transformedBody] = this.transformFunctionBody(node.parameters, body, spreadIdentifier);