Skip to content

Commit b6ce8a1

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

14 files changed

+169
-48
lines changed

src/compiler/checker.ts

Lines changed: 97 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7567,13 +7567,17 @@ namespace ts {
75677567
}
75687568

75697569
function getRestTypeOfSignature(signature: Signature): Type {
7570+
return tryGetRestTypeOfSignature(signature) || anyType;
7571+
}
7572+
7573+
function tryGetRestTypeOfSignature(signature: Signature): Type | undefined {
75707574
if (signature.hasRestParameter) {
75717575
const type = getTypeOfSymbol(last(signature.parameters));
75727576
if (getObjectFlags(type) & ObjectFlags.Reference && (<TypeReference>type).target === globalArrayType) {
75737577
return (<TypeReference>type).typeArguments![0];
75747578
}
75757579
}
7576-
return anyType;
7580+
return undefined;
75777581
}
75787582

75797583
function getSignatureInstantiation(signature: Signature, typeArguments: Type[] | undefined, isJavascript: boolean): Signature {
@@ -18559,38 +18563,7 @@ namespace ts {
1855918563
diagnostics.add(createDiagnosticForNode(node, fallbackError));
1856018564
}
1856118565

18562-
// No signature was applicable. We have already reported the errors for the invalid signature.
18563-
// If this is a type resolution session, e.g. Language Service, try to get better information than anySignature.
18564-
// Pick the longest signature. This way we can get a contextual type for cases like:
18565-
// declare function f(a: { xa: number; xb: number; }, b: number);
18566-
// f({ |
18567-
// Also, use explicitly-supplied type arguments if they are provided, so we can get a contextual signature in cases like:
18568-
// declare function f<T>(k: keyof T);
18569-
// f<Foo>("
18570-
if (!produceDiagnostics) {
18571-
Debug.assert(candidates.length > 0); // Else would have exited above.
18572-
const bestIndex = getLongestCandidateIndex(candidates, apparentArgumentCount === undefined ? args!.length : apparentArgumentCount);
18573-
const candidate = candidates[bestIndex];
18574-
18575-
const { typeParameters } = candidate;
18576-
if (typeParameters && callLikeExpressionMayHaveTypeArguments(node) && node.typeArguments) {
18577-
const typeArguments = node.typeArguments.map(getTypeOfNode) as Type[]; // TODO: GH#18217
18578-
while (typeArguments.length > typeParameters.length) {
18579-
typeArguments.pop();
18580-
}
18581-
while (typeArguments.length < typeParameters.length) {
18582-
typeArguments.push(getDefaultTypeArgumentType(isInJavaScriptFile(node)));
18583-
}
18584-
18585-
const instantiated = createSignatureInstantiation(candidate, typeArguments);
18586-
candidates[bestIndex] = instantiated;
18587-
return instantiated;
18588-
}
18589-
18590-
return candidate;
18591-
}
18592-
18593-
return resolveErrorCall(node);
18566+
return produceDiagnostics || !args ? resolveErrorCall(node) : getCandidateForOverloadFailure(node, candidates, args, !!candidatesOutArray);
1859418567

1859518568
function chooseOverload(candidates: Signature[], relation: Map<RelationComparisonResult>, signatureHelpTrailingComma = false) {
1859618569
candidateForArgumentError = undefined;
@@ -18661,6 +18634,92 @@ namespace ts {
1866118634
}
1866218635
}
1866318636

18637+
// No signature was applicable. We have already reported the errors for the invalid signature.
18638+
// If this is a type resolution session, e.g. Language Service, try to get better information than anySignature.
18639+
function getCandidateForOverloadFailure(
18640+
node: CallLikeExpression,
18641+
candidates: Signature[],
18642+
args: ReadonlyArray<Expression>,
18643+
hasCandidatesOutArray: boolean,
18644+
): Signature {
18645+
Debug.assert(candidates.length > 0); // Else should not have called this.
18646+
// Normally we will combine overloads. Skip this if they have type parameters since that's hard to combine.
18647+
// Don't do this if there is a `candidatesOutArray`,
18648+
// because then we want the chosen best candidate to be one of the overloads, not a combination.
18649+
return hasCandidatesOutArray || candidates.length === 1 || candidates.some(c => !!c.typeParameters)
18650+
? pickLongestCandidateSignature(node, candidates, args)
18651+
: createUnionOfSignaturesForOverloadFailure(candidates);
18652+
}
18653+
18654+
function createUnionOfSignaturesForOverloadFailure(candidates: ReadonlyArray<Signature>): Signature {
18655+
const thisParameters = mapDefined(candidates, c => c.thisParameter);
18656+
let thisParameter: Symbol | undefined;
18657+
if (thisParameters.length) {
18658+
thisParameter = createCombinedSymbolFromTypes(thisParameters, thisParameters.map(getTypeOfParameter));
18659+
}
18660+
const { min: minArgumentCount, max: maxNonRestParam } = minAndMax(candidates, getNumNonRestParameters);
18661+
const parameters: Symbol[] = [];
18662+
for (let i = 0; i < maxNonRestParam; i++) {
18663+
const symbols = mapDefined(candidates, ({ parameters, hasRestParameter }) => hasRestParameter ?
18664+
i < parameters.length - 1 ? parameters[i] : last(parameters) :
18665+
i < parameters.length ? parameters[i] : undefined);
18666+
Debug.assert(symbols.length !== 0);
18667+
parameters.push(createCombinedSymbolFromTypes(symbols, mapDefined(candidates, candidate => tryGetTypeAtPosition(candidate, i))));
18668+
}
18669+
const restParameterSymbols = mapDefined(candidates, c => c.hasRestParameter ? last(c.parameters) : undefined);
18670+
const hasRestParameter = restParameterSymbols.length !== 0;
18671+
if (hasRestParameter) {
18672+
const type = createArrayType(getUnionType(mapDefined(candidates, tryGetRestTypeOfSignature), UnionReduction.Subtype));
18673+
parameters.push(createCombinedSymbolForOverloadFailure(restParameterSymbols, type));
18674+
}
18675+
return createSignature(
18676+
candidates[0].declaration,
18677+
/*typeParameters*/ undefined, // Before calling this we tested for `!candidates.some(c => !!c.typeParameters)`.
18678+
thisParameter,
18679+
parameters,
18680+
/*resolvedReturnType*/ getIntersectionType(candidates.map(getReturnTypeOfSignature)),
18681+
/*typePredicate*/ undefined,
18682+
minArgumentCount,
18683+
hasRestParameter,
18684+
/*hasLiteralTypes*/ candidates.some(c => c.hasLiteralTypes));
18685+
}
18686+
18687+
function createCombinedSymbolFromTypes(sources: ReadonlyArray<Symbol>, types: Type[]): Symbol {
18688+
return createCombinedSymbolForOverloadFailure(sources, getUnionType(types, UnionReduction.Subtype));
18689+
}
18690+
18691+
function createCombinedSymbolForOverloadFailure(sources: ReadonlyArray<Symbol>, type: Type): Symbol {
18692+
// This function is currently only used for erroneous overloads, so it's good enough to just use the first source.
18693+
return createSymbolWithType(first(sources), type);
18694+
}
18695+
18696+
function pickLongestCandidateSignature(node: CallLikeExpression, candidates: Signature[], args: ReadonlyArray<Expression>): Signature {
18697+
// Pick the longest signature. This way we can get a contextual type for cases like:
18698+
// declare function f(a: { xa: number; xb: number; }, b: number);
18699+
// f({ |
18700+
// Also, use explicitly-supplied type arguments if they are provided, so we can get a contextual signature in cases like:
18701+
// declare function f<T>(k: keyof T);
18702+
// f<Foo>("
18703+
const bestIndex = getLongestCandidateIndex(candidates, apparentArgumentCount === undefined ? args.length : apparentArgumentCount);
18704+
const candidate = candidates[bestIndex];
18705+
const { typeParameters } = candidate;
18706+
if (!typeParameters) {
18707+
return candidate;
18708+
}
18709+
18710+
const typeArgumentNodes: ReadonlyArray<TypeNode> = callLikeExpressionMayHaveTypeArguments(node) ? node.typeArguments || emptyArray : emptyArray;
18711+
const typeArguments = typeArgumentNodes.map(n => getTypeOfNode(n) || anyType);
18712+
while (typeArguments.length > typeParameters.length) {
18713+
typeArguments.pop();
18714+
}
18715+
while (typeArguments.length < typeParameters.length) {
18716+
typeArguments.push(getConstraintFromTypeParameter(typeParameters[typeArguments.length]) || getDefaultTypeArgumentType(isInJavaScriptFile(node)));
18717+
}
18718+
const instantiated = createSignatureInstantiation(candidate, typeArguments);
18719+
candidates[bestIndex] = instantiated;
18720+
return instantiated;
18721+
}
18722+
1866418723
function getLongestCandidateIndex(candidates: Signature[], argsCount: number): number {
1866518724
let maxParamsIndex = -1;
1866618725
let maxParams = -1;
@@ -19400,12 +19459,15 @@ namespace ts {
1940019459
}
1940119460

1940219461
function getTypeAtPosition(signature: Signature, pos: number): Type {
19462+
return tryGetTypeAtPosition(signature, pos) || anyType;
19463+
}
19464+
19465+
function tryGetTypeAtPosition(signature: Signature, pos: number): Type | undefined {
1940319466
return signature.hasRestParameter ?
1940419467
pos < signature.parameters.length - 1 ? getTypeOfParameter(signature.parameters[pos]) : getRestTypeOfSignature(signature) :
19405-
pos < signature.parameters.length ? getTypeOfParameter(signature.parameters[pos]) : anyType;
19468+
pos < signature.parameters.length ? getTypeOfParameter(signature.parameters[pos]) : undefined;
1940619469
}
1940719470

19408-
1940919471
function getTypeOfFirstParameterOfSignature(signature: Signature) {
1941019472
return getTypeOfFirstParameterOfSignatureWithFallback(signature, neverType);
1941119473
}

src/compiler/utilities.ts

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

80958095
return findBestPatternMatch(patterns, _ => _, candidate);
80968096
}
8097+
8098+
export function minAndMax<T>(arr: ReadonlyArray<T>, getValue: (value: T) => number): { readonly min: number, readonly max: number } {
8099+
Debug.assert(arr.length !== 0);
8100+
let min = getValue(arr[0]);
8101+
let max = min;
8102+
for (let i = 1; i < arr.length; i++) {
8103+
const b = getValue(arr[i]);
8104+
if (b < min) {
8105+
min = b;
8106+
}
8107+
else if (b > max) {
8108+
max = b;
8109+
}
8110+
}
8111+
return { min, max };
8112+
}
80978113
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7367,6 +7367,10 @@ declare namespace ts {
73677367
* (These are verified by verifyCompilerOptions to have 0 or 1 "*" characters.)
73687368
*/
73697369
function matchPatternOrExact(patternStrings: ReadonlyArray<string>, candidate: string): string | Pattern | undefined;
7370+
function minAndMax<T>(arr: ReadonlyArray<T>, getValue: (value: T) => number): {
7371+
readonly min: number;
7372+
readonly max: number;
7373+
};
73707374
}
73717375
declare namespace ts {
73727376
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");

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

0 commit comments

Comments
 (0)