From 6a10bd1f980a5bdfe8a3e52b3ba0a4ed254f8b6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Sun, 3 Mar 2024 11:02:01 +0100 Subject: [PATCH 1/9] Use destructured names as fallback labels when expanding parameters --- src/compiler/checker.ts | 63 ++++++++-- .../restParameterWithBindingPattern3.types | 2 +- ...reHelpExpandedRestTuplesFallbackLabels1.ts | 116 ++++++++++++++++++ 3 files changed, 171 insertions(+), 10 deletions(-) create mode 100644 tests/cases/fourslash/signatureHelpExpandedRestTuplesFallbackLabels1.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a3dfbe44a6b54..ee58bf4f51d71 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13255,23 +13255,68 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return result; } + function getFallbackExpandedLabels(restType: TupleTypeReference, restDeclaration: Declaration | undefined) { + if (!restDeclaration) { + return; + } + Debug.assert(isParameter(restDeclaration)); + if (restDeclaration.name.kind !== SyntaxKind.ArrayBindingPattern) { + return; + } + const nameElements = restDeclaration.name.elements; + const elementFlags = restType.target.elementFlags; + const length = Math.min(nameElements.length, elementFlags.length); + const result: (__String | undefined)[] = []; + for (let i = 0; i < length; i++) { + const nameElement = nameElements[i]; + + if (elementFlags[i] & ElementFlags.Variable) { + if (nameElement.kind === SyntaxKind.BindingElement && nameElement.dotDotDotToken && nameElement.name.kind === SyntaxKind.Identifier) { + result.push(nameElement.name.escapedText); + } + break; + } + if (nameElement.kind !== SyntaxKind.BindingElement) { + result.push(undefined); + continue; + } + if (nameElement.dotDotDotToken) { + break; + } + if (nameElement.name.kind !== SyntaxKind.Identifier) { + result.push(undefined); + continue; + } + result.push(nameElement.name.escapedText); + } + // fill the rest of the labels with undefined + // it makes it easier to consume the fallback labels later + // since they will have the same length as the labeledElementDeclarations etc + for (let i = result.length; i < elementFlags.length; i++) { + result.push(undefined); + } + return result; + } + 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, getFallbackExpandedLabels(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, getFallbackExpandedLabels(t as TupleTypeReference, restDeclaration))); } } return [sig.parameters]; - function expandSignatureParametersWithTupleMembers(restType: TupleTypeReference, restIndex: number, restName: __String) { + function expandSignatureParametersWithTupleMembers(restType: TupleTypeReference, restIndex: number, restName: __String, fallbackLabels: (__String | undefined)[] | undefined) { const elementTypes = getTypeArguments(restType); - const associatedNames = getUniqAssociatedNamesFromTupleType(restType, restName); + const associatedNames = getUniqAssociatedNamesFromTupleType(restType, restName, fallbackLabels); 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] : @@ -13286,10 +13331,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, fallbackLabels: (__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, fallbackLabels?.[i]); const prevCounter = associatedNamesMap.get(name); if (prevCounter === undefined) { associatedNamesMap.set(name, 1); @@ -36298,10 +36343,10 @@ 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, fallbackLabel?: __String): __String; + function getTupleElementLabel(d: ParameterDeclaration | NamedTupleMember | undefined, index?: number, restParameterName = "arg" as __String, fallbackLabel?: __String) { if (!d) { - return `${restParameterName}_${index}` as __String; + return fallbackLabel ?? `${restParameterName}_${index}` as __String; } Debug.assert(isIdentifier(d.name)); // Parameter declarations could be binding patterns, but we only allow identifier names return d.name.escapedText; diff --git a/tests/baselines/reference/restParameterWithBindingPattern3.types b/tests/baselines/reference/restParameterWithBindingPattern3.types index 8911cd6931c30..97681822dbc32 100644 --- a/tests/baselines/reference/restParameterWithBindingPattern3.types +++ b/tests/baselines/reference/restParameterWithBindingPattern3.types @@ -20,7 +20,7 @@ function c(...{0: a, length, 3: d}: [boolean, string, number]) { } >d : undefined 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 > : undefined diff --git a/tests/cases/fourslash/signatureHelpExpandedRestTuplesFallbackLabels1.ts b/tests/cases/fourslash/signatureHelpExpandedRestTuplesFallbackLabels1.ts new file mode 100644 index 0000000000000..e5871886c475c --- /dev/null +++ b/tests/cases/fourslash/signatureHelpExpandedRestTuplesFallbackLabels1.ts @@ -0,0 +1,116 @@ +/// + +//// 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*/); +//// +//// type FruitAndInfo2 = ["apple", ...AppleInfo[]] | ["banana", ...BananaInfo[]]; +//// +//// function logFruitTuple4(...[fruit, firstInfo]: FruitAndInfo2) {} +//// logFruitTuple4(/*6*/); +//// logFruitTuple4("apple", /*7*/); +//// +//// function logFruitTuple5(...[fruit, ...fruitInfo]: FruitAndInfo2) {} +//// logFruitTuple5(/*8*/); +//// logFruitTuple5("apple", /*9*/); + +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: true, + }, + { + marker: "7", + text: `logFruitTuple4(fruit: "apple", ...__0_1: AppleInfo[]): void`, + overloadsCount: 2, + parameterCount: 2, + parameterName: "__0_1", + parameterSpan: "...__0_1: AppleInfo[]", + isVariadic: true, + }, + { + marker: "8", + text: `logFruitTuple5(fruit: "apple", ...fruitInfo: AppleInfo[]): void`, + overloadsCount: 2, + parameterCount: 2, + parameterName: "fruit", + parameterSpan: `fruit: "apple"`, + isVariadic: true, + }, + { + marker: "9", + text: `logFruitTuple5(fruit: "apple", ...fruitInfo: AppleInfo[]): void`, + overloadsCount: 2, + parameterCount: 2, + parameterName: "fruitInfo", + parameterSpan: "...fruitInfo: AppleInfo[]", + isVariadic: true, + }, +); From 4a034206843471e702a989d7bd13a37ce07d7bd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Wed, 20 Mar 2024 23:06:37 +0100 Subject: [PATCH 2/9] use `Debug.assertNode` --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 87b1ed0e1bfda..d9035da20efa4 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13353,7 +13353,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (!restDeclaration) { return; } - Debug.assert(isParameter(restDeclaration)); + Debug.assertNode(restDeclaration, isParameter); if (restDeclaration.name.kind !== SyntaxKind.ArrayBindingPattern) { return; } From 1483d54f70ee529021e46f32a767df4ffaece143 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Wed, 20 Mar 2024 23:28:24 +0100 Subject: [PATCH 3/9] just preallocate `result` array --- src/compiler/checker.ts | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d9035da20efa4..9e89fdc5fb3fd 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13359,35 +13359,27 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } const nameElements = restDeclaration.name.elements; const elementFlags = restType.target.elementFlags; + const result: (__String | undefined)[] = arrayOf(elementFlags.length, () => undefined); const length = Math.min(nameElements.length, elementFlags.length); - const result: (__String | undefined)[] = []; for (let i = 0; i < length; i++) { const nameElement = nameElements[i]; if (elementFlags[i] & ElementFlags.Variable) { if (nameElement.kind === SyntaxKind.BindingElement && nameElement.dotDotDotToken && nameElement.name.kind === SyntaxKind.Identifier) { - result.push(nameElement.name.escapedText); + result[i] = nameElement.name.escapedText; } break; } if (nameElement.kind !== SyntaxKind.BindingElement) { - result.push(undefined); continue; } if (nameElement.dotDotDotToken) { break; } if (nameElement.name.kind !== SyntaxKind.Identifier) { - result.push(undefined); continue; } - result.push(nameElement.name.escapedText); - } - // fill the rest of the labels with undefined - // it makes it easier to consume the fallback labels later - // since they will have the same length as the labeledElementDeclarations etc - for (let i = result.length; i < elementFlags.length; i++) { - result.push(undefined); + result[i] = nameElement.name.escapedText; } return result; } From 34d72ac95a4d3502f921303471c2d2c3a0df089b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Tue, 26 Mar 2024 09:43:28 +0100 Subject: [PATCH 4/9] tweak code --- src/compiler/checker.ts | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9e89fdc5fb3fd..4b31ba1ec682c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13359,29 +13359,31 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } const nameElements = restDeclaration.name.elements; const elementFlags = restType.target.elementFlags; - const result: (__String | undefined)[] = arrayOf(elementFlags.length, () => undefined); - const length = Math.min(nameElements.length, elementFlags.length); - for (let i = 0; i < length; i++) { + let seenDotDotDotish = false; + return map(elementFlags, (elementFlag, i) => { + if (seenDotDotDotish || i >= nameElements.length) return undefined; + const nameElement = nameElements[i]; - if (elementFlags[i] & ElementFlags.Variable) { - if (nameElement.kind === SyntaxKind.BindingElement && nameElement.dotDotDotToken && nameElement.name.kind === SyntaxKind.Identifier) { - result[i] = nameElement.name.escapedText; + if (elementFlag & ElementFlags.Variable) { + seenDotDotDotish = true; + if (nameElement.kind === SyntaxKind.BindingElement && nameElement.name.kind === SyntaxKind.Identifier && nameElement.dotDotDotToken) { + return nameElement.name.escapedText; } - break; + return undefined; } if (nameElement.kind !== SyntaxKind.BindingElement) { - continue; + return undefined; } if (nameElement.dotDotDotToken) { - break; + seenDotDotDotish = true; + return undefined; } - if (nameElement.name.kind !== SyntaxKind.Identifier) { - continue; + if (nameElement.kind === SyntaxKind.BindingElement && nameElement.name.kind === SyntaxKind.Identifier) { + return nameElement.name.escapedText; } - result[i] = nameElement.name.escapedText; - } - return result; + return undefined; + }); } function getExpandedParameters(sig: Signature, skipUnionExpanding?: boolean): readonly (readonly Symbol[])[] { From 431e0048a38416c078b6cb9ce85b633274c5831a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Tue, 26 Mar 2024 09:49:55 +0100 Subject: [PATCH 5/9] rename variable --- src/compiler/checker.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4b31ba1ec682c..1364cd232e7b5 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13359,14 +13359,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } const nameElements = restDeclaration.name.elements; const elementFlags = restType.target.elementFlags; - let seenDotDotDotish = false; + let structuresMatch = true; return map(elementFlags, (elementFlag, i) => { - if (seenDotDotDotish || i >= nameElements.length) return undefined; + if (!structuresMatch || i >= nameElements.length) { + return undefined; + } const nameElement = nameElements[i]; if (elementFlag & ElementFlags.Variable) { - seenDotDotDotish = true; + // they definitely won't match at the next iteration + // it's impossible 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; } @@ -13376,7 +13380,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return undefined; } if (nameElement.dotDotDotToken) { - seenDotDotDotish = true; + structuresMatch = false; return undefined; } if (nameElement.kind === SyntaxKind.BindingElement && nameElement.name.kind === SyntaxKind.Identifier) { From e474c9a4adc2274d13ede2109a4b952c79ae6fcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Tue, 26 Mar 2024 10:14:42 +0100 Subject: [PATCH 6/9] add extra tests --- src/compiler/checker.ts | 2 +- ...reHelpExpandedRestTuplesFallbackLabels1.ts | 34 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1364cd232e7b5..c07e0e4440f9a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13369,7 +13369,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (elementFlag & ElementFlags.Variable) { // they definitely won't match at the next iteration - // it's impossible to have a non-dotDotDot element after a dotDotDot element + // 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; diff --git a/tests/cases/fourslash/signatureHelpExpandedRestTuplesFallbackLabels1.ts b/tests/cases/fourslash/signatureHelpExpandedRestTuplesFallbackLabels1.ts index e5871886c475c..d0005bdac0ae3 100644 --- a/tests/cases/fourslash/signatureHelpExpandedRestTuplesFallbackLabels1.ts +++ b/tests/cases/fourslash/signatureHelpExpandedRestTuplesFallbackLabels1.ts @@ -30,6 +30,13 @@ //// function logFruitTuple5(...[fruit, ...fruitInfo]: FruitAndInfo2) {} //// logFruitTuple5(/*8*/); //// logFruitTuple5("apple", /*9*/); +//// +//// type FruitAndInfo3 = ["apple", ...AppleInfo[], number] | ["banana", ...BananaInfo[], number]; +//// +//// function logFruitTuple6(...[fruit, fruitInfoOrNumber, secondFruitInfoOrNumber]: FruitAndInfo3) {} +//// logFruitTuple6(/*10*/); +//// logFruitTuple6("apple", /*11*/); +//// logFruitTuple6("apple", { color: "red" }, /*12*/); verify.signatureHelp( { @@ -113,4 +120,31 @@ verify.signatureHelp( parameterSpan: "...fruitInfo: AppleInfo[]", isVariadic: true, }, + { + marker: "10", + text: `logFruitTuple6(fruit: "apple", ...__0_1: AppleInfo[], __0_2: number): void`, + overloadsCount: 2, + parameterCount: 3, + parameterName: "fruit", + parameterSpan: `fruit: "apple"`, + isVariadic: false, + }, + { + marker: "11", + text: `logFruitTuple6(fruit: "apple", ...__0_1: AppleInfo[], __0_2: number): void`, + overloadsCount: 2, + parameterCount: 3, + parameterName: "__0_1", + parameterSpan: `...__0_1: AppleInfo[]`, + isVariadic: false, + }, + { + marker: "12", + text: `logFruitTuple6(fruit: "apple", ...__0_1: AppleInfo[], __0_2: number): void`, + overloadsCount: 2, + parameterCount: 3, + parameterName: "__0_2", + parameterSpan: `__0_2: number`, + isVariadic: false, + }, ); From 15936718dd078c60fd76f68cd9a80f1419ae28ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Tue, 26 Mar 2024 10:38:13 +0100 Subject: [PATCH 7/9] add more tests --- ...reHelpExpandedRestTuplesFallbackLabels1.ts | 130 +++++++++++++++--- 1 file changed, 108 insertions(+), 22 deletions(-) diff --git a/tests/cases/fourslash/signatureHelpExpandedRestTuplesFallbackLabels1.ts b/tests/cases/fourslash/signatureHelpExpandedRestTuplesFallbackLabels1.ts index d0005bdac0ae3..6ca9d8c568313 100644 --- a/tests/cases/fourslash/signatureHelpExpandedRestTuplesFallbackLabels1.ts +++ b/tests/cases/fourslash/signatureHelpExpandedRestTuplesFallbackLabels1.ts @@ -20,23 +20,37 @@ //// function logFruitTuple3(...[fruit, ...rest]: FruitAndInfo1) {} //// logFruitTuple3(/*4*/); //// logFruitTuple3("apple", /*5*/); -//// -//// type FruitAndInfo2 = ["apple", ...AppleInfo[]] | ["banana", ...BananaInfo[]]; -//// -//// function logFruitTuple4(...[fruit, firstInfo]: FruitAndInfo2) {} + +//// function logFruitTuple4(...[fruit, ...[info]]: FruitAndInfo1) {} //// logFruitTuple4(/*6*/); //// logFruitTuple4("apple", /*7*/); //// -//// function logFruitTuple5(...[fruit, ...fruitInfo]: FruitAndInfo2) {} +//// type FruitAndInfo2 = ["apple", ...AppleInfo[]] | ["banana", ...BananaInfo[]]; +//// +//// function logFruitTuple5(...[fruit, firstInfo]: FruitAndInfo2) {} //// logFruitTuple5(/*8*/); //// logFruitTuple5("apple", /*9*/); //// -//// type FruitAndInfo3 = ["apple", ...AppleInfo[], number] | ["banana", ...BananaInfo[], number]; -//// -//// function logFruitTuple6(...[fruit, fruitInfoOrNumber, secondFruitInfoOrNumber]: FruitAndInfo3) {} +//// function logFruitTuple6(...[fruit, ...fruitInfo]: FruitAndInfo2) {} //// logFruitTuple6(/*10*/); //// logFruitTuple6("apple", /*11*/); -//// logFruitTuple6("apple", { color: "red" }, /*12*/); +//// +//// 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*/); verify.signatureHelp( { @@ -86,25 +100,25 @@ verify.signatureHelp( }, { marker: "6", - text: `logFruitTuple4(fruit: "apple", ...__0_1: AppleInfo[]): void`, + text: `logFruitTuple4(fruit: "apple", __0_1: AppleInfo): void`, overloadsCount: 2, parameterCount: 2, parameterName: "fruit", parameterSpan: `fruit: "apple"`, - isVariadic: true, + isVariadic: false, }, { marker: "7", - text: `logFruitTuple4(fruit: "apple", ...__0_1: AppleInfo[]): void`, + text: `logFruitTuple4(fruit: "apple", __0_1: AppleInfo): void`, overloadsCount: 2, parameterCount: 2, parameterName: "__0_1", - parameterSpan: "...__0_1: AppleInfo[]", - isVariadic: true, + parameterSpan: "__0_1: AppleInfo", + isVariadic: false, }, { marker: "8", - text: `logFruitTuple5(fruit: "apple", ...fruitInfo: AppleInfo[]): void`, + text: `logFruitTuple5(fruit: "apple", ...__0_1: AppleInfo[]): void`, overloadsCount: 2, parameterCount: 2, parameterName: "fruit", @@ -113,7 +127,25 @@ verify.signatureHelp( }, { marker: "9", - text: `logFruitTuple5(fruit: "apple", ...fruitInfo: AppleInfo[]): void`, + 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", @@ -121,8 +153,8 @@ verify.signatureHelp( isVariadic: true, }, { - marker: "10", - text: `logFruitTuple6(fruit: "apple", ...__0_1: AppleInfo[], __0_2: number): void`, + marker: "12", + text: `logFruitTuple7(fruit: "apple", ...__0_1: AppleInfo[], __0_2: number): void`, overloadsCount: 2, parameterCount: 3, parameterName: "fruit", @@ -130,8 +162,8 @@ verify.signatureHelp( isVariadic: false, }, { - marker: "11", - text: `logFruitTuple6(fruit: "apple", ...__0_1: AppleInfo[], __0_2: number): void`, + marker: "13", + text: `logFruitTuple7(fruit: "apple", ...__0_1: AppleInfo[], __0_2: number): void`, overloadsCount: 2, parameterCount: 3, parameterName: "__0_1", @@ -139,8 +171,62 @@ verify.signatureHelp( isVariadic: false, }, { - marker: "12", - text: `logFruitTuple6(fruit: "apple", ...__0_1: AppleInfo[], __0_2: number): void`, + 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", From 0e3c2072c06552d55e68d1a2630414ed532bbfc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Thu, 27 Jun 2024 18:40:27 +0200 Subject: [PATCH 8/9] prefer local labels --- src/compiler/checker.ts | 23 +++++++++++-------- ...reHelpExpandedRestTuplesFallbackLabels1.ts | 22 ++++++++++++++++++ 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 759949744e7d7..c5d731c96aa94 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13586,7 +13586,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return result; } - function getFallbackExpandedLabels(restType: TupleTypeReference, restDeclaration: Declaration | undefined) { + function getLocalExpandedLabels(restType: TupleTypeReference, restDeclaration: Declaration | undefined) { if (!restDeclaration) { return; } @@ -13635,17 +13635,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const restDeclaration = sig.parameters[restIndex].valueDeclaration; if (isTupleType(restType)) { - return [expandSignatureParametersWithTupleMembers(restType, restIndex, restName, getFallbackExpandedLabels(restType, restDeclaration))]; + 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, getFallbackExpandedLabels(t as TupleTypeReference, restDeclaration))); + 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, fallbackLabels: (__String | undefined)[] | undefined) { + function expandSignatureParametersWithTupleMembers(restType: TupleTypeReference, restIndex: number, restName: __String, localLabels: (__String | undefined)[] | undefined) { const elementTypes = getTypeArguments(restType); - const associatedNames = getUniqAssociatedNamesFromTupleType(restType, restName, fallbackLabels); + 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] : @@ -13660,10 +13660,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return concatenate(sig.parameters.slice(0, restIndex), restParams); } - function getUniqAssociatedNamesFromTupleType(type: TupleTypeReference, restName: __String, fallbackLabels: (__String | undefined)[] | undefined) { + 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, fallbackLabels?.[i]); + const name = getTupleElementLabel(labeledElement, i, restName, localLabels?.[i]); const prevCounter = associatedNamesMap.get(name); if (prevCounter === undefined) { associatedNamesMap.set(name, 1); @@ -37310,10 +37310,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function getTupleElementLabel(d: ParameterDeclaration | NamedTupleMember): __String; - function getTupleElementLabel(d: ParameterDeclaration | NamedTupleMember | undefined, index: number, restParameterName?: __String, fallbackLabel?: __String): __String; - function getTupleElementLabel(d: ParameterDeclaration | NamedTupleMember | undefined, index?: number, restParameterName = "arg" as __String, fallbackLabel?: __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 fallbackLabel ?? `${restParameterName}_${index}` as __String; + return `${restParameterName}_${index}` as __String; } Debug.assert(isIdentifier(d.name)); // Parameter declarations could be binding patterns, but we only allow identifier names return d.name.escapedText; diff --git a/tests/cases/fourslash/signatureHelpExpandedRestTuplesFallbackLabels1.ts b/tests/cases/fourslash/signatureHelpExpandedRestTuplesFallbackLabels1.ts index 6ca9d8c568313..f6b080d13882e 100644 --- a/tests/cases/fourslash/signatureHelpExpandedRestTuplesFallbackLabels1.ts +++ b/tests/cases/fourslash/signatureHelpExpandedRestTuplesFallbackLabels1.ts @@ -52,6 +52,10 @@ //// logFruitTuple9("apple", /*19*/); //// logFruitTuple9("apple", { color: "red" }, /*20*/); +//// function withPair(...[first, second]: [number, named: string]) {} +//// withPair(/*21*/); +//// withPair(101, /*22*/); + verify.signatureHelp( { marker: "1", @@ -233,4 +237,22 @@ verify.signatureHelp( 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, + }, ); From 621149e8e7efd8bacece811367c73d76e3c999ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Thu, 27 Jun 2024 18:41:19 +0200 Subject: [PATCH 9/9] rename test case --- ...kLabels1.ts => signatureHelpExpandedRestTuplesLocalLabels1.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/cases/fourslash/{signatureHelpExpandedRestTuplesFallbackLabels1.ts => signatureHelpExpandedRestTuplesLocalLabels1.ts} (100%) diff --git a/tests/cases/fourslash/signatureHelpExpandedRestTuplesFallbackLabels1.ts b/tests/cases/fourslash/signatureHelpExpandedRestTuplesLocalLabels1.ts similarity index 100% rename from tests/cases/fourslash/signatureHelpExpandedRestTuplesFallbackLabels1.ts rename to tests/cases/fourslash/signatureHelpExpandedRestTuplesLocalLabels1.ts