Skip to content

Commit 1835754

Browse files
author
Andy
authored
Provide better services for incomplete generic calls (microsoft#16535)
* Provide better services for incomplete generic calls * Use clearer name * Remove `inferredAnyDefaultTypeArgument` and `getBestGuessSignature`; have `resolveSignature` always get the best signature if !produceDiagnostics * Update names and comments
1 parent aeb5264 commit 1835754

17 files changed

+180
-148
lines changed

src/compiler/checker.ts

Lines changed: 71 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ namespace ts {
7575
undefinedSymbol.declarations = [];
7676
const argumentsSymbol = createSymbol(SymbolFlags.Property, "arguments");
7777

78+
/** This will be set during calls to `getResolvedSignature` where services determines an apparent number of arguments greater than what is actually provided. */
79+
let apparentArgumentCount: number | undefined;
80+
7881
// for public members that accept a Node or one of its subtypes, we must guard against
7982
// synthetic nodes created during transformations by calling `getParseTreeNode`.
8083
// for most of these, we perform the guard only on `checker` to avoid any possible
@@ -160,9 +163,12 @@ namespace ts {
160163
return node ? getContextualType(node) : undefined;
161164
},
162165
getFullyQualifiedName,
163-
getResolvedSignature: (node, candidatesOutArray?) => {
166+
getResolvedSignature: (node, candidatesOutArray, theArgumentCount) => {
164167
node = getParseTreeNode(node, isCallLikeExpression);
165-
return node ? getResolvedSignature(node, candidatesOutArray) : undefined;
168+
apparentArgumentCount = theArgumentCount;
169+
const res = node ? getResolvedSignature(node, candidatesOutArray) : undefined;
170+
apparentArgumentCount = undefined;
171+
return res;
166172
},
167173
getConstantValue: node => {
168174
node = getParseTreeNode(node, canHaveConstantValue);
@@ -6313,12 +6319,12 @@ namespace ts {
63136319
// If a type parameter does not have a default type, or if the default type
63146320
// is a forward reference, the empty object type is used.
63156321
for (let i = numTypeArguments; i < numTypeParameters; i++) {
6316-
typeArguments[i] = isJavaScript ? anyType : emptyObjectType;
6322+
typeArguments[i] = getDefaultTypeArgumentType(isJavaScript);
63176323
}
63186324
for (let i = numTypeArguments; i < numTypeParameters; i++) {
63196325
const mapper = createTypeMapper(typeParameters, typeArguments);
63206326
const defaultType = getDefaultFromTypeParameter(typeParameters[i]);
6321-
typeArguments[i] = defaultType ? instantiateType(defaultType, mapper) : isJavaScript ? anyType : emptyObjectType;
6327+
typeArguments[i] = defaultType ? instantiateType(defaultType, mapper) : getDefaultTypeArgumentType(isJavaScript);
63226328
}
63236329
}
63246330
}
@@ -7968,15 +7974,16 @@ namespace ts {
79687974
};
79697975
}
79707976

7971-
function createTypeMapper(sources: Type[], targets: Type[]): TypeMapper {
7977+
function createTypeMapper(sources: TypeParameter[], targets: Type[]): TypeMapper {
7978+
Debug.assert(targets === undefined || sources.length === targets.length);
79727979
const mapper: TypeMapper = sources.length === 1 ? makeUnaryTypeMapper(sources[0], targets ? targets[0] : anyType) :
79737980
sources.length === 2 ? makeBinaryTypeMapper(sources[0], targets ? targets[0] : anyType, sources[1], targets ? targets[1] : anyType) :
79747981
makeArrayTypeMapper(sources, targets);
79757982
mapper.mappedTypes = sources;
79767983
return mapper;
79777984
}
79787985

7979-
function createTypeEraser(sources: Type[]): TypeMapper {
7986+
function createTypeEraser(sources: TypeParameter[]): TypeMapper {
79807987
return createTypeMapper(sources, /*targets*/ undefined);
79817988
}
79827989

@@ -10628,7 +10635,7 @@ namespace ts {
1062810635
context));
1062910636
}
1063010637
else {
10631-
inferredType = context.flags & InferenceFlags.AnyDefault ? anyType : emptyObjectType;
10638+
inferredType = getDefaultTypeArgumentType(!!(context.flags & InferenceFlags.AnyDefault));
1063210639
}
1063310640
}
1063410641
inference.inferredType = inferredType;
@@ -10644,6 +10651,10 @@ namespace ts {
1064410651
return inferredType;
1064510652
}
1064610653

10654+
function getDefaultTypeArgumentType(isInJavaScriptFile: boolean): Type {
10655+
return isInJavaScriptFile ? anyType : emptyObjectType;
10656+
}
10657+
1064710658
function getInferredTypes(context: InferenceContext): Type[] {
1064810659
const result: Type[] = [];
1064910660
for (let i = 0; i < context.inferences.length; i++) {
@@ -12787,7 +12798,9 @@ namespace ts {
1278712798
const args = getEffectiveCallArguments(callTarget);
1278812799
const argIndex = indexOf(args, arg);
1278912800
if (argIndex >= 0) {
12790-
const signature = getResolvedOrAnySignature(callTarget);
12801+
// If we're already in the process of resolving the given signature, don't resolve again as
12802+
// that could cause infinite recursion. Instead, return anySignature.
12803+
const signature = getNodeLinks(callTarget).resolvedSignature === resolvingSignature ? resolvingSignature : getResolvedSignature(callTarget);
1279112804
return getTypeAtPosition(signature, argIndex);
1279212805
}
1279312806
return undefined;
@@ -14962,16 +14975,14 @@ namespace ts {
1496214975
}
1496314976

1496414977
function inferTypeArguments(node: CallLikeExpression, signature: Signature, args: Expression[], excludeArgument: boolean[], context: InferenceContext): Type[] {
14965-
const inferences = context.inferences;
14966-
1496714978
// Clear out all the inference results from the last time inferTypeArguments was called on this context
14968-
for (let i = 0; i < inferences.length; i++) {
14979+
for (const inference of context.inferences) {
1496914980
// As an optimization, we don't have to clear (and later recompute) inferred types
1497014981
// for type parameters that have already been fixed on the previous call to inferTypeArguments.
1497114982
// It would be just as correct to reset all of them. But then we'd be repeating the same work
1497214983
// for the type parameters that were fixed, namely the work done by getInferredType.
14973-
if (!inferences[i].isFixed) {
14974-
inferences[i].inferredType = undefined;
14984+
if (!inference.isFixed) {
14985+
inference.inferredType = undefined;
1497514986
}
1497614987
}
1497714988

@@ -15640,27 +15651,43 @@ namespace ts {
1564015651
}
1564115652

1564215653
// No signature was applicable. We have already reported the errors for the invalid signature.
15643-
// If this is a type resolution session, e.g. Language Service, try to get better information that anySignature.
15644-
// Pick the first candidate that matches the arity. This way we can get a contextual type for cases like:
15645-
// declare function f(a: { xa: number; xb: number; });
15646-
// f({ |
15654+
// If this is a type resolution session, e.g. Language Service, try to get better information than anySignature.
15655+
// Pick the longest signature. This way we can get a contextual type for cases like:
15656+
// declare function f(a: { xa: number; xb: number; }, b: number);
15657+
// f({ |
15658+
// Also, use explicitly-supplied type arguments if they are provided, so we can get a contextual signature in cases like:
15659+
// declare function f<T>(k: keyof T);
15660+
// f<Foo>("
1564715661
if (!produceDiagnostics) {
15648-
for (let candidate of candidates) {
15649-
if (hasCorrectArity(node, args, candidate)) {
15650-
if (candidate.typeParameters && typeArguments) {
15651-
candidate = getSignatureInstantiation(candidate, map(typeArguments, getTypeFromTypeNode));
15652-
}
15653-
return candidate;
15662+
Debug.assert(candidates.length > 0); // Else would have exited above.
15663+
const bestIndex = getLongestCandidateIndex(candidates, apparentArgumentCount === undefined ? args.length : apparentArgumentCount);
15664+
const candidate = candidates[bestIndex];
15665+
15666+
const { typeParameters } = candidate;
15667+
if (typeParameters && callLikeExpressionMayHaveTypeArguments(node) && node.typeArguments) {
15668+
const typeArguments = node.typeArguments.map(getTypeOfNode);
15669+
while (typeArguments.length > typeParameters.length) {
15670+
typeArguments.pop();
15671+
}
15672+
while (typeArguments.length < typeParameters.length) {
15673+
typeArguments.push(getDefaultTypeArgumentType(isInJavaScriptFile(node)));
1565415674
}
15675+
15676+
const instantiated = createSignatureInstantiation(candidate, typeArguments);
15677+
candidates[bestIndex] = instantiated;
15678+
return instantiated;
1565515679
}
15680+
15681+
return candidate;
1565615682
}
1565715683

1565815684
return resolveErrorCall(node);
1565915685

1566015686
function chooseOverload(candidates: Signature[], relation: Map<RelationComparisonResult>, signatureHelpTrailingComma = false) {
1566115687
candidateForArgumentError = undefined;
1566215688
candidateForTypeArgumentError = undefined;
15663-
for (const originalCandidate of candidates) {
15689+
for (let candidateIndex = 0; candidateIndex < candidates.length; candidateIndex++) {
15690+
const originalCandidate = candidates[candidateIndex];
1566415691
if (!hasCorrectArity(node, args, originalCandidate, signatureHelpTrailingComma)) {
1566515692
continue;
1566615693
}
@@ -15692,6 +15719,7 @@ namespace ts {
1569215719
}
1569315720
const index = excludeArgument ? indexOf(excludeArgument, /*value*/ true) : -1;
1569415721
if (index < 0) {
15722+
candidates[candidateIndex] = candidate;
1569515723
return candidate;
1569615724
}
1569715725
excludeArgument[index] = false;
@@ -15703,6 +15731,24 @@ namespace ts {
1570315731

1570415732
}
1570515733

15734+
function getLongestCandidateIndex(candidates: Signature[], argsCount: number): number {
15735+
let maxParamsIndex = -1;
15736+
let maxParams = -1;
15737+
15738+
for (let i = 0; i < candidates.length; i++) {
15739+
const candidate = candidates[i];
15740+
if (candidate.hasRestParameter || candidate.parameters.length >= argsCount) {
15741+
return i;
15742+
}
15743+
if (candidate.parameters.length > maxParams) {
15744+
maxParams = candidate.parameters.length;
15745+
maxParamsIndex = i;
15746+
}
15747+
}
15748+
15749+
return maxParamsIndex;
15750+
}
15751+
1570615752
function resolveCallExpression(node: CallExpression, candidatesOutArray: Signature[]): Signature {
1570715753
if (node.expression.kind === SyntaxKind.SuperKeyword) {
1570815754
const superType = checkSuperExpression(node.expression);
@@ -16017,9 +16063,7 @@ namespace ts {
1601716063

1601816064
const callSignatures = elementType && getSignaturesOfType(elementType, SignatureKind.Call);
1601916065
if (callSignatures && callSignatures.length > 0) {
16020-
let callSignature: Signature;
16021-
callSignature = resolveCall(openingLikeElement, callSignatures, candidatesOutArray);
16022-
return callSignature;
16066+
return resolveCall(openingLikeElement, callSignatures, candidatesOutArray);
1602316067
}
1602416068

1602516069
return undefined;
@@ -16069,12 +16113,6 @@ namespace ts {
1606916113
return result;
1607016114
}
1607116115

16072-
function getResolvedOrAnySignature(node: CallLikeExpression) {
16073-
// If we're already in the process of resolving the given signature, don't resolve again as
16074-
// that could cause infinite recursion. Instead, return anySignature.
16075-
return getNodeLinks(node).resolvedSignature === resolvingSignature ? resolvingSignature : getResolvedSignature(node);
16076-
}
16077-
1607816116
/**
1607916117
* Indicates whether a declaration can be treated as a constructor in a JavaScript
1608016118
* file.

src/compiler/types.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2587,7 +2587,11 @@ namespace ts {
25872587
getAugmentedPropertiesOfType(type: Type): Symbol[];
25882588
getRootSymbols(symbol: Symbol): Symbol[];
25892589
getContextualType(node: Expression): Type | undefined;
2590-
getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: Signature[]): Signature | undefined;
2590+
/**
2591+
* returns unknownSignature in the case of an error. Don't know when it returns undefined.
2592+
* @param argumentCount Apparent number of arguments, passed in case of a possibly incomplete call. This should come from an ArgumentListInfo. See `signatureHelp.ts`.
2593+
*/
2594+
getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: Signature[], argumentCount?: number): Signature | undefined;
25912595
getSignatureFromDeclaration(declaration: SignatureDeclaration): Signature | undefined;
25922596
isImplementationOfOverload(node: FunctionLikeDeclaration): boolean | undefined;
25932597
isUndefinedSymbol(symbol: Symbol): boolean;
@@ -3382,8 +3386,8 @@ namespace ts {
33823386
/* @internal */
33833387
export interface TypeMapper {
33843388
(t: TypeParameter): Type;
3385-
mappedTypes?: Type[]; // Types mapped by this mapper
3386-
instantiations?: Type[]; // Cache of instantiations created using this type mapper.
3389+
mappedTypes?: TypeParameter[]; // Types mapped by this mapper
3390+
instantiations?: Type[]; // Cache of instantiations created using this type mapper.
33873391
}
33883392

33893393
export const enum InferencePriority {

src/compiler/utilities.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4688,6 +4688,11 @@ namespace ts {
46884688
// All node tests in the following list should *not* reference parent pointers so that
46894689
// they may be used with transformations.
46904690
namespace ts {
4691+
/* @internal */
4692+
export function isSyntaxList(n: Node): n is SyntaxList {
4693+
return n.kind === SyntaxKind.SyntaxList;
4694+
}
4695+
46914696
/* @internal */
46924697
export function isNode(node: Node) {
46934698
return isNodeKind(node.kind);

src/services/completions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ namespace ts.Completions {
234234
const entries: CompletionEntry[] = [];
235235
const uniques = createMap<true>();
236236

237-
typeChecker.getResolvedSignature(argumentInfo.invocation, candidates);
237+
typeChecker.getResolvedSignature(argumentInfo.invocation, candidates, argumentInfo.argumentCount);
238238

239239
for (const candidate of candidates) {
240240
addStringLiteralCompletionsFromType(typeChecker.getParameterType(candidate, argumentInfo.argumentIndex), entries, typeChecker, uniques);

0 commit comments

Comments
 (0)