Skip to content

Commit 8463d9e

Browse files
author
Andy Hanson
committed
In services, when overload resolution fails, create a union signature
1 parent 7dfd6c7 commit 8463d9e

File tree

4 files changed

+137
-30
lines changed

4 files changed

+137
-30
lines changed

src/compiler/checker.ts

Lines changed: 99 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6682,13 +6682,17 @@ namespace ts {
66826682
}
66836683

66846684
function getRestTypeOfSignature(signature: Signature): Type {
6685+
return tryGetRestTypeOfSignature(signature) || anyType;
6686+
}
6687+
6688+
function tryGetRestTypeOfSignature(signature: Signature): Type | undefined {
66856689
if (signature.hasRestParameter) {
66866690
const type = getTypeOfSymbol(lastOrUndefined(signature.parameters));
66876691
if (getObjectFlags(type) & ObjectFlags.Reference && (<TypeReference>type).target === globalArrayType) {
66886692
return (<TypeReference>type).typeArguments[0];
66896693
}
66906694
}
6691-
return anyType;
6695+
return undefined;
66926696
}
66936697

66946698
function getSignatureInstantiation(signature: Signature, typeArguments: Type[], isJavascript: boolean): Signature {
@@ -10430,6 +10434,11 @@ namespace ts {
1043010434
return symbol;
1043110435
}
1043210436

10437+
function createCombinedSymbolWithType(sources: ReadonlyArray<Symbol>, type: Type): Symbol {
10438+
// This function is currently only used for erroneous overloads, so it's good enough to just use the first source.
10439+
return createSymbolWithType(first(sources), type);
10440+
}
10441+
1043310442
function transformTypeOfMembers(type: Type, f: (propertyType: Type) => Type) {
1043410443
const members = createSymbolTable();
1043510444
for (const property of getPropertiesOfObjectType(type)) {
@@ -16433,35 +16442,8 @@ namespace ts {
1643316442
diagnostics.add(createDiagnosticForNode(node, fallbackError));
1643416443
}
1643516444

16436-
// No signature was applicable. We have already reported the errors for the invalid signature.
16437-
// If this is a type resolution session, e.g. Language Service, try to get better information than anySignature.
16438-
// Pick the longest signature. This way we can get a contextual type for cases like:
16439-
// declare function f(a: { xa: number; xb: number; }, b: number);
16440-
// f({ |
16441-
// Also, use explicitly-supplied type arguments if they are provided, so we can get a contextual signature in cases like:
16442-
// declare function f<T>(k: keyof T);
16443-
// f<Foo>("
1644416445
if (!produceDiagnostics) {
16445-
Debug.assert(candidates.length > 0); // Else would have exited above.
16446-
const bestIndex = getLongestCandidateIndex(candidates, apparentArgumentCount === undefined ? args.length : apparentArgumentCount);
16447-
const candidate = candidates[bestIndex];
16448-
16449-
const { typeParameters } = candidate;
16450-
if (typeParameters && callLikeExpressionMayHaveTypeArguments(node) && node.typeArguments) {
16451-
const typeArguments = node.typeArguments.map(getTypeOfNode);
16452-
while (typeArguments.length > typeParameters.length) {
16453-
typeArguments.pop();
16454-
}
16455-
while (typeArguments.length < typeParameters.length) {
16456-
typeArguments.push(getDefaultTypeArgumentType(isInJavaScriptFile(node)));
16457-
}
16458-
16459-
const instantiated = createSignatureInstantiation(candidate, typeArguments);
16460-
candidates[bestIndex] = instantiated;
16461-
return instantiated;
16462-
}
16463-
16464-
return candidate;
16446+
return getCandidateForOverloadFailure(node, candidates, args, !!candidatesOutArray);
1646516447
}
1646616448

1646716449
return resolveErrorCall(node);
@@ -16535,6 +16517,89 @@ namespace ts {
1653516517
}
1653616518
}
1653716519

16520+
// No signature was applicable. We have already reported the errors for the invalid signature.
16521+
// If this is a type resolution session, e.g. Language Service, try to get better information than anySignature.
16522+
function getCandidateForOverloadFailure(
16523+
node: CallLikeExpression,
16524+
candidates: Signature[],
16525+
args: ReadonlyArray<Expression>,
16526+
hasCandidatesOutArray: boolean,
16527+
): Signature {
16528+
Debug.assert(candidates.length > 0); // Else should not have called this.
16529+
16530+
// Normally we will combine overloads. Skip this if they have type parameters since that's hard to combine.
16531+
// Don't do this if there is a `candidatesOutArray`,
16532+
// because then we want the chosen best candidate to be one of the overloads, not a combination.
16533+
if (!hasCandidatesOutArray && candidates.length > 1 && !candidates.some(c => !!c.typeParameters)) {
16534+
return createUnionOfSignaturesForOverloadFailure(candidates);
16535+
}
16536+
16537+
// Pick the longest signature. This way we can get a contextual type for cases like:
16538+
// declare function f(a: { xa: number; xb: number; }, b: number);
16539+
// f({ |
16540+
// Also, use explicitly-supplied type arguments if they are provided, so we can get a contextual signature in cases like:
16541+
// declare function f<T>(k: keyof T);
16542+
// f<Foo>("
16543+
const bestIndex = getLongestCandidateIndex(candidates, apparentArgumentCount === undefined ? args.length : apparentArgumentCount);
16544+
const candidate = candidates[bestIndex];
16545+
16546+
const { typeParameters } = candidate;
16547+
if (typeParameters && callLikeExpressionMayHaveTypeArguments(node) && node.typeArguments) {
16548+
const typeArguments = node.typeArguments.map(getTypeOfNode);
16549+
while (typeArguments.length > typeParameters.length) {
16550+
typeArguments.pop();
16551+
}
16552+
while (typeArguments.length < typeParameters.length) {
16553+
typeArguments.push(getDefaultTypeArgumentType(isInJavaScriptFile(node)));
16554+
}
16555+
16556+
const instantiated = createSignatureInstantiation(candidate, typeArguments);
16557+
candidates[bestIndex] = instantiated;
16558+
return instantiated;
16559+
}
16560+
16561+
return candidate;
16562+
}
16563+
16564+
function createUnionOfSignaturesForOverloadFailure(candidates: ReadonlyArray<Signature>): Signature {
16565+
const thisParameters = mapDefined(candidates, c => c.thisParameter);
16566+
let thisParameter: Symbol | undefined;
16567+
if (thisParameters.length) {
16568+
thisParameter = createCombinedSymbolWithType(thisParameters, getUnionType(thisParameters.map(getTypeOfParameter), /*subtypeReduction*/ true));
16569+
}
16570+
16571+
const { min: minArgumentCount, max: maxNonRestParam } = minAndMax(candidates, getNumNonRestParameters);
16572+
const hasRestParameter = candidates.some(c => c.hasRestParameter);
16573+
const hasLiteralTypes = candidates.some(c => c.hasLiteralTypes);
16574+
const parameters: ts.Symbol[] = [];
16575+
for (let i = 0; i < maxNonRestParam; i++) {
16576+
const symbols = mapDefined(candidates, ({ parameters, hasRestParameter }) => hasRestParameter ?
16577+
i < parameters.length - 1 ? parameters[i] : last(parameters) :
16578+
i < parameters.length ? parameters[i] : undefined);
16579+
Debug.assert(symbols.length !== 0);
16580+
const types = mapDefined(candidates, candidate => tryGetTypeAtPosition(candidate, i));
16581+
parameters.push(createCombinedSymbolWithType(symbols, getUnionType(types, /*subtypeReduction*/ true)));
16582+
}
16583+
16584+
if (hasRestParameter) {
16585+
const symbols = mapDefined(candidates, c => c.hasRestParameter ? last(c.parameters) : undefined);
16586+
Debug.assert(symbols.length !== 0);
16587+
const type = createArrayType(getUnionType(mapDefined(candidates, tryGetRestTypeOfSignature), /*subtypeReduction*/ true));
16588+
parameters.push(createCombinedSymbolWithType(symbols, type));
16589+
}
16590+
16591+
return createSignature(
16592+
candidates[0].declaration,
16593+
/*typeParameters*/ undefined,
16594+
thisParameter,
16595+
parameters,
16596+
/*resolvedReturnType*/ unknownType,
16597+
/*typePredicate*/ undefined,
16598+
minArgumentCount,
16599+
hasRestParameter,
16600+
hasLiteralTypes);
16601+
}
16602+
1653816603
function getLongestCandidateIndex(candidates: Signature[], argsCount: number): number {
1653916604
let maxParamsIndex = -1;
1654016605
let maxParams = -1;
@@ -17175,9 +17240,13 @@ namespace ts {
1717517240
}
1717617241

1717717242
function getTypeAtPosition(signature: Signature, pos: number): Type {
17243+
return tryGetTypeAtPosition(signature, pos) || anyType;
17244+
}
17245+
17246+
function tryGetTypeAtPosition(signature: Signature, pos: number): Type | undefined {
1717817247
return signature.hasRestParameter ?
1717917248
pos < signature.parameters.length - 1 ? getTypeOfParameter(signature.parameters[pos]) : getRestTypeOfSignature(signature) :
17180-
pos < signature.parameters.length ? getTypeOfParameter(signature.parameters[pos]) : anyType;
17249+
pos < signature.parameters.length ? getTypeOfParameter(signature.parameters[pos]) : undefined;
1718117250
}
1718217251

1718317252
function getTypeOfFirstParameterOfSignature(signature: Signature) {

src/compiler/core.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -778,6 +778,22 @@ namespace ts {
778778
return to;
779779
}
780780

781+
export function minAndMax<T>(arr: ReadonlyArray<T>, getValue: (value: T) => number): { min: number, max: number } {
782+
Debug.assert(arr.length !== 0);
783+
let min = getValue(arr[0]);
784+
let max = min;
785+
for (let i = 1; i < arr.length; i++) {
786+
const b = getValue(arr[i]);
787+
if (b < min) {
788+
min = b;
789+
}
790+
else if (b > max) {
791+
max = b;
792+
}
793+
}
794+
return { min, max };
795+
}
796+
781797
/**
782798
* Gets the actual offset into an array for a relative offset. Negative offsets indicate a
783799
* position offset from the end of the array.
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.completionsAt("", ["a", "b"]);
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
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.completionsAt("1", ["a", "b", "c"]);
13+
verify.completionsAt("2", ["b", "c"]);

0 commit comments

Comments
 (0)