Parameters infer from body and call sites (#28342)

* Parameters infer from body usage and call sites

* Function expressions infer from variable decl usages

If the function expression is the initialiser of a variable declaration.

* Update isApplicableFunctionForInference too

* Update baseline
This commit is contained in:
Nathan Shively-Sanders
2018-11-05 11:29:16 -08:00
committed by GitHub
parent eba83f4ea7
commit 4cb210ce2e
5 changed files with 53 additions and 20 deletions

View File

@@ -194,7 +194,8 @@ namespace ts.codefix {
case SyntaxKind.Constructor:
return true;
case SyntaxKind.FunctionExpression:
return !!declaration.name;
const parent = declaration.parent;
return isVariableDeclaration(parent) && isIdentifier(parent.name) || !!declaration.name;
}
return false;
}
@@ -335,22 +336,31 @@ namespace ts.codefix {
}
function inferTypeForVariableFromUsage(token: Identifier, program: Program, cancellationToken: CancellationToken): Type {
return InferFromReference.inferTypeFromReferences(getReferences(token, program, cancellationToken), program.getTypeChecker(), cancellationToken);
const references = getReferences(token, program, cancellationToken);
const checker = program.getTypeChecker();
const types = InferFromReference.inferTypesFromReferences(references, checker, cancellationToken);
return InferFromReference.unifyFromContext(types, checker);
}
function inferTypeForParametersFromUsage(containingFunction: FunctionLikeDeclaration, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): ParameterInference[] | undefined {
let searchToken;
switch (containingFunction.kind) {
case SyntaxKind.Constructor:
searchToken = findChildOfKind<Token<SyntaxKind.ConstructorKeyword>>(containingFunction, SyntaxKind.ConstructorKeyword, sourceFile);
break;
case SyntaxKind.FunctionExpression:
const parent = containingFunction.parent;
searchToken = isVariableDeclaration(parent) && isIdentifier(parent.name) ?
parent.name :
containingFunction.name;
break;
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.MethodDeclaration:
const isConstructor = containingFunction.kind === SyntaxKind.Constructor;
const searchToken = isConstructor ?
findChildOfKind<Token<SyntaxKind.ConstructorKeyword>>(containingFunction, SyntaxKind.ConstructorKeyword, sourceFile) :
containingFunction.name;
if (searchToken) {
return InferFromReference.inferTypeForParametersFromReferences(getReferences(searchToken, program, cancellationToken), containingFunction, program, cancellationToken);
}
searchToken = containingFunction.name;
break;
}
if (searchToken) {
return InferFromReference.inferTypeForParametersFromReferences(getReferences(searchToken, program, cancellationToken), containingFunction, program, cancellationToken);
}
}
@@ -380,13 +390,13 @@ namespace ts.codefix {
stringIndexContext?: UsageContext;
}
export function inferTypeFromReferences(references: ReadonlyArray<Identifier>, checker: TypeChecker, cancellationToken: CancellationToken): Type {
export function inferTypesFromReferences(references: ReadonlyArray<Identifier>, checker: TypeChecker, cancellationToken: CancellationToken): Type[] {
const usageContext: UsageContext = {};
for (const reference of references) {
cancellationToken.throwIfCancellationRequested();
inferTypeFromContext(reference, checker, usageContext);
}
return unifyFromContext(inferFromContext(usageContext, checker), checker);
return inferFromContext(usageContext, checker);
}
export function inferTypeForParametersFromReferences(references: ReadonlyArray<Identifier>, declaration: FunctionLikeDeclaration, program: Program, cancellationToken: CancellationToken): ParameterInference[] | undefined {
@@ -394,7 +404,6 @@ namespace ts.codefix {
if (references.length === 0) {
return undefined;
}
if (!declaration.parameters) {
return undefined;
}
@@ -414,10 +423,8 @@ namespace ts.codefix {
if (callContext.argumentTypes.length <= parameterIndex) {
isOptional = isInJSFile(declaration);
types.push(checker.getUndefinedType());
continue;
}
if (isRest) {
else if (isRest) {
for (let i = parameterIndex; i < callContext.argumentTypes.length; i++) {
types.push(checker.getBaseTypeOfLiteralType(callContext.argumentTypes[i]));
}
@@ -426,10 +433,10 @@ namespace ts.codefix {
types.push(checker.getBaseTypeOfLiteralType(callContext.argumentTypes[parameterIndex]));
}
}
let type = unifyFromContext(types, checker);
if (type.flags & TypeFlags.Any && isIdentifier(parameter.name)) {
type = inferTypeForVariableFromUsage(parameter.name, program, cancellationToken);
if (isIdentifier(parameter.name)) {
types.push(...inferTypesFromReferences(getReferences(parameter.name, program, cancellationToken), checker, cancellationToken));
}
const type = unifyFromContext(types, checker);
return {
type: isRest ? checker.createArrayType(type) : type,
isOptional: isOptional && !isRest,
@@ -667,7 +674,7 @@ namespace ts.codefix {
}
}
function unifyFromContext(inferences: ReadonlyArray<Type>, checker: TypeChecker, fallback = checker.getAnyType()): Type {
export function unifyFromContext(inferences: ReadonlyArray<Type>, checker: TypeChecker, fallback = checker.getAnyType()): Type {
if (!inferences.length) return fallback;
const hasNonVacuousType = inferences.some(i => !(i.flags & (TypeFlags.Any | TypeFlags.Void)));
const hasNonVacuousNonAnonymousType = inferences.some(

View File

@@ -10,6 +10,7 @@
verify.codeFix({
description: "Annotate with type from JSDoc",
index: 0,
newFileContent:
`/**
* @param {number} x

View File

@@ -0,0 +1,16 @@
/// <reference path='fourslash.ts' />
////class C {
////
////}
////var c = new C()
////function f([|x, y |]) {
//// if (y) {
//// x = 1
//// }
//// return x
////}
////f(new C())
verify.rangeAfterCodeFix("x: number | C, y: undefined",/*includeWhiteSpace*/ undefined, /*errorCode*/ undefined, 0);

View File

@@ -0,0 +1,9 @@
/// <reference path='fourslash.ts' />
////var f = function ([|x |]) {
//// return x
////}
////f(1)
verify.rangeAfterCodeFix("x: number",/*includeWhiteSpace*/ undefined, /*errorCode*/ undefined, 0);

View File

@@ -16,4 +16,4 @@
////kw("6", { beforeExpr: true, prefix: true, startsExpr: true })
verify.rangeAfterCodeFix("name: string, options: { startsExpr?: boolean; beforeExpr?: boolean; isLoop?: boolean; prefix?: boolean; } | undefined",/*includeWhiteSpace*/ undefined, /*errorCode*/ undefined, 0);
verify.rangeAfterCodeFix("name: string | number, options: { startsExpr?: boolean; beforeExpr?: boolean; isLoop?: boolean; prefix?: boolean; keyword?: any; } | undefined",/*includeWhiteSpace*/ undefined, /*errorCode*/ undefined, 0);