Skip to content

Commit 40f6ad1

Browse files
author
Andy Hanson
committed
In services, when overload resolution fails, create a union signature (2)
1 parent b59824a commit 40f6ad1

14 files changed

+176
-49
lines changed

src/compiler/checker.ts

Lines changed: 104 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7602,9 +7602,13 @@ namespace ts {
76027602
return !signature.resolvedReturnType && findResolutionCycleStartIndex(signature, TypeSystemPropertyName.ResolvedReturnType) >= 0;
76037603
}
76047604

7605-
function getRestTypeOfSignature(signature: Signature) {
7605+
function getRestTypeOfSignature(signature: Signature): Type {
7606+
return tryGetRestTypeOfSignature(signature) || anyType;
7607+
}
7608+
7609+
function tryGetRestTypeOfSignature(signature: Signature): Type | undefined {
76067610
const type = getTypeOfRestParameter(signature);
7607-
return type && getIndexTypeOfType(type, IndexKind.Number) || anyType;
7611+
return type && getIndexTypeOfType(type, IndexKind.Number);
76087612
}
76097613

76107614
function getSignatureInstantiation(signature: Signature, typeArguments: Type[] | undefined, isJavascript: boolean): Signature {
@@ -18769,38 +18773,7 @@ namespace ts {
1876918773
diagnostics.add(createDiagnosticForNode(node, fallbackError));
1877018774
}
1877118775

18772-
// No signature was applicable. We have already reported the errors for the invalid signature.
18773-
// If this is a type resolution session, e.g. Language Service, try to get better information than anySignature.
18774-
// Pick the longest signature. This way we can get a contextual type for cases like:
18775-
// declare function f(a: { xa: number; xb: number; }, b: number);
18776-
// f({ |
18777-
// Also, use explicitly-supplied type arguments if they are provided, so we can get a contextual signature in cases like:
18778-
// declare function f<T>(k: keyof T);
18779-
// f<Foo>("
18780-
if (!produceDiagnostics) {
18781-
Debug.assert(candidates.length > 0); // Else would have exited above.
18782-
const bestIndex = getLongestCandidateIndex(candidates, apparentArgumentCount === undefined ? args!.length : apparentArgumentCount);
18783-
const candidate = candidates[bestIndex];
18784-
18785-
const { typeParameters } = candidate;
18786-
if (typeParameters && callLikeExpressionMayHaveTypeArguments(node) && node.typeArguments) {
18787-
const typeArguments = node.typeArguments.map(getTypeOfNode) as Type[]; // TODO: GH#18217
18788-
while (typeArguments.length > typeParameters.length) {
18789-
typeArguments.pop();
18790-
}
18791-
while (typeArguments.length < typeParameters.length) {
18792-
typeArguments.push(getDefaultTypeArgumentType(isInJavaScriptFile(node)));
18793-
}
18794-
18795-
const instantiated = createSignatureInstantiation(candidate, typeArguments);
18796-
candidates[bestIndex] = instantiated;
18797-
return instantiated;
18798-
}
18799-
18800-
return candidate;
18801-
}
18802-
18803-
return resolveErrorCall(node);
18776+
return produceDiagnostics || !args ? resolveErrorCall(node) : getCandidateForOverloadFailure(node, candidates, args, !!candidatesOutArray);
1880418777

1880518778
function chooseOverload(candidates: Signature[], relation: Map<RelationComparisonResult>, signatureHelpTrailingComma = false) {
1880618779
candidateForArgumentError = undefined;
@@ -18871,6 +18844,97 @@ namespace ts {
1887118844
}
1887218845
}
1887318846

18847+
// No signature was applicable. We have already reported the errors for the invalid signature.
18848+
// If this is a type resolution session, e.g. Language Service, try to get better information than anySignature.
18849+
function getCandidateForOverloadFailure(
18850+
node: CallLikeExpression,
18851+
candidates: Signature[],
18852+
args: ReadonlyArray<Expression>,
18853+
hasCandidatesOutArray: boolean,
18854+
): Signature {
18855+
Debug.assert(candidates.length > 0); // Else should not have called this.
18856+
// Normally we will combine overloads. Skip this if they have type parameters since that's hard to combine.
18857+
// Don't do this if there is a `candidatesOutArray`,
18858+
// because then we want the chosen best candidate to be one of the overloads, not a combination.
18859+
return hasCandidatesOutArray || candidates.length === 1 || candidates.some(c => !!c.typeParameters)
18860+
? pickLongestCandidateSignature(node, candidates, args)
18861+
: createUnionOfSignaturesForOverloadFailure(candidates);
18862+
}
18863+
18864+
function createUnionOfSignaturesForOverloadFailure(candidates: ReadonlyArray<Signature>): Signature {
18865+
const thisParameters = mapDefined(candidates, c => c.thisParameter);
18866+
let thisParameter: Symbol | undefined;
18867+
if (thisParameters.length) {
18868+
thisParameter = createCombinedSymbolFromTypes(thisParameters, thisParameters.map(getTypeOfParameter));
18869+
}
18870+
const { min: minArgumentCount, max: maxNonRestParam } = minAndMax(candidates, getNumNonRestParameters);
18871+
const parameters: Symbol[] = [];
18872+
for (let i = 0; i < maxNonRestParam; i++) {
18873+
const symbols = mapDefined(candidates, ({ parameters, hasRestParameter }) => hasRestParameter ?
18874+
i < parameters.length - 1 ? parameters[i] : last(parameters) :
18875+
i < parameters.length ? parameters[i] : undefined);
18876+
Debug.assert(symbols.length !== 0);
18877+
parameters.push(createCombinedSymbolFromTypes(symbols, mapDefined(candidates, candidate => tryGetTypeAtPosition(candidate, i))));
18878+
}
18879+
const restParameterSymbols = mapDefined(candidates, c => c.hasRestParameter ? last(c.parameters) : undefined);
18880+
const hasRestParameter = restParameterSymbols.length !== 0;
18881+
if (hasRestParameter) {
18882+
const type = createArrayType(getUnionType(mapDefined(candidates, tryGetRestTypeOfSignature), UnionReduction.Subtype));
18883+
parameters.push(createCombinedSymbolForOverloadFailure(restParameterSymbols, type));
18884+
}
18885+
return createSignature(
18886+
candidates[0].declaration,
18887+
/*typeParameters*/ undefined, // Before calling this we tested for `!candidates.some(c => !!c.typeParameters)`.
18888+
thisParameter,
18889+
parameters,
18890+
/*resolvedReturnType*/ getIntersectionType(candidates.map(getReturnTypeOfSignature)),
18891+
/*typePredicate*/ undefined,
18892+
minArgumentCount,
18893+
hasRestParameter,
18894+
/*hasLiteralTypes*/ candidates.some(c => c.hasLiteralTypes));
18895+
}
18896+
18897+
function getNumNonRestParameters(signature: Signature): number {
18898+
const numParams = signature.parameters.length;
18899+
return signature.hasRestParameter ? numParams - 1 : numParams;
18900+
}
18901+
18902+
function createCombinedSymbolFromTypes(sources: ReadonlyArray<Symbol>, types: Type[]): Symbol {
18903+
return createCombinedSymbolForOverloadFailure(sources, getUnionType(types, UnionReduction.Subtype));
18904+
}
18905+
18906+
function createCombinedSymbolForOverloadFailure(sources: ReadonlyArray<Symbol>, type: Type): Symbol {
18907+
// This function is currently only used for erroneous overloads, so it's good enough to just use the first source.
18908+
return createSymbolWithType(first(sources), type);
18909+
}
18910+
18911+
function pickLongestCandidateSignature(node: CallLikeExpression, candidates: Signature[], args: ReadonlyArray<Expression>): Signature {
18912+
// Pick the longest signature. This way we can get a contextual type for cases like:
18913+
// declare function f(a: { xa: number; xb: number; }, b: number);
18914+
// f({ |
18915+
// Also, use explicitly-supplied type arguments if they are provided, so we can get a contextual signature in cases like:
18916+
// declare function f<T>(k: keyof T);
18917+
// f<Foo>("
18918+
const bestIndex = getLongestCandidateIndex(candidates, apparentArgumentCount === undefined ? args.length : apparentArgumentCount);
18919+
const candidate = candidates[bestIndex];
18920+
const { typeParameters } = candidate;
18921+
if (!typeParameters) {
18922+
return candidate;
18923+
}
18924+
18925+
const typeArgumentNodes: ReadonlyArray<TypeNode> = callLikeExpressionMayHaveTypeArguments(node) ? node.typeArguments || emptyArray : emptyArray;
18926+
const typeArguments = typeArgumentNodes.map(n => getTypeOfNode(n) || anyType);
18927+
while (typeArguments.length > typeParameters.length) {
18928+
typeArguments.pop();
18929+
}
18930+
while (typeArguments.length < typeParameters.length) {
18931+
typeArguments.push(getConstraintFromTypeParameter(typeParameters[typeArguments.length]) || getDefaultTypeArgumentType(isInJavaScriptFile(node)));
18932+
}
18933+
const instantiated = createSignatureInstantiation(candidate, typeArguments);
18934+
candidates[bestIndex] = instantiated;
18935+
return instantiated;
18936+
}
18937+
1887418938
function getLongestCandidateIndex(candidates: Signature[], argsCount: number): number {
1887518939
let maxParamsIndex = -1;
1887618940
let maxParams = -1;
@@ -19626,6 +19690,10 @@ namespace ts {
1962619690
}
1962719691

1962819692
function getTypeAtPosition(signature: Signature, pos: number): Type {
19693+
return tryGetTypeAtPosition(signature, pos) || anyType;
19694+
}
19695+
19696+
function tryGetTypeAtPosition(signature: Signature, pos: number): Type | undefined {
1962919697
const paramCount = signature.parameters.length - (signature.hasRestParameter ? 1 : 0);
1963019698
if (pos < paramCount) {
1963119699
return getTypeOfParameter(signature.parameters[pos]);
@@ -19641,9 +19709,9 @@ namespace ts {
1964119709
return tupleRestType;
1964219710
}
1964319711
}
19644-
return getIndexTypeOfType(restType, IndexKind.Number) || anyType;
19712+
return getIndexTypeOfType(restType, IndexKind.Number);
1964519713
}
19646-
return anyType;
19714+
return undefined;
1964719715
}
1964819716

1964919717
function getTypeOfRestParameter(signature: Signature) {

src/compiler/utilities.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8098,4 +8098,20 @@ namespace ts {
80988098

80998099
return findBestPatternMatch(patterns, _ => _, candidate);
81008100
}
8101+
8102+
export function minAndMax<T>(arr: ReadonlyArray<T>, getValue: (value: T) => number): { readonly min: number, readonly max: number } {
8103+
Debug.assert(arr.length !== 0);
8104+
let min = getValue(arr[0]);
8105+
let max = min;
8106+
for (let i = 1; i < arr.length; i++) {
8107+
const value = getValue(arr[i]);
8108+
if (value < min) {
8109+
min = value;
8110+
}
8111+
else if (value > max) {
8112+
max = value;
8113+
}
8114+
}
8115+
return { min, max };
8116+
}
81018117
}

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7399,6 +7399,10 @@ declare namespace ts {
73997399
* (These are verified by verifyCompilerOptions to have 0 or 1 "*" characters.)
74007400
*/
74017401
function matchPatternOrExact(patternStrings: ReadonlyArray<string>, candidate: string): string | Pattern | undefined;
7402+
function minAndMax<T>(arr: ReadonlyArray<T>, getValue: (value: T) => number): {
7403+
readonly min: number;
7404+
readonly max: number;
7405+
};
74027406
}
74037407
declare namespace ts {
74047408
function createNode(kind: SyntaxKind, pos?: number, end?: number): Node;

tests/cases/fourslash/automaticConstructorToggling.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ edit.deleteAtCaret('constructor(val: T) { }'.length);
2828
verify.quickInfos({
2929
Asig: "constructor A<string>(): A<string>",
3030
Bsig: "constructor B<string>(val: string): B<string>",
31-
Csig: "constructor C<T>(): C<T>", // Cannot resolve signature
31+
Csig: "constructor C<{}>(): C<{}>", // Cannot resolve signature
3232
Dsig: "constructor D<string>(val: string): D<string>" // Cannot resolve signature
3333
});
3434

@@ -37,6 +37,6 @@ edit.deleteAtCaret("val: T".length);
3737
verify.quickInfos({
3838
Asig: "constructor A<string>(): A<string>",
3939
Bsig: "constructor B<string>(val: string): B<string>",
40-
Csig: "constructor C<T>(): C<T>", // Cannot resolve signature
40+
Csig: "constructor C<{}>(): C<{}>", // Cannot resolve signature
4141
Dsig: "constructor D<string>(): D<string>"
4242
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////interface A { a: number }
4+
////interface B { b: number }
5+
////declare function f(a: A): void;
6+
////declare function f(b: B): void;
7+
////f({ /**/ });
8+
9+
verify.completions({ marker: "", exact: ["a", "b"] });
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////interface A { a: number }
4+
////interface B { b: number }
5+
////interface C { c: number }
6+
////declare function f(a: A): void;
7+
////declare function f(...bs: B[]): void;
8+
////declare function f(...cs: C[]): void;
9+
////f({ /*1*/ });
10+
////f({ a: 1 }, { /*2*/ });
11+
12+
verify.completions(
13+
{ marker: "1", exact: ["a", "b", "c"] },
14+
{ marker: "2", exact: ["b", "c"] },
15+
);
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////interface A { a: number }
4+
////interface B { b: number }
5+
////declare function f(n: number): A;
6+
////declare function f(s: string): B;
7+
////f()./**/
8+
9+
verify.completions({ marker: "", exact: ["a", "b"] });

tests/cases/fourslash/genericFunctionSignatureHelp3.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@
1717
////foo7(1, <string>(/*7*/ // signature help shows y as T
1818

1919
verify.signatureHelp(
20-
{ marker: "1", text: "foo1<T>(x: number, callback: (y1: T) => number): void" },
20+
{ marker: "1", text: "foo1(x: number, callback: (y1: {}) => number): void" },
2121
// TODO: GH#23631
2222
// { marker: "2", text: "foo2(x: number, callback: (y2: {}) => number): void" },
23-
{ marker: "3", text: "foo3<T>(x: number, callback: (y3: T) => number): void" },
23+
{ marker: "3", text: "foo3(x: number, callback: (y3: {}) => number): void" },
2424
// TODO: GH#23631
2525
// { marker: "4", text: "foo4(x: number, callback: (y4: string) => number): void" },
2626
{ marker: "5", text: "foo5(x: number, callback: (y5: string) => number): void" },
@@ -31,4 +31,4 @@ goTo.marker('6');
3131
// verify.signatureHelp({ text: "foo6(x: number, callback: (y6: {}) => number): void" });
3232
edit.insert('string>(null,null);'); // need to make this line parse so we can get reasonable LS answers to later tests
3333

34-
verify.signatureHelp({ marker: "7", text: "foo7<T>(x: number, callback: (y7: T) => number): void" });
34+
verify.signatureHelp({ marker: "7", text: "foo7(x: number, callback: (y7: {}) => number): void" });

tests/cases/fourslash/genericFunctionSignatureHelp3MultiFile.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@
2424
////foo7(1, <string>(/*7*/ // signature help shows y as T
2525

2626
verify.signatureHelp(
27-
{ marker: "1", text: "foo1<T>(x: number, callback: (y1: T) => number): void" },
28-
{ marker: "2", text: "foo2<T>(x: number, callback: (y2: T) => number): void" },
29-
{ marker: "3", text: "foo3<T>(x: number, callback: (y3: T) => number): void" },
27+
{ marker: "1", text: "foo1(x: number, callback: (y1: {}) => number): void" },
28+
{ marker: "2", text: "foo2(x: number, callback: (y2: {}) => number): void" },
29+
{ marker: "3", text: "foo3(x: number, callback: (y3: {}) => number): void" },
3030
{ marker: "4", text: "foo4(x: number, callback: (y4: string) => number): void" },
3131
{ marker: "5", text: "foo5(x: number, callback: (y5: string) => number): void" },
3232
);
@@ -35,4 +35,4 @@ goTo.marker('6');
3535
verify.signatureHelp({ text: "foo6(x: number, callback: (y6: {}) => number): void" });
3636
edit.insert('string>(null,null);'); // need to make this line parse so we can get reasonable LS answers to later tests
3737

38-
verify.signatureHelp({ marker: "7", text: "foo7<T>(x: number, callback: (y7: T) => number): void" })
38+
verify.signatureHelp({ marker: "7", text: "foo7(x: number, callback: (y7: {}) => number): void" })

tests/cases/fourslash/jsDocFunctionSignatures10.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@
1212
////fo/**/o()
1313

1414
goTo.marker();
15-
verify.quickInfoIs("function foo<T>(x: T): void", "Do some foo things");
15+
verify.quickInfoIs("function foo<any>(x: any): void", "Do some foo things");

0 commit comments

Comments
 (0)