Skip to content

Commit 04c7c91

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

File tree

4 files changed

+130
-30
lines changed

4 files changed

+130
-30
lines changed

src/compiler/checker.ts

Lines changed: 92 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);
1646516447
}
1646616448

1646716449
return resolveErrorCall(node);
@@ -16535,6 +16517,82 @@ 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(node: CallLikeExpression, candidates: Signature[], args: ReadonlyArray<Expression>): Signature {
16523+
Debug.assert(candidates.length > 0); // Else should not have called this.
16524+
16525+
// Normally we will combine overloads. Skip this if they have type parameters since that's hard to combine.
16526+
if (candidates.length > 1 && !candidates.some(c => !!c.typeParameters)) {
16527+
return createUnionOfSignaturesForOverloadFailure(candidates);
16528+
}
16529+
16530+
// Pick the longest signature. This way we can get a contextual type for cases like:
16531+
// declare function f(a: { xa: number; xb: number; }, b: number);
16532+
// f({ |
16533+
// Also, use explicitly-supplied type arguments if they are provided, so we can get a contextual signature in cases like:
16534+
// declare function f<T>(k: keyof T);
16535+
// f<Foo>("
16536+
const bestIndex = getLongestCandidateIndex(candidates, apparentArgumentCount === undefined ? args.length : apparentArgumentCount);
16537+
const candidate = candidates[bestIndex];
16538+
16539+
const { typeParameters } = candidate;
16540+
if (typeParameters && callLikeExpressionMayHaveTypeArguments(node) && node.typeArguments) {
16541+
const typeArguments = node.typeArguments.map(getTypeOfNode);
16542+
while (typeArguments.length > typeParameters.length) {
16543+
typeArguments.pop();
16544+
}
16545+
while (typeArguments.length < typeParameters.length) {
16546+
typeArguments.push(getDefaultTypeArgumentType(isInJavaScriptFile(node)));
16547+
}
16548+
16549+
const instantiated = createSignatureInstantiation(candidate, typeArguments);
16550+
candidates[bestIndex] = instantiated;
16551+
return instantiated;
16552+
}
16553+
16554+
return candidate;
16555+
}
16556+
16557+
function createUnionOfSignaturesForOverloadFailure(candidates: ReadonlyArray<Signature>): Signature {
16558+
const thisParameters = mapDefined(candidates, c => c.thisParameter);
16559+
let thisParameter: Symbol | undefined;
16560+
if (thisParameters.length) {
16561+
thisParameter = createCombinedSymbolWithType(thisParameters, getUnionType(thisParameters.map(getTypeOfParameter), /*subtypeReduction*/ true));
16562+
}
16563+
16564+
const { min: minArgumentCount, max: maxNonRestParam } = minAndMax(candidates, getNumNonRestParameters);
16565+
const hasRestParameter = candidates.some(c => c.hasRestParameter);
16566+
const hasLiteralTypes = candidates.some(c => c.hasLiteralTypes);
16567+
const parameters: ts.Symbol[] = [];
16568+
for (let i = 0; i < maxNonRestParam; i++) {
16569+
const symbols = mapDefined(candidates, ({ parameters, hasRestParameter }) => hasRestParameter ?
16570+
i < parameters.length - 1 ? parameters[i] : last(parameters) :
16571+
i < parameters.length ? parameters[i] : undefined);
16572+
Debug.assert(symbols.length !== 0);
16573+
const types = mapDefined(candidates, candidate => tryGetTypeAtPosition(candidate, i));
16574+
parameters.push(createCombinedSymbolWithType(symbols, getUnionType(types, /*subtypeReduction*/ true)));
16575+
}
16576+
16577+
if (hasRestParameter) {
16578+
const symbols = mapDefined(candidates, c => c.hasRestParameter ? last(c.parameters) : undefined);
16579+
Debug.assert(symbols.length !== 0);
16580+
const type = createArrayType(getUnionType(mapDefined(candidates, tryGetRestTypeOfSignature), /*subtypeReduction*/ true));
16581+
parameters.push(createCombinedSymbolWithType(symbols, type));
16582+
}
16583+
16584+
return createSignature(
16585+
candidates[0].declaration,
16586+
/*typeParameters*/ undefined,
16587+
thisParameter,
16588+
parameters,
16589+
/*resolvedReturnType*/ unknownType,
16590+
/*typePredicate*/ undefined,
16591+
minArgumentCount,
16592+
hasRestParameter,
16593+
hasLiteralTypes);
16594+
}
16595+
1653816596
function getLongestCandidateIndex(candidates: Signature[], argsCount: number): number {
1653916597
let maxParamsIndex = -1;
1654016598
let maxParams = -1;
@@ -17175,9 +17233,13 @@ namespace ts {
1717517233
}
1717617234

1717717235
function getTypeAtPosition(signature: Signature, pos: number): Type {
17236+
return tryGetTypeAtPosition(signature, pos) || anyType;
17237+
}
17238+
17239+
function tryGetTypeAtPosition(signature: Signature, pos: number): Type | undefined {
1717817240
return signature.hasRestParameter ?
1717917241
pos < signature.parameters.length - 1 ? getTypeOfParameter(signature.parameters[pos]) : getRestTypeOfSignature(signature) :
17180-
pos < signature.parameters.length ? getTypeOfParameter(signature.parameters[pos]) : anyType;
17242+
pos < signature.parameters.length ? getTypeOfParameter(signature.parameters[pos]) : undefined;
1718117243
}
1718217244

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