diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c1b57e5def701..4863c298e2b1b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3200,7 +3200,7 @@ namespace ts { if ((symbol as TransientSymbol).syntheticLiteralTypeOrigin) { const stringValue = (symbol as TransientSymbol).syntheticLiteralTypeOrigin.value; if (!isIdentifierText(stringValue, compilerOptions.target)) { - return `"${escapeString(stringValue, CharacterCodes.doubleQuote)}"`; + return '"' + escapeString(stringValue, CharacterCodes.doubleQuote) + '"'; } } return symbolName(symbol); @@ -5568,9 +5568,9 @@ namespace ts { /** * Gets the symbolic name for a late-bound member from its type. */ - function getLateBoundNameFromType(type: LiteralType | UniqueESSymbolType) { + function getLateBoundNameFromType(type: LiteralType | UniqueESSymbolType): __String | undefined { if (type.flags & TypeFlags.UniqueESSymbol) { - return `__@${type.symbol.escapedName}@${getSymbolId(type.symbol)}` as __String; + return "__@" + type.symbol.escapedName + "@" + getSymbolId(type.symbol) as __String; } if (type.flags & TypeFlags.StringOrNumberLiteral) { return escapeLeadingUnderscores("" + (type).value); @@ -7037,13 +7037,17 @@ namespace ts { } function getRestTypeOfSignature(signature: Signature): Type { + return tryGetRestTypeOfSignature(signature) || anyType; + } + + function tryGetRestTypeOfSignature(signature: Signature): Type | undefined { if (signature.hasRestParameter) { const type = getTypeOfSymbol(lastOrUndefined(signature.parameters)); if (getObjectFlags(type) & ObjectFlags.Reference && (type).target === globalArrayType) { return (type).typeArguments[0]; } } - return anyType; + return undefined; } function getSignatureInstantiation(signature: Signature, typeArguments: Type[], isJavascript: boolean): Signature { @@ -7772,7 +7776,7 @@ namespace ts { // Add the given types to the given type set. Order is preserved, duplicates are removed, // and nested types of the given kind are flattened into the set. - function addTypesToUnion(typeSet: TypeSet, types: Type[]) { + function addTypesToUnion(typeSet: TypeSet, types: ReadonlyArray) { for (const type of types) { addTypeToUnion(typeSet, type); } @@ -7851,7 +7855,7 @@ namespace ts { // expression constructs such as array literals and the || and ?: operators). Named types can // circularly reference themselves and therefore cannot be subtype reduced during their declaration. // For example, "type Item = string | (() => Item" is a named type that circularly references itself. - function getUnionType(types: Type[], unionReduction: UnionReduction = UnionReduction.Literal, aliasSymbol?: Symbol, aliasTypeArguments?: Type[]): Type { + function getUnionType(types: ReadonlyArray, unionReduction: UnionReduction = UnionReduction.Literal, aliasSymbol?: Symbol, aliasTypeArguments?: Type[]): Type { if (types.length === 0) { return neverType; } @@ -17060,35 +17064,8 @@ 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(k: keyof T); - // f(" 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); - 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 getCandidateForOverloadFailure(node, candidates, args, !!candidatesOutArray); } return resolveErrorCall(node); @@ -17162,7 +17139,103 @@ namespace ts { } } - function getLongestCandidateIndex(candidates: Signature[], argsCount: number): number { + // 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, + 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 { + 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: ts.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 createCombinedSymbolFromTypes(sources: ReadonlyArray, types: ReadonlyArray): Symbol { + return createCombinedSymbolForOverloadFailure(sources, getUnionType(types, UnionReduction.Subtype)); + } + + function createCombinedSymbolForOverloadFailure(sources: ReadonlyArray, 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): 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(k: keyof T); + // f(" + const bestIndex = getLongestCandidateIndex(candidates, apparentArgumentCount === undefined ? args.length : apparentArgumentCount); + const candidate = candidates[bestIndex]; + + const { typeParameters } = candidate; + if (!typeParameters) { + return candidate; + } + + if (!callLikeExpressionMayHaveTypeArguments(node) || !node.typeArguments) { + // TODO: This leaks a type parameter! See GH#19854 + // Could use `callLikeExpressionMayHaveTypeArguments(node) ? node.typeArguments || emptyArray : emptyArray;` instead of `node.typeArguments`. + return candidate; + } + + const typeArguments = node.typeArguments.map(getTypeOfNode); + 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; + } + + function getLongestCandidateIndex(candidates: ReadonlyArray, argsCount: number): number { let maxParamsIndex = -1; let maxParams = -1; @@ -17815,9 +17888,13 @@ namespace ts { } function getTypeAtPosition(signature: Signature, pos: number): Type { + return tryGetTypeAtPosition(signature, pos) || anyType; + } + + function tryGetTypeAtPosition(signature: Signature, pos: number): Type | undefined { return signature.hasRestParameter ? pos < signature.parameters.length - 1 ? getTypeOfParameter(signature.parameters[pos]) : getRestTypeOfSignature(signature) : - pos < signature.parameters.length ? getTypeOfParameter(signature.parameters[pos]) : anyType; + pos < signature.parameters.length ? getTypeOfParameter(signature.parameters[pos]) : undefined; } function getTypeOfFirstParameterOfSignature(signature: Signature) { diff --git a/src/compiler/core.ts b/src/compiler/core.ts index a79a3751db67f..8c24d205bae81 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -912,6 +912,22 @@ namespace ts { return to; } + export function minAndMax(arr: ReadonlyArray, getValue: (value: T) => number): { min: number, max: number } { + Debug.assert(arr.length !== 0); + let min = getValue(arr[0]); + let max = min; + for (let i = 1; i < arr.length; i++) { + const b = getValue(arr[i]); + if (b < min) { + min = b; + } + else if (b > max) { + max = b; + } + } + return { min, max }; + } + /** * Gets the actual offset into an array for a relative offset. Negative offsets indicate a * position offset from the end of the array. diff --git a/tests/cases/fourslash/completionsCombineOverloads.ts b/tests/cases/fourslash/completionsCombineOverloads.ts new file mode 100644 index 0000000000000..f6b73a6e31eac --- /dev/null +++ b/tests/cases/fourslash/completionsCombineOverloads.ts @@ -0,0 +1,9 @@ +/// + +////interface A { a: number } +////interface B { b: number } +////declare function f(a: A): void; +////declare function f(b: B): void; +////f({ /**/ }); + +verify.completionsAt("", ["a", "b"]); diff --git a/tests/cases/fourslash/completionsCombineOverloads_restParameter.ts b/tests/cases/fourslash/completionsCombineOverloads_restParameter.ts new file mode 100644 index 0000000000000..940d4c1c56fc7 --- /dev/null +++ b/tests/cases/fourslash/completionsCombineOverloads_restParameter.ts @@ -0,0 +1,13 @@ +/// + +////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.completionsAt("1", ["a", "b", "c"]); +verify.completionsAt("2", ["b", "c"]); diff --git a/tests/cases/fourslash/completionsCombineOverloads_returnType.ts b/tests/cases/fourslash/completionsCombineOverloads_returnType.ts new file mode 100644 index 0000000000000..4b31857063fe6 --- /dev/null +++ b/tests/cases/fourslash/completionsCombineOverloads_returnType.ts @@ -0,0 +1,9 @@ +/// + +////interface A { a: number } +////interface B { b: number } +////declare function f(n: number): A; +////declare function f(s: string): B; +////f()./**/ + +verify.completionsAt("", ["a", "b"]); diff --git a/tests/cases/fourslash/quickInfo_errorSignatureFillsInTypeParameter.ts b/tests/cases/fourslash/quickInfo_errorSignatureFillsInTypeParameter.ts new file mode 100644 index 0000000000000..c2af64ebd87ab --- /dev/null +++ b/tests/cases/fourslash/quickInfo_errorSignatureFillsInTypeParameter.ts @@ -0,0 +1,6 @@ +/// + +////declare function f(x: number): T; +////const x/**/ = f(); + +verify.quickInfoAt("", "const x: T"); // TODO: GH#19854