Skip to content

Commit d0c4f72

Browse files
authored
Using getContextualType in inferAssignedType (#487)
* using getContextualType for inferAssignedType * test for new inference bug
1 parent 8515a03 commit d0c4f72

3 files changed

Lines changed: 32 additions & 109 deletions

File tree

src/LuaTransformer.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1088,7 +1088,7 @@ export class LuaTransformer {
10881088
}
10891089

10901090
const type = this.checker.getTypeAtLocation(node);
1091-
const context = tsHelper.getFunctionContextType(type, this.checker, this.program) !== ContextType.Void
1091+
const context = tsHelper.getFunctionContextType(type, this.checker) !== ContextType.Void
10921092
? this.createSelfIdentifier()
10931093
: undefined;
10941094
const [paramNames, dots, restParamName] = this.transformParameters(node.parameters, context);
@@ -1594,7 +1594,7 @@ export class LuaTransformer {
15941594
}
15951595

15961596
const type = this.checker.getTypeAtLocation(functionDeclaration);
1597-
const context = tsHelper.getFunctionContextType(type, this.checker, this.program) !== ContextType.Void
1597+
const context = tsHelper.getFunctionContextType(type, this.checker) !== ContextType.Void
15981598
? this.createSelfIdentifier()
15991599
: undefined;
16001600
const [params, dotsLiteral, restParamName] = this.transformParameters(functionDeclaration.parameters, context);
@@ -1794,7 +1794,7 @@ export class LuaTransformer {
17941794
const expressionType = this.checker.getTypeAtLocation(statement.expression);
17951795
this.validateFunctionAssignment(statement, expressionType, returnType);
17961796
}
1797-
if (tsHelper.isInTupleReturnFunction(statement, this.checker, this.program)) {
1797+
if (tsHelper.isInTupleReturnFunction(statement, this.checker)) {
17981798
// Parent function is a TupleReturn function
17991799
if (ts.isArrayLiteralExpression(statement.expression)) {
18001800
// If return expression is an array literal, leave out brackets.
@@ -3005,7 +3005,7 @@ export class LuaTransformer {
30053005
): ExpressionVisitResult
30063006
{
30073007
const type = this.checker.getTypeAtLocation(node);
3008-
const hasContext = tsHelper.getFunctionContextType(type, this.checker, this.program) !== ContextType.Void;
3008+
const hasContext = tsHelper.getFunctionContextType(type, this.checker) !== ContextType.Void;
30093009
// Build parameter string
30103010
const [paramNames, dotsLiteral, spreadIdentifier] = this.transformParameters(
30113011
node.parameters,
@@ -3099,7 +3099,7 @@ export class LuaTransformer {
30993099
const isTupleReturn = tsHelper.isTupleReturnCall(node, this.checker);
31003100
const isTupleReturnForward = node.parent
31013101
&& ts.isReturnStatement(node.parent)
3102-
&& tsHelper.isInTupleReturnFunction(node, this.checker, this.program);
3102+
&& tsHelper.isInTupleReturnFunction(node, this.checker);
31033103
const isInDestructingAssignment = tsHelper.isInDestructingAssignment(node);
31043104
const isInSpread = node.parent && ts.isSpreadElement(node.parent);
31053105
const returnValueIsUsed = node.parent && !ts.isExpressionStatement(node.parent);
@@ -3892,7 +3892,7 @@ export class LuaTransformer {
38923892
public transformFunctionCallExpression(node: ts.CallExpression): tstl.CallExpression {
38933893
const expression = node.expression as ts.PropertyAccessExpression;
38943894
const callerType = this.checker.getTypeAtLocation(expression.expression);
3895-
if (tsHelper.getFunctionContextType(callerType, this.checker, this.program) === ContextType.Void) {
3895+
if (tsHelper.getFunctionContextType(callerType, this.checker) === ContextType.Void) {
38963896
throw TSTLErrors.UnsupportedSelfFunctionConversion(node);
38973897
}
38983898
const signature = this.checker.getResolvedSignature(node);
@@ -4328,8 +4328,8 @@ export class LuaTransformer {
43284328
fromTypeCache.add(toType);
43294329

43304330
// Check function assignments
4331-
const fromContext = tsHelper.getFunctionContextType(fromType, this.checker, this.program);
4332-
const toContext = tsHelper.getFunctionContextType(toType, this.checker, this.program);
4331+
const fromContext = tsHelper.getFunctionContextType(fromType, this.checker);
4332+
const toContext = tsHelper.getFunctionContextType(toType, this.checker);
43334333

43344334
if (fromContext === ContextType.Mixed || toContext === ContextType.Mixed) {
43354335
throw TSTLErrors.UnsupportedOverloadAssignment(node, toName);

src/TSHelper.ts

Lines changed: 9 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -183,12 +183,12 @@ export class TSHelper {
183183
}
184184
}
185185

186-
public static isInTupleReturnFunction(node: ts.Node, checker: ts.TypeChecker, program: ts.Program): boolean {
186+
public static isInTupleReturnFunction(node: ts.Node, checker: ts.TypeChecker): boolean {
187187
const declaration = TSHelper.findFirstNodeAbove(node, ts.isFunctionLike);
188188
if (declaration) {
189189
let functionType: ts.Type;
190190
if (ts.isFunctionExpression(declaration) || ts.isArrowFunction(declaration)) {
191-
functionType = TSHelper.inferAssignedType(declaration, checker, program);
191+
functionType = TSHelper.inferAssignedType(declaration, checker);
192192
} else {
193193
functionType = checker.getTypeAtLocation(declaration);
194194
}
@@ -453,99 +453,8 @@ export class TSHelper {
453453
) !== undefined;
454454
}
455455

456-
public static inferAssignedType(expression: ts.Expression, checker: ts.TypeChecker, program: ts.Program): ts.Type {
457-
if (ts.isParenthesizedExpression(expression.parent)) {
458-
// Ignore expressions wrapped in parenthesis
459-
return this.inferAssignedType(expression.parent, checker, program);
460-
461-
} else if (ts.isCallOrNewExpression(expression.parent)) {
462-
// Expression being passed as argument to a function
463-
const argumentIndex = expression.parent.arguments.indexOf(expression);
464-
if (argumentIndex >= 0) {
465-
const parentSignature = checker.getResolvedSignature(expression.parent);
466-
if (parentSignature.parameters.length > 0) { // In case function type is 'any'
467-
const signatureIndex = Math.min(argumentIndex, parentSignature.parameters.length - 1);
468-
let parameterType = checker.getTypeOfSymbolAtLocation(
469-
parentSignature.parameters[signatureIndex],
470-
expression
471-
);
472-
if (TSHelper.isArrayType(parameterType, checker, program)) {
473-
// Check for elipses argument
474-
const parentSignatureDeclaration = parentSignature.getDeclaration();
475-
if (parentSignatureDeclaration) {
476-
let declarationIndex = signatureIndex;
477-
if (this.getExplicitThisParameter(parentSignatureDeclaration)) {
478-
// Ignore 'this' parameter
479-
++declarationIndex;
480-
}
481-
const parameterDeclaration = parentSignatureDeclaration.parameters[declarationIndex];
482-
if ((parameterType.flags & ts.TypeFlags.Object) !== 0
483-
&& ((parameterType as ts.ObjectType).objectFlags & ts.ObjectFlags.Reference) !== 0
484-
&& parameterDeclaration.dotDotDotToken)
485-
{
486-
// Determine type from elipsis array/tuple type
487-
const parameterTypeReference = (parameterType as ts.TypeReference);
488-
parameterType = parameterTypeReference.typeArguments[argumentIndex - declarationIndex];
489-
}
490-
}
491-
}
492-
return parameterType;
493-
}
494-
}
495-
496-
} else if (ts.isReturnStatement(expression.parent)) {
497-
// Expression being returned from a function
498-
return this.getContainingFunctionReturnType(expression.parent, checker);
499-
500-
} else if (ts.isPropertyDeclaration(expression.parent)) {
501-
// Expression being assigned to a class property
502-
return checker.getTypeAtLocation(expression.parent);
503-
504-
} else if (ts.isPropertyAssignment(expression.parent)) {
505-
// Expression being assigned to an object literal property
506-
const objType = this.inferAssignedType(expression.parent.parent, checker, program);
507-
const property = objType.getProperty(expression.parent.name.getText());
508-
if (!property) {
509-
const stringPropertyType = objType.getStringIndexType();
510-
if (stringPropertyType) {
511-
return stringPropertyType;
512-
}
513-
} else {
514-
return checker.getTypeAtLocation(property.valueDeclaration);
515-
}
516-
517-
} else if (ts.isArrayLiteralExpression(expression.parent)) {
518-
// Expression in an array literal
519-
const arrayType = this.inferAssignedType(expression.parent, checker, program);
520-
if (ts.isTupleTypeNode(checker.typeToTypeNode(arrayType))) {
521-
// Tuples
522-
const i = expression.parent.elements.indexOf(expression);
523-
const elementType = (arrayType as ts.TypeReference).typeArguments[i];
524-
return elementType;
525-
} else {
526-
// Standard arrays
527-
return arrayType.getNumberIndexType();
528-
}
529-
530-
} else if (ts.isVariableDeclaration(expression.parent)) {
531-
// Expression assigned to declaration
532-
return checker.getTypeAtLocation(expression.parent.name);
533-
534-
} else if (ts.isBinaryExpression(expression.parent)) {
535-
if (expression.parent.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
536-
// Expression assigned to variable
537-
return checker.getTypeAtLocation(expression.parent.left);
538-
} else {
539-
// Other binary expressions
540-
return TSHelper.inferAssignedType(expression.parent, checker, program);
541-
}
542-
543-
} else if (ts.isAssertionExpression(expression.parent)) {
544-
// Expression being cast
545-
return checker.getTypeFromTypeNode(expression.parent.type);
546-
}
547-
548-
return checker.getTypeAtLocation(expression);
456+
public static inferAssignedType(expression: ts.Expression, checker: ts.TypeChecker): ts.Type {
457+
return checker.getContextualType(expression) || checker.getTypeAtLocation(expression);
549458
}
550459

551460
public static getAllCallSignatures(type: ts.Type): ReadonlyArray<ts.Signature> {
@@ -557,8 +466,7 @@ export class TSHelper {
557466

558467
public static getSignatureDeclarations(
559468
signatures: ReadonlyArray<ts.Signature>,
560-
checker: ts.TypeChecker,
561-
program: ts.Program
469+
checker: ts.TypeChecker
562470
): ts.SignatureDeclaration[]
563471
{
564472
const signatureDeclarations: ts.SignatureDeclaration[] = [];
@@ -568,7 +476,7 @@ export class TSHelper {
568476
&& !TSHelper.getExplicitThisParameter(signatureDeclaration))
569477
{
570478
// Infer type of function expressions/arrow functions
571-
const inferredType = TSHelper.inferAssignedType(signatureDeclaration, checker, program);
479+
const inferredType = TSHelper.inferAssignedType(signatureDeclaration, checker);
572480
if (inferredType) {
573481
const inferredSignatures = TSHelper.getAllCallSignatures(inferredType);
574482
if (inferredSignatures.length > 0) {
@@ -658,22 +566,22 @@ export class TSHelper {
658566
return contexts.reduce(reducer, ContextType.None);
659567
}
660568

661-
public static getFunctionContextType(type: ts.Type, checker: ts.TypeChecker, program: ts.Program): ContextType {
569+
public static getFunctionContextType(type: ts.Type, checker: ts.TypeChecker): ContextType {
662570
if (type.isTypeParameter()) {
663571
type = type.getConstraint() || type;
664572
}
665573

666574
if (type.isUnion()) {
667575
return TSHelper.reduceContextTypes(
668-
type.types.map(t => TSHelper.getFunctionContextType(t, checker, program))
576+
type.types.map(t => TSHelper.getFunctionContextType(t, checker))
669577
);
670578
}
671579

672580
const signatures = checker.getSignaturesOfType(type, ts.SignatureKind.Call);
673581
if (signatures.length === 0) {
674582
return ContextType.None;
675583
}
676-
const signatureDeclarations = TSHelper.getSignatureDeclarations(signatures, checker, program);
584+
const signatureDeclarations = TSHelper.getSignatureDeclarations(signatures, checker);
677585
return TSHelper.reduceContextTypes(
678586
signatureDeclarations.map(s => TSHelper.getDeclarationContextType(s, checker)));
679587
}

test/unit/assignments.spec.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1122,6 +1122,21 @@ export class AssignmentTests {
11221122
Expect(util.transpileAndExecute(code)).toBe("foo");
11231123
}
11241124

1125+
@TestCase("(this: void, s: string) => string", "s => s")
1126+
@TestCase("(this: any, s: string) => string", "s => s")
1127+
@TestCase("(s: string) => string", "s => s")
1128+
@TestCase("(this: void, s: string) => string", "function(s) { return s; }")
1129+
@TestCase("(this: any, s: string) => string", "function(s) { return s; }")
1130+
@TestCase("(s: string) => string", "function(s) { return s; }")
1131+
@Test("Function expression type inference in union tuple")
1132+
public functionExpressionTypeInferenceInUnionTuple(funcType: string, funcExp: string): void {
1133+
const code =
1134+
`interface I { callback: ${funcType}; }
1135+
let a: I[] | number = [{ callback: ${funcExp} }];
1136+
return a[0].callback("foo");`;
1137+
Expect(util.transpileAndExecute(code)).toBe("foo");
1138+
}
1139+
11251140
@TestCase("(this: void, s: string) => string", "s => s")
11261141
@TestCase("(this: any, s: string) => string", "s => s")
11271142
@TestCase("(s: string) => string", "s => s")

0 commit comments

Comments
 (0)