Skip to content

Commit 990d445

Browse files
author
Andy
authored
In services, when overload resolution fails, create a union signature (2) (#25100)
1 parent f66c7db commit 990d445

15 files changed

+175
-52
lines changed

src/compiler/checker.ts

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

7705-
function getRestTypeOfSignature(signature: Signature) {
7705+
function getRestTypeOfSignature(signature: Signature): Type {
7706+
return tryGetRestTypeOfSignature(signature) || anyType;
7707+
}
7708+
7709+
function tryGetRestTypeOfSignature(signature: Signature): Type | undefined {
77067710
const type = getTypeOfRestParameter(signature);
7707-
return type && getIndexTypeOfType(type, IndexKind.Number) || anyType;
7711+
return type && getIndexTypeOfType(type, IndexKind.Number);
77087712
}
77097713

77107714
function getSignatureInstantiation(signature: Signature, typeArguments: Type[] | undefined, isJavascript: boolean): Signature {
@@ -19069,38 +19073,7 @@ namespace ts {
1906919073
diagnostics.add(createDiagnosticForNode(node, fallbackError));
1907019074
}
1907119075

19072-
// No signature was applicable. We have already reported the errors for the invalid signature.
19073-
// If this is a type resolution session, e.g. Language Service, try to get better information than anySignature.
19074-
// Pick the longest signature. This way we can get a contextual type for cases like:
19075-
// declare function f(a: { xa: number; xb: number; }, b: number);
19076-
// f({ |
19077-
// Also, use explicitly-supplied type arguments if they are provided, so we can get a contextual signature in cases like:
19078-
// declare function f<T>(k: keyof T);
19079-
// f<Foo>("
19080-
if (!produceDiagnostics) {
19081-
Debug.assert(candidates.length > 0); // Else would have exited above.
19082-
const bestIndex = getLongestCandidateIndex(candidates, apparentArgumentCount === undefined ? args!.length : apparentArgumentCount);
19083-
const candidate = candidates[bestIndex];
19084-
19085-
const { typeParameters } = candidate;
19086-
if (typeParameters && callLikeExpressionMayHaveTypeArguments(node) && node.typeArguments) {
19087-
const typeArguments = node.typeArguments.map(getTypeOfNode) as Type[]; // TODO: GH#18217
19088-
while (typeArguments.length > typeParameters.length) {
19089-
typeArguments.pop();
19090-
}
19091-
while (typeArguments.length < typeParameters.length) {
19092-
typeArguments.push(getDefaultTypeArgumentType(isInJavaScriptFile(node)));
19093-
}
19094-
19095-
const instantiated = createSignatureInstantiation(candidate, typeArguments);
19096-
candidates[bestIndex] = instantiated;
19097-
return instantiated;
19098-
}
19099-
19100-
return candidate;
19101-
}
19102-
19103-
return resolveErrorCall(node);
19076+
return produceDiagnostics || !args ? resolveErrorCall(node) : getCandidateForOverloadFailure(node, candidates, args, !!candidatesOutArray);
1910419077

1910519078
function chooseOverload(candidates: Signature[], relation: Map<RelationComparisonResult>, signatureHelpTrailingComma = false) {
1910619079
candidateForArgumentError = undefined;
@@ -19171,6 +19144,97 @@ namespace ts {
1917119144
}
1917219145
}
1917319146

19147+
// No signature was applicable. We have already reported the errors for the invalid signature.
19148+
// If this is a type resolution session, e.g. Language Service, try to get better information than anySignature.
19149+
function getCandidateForOverloadFailure(
19150+
node: CallLikeExpression,
19151+
candidates: Signature[],
19152+
args: ReadonlyArray<Expression>,
19153+
hasCandidatesOutArray: boolean,
19154+
): Signature {
19155+
Debug.assert(candidates.length > 0); // Else should not have called this.
19156+
// Normally we will combine overloads. Skip this if they have type parameters since that's hard to combine.
19157+
// Don't do this if there is a `candidatesOutArray`,
19158+
// because then we want the chosen best candidate to be one of the overloads, not a combination.
19159+
return hasCandidatesOutArray || candidates.length === 1 || candidates.some(c => !!c.typeParameters)
19160+
? pickLongestCandidateSignature(node, candidates, args)
19161+
: createUnionOfSignaturesForOverloadFailure(candidates);
19162+
}
19163+
19164+
function createUnionOfSignaturesForOverloadFailure(candidates: ReadonlyArray<Signature>): Signature {
19165+
const thisParameters = mapDefined(candidates, c => c.thisParameter);
19166+
let thisParameter: Symbol | undefined;
19167+
if (thisParameters.length) {
19168+
thisParameter = createCombinedSymbolFromTypes(thisParameters, thisParameters.map(getTypeOfParameter));
19169+
}
19170+
const { min: minArgumentCount, max: maxNonRestParam } = minAndMax(candidates, getNumNonRestParameters);
19171+
const parameters: Symbol[] = [];
19172+
for (let i = 0; i < maxNonRestParam; i++) {
19173+
const symbols = mapDefined(candidates, ({ parameters, hasRestParameter }) => hasRestParameter ?
19174+
i < parameters.length - 1 ? parameters[i] : last(parameters) :
19175+
i < parameters.length ? parameters[i] : undefined);
19176+
Debug.assert(symbols.length !== 0);
19177+
parameters.push(createCombinedSymbolFromTypes(symbols, mapDefined(candidates, candidate => tryGetTypeAtPosition(candidate, i))));
19178+
}
19179+
const restParameterSymbols = mapDefined(candidates, c => c.hasRestParameter ? last(c.parameters) : undefined);
19180+
const hasRestParameter = restParameterSymbols.length !== 0;
19181+
if (hasRestParameter) {
19182+
const type = createArrayType(getUnionType(mapDefined(candidates, tryGetRestTypeOfSignature), UnionReduction.Subtype));
19183+
parameters.push(createCombinedSymbolForOverloadFailure(restParameterSymbols, type));
19184+
}
19185+
return createSignature(
19186+
candidates[0].declaration,
19187+
/*typeParameters*/ undefined, // Before calling this we tested for `!candidates.some(c => !!c.typeParameters)`.
19188+
thisParameter,
19189+
parameters,
19190+
/*resolvedReturnType*/ getIntersectionType(candidates.map(getReturnTypeOfSignature)),
19191+
/*typePredicate*/ undefined,
19192+
minArgumentCount,
19193+
hasRestParameter,
19194+
/*hasLiteralTypes*/ candidates.some(c => c.hasLiteralTypes));
19195+
}
19196+
19197+
function getNumNonRestParameters(signature: Signature): number {
19198+
const numParams = signature.parameters.length;
19199+
return signature.hasRestParameter ? numParams - 1 : numParams;
19200+
}
19201+
19202+
function createCombinedSymbolFromTypes(sources: ReadonlyArray<Symbol>, types: Type[]): Symbol {
19203+
return createCombinedSymbolForOverloadFailure(sources, getUnionType(types, UnionReduction.Subtype));
19204+
}
19205+
19206+
function createCombinedSymbolForOverloadFailure(sources: ReadonlyArray<Symbol>, type: Type): Symbol {
19207+
// This function is currently only used for erroneous overloads, so it's good enough to just use the first source.
19208+
return createSymbolWithType(first(sources), type);
19209+
}
19210+
19211+
function pickLongestCandidateSignature(node: CallLikeExpression, candidates: Signature[], args: ReadonlyArray<Expression>): Signature {
19212+
// Pick the longest signature. This way we can get a contextual type for cases like:
19213+
// declare function f(a: { xa: number; xb: number; }, b: number);
19214+
// f({ |
19215+
// Also, use explicitly-supplied type arguments if they are provided, so we can get a contextual signature in cases like:
19216+
// declare function f<T>(k: keyof T);
19217+
// f<Foo>("
19218+
const bestIndex = getLongestCandidateIndex(candidates, apparentArgumentCount === undefined ? args.length : apparentArgumentCount);
19219+
const candidate = candidates[bestIndex];
19220+
const { typeParameters } = candidate;
19221+
if (!typeParameters) {
19222+
return candidate;
19223+
}
19224+
19225+
const typeArgumentNodes: ReadonlyArray<TypeNode> = callLikeExpressionMayHaveTypeArguments(node) ? node.typeArguments || emptyArray : emptyArray;
19226+
const typeArguments = typeArgumentNodes.map(n => getTypeOfNode(n) || anyType);
19227+
while (typeArguments.length > typeParameters.length) {
19228+
typeArguments.pop();
19229+
}
19230+
while (typeArguments.length < typeParameters.length) {
19231+
typeArguments.push(getConstraintFromTypeParameter(typeParameters[typeArguments.length]) || getDefaultTypeArgumentType(isInJavaScriptFile(node)));
19232+
}
19233+
const instantiated = createSignatureInstantiation(candidate, typeArguments);
19234+
candidates[bestIndex] = instantiated;
19235+
return instantiated;
19236+
}
19237+
1917419238
function getLongestCandidateIndex(candidates: Signature[], argsCount: number): number {
1917519239
let maxParamsIndex = -1;
1917619240
let maxParams = -1;
@@ -19955,6 +20019,10 @@ namespace ts {
1995520019
}
1995620020

1995720021
function getTypeAtPosition(signature: Signature, pos: number): Type {
20022+
return tryGetTypeAtPosition(signature, pos) || anyType;
20023+
}
20024+
20025+
function tryGetTypeAtPosition(signature: Signature, pos: number): Type | undefined {
1995820026
const paramCount = signature.parameters.length - (signature.hasRestParameter ? 1 : 0);
1995920027
if (pos < paramCount) {
1996020028
return getTypeOfParameter(signature.parameters[pos]);
@@ -19970,9 +20038,9 @@ namespace ts {
1997020038
return tupleRestType;
1997120039
}
1997220040
}
19973-
return getIndexTypeOfType(restType, IndexKind.Number) || anyType;
20041+
return getIndexTypeOfType(restType, IndexKind.Number);
1997420042
}
19975-
return anyType;
20043+
return undefined;
1997620044
}
1997720045

1997820046
function getRestTypeAtPosition(source: Signature, pos: number): Type {

src/compiler/utilities.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8090,4 +8090,20 @@ namespace ts {
80908090
Debug.assert(index !== -1);
80918091
return arr.slice(index);
80928092
}
8093+
8094+
export function minAndMax<T>(arr: ReadonlyArray<T>, getValue: (value: T) => number): { readonly min: number, readonly max: number } {
8095+
Debug.assert(arr.length !== 0);
8096+
let min = getValue(arr[0]);
8097+
let max = min;
8098+
for (let i = 1; i < arr.length; i++) {
8099+
const value = getValue(arr[i]);
8100+
if (value < min) {
8101+
min = value;
8102+
}
8103+
else if (value > max) {
8104+
max = value;
8105+
}
8106+
}
8107+
return { min, max };
8108+
}
80938109
}

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");

tests/cases/fourslash/jsdocReturnsTag.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
verify.signatureHelp({
1616
marker: "",
17-
text: "find<T>(l: T[], x: T): T",
17+
text: "find(l: any[], x: any): any",
1818
docComment: "Find an item",
1919
tags: [
2020
// TODO: GH#24130 (see PR #24600's commits for potential fix)

tests/cases/fourslash/quickInfoCanBeTruncated.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,7 @@
509509
//// function f<T extends A>(s: T, x: Exclude<A, T>, y: string) {}
510510
//// f("_499", /*3*/);
511511
//// type Decomposed/*4*/ = {[K in A]: Foo[K]}
512-
//// type LongTuple/*5*/ = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17.18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70];
512+
//// type LongTuple/*5*/ = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17.18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70];
513513
//// type DeeplyMapped/*6*/ = {[K in keyof Foo]: {[K2 in keyof Foo]: [K, K2, Foo[K], Foo[K2]]}}
514514

515515
goTo.marker("1");
@@ -519,7 +519,7 @@ verify.quickInfoIs(`type Less = "_1" | "_2" | "_3" | "_4" | "_5" | "_6" | "_7" |
519519
goTo.marker("3");
520520
verify.signatureHelp({
521521
marker: "3",
522-
text: `f<T extends "_0" | "_1" | "_2" | "_3" | "_4" | "_5" | "_6" | "_7" | "_8" | "_9" | "_10" | "_11" | "_12" | "_13" | "_14" | "_15" | "_16" | "_17" | "_18" | "_19" | "_20" | "_21" | "_22" | "_23" | "_24" | ... 474 more ... | "_499">(s: T, x: Exclude<"_0", T> | Exclude<"_1", T> | Exclude<"_2", T> | Exclude<"_3", T> | Exclude<"_4", T> | Exclude<"_5", T> | Exclude<"_6", T> | Exclude<"_7", T> | Exclude<...> | ... 490 more ... | Exclude<...>, y: string): void`
522+
text: `f(s: "_0" | "_1" | "_2" | "_3" | "_4" | "_5" | "_6" | "_7" | "_8" | "_9" | "_10" | "_11" | "_12" | "_13" | "_14" | "_15" | "_16" | "_17" | "_18" | "_19" | "_20" | "_21" | "_22" | "_23" | "_24" | ... 474 more ... | "_499", x: never, y: string): void`
523523
});
524524
goTo.marker("4");
525525
verify.quickInfoIs(`type Decomposed = {
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
////declare function f<T>(x: number): T;
4+
////const x/**/ = f();
5+
6+
verify.quickInfoAt("", "const x: {}");

tests/cases/fourslash/signatureHelpExplicitTypeArguments.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
verify.signatureHelp(
1010
{ marker: "1", text: "f(x: number, y: string): number" },
11-
{ marker: "2", text: "f<T = boolean, U = string>(x: T, y: U): T" },
11+
{ marker: "2", text: "f(x: {}, y: {}): {}" },
1212
// too few -- fill in rest with {}
1313
{ marker: "3", text: "f(x: number, y: {}): number" },
1414
// too many -- ignore extra type arguments

tests/cases/fourslash/signatureHelpOnTypePredicates.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@
99

1010
verify.signatureHelp(
1111
{ marker: "1", text: "f1(a: any): a is number" },
12-
{ marker: "2", text: "f2<T>(a: any): a is T" },
12+
{ marker: "2", text: "f2(a: any): a is {}" },
1313
{ marker: "3", text: "f3(a: any, ...b: any[]): a is number", isVariadic: true },
1414
)

tests/cases/fourslash/signatureHelpWithTriggers02.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ goTo.marker("1");
99

1010
edit.insert("(");
1111
verify.signatureHelp({
12-
text: "bar<U>(x: U, y: U): U",
12+
text: "bar(x: {}, y: {}): {}",
1313
triggerReason: {
1414
kind: "characterTyped",
1515
triggerCharacter: "(",

0 commit comments

Comments
 (0)