Skip to content

Commit 369f2b0

Browse files
authored
Derive tuple labels for rest elements from array binding patterns (#59045)
1 parent a9139bf commit 369f2b0

15 files changed

+7152
-127
lines changed

src/compiler/checker.ts

Lines changed: 96 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13574,20 +13574,20 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1357413574
function getExpandedParameters(sig: Signature, skipUnionExpanding?: boolean): readonly (readonly Symbol[])[] {
1357513575
if (signatureHasRestParameter(sig)) {
1357613576
const restIndex = sig.parameters.length - 1;
13577-
const restName = sig.parameters[restIndex].escapedName;
13578-
const restType = getTypeOfSymbol(sig.parameters[restIndex]);
13577+
const restSymbol = sig.parameters[restIndex];
13578+
const restType = getTypeOfSymbol(restSymbol);
1357913579
if (isTupleType(restType)) {
13580-
return [expandSignatureParametersWithTupleMembers(restType, restIndex, restName)];
13580+
return [expandSignatureParametersWithTupleMembers(restType, restIndex, restSymbol)];
1358113581
}
1358213582
else if (!skipUnionExpanding && restType.flags & TypeFlags.Union && every((restType as UnionType).types, isTupleType)) {
13583-
return map((restType as UnionType).types, t => expandSignatureParametersWithTupleMembers(t as TupleTypeReference, restIndex, restName));
13583+
return map((restType as UnionType).types, t => expandSignatureParametersWithTupleMembers(t as TupleTypeReference, restIndex, restSymbol));
1358413584
}
1358513585
}
1358613586
return [sig.parameters];
1358713587

13588-
function expandSignatureParametersWithTupleMembers(restType: TupleTypeReference, restIndex: number, restName: __String) {
13588+
function expandSignatureParametersWithTupleMembers(restType: TupleTypeReference, restIndex: number, restSymbol: Symbol) {
1358913589
const elementTypes = getTypeArguments(restType);
13590-
const associatedNames = getUniqAssociatedNamesFromTupleType(restType, restName);
13590+
const associatedNames = getUniqAssociatedNamesFromTupleType(restType, restSymbol);
1359113591
const restParams = map(elementTypes, (t, i) => {
1359213592
// Lookup the label from the individual tuple passed in before falling back to the signature `rest` parameter name
1359313593
const name = associatedNames && associatedNames[i] ? associatedNames[i] :
@@ -13602,20 +13602,29 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1360213602
return concatenate(sig.parameters.slice(0, restIndex), restParams);
1360313603
}
1360413604

13605-
function getUniqAssociatedNamesFromTupleType(type: TupleTypeReference, restName: __String) {
13606-
const associatedNamesMap = new Map<__String, number>();
13607-
return map(type.target.labeledElementDeclarations, (labeledElement, i) => {
13608-
const name = getTupleElementLabel(labeledElement, i, restName);
13609-
const prevCounter = associatedNamesMap.get(name);
13610-
if (prevCounter === undefined) {
13611-
associatedNamesMap.set(name, 1);
13612-
return name;
13605+
function getUniqAssociatedNamesFromTupleType(type: TupleTypeReference, restSymbol: Symbol) {
13606+
const names = map(type.target.labeledElementDeclarations, (labeledElement, i) => getTupleElementLabel(labeledElement, i, type.target.elementFlags[i], restSymbol));
13607+
if (names) {
13608+
const duplicates: number[] = [];
13609+
const uniqueNames = new Set<__String>();
13610+
for (let i = 0; i < names.length; i++) {
13611+
const name = names[i];
13612+
if (!tryAddToSet(uniqueNames, name)) {
13613+
duplicates.push(i);
13614+
}
1361313615
}
13614-
else {
13615-
associatedNamesMap.set(name, prevCounter + 1);
13616-
return `${name}_${prevCounter}` as __String;
13616+
const counters = new Map<__String, number>();
13617+
for (const i of duplicates) {
13618+
let counter = counters.get(names[i]) ?? 1;
13619+
let name: __String;
13620+
while (!tryAddToSet(uniqueNames, name = `${names[i]}_${counter}` as __String)) {
13621+
counter++;
13622+
}
13623+
names[i] = name;
13624+
counters.set(names[i], counter + 1);
1361713625
}
13618-
});
13626+
}
13627+
return names;
1361913628
}
1362013629
}
1362113630

@@ -37247,11 +37256,73 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3724737256
);
3724837257
}
3724937258

37259+
/**
37260+
* Gets a tuple element label by recursively walking `ArrayBindingPattern` nodes in a `BindingName`.
37261+
* @param node The source node from which to derive a label
37262+
* @param index The index into the tuple
37263+
* @param elementFlags The {@see ElementFlags} of the tuple element
37264+
*/
37265+
function getTupleElementLabelFromBindingElement(node: BindingElement | ParameterDeclaration, index: number, elementFlags: ElementFlags): __String {
37266+
switch (node.name.kind) {
37267+
case SyntaxKind.Identifier: {
37268+
const name = node.name.escapedText;
37269+
if (node.dotDotDotToken) {
37270+
// given
37271+
// (...[x, y, ...z]: [number, number, ...number[]]) => ...
37272+
// this produces
37273+
// (x: number, y: number, ...z: number[]) => ...
37274+
// which preserves rest elements of 'z'
37275+
37276+
// given
37277+
// (...[x, y, ...z]: [number, number, ...[...number[], number]]) => ...
37278+
// this produces
37279+
// (x: number, y: number, ...z: number[], z_1: number) => ...
37280+
// which preserves rest elements of z but gives distinct numbers to fixed elements of 'z'
37281+
return elementFlags & ElementFlags.Variable ? name : `${name}_${index}` as __String;
37282+
}
37283+
else {
37284+
// given
37285+
// (...[x]: [number]) => ...
37286+
// this produces
37287+
// (x: number) => ...
37288+
// which preserves fixed elements of 'x'
37289+
37290+
// given
37291+
// (...[x]: ...number[]) => ...
37292+
// this produces
37293+
// (x_0: number) => ...
37294+
// which which numbers fixed elements of 'x' whose tuple element type is variable
37295+
return elementFlags & ElementFlags.Fixed ? name : `${name}_n` as __String;
37296+
}
37297+
}
37298+
case SyntaxKind.ArrayBindingPattern: {
37299+
if (node.dotDotDotToken) {
37300+
const elements = node.name.elements;
37301+
const lastElement = tryCast(lastOrUndefined(elements), isBindingElement);
37302+
const elementCount = elements.length - (lastElement?.dotDotDotToken ? 1 : 0);
37303+
if (index < elementCount) {
37304+
const element = elements[index];
37305+
if (isBindingElement(element)) {
37306+
return getTupleElementLabelFromBindingElement(element, index, elementFlags);
37307+
}
37308+
}
37309+
else if (lastElement?.dotDotDotToken) {
37310+
return getTupleElementLabelFromBindingElement(lastElement, index - elementCount, elementFlags);
37311+
}
37312+
}
37313+
break;
37314+
}
37315+
}
37316+
return `arg_${index}` as __String;
37317+
}
37318+
3725037319
function getTupleElementLabel(d: ParameterDeclaration | NamedTupleMember): __String;
37251-
function getTupleElementLabel(d: ParameterDeclaration | NamedTupleMember | undefined, index: number, restParameterName?: __String): __String;
37252-
function getTupleElementLabel(d: ParameterDeclaration | NamedTupleMember | undefined, index?: number, restParameterName = "arg" as __String) {
37320+
function getTupleElementLabel(d: ParameterDeclaration | NamedTupleMember | undefined, index: number, elementFlags: ElementFlags, restSymbol?: Symbol): __String;
37321+
function getTupleElementLabel(d: ParameterDeclaration | NamedTupleMember | undefined, index = 0, elementFlags = ElementFlags.Fixed, restSymbol?: Symbol) {
3725337322
if (!d) {
37254-
return `${restParameterName}_${index}` as __String;
37323+
const restParameter = tryCast(restSymbol?.valueDeclaration, isParameter);
37324+
return restParameter ? getTupleElementLabelFromBindingElement(restParameter, index, elementFlags) :
37325+
`${restSymbol?.escapedName ?? "arg"}_${index}` as __String;
3725537326
}
3725637327
Debug.assert(isIdentifier(d.name)); // Parameter declarations could be binding patterns, but we only allow identifier names
3725737328
return d.name.escapedText;
@@ -37265,9 +37336,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3726537336
const restParameter = signature.parameters[paramCount] || unknownSymbol;
3726637337
const restType = overrideRestType || getTypeOfSymbol(restParameter);
3726737338
if (isTupleType(restType)) {
37268-
const associatedNames = ((restType as TypeReference).target as TupleType).labeledElementDeclarations;
37339+
const tupleType = (restType as TypeReference).target as TupleType;
3726937340
const index = pos - paramCount;
37270-
return getTupleElementLabel(associatedNames?.[index], index, restParameter.escapedName);
37341+
const associatedName = tupleType.labeledElementDeclarations?.[index];
37342+
const elementFlags = tupleType.elementFlags[index];
37343+
return getTupleElementLabel(associatedName, index, elementFlags, restParameter);
3727137344
}
3727237345
return restParameter.escapedName;
3727337346
}

tests/baselines/reference/argumentsSpreadRestIterables(target=es5).types

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,10 @@ declare const itNum: Iterable<number>
4040
;(function(a, ...rest) {})('', true, ...itNum)
4141
>(function(a, ...rest) {})('', true, ...itNum) : void
4242
> : ^^^^
43-
>(function(a, ...rest) {}) : (a: string, rest_0: boolean, ...rest_1: Iterable<number>[]) => void
44-
> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
45-
>function(a, ...rest) {} : (a: string, rest_0: boolean, ...rest_1: Iterable<number>[]) => void
46-
> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
43+
>(function(a, ...rest) {}) : (a: string, rest_0: boolean, ...rest: Iterable<number>[]) => void
44+
> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
45+
>function(a, ...rest) {} : (a: string, rest_0: boolean, ...rest: Iterable<number>[]) => void
46+
> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4747
>a : string
4848
> : ^^^^^^
4949
>rest : [boolean, ...Iterable<number>[]]

tests/baselines/reference/argumentsSpreadRestIterables(target=esnext).types

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,10 @@ declare const itNum: Iterable<number>
4040
;(function(a, ...rest) {})('', true, ...itNum)
4141
>(function(a, ...rest) {})('', true, ...itNum) : void
4242
> : ^^^^
43-
>(function(a, ...rest) {}) : (a: string, rest_0: boolean, ...rest_1: number[]) => void
44-
> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
45-
>function(a, ...rest) {} : (a: string, rest_0: boolean, ...rest_1: number[]) => void
46-
> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
43+
>(function(a, ...rest) {}) : (a: string, rest_0: boolean, ...rest: number[]) => void
44+
> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
45+
>function(a, ...rest) {} : (a: string, rest_0: boolean, ...rest: number[]) => void
46+
> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4747
>a : string
4848
> : ^^^^^^
4949
>rest : [boolean, ...number[]]

0 commit comments

Comments
 (0)