Skip to content

Commit a30beba

Browse files
authored
Function inference fixes (#469)
* fixed function expression inference when assigning to tuples as casts * removed FocusTest * added fix for inference in constructors
1 parent 52d04f9 commit a30beba

File tree

2 files changed

+75
-2
lines changed

2 files changed

+75
-2
lines changed

src/TSHelper.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,7 @@ export class TSHelper {
436436
// Ignore expressions wrapped in parenthesis
437437
return this.inferAssignedType(expression.parent, checker);
438438

439-
} else if (ts.isCallExpression(expression.parent)) {
439+
} else if (ts.isCallOrNewExpression(expression.parent)) {
440440
// Expression being passed as argument to a function
441441
const argumentIndex = expression.parent.arguments.indexOf(expression);
442442
if (argumentIndex >= 0) {
@@ -514,11 +514,22 @@ export class TSHelper {
514514
{
515515
// Expression assigned to variable
516516
return checker.getTypeAtLocation(expression.parent.left);
517+
518+
} else if (ts.isAssertionExpression(expression.parent)) {
519+
// Expression being cast
520+
return checker.getTypeFromTypeNode(expression.parent.type);
517521
}
518522

519523
return checker.getTypeAtLocation(expression);
520524
}
521525

526+
public static getAllCallSignatures(type: ts.Type): ReadonlyArray<ts.Signature> {
527+
if (type.isUnion()) {
528+
return type.types.map(t => TSHelper.getAllCallSignatures(t)).reduce((a, b) => a.concat(b));
529+
}
530+
return type.getCallSignatures();
531+
}
532+
522533
public static getSignatureDeclarations(
523534
signatures: ReadonlyArray<ts.Signature>,
524535
checker: ts.TypeChecker
@@ -533,7 +544,7 @@ export class TSHelper {
533544
// Infer type of function expressions/arrow functions
534545
const inferredType = TSHelper.inferAssignedType(signatureDeclaration, checker);
535546
if (inferredType) {
536-
const inferredSignatures = inferredType.getCallSignatures();
547+
const inferredSignatures = TSHelper.getAllCallSignatures(inferredType);
537548
if (inferredSignatures.length > 0) {
538549
signatureDeclarations.push(...inferredSignatures.map(s => s.getDeclaration()));
539550
continue;

test/unit/assignments.spec.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -973,6 +973,68 @@ export class AssignmentTests {
973973
Expect(util.transpileAndExecute(code)).toBe("foo");
974974
}
975975

976+
@TestCase("(this: void, s: string) => string", "s => s")
977+
@TestCase("(this: any, s: string) => string", "s => s")
978+
@TestCase("(s: string) => string", "s => s")
979+
@TestCase("(this: void, s: string) => string", "function(s) { return s; }")
980+
@TestCase("(this: any, s: string) => string", "function(s) { return s; }")
981+
@TestCase("(s: string) => string", "function(s) { return s; }")
982+
@Test("Function expression type inference in union")
983+
public functionExpressionTypeInferenceInUnion(funcType: string, funcExp: string): void {
984+
const code =
985+
`type U = string | number | (${funcType});
986+
const u: U = ${funcExp};
987+
return (u as ${funcType})("foo");`;
988+
Expect(util.transpileAndExecute(code)).toBe("foo");
989+
}
990+
991+
@TestCase("(this: void, s: string) => string", "s => s")
992+
@TestCase("(this: any, s: string) => string", "s => s")
993+
@TestCase("(s: string) => string", "s => s")
994+
@TestCase("(this: void, s: string) => string", "function(s) { return s; }")
995+
@TestCase("(this: any, s: string) => string", "function(s) { return s; }")
996+
@TestCase("(s: string) => string", "function(s) { return s; }")
997+
@Test("Function expression type inference in as cast")
998+
public functionExpressionTypeInferenceInAsCast(funcType: string, funcExp: string): void {
999+
const code =
1000+
`const fn: ${funcType} = (${funcExp}) as (${funcType});
1001+
return fn("foo");`;
1002+
console.log(code);
1003+
Expect(util.transpileAndExecute(code)).toBe("foo");
1004+
}
1005+
1006+
@TestCase("(this: void, s: string) => string", "s => s")
1007+
@TestCase("(this: any, s: string) => string", "s => s")
1008+
@TestCase("(s: string) => string", "s => s")
1009+
@TestCase("(this: void, s: string) => string", "function(s) { return s; }")
1010+
@TestCase("(this: any, s: string) => string", "function(s) { return s; }")
1011+
@TestCase("(s: string) => string", "function(s) { return s; }")
1012+
@Test("Function expression type inference in type assertion")
1013+
public functionExpressionTypeInferenceInTypeAssert(funcType: string, funcExp: string): void {
1014+
const code =
1015+
`const fn: ${funcType} = <${funcType}>(${funcExp});
1016+
return fn("foo");`;
1017+
Expect(util.transpileAndExecute(code)).toBe("foo");
1018+
}
1019+
1020+
@TestCase("(this: void, s: string) => string", "s => s")
1021+
@TestCase("(this: any, s: string) => string", "s => s")
1022+
@TestCase("(s: string) => string", "s => s")
1023+
@TestCase("(this: void, s: string) => string", "function(s) { return s; }")
1024+
@TestCase("(this: any, s: string) => string", "function(s) { return s; }")
1025+
@TestCase("(s: string) => string", "function(s) { return s; }")
1026+
@Test("Function expression type inference in constructor")
1027+
public functionExpresssionTypeInferenceInConstructor(funcType: string, funcExp: string): void {
1028+
const code =
1029+
`class C {
1030+
result: string;
1031+
constructor(fn: (s: string) => string) { this.result = fn("foo"); }
1032+
}
1033+
const c = new C(s => s);
1034+
return c.result;`;
1035+
Expect(util.transpileAndExecute(code)).toBe("foo");
1036+
}
1037+
9761038
@Test("String table access")
9771039
public stringTableAccess(assignType: string): void {
9781040
const code = `const dict : {[key:string]:any} = {};

0 commit comments

Comments
 (0)