mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-06-29 19:42:39 -05:00
In services, when overload resolution fails, create a union signature (2) (#25100)
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>"
|
||||
});
|
||||
|
||||
9
tests/cases/fourslash/completionsCombineOverloads.ts
Normal file
9
tests/cases/fourslash/completionsCombineOverloads.ts
Normal 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"] });
|
||||
@@ -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"] },
|
||||
);
|
||||
@@ -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"] });
|
||||
@@ -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" });
|
||||
|
||||
@@ -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" })
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
/// <reference path='fourslash.ts'/>
|
||||
|
||||
////declare function f<T>(x: number): T;
|
||||
////const x/**/ = f();
|
||||
|
||||
verify.quickInfoAt("", "const x: {}");
|
||||
@@ -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
|
||||
|
||||
@@ -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 },
|
||||
)
|
||||
|
||||
@@ -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: "(",
|
||||
|
||||
Reference in New Issue
Block a user