Derive tuple labels for rest elements from array binding patterns (#59045)

This commit is contained in:
Ron Buckton
2024-07-17 15:13:36 -04:00
committed by GitHub
parent a9139bfdfe
commit 369f2b0fb8
15 changed files with 7152 additions and 127 deletions

View File

@@ -13574,20 +13574,20 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
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 restSymbol = sig.parameters[restIndex];
const restType = getTypeOfSymbol(restSymbol);
if (isTupleType(restType)) {
return [expandSignatureParametersWithTupleMembers(restType, restIndex, restName)];
return [expandSignatureParametersWithTupleMembers(restType, restIndex, restSymbol)];
}
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, restSymbol));
}
}
return [sig.parameters];
function expandSignatureParametersWithTupleMembers(restType: TupleTypeReference, restIndex: number, restName: __String) {
function expandSignatureParametersWithTupleMembers(restType: TupleTypeReference, restIndex: number, restSymbol: Symbol) {
const elementTypes = getTypeArguments(restType);
const associatedNames = getUniqAssociatedNamesFromTupleType(restType, restName);
const associatedNames = getUniqAssociatedNamesFromTupleType(restType, restSymbol);
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] :
@@ -13602,20 +13602,29 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return concatenate(sig.parameters.slice(0, restIndex), restParams);
}
function getUniqAssociatedNamesFromTupleType(type: TupleTypeReference, restName: __String) {
const associatedNamesMap = new Map<__String, number>();
return map(type.target.labeledElementDeclarations, (labeledElement, i) => {
const name = getTupleElementLabel(labeledElement, i, restName);
const prevCounter = associatedNamesMap.get(name);
if (prevCounter === undefined) {
associatedNamesMap.set(name, 1);
return name;
function getUniqAssociatedNamesFromTupleType(type: TupleTypeReference, restSymbol: Symbol) {
const names = map(type.target.labeledElementDeclarations, (labeledElement, i) => getTupleElementLabel(labeledElement, i, type.target.elementFlags[i], restSymbol));
if (names) {
const duplicates: number[] = [];
const uniqueNames = new Set<__String>();
for (let i = 0; i < names.length; i++) {
const name = names[i];
if (!tryAddToSet(uniqueNames, name)) {
duplicates.push(i);
}
}
else {
associatedNamesMap.set(name, prevCounter + 1);
return `${name}_${prevCounter}` as __String;
const counters = new Map<__String, number>();
for (const i of duplicates) {
let counter = counters.get(names[i]) ?? 1;
let name: __String;
while (!tryAddToSet(uniqueNames, name = `${names[i]}_${counter}` as __String)) {
counter++;
}
names[i] = name;
counters.set(names[i], counter + 1);
}
});
}
return names;
}
}
@@ -37247,11 +37256,73 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
);
}
/**
* Gets a tuple element label by recursively walking `ArrayBindingPattern` nodes in a `BindingName`.
* @param node The source node from which to derive a label
* @param index The index into the tuple
* @param elementFlags The {@see ElementFlags} of the tuple element
*/
function getTupleElementLabelFromBindingElement(node: BindingElement | ParameterDeclaration, index: number, elementFlags: ElementFlags): __String {
switch (node.name.kind) {
case SyntaxKind.Identifier: {
const name = node.name.escapedText;
if (node.dotDotDotToken) {
// given
// (...[x, y, ...z]: [number, number, ...number[]]) => ...
// this produces
// (x: number, y: number, ...z: number[]) => ...
// which preserves rest elements of 'z'
// given
// (...[x, y, ...z]: [number, number, ...[...number[], number]]) => ...
// this produces
// (x: number, y: number, ...z: number[], z_1: number) => ...
// which preserves rest elements of z but gives distinct numbers to fixed elements of 'z'
return elementFlags & ElementFlags.Variable ? name : `${name}_${index}` as __String;
}
else {
// given
// (...[x]: [number]) => ...
// this produces
// (x: number) => ...
// which preserves fixed elements of 'x'
// given
// (...[x]: ...number[]) => ...
// this produces
// (x_0: number) => ...
// which which numbers fixed elements of 'x' whose tuple element type is variable
return elementFlags & ElementFlags.Fixed ? name : `${name}_n` as __String;
}
}
case SyntaxKind.ArrayBindingPattern: {
if (node.dotDotDotToken) {
const elements = node.name.elements;
const lastElement = tryCast(lastOrUndefined(elements), isBindingElement);
const elementCount = elements.length - (lastElement?.dotDotDotToken ? 1 : 0);
if (index < elementCount) {
const element = elements[index];
if (isBindingElement(element)) {
return getTupleElementLabelFromBindingElement(element, index, elementFlags);
}
}
else if (lastElement?.dotDotDotToken) {
return getTupleElementLabelFromBindingElement(lastElement, index - elementCount, elementFlags);
}
}
break;
}
}
return `arg_${index}` as __String;
}
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, elementFlags: ElementFlags, restSymbol?: Symbol): __String;
function getTupleElementLabel(d: ParameterDeclaration | NamedTupleMember | undefined, index = 0, elementFlags = ElementFlags.Fixed, restSymbol?: Symbol) {
if (!d) {
return `${restParameterName}_${index}` as __String;
const restParameter = tryCast(restSymbol?.valueDeclaration, isParameter);
return restParameter ? getTupleElementLabelFromBindingElement(restParameter, index, elementFlags) :
`${restSymbol?.escapedName ?? "arg"}_${index}` as __String;
}
Debug.assert(isIdentifier(d.name)); // Parameter declarations could be binding patterns, but we only allow identifier names
return d.name.escapedText;
@@ -37265,9 +37336,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const restParameter = signature.parameters[paramCount] || unknownSymbol;
const restType = overrideRestType || getTypeOfSymbol(restParameter);
if (isTupleType(restType)) {
const associatedNames = ((restType as TypeReference).target as TupleType).labeledElementDeclarations;
const tupleType = (restType as TypeReference).target as TupleType;
const index = pos - paramCount;
return getTupleElementLabel(associatedNames?.[index], index, restParameter.escapedName);
const associatedName = tupleType.labeledElementDeclarations?.[index];
const elementFlags = tupleType.elementFlags[index];
return getTupleElementLabel(associatedName, index, elementFlags, restParameter);
}
return restParameter.escapedName;
}