diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index ab7587e97..c5d43c47f 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -1088,7 +1088,7 @@ export class LuaTransformer { } const type = this.checker.getTypeAtLocation(node); - const context = tsHelper.getFunctionContextType(type, this.checker, this.program) !== ContextType.Void + const context = tsHelper.getFunctionContextType(type, this.checker) !== ContextType.Void ? this.createSelfIdentifier() : undefined; const [paramNames, dots, restParamName] = this.transformParameters(node.parameters, context); @@ -1594,7 +1594,7 @@ export class LuaTransformer { } const type = this.checker.getTypeAtLocation(functionDeclaration); - const context = tsHelper.getFunctionContextType(type, this.checker, this.program) !== ContextType.Void + const context = tsHelper.getFunctionContextType(type, this.checker) !== ContextType.Void ? this.createSelfIdentifier() : undefined; const [params, dotsLiteral, restParamName] = this.transformParameters(functionDeclaration.parameters, context); @@ -1794,7 +1794,7 @@ export class LuaTransformer { const expressionType = this.checker.getTypeAtLocation(statement.expression); this.validateFunctionAssignment(statement, expressionType, returnType); } - if (tsHelper.isInTupleReturnFunction(statement, this.checker, this.program)) { + if (tsHelper.isInTupleReturnFunction(statement, this.checker)) { // Parent function is a TupleReturn function if (ts.isArrayLiteralExpression(statement.expression)) { // If return expression is an array literal, leave out brackets. @@ -3005,7 +3005,7 @@ export class LuaTransformer { ): ExpressionVisitResult { const type = this.checker.getTypeAtLocation(node); - const hasContext = tsHelper.getFunctionContextType(type, this.checker, this.program) !== ContextType.Void; + const hasContext = tsHelper.getFunctionContextType(type, this.checker) !== ContextType.Void; // Build parameter string const [paramNames, dotsLiteral, spreadIdentifier] = this.transformParameters( node.parameters, @@ -3099,7 +3099,7 @@ export class LuaTransformer { const isTupleReturn = tsHelper.isTupleReturnCall(node, this.checker); const isTupleReturnForward = node.parent && ts.isReturnStatement(node.parent) - && tsHelper.isInTupleReturnFunction(node, this.checker, this.program); + && 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); @@ -3885,7 +3885,7 @@ export class LuaTransformer { public transformFunctionCallExpression(node: ts.CallExpression): tstl.CallExpression { const expression = node.expression as ts.PropertyAccessExpression; const callerType = this.checker.getTypeAtLocation(expression.expression); - if (tsHelper.getFunctionContextType(callerType, this.checker, this.program) === ContextType.Void) { + if (tsHelper.getFunctionContextType(callerType, this.checker) === ContextType.Void) { throw TSTLErrors.UnsupportedSelfFunctionConversion(node); } const params = this.transformArguments(node.arguments); @@ -4320,8 +4320,8 @@ export class LuaTransformer { fromTypeCache.add(toType); // Check function assignments - const fromContext = tsHelper.getFunctionContextType(fromType, this.checker, this.program); - const toContext = tsHelper.getFunctionContextType(toType, this.checker, this.program); + const fromContext = tsHelper.getFunctionContextType(fromType, this.checker); + const toContext = tsHelper.getFunctionContextType(toType, this.checker); if (fromContext === ContextType.Mixed || toContext === ContextType.Mixed) { throw TSTLErrors.UnsupportedOverloadAssignment(node, toName); diff --git a/src/TSHelper.ts b/src/TSHelper.ts index 15b02402a..62719cc34 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -183,12 +183,12 @@ export class TSHelper { } } - public static isInTupleReturnFunction(node: ts.Node, checker: ts.TypeChecker, program: ts.Program): boolean { + public static isInTupleReturnFunction(node: ts.Node, checker: ts.TypeChecker): boolean { const declaration = TSHelper.findFirstNodeAbove(node, ts.isFunctionLike); if (declaration) { let functionType: ts.Type; if (ts.isFunctionExpression(declaration) || ts.isArrowFunction(declaration)) { - functionType = TSHelper.inferAssignedType(declaration, checker, program); + functionType = TSHelper.inferAssignedType(declaration, checker); } else { functionType = checker.getTypeAtLocation(declaration); } @@ -453,99 +453,8 @@ export class TSHelper { ) !== undefined; } - public static inferAssignedType(expression: ts.Expression, checker: ts.TypeChecker, program: ts.Program): ts.Type { - if (ts.isParenthesizedExpression(expression.parent)) { - // Ignore expressions wrapped in parenthesis - return this.inferAssignedType(expression.parent, checker, program); - - } else if (ts.isCallOrNewExpression(expression.parent)) { - // Expression being passed as argument to a function - const argumentIndex = expression.parent.arguments.indexOf(expression); - if (argumentIndex >= 0) { - const parentSignature = checker.getResolvedSignature(expression.parent); - 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, program)) { - // 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 parameterType; - } - } - - } else if (ts.isReturnStatement(expression.parent)) { - // Expression being returned from a function - return this.getContainingFunctionReturnType(expression.parent, checker); - - } else if (ts.isPropertyDeclaration(expression.parent)) { - // Expression being assigned to a class property - return checker.getTypeAtLocation(expression.parent); - - } else if (ts.isPropertyAssignment(expression.parent)) { - // Expression being assigned to an object literal property - const objType = this.inferAssignedType(expression.parent.parent, checker, program); - const property = objType.getProperty(expression.parent.name.getText()); - if (!property) { - const stringPropertyType = objType.getStringIndexType(); - if (stringPropertyType) { - return stringPropertyType; - } - } else { - return checker.getTypeAtLocation(property.valueDeclaration); - } - - } else if (ts.isArrayLiteralExpression(expression.parent)) { - // Expression in an array literal - const arrayType = this.inferAssignedType(expression.parent, checker, program); - if (ts.isTupleTypeNode(checker.typeToTypeNode(arrayType))) { - // Tuples - const i = expression.parent.elements.indexOf(expression); - const elementType = (arrayType as ts.TypeReference).typeArguments[i]; - return elementType; - } else { - // Standard arrays - return arrayType.getNumberIndexType(); - } - - } else if (ts.isVariableDeclaration(expression.parent)) { - // Expression assigned to declaration - return checker.getTypeAtLocation(expression.parent.name); - - } else if (ts.isBinaryExpression(expression.parent)) { - if (expression.parent.operatorToken.kind === ts.SyntaxKind.EqualsToken) { - // Expression assigned to variable - return checker.getTypeAtLocation(expression.parent.left); - } else { - // Other binary expressions - return TSHelper.inferAssignedType(expression.parent, checker, program); - } - - } else if (ts.isAssertionExpression(expression.parent)) { - // Expression being cast - return checker.getTypeFromTypeNode(expression.parent.type); - } - - return checker.getTypeAtLocation(expression); + public static inferAssignedType(expression: ts.Expression, checker: ts.TypeChecker): ts.Type { + return checker.getContextualType(expression) || checker.getTypeAtLocation(expression); } public static getAllCallSignatures(type: ts.Type): ReadonlyArray { @@ -557,8 +466,7 @@ export class TSHelper { public static getSignatureDeclarations( signatures: ReadonlyArray, - checker: ts.TypeChecker, - program: ts.Program + checker: ts.TypeChecker ): ts.SignatureDeclaration[] { const signatureDeclarations: ts.SignatureDeclaration[] = []; @@ -568,7 +476,7 @@ export class TSHelper { && !TSHelper.getExplicitThisParameter(signatureDeclaration)) { // Infer type of function expressions/arrow functions - const inferredType = TSHelper.inferAssignedType(signatureDeclaration, checker, program); + const inferredType = TSHelper.inferAssignedType(signatureDeclaration, checker); if (inferredType) { const inferredSignatures = TSHelper.getAllCallSignatures(inferredType); if (inferredSignatures.length > 0) { @@ -658,14 +566,14 @@ export class TSHelper { return contexts.reduce(reducer, ContextType.None); } - public static getFunctionContextType(type: ts.Type, checker: ts.TypeChecker, program: ts.Program): ContextType { + public static getFunctionContextType(type: ts.Type, checker: ts.TypeChecker): ContextType { if (type.isTypeParameter()) { type = type.getConstraint() || type; } if (type.isUnion()) { return TSHelper.reduceContextTypes( - type.types.map(t => TSHelper.getFunctionContextType(t, checker, program)) + type.types.map(t => TSHelper.getFunctionContextType(t, checker)) ); } @@ -673,7 +581,7 @@ export class TSHelper { if (signatures.length === 0) { return ContextType.None; } - const signatureDeclarations = TSHelper.getSignatureDeclarations(signatures, checker, program); + const signatureDeclarations = TSHelper.getSignatureDeclarations(signatures, checker); return TSHelper.reduceContextTypes( signatureDeclarations.map(s => TSHelper.getDeclarationContextType(s, checker))); } diff --git a/test/unit/assignments.spec.ts b/test/unit/assignments.spec.ts index 257dc2b2d..bd4ff4d7b 100644 --- a/test/unit/assignments.spec.ts +++ b/test/unit/assignments.spec.ts @@ -1099,6 +1099,21 @@ export class AssignmentTests { Expect(util.transpileAndExecute(code)).toBe("foo"); } + @TestCase("(this: void, s: string) => string", "s => s") + @TestCase("(this: any, s: string) => string", "s => s") + @TestCase("(s: string) => string", "s => s") + @TestCase("(this: void, s: string) => string", "function(s) { return s; }") + @TestCase("(this: any, s: string) => string", "function(s) { return s; }") + @TestCase("(s: string) => string", "function(s) { return s; }") + @Test("Function expression type inference in union tuple") + public functionExpressionTypeInferenceInUnionTuple(funcType: string, funcExp: string): void { + const code = + `interface I { callback: ${funcType}; } + let a: I[] | number = [{ callback: ${funcExp} }]; + return a[0].callback("foo");`; + Expect(util.transpileAndExecute(code)).toBe("foo"); + } + @TestCase("(this: void, s: string) => string", "s => s") @TestCase("(this: any, s: string) => string", "s => s") @TestCase("(s: string) => string", "s => s")