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

This commit is contained in:
Andy
2018-07-11 08:54:47 -07:00
committed by GitHub
parent f66c7dbba2
commit 990d445bb6
15 changed files with 175 additions and 52 deletions

View File

@@ -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 {
@@ -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;
@@ -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;
@@ -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]);
@@ -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 {

View File

@@ -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 };
}
}

View File

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

View File

@@ -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"] });

View File

@@ -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"] },
);

View File

@@ -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"] });

View File

@@ -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" },
@@ -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" });

View File

@@ -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" },
);
@@ -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" })

View File

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

View File

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

View File

@@ -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");
@@ -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 = {

View File

@@ -0,0 +1,6 @@
/// <reference path='fourslash.ts'/>
////declare function f<T>(x: number): T;
////const x/**/ = f();
verify.quickInfoAt("", "const x: {}");

View File

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

View File

@@ -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 },
)

View File

@@ -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: "(",