diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2ca07e30f287b..c5d731c96aa94 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13586,23 +13586,66 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return result; } + function getLocalExpandedLabels(restType: TupleTypeReference, restDeclaration: Declaration | undefined) { + if (!restDeclaration) { + return; + } + Debug.assertNode(restDeclaration, isParameter); + if (restDeclaration.name.kind !== SyntaxKind.ArrayBindingPattern) { + return; + } + const nameElements = restDeclaration.name.elements; + const elementFlags = restType.target.elementFlags; + let structuresMatch = true; + return map(elementFlags, (elementFlag, i) => { + if (!structuresMatch || i >= nameElements.length) { + return undefined; + } + + const nameElement = nameElements[i]; + + if (elementFlag & ElementFlags.Variable) { + // they definitely won't match at the next iteration + // it's not valid to have a non-dotDotDot element after a dotDotDot element + structuresMatch = false; + if (nameElement.kind === SyntaxKind.BindingElement && nameElement.name.kind === SyntaxKind.Identifier && nameElement.dotDotDotToken) { + return nameElement.name.escapedText; + } + return undefined; + } + if (nameElement.kind !== SyntaxKind.BindingElement) { + return undefined; + } + if (nameElement.dotDotDotToken) { + structuresMatch = false; + return undefined; + } + if (nameElement.kind === SyntaxKind.BindingElement && nameElement.name.kind === SyntaxKind.Identifier) { + return nameElement.name.escapedText; + } + return undefined; + }); + } + function getExpandedParameters(sig: Signature, skipUnionExpanding?: boolean): readonly (readonly Symbol[])[] { if (signatureHasRestParameter(sig)) { const restIndex = sig.parameters.length - 1; const restName = sig.parameters[restIndex].escapedName; const restType = getTypeOfSymbol(sig.parameters[restIndex]); + const restDeclaration = sig.parameters[restIndex].valueDeclaration; + if (isTupleType(restType)) { - return [expandSignatureParametersWithTupleMembers(restType, restIndex, restName)]; + return [expandSignatureParametersWithTupleMembers(restType, restIndex, restName, getLocalExpandedLabels(restType, restDeclaration))]; } else if (!skipUnionExpanding && restType.flags & TypeFlags.Union && every((restType as UnionType).types, isTupleType)) { - return map((restType as UnionType).types, t => expandSignatureParametersWithTupleMembers(t as TupleTypeReference, restIndex, restName)); + return map((restType as UnionType).types, t => expandSignatureParametersWithTupleMembers(t as TupleTypeReference, restIndex, restName, getLocalExpandedLabels(t as TupleTypeReference, restDeclaration))); } } return [sig.parameters]; - function expandSignatureParametersWithTupleMembers(restType: TupleTypeReference, restIndex: number, restName: __String) { + function expandSignatureParametersWithTupleMembers(restType: TupleTypeReference, restIndex: number, restName: __String, localLabels: (__String | undefined)[] | undefined) { const elementTypes = getTypeArguments(restType); - const associatedNames = getUniqAssociatedNamesFromTupleType(restType, restName); + const associatedNames = getUniqAssociatedNamesFromTupleType(restType, restName, localLabels); const restParams = map(elementTypes, (t, i) => { // Lookup the label from the individual tuple passed in before falling back to the signature `rest` parameter name const name = associatedNames && associatedNames[i] ? associatedNames[i] : @@ -13617,10 +13660,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return concatenate(sig.parameters.slice(0, restIndex), restParams); } - function getUniqAssociatedNamesFromTupleType(type: TupleTypeReference, restName: __String) { + function getUniqAssociatedNamesFromTupleType(type: TupleTypeReference, restName: __String, localLabels: (__String | undefined)[] | undefined) { const associatedNamesMap = new Map<__String, number>(); return map(type.target.labeledElementDeclarations, (labeledElement, i) => { - const name = getTupleElementLabel(labeledElement, i, restName); + const name = getTupleElementLabel(labeledElement, i, restName, localLabels?.[i]); const prevCounter = associatedNamesMap.get(name); if (prevCounter === undefined) { associatedNamesMap.set(name, 1); @@ -37267,8 +37310,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function getTupleElementLabel(d: ParameterDeclaration | NamedTupleMember): __String; - function getTupleElementLabel(d: ParameterDeclaration | NamedTupleMember | undefined, index: number, restParameterName?: __String): __String; - function getTupleElementLabel(d: ParameterDeclaration | NamedTupleMember | undefined, index?: number, restParameterName = "arg" as __String) { + function getTupleElementLabel(d: ParameterDeclaration | NamedTupleMember | undefined, index: number, restParameterName?: __String, localLabel?: __String): __String; + function getTupleElementLabel(d: ParameterDeclaration | NamedTupleMember | undefined, index?: number, restParameterName = "arg" as __String, localLabel?: __String) { + if (localLabel) { + return localLabel; + } if (!d) { return `${restParameterName}_${index}` as __String; } diff --git a/tests/baselines/reference/restParameterWithBindingPattern3.types b/tests/baselines/reference/restParameterWithBindingPattern3.types index 4f8e4184fd7c5..6c78bbeae904b 100644 --- a/tests/baselines/reference/restParameterWithBindingPattern3.types +++ b/tests/baselines/reference/restParameterWithBindingPattern3.types @@ -32,8 +32,8 @@ function c(...{0: a, length, 3: d}: [boolean, string, number]) { } > : ^^^^^^^^^ function d(...[a, , , d]: [boolean, string, number]) { } ->d : (__0_0: boolean, __0_1: string, __0_2: number) => void -> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>d : (a: boolean, __0_1: string, __0_2: number) => void +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >a : boolean > : ^^^^^^^ > : undefined diff --git a/tests/cases/fourslash/signatureHelpExpandedRestTuplesLocalLabels1.ts b/tests/cases/fourslash/signatureHelpExpandedRestTuplesLocalLabels1.ts new file mode 100644 index 0000000000000..f6b080d13882e --- /dev/null +++ b/tests/cases/fourslash/signatureHelpExpandedRestTuplesLocalLabels1.ts @@ -0,0 +1,258 @@ +/// + +//// interface AppleInfo { +//// color: "green" | "red"; +//// } +//// +//// interface BananaInfo { +//// curvature: number; +//// } +//// +//// type FruitAndInfo1 = ["apple", AppleInfo] | ["banana", BananaInfo]; +//// +//// function logFruitTuple1(...[fruit, info]: FruitAndInfo1) {} +//// logFruitTuple1(/*1*/); +//// +//// function logFruitTuple2(...[, info]: FruitAndInfo1) {} +//// logFruitTuple2(/*2*/); +//// logFruitTuple2("apple", /*3*/); +//// +//// function logFruitTuple3(...[fruit, ...rest]: FruitAndInfo1) {} +//// logFruitTuple3(/*4*/); +//// logFruitTuple3("apple", /*5*/); + +//// function logFruitTuple4(...[fruit, ...[info]]: FruitAndInfo1) {} +//// logFruitTuple4(/*6*/); +//// logFruitTuple4("apple", /*7*/); +//// +//// type FruitAndInfo2 = ["apple", ...AppleInfo[]] | ["banana", ...BananaInfo[]]; +//// +//// function logFruitTuple5(...[fruit, firstInfo]: FruitAndInfo2) {} +//// logFruitTuple5(/*8*/); +//// logFruitTuple5("apple", /*9*/); +//// +//// function logFruitTuple6(...[fruit, ...fruitInfo]: FruitAndInfo2) {} +//// logFruitTuple6(/*10*/); +//// logFruitTuple6("apple", /*11*/); +//// +//// type FruitAndInfo3 = ["apple", ...AppleInfo[], number] | ["banana", ...BananaInfo[], number]; +//// +//// function logFruitTuple7(...[fruit, fruitInfoOrNumber, secondFruitInfoOrNumber]: FruitAndInfo3) {} +//// logFruitTuple7(/*12*/); +//// logFruitTuple7("apple", /*13*/); +//// logFruitTuple7("apple", { color: "red" }, /*14*/); +//// +//// function logFruitTuple8(...[fruit, , secondFruitInfoOrNumber]: FruitAndInfo3) {} +//// logFruitTuple8(/*15*/); +//// logFruitTuple8("apple", /*16*/); +//// logFruitTuple8("apple", { color: "red" }, /*17*/); +//// +//// function logFruitTuple9(...[...[fruit, fruitInfoOrNumber, secondFruitInfoOrNumber]]: FruitAndInfo3) {} +//// logFruitTuple9(/*18*/); +//// logFruitTuple9("apple", /*19*/); +//// logFruitTuple9("apple", { color: "red" }, /*20*/); + +//// function withPair(...[first, second]: [number, named: string]) {} +//// withPair(/*21*/); +//// withPair(101, /*22*/); + +verify.signatureHelp( + { + marker: "1", + text: `logFruitTuple1(fruit: "apple", info: AppleInfo): void`, + overloadsCount: 2, + parameterCount: 2, + parameterName: "fruit", + parameterSpan: `fruit: "apple"`, + isVariadic: false, + }, + { + marker: "2", + text: `logFruitTuple2(__0_0: "apple", info: AppleInfo): void`, + overloadsCount: 2, + parameterCount: 2, + parameterName: "__0_0", + parameterSpan: `__0_0: "apple"`, + isVariadic: false, + }, + { + marker: "3", + text: `logFruitTuple2(__0_0: "apple", info: AppleInfo): void`, + overloadsCount: 2, + parameterCount: 2, + parameterName: "info", + parameterSpan: "info: AppleInfo", + isVariadic: false, + }, + { + marker: "4", + text: `logFruitTuple3(fruit: "apple", __0_1: AppleInfo): void`, + overloadsCount: 2, + parameterCount: 2, + parameterName: "fruit", + parameterSpan: `fruit: "apple"`, + isVariadic: false, + }, + { + marker: "5", + text: `logFruitTuple3(fruit: "apple", __0_1: AppleInfo): void`, + overloadsCount: 2, + parameterCount: 2, + parameterName: "__0_1", + parameterSpan: "__0_1: AppleInfo", + isVariadic: false, + }, + { + marker: "6", + text: `logFruitTuple4(fruit: "apple", __0_1: AppleInfo): void`, + overloadsCount: 2, + parameterCount: 2, + parameterName: "fruit", + parameterSpan: `fruit: "apple"`, + isVariadic: false, + }, + { + marker: "7", + text: `logFruitTuple4(fruit: "apple", __0_1: AppleInfo): void`, + overloadsCount: 2, + parameterCount: 2, + parameterName: "__0_1", + parameterSpan: "__0_1: AppleInfo", + isVariadic: false, + }, + { + marker: "8", + text: `logFruitTuple5(fruit: "apple", ...__0_1: AppleInfo[]): void`, + overloadsCount: 2, + parameterCount: 2, + parameterName: "fruit", + parameterSpan: `fruit: "apple"`, + isVariadic: true, + }, + { + marker: "9", + text: `logFruitTuple5(fruit: "apple", ...__0_1: AppleInfo[]): void`, + overloadsCount: 2, + parameterCount: 2, + parameterName: "__0_1", + parameterSpan: "...__0_1: AppleInfo[]", + isVariadic: true, + }, + { + marker: "10", + text: `logFruitTuple6(fruit: "apple", ...fruitInfo: AppleInfo[]): void`, + overloadsCount: 2, + parameterCount: 2, + parameterName: "fruit", + parameterSpan: `fruit: "apple"`, + isVariadic: true, + }, + { + marker: "11", + text: `logFruitTuple6(fruit: "apple", ...fruitInfo: AppleInfo[]): void`, + overloadsCount: 2, + parameterCount: 2, + parameterName: "fruitInfo", + parameterSpan: "...fruitInfo: AppleInfo[]", + isVariadic: true, + }, + { + marker: "12", + text: `logFruitTuple7(fruit: "apple", ...__0_1: AppleInfo[], __0_2: number): void`, + overloadsCount: 2, + parameterCount: 3, + parameterName: "fruit", + parameterSpan: `fruit: "apple"`, + isVariadic: false, + }, + { + marker: "13", + text: `logFruitTuple7(fruit: "apple", ...__0_1: AppleInfo[], __0_2: number): void`, + overloadsCount: 2, + parameterCount: 3, + parameterName: "__0_1", + parameterSpan: `...__0_1: AppleInfo[]`, + isVariadic: false, + }, + { + marker: "14", + text: `logFruitTuple7(fruit: "apple", ...__0_1: AppleInfo[], __0_2: number): void`, + overloadsCount: 2, + parameterCount: 3, + parameterName: "__0_2", + parameterSpan: `__0_2: number`, + isVariadic: false, + }, + { + marker: "15", + text: `logFruitTuple8(fruit: "apple", ...__0_1: AppleInfo[], __0_2: number): void`, + overloadsCount: 2, + parameterCount: 3, + parameterName: "fruit", + parameterSpan: `fruit: "apple"`, + isVariadic: false, + }, + { + marker: "16", + text: `logFruitTuple8(fruit: "apple", ...__0_1: AppleInfo[], __0_2: number): void`, + overloadsCount: 2, + parameterCount: 3, + parameterName: "__0_1", + parameterSpan: `...__0_1: AppleInfo[]`, + isVariadic: false, + }, + { + marker: "17", + text: `logFruitTuple8(fruit: "apple", ...__0_1: AppleInfo[], __0_2: number): void`, + overloadsCount: 2, + parameterCount: 3, + parameterName: "__0_2", + parameterSpan: `__0_2: number`, + isVariadic: false, + }, + { + marker: "18", + text: `logFruitTuple9(__0_0: "apple", ...__0_1: AppleInfo[], __0_2: number): void`, + overloadsCount: 2, + parameterCount: 3, + parameterName: "__0_0", + parameterSpan: `__0_0: "apple"`, + isVariadic: false, + }, + { + marker: "19", + text: `logFruitTuple9(__0_0: "apple", ...__0_1: AppleInfo[], __0_2: number): void`, + overloadsCount: 2, + parameterCount: 3, + parameterName: "__0_1", + parameterSpan: `...__0_1: AppleInfo[]`, + isVariadic: false, + }, + { + marker: "20", + text: `logFruitTuple9(__0_0: "apple", ...__0_1: AppleInfo[], __0_2: number): void`, + overloadsCount: 2, + parameterCount: 3, + parameterName: "__0_2", + parameterSpan: `__0_2: number`, + isVariadic: false, + }, + { + marker: "21", + text: `withPair(first: number, second: string): void`, + overloadsCount: 1, + parameterCount: 2, + parameterName: "first", + parameterSpan: `first: number`, + isVariadic: false, + }, + { + marker: "22", + text: `withPair(first: number, second: string): void`, + overloadsCount: 1, + parameterCount: 2, + parameterName: "second", + parameterSpan: `second: string`, + isVariadic: false, + }, +);