Skip to content

In services, when overload resolution fails, create a union signature (2) #25100

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
3 commits merged into from
Jul 11, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 104 additions & 36 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7702,9 +7702,13 @@ namespace ts {
return !signature.resolvedReturnType && findResolutionCycleStartIndex(signature, TypeSystemPropertyName.ResolvedReturnType) >= 0;
}

function getRestTypeOfSignature(signature: Signature) {
function getRestTypeOfSignature(signature: Signature): Type {
return tryGetRestTypeOfSignature(signature) || anyType;
}

function tryGetRestTypeOfSignature(signature: Signature): Type | undefined {
const type = getTypeOfRestParameter(signature);
return type && getIndexTypeOfType(type, IndexKind.Number) || anyType;
return type && getIndexTypeOfType(type, IndexKind.Number);
}

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

// No signature was applicable. We have already reported the errors for the invalid signature.
// If this is a type resolution session, e.g. Language Service, try to get better information than anySignature.
// Pick the longest signature. This way we can get a contextual type for cases like:
// declare function f(a: { xa: number; xb: number; }, b: number);
// f({ |
// Also, use explicitly-supplied type arguments if they are provided, so we can get a contextual signature in cases like:
// declare function f<T>(k: keyof T);
// f<Foo>("
if (!produceDiagnostics) {
Debug.assert(candidates.length > 0); // Else would have exited above.
const bestIndex = getLongestCandidateIndex(candidates, apparentArgumentCount === undefined ? args!.length : apparentArgumentCount);
const candidate = candidates[bestIndex];

const { typeParameters } = candidate;
if (typeParameters && callLikeExpressionMayHaveTypeArguments(node) && node.typeArguments) {
const typeArguments = node.typeArguments.map(getTypeOfNode) as Type[]; // TODO: GH#18217
while (typeArguments.length > typeParameters.length) {
typeArguments.pop();
}
while (typeArguments.length < typeParameters.length) {
typeArguments.push(getDefaultTypeArgumentType(isInJavaScriptFile(node)));
}

const instantiated = createSignatureInstantiation(candidate, typeArguments);
candidates[bestIndex] = instantiated;
return instantiated;
}

return candidate;
}

return resolveErrorCall(node);
return produceDiagnostics || !args ? resolveErrorCall(node) : getCandidateForOverloadFailure(node, candidates, args, !!candidatesOutArray);

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

// No signature was applicable. We have already reported the errors for the invalid signature.
// If this is a type resolution session, e.g. Language Service, try to get better information than anySignature.
function getCandidateForOverloadFailure(
node: CallLikeExpression,
candidates: Signature[],
args: ReadonlyArray<Expression>,
hasCandidatesOutArray: boolean,
): Signature {
Debug.assert(candidates.length > 0); // Else should not have called this.
// Normally we will combine overloads. Skip this if they have type parameters since that's hard to combine.
// Don't do this if there is a `candidatesOutArray`,
// because then we want the chosen best candidate to be one of the overloads, not a combination.
return hasCandidatesOutArray || candidates.length === 1 || candidates.some(c => !!c.typeParameters)
? pickLongestCandidateSignature(node, candidates, args)
: createUnionOfSignaturesForOverloadFailure(candidates);
}

function createUnionOfSignaturesForOverloadFailure(candidates: ReadonlyArray<Signature>): Signature {
const thisParameters = mapDefined(candidates, c => c.thisParameter);
let thisParameter: Symbol | undefined;
if (thisParameters.length) {
thisParameter = createCombinedSymbolFromTypes(thisParameters, thisParameters.map(getTypeOfParameter));
}
const { min: minArgumentCount, max: maxNonRestParam } = minAndMax(candidates, getNumNonRestParameters);
const parameters: Symbol[] = [];
for (let i = 0; i < maxNonRestParam; i++) {
const symbols = mapDefined(candidates, ({ parameters, hasRestParameter }) => hasRestParameter ?
i < parameters.length - 1 ? parameters[i] : last(parameters) :
i < parameters.length ? parameters[i] : undefined);
Debug.assert(symbols.length !== 0);
parameters.push(createCombinedSymbolFromTypes(symbols, mapDefined(candidates, candidate => tryGetTypeAtPosition(candidate, i))));
}
const restParameterSymbols = mapDefined(candidates, c => c.hasRestParameter ? last(c.parameters) : undefined);
const hasRestParameter = restParameterSymbols.length !== 0;
if (hasRestParameter) {
const type = createArrayType(getUnionType(mapDefined(candidates, tryGetRestTypeOfSignature), UnionReduction.Subtype));
parameters.push(createCombinedSymbolForOverloadFailure(restParameterSymbols, type));
}
return createSignature(
candidates[0].declaration,
/*typeParameters*/ undefined, // Before calling this we tested for `!candidates.some(c => !!c.typeParameters)`.
thisParameter,
parameters,
/*resolvedReturnType*/ getIntersectionType(candidates.map(getReturnTypeOfSignature)),
/*typePredicate*/ undefined,
minArgumentCount,
hasRestParameter,
/*hasLiteralTypes*/ candidates.some(c => c.hasLiteralTypes));
}

function getNumNonRestParameters(signature: Signature): number {
const numParams = signature.parameters.length;
return signature.hasRestParameter ? numParams - 1 : numParams;
}

function createCombinedSymbolFromTypes(sources: ReadonlyArray<Symbol>, types: Type[]): Symbol {
return createCombinedSymbolForOverloadFailure(sources, getUnionType(types, UnionReduction.Subtype));
}

function createCombinedSymbolForOverloadFailure(sources: ReadonlyArray<Symbol>, type: Type): Symbol {
// This function is currently only used for erroneous overloads, so it's good enough to just use the first source.
return createSymbolWithType(first(sources), type);
}

function pickLongestCandidateSignature(node: CallLikeExpression, candidates: Signature[], args: ReadonlyArray<Expression>): Signature {
// Pick the longest signature. This way we can get a contextual type for cases like:
// declare function f(a: { xa: number; xb: number; }, b: number);
// f({ |
// Also, use explicitly-supplied type arguments if they are provided, so we can get a contextual signature in cases like:
// declare function f<T>(k: keyof T);
// f<Foo>("
const bestIndex = getLongestCandidateIndex(candidates, apparentArgumentCount === undefined ? args.length : apparentArgumentCount);
const candidate = candidates[bestIndex];
const { typeParameters } = candidate;
if (!typeParameters) {
return candidate;
}

const typeArgumentNodes: ReadonlyArray<TypeNode> = callLikeExpressionMayHaveTypeArguments(node) ? node.typeArguments || emptyArray : emptyArray;
const typeArguments = typeArgumentNodes.map(n => getTypeOfNode(n) || anyType);
while (typeArguments.length > typeParameters.length) {
typeArguments.pop();
}
while (typeArguments.length < typeParameters.length) {
typeArguments.push(getConstraintFromTypeParameter(typeParameters[typeArguments.length]) || getDefaultTypeArgumentType(isInJavaScriptFile(node)));
}
const instantiated = createSignatureInstantiation(candidate, typeArguments);
candidates[bestIndex] = instantiated;
return instantiated;
}

function getLongestCandidateIndex(candidates: Signature[], argsCount: number): number {
let maxParamsIndex = -1;
let maxParams = -1;
Expand Down Expand Up @@ -19955,6 +20019,10 @@ namespace ts {
}

function getTypeAtPosition(signature: Signature, pos: number): Type {
return tryGetTypeAtPosition(signature, pos) || anyType;
}

function tryGetTypeAtPosition(signature: Signature, pos: number): Type | undefined {
const paramCount = signature.parameters.length - (signature.hasRestParameter ? 1 : 0);
if (pos < paramCount) {
return getTypeOfParameter(signature.parameters[pos]);
Expand All @@ -19970,9 +20038,9 @@ namespace ts {
return tupleRestType;
}
}
return getIndexTypeOfType(restType, IndexKind.Number) || anyType;
return getIndexTypeOfType(restType, IndexKind.Number);
}
return anyType;
return undefined;
}

function getRestTypeAtPosition(source: Signature, pos: number): Type {
Expand Down
16 changes: 16 additions & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8090,4 +8090,20 @@ namespace ts {
Debug.assert(index !== -1);
return arr.slice(index);
}

export function minAndMax<T>(arr: ReadonlyArray<T>, getValue: (value: T) => number): { readonly min: number, readonly max: number } {
Debug.assert(arr.length !== 0);
let min = getValue(arr[0]);
let max = min;
for (let i = 1; i < arr.length; i++) {
const value = getValue(arr[i]);
if (value < min) {
min = value;
}
else if (value > max) {
max = value;
}
}
return { min, max };
}
}
4 changes: 2 additions & 2 deletions tests/cases/fourslash/automaticConstructorToggling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ edit.deleteAtCaret('constructor(val: T) { }'.length);
verify.quickInfos({
Asig: "constructor A<string>(): A<string>",
Bsig: "constructor B<string>(val: string): B<string>",
Csig: "constructor C<T>(): C<T>", // Cannot resolve signature
Csig: "constructor C<{}>(): C<{}>", // Cannot resolve signature
Dsig: "constructor D<string>(val: string): D<string>" // Cannot resolve signature
});

Expand All @@ -37,6 +37,6 @@ edit.deleteAtCaret("val: T".length);
verify.quickInfos({
Asig: "constructor A<string>(): A<string>",
Bsig: "constructor B<string>(val: string): B<string>",
Csig: "constructor C<T>(): C<T>", // Cannot resolve signature
Csig: "constructor C<{}>(): C<{}>", // Cannot resolve signature
Dsig: "constructor D<string>(): D<string>"
});
9 changes: 9 additions & 0 deletions tests/cases/fourslash/completionsCombineOverloads.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/// <reference path="fourslash.ts" />

////interface A { a: number }
////interface B { b: number }
////declare function f(a: A): void;
////declare function f(b: B): void;
////f({ /**/ });

verify.completions({ marker: "", exact: ["a", "b"] });
15 changes: 15 additions & 0 deletions tests/cases/fourslash/completionsCombineOverloads_restParameter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/// <reference path="fourslash.ts" />

////interface A { a: number }
////interface B { b: number }
////interface C { c: number }
////declare function f(a: A): void;
////declare function f(...bs: B[]): void;
////declare function f(...cs: C[]): void;
////f({ /*1*/ });
////f({ a: 1 }, { /*2*/ });

verify.completions(
{ marker: "1", exact: ["a", "b", "c"] },
{ marker: "2", exact: ["b", "c"] },
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/// <reference path="fourslash.ts" />

////interface A { a: number }
////interface B { b: number }
////declare function f(n: number): A;
////declare function f(s: string): B;
////f()./**/

verify.completions({ marker: "", exact: ["a", "b"] });
6 changes: 3 additions & 3 deletions tests/cases/fourslash/genericFunctionSignatureHelp3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@
////foo7(1, <string>(/*7*/ // signature help shows y as T

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

verify.signatureHelp({ marker: "7", text: "foo7<T>(x: number, callback: (y7: T) => number): void" });
verify.signatureHelp({ marker: "7", text: "foo7(x: number, callback: (y7: {}) => number): void" });
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@
////foo7(1, <string>(/*7*/ // signature help shows y as T

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

verify.signatureHelp({ marker: "7", text: "foo7<T>(x: number, callback: (y7: T) => number): void" })
verify.signatureHelp({ marker: "7", text: "foo7(x: number, callback: (y7: {}) => number): void" })
2 changes: 1 addition & 1 deletion tests/cases/fourslash/jsDocFunctionSignatures10.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@
////fo/**/o()

goTo.marker();
verify.quickInfoIs("function foo<T>(x: T): void", "Do some foo things");
verify.quickInfoIs("function foo<any>(x: any): void", "Do some foo things");
2 changes: 1 addition & 1 deletion tests/cases/fourslash/jsdocReturnsTag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

verify.signatureHelp({
marker: "",
text: "find<T>(l: T[], x: T): T",
text: "find(l: any[], x: any): any",
docComment: "Find an item",
tags: [
// TODO: GH#24130 (see PR #24600's commits for potential fix)
Expand Down
4 changes: 2 additions & 2 deletions tests/cases/fourslash/quickInfoCanBeTruncated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,7 @@
//// function f<T extends A>(s: T, x: Exclude<A, T>, y: string) {}
//// f("_499", /*3*/);
//// type Decomposed/*4*/ = {[K in A]: Foo[K]}
//// 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];
//// 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];
//// type DeeplyMapped/*6*/ = {[K in keyof Foo]: {[K2 in keyof Foo]: [K, K2, Foo[K], Foo[K2]]}}

goTo.marker("1");
Expand All @@ -519,7 +519,7 @@ verify.quickInfoIs(`type Less = "_1" | "_2" | "_3" | "_4" | "_5" | "_6" | "_7" |
goTo.marker("3");
verify.signatureHelp({
marker: "3",
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`
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`
});
goTo.marker("4");
verify.quickInfoIs(`type Decomposed = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/// <reference path='fourslash.ts'/>

////declare function f<T>(x: number): T;
////const x/**/ = f();

verify.quickInfoAt("", "const x: {}");
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

verify.signatureHelp(
{ marker: "1", text: "f(x: number, y: string): number" },
{ marker: "2", text: "f<T = boolean, U = string>(x: T, y: U): T" },
{ marker: "2", text: "f(x: {}, y: {}): {}" },
// too few -- fill in rest with {}
{ marker: "3", text: "f(x: number, y: {}): number" },
// too many -- ignore extra type arguments
Expand Down
2 changes: 1 addition & 1 deletion tests/cases/fourslash/signatureHelpOnTypePredicates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@

verify.signatureHelp(
{ marker: "1", text: "f1(a: any): a is number" },
{ marker: "2", text: "f2<T>(a: any): a is T" },
{ marker: "2", text: "f2(a: any): a is {}" },
{ marker: "3", text: "f3(a: any, ...b: any[]): a is number", isVariadic: true },
)
2 changes: 1 addition & 1 deletion tests/cases/fourslash/signatureHelpWithTriggers02.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ goTo.marker("1");

edit.insert("(");
verify.signatureHelp({
text: "bar<U>(x: U, y: U): U",
text: "bar(x: {}, y: {}): {}",
triggerReason: {
kind: "characterTyped",
triggerCharacter: "(",
Expand Down