Skip to content

Commit 661d5b0

Browse files
authored
improved function expression type inference (#381)
* working on better function expression inference * improved function expression type inference * added function assignment tests with function expressions wrapped in parenthesis
1 parent 3f9a4c2 commit 661d5b0

File tree

2 files changed

+233
-23
lines changed

2 files changed

+233
-23
lines changed

src/TSHelper.ts

Lines changed: 72 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,70 @@ export class TSHelper {
383383
param => ts.isIdentifier(param.name) && param.name.originalKeywordKind === ts.SyntaxKind.ThisKeyword);
384384
}
385385

386+
public static inferAssignedType(expression: ts.Expression, checker: ts.TypeChecker): ts.Type {
387+
if (ts.isParenthesizedExpression(expression.parent)) {
388+
// Ignore expressions wrapped in parenthesis
389+
return this.inferAssignedType(expression.parent, checker);
390+
391+
} else if (ts.isCallExpression(expression.parent)) {
392+
// Expression being passed as argument to a function
393+
let i = expression.parent.arguments.indexOf(expression);
394+
if (i >= 0) {
395+
const parentSignature = checker.getResolvedSignature(expression.parent);
396+
const parentSignatureDeclaration = parentSignature.getDeclaration();
397+
if (parentSignatureDeclaration) {
398+
if (this.getExplicitThisParameter(parentSignatureDeclaration)) {
399+
++i;
400+
}
401+
return checker.getTypeAtLocation(parentSignatureDeclaration.parameters[i]);
402+
}
403+
}
404+
405+
} else if (ts.isReturnStatement(expression.parent)) {
406+
// Expression being returned from a function
407+
return this.getContainingFunctionReturnType(expression.parent, checker);
408+
409+
} else if (ts.isPropertyDeclaration(expression.parent)) {
410+
// Expression being assigned to a class property
411+
return checker.getTypeAtLocation(expression.parent);
412+
413+
} else if (ts.isPropertyAssignment(expression.parent)) {
414+
// Expression being assigned to an object literal property
415+
const objType = this.inferAssignedType(expression.parent.parent, checker);
416+
const property = objType.getProperty(expression.parent.name.getText());
417+
if (!property) {
418+
return objType.getStringIndexType();
419+
} else {
420+
return checker.getTypeAtLocation(property.valueDeclaration);
421+
}
422+
423+
} else if (ts.isArrayLiteralExpression(expression.parent)) {
424+
// Expression in an array literal
425+
const arrayType = this.inferAssignedType(expression.parent, checker);
426+
if (ts.isTupleTypeNode(checker.typeToTypeNode(arrayType))) {
427+
// Tuples
428+
const i = expression.parent.elements.indexOf(expression);
429+
const elementType = (arrayType as ts.TypeReference).typeArguments[i];
430+
return elementType;
431+
} else {
432+
// Standard arrays
433+
return arrayType.getNumberIndexType();
434+
}
435+
436+
} else if (ts.isVariableDeclaration(expression.parent)) {
437+
// Expression assigned to declaration
438+
return checker.getTypeAtLocation(expression.parent.name);
439+
440+
} else if (ts.isBinaryExpression(expression.parent)
441+
&& expression.parent.operatorToken.kind === ts.SyntaxKind.EqualsToken)
442+
{
443+
// Expression assigned to variable
444+
return checker.getTypeAtLocation(expression.parent.left);
445+
}
446+
447+
return checker.getTypeAtLocation(expression);
448+
}
449+
386450
public static getSignatureDeclarations(
387451
signatures: ts.Signature[],
388452
checker: ts.TypeChecker
@@ -392,29 +456,14 @@ export class TSHelper {
392456
for (const signature of signatures) {
393457
const signatureDeclaration = signature.getDeclaration();
394458
if ((ts.isFunctionExpression(signatureDeclaration) || ts.isArrowFunction(signatureDeclaration))
395-
&& !this.getExplicitThisParameter(signatureDeclaration)) {
396-
// Function expressions: get signatures of type being assigned to, unless 'this' was explicit
397-
let declType: ts.Type;
398-
if (ts.isCallExpression(signatureDeclaration.parent)) {
399-
// Function expression being passed as argument to another function
400-
const i = signatureDeclaration.parent.arguments.indexOf(signatureDeclaration);
401-
if (i >= 0) {
402-
const parentSignature = checker.getResolvedSignature(signatureDeclaration.parent);
403-
const parentSignatureDeclaration = parentSignature.getDeclaration();
404-
if (parentSignatureDeclaration) {
405-
declType = checker.getTypeAtLocation(parentSignatureDeclaration.parameters[i]);
406-
}
407-
}
408-
} else if (ts.isReturnStatement(signatureDeclaration.parent)) {
409-
declType = this.getContainingFunctionReturnType(signatureDeclaration.parent, checker);
410-
} else {
411-
// Function expression being assigned
412-
declType = checker.getTypeAtLocation(signatureDeclaration.parent);
413-
}
414-
if (declType) {
415-
const declSignatures = declType.getCallSignatures();
416-
if (declSignatures.length > 0) {
417-
declSignatures.map(s => s.getDeclaration()).forEach(decl => signatureDeclarations.push(decl));
459+
&& !this.getExplicitThisParameter(signatureDeclaration))
460+
{
461+
// Infer type of function expressions/arrow functions
462+
const inferredType = this.inferAssignedType(signatureDeclaration, checker);
463+
if (inferredType) {
464+
const inferredSignatures = inferredType.getCallSignatures();
465+
if (inferredSignatures.length > 0) {
466+
signatureDeclarations.push(...inferredSignatures.map(s => s.getDeclaration()));
418467
continue;
419468
}
420469
}

0 commit comments

Comments
 (0)