From 8463d9e961905ab99efaca970888865477743c55 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Tue, 31 Oct 2017 13:08:46 -0700 Subject: [PATCH 1/6] In services, when overload resolution fails, create a union signature --- src/compiler/checker.ts | 129 ++++++++++++++---- src/compiler/core.ts | 16 +++ .../fourslash/completionsCombineOverloads.ts | 9 ++ ...mpletionsCombineOverloads_restParameter.ts | 13 ++ 4 files changed, 137 insertions(+), 30 deletions(-) create mode 100644 tests/cases/fourslash/completionsCombineOverloads.ts create mode 100644 tests/cases/fourslash/completionsCombineOverloads_restParameter.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 82ee2bcec77db..1bd21b3c728f5 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6682,13 +6682,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 { @@ -10430,6 +10434,11 @@ namespace ts { return symbol; } + function createCombinedSymbolWithType(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 transformTypeOfMembers(type: Type, f: (propertyType: Type) => Type) { const members = createSymbolTable(); for (const property of getPropertiesOfObjectType(type)) { @@ -16433,35 +16442,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); @@ -16535,6 +16517,89 @@ 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, + 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. + if (!hasCandidatesOutArray && candidates.length > 1 && !candidates.some(c => !!c.typeParameters)) { + return createUnionOfSignaturesForOverloadFailure(candidates); + } + + // 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 && 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; + } + + function createUnionOfSignaturesForOverloadFailure(candidates: ReadonlyArray): Signature { + const thisParameters = mapDefined(candidates, c => c.thisParameter); + let thisParameter: Symbol | undefined; + if (thisParameters.length) { + thisParameter = createCombinedSymbolWithType(thisParameters, getUnionType(thisParameters.map(getTypeOfParameter), /*subtypeReduction*/ true)); + } + + const { min: minArgumentCount, max: maxNonRestParam } = minAndMax(candidates, getNumNonRestParameters); + const hasRestParameter = candidates.some(c => c.hasRestParameter); + const hasLiteralTypes = candidates.some(c => c.hasLiteralTypes); + 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); + const types = mapDefined(candidates, candidate => tryGetTypeAtPosition(candidate, i)); + parameters.push(createCombinedSymbolWithType(symbols, getUnionType(types, /*subtypeReduction*/ true))); + } + + if (hasRestParameter) { + const symbols = mapDefined(candidates, c => c.hasRestParameter ? last(c.parameters) : undefined); + Debug.assert(symbols.length !== 0); + const type = createArrayType(getUnionType(mapDefined(candidates, tryGetRestTypeOfSignature), /*subtypeReduction*/ true)); + parameters.push(createCombinedSymbolWithType(symbols, type)); + } + + return createSignature( + candidates[0].declaration, + /*typeParameters*/ undefined, + thisParameter, + parameters, + /*resolvedReturnType*/ unknownType, + /*typePredicate*/ undefined, + minArgumentCount, + hasRestParameter, + hasLiteralTypes); + } + function getLongestCandidateIndex(candidates: Signature[], argsCount: number): number { let maxParamsIndex = -1; let maxParams = -1; @@ -17175,9 +17240,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 b954d16fd9d25..6ece22c7fc93c 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -778,6 +778,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"]); From 96c20857c0090ce2cf2f01b42c0d1e2165b218ad Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Wed, 6 Dec 2017 09:06:08 -0800 Subject: [PATCH 2/6] Code review --- src/compiler/checker.ts | 90 +++++++++++++++++++++-------------------- 1 file changed, 47 insertions(+), 43 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1bd21b3c728f5..12268d1d6f6a7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7429,7 +7429,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); } @@ -7504,7 +7504,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[], subtypeReduction?: boolean, aliasSymbol?: Symbol, aliasTypeArguments?: Type[]): Type { + function getUnionType(types: ReadonlyArray, subtypeReduction?: boolean, aliasSymbol?: Symbol, aliasTypeArguments?: Type[]): Type { if (types.length === 0) { return neverType; } @@ -10434,11 +10434,6 @@ namespace ts { return symbol; } - function createCombinedSymbolWithType(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 transformTypeOfMembers(type: Type, f: (propertyType: Type) => Type) { const members = createSymbolTable(); for (const property of getPropertiesOfObjectType(type)) { @@ -16526,49 +16521,22 @@ namespace ts { 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. - if (!hasCandidatesOutArray && candidates.length > 1 && !candidates.some(c => !!c.typeParameters)) { - return createUnionOfSignaturesForOverloadFailure(candidates); - } - - // 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 && 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 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 = createCombinedSymbolWithType(thisParameters, getUnionType(thisParameters.map(getTypeOfParameter), /*subtypeReduction*/ true)); + thisParameter = createCombinedSymbolFromTypes(thisParameters, thisParameters.map(getTypeOfParameter)); } - const { min: minArgumentCount, max: maxNonRestParam } = minAndMax(candidates, getNumNonRestParameters); + const { min: minArgumentCount, max: maxNonRestParam } = minAndMax(candidates, getNumNonRestParameters); const hasRestParameter = candidates.some(c => c.hasRestParameter); const hasLiteralTypes = candidates.some(c => c.hasLiteralTypes); const parameters: ts.Symbol[] = []; @@ -16577,15 +16545,14 @@ namespace ts { i < parameters.length - 1 ? parameters[i] : last(parameters) : i < parameters.length ? parameters[i] : undefined); Debug.assert(symbols.length !== 0); - const types = mapDefined(candidates, candidate => tryGetTypeAtPosition(candidate, i)); - parameters.push(createCombinedSymbolWithType(symbols, getUnionType(types, /*subtypeReduction*/ true))); + parameters.push(createCombinedSymbolFromTypes(symbols, mapDefined(candidates, candidate => tryGetTypeAtPosition(candidate, i)))); } if (hasRestParameter) { const symbols = mapDefined(candidates, c => c.hasRestParameter ? last(c.parameters) : undefined); Debug.assert(symbols.length !== 0); const type = createArrayType(getUnionType(mapDefined(candidates, tryGetRestTypeOfSignature), /*subtypeReduction*/ true)); - parameters.push(createCombinedSymbolWithType(symbols, type)); + parameters.push(createCombinedSymbolForOverloadFailure(symbols, type)); } return createSignature( @@ -16600,7 +16567,44 @@ namespace ts { hasLiteralTypes); } - function getLongestCandidateIndex(candidates: Signature[], argsCount: number): number { + function createCombinedSymbolFromTypes(sources: ReadonlyArray, types: ReadonlyArray): Symbol { + return createCombinedSymbolForOverloadFailure(sources, getUnionType(types, /*subtypeReduction*/ true)); + } + + 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 && 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; + } + + function getLongestCandidateIndex(candidates: ReadonlyArray, argsCount: number): number { let maxParamsIndex = -1; let maxParams = -1; From c8f70c4a3ef98ab50a3f208c280a5f550ed8555f Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Wed, 6 Dec 2017 09:20:39 -0800 Subject: [PATCH 3/6] Make a bit neater --- src/compiler/checker.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 48fb026b7cafd..f9b687126188e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -17128,8 +17128,6 @@ namespace ts { } const { min: minArgumentCount, max: maxNonRestParam } = minAndMax(candidates, getNumNonRestParameters); - const hasRestParameter = candidates.some(c => c.hasRestParameter); - const hasLiteralTypes = candidates.some(c => c.hasLiteralTypes); const parameters: ts.Symbol[] = []; for (let i = 0; i < maxNonRestParam; i++) { const symbols = mapDefined(candidates, ({ parameters, hasRestParameter }) => hasRestParameter ? @@ -17139,23 +17137,23 @@ namespace ts { 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 symbols = mapDefined(candidates, c => c.hasRestParameter ? last(c.parameters) : undefined); - Debug.assert(symbols.length !== 0); const type = createArrayType(getUnionType(mapDefined(candidates, tryGetRestTypeOfSignature), /*subtypeReduction*/ true)); - parameters.push(createCombinedSymbolForOverloadFailure(symbols, type)); + parameters.push(createCombinedSymbolForOverloadFailure(restParameterSymbols, type)); } return createSignature( candidates[0].declaration, - /*typeParameters*/ undefined, + /*typeParameters*/ undefined, // Before calling this we tested for `!candidates.some(c => !!c.typeParameters)`. thisParameter, parameters, /*resolvedReturnType*/ unknownType, /*typePredicate*/ undefined, minArgumentCount, hasRestParameter, - hasLiteralTypes); + /*hasLiteralTypes*/ candidates.some(c => c.hasLiteralTypes)); } function createCombinedSymbolFromTypes(sources: ReadonlyArray, types: ReadonlyArray): Symbol { From b1d6817e8505d0eb66b6ffab878093790bb0755d Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Wed, 6 Dec 2017 09:37:38 -0800 Subject: [PATCH 4/6] Use intersection of return types --- src/compiler/checker.ts | 2 +- .../fourslash/completionsCombineOverloads_returnType.ts | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 tests/cases/fourslash/completionsCombineOverloads_returnType.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f9b687126188e..52438fa65ab87 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -17149,7 +17149,7 @@ namespace ts { /*typeParameters*/ undefined, // Before calling this we tested for `!candidates.some(c => !!c.typeParameters)`. thisParameter, parameters, - /*resolvedReturnType*/ unknownType, + /*resolvedReturnType*/ getIntersectionType(candidates.map(getReturnTypeOfSignature)), /*typePredicate*/ undefined, minArgumentCount, hasRestParameter, 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"]); From e91568c4d3e6fd9d486238e150aa7cb74f4adbad Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Wed, 6 Dec 2017 10:38:49 -0800 Subject: [PATCH 5/6] Always fill in type arguments in pickLongestCandidateSignature (Fixes #19854) --- src/compiler/checker.ts | 31 ++++++++++--------- src/harness/harness.ts | 8 +++-- ...Info_errorSignatureFillsInTypeParameter.ts | 6 ++++ 3 files changed, 28 insertions(+), 17 deletions(-) create mode 100644 tests/cases/fourslash/quickInfo_errorSignatureFillsInTypeParameter.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 52438fa65ab87..5d596d1d58c39 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3199,7 +3199,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); @@ -5562,9 +5562,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); @@ -17176,21 +17176,22 @@ namespace ts { 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))); - } + if (!typeParameters) { + return candidate; + } - const instantiated = createSignatureInstantiation(candidate, typeArguments); - candidates[bestIndex] = instantiated; - return instantiated; + const typeArgumentNodes: ReadonlyArray = callLikeExpressionMayHaveTypeArguments(node) ? node.typeArguments || emptyArray : emptyArray; + const typeArguments = typeArgumentNodes.map(getTypeOfNode); + while (typeArguments.length > typeParameters.length) { + typeArguments.pop(); + } + while (typeArguments.length < typeParameters.length) { + typeArguments.push(getDefaultTypeArgumentType(isInJavaScriptFile(node))); } - return candidate; + const instantiated = createSignatureInstantiation(candidate, typeArguments); + candidates[bestIndex] = instantiated; + return instantiated; } function getLongestCandidateIndex(candidates: ReadonlyArray, argsCount: number): number { diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 0b361f8db389b..33592d3dae9e9 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -37,7 +37,7 @@ namespace assert { assert(!expr, msg); } export function equal(a: T, b: T, msg = "Expected values to be equal."): void { - assert(a === b, msg); + assert(a === b, () => `${msg} Expected:\n${stringify(a)}\nActual:\n${stringify(b)}`); } export function notEqual(a: T, b: T, msg = "Expected values to not be equal."): void { assert(a !== b, msg); @@ -49,7 +49,7 @@ namespace assert { assert(x === undefined, msg); } export function deepEqual(a: T, b: T, msg?: string): void { - assert(isDeepEqual(a, b), msg || (() => `Expected values to be deeply equal:\nExpected:\n${JSON.stringify(a, undefined, 4)}\nActual:\n${JSON.stringify(b, undefined, 4)}`)); + assert(isDeepEqual(a, b), msg || (() => `Expected values to be deeply equal:\nExpected:\n${stringify(a)}\nActual:\n${stringify(b)}`)); } export function lengthOf(a: ReadonlyArray<{}>, length: number, msg = "Expected length to match."): void { assert(a.length === length, msg); @@ -76,6 +76,10 @@ namespace assert { const bKeys = Object.keys(b).sort(); return aKeys.length === bKeys.length && aKeys.every((key, i) => bKeys[i] === key && isDeepEqual((a as any)[key], (b as any)[key])); } + + function stringify(value: {} | null | undefined): string { + return JSON.stringify(value, undefined, 4); + } } declare var __dirname: string; // Node-specific var global: NodeJS.Global = Function("return this").call(undefined); diff --git a/tests/cases/fourslash/quickInfo_errorSignatureFillsInTypeParameter.ts b/tests/cases/fourslash/quickInfo_errorSignatureFillsInTypeParameter.ts new file mode 100644 index 0000000000000..72a81a400f32f --- /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: {}"); From c1dd868305c9c235afe0b2052406106abe01c341 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Mon, 18 Dec 2017 15:05:47 -0800 Subject: [PATCH 6/6] Disable GH#19854 fix because it has impacts many test cases --- src/compiler/checker.ts | 9 +++++++-- .../quickInfo_errorSignatureFillsInTypeParameter.ts | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 73d3831710a64..4863c298e2b1b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -17216,8 +17216,13 @@ namespace ts { return candidate; } - const typeArgumentNodes: ReadonlyArray = callLikeExpressionMayHaveTypeArguments(node) ? node.typeArguments || emptyArray : emptyArray; - const typeArguments = typeArgumentNodes.map(getTypeOfNode); + 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(); } diff --git a/tests/cases/fourslash/quickInfo_errorSignatureFillsInTypeParameter.ts b/tests/cases/fourslash/quickInfo_errorSignatureFillsInTypeParameter.ts index 72a81a400f32f..c2af64ebd87ab 100644 --- a/tests/cases/fourslash/quickInfo_errorSignatureFillsInTypeParameter.ts +++ b/tests/cases/fourslash/quickInfo_errorSignatureFillsInTypeParameter.ts @@ -3,4 +3,4 @@ ////declare function f(x: number): T; ////const x/**/ = f(); -verify.quickInfoAt("", "const x: {}"); +verify.quickInfoAt("", "const x: T"); // TODO: GH#19854