From 3be6e75d6aee0c2a0a3be1b42ab84aa8c5c2e8ac Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Mon, 26 Aug 2019 11:13:17 -0700 Subject: [PATCH 01/13] Improve names in infer-from-usage Basically, drop "Context" from all names, because it just indicates that it's an implementation of the State monad. --- src/services/codefixes/inferFromUsage.ts | 238 +++++++++++------------ 1 file changed, 119 insertions(+), 119 deletions(-) diff --git a/src/services/codefixes/inferFromUsage.ts b/src/services/codefixes/inferFromUsage.ts index 396d7f44fa..417ed5c625 100644 --- a/src/services/codefixes/inferFromUsage.ts +++ b/src/services/codefixes/inferFromUsage.ts @@ -359,7 +359,7 @@ namespace ts.codefix { const references = getReferences(token, program, cancellationToken); const checker = program.getTypeChecker(); const types = InferFromReference.inferTypesFromReferences(references, checker, cancellationToken); - return InferFromReference.unifyFromContext(types, checker); + return InferFromReference.unifyFromUsage(types, checker); } function inferFunctionReferencesFromUsage(containingFunction: FunctionLike, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): ReadonlyArray | undefined { @@ -395,33 +395,33 @@ namespace ts.codefix { } namespace InferFromReference { - interface CallContext { + interface CallUsage { argumentTypes: Type[]; - returnType: UsageContext; + returnType: Usage; } - interface UsageContext { + interface Usage { isNumber?: boolean; isString?: boolean; /** Used ambiguously, eg x + ___ or object[___]; results in string | number if no other evidence exists */ isNumberOrString?: boolean; candidateTypes?: Type[]; - properties?: UnderscoreEscapedMap; - callContexts?: CallContext[]; - constructContexts?: CallContext[]; - numberIndexContext?: UsageContext; - stringIndexContext?: UsageContext; + properties?: UnderscoreEscapedMap; + calls?: CallUsage[]; + constructs?: CallUsage[]; + numberIndex?: Usage; + stringIndex?: Usage; candidateThisTypes?: Type[]; } export function inferTypesFromReferences(references: ReadonlyArray, checker: TypeChecker, cancellationToken: CancellationToken): Type[] { - const usageContext: UsageContext = {}; + const usage: Usage = {}; for (const reference of references) { cancellationToken.throwIfCancellationRequested(); - inferTypeFromContext(reference, checker, usageContext); + calculateUsageOfNode(reference, checker, usage); } - return inferFromContext(usageContext, checker); + return inferFromUsage(usage, checker); } export function inferTypeForParametersFromReferences(references: ReadonlyArray | undefined, declaration: FunctionLike, program: Program, cancellationToken: CancellationToken): ParameterInference[] | undefined { @@ -430,35 +430,35 @@ namespace ts.codefix { } const checker = program.getTypeChecker(); - const usageContext: UsageContext = {}; + const usage: Usage = {}; for (const reference of references) { cancellationToken.throwIfCancellationRequested(); - inferTypeFromContext(reference, checker, usageContext); + calculateUsageOfNode(reference, checker, usage); } - const callContexts = [...usageContext.constructContexts || [], ...usageContext.callContexts || []]; + const calls = [...usage.constructs || [], ...usage.calls || []]; return declaration.parameters.map((parameter, parameterIndex): ParameterInference => { const types = []; const isRest = isRestParameter(parameter); let isOptional = false; - for (const callContext of callContexts) { - if (callContext.argumentTypes.length <= parameterIndex) { + for (const call of calls) { + if (call.argumentTypes.length <= parameterIndex) { isOptional = isInJSFile(declaration); types.push(checker.getUndefinedType()); } else if (isRest) { - for (let i = parameterIndex; i < callContext.argumentTypes.length; i++) { - types.push(checker.getBaseTypeOfLiteralType(callContext.argumentTypes[i])); + for (let i = parameterIndex; i < call.argumentTypes.length; i++) { + types.push(checker.getBaseTypeOfLiteralType(call.argumentTypes[i])); } } else { - types.push(checker.getBaseTypeOfLiteralType(callContext.argumentTypes[parameterIndex])); + types.push(checker.getBaseTypeOfLiteralType(call.argumentTypes[parameterIndex])); } } if (isIdentifier(parameter.name)) { const inferred = inferTypesFromReferences(getReferences(parameter.name, program, cancellationToken), checker, cancellationToken); types.push(...(isRest ? mapDefined(inferred, checker.getElementTypeOfArrayType) : inferred)); } - const type = unifyFromContext(types, checker); + const type = unifyFromUsage(types, checker); return { type: isRest ? checker.createArrayType(type) : type, isOptional: isOptional && !isRest, @@ -473,89 +473,89 @@ namespace ts.codefix { } const checker = program.getTypeChecker(); - const usageContext: UsageContext = {}; + const usage: Usage = {}; for (const reference of references) { cancellationToken.throwIfCancellationRequested(); - inferTypeFromContext(reference, checker, usageContext); + calculateUsageOfNode(reference, checker, usage); } - return unifyFromContext(usageContext.candidateThisTypes || emptyArray, checker); + return unifyFromUsage(usage.candidateThisTypes || emptyArray, checker); } - function inferTypeFromContext(node: Expression, checker: TypeChecker, usageContext: UsageContext): void { + function calculateUsageOfNode(node: Expression, checker: TypeChecker, usage: Usage): void { while (isRightSideOfQualifiedNameOrPropertyAccess(node)) { node = node.parent; } switch (node.parent.kind) { case SyntaxKind.PostfixUnaryExpression: - usageContext.isNumber = true; + usage.isNumber = true; break; case SyntaxKind.PrefixUnaryExpression: - inferTypeFromPrefixUnaryExpressionContext(node.parent, usageContext); + inferTypeFromPrefixUnaryExpression(node.parent, usage); break; case SyntaxKind.BinaryExpression: - inferTypeFromBinaryExpressionContext(node, node.parent, checker, usageContext); + inferTypeFromBinaryExpression(node, node.parent, checker, usage); break; case SyntaxKind.CaseClause: case SyntaxKind.DefaultClause: - inferTypeFromSwitchStatementLabelContext(node.parent, checker, usageContext); + inferTypeFromSwitchStatementLabel(node.parent, checker, usage); break; case SyntaxKind.CallExpression: case SyntaxKind.NewExpression: if ((node.parent).expression === node) { - inferTypeFromCallExpressionContext(node.parent, checker, usageContext); + inferTypeFromCallExpression(node.parent, checker, usage); } else { - inferTypeFromContextualType(node, checker, usageContext); + inferTypeFromContextualType(node, checker, usage); } break; case SyntaxKind.PropertyAccessExpression: - inferTypeFromPropertyAccessExpressionContext(node.parent, checker, usageContext); + inferTypeFromPropertyAccessExpression(node.parent, checker, usage); break; case SyntaxKind.ElementAccessExpression: - inferTypeFromPropertyElementExpressionContext(node.parent, node, checker, usageContext); + inferTypeFromPropertyElementExpression(node.parent, node, checker, usage); break; case SyntaxKind.PropertyAssignment: case SyntaxKind.ShorthandPropertyAssignment: - inferTypeFromPropertyAssignment(node.parent, checker, usageContext); + inferTypeFromPropertyAssignment(node.parent, checker, usage); break; case SyntaxKind.PropertyDeclaration: - inferTypeFromPropertyDeclaration(node.parent, checker, usageContext); + inferTypeFromPropertyDeclaration(node.parent, checker, usage); break; case SyntaxKind.VariableDeclaration: { const { name, initializer } = node.parent as VariableDeclaration; if (node === name) { if (initializer) { // This can happen for `let x = null;` which still has an implicit-any error. - addCandidateType(usageContext, checker.getTypeAtLocation(initializer)); + addCandidateType(usage, checker.getTypeAtLocation(initializer)); } break; } } // falls through default: - return inferTypeFromContextualType(node, checker, usageContext); + return inferTypeFromContextualType(node, checker, usage); } } - function inferTypeFromContextualType(node: Expression, checker: TypeChecker, usageContext: UsageContext): void { + function inferTypeFromContextualType(node: Expression, checker: TypeChecker, usage: Usage): void { if (isExpressionNode(node)) { - addCandidateType(usageContext, checker.getContextualType(node)); + addCandidateType(usage, checker.getContextualType(node)); } } - function inferTypeFromPrefixUnaryExpressionContext(node: PrefixUnaryExpression, usageContext: UsageContext): void { + function inferTypeFromPrefixUnaryExpression(node: PrefixUnaryExpression, usage: Usage): void { switch (node.operator) { case SyntaxKind.PlusPlusToken: case SyntaxKind.MinusMinusToken: case SyntaxKind.MinusToken: case SyntaxKind.TildeToken: - usageContext.isNumber = true; + usage.isNumber = true; break; case SyntaxKind.PlusToken: - usageContext.isNumberOrString = true; + usage.isNumberOrString = true; break; // case SyntaxKind.ExclamationToken: @@ -563,7 +563,7 @@ namespace ts.codefix { } } - function inferTypeFromBinaryExpressionContext(node: Expression, parent: BinaryExpression, checker: TypeChecker, usageContext: UsageContext): void { + function inferTypeFromBinaryExpression(node: Expression, parent: BinaryExpression, checker: TypeChecker, usage: Usage): void { switch (parent.operatorToken.kind) { // ExponentiationOperator case SyntaxKind.AsteriskAsteriskToken: @@ -606,10 +606,10 @@ namespace ts.codefix { case SyntaxKind.GreaterThanEqualsToken: const operandType = checker.getTypeAtLocation(parent.left === node ? parent.right : parent.left); if (operandType.flags & TypeFlags.EnumLike) { - addCandidateType(usageContext, operandType); + addCandidateType(usage, operandType); } else { - usageContext.isNumber = true; + usage.isNumber = true; } break; @@ -617,16 +617,16 @@ namespace ts.codefix { case SyntaxKind.PlusToken: const otherOperandType = checker.getTypeAtLocation(parent.left === node ? parent.right : parent.left); if (otherOperandType.flags & TypeFlags.EnumLike) { - addCandidateType(usageContext, otherOperandType); + addCandidateType(usage, otherOperandType); } else if (otherOperandType.flags & TypeFlags.NumberLike) { - usageContext.isNumber = true; + usage.isNumber = true; } else if (otherOperandType.flags & TypeFlags.StringLike) { - usageContext.isString = true; + usage.isString = true; } else { - usageContext.isNumberOrString = true; + usage.isNumberOrString = true; } break; @@ -636,12 +636,12 @@ namespace ts.codefix { case SyntaxKind.EqualsEqualsEqualsToken: case SyntaxKind.ExclamationEqualsEqualsToken: case SyntaxKind.ExclamationEqualsToken: - addCandidateType(usageContext, checker.getTypeAtLocation(parent.left === node ? parent.right : parent.left)); + addCandidateType(usage, checker.getTypeAtLocation(parent.left === node ? parent.right : parent.left)); break; case SyntaxKind.InKeyword: if (node === parent.left) { - usageContext.isString = true; + usage.isString = true; } break; @@ -651,7 +651,7 @@ namespace ts.codefix { (node.parent.parent.kind === SyntaxKind.VariableDeclaration || isAssignmentExpression(node.parent.parent, /*excludeCompoundAssignment*/ true))) { // var x = x || {}; // TODO: use getFalsyflagsOfType - addCandidateType(usageContext, checker.getTypeAtLocation(parent.right)); + addCandidateType(usage, checker.getTypeAtLocation(parent.right)); } break; @@ -663,68 +663,68 @@ namespace ts.codefix { } } - function inferTypeFromSwitchStatementLabelContext(parent: CaseOrDefaultClause, checker: TypeChecker, usageContext: UsageContext): void { - addCandidateType(usageContext, checker.getTypeAtLocation(parent.parent.parent.expression)); + function inferTypeFromSwitchStatementLabel(parent: CaseOrDefaultClause, checker: TypeChecker, usage: Usage): void { + addCandidateType(usage, checker.getTypeAtLocation(parent.parent.parent.expression)); } - function inferTypeFromCallExpressionContext(parent: CallExpression | NewExpression, checker: TypeChecker, usageContext: UsageContext): void { - const callContext: CallContext = { + function inferTypeFromCallExpression(parent: CallExpression | NewExpression, checker: TypeChecker, usage: Usage): void { + const call: CallUsage = { argumentTypes: [], returnType: {} }; if (parent.arguments) { for (const argument of parent.arguments) { - callContext.argumentTypes.push(checker.getTypeAtLocation(argument)); + call.argumentTypes.push(checker.getTypeAtLocation(argument)); } } - inferTypeFromContext(parent, checker, callContext.returnType); + calculateUsageOfNode(parent, checker, call.returnType); if (parent.kind === SyntaxKind.CallExpression) { - (usageContext.callContexts || (usageContext.callContexts = [])).push(callContext); + (usage.calls || (usage.calls = [])).push(call); } else { - (usageContext.constructContexts || (usageContext.constructContexts = [])).push(callContext); + (usage.constructs || (usage.constructs = [])).push(call); } } - function inferTypeFromPropertyAccessExpressionContext(parent: PropertyAccessExpression, checker: TypeChecker, usageContext: UsageContext): void { + function inferTypeFromPropertyAccessExpression(parent: PropertyAccessExpression, checker: TypeChecker, usage: Usage): void { const name = escapeLeadingUnderscores(parent.name.text); - if (!usageContext.properties) { - usageContext.properties = createUnderscoreEscapedMap(); + if (!usage.properties) { + usage.properties = createUnderscoreEscapedMap(); } - const propertyUsageContext = usageContext.properties.get(name) || { }; - inferTypeFromContext(parent, checker, propertyUsageContext); - usageContext.properties.set(name, propertyUsageContext); + const propertyUsage = usage.properties.get(name) || { }; + calculateUsageOfNode(parent, checker, propertyUsage); + usage.properties.set(name, propertyUsage); } - function inferTypeFromPropertyElementExpressionContext(parent: ElementAccessExpression, node: Expression, checker: TypeChecker, usageContext: UsageContext): void { + function inferTypeFromPropertyElementExpression(parent: ElementAccessExpression, node: Expression, checker: TypeChecker, usage: Usage): void { if (node === parent.argumentExpression) { - usageContext.isNumberOrString = true; + usage.isNumberOrString = true; return; } else { const indexType = checker.getTypeAtLocation(parent.argumentExpression); - const indexUsageContext = {}; - inferTypeFromContext(parent, checker, indexUsageContext); + const indexUsage = {}; + calculateUsageOfNode(parent, checker, indexUsage); if (indexType.flags & TypeFlags.NumberLike) { - usageContext.numberIndexContext = indexUsageContext; + usage.numberIndex = indexUsage; } else { - usageContext.stringIndexContext = indexUsageContext; + usage.stringIndex = indexUsage; } } } - function inferTypeFromPropertyAssignment(assignment: PropertyAssignment | ShorthandPropertyAssignment, checker: TypeChecker, usageContext: UsageContext) { + function inferTypeFromPropertyAssignment(assignment: PropertyAssignment | ShorthandPropertyAssignment, checker: TypeChecker, usage: Usage) { const nodeWithRealType = isVariableDeclaration(assignment.parent.parent) ? assignment.parent.parent : assignment.parent; - addCandidateThisType(usageContext, checker.getTypeAtLocation(nodeWithRealType)); + addCandidateThisType(usage, checker.getTypeAtLocation(nodeWithRealType)); } - function inferTypeFromPropertyDeclaration(declaration: PropertyDeclaration, checker: TypeChecker, usageContext: UsageContext) { - addCandidateThisType(usageContext, checker.getTypeAtLocation(declaration.parent)); + function inferTypeFromPropertyDeclaration(declaration: PropertyDeclaration, checker: TypeChecker, usage: Usage) { + addCandidateThisType(usage, checker.getTypeAtLocation(declaration.parent)); } interface Priority { @@ -745,7 +745,7 @@ namespace ts.codefix { return inferences.filter(i => toRemove.every(f => !f(i))); } - export function unifyFromContext(inferences: ReadonlyArray, checker: TypeChecker, fallback = checker.getAnyType()): Type { + export function unifyFromUsage(inferences: ReadonlyArray, checker: TypeChecker, fallback = checker.getAnyType()): Type { if (!inferences.length) return fallback; // 1. string or number individually override string | number @@ -815,82 +815,82 @@ namespace ts.codefix { numberIndices.length ? checker.createIndexInfo(checker.getUnionType(numberIndices), numberIndexReadonly) : undefined); } - function inferFromContext(usageContext: UsageContext, checker: TypeChecker) { + function inferFromUsage(usage: Usage, checker: TypeChecker) { const types = []; - if (usageContext.isNumber) { + if (usage.isNumber) { types.push(checker.getNumberType()); } - if (usageContext.isString) { + if (usage.isString) { types.push(checker.getStringType()); } - if (usageContext.isNumberOrString) { + if (usage.isNumberOrString) { types.push(checker.getUnionType([checker.getStringType(), checker.getNumberType()])); } - types.push(...(usageContext.candidateTypes || []).map(t => checker.getBaseTypeOfLiteralType(t))); + types.push(...(usage.candidateTypes || []).map(t => checker.getBaseTypeOfLiteralType(t))); - if (usageContext.properties && hasCallContext(usageContext.properties.get("then" as __String))) { - const paramType = getParameterTypeFromCallContexts(0, usageContext.properties.get("then" as __String)!.callContexts!, /*isRestParameter*/ false, checker)!; // TODO: GH#18217 + if (usage.properties && hasCalls(usage.properties.get("then" as __String))) { + const paramType = getParameterTypeFromCalls(0, usage.properties.get("then" as __String)!.calls!, /*isRestParameter*/ false, checker)!; // TODO: GH#18217 const types = paramType.getCallSignatures().map(c => c.getReturnType()); types.push(checker.createPromiseType(types.length ? checker.getUnionType(types, UnionReduction.Subtype) : checker.getAnyType())); } - else if (usageContext.properties && hasCallContext(usageContext.properties.get("push" as __String))) { - types.push(checker.createArrayType(getParameterTypeFromCallContexts(0, usageContext.properties.get("push" as __String)!.callContexts!, /*isRestParameter*/ false, checker)!)); + else if (usage.properties && hasCalls(usage.properties.get("push" as __String))) { + types.push(checker.createArrayType(getParameterTypeFromCalls(0, usage.properties.get("push" as __String)!.calls!, /*isRestParameter*/ false, checker)!)); } - if (usageContext.numberIndexContext) { - types.push(checker.createArrayType(recur(usageContext.numberIndexContext))); + if (usage.numberIndex) { + types.push(checker.createArrayType(recur(usage.numberIndex))); } - else if (usageContext.properties || usageContext.callContexts || usageContext.constructContexts || usageContext.stringIndexContext) { + else if (usage.properties || usage.calls || usage.constructs || usage.stringIndex) { const members = createUnderscoreEscapedMap(); const callSignatures: Signature[] = []; const constructSignatures: Signature[] = []; let stringIndexInfo: IndexInfo | undefined; - if (usageContext.properties) { - usageContext.properties.forEach((context, name) => { + if (usage.properties) { + usage.properties.forEach((u, name) => { const symbol = checker.createSymbol(SymbolFlags.Property, name); - symbol.type = recur(context); + symbol.type = recur(u); members.set(name, symbol); }); } - if (usageContext.callContexts) { - for (const callContext of usageContext.callContexts) { - callSignatures.push(getSignatureFromCallContext(callContext, checker)); + if (usage.calls) { + for (const call of usage.calls) { + callSignatures.push(getSignatureFromCall(call, checker)); } } - if (usageContext.constructContexts) { - for (const constructContext of usageContext.constructContexts) { - constructSignatures.push(getSignatureFromCallContext(constructContext, checker)); + if (usage.constructs) { + for (const construct of usage.constructs) { + constructSignatures.push(getSignatureFromCall(construct, checker)); } } - if (usageContext.stringIndexContext) { - stringIndexInfo = checker.createIndexInfo(recur(usageContext.stringIndexContext), /*isReadonly*/ false); + if (usage.stringIndex) { + stringIndexInfo = checker.createIndexInfo(recur(usage.stringIndex), /*isReadonly*/ false); } types.push(checker.createAnonymousType(/*symbol*/ undefined!, members, callSignatures, constructSignatures, stringIndexInfo, /*numberIndexInfo*/ undefined)); // TODO: GH#18217 } return types; - function recur(innerContext: UsageContext): Type { - return unifyFromContext(inferFromContext(innerContext, checker), checker); + function recur(innerUsage: Usage): Type { + return unifyFromUsage(inferFromUsage(innerUsage, checker), checker); } } - function getParameterTypeFromCallContexts(parameterIndex: number, callContexts: CallContext[], isRestParameter: boolean, checker: TypeChecker) { + function getParameterTypeFromCalls(parameterIndex: number, calls: CallUsage[], isRestParameter: boolean, checker: TypeChecker) { let types: Type[] = []; - if (callContexts) { - for (const callContext of callContexts) { - if (callContext.argumentTypes.length > parameterIndex) { + if (calls) { + for (const call of calls) { + if (call.argumentTypes.length > parameterIndex) { if (isRestParameter) { - types = concatenate(types, map(callContext.argumentTypes.slice(parameterIndex), a => checker.getBaseTypeOfLiteralType(a))); + types = concatenate(types, map(call.argumentTypes.slice(parameterIndex), a => checker.getBaseTypeOfLiteralType(a))); } else { - types.push(checker.getBaseTypeOfLiteralType(callContext.argumentTypes[parameterIndex])); + types.push(checker.getBaseTypeOfLiteralType(call.argumentTypes[parameterIndex])); } } } @@ -903,32 +903,32 @@ namespace ts.codefix { return undefined; } - function getSignatureFromCallContext(callContext: CallContext, checker: TypeChecker): Signature { + function getSignatureFromCall(call: CallUsage, checker: TypeChecker): Signature { const parameters: Symbol[] = []; - for (let i = 0; i < callContext.argumentTypes.length; i++) { + for (let i = 0; i < call.argumentTypes.length; i++) { const symbol = checker.createSymbol(SymbolFlags.FunctionScopedVariable, escapeLeadingUnderscores(`arg${i}`)); - symbol.type = checker.getWidenedType(checker.getBaseTypeOfLiteralType(callContext.argumentTypes[i])); + symbol.type = checker.getWidenedType(checker.getBaseTypeOfLiteralType(call.argumentTypes[i])); parameters.push(symbol); } - const returnType = unifyFromContext(inferFromContext(callContext.returnType, checker), checker, checker.getVoidType()); + const returnType = unifyFromUsage(inferFromUsage(call.returnType, checker), checker, checker.getVoidType()); // TODO: GH#18217 - return checker.createSignature(/*declaration*/ undefined!, /*typeParameters*/ undefined, /*thisParameter*/ undefined, parameters, returnType, /*typePredicate*/ undefined, callContext.argumentTypes.length, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); + return checker.createSignature(/*declaration*/ undefined!, /*typeParameters*/ undefined, /*thisParameter*/ undefined, parameters, returnType, /*typePredicate*/ undefined, call.argumentTypes.length, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); } - function addCandidateType(context: UsageContext, type: Type | undefined) { + function addCandidateType(usage: Usage, type: Type | undefined) { if (type && !(type.flags & TypeFlags.Any) && !(type.flags & TypeFlags.Never)) { - (context.candidateTypes || (context.candidateTypes = [])).push(type); + (usage.candidateTypes || (usage.candidateTypes = [])).push(type); } } - function addCandidateThisType(context: UsageContext, type: Type | undefined) { + function addCandidateThisType(usage: Usage, type: Type | undefined) { if (type && !(type.flags & TypeFlags.Any) && !(type.flags & TypeFlags.Never)) { - (context.candidateThisTypes || (context.candidateThisTypes = [])).push(type); + (usage.candidateThisTypes || (usage.candidateThisTypes = [])).push(type); } } - function hasCallContext(usageContext: UsageContext | undefined): boolean { - return !!usageContext && !!usageContext.callContexts; + function hasCalls(usage: Usage | undefined): boolean { + return !!usage && !!usage.calls; } } } From d347b08a42bb705415fe8b859a69697c1d155a59 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Tue, 27 Aug 2019 13:13:58 -0700 Subject: [PATCH 02/13] Copied from old branch 1. Everything explodes! Out of stack space! 2. Results aren't used yet. 3. But call and construct use the new getSignatureFromCalls, so I expect some baseline changes after I get the infinite recursion fixed. --- src/compiler/checker.ts | 3 + src/compiler/types.ts | 1 + src/services/codefixes/inferFromUsage.ts | 85 ++++++++++++++++++++---- 3 files changed, 75 insertions(+), 14 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index cfd83efeb3..1b6fee6f0d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -524,6 +524,9 @@ namespace ts { }, getApparentType, getUnionType, + isTypeAssignableTo: (source, target) => { + return isTypeAssignableTo(source, target); + }, createAnonymousType, createSignature, createSymbol, diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 2c8a882c16..6e92eba8de 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3286,6 +3286,7 @@ namespace ts { /* @internal */ getElementTypeOfArrayType(arrayType: Type): Type | undefined; /* @internal */ createPromiseType(type: Type): Type; + /* @internal */ isTypeAssignableTo(source: Type, target: Type): boolean; /* @internal */ createAnonymousType(symbol: Symbol, members: SymbolTable, callSignatures: Signature[], constructSignatures: Signature[], stringIndexInfo: IndexInfo | undefined, numberIndexInfo: IndexInfo | undefined): Type; /* @internal */ createSignature( declaration: SignatureDeclaration, diff --git a/src/services/codefixes/inferFromUsage.ts b/src/services/codefixes/inferFromUsage.ts index 2eb9eaca7b..4b111dae25 100644 --- a/src/services/codefixes/inferFromUsage.ts +++ b/src/services/codefixes/inferFromUsage.ts @@ -401,7 +401,7 @@ namespace ts.codefix { interface CallUsage { argumentTypes: Type[]; - returnType: Usage; + return_: Usage; } interface Usage { @@ -671,16 +671,17 @@ namespace ts.codefix { function inferTypeFromCallExpression(parent: CallExpression | NewExpression, usage: Usage): void { const call: CallUsage = { argumentTypes: [], - returnType: {} + return_: {} }; if (parent.arguments) { for (const argument of parent.arguments) { + // TODO: should recursively infer a usage here, right? call.argumentTypes.push(checker.getTypeAtLocation(argument)); } } - calculateUsageOfNode(parent, call.returnType); + calculateUsageOfNode(parent, call.return_); if (parent.kind === SyntaxKind.CallExpression) { (usage.calls || (usage.calls = [])).push(call); } @@ -830,6 +831,7 @@ namespace ts.codefix { } types.push(...(usage.candidateTypes || []).map(t => checker.getBaseTypeOfLiteralType(t))); + types.push(...findBuiltinType(usage)); if (usage.properties && hasCalls(usage.properties.get("then" as __String))) { const paramType = getParameterTypeFromCalls(0, usage.properties.get("then" as __String)!.calls!, /*isRestParameter*/ false)!; // TODO: GH#18217 @@ -858,15 +860,11 @@ namespace ts.codefix { } if (usage.calls) { - for (const call of usage.calls) { - callSignatures.push(getSignatureFromCall(call)); - } + callSignatures.push(getSignatureFromCalls(usage.calls)); } if (usage.constructs) { - for (const construct of usage.constructs) { - constructSignatures.push(getSignatureFromCall(construct)); - } + constructSignatures.push(getSignatureFromCalls(usage.constructs)); } if (usage.stringIndex) { @@ -882,6 +880,61 @@ namespace ts.codefix { } } + function combineUsages(usages: Usage[]): Usage { + return { + isNumber: usages.some(u => u.isNumber), + isString: usages.some(u => u.isString), + isNumberOrString: usages.some(u => u.isNumberOrString), + candidateTypes: flatMap(usages, u => u.candidateTypes) as Type[], + properties: undefined, // TODO + calls: flatMap(usages, u => u.calls) as CallUsage[], + constructs: flatMap(usages, u => u.constructs) as CallUsage[], + numberIndex: forEach(usages, u => u.numberIndex), + stringIndex: forEach(usages, u => u.stringIndex), + candidateThisTypes: flatMap(usages, u => u.candidateThisTypes) as Type[], + } + } + + function findBuiltinType(usage: Usage): Type[] { + const builtins = [ + checker.getStringType(), + checker.getNumberType(), + checker.createArrayType(checker.getAnyType()), + checker.createPromiseType(checker.getAnyType()), + // checker.getFunctionType() // not sure what this was supposed to be good for. + ]; + const matches = builtins.filter(t => matchesAllPropertiesOf(t, usage)); + if (false && 0 < matches.length && matches.length < 3) { + return matches; + } + return []; + } + + function matchesAllPropertiesOf(type: Type, usage: Usage) { + if (!usage.properties) return false; + let result = true; + usage.properties.forEach((prop, name) => { + const source = checker.getUnionType(inferFromUsage(prop)); + const target = checker.getTypeOfPropertyOfType(type, name as string); + if (target && prop.calls) { + const sigs = checker.getSignaturesOfType(target, ts.SignatureKind.Call); + result = result && !!sigs.length && sigs.some( + sig => checker.isTypeAssignableTo( + getFunctionFromCalls(prop.calls!), + checker.createAnonymousType(undefined!, createSymbolTable(), [sig], emptyArray, undefined, undefined))); + } + else { + result = result && !!source && !!target && checker.isTypeAssignableTo(source, target); + } + }); + return result; + } + + + function getFunctionFromCalls(calls: CallUsage[]) { + return checker.createAnonymousType(undefined!, createSymbolTable(), [getSignatureFromCalls(calls)], emptyArray, undefined, undefined); + } + function getParameterTypeFromCalls(parameterIndex: number, calls: CallUsage[], isRestParameter: boolean) { let types: Type[] = []; if (calls) { @@ -904,16 +957,20 @@ namespace ts.codefix { return undefined; } - function getSignatureFromCall(call: CallUsage): Signature { + function getSignatureFromCalls(calls: CallUsage[]): Signature { const parameters: Symbol[] = []; - for (let i = 0; i < call.argumentTypes.length; i++) { + const length = Math.max(...calls.map(c => c.argumentTypes.length)); + for (let i = 0; i < length; i++) { const symbol = checker.createSymbol(SymbolFlags.FunctionScopedVariable, escapeLeadingUnderscores(`arg${i}`)); - symbol.type = checker.getWidenedType(checker.getBaseTypeOfLiteralType(call.argumentTypes[i])); + symbol.type = unifyFromUsage(calls.map(call => call.argumentTypes[i] || checker.getUndefinedType())); + if (calls.some(call => call.argumentTypes[i] === undefined)) { + symbol.flags |= SymbolFlags.Optional; + } parameters.push(symbol); } - const returnType = unifyFromUsage(inferFromUsage(call.returnType), checker.getVoidType()); + const returnType = unifyFromUsage(inferFromUsage(combineUsages(calls.map(call => call.return_))), checker.getVoidType()); // TODO: GH#18217 - return checker.createSignature(/*declaration*/ undefined!, /*typeParameters*/ undefined, /*thisParameter*/ undefined, parameters, returnType, /*typePredicate*/ undefined, call.argumentTypes.length, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); + return checker.createSignature(/*declaration*/ undefined!, /*typeParameters*/ undefined, /*thisParameter*/ undefined, parameters, returnType, /*typePredicate*/ undefined, length, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); } function addCandidateType(usage: Usage, type: Type | undefined) { From 945d423ef5b511ae157b15849b2b822de4b48953 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 28 Aug 2019 14:12:21 -0700 Subject: [PATCH 03/13] Fix bugs in combineUsages/getSignatureFromCalls --- src/services/codefixes/inferFromUsage.ts | 76 +++++++++++++++++------- 1 file changed, 56 insertions(+), 20 deletions(-) diff --git a/src/services/codefixes/inferFromUsage.ts b/src/services/codefixes/inferFromUsage.ts index 4b111dae25..92382c462f 100644 --- a/src/services/codefixes/inferFromUsage.ts +++ b/src/services/codefixes/inferFromUsage.ts @@ -405,18 +405,19 @@ namespace ts.codefix { } interface Usage { - isNumber?: boolean; - isString?: boolean; + isNumber: boolean | undefined; + isString: boolean | undefined; /** Used ambiguously, eg x + ___ or object[___]; results in string | number if no other evidence exists */ - isNumberOrString?: boolean; + isNumberOrString: boolean | undefined; - candidateTypes?: Type[]; - properties?: UnderscoreEscapedMap; - calls?: CallUsage[]; - constructs?: CallUsage[]; - numberIndex?: Usage; - stringIndex?: Usage; - candidateThisTypes?: Type[]; + candidateTypes: Type[] | undefined; + properties: UnderscoreEscapedMap | undefined; + calls: CallUsage[] | undefined; + constructs: CallUsage[] | undefined; + numberIndex: Usage | undefined; + stringIndex: Usage | undefined; + candidateThisTypes: Type[] | undefined; + inferredTypes: Type[] | undefined; } function single(): Type { @@ -428,7 +429,7 @@ namespace ts.codefix { return undefined; } - const usage: Usage = {}; + const usage = createEmptyUsage(); for (const reference of references) { cancellationToken.throwIfCancellationRequested(); calculateUsageOfNode(reference, usage); @@ -466,7 +467,7 @@ namespace ts.codefix { } function thisParameter() { - const usage: Usage = {}; + const usage = createEmptyUsage(); for (const reference of references) { cancellationToken.throwIfCancellationRequested(); calculateUsageOfNode(reference, usage); @@ -476,7 +477,7 @@ namespace ts.codefix { } function inferTypesFromReferencesSingle(references: readonly Identifier[]): Type[] { - const usage: Usage = {}; + const usage: Usage = createEmptyUsage(); for (const reference of references) { cancellationToken.throwIfCancellationRequested(); calculateUsageOfNode(reference, usage); @@ -671,7 +672,7 @@ namespace ts.codefix { function inferTypeFromCallExpression(parent: CallExpression | NewExpression, usage: Usage): void { const call: CallUsage = { argumentTypes: [], - return_: {} + return_: createEmptyUsage() }; if (parent.arguments) { @@ -695,7 +696,7 @@ namespace ts.codefix { if (!usage.properties) { usage.properties = createUnderscoreEscapedMap(); } - const propertyUsage = usage.properties.get(name) || { }; + const propertyUsage = usage.properties.get(name) || createEmptyUsage(); calculateUsageOfNode(parent, propertyUsage); usage.properties.set(name, propertyUsage); } @@ -707,7 +708,7 @@ namespace ts.codefix { } else { const indexType = checker.getTypeAtLocation(parent.argumentExpression); - const indexUsage = {}; + const indexUsage = createEmptyUsage(); calculateUsageOfNode(parent, indexUsage); if (indexType.flags & TypeFlags.NumberLike) { usage.numberIndex = indexUsage; @@ -845,7 +846,10 @@ namespace ts.codefix { if (usage.numberIndex) { types.push(checker.createArrayType(recur(usage.numberIndex))); } - else if (usage.properties || usage.calls || usage.constructs || usage.stringIndex) { + else if (usage.properties && usage.properties.size + || usage.calls && usage.calls.length + || usage.constructs && usage.constructs.length + || usage.stringIndex) { const members = createUnderscoreEscapedMap(); const callSignatures: Signature[] = []; const constructSignatures: Signature[] = []; @@ -873,25 +877,57 @@ namespace ts.codefix { types.push(checker.createAnonymousType(/*symbol*/ undefined!, members, callSignatures, constructSignatures, stringIndexInfo, /*numberIndexInfo*/ undefined)); // TODO: GH#18217 } - return types; + return types; // TODO: Should cache this since I HOPE it doesn't change function recur(innerUsage: Usage): Type { return unifyFromUsage(inferFromUsage(innerUsage)); } } + function createEmptyUsage(): Usage { + return { + isNumber: undefined, + isString: undefined, + isNumberOrString: undefined, + candidateTypes: undefined, + properties: undefined, + calls: undefined, + constructs: undefined, + numberIndex: undefined, + stringIndex: undefined, + candidateThisTypes: undefined, + inferredTypes: undefined, + } + } + function combineUsages(usages: Usage[]): Usage { + const combinedProperties = createUnderscoreEscapedMap() + for (const u of usages) { + if (u.properties) { + u.properties.forEach((p,name) => { + if (!combinedProperties.has(name)) { + combinedProperties.set(name, []); + } + combinedProperties.get(name)!.push(p); + }); + } + } + const properties = createUnderscoreEscapedMap() + combinedProperties.forEach((ps,name) => { + properties.set(name, combineUsages(ps)); + }); return { isNumber: usages.some(u => u.isNumber), isString: usages.some(u => u.isString), isNumberOrString: usages.some(u => u.isNumberOrString), candidateTypes: flatMap(usages, u => u.candidateTypes) as Type[], - properties: undefined, // TODO + properties, calls: flatMap(usages, u => u.calls) as CallUsage[], constructs: flatMap(usages, u => u.constructs) as CallUsage[], numberIndex: forEach(usages, u => u.numberIndex), stringIndex: forEach(usages, u => u.stringIndex), candidateThisTypes: flatMap(usages, u => u.candidateThisTypes) as Type[], + inferredTypes: undefined, // clear type cache } } @@ -901,7 +937,7 @@ namespace ts.codefix { checker.getNumberType(), checker.createArrayType(checker.getAnyType()), checker.createPromiseType(checker.getAnyType()), - // checker.getFunctionType() // not sure what this was supposed to be good for. + // checker.getFunctionType() // TODO: not sure what this was supposed to be good for. ]; const matches = builtins.filter(t => matchesAllPropertiesOf(t, usage)); if (false && 0 < matches.length && matches.length < 3) { From 37150d9cb548e6e7802b731e8c03a298434e18d7 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Thu, 29 Aug 2019 12:40:33 -0700 Subject: [PATCH 04/13] Turn on findBuiltinTypes Type parameter inference is special-cased, just moved from its previous place with no improvement. --- src/services/codefixes/inferFromUsage.ts | 103 +++++++++--------- .../codeFixInferFromFunctionUsage.ts | 4 +- .../codeFixInferFromPrimitiveUsage.ts | 9 ++ .../codeFixInferFromUsageCallBodyBoth.ts | 2 +- 4 files changed, 65 insertions(+), 53 deletions(-) create mode 100644 tests/cases/fourslash/codeFixInferFromPrimitiveUsage.ts diff --git a/src/services/codefixes/inferFromUsage.ts b/src/services/codefixes/inferFromUsage.ts index 92382c462f..ab1c2a4a52 100644 --- a/src/services/codefixes/inferFromUsage.ts +++ b/src/services/codefixes/inferFromUsage.ts @@ -421,7 +421,7 @@ namespace ts.codefix { } function single(): Type { - return unifyFromUsage(inferTypesFromReferencesSingle(references)); + return unifyTypes(inferTypesFromReferencesSingle(references)); } function parameters(declaration: FunctionLike): ParameterInference[] | undefined { @@ -457,7 +457,7 @@ namespace ts.codefix { const inferred = inferTypesFromReferencesSingle(getReferences(parameter.name, program, cancellationToken)); types.push(...(isRest ? mapDefined(inferred, checker.getElementTypeOfArrayType) : inferred)); } - const type = unifyFromUsage(types); + const type = unifyTypes(types); return { type: isRest ? checker.createArrayType(type) : type, isOptional: isOptional && !isRest, @@ -473,7 +473,7 @@ namespace ts.codefix { calculateUsageOfNode(reference, usage); } - return unifyFromUsage(usage.candidateThisTypes || emptyArray); + return unifyTypes(usage.candidateThisTypes || emptyArray); } function inferTypesFromReferencesSingle(references: readonly Identifier[]): Type[] { @@ -627,6 +627,9 @@ namespace ts.codefix { else if (otherOperandType.flags & TypeFlags.StringLike) { usage.isString = true; } + else if (otherOperandType.flags & TypeFlags.Any) { + // do nothing, maybe we'll learn something elsewhere + } else { usage.isNumberOrString = true; } @@ -748,7 +751,7 @@ namespace ts.codefix { return inferences.filter(i => toRemove.every(f => !f(i))); } - function unifyFromUsage(inferences: ReadonlyArray, fallback = checker.getAnyType()): Type { + function unifyTypes(inferences: ReadonlyArray, fallback = checker.getAnyType()): Type { if (!inferences.length) return fallback; // 1. string or number individually override string | number @@ -774,7 +777,7 @@ namespace ts.codefix { good = good.filter(i => !(checker.getObjectFlags(i) & ObjectFlags.Anonymous)); good.push(unifyAnonymousTypes(anons)); } - return checker.getWidenedType(checker.getUnionType(good)); + return checker.getWidenedType(checker.getUnionType(good, UnionReduction.Subtype)); } function unifyAnonymousTypes(anons: AnonymousType[]) { @@ -832,16 +835,7 @@ namespace ts.codefix { } types.push(...(usage.candidateTypes || []).map(t => checker.getBaseTypeOfLiteralType(t))); - types.push(...findBuiltinType(usage)); - - if (usage.properties && hasCalls(usage.properties.get("then" as __String))) { - const paramType = getParameterTypeFromCalls(0, usage.properties.get("then" as __String)!.calls!, /*isRestParameter*/ false)!; // TODO: GH#18217 - const types = paramType.getCallSignatures().map(sig => sig.getReturnType()); - types.push(checker.createPromiseType(types.length ? checker.getUnionType(types, UnionReduction.Subtype) : checker.getAnyType())); - } - else if (usage.properties && hasCalls(usage.properties.get("push" as __String))) { - types.push(checker.createArrayType(getParameterTypeFromCalls(0, usage.properties.get("push" as __String)!.calls!, /*isRestParameter*/ false)!)); - } + types.push(...findBuiltinTypes(usage)); if (usage.numberIndex) { types.push(checker.createArrayType(recur(usage.numberIndex))); @@ -864,11 +858,12 @@ namespace ts.codefix { } if (usage.calls) { - callSignatures.push(getSignatureFromCalls(usage.calls)); + callSignatures.push(getSignatureFromCalls(usage.calls, checker.getVoidType())); } if (usage.constructs) { - constructSignatures.push(getSignatureFromCalls(usage.constructs)); + // TODO: fallback return should maybe be {}? + constructSignatures.push(getSignatureFromCalls(usage.constructs, checker.getVoidType())); } if (usage.stringIndex) { @@ -880,7 +875,7 @@ namespace ts.codefix { return types; // TODO: Should cache this since I HOPE it doesn't change function recur(innerUsage: Usage): Type { - return unifyFromUsage(inferFromUsage(innerUsage)); + return unifyTypes(inferFromUsage(innerUsage)); } } @@ -931,7 +926,8 @@ namespace ts.codefix { } } - function findBuiltinType(usage: Usage): Type[] { + function findBuiltinTypes(usage: Usage): Type[] { + if (!usage.properties || !usage.properties.size) return []; const builtins = [ checker.getStringType(), checker.getNumberType(), @@ -939,9 +935,21 @@ namespace ts.codefix { checker.createPromiseType(checker.getAnyType()), // checker.getFunctionType() // TODO: not sure what this was supposed to be good for. ]; + // TODO: Still need to infer type parameters const matches = builtins.filter(t => matchesAllPropertiesOf(t, usage)); - if (false && 0 < matches.length && matches.length < 3) { - return matches; + if (0 < matches.length && matches.length < 3) { + return matches.map(m => { + // special-case array and promise for now + if (m === builtins[3] && hasCalls(usage.properties!.get("then" as __String))) { + const paramType = getParameterTypeFromCalls(0, usage.properties!.get("then" as __String)!.calls!, /*isRestParameter*/ false)!; // TODO: GH#18217 + const returns = paramType.getCallSignatures().map(sig => sig.getReturnType()); + return checker.createPromiseType(returns.length ? checker.getUnionType(returns, UnionReduction.Subtype) : checker.getAnyType()); + } + else if (m === builtins[2] && hasCalls(usage.properties!.get("push" as __String))) { + return checker.createArrayType(getParameterTypeFromCalls(0, usage.properties!.get("push" as __String)!.calls!, /*isRestParameter*/ false)!); + } + return m; + }); } return []; } @@ -949,18 +957,18 @@ namespace ts.codefix { function matchesAllPropertiesOf(type: Type, usage: Usage) { if (!usage.properties) return false; let result = true; - usage.properties.forEach((prop, name) => { - const source = checker.getUnionType(inferFromUsage(prop)); - const target = checker.getTypeOfPropertyOfType(type, name as string); - if (target && prop.calls) { - const sigs = checker.getSignaturesOfType(target, ts.SignatureKind.Call); - result = result && !!sigs.length && sigs.some( - sig => checker.isTypeAssignableTo( - getFunctionFromCalls(prop.calls!), - checker.createAnonymousType(undefined!, createSymbolTable(), [sig], emptyArray, undefined, undefined))); + usage.properties.forEach((propUsage, name) => { + const source = checker.getTypeOfPropertyOfType(type, name as string); + if (!source) { + result = false; + return; + } + if (propUsage.calls) { + const sigs = checker.getSignaturesOfType(source, ts.SignatureKind.Call); + result = result && !!sigs.length && checker.isTypeAssignableTo(source, getFunctionFromCalls(propUsage.calls)); } else { - result = result && !!source && !!target && checker.isTypeAssignableTo(source, target); + result = result && checker.isTypeAssignableTo(source, unifyTypes(inferFromUsage(propUsage))); } }); return result; @@ -968,43 +976,38 @@ namespace ts.codefix { function getFunctionFromCalls(calls: CallUsage[]) { - return checker.createAnonymousType(undefined!, createSymbolTable(), [getSignatureFromCalls(calls)], emptyArray, undefined, undefined); + return checker.createAnonymousType(undefined!, createSymbolTable(), [getSignatureFromCalls(calls, checker.getAnyType())], emptyArray, undefined, undefined); } function getParameterTypeFromCalls(parameterIndex: number, calls: CallUsage[], isRestParameter: boolean) { + // TODO: This is largely redundant with getSignatureFromCalls, I think. (though it handles rest parameters correctly, so that needs to be integrated there) let types: Type[] = []; - if (calls) { - for (const call of calls) { - if (call.argumentTypes.length > parameterIndex) { - if (isRestParameter) { - types = concatenate(types, map(call.argumentTypes.slice(parameterIndex), a => checker.getBaseTypeOfLiteralType(a))); - } - else { - types.push(checker.getBaseTypeOfLiteralType(call.argumentTypes[parameterIndex])); - } + for (const call of calls) { + if (call.argumentTypes.length > parameterIndex) { + if (isRestParameter) { + types = concatenate(types, map(call.argumentTypes.slice(parameterIndex), a => checker.getBaseTypeOfLiteralType(a))); + } + else { + types.push(checker.getBaseTypeOfLiteralType(call.argumentTypes[parameterIndex])); } } } - - if (types.length) { - const type = checker.getWidenedType(checker.getUnionType(types, UnionReduction.Subtype)); - return isRestParameter ? checker.createArrayType(type) : type; - } - return undefined; + const type = unifyTypes(types); + return isRestParameter ? checker.createArrayType(type) : type; } - function getSignatureFromCalls(calls: CallUsage[]): Signature { + function getSignatureFromCalls(calls: CallUsage[], fallbackReturn: Type): Signature { const parameters: Symbol[] = []; const length = Math.max(...calls.map(c => c.argumentTypes.length)); for (let i = 0; i < length; i++) { const symbol = checker.createSymbol(SymbolFlags.FunctionScopedVariable, escapeLeadingUnderscores(`arg${i}`)); - symbol.type = unifyFromUsage(calls.map(call => call.argumentTypes[i] || checker.getUndefinedType())); + symbol.type = unifyTypes(calls.map(call => call.argumentTypes[i] || checker.getUndefinedType())); if (calls.some(call => call.argumentTypes[i] === undefined)) { symbol.flags |= SymbolFlags.Optional; } parameters.push(symbol); } - const returnType = unifyFromUsage(inferFromUsage(combineUsages(calls.map(call => call.return_))), checker.getVoidType()); + const returnType = unifyTypes(inferFromUsage(combineUsages(calls.map(call => call.return_))), fallbackReturn); // TODO: GH#18217 return checker.createSignature(/*declaration*/ undefined!, /*typeParameters*/ undefined, /*thisParameter*/ undefined, parameters, returnType, /*typePredicate*/ undefined, length, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); } diff --git a/tests/cases/fourslash/codeFixInferFromFunctionUsage.ts b/tests/cases/fourslash/codeFixInferFromFunctionUsage.ts index 27a039878b..f2dbad67b4 100644 --- a/tests/cases/fourslash/codeFixInferFromFunctionUsage.ts +++ b/tests/cases/fourslash/codeFixInferFromFunctionUsage.ts @@ -2,8 +2,8 @@ // @noImplicitAny: true ////function wrap( [| arr |] ) { -//// arr.sort(function (a: number, b: number) { return a < b ? -1 : 1 }) +//// arr.other(function (a: number, b: number) { return a < b ? -1 : 1 }) //// } // https://github.com/Microsoft/TypeScript/issues/29330 -verify.rangeAfterCodeFix("arr: { sort: (arg0: (a: number, b: number) => 1 | -1) => void; }"); +verify.rangeAfterCodeFix("arr: { other: (arg0: (a: number, b: number) => 1 | -1) => void; }"); diff --git a/tests/cases/fourslash/codeFixInferFromPrimitiveUsage.ts b/tests/cases/fourslash/codeFixInferFromPrimitiveUsage.ts new file mode 100644 index 0000000000..81c2577008 --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromPrimitiveUsage.ts @@ -0,0 +1,9 @@ +/// + +// @noImplicitAny: true +////function wrap( [| s |] ) { +//// return s.length + s.toUpperCase() +//// } + +// https://github.com/Microsoft/TypeScript/issues/29330 +verify.rangeAfterCodeFix("s: string"); diff --git a/tests/cases/fourslash/codeFixInferFromUsageCallBodyBoth.ts b/tests/cases/fourslash/codeFixInferFromUsageCallBodyBoth.ts index 859d5ea2ce..f59d1bc190 100644 --- a/tests/cases/fourslash/codeFixInferFromUsageCallBodyBoth.ts +++ b/tests/cases/fourslash/codeFixInferFromUsageCallBodyBoth.ts @@ -1,7 +1,7 @@ /// ////class C { -//// +//// p = 2 ////} ////var c = new C() ////function f([|x, y |]) { From 383286ff533aad1e9fe3bfa907ef84689eff920e Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Fri, 30 Aug 2019 13:44:06 -0700 Subject: [PATCH 05/13] Add type parameter inference It's a smeary copy of the checker's type parameter, so I feel bad about duplicating that code. Not sure what the solution is, architecturally. --- src/services/codefixes/inferFromUsage.ts | 132 ++++++++++++------ .../codeFixInferFromPrimitiveUsage.ts | 7 +- 2 files changed, 94 insertions(+), 45 deletions(-) diff --git a/src/services/codefixes/inferFromUsage.ts b/src/services/codefixes/inferFromUsage.ts index ab1c2a4a52..0a190e7b37 100644 --- a/src/services/codefixes/inferFromUsage.ts +++ b/src/services/codefixes/inferFromUsage.ts @@ -393,6 +393,19 @@ namespace ts.codefix { function inferTypeFromReferences(program: Program, references: ReadonlyArray, cancellationToken: CancellationToken) { const checker = program.getTypeChecker(); + const builtinConstructors: { [s: string]: (t: Type) => Type } = { + string: () => checker.getStringType(), + number: () => checker.getNumberType(), + Array: t => checker.createArrayType(t), + Promise: t => checker.createPromiseType(t), + }; + const builtins = [ + checker.getStringType(), + checker.getNumberType(), + checker.createArrayType(checker.getAnyType()), + checker.createPromiseType(checker.getAnyType()), + ]; + return { single, parameters, @@ -777,7 +790,7 @@ namespace ts.codefix { good = good.filter(i => !(checker.getObjectFlags(i) & ObjectFlags.Anonymous)); good.push(unifyAnonymousTypes(anons)); } - return checker.getWidenedType(checker.getUnionType(good, UnionReduction.Subtype)); + return checker.getWidenedType(checker.getUnionType(good.map(checker.getBaseTypeOfLiteralType), UnionReduction.Subtype)); } function unifyAnonymousTypes(anons: AnonymousType[]) { @@ -928,28 +941,9 @@ namespace ts.codefix { function findBuiltinTypes(usage: Usage): Type[] { if (!usage.properties || !usage.properties.size) return []; - const builtins = [ - checker.getStringType(), - checker.getNumberType(), - checker.createArrayType(checker.getAnyType()), - checker.createPromiseType(checker.getAnyType()), - // checker.getFunctionType() // TODO: not sure what this was supposed to be good for. - ]; - // TODO: Still need to infer type parameters const matches = builtins.filter(t => matchesAllPropertiesOf(t, usage)); if (0 < matches.length && matches.length < 3) { - return matches.map(m => { - // special-case array and promise for now - if (m === builtins[3] && hasCalls(usage.properties!.get("then" as __String))) { - const paramType = getParameterTypeFromCalls(0, usage.properties!.get("then" as __String)!.calls!, /*isRestParameter*/ false)!; // TODO: GH#18217 - const returns = paramType.getCallSignatures().map(sig => sig.getReturnType()); - return checker.createPromiseType(returns.length ? checker.getUnionType(returns, UnionReduction.Subtype) : checker.getAnyType()); - } - else if (m === builtins[2] && hasCalls(usage.properties!.get("push" as __String))) { - return checker.createArrayType(getParameterTypeFromCalls(0, usage.properties!.get("push" as __String)!.calls!, /*isRestParameter*/ false)!); - } - return m; - }); + return matches.map(m => inferTypeParameterFromUsage(m, usage)); } return []; } @@ -974,28 +968,86 @@ namespace ts.codefix { return result; } + // inference is limited to + // 1. generic types with a single parameter + // 2. inference to/from calls with a single signature + function inferTypeParameterFromUsage(type: Type, usage: Usage) { + if (!usage.properties || !(getObjectFlags(type) & ObjectFlags.Reference)) return type; + const generic = (type as TypeReference).target; + const singleTypeParameter = singleOrUndefined(generic.typeParameters); + if (!singleTypeParameter) return type; + + const types: Type[] = []; + usage.properties.forEach((propUsage, name) => { + const source = checker.getTypeOfPropertyOfType(generic, name as string); + if (!source) { + return Debug.fail("generic should have all the properties of its reference."); + } + if (!propUsage.calls) return; + + types.push(...infer(source, getFunctionFromCalls(propUsage.calls), singleTypeParameter)); + }); + return builtinConstructors[type.symbol.escapedName as string](unifyTypes(types)); + } + + // TODO: Source and target are bad names. Should be builtinType and usageType...or something + // and search is a bad name + function infer(source: Type, target: Type, search: Type): readonly Type[] { + if (source === search) { + return [target]; + } + else if (source.flags & TypeFlags.UnionOrIntersection) { + return flatMap((source as UnionOrIntersectionType).types, t => infer(t, target, search)); + } + else if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference) { + // this is wrong because we need a reference to the targetType to, so we can check that it's also a reference + const sourceArgs = (source as TypeReference).typeArguments; + const targetArgs = (target as TypeReference).typeArguments; + const types = []; + if (sourceArgs && targetArgs) { + for (let i = 0; i < sourceArgs.length; i++) { + if (targetArgs[i]) { + types.push(...infer(sourceArgs[i], targetArgs[i], search)); + } + } + } + return types; + } + const sourceSigs = checker.getSignaturesOfType(source, SignatureKind.Call); + const targetSigs = checker.getSignaturesOfType(target, SignatureKind.Call); + if (sourceSigs.length === 1 && targetSigs.length === 1) { + return inferFromSignatures(sourceSigs[0], targetSigs[0], search); + } + return []; + } + + function inferFromSignatures(sourceSig: Signature, targetSig: Signature, search: Type) { + const types = []; + for (let i = 0; i < sourceSig.parameters.length; i++) { + const sourceParam = sourceSig.parameters[i]; + const targetParam = targetSig.parameters[i]; + const isRest = sourceSig.declaration && isRestParameter(sourceSig.declaration.parameters[i]); + if (!targetParam) { + break; + } + let sourceType = checker.getTypeOfSymbolAtLocation(sourceParam, sourceParam.valueDeclaration); + let elementType = isRest && checker.getElementTypeOfArrayType(sourceType); + if (elementType) { + sourceType = elementType; + } + const targetType = (targetParam as SymbolLinks).type || checker.getTypeOfSymbolAtLocation(targetParam, targetParam.valueDeclaration); + types.push(...infer(sourceType, targetType, search)); + } + const sourceReturn = checker.getReturnTypeOfSignature(sourceSig); + const targetReturn = checker.getReturnTypeOfSignature(targetSig); + types.push(...infer(sourceReturn, targetReturn, search)); + return types; + } function getFunctionFromCalls(calls: CallUsage[]) { return checker.createAnonymousType(undefined!, createSymbolTable(), [getSignatureFromCalls(calls, checker.getAnyType())], emptyArray, undefined, undefined); } - function getParameterTypeFromCalls(parameterIndex: number, calls: CallUsage[], isRestParameter: boolean) { - // TODO: This is largely redundant with getSignatureFromCalls, I think. (though it handles rest parameters correctly, so that needs to be integrated there) - let types: Type[] = []; - for (const call of calls) { - if (call.argumentTypes.length > parameterIndex) { - if (isRestParameter) { - types = concatenate(types, map(call.argumentTypes.slice(parameterIndex), a => checker.getBaseTypeOfLiteralType(a))); - } - else { - types.push(checker.getBaseTypeOfLiteralType(call.argumentTypes[parameterIndex])); - } - } - } - const type = unifyTypes(types); - return isRestParameter ? checker.createArrayType(type) : type; - } - function getSignatureFromCalls(calls: CallUsage[], fallbackReturn: Type): Signature { const parameters: Symbol[] = []; const length = Math.max(...calls.map(c => c.argumentTypes.length)); @@ -1023,9 +1075,5 @@ namespace ts.codefix { (usage.candidateThisTypes || (usage.candidateThisTypes = [])).push(type); } } - - function hasCalls(usage: Usage | undefined): boolean { - return !!usage && !!usage.calls; - } } } diff --git a/tests/cases/fourslash/codeFixInferFromPrimitiveUsage.ts b/tests/cases/fourslash/codeFixInferFromPrimitiveUsage.ts index 81c2577008..b74e49b4d3 100644 --- a/tests/cases/fourslash/codeFixInferFromPrimitiveUsage.ts +++ b/tests/cases/fourslash/codeFixInferFromPrimitiveUsage.ts @@ -1,9 +1,10 @@ /// // @noImplicitAny: true -////function wrap( [| s |] ) { -//// return s.length + s.toUpperCase() +//// function wrap( [| s |] ) { +//// return s.length + s.indexOf('hi') //// } // https://github.com/Microsoft/TypeScript/issues/29330 -verify.rangeAfterCodeFix("s: string"); +verify.rangeAfterCodeFix("s: string | string[]"); + From 052a3d9d73a009e11f26fa8ba99f6e72c63b5e6d Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Thu, 5 Sep 2019 16:16:35 -0700 Subject: [PATCH 06/13] Infer void from expr statement usage, not calls This makes inferences a lot better. --- src/services/codefixes/inferFromUsage.ts | 8 +++++--- .../cases/fourslash/codeFixInferFromCallInAssignment.ts | 9 +++++++++ .../fourslash/codeFixInferFromExpressionStatement.ts | 8 ++++++++ .../codeFixInferFromUsageCommentAfterParameter.ts | 2 +- tests/cases/fourslash/codeFixInferFromUsageJSXElement.ts | 2 +- .../fourslash/codeFixInferFromUsagePropertyAccess.ts | 2 +- .../fourslash/codeFixInferFromUsagePropertyAccessJS.ts | 2 +- 7 files changed, 26 insertions(+), 7 deletions(-) create mode 100644 tests/cases/fourslash/codeFixInferFromCallInAssignment.ts create mode 100644 tests/cases/fourslash/codeFixInferFromExpressionStatement.ts diff --git a/src/services/codefixes/inferFromUsage.ts b/src/services/codefixes/inferFromUsage.ts index 0a190e7b37..daa2166103 100644 --- a/src/services/codefixes/inferFromUsage.ts +++ b/src/services/codefixes/inferFromUsage.ts @@ -504,6 +504,9 @@ namespace ts.codefix { } switch (node.parent.kind) { + case SyntaxKind.ExpressionStatement: + addCandidateType(usage, checker.getVoidType()); + break; case SyntaxKind.PostfixUnaryExpression: usage.isNumber = true; break; @@ -871,12 +874,11 @@ namespace ts.codefix { } if (usage.calls) { - callSignatures.push(getSignatureFromCalls(usage.calls, checker.getVoidType())); + callSignatures.push(getSignatureFromCalls(usage.calls, checker.getAnyType())); } if (usage.constructs) { - // TODO: fallback return should maybe be {}? - constructSignatures.push(getSignatureFromCalls(usage.constructs, checker.getVoidType())); + constructSignatures.push(getSignatureFromCalls(usage.constructs, checker.getAnyType())); } if (usage.stringIndex) { diff --git a/tests/cases/fourslash/codeFixInferFromCallInAssignment.ts b/tests/cases/fourslash/codeFixInferFromCallInAssignment.ts new file mode 100644 index 0000000000..85e1d3bc50 --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromCallInAssignment.ts @@ -0,0 +1,9 @@ +/// + +// @noImplicitAny: true +//// function inferAny( [| app |] ) { +//// const result = app.use('hi') +//// return result +//// } + +verify.rangeAfterCodeFix("app: { use: (arg0: string) => any; }"); diff --git a/tests/cases/fourslash/codeFixInferFromExpressionStatement.ts b/tests/cases/fourslash/codeFixInferFromExpressionStatement.ts new file mode 100644 index 0000000000..b5969c7937 --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromExpressionStatement.ts @@ -0,0 +1,8 @@ +/// + +// @noImplicitAny: true +//// function inferVoid( [| app |] ) { +//// app.use('hi') +//// } + +verify.rangeAfterCodeFix("app: { use: (arg0: string) => void; }"); diff --git a/tests/cases/fourslash/codeFixInferFromUsageCommentAfterParameter.ts b/tests/cases/fourslash/codeFixInferFromUsageCommentAfterParameter.ts index bdefd2b5de..866bfe04c1 100644 --- a/tests/cases/fourslash/codeFixInferFromUsageCommentAfterParameter.ts +++ b/tests/cases/fourslash/codeFixInferFromUsageCommentAfterParameter.ts @@ -16,7 +16,7 @@ verify.codeFix({ index: 0, newFileContent: `/** - * @param {(arg0: any) => void} callback + * @param {(arg0: any) => any} callback */ function coll(callback /*, name1, name2, ... */) { return callback(this); diff --git a/tests/cases/fourslash/codeFixInferFromUsageJSXElement.ts b/tests/cases/fourslash/codeFixInferFromUsageJSXElement.ts index 3200fffafb..abe35fe46c 100644 --- a/tests/cases/fourslash/codeFixInferFromUsageJSXElement.ts +++ b/tests/cases/fourslash/codeFixInferFromUsageJSXElement.ts @@ -30,4 +30,4 @@ //// } -verify.rangeAfterCodeFix("props: { isLoading: any; update: (arg0: any) => void; }",/*includeWhiteSpace*/ undefined, /*errorCode*/ undefined, 0); +verify.rangeAfterCodeFix("props: { isLoading: any; update: (arg0: any) => any; }",/*includeWhiteSpace*/ undefined, /*errorCode*/ undefined, 0); diff --git a/tests/cases/fourslash/codeFixInferFromUsagePropertyAccess.ts b/tests/cases/fourslash/codeFixInferFromUsagePropertyAccess.ts index c8b85ee11e..44d2e2d7b1 100644 --- a/tests/cases/fourslash/codeFixInferFromUsagePropertyAccess.ts +++ b/tests/cases/fourslash/codeFixInferFromUsagePropertyAccess.ts @@ -12,4 +12,4 @@ //// return x.y.z ////} -verify.rangeAfterCodeFix("a: { b: { c: any; }; }, m: { n: () => number; }, x: { y: { z: number[]; }; }", /*includeWhiteSpace*/ undefined, /*errorCode*/ undefined, /*index*/0); \ No newline at end of file +verify.rangeAfterCodeFix("a: { b: { c: void; }; }, m: { n: () => number; }, x: { y: { z: number[]; }; }", /*includeWhiteSpace*/ undefined, /*errorCode*/ undefined, /*index*/0); diff --git a/tests/cases/fourslash/codeFixInferFromUsagePropertyAccessJS.ts b/tests/cases/fourslash/codeFixInferFromUsagePropertyAccessJS.ts index ed9d4b33b3..357b1a991b 100644 --- a/tests/cases/fourslash/codeFixInferFromUsagePropertyAccessJS.ts +++ b/tests/cases/fourslash/codeFixInferFromUsagePropertyAccessJS.ts @@ -21,7 +21,7 @@ verify.codeFix({ index: 0, newFileContent: `/** - * @param {{ b: { c: any; }; }} a + * @param {{ b: { c: void; }; }} a * @param {{ n: () => number; }} m * @param {{ y: { z: number[]; }; }} x */ From d32c6b2df1e1988c9565b4a7d08e422337540c2a Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Thu, 5 Sep 2019 16:21:08 -0700 Subject: [PATCH 07/13] Fallback type is always any now void is explicitly inferred now, never used as a fallback. --- src/services/codefixes/inferFromUsage.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/services/codefixes/inferFromUsage.ts b/src/services/codefixes/inferFromUsage.ts index daa2166103..b453535b46 100644 --- a/src/services/codefixes/inferFromUsage.ts +++ b/src/services/codefixes/inferFromUsage.ts @@ -767,8 +767,8 @@ namespace ts.codefix { return inferences.filter(i => toRemove.every(f => !f(i))); } - function unifyTypes(inferences: ReadonlyArray, fallback = checker.getAnyType()): Type { - if (!inferences.length) return fallback; + function unifyTypes(inferences: ReadonlyArray): Type { + if (!inferences.length) return checker.getAnyType(); // 1. string or number individually override string | number // 2. non-any, non-void overrides any or void @@ -874,11 +874,11 @@ namespace ts.codefix { } if (usage.calls) { - callSignatures.push(getSignatureFromCalls(usage.calls, checker.getAnyType())); + callSignatures.push(getSignatureFromCalls(usage.calls)); } if (usage.constructs) { - constructSignatures.push(getSignatureFromCalls(usage.constructs, checker.getAnyType())); + constructSignatures.push(getSignatureFromCalls(usage.constructs)); } if (usage.stringIndex) { @@ -1047,10 +1047,10 @@ namespace ts.codefix { } function getFunctionFromCalls(calls: CallUsage[]) { - return checker.createAnonymousType(undefined!, createSymbolTable(), [getSignatureFromCalls(calls, checker.getAnyType())], emptyArray, undefined, undefined); + return checker.createAnonymousType(undefined!, createSymbolTable(), [getSignatureFromCalls(calls)], emptyArray, undefined, undefined); } - function getSignatureFromCalls(calls: CallUsage[], fallbackReturn: Type): Signature { + function getSignatureFromCalls(calls: CallUsage[]): Signature { const parameters: Symbol[] = []; const length = Math.max(...calls.map(c => c.argumentTypes.length)); for (let i = 0; i < length; i++) { @@ -1061,7 +1061,7 @@ namespace ts.codefix { } parameters.push(symbol); } - const returnType = unifyTypes(inferFromUsage(combineUsages(calls.map(call => call.return_))), fallbackReturn); + const returnType = unifyTypes(inferFromUsage(combineUsages(calls.map(call => call.return_)))); // TODO: GH#18217 return checker.createSignature(/*declaration*/ undefined!, /*typeParameters*/ undefined, /*thisParameter*/ undefined, parameters, returnType, /*typePredicate*/ undefined, length, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); } From f394190572fb76b194df5f093f1af51abc1ffb98 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Fri, 6 Sep 2019 11:18:50 -0700 Subject: [PATCH 08/13] Tonnes of cleanup --- src/services/codefixes/inferFromUsage.ts | 167 ++++++++++++----------- 1 file changed, 87 insertions(+), 80 deletions(-) diff --git a/src/services/codefixes/inferFromUsage.ts b/src/services/codefixes/inferFromUsage.ts index b453535b46..2e9fb0dfc7 100644 --- a/src/services/codefixes/inferFromUsage.ts +++ b/src/services/codefixes/inferFromUsage.ts @@ -433,6 +433,53 @@ namespace ts.codefix { inferredTypes: Type[] | undefined; } + function createEmptyUsage(): Usage { + return { + isNumber: undefined, + isString: undefined, + isNumberOrString: undefined, + candidateTypes: undefined, + properties: undefined, + calls: undefined, + constructs: undefined, + numberIndex: undefined, + stringIndex: undefined, + candidateThisTypes: undefined, + inferredTypes: undefined, + }; + } + + function combineUsages(usages: Usage[]): Usage { + const combinedProperties = createUnderscoreEscapedMap(); + for (const u of usages) { + if (u.properties) { + u.properties.forEach((p, name) => { + if (!combinedProperties.has(name)) { + combinedProperties.set(name, []); + } + combinedProperties.get(name)!.push(p); + }); + } + } + const properties = createUnderscoreEscapedMap(); + combinedProperties.forEach((ps, name) => { + properties.set(name, combineUsages(ps)); + }); + return { + isNumber: usages.some(u => u.isNumber), + isString: usages.some(u => u.isString), + isNumberOrString: usages.some(u => u.isNumberOrString), + candidateTypes: flatMap(usages, u => u.candidateTypes) as Type[], + properties, + calls: flatMap(usages, u => u.calls) as CallUsage[], + constructs: flatMap(usages, u => u.constructs) as CallUsage[], + numberIndex: forEach(usages, u => u.numberIndex), + stringIndex: forEach(usages, u => u.stringIndex), + candidateThisTypes: flatMap(usages, u => u.candidateThisTypes) as Type[], + inferredTypes: undefined, // clear type cache + }; + } + function single(): Type { return unifyTypes(inferTypesFromReferencesSingle(references)); } @@ -767,6 +814,10 @@ namespace ts.codefix { return inferences.filter(i => toRemove.every(f => !f(i))); } + function unifyFromUsage(usage: Usage) { + return unifyTypes(inferFromUsage(usage)); + } + function unifyTypes(inferences: ReadonlyArray): Type { if (!inferences.length) return checker.getAnyType(); @@ -837,7 +888,7 @@ namespace ts.codefix { numberIndices.length ? checker.createIndexInfo(checker.getUnionType(numberIndices), numberIndexReadonly) : undefined); } - function inferFromUsage(usage: Usage) { + function inferFromUsage(usage: Usage): Type[] { const types = []; if (usage.isNumber) { @@ -851,12 +902,20 @@ namespace ts.codefix { } types.push(...(usage.candidateTypes || []).map(t => checker.getBaseTypeOfLiteralType(t))); - types.push(...findBuiltinTypes(usage)); + types.push(...inferNamedTypesFromProperties(usage)); if (usage.numberIndex) { - types.push(checker.createArrayType(recur(usage.numberIndex))); + types.push(checker.createArrayType(unifyFromUsage(usage.numberIndex))); } - else if (usage.properties && usage.properties.size + const structural = inferStructuralType(usage); + if (structural) { + types.push(structural); + } + return types; + } + + function inferStructuralType(usage: Usage) { + if (usage.properties && usage.properties.size || usage.calls && usage.calls.length || usage.constructs && usage.constructs.length || usage.stringIndex) { @@ -868,7 +927,7 @@ namespace ts.codefix { if (usage.properties) { usage.properties.forEach((u, name) => { const symbol = checker.createSymbol(SymbolFlags.Property, name); - symbol.type = recur(u); + symbol.type = unifyFromUsage(u); members.set(name, symbol); }); } @@ -882,75 +941,23 @@ namespace ts.codefix { } if (usage.stringIndex) { - stringIndexInfo = checker.createIndexInfo(recur(usage.stringIndex), /*isReadonly*/ false); + stringIndexInfo = checker.createIndexInfo(unifyFromUsage(usage.stringIndex), /*isReadonly*/ false); } - types.push(checker.createAnonymousType(/*symbol*/ undefined!, members, callSignatures, constructSignatures, stringIndexInfo, /*numberIndexInfo*/ undefined)); // TODO: GH#18217 - } - return types; // TODO: Should cache this since I HOPE it doesn't change - - function recur(innerUsage: Usage): Type { - return unifyTypes(inferFromUsage(innerUsage)); + return checker.createAnonymousType(/*symbol*/ undefined!, members, callSignatures, constructSignatures, stringIndexInfo, /*numberIndexInfo*/ undefined); // TODO: GH#18217 } } - function createEmptyUsage(): Usage { - return { - isNumber: undefined, - isString: undefined, - isNumberOrString: undefined, - candidateTypes: undefined, - properties: undefined, - calls: undefined, - constructs: undefined, - numberIndex: undefined, - stringIndex: undefined, - candidateThisTypes: undefined, - inferredTypes: undefined, - } - } - - function combineUsages(usages: Usage[]): Usage { - const combinedProperties = createUnderscoreEscapedMap() - for (const u of usages) { - if (u.properties) { - u.properties.forEach((p,name) => { - if (!combinedProperties.has(name)) { - combinedProperties.set(name, []); - } - combinedProperties.get(name)!.push(p); - }); - } - } - const properties = createUnderscoreEscapedMap() - combinedProperties.forEach((ps,name) => { - properties.set(name, combineUsages(ps)); - }); - return { - isNumber: usages.some(u => u.isNumber), - isString: usages.some(u => u.isString), - isNumberOrString: usages.some(u => u.isNumberOrString), - candidateTypes: flatMap(usages, u => u.candidateTypes) as Type[], - properties, - calls: flatMap(usages, u => u.calls) as CallUsage[], - constructs: flatMap(usages, u => u.constructs) as CallUsage[], - numberIndex: forEach(usages, u => u.numberIndex), - stringIndex: forEach(usages, u => u.stringIndex), - candidateThisTypes: flatMap(usages, u => u.candidateThisTypes) as Type[], - inferredTypes: undefined, // clear type cache - } - } - - function findBuiltinTypes(usage: Usage): Type[] { + function inferNamedTypesFromProperties(usage: Usage): Type[] { if (!usage.properties || !usage.properties.size) return []; - const matches = builtins.filter(t => matchesAllPropertiesOf(t, usage)); + const matches = builtins.filter(t => allPropertiesAreAssignableToUsage(t, usage)); if (0 < matches.length && matches.length < 3) { - return matches.map(m => inferTypeParameterFromUsage(m, usage)); + return matches.map(m => inferInstantiationFromUsage(m, usage)); } return []; } - function matchesAllPropertiesOf(type: Type, usage: Usage) { + function allPropertiesAreAssignableToUsage(type: Type, usage: Usage) { if (!usage.properties) return false; let result = true; usage.properties.forEach((propUsage, name) => { @@ -960,34 +967,34 @@ namespace ts.codefix { return; } if (propUsage.calls) { - const sigs = checker.getSignaturesOfType(source, ts.SignatureKind.Call); + const sigs = checker.getSignaturesOfType(source, SignatureKind.Call); result = result && !!sigs.length && checker.isTypeAssignableTo(source, getFunctionFromCalls(propUsage.calls)); } else { - result = result && checker.isTypeAssignableTo(source, unifyTypes(inferFromUsage(propUsage))); + result = result && checker.isTypeAssignableTo(source, unifyFromUsage(propUsage)); } }); return result; } - // inference is limited to - // 1. generic types with a single parameter - // 2. inference to/from calls with a single signature - function inferTypeParameterFromUsage(type: Type, usage: Usage) { - if (!usage.properties || !(getObjectFlags(type) & ObjectFlags.Reference)) return type; + /** + * inference is limited to + * 1. generic types with a single parameter + * 2. inference to/from calls with a single signature + */ + function inferInstantiationFromUsage(type: Type, usage: Usage) { + if (!(getObjectFlags(type) & ObjectFlags.Reference) || !usage.properties) { + return type; + } const generic = (type as TypeReference).target; const singleTypeParameter = singleOrUndefined(generic.typeParameters); if (!singleTypeParameter) return type; const types: Type[] = []; usage.properties.forEach((propUsage, name) => { - const source = checker.getTypeOfPropertyOfType(generic, name as string); - if (!source) { - return Debug.fail("generic should have all the properties of its reference."); - } - if (!propUsage.calls) return; - - types.push(...infer(source, getFunctionFromCalls(propUsage.calls), singleTypeParameter)); + const genericPropertyType = checker.getTypeOfPropertyOfType(generic, name as string); + Debug.assert(!!genericPropertyType, "generic should have all the properties of its reference."); + types.push(...infer(genericPropertyType!, unifyFromUsage(propUsage), singleTypeParameter)); }); return builtinConstructors[type.symbol.escapedName as string](unifyTypes(types)); } @@ -1033,7 +1040,7 @@ namespace ts.codefix { break; } let sourceType = checker.getTypeOfSymbolAtLocation(sourceParam, sourceParam.valueDeclaration); - let elementType = isRest && checker.getElementTypeOfArrayType(sourceType); + const elementType = isRest && checker.getElementTypeOfArrayType(sourceType); if (elementType) { sourceType = elementType; } @@ -1047,7 +1054,7 @@ namespace ts.codefix { } function getFunctionFromCalls(calls: CallUsage[]) { - return checker.createAnonymousType(undefined!, createSymbolTable(), [getSignatureFromCalls(calls)], emptyArray, undefined, undefined); + return checker.createAnonymousType(undefined!, createSymbolTable(), [getSignatureFromCalls(calls)], emptyArray, /*stringIndexInfo*/ undefined, /*numberIndexInfo*/ undefined); } function getSignatureFromCalls(calls: CallUsage[]): Signature { @@ -1061,7 +1068,7 @@ namespace ts.codefix { } parameters.push(symbol); } - const returnType = unifyTypes(inferFromUsage(combineUsages(calls.map(call => call.return_)))); + const returnType = unifyFromUsage(combineUsages(calls.map(call => call.return_))); // TODO: GH#18217 return checker.createSignature(/*declaration*/ undefined!, /*typeParameters*/ undefined, /*thisParameter*/ undefined, parameters, returnType, /*typePredicate*/ undefined, length, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); } From 1703ae0f46407beb4701e7234c9de558b20e17c0 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Fri, 6 Sep 2019 11:27:34 -0700 Subject: [PATCH 09/13] Renames and more cleanup --- src/services/codefixes/inferFromUsage.ts | 63 +++++++++++------------- 1 file changed, 30 insertions(+), 33 deletions(-) diff --git a/src/services/codefixes/inferFromUsage.ts b/src/services/codefixes/inferFromUsage.ts index 2e9fb0dfc7..0c711cfa4c 100644 --- a/src/services/codefixes/inferFromUsage.ts +++ b/src/services/codefixes/inferFromUsage.ts @@ -743,7 +743,6 @@ namespace ts.codefix { if (parent.arguments) { for (const argument of parent.arguments) { - // TODO: should recursively infer a usage here, right? call.argumentTypes.push(checker.getTypeAtLocation(argument)); } } @@ -999,57 +998,55 @@ namespace ts.codefix { return builtinConstructors[type.symbol.escapedName as string](unifyTypes(types)); } - // TODO: Source and target are bad names. Should be builtinType and usageType...or something - // and search is a bad name - function infer(source: Type, target: Type, search: Type): readonly Type[] { - if (source === search) { - return [target]; + function infer(genericType: Type, usageType: Type, typeParameter: Type): readonly Type[] { + if (genericType === typeParameter) { + return [usageType]; } - else if (source.flags & TypeFlags.UnionOrIntersection) { - return flatMap((source as UnionOrIntersectionType).types, t => infer(t, target, search)); + else if (genericType.flags & TypeFlags.UnionOrIntersection) { + return flatMap((genericType as UnionOrIntersectionType).types, t => infer(t, usageType, typeParameter)); } - else if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference) { + else if (getObjectFlags(genericType) & ObjectFlags.Reference && getObjectFlags(usageType) & ObjectFlags.Reference) { // this is wrong because we need a reference to the targetType to, so we can check that it's also a reference - const sourceArgs = (source as TypeReference).typeArguments; - const targetArgs = (target as TypeReference).typeArguments; + const genericArgs = (genericType as TypeReference).typeArguments; + const usageArgs = (usageType as TypeReference).typeArguments; const types = []; - if (sourceArgs && targetArgs) { - for (let i = 0; i < sourceArgs.length; i++) { - if (targetArgs[i]) { - types.push(...infer(sourceArgs[i], targetArgs[i], search)); + if (genericArgs && usageArgs) { + for (let i = 0; i < genericArgs.length; i++) { + if (usageArgs[i]) { + types.push(...infer(genericArgs[i], usageArgs[i], typeParameter)); } } } return types; } - const sourceSigs = checker.getSignaturesOfType(source, SignatureKind.Call); - const targetSigs = checker.getSignaturesOfType(target, SignatureKind.Call); - if (sourceSigs.length === 1 && targetSigs.length === 1) { - return inferFromSignatures(sourceSigs[0], targetSigs[0], search); + const genericSigs = checker.getSignaturesOfType(genericType, SignatureKind.Call); + const usageSigs = checker.getSignaturesOfType(usageType, SignatureKind.Call); + if (genericSigs.length === 1 && usageSigs.length === 1) { + return inferFromSignatures(genericSigs[0], usageSigs[0], typeParameter); } return []; } - function inferFromSignatures(sourceSig: Signature, targetSig: Signature, search: Type) { + function inferFromSignatures(genericSig: Signature, usageSig: Signature, typeParameter: Type) { const types = []; - for (let i = 0; i < sourceSig.parameters.length; i++) { - const sourceParam = sourceSig.parameters[i]; - const targetParam = targetSig.parameters[i]; - const isRest = sourceSig.declaration && isRestParameter(sourceSig.declaration.parameters[i]); - if (!targetParam) { + for (let i = 0; i < genericSig.parameters.length; i++) { + const genericParam = genericSig.parameters[i]; + const usageParam = usageSig.parameters[i]; + const isRest = genericSig.declaration && isRestParameter(genericSig.declaration.parameters[i]); + if (!usageParam) { break; } - let sourceType = checker.getTypeOfSymbolAtLocation(sourceParam, sourceParam.valueDeclaration); - const elementType = isRest && checker.getElementTypeOfArrayType(sourceType); + let genericParamType = checker.getTypeOfSymbolAtLocation(genericParam, genericParam.valueDeclaration); + const elementType = isRest && checker.getElementTypeOfArrayType(genericParamType); if (elementType) { - sourceType = elementType; + genericParamType = elementType; } - const targetType = (targetParam as SymbolLinks).type || checker.getTypeOfSymbolAtLocation(targetParam, targetParam.valueDeclaration); - types.push(...infer(sourceType, targetType, search)); + const targetType = (usageParam as SymbolLinks).type || checker.getTypeOfSymbolAtLocation(usageParam, usageParam.valueDeclaration); + types.push(...infer(genericParamType, targetType, typeParameter)); } - const sourceReturn = checker.getReturnTypeOfSignature(sourceSig); - const targetReturn = checker.getReturnTypeOfSignature(targetSig); - types.push(...infer(sourceReturn, targetReturn, search)); + const genericReturn = checker.getReturnTypeOfSignature(genericSig); + const usageReturn = checker.getReturnTypeOfSignature(usageSig); + types.push(...infer(genericReturn, usageReturn, typeParameter)); return types; } From 330e51f09811a41bddd71bfe2eea3baa4e6651bc Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Fri, 6 Sep 2019 15:15:19 -0700 Subject: [PATCH 10/13] Add test + reshuffle/rename new code --- src/services/codefixes/inferFromUsage.ts | 63 +++++++------------ .../codeFixInferFromUsageAddition.ts | 9 +++ .../fourslash/codeFixInferFromUsageArray.ts | 14 +++++ .../codeFixInferFromUsageLiteralTypes.ts | 10 +++ .../fourslash/codeFixInferFromUsagePromise.ts | 9 +++ .../fourslash/codeFixInferFromUsageString.ts | 12 ++++ 6 files changed, 77 insertions(+), 40 deletions(-) create mode 100644 tests/cases/fourslash/codeFixInferFromUsageAddition.ts create mode 100644 tests/cases/fourslash/codeFixInferFromUsageArray.ts create mode 100644 tests/cases/fourslash/codeFixInferFromUsageLiteralTypes.ts create mode 100644 tests/cases/fourslash/codeFixInferFromUsagePromise.ts create mode 100644 tests/cases/fourslash/codeFixInferFromUsageString.ts diff --git a/src/services/codefixes/inferFromUsage.ts b/src/services/codefixes/inferFromUsage.ts index 0c711cfa4c..520f8520d2 100644 --- a/src/services/codefixes/inferFromUsage.ts +++ b/src/services/codefixes/inferFromUsage.ts @@ -899,59 +899,42 @@ namespace ts.codefix { if (usage.isNumberOrString) { types.push(checker.getUnionType([checker.getStringType(), checker.getNumberType()])); } + if (usage.numberIndex) { + types.push(checker.createArrayType(unifyFromUsage(usage.numberIndex))); + } + if (usage.properties && usage.properties.size + || usage.calls && usage.calls.length + || usage.constructs && usage.constructs.length + || usage.stringIndex) { + types.push(inferStructuralType(usage)); + } types.push(...(usage.candidateTypes || []).map(t => checker.getBaseTypeOfLiteralType(t))); types.push(...inferNamedTypesFromProperties(usage)); - if (usage.numberIndex) { - types.push(checker.createArrayType(unifyFromUsage(usage.numberIndex))); - } - const structural = inferStructuralType(usage); - if (structural) { - types.push(structural); - } return types; } function inferStructuralType(usage: Usage) { - if (usage.properties && usage.properties.size - || usage.calls && usage.calls.length - || usage.constructs && usage.constructs.length - || usage.stringIndex) { - const members = createUnderscoreEscapedMap(); - const callSignatures: Signature[] = []; - const constructSignatures: Signature[] = []; - let stringIndexInfo: IndexInfo | undefined; - - if (usage.properties) { - usage.properties.forEach((u, name) => { - const symbol = checker.createSymbol(SymbolFlags.Property, name); - symbol.type = unifyFromUsage(u); - members.set(name, symbol); - }); - } - - if (usage.calls) { - callSignatures.push(getSignatureFromCalls(usage.calls)); - } - - if (usage.constructs) { - constructSignatures.push(getSignatureFromCalls(usage.constructs)); - } - - if (usage.stringIndex) { - stringIndexInfo = checker.createIndexInfo(unifyFromUsage(usage.stringIndex), /*isReadonly*/ false); - } - - return checker.createAnonymousType(/*symbol*/ undefined!, members, callSignatures, constructSignatures, stringIndexInfo, /*numberIndexInfo*/ undefined); // TODO: GH#18217 + const members = createUnderscoreEscapedMap(); + if (usage.properties) { + usage.properties.forEach((u, name) => { + const symbol = checker.createSymbol(SymbolFlags.Property, name); + symbol.type = unifyFromUsage(u); + members.set(name, symbol); + }); } + const callSignatures: Signature[] = usage.calls ? [getSignatureFromCalls(usage.calls)] : []; + const constructSignatures: Signature[] = usage.constructs ? [getSignatureFromCalls(usage.constructs)] : []; + const stringIndexInfo = usage.stringIndex && checker.createIndexInfo(unifyFromUsage(usage.stringIndex), /*isReadonly*/ false); + return checker.createAnonymousType(/*symbol*/ undefined!, members, callSignatures, constructSignatures, stringIndexInfo, /*numberIndexInfo*/ undefined); // TODO: GH#18217 } function inferNamedTypesFromProperties(usage: Usage): Type[] { if (!usage.properties || !usage.properties.size) return []; - const matches = builtins.filter(t => allPropertiesAreAssignableToUsage(t, usage)); - if (0 < matches.length && matches.length < 3) { - return matches.map(m => inferInstantiationFromUsage(m, usage)); + const types = builtins.filter(t => allPropertiesAreAssignableToUsage(t, usage)); + if (0 < types.length && types.length < 3) { + return types.map(t => inferInstantiationFromUsage(t, usage)); } return []; } diff --git a/tests/cases/fourslash/codeFixInferFromUsageAddition.ts b/tests/cases/fourslash/codeFixInferFromUsageAddition.ts new file mode 100644 index 0000000000..4b57d0603f --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromUsageAddition.ts @@ -0,0 +1,9 @@ +/// + +// @noImplicitAny: true +//// function foo([|a, m |]) { +//// return a + m +//// } + +verify.rangeAfterCodeFix("a: any, m: any", /*includeWhiteSpace*/ undefined, /*errorCode*/ undefined, /*index*/0); + diff --git a/tests/cases/fourslash/codeFixInferFromUsageArray.ts b/tests/cases/fourslash/codeFixInferFromUsageArray.ts new file mode 100644 index 0000000000..03d6b09e99 --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromUsageArray.ts @@ -0,0 +1,14 @@ +/// + +// @noImplicitAny: true +//// function foo([|p, a, b, c, d, e |]) { +//// var x: string = a.pop() +//// b.reverse() +//// var rr: boolean[] = c.reverse() +//// d.some(t => t > 1); // can't infer from callbacks right now +//// var y = e.concat(12); // can't infer from overloaded functions right now +//// return p.push(12) +//// } + +verify.rangeAfterCodeFix("p: number[], a: string[], b: any[], c: boolean[], d: any[], e: number[]", /*includeWhiteSpace*/ undefined, /*errorCode*/ undefined, /*index*/0); + diff --git a/tests/cases/fourslash/codeFixInferFromUsageLiteralTypes.ts b/tests/cases/fourslash/codeFixInferFromUsageLiteralTypes.ts new file mode 100644 index 0000000000..3b8c944115 --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromUsageLiteralTypes.ts @@ -0,0 +1,10 @@ +/// + +// @noImplicitAny: true +//// function foo([|a, m |]) { +//// a = 'hi' +//// m = 1 +//// } + +verify.rangeAfterCodeFix("a: string, m: number", /*includeWhiteSpace*/ undefined, /*errorCode*/ undefined, /*index*/0); + diff --git a/tests/cases/fourslash/codeFixInferFromUsagePromise.ts b/tests/cases/fourslash/codeFixInferFromUsagePromise.ts new file mode 100644 index 0000000000..d06bb3b6a0 --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromUsagePromise.ts @@ -0,0 +1,9 @@ +/// + +// @noImplicitAny: true +//// function foo([|p |]) { +//// return p.then((x: string[]) => x[0]) +//// } + +verify.rangeAfterCodeFix("p: Promise", /*includeWhiteSpace*/ undefined, /*errorCode*/ undefined, /*index*/0); + diff --git a/tests/cases/fourslash/codeFixInferFromUsageString.ts b/tests/cases/fourslash/codeFixInferFromUsageString.ts new file mode 100644 index 0000000000..dc5787dc75 --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromUsageString.ts @@ -0,0 +1,12 @@ +/// + +// @noImplicitAny: true +//// function foo([|p, a, b, c, d |]) { +//// var x +//// p.charAt(x) +//// a.charAt(0) +//// b.concat('hi') +//// } + +verify.rangeAfterCodeFix("p: string, a: string, b: string | string[]", /*includeWhiteSpace*/ undefined, /*errorCode*/ undefined, /*index*/0); + From 3c79225f48773cccdf247578cf2297111f0ccd2a Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Thu, 12 Sep 2019 11:18:47 -0700 Subject: [PATCH 11/13] Update baselines with any[] inferences --- tests/cases/fourslash/codeFixInferFromUsageArray.ts | 2 +- tests/cases/fourslash/codeFixInferFromUsageString.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/cases/fourslash/codeFixInferFromUsageArray.ts b/tests/cases/fourslash/codeFixInferFromUsageArray.ts index 03d6b09e99..75f923d5ae 100644 --- a/tests/cases/fourslash/codeFixInferFromUsageArray.ts +++ b/tests/cases/fourslash/codeFixInferFromUsageArray.ts @@ -10,5 +10,5 @@ //// return p.push(12) //// } -verify.rangeAfterCodeFix("p: number[], a: string[], b: any[], c: boolean[], d: any[], e: number[]", /*includeWhiteSpace*/ undefined, /*errorCode*/ undefined, /*index*/0); +verify.rangeAfterCodeFix("p: number[], a: string[], b: any[], c: boolean[], d: any[], e: any[]", /*includeWhiteSpace*/ undefined, /*errorCode*/ undefined, /*index*/0); diff --git a/tests/cases/fourslash/codeFixInferFromUsageString.ts b/tests/cases/fourslash/codeFixInferFromUsageString.ts index dc5787dc75..d58998cf4a 100644 --- a/tests/cases/fourslash/codeFixInferFromUsageString.ts +++ b/tests/cases/fourslash/codeFixInferFromUsageString.ts @@ -1,12 +1,12 @@ /// // @noImplicitAny: true -//// function foo([|p, a, b, c, d |]) { +//// function foo([|p, a, b |]) { //// var x //// p.charAt(x) //// a.charAt(0) //// b.concat('hi') //// } -verify.rangeAfterCodeFix("p: string, a: string, b: string | string[]", /*includeWhiteSpace*/ undefined, /*errorCode*/ undefined, /*index*/0); +verify.rangeAfterCodeFix("p: string, a: string, b: string | any[]", /*includeWhiteSpace*/ undefined, /*errorCode*/ undefined, /*index*/0); From de7d68a6d8704545fa4d04e3e0cf3072bda887c2 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Thu, 12 Sep 2019 11:30:51 -0700 Subject: [PATCH 12/13] Even more renaming --- src/services/codefixes/inferFromUsage.ts | 46 ++++++++++++------------ 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/services/codefixes/inferFromUsage.ts b/src/services/codefixes/inferFromUsage.ts index 00b95c57d1..090c241b05 100644 --- a/src/services/codefixes/inferFromUsage.ts +++ b/src/services/codefixes/inferFromUsage.ts @@ -481,7 +481,7 @@ namespace ts.codefix { } function single(): Type { - return unifyTypes(inferTypesFromReferencesSingle(references)); + return combineTypes(inferTypesFromReferencesSingle(references)); } function parameters(declaration: FunctionLike): ParameterInference[] | undefined { @@ -517,7 +517,7 @@ namespace ts.codefix { const inferred = inferTypesFromReferencesSingle(getReferences(parameter.name, program, cancellationToken)); types.push(...(isRest ? mapDefined(inferred, checker.getElementTypeOfArrayType) : inferred)); } - const type = unifyTypes(types); + const type = combineTypes(types); return { type: isRest ? checker.createArrayType(type) : type, isOptional: isOptional && !isRest, @@ -533,7 +533,7 @@ namespace ts.codefix { calculateUsageOfNode(reference, usage); } - return unifyTypes(usage.candidateThisTypes || emptyArray); + return combineTypes(usage.candidateThisTypes || emptyArray); } function inferTypesFromReferencesSingle(references: readonly Identifier[]): Type[] { @@ -542,7 +542,7 @@ namespace ts.codefix { cancellationToken.throwIfCancellationRequested(); calculateUsageOfNode(reference, usage); } - return inferFromUsage(usage); + return inferTypes(usage); } function calculateUsageOfNode(node: Expression, usage: Usage): void { @@ -819,11 +819,11 @@ namespace ts.codefix { return inferences.filter(i => toRemove.every(f => !f(i))); } - function unifyFromUsage(usage: Usage) { - return unifyTypes(inferFromUsage(usage)); + function combineFromUsage(usage: Usage) { + return combineTypes(inferTypes(usage)); } - function unifyTypes(inferences: readonly Type[]): Type { + function combineTypes(inferences: readonly Type[]): Type { if (!inferences.length) return checker.getAnyType(); // 1. string or number individually override string | number @@ -847,12 +847,12 @@ namespace ts.codefix { const anons = good.filter(i => checker.getObjectFlags(i) & ObjectFlags.Anonymous) as AnonymousType[]; if (anons.length) { good = good.filter(i => !(checker.getObjectFlags(i) & ObjectFlags.Anonymous)); - good.push(unifyAnonymousTypes(anons)); + good.push(combineAnonymousTypes(anons)); } return checker.getWidenedType(checker.getUnionType(good.map(checker.getBaseTypeOfLiteralType), UnionReduction.Subtype)); } - function unifyAnonymousTypes(anons: AnonymousType[]) { + function combineAnonymousTypes(anons: AnonymousType[]) { if (anons.length === 1) { return anons[0]; } @@ -893,7 +893,7 @@ namespace ts.codefix { numberIndices.length ? checker.createIndexInfo(checker.getUnionType(numberIndices), numberIndexReadonly) : undefined); } - function inferFromUsage(usage: Usage): Type[] { + function inferTypes(usage: Usage): Type[] { const types = []; if (usage.isNumber) { @@ -906,7 +906,7 @@ namespace ts.codefix { types.push(checker.getUnionType([checker.getStringType(), checker.getNumberType()])); } if (usage.numberIndex) { - types.push(checker.createArrayType(unifyFromUsage(usage.numberIndex))); + types.push(checker.createArrayType(combineFromUsage(usage.numberIndex))); } if (usage.properties && usage.properties.size || usage.calls && usage.calls.length @@ -926,13 +926,13 @@ namespace ts.codefix { if (usage.properties) { usage.properties.forEach((u, name) => { const symbol = checker.createSymbol(SymbolFlags.Property, name); - symbol.type = unifyFromUsage(u); + symbol.type = combineFromUsage(u); members.set(name, symbol); }); } const callSignatures: Signature[] = usage.calls ? [getSignatureFromCalls(usage.calls)] : []; const constructSignatures: Signature[] = usage.constructs ? [getSignatureFromCalls(usage.constructs)] : []; - const stringIndexInfo = usage.stringIndex && checker.createIndexInfo(unifyFromUsage(usage.stringIndex), /*isReadonly*/ false); + const stringIndexInfo = usage.stringIndex && checker.createIndexInfo(combineFromUsage(usage.stringIndex), /*isReadonly*/ false); return checker.createAnonymousType(/*symbol*/ undefined!, members, callSignatures, constructSignatures, stringIndexInfo, /*numberIndexInfo*/ undefined); // TODO: GH#18217 } @@ -959,7 +959,7 @@ namespace ts.codefix { result = result && !!sigs.length && checker.isTypeAssignableTo(source, getFunctionFromCalls(propUsage.calls)); } else { - result = result && checker.isTypeAssignableTo(source, unifyFromUsage(propUsage)); + result = result && checker.isTypeAssignableTo(source, combineFromUsage(propUsage)); } }); return result; @@ -982,17 +982,17 @@ namespace ts.codefix { usage.properties.forEach((propUsage, name) => { const genericPropertyType = checker.getTypeOfPropertyOfType(generic, name as string); Debug.assert(!!genericPropertyType, "generic should have all the properties of its reference."); - types.push(...infer(genericPropertyType!, unifyFromUsage(propUsage), singleTypeParameter)); + types.push(...inferTypeParameters(genericPropertyType!, combineFromUsage(propUsage), singleTypeParameter)); }); - return builtinConstructors[type.symbol.escapedName as string](unifyTypes(types)); + return builtinConstructors[type.symbol.escapedName as string](combineTypes(types)); } - function infer(genericType: Type, usageType: Type, typeParameter: Type): readonly Type[] { + function inferTypeParameters(genericType: Type, usageType: Type, typeParameter: Type): readonly Type[] { if (genericType === typeParameter) { return [usageType]; } else if (genericType.flags & TypeFlags.UnionOrIntersection) { - return flatMap((genericType as UnionOrIntersectionType).types, t => infer(t, usageType, typeParameter)); + return flatMap((genericType as UnionOrIntersectionType).types, t => inferTypeParameters(t, usageType, typeParameter)); } else if (getObjectFlags(genericType) & ObjectFlags.Reference && getObjectFlags(usageType) & ObjectFlags.Reference) { // this is wrong because we need a reference to the targetType to, so we can check that it's also a reference @@ -1002,7 +1002,7 @@ namespace ts.codefix { if (genericArgs && usageArgs) { for (let i = 0; i < genericArgs.length; i++) { if (usageArgs[i]) { - types.push(...infer(genericArgs[i], usageArgs[i], typeParameter)); + types.push(...inferTypeParameters(genericArgs[i], usageArgs[i], typeParameter)); } } } @@ -1031,11 +1031,11 @@ namespace ts.codefix { genericParamType = elementType; } const targetType = (usageParam as SymbolLinks).type || checker.getTypeOfSymbolAtLocation(usageParam, usageParam.valueDeclaration); - types.push(...infer(genericParamType, targetType, typeParameter)); + types.push(...inferTypeParameters(genericParamType, targetType, typeParameter)); } const genericReturn = checker.getReturnTypeOfSignature(genericSig); const usageReturn = checker.getReturnTypeOfSignature(usageSig); - types.push(...infer(genericReturn, usageReturn, typeParameter)); + types.push(...inferTypeParameters(genericReturn, usageReturn, typeParameter)); return types; } @@ -1048,13 +1048,13 @@ namespace ts.codefix { const length = Math.max(...calls.map(c => c.argumentTypes.length)); for (let i = 0; i < length; i++) { const symbol = checker.createSymbol(SymbolFlags.FunctionScopedVariable, escapeLeadingUnderscores(`arg${i}`)); - symbol.type = unifyTypes(calls.map(call => call.argumentTypes[i] || checker.getUndefinedType())); + symbol.type = combineTypes(calls.map(call => call.argumentTypes[i] || checker.getUndefinedType())); if (calls.some(call => call.argumentTypes[i] === undefined)) { symbol.flags |= SymbolFlags.Optional; } parameters.push(symbol); } - const returnType = unifyFromUsage(combineUsages(calls.map(call => call.return_))); + const returnType = combineFromUsage(combineUsages(calls.map(call => call.return_))); // TODO: GH#18217 return checker.createSignature(/*declaration*/ undefined!, /*typeParameters*/ undefined, /*thisParameter*/ undefined, parameters, returnType, /*typePredicate*/ undefined, length, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); } From 84e857b6f3624fcb01db807affa49ca8d9580f3b Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Mon, 23 Sep 2019 15:57:32 -0700 Subject: [PATCH 13/13] use forEachEntry --- src/services/codefixes/inferFromUsage.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/services/codefixes/inferFromUsage.ts b/src/services/codefixes/inferFromUsage.ts index 090c241b05..5dd3478288 100644 --- a/src/services/codefixes/inferFromUsage.ts +++ b/src/services/codefixes/inferFromUsage.ts @@ -947,22 +947,19 @@ namespace ts.codefix { function allPropertiesAreAssignableToUsage(type: Type, usage: Usage) { if (!usage.properties) return false; - let result = true; - usage.properties.forEach((propUsage, name) => { + return !forEachEntry(usage.properties, (propUsage, name) => { const source = checker.getTypeOfPropertyOfType(type, name as string); if (!source) { - result = false; - return; + return true; } if (propUsage.calls) { const sigs = checker.getSignaturesOfType(source, SignatureKind.Call); - result = result && !!sigs.length && checker.isTypeAssignableTo(source, getFunctionFromCalls(propUsage.calls)); + return !sigs.length || !checker.isTypeAssignableTo(source, getFunctionFromCalls(propUsage.calls)); } else { - result = result && checker.isTypeAssignableTo(source, combineFromUsage(propUsage)); + return !checker.isTypeAssignableTo(source, combineFromUsage(propUsage)); } }); - return result; } /**