Skip to content

Commit 5b85e94

Browse files
JoshuaKGoldbergjakebaileyAndaristDaniel Rosenwasser
authored
Allowed tuples to have both named and anonymous members (#53356)
Co-authored-by: Jake Bailey <[email protected]> Co-authored-by: Mateusz Burzyński <[email protected]> Co-authored-by: Daniel Rosenwasser <[email protected]>
1 parent a94eb31 commit 5b85e94

17 files changed

+518
-106
lines changed

src/compiler/checker.ts

Lines changed: 37 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -6888,21 +6888,21 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
68886888
const arity = getTypeReferenceArity(type);
68896889
const tupleConstituentNodes = mapToTypeNodes(typeArguments.slice(0, arity), context);
68906890
if (tupleConstituentNodes) {
6891-
if ((type.target as TupleType).labeledElementDeclarations) {
6892-
for (let i = 0; i < tupleConstituentNodes.length; i++) {
6893-
const flags = (type.target as TupleType).elementFlags[i];
6891+
const { labeledElementDeclarations } = type.target as TupleType;
6892+
for (let i = 0; i < tupleConstituentNodes.length; i++) {
6893+
const flags = (type.target as TupleType).elementFlags[i];
6894+
const labeledElementDeclaration = labeledElementDeclarations?.[i];
6895+
6896+
if (labeledElementDeclaration) {
68946897
tupleConstituentNodes[i] = factory.createNamedTupleMember(
68956898
flags & ElementFlags.Variable ? factory.createToken(SyntaxKind.DotDotDotToken) : undefined,
6896-
factory.createIdentifier(unescapeLeadingUnderscores(getTupleElementLabel((type.target as TupleType).labeledElementDeclarations![i]))),
6899+
factory.createIdentifier(unescapeLeadingUnderscores(getTupleElementLabel(labeledElementDeclaration))),
68976900
flags & ElementFlags.Optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined,
68986901
flags & ElementFlags.Rest ? factory.createArrayTypeNode(tupleConstituentNodes[i]) :
68996902
tupleConstituentNodes[i]
69006903
);
69016904
}
6902-
}
6903-
else {
6904-
for (let i = 0; i < Math.min(arity, tupleConstituentNodes.length); i++) {
6905-
const flags = (type.target as TupleType).elementFlags[i];
6905+
else {
69066906
tupleConstituentNodes[i] =
69076907
flags & ElementFlags.Variable ? factory.createRestTypeNode(flags & ElementFlags.Rest ? factory.createArrayTypeNode(tupleConstituentNodes[i]) : tupleConstituentNodes[i]) :
69086908
flags & ElementFlags.Optional ? factory.createOptionalTypeNode(tupleConstituentNodes[i]) :
@@ -12657,19 +12657,20 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1265712657
function getExpandedParameters(sig: Signature, skipUnionExpanding?: boolean): readonly (readonly Symbol[])[] {
1265812658
if (signatureHasRestParameter(sig)) {
1265912659
const restIndex = sig.parameters.length - 1;
12660+
const restName = sig.parameters[restIndex].escapedName;
1266012661
const restType = getTypeOfSymbol(sig.parameters[restIndex]);
1266112662
if (isTupleType(restType)) {
12662-
return [expandSignatureParametersWithTupleMembers(restType, restIndex)];
12663+
return [expandSignatureParametersWithTupleMembers(restType, restIndex, restName)];
1266312664
}
1266412665
else if (!skipUnionExpanding && restType.flags & TypeFlags.Union && every((restType as UnionType).types, isTupleType)) {
12665-
return map((restType as UnionType).types, t => expandSignatureParametersWithTupleMembers(t as TupleTypeReference, restIndex));
12666+
return map((restType as UnionType).types, t => expandSignatureParametersWithTupleMembers(t as TupleTypeReference, restIndex, restName));
1266612667
}
1266712668
}
1266812669
return [sig.parameters];
1266912670

12670-
function expandSignatureParametersWithTupleMembers(restType: TupleTypeReference, restIndex: number) {
12671-
const elementTypes = getElementTypes(restType);
12672-
const associatedNames = getUniqAssociatedNamesFromTupleType(restType);
12671+
function expandSignatureParametersWithTupleMembers(restType: TupleTypeReference, restIndex: number, restName: __String) {
12672+
const elementTypes = getTypeArguments(restType);
12673+
const associatedNames = getUniqAssociatedNamesFromTupleType(restType, restName);
1267312674
const restParams = map(elementTypes, (t, i) => {
1267412675
// Lookup the label from the individual tuple passed in before falling back to the signature `rest` parameter name
1267512676
const name = associatedNames && associatedNames[i] ? associatedNames[i] :
@@ -12684,10 +12685,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1268412685
return concatenate(sig.parameters.slice(0, restIndex), restParams);
1268512686
}
1268612687

12687-
function getUniqAssociatedNamesFromTupleType(type: TupleTypeReference) {
12688+
function getUniqAssociatedNamesFromTupleType(type: TupleTypeReference, restName: __String) {
1268812689
const associatedNamesMap = new Map<__String, number>();
12689-
return map(type.target.labeledElementDeclarations, labeledElement => {
12690-
const name = getTupleElementLabel(labeledElement);
12690+
return map(type.target.labeledElementDeclarations, (labeledElement, i) => {
12691+
const name = getTupleElementLabel(labeledElement, i, restName);
1269112692
const prevCounter = associatedNamesMap.get(name);
1269212693
if (prevCounter === undefined) {
1269312694
associatedNamesMap.set(name, 1);
@@ -15963,8 +15964,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1596315964
return readonly ? globalReadonlyArrayType : globalArrayType;
1596415965
}
1596515966
const elementFlags = map((node as TupleTypeNode).elements, getTupleElementFlags);
15966-
const missingName = some((node as TupleTypeNode).elements, e => e.kind !== SyntaxKind.NamedTupleMember);
15967-
return getTupleTargetType(elementFlags, readonly, /*associatedNames*/ missingName ? undefined : (node as TupleTypeNode).elements as readonly NamedTupleMember[]);
15967+
return getTupleTargetType(elementFlags, readonly, map((node as TupleTypeNode).elements, memberIfLabeledElementDeclaration));
15968+
}
15969+
15970+
function memberIfLabeledElementDeclaration(member: Node): NamedTupleMember | ParameterDeclaration | undefined {
15971+
return isNamedTupleMember(member) || isParameter(member) ? member : undefined;
1596815972
}
1596915973

1597015974
// Return true if the given type reference node is directly aliased or if it needs to be deferred
@@ -16054,21 +16058,22 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1605416058
return isTypeOperatorNode(node) && node.operator === SyntaxKind.ReadonlyKeyword;
1605516059
}
1605616060

16057-
function createTupleType(elementTypes: readonly Type[], elementFlags?: readonly ElementFlags[], readonly = false, namedMemberDeclarations?: readonly (NamedTupleMember | ParameterDeclaration)[]) {
16061+
function createTupleType(elementTypes: readonly Type[], elementFlags?: readonly ElementFlags[], readonly = false, namedMemberDeclarations: readonly (NamedTupleMember | ParameterDeclaration | undefined)[] = []) {
1605816062
const tupleTarget = getTupleTargetType(elementFlags || map(elementTypes, _ => ElementFlags.Required), readonly, namedMemberDeclarations);
1605916063
return tupleTarget === emptyGenericType ? emptyObjectType :
1606016064
elementTypes.length ? createNormalizedTypeReference(tupleTarget, elementTypes) :
1606116065
tupleTarget;
1606216066
}
1606316067

16064-
function getTupleTargetType(elementFlags: readonly ElementFlags[], readonly: boolean, namedMemberDeclarations?: readonly (NamedTupleMember | ParameterDeclaration)[]): GenericType {
16068+
function getTupleTargetType(elementFlags: readonly ElementFlags[], readonly: boolean, namedMemberDeclarations: readonly (NamedTupleMember | ParameterDeclaration | undefined)[]): GenericType {
1606516069
if (elementFlags.length === 1 && elementFlags[0] & ElementFlags.Rest) {
1606616070
// [...X[]] is equivalent to just X[]
1606716071
return readonly ? globalReadonlyArrayType : globalArrayType;
1606816072
}
16073+
const memberIds = mapDefined(namedMemberDeclarations, node => node ? getNodeId(node) : undefined);
1606916074
const key = map(elementFlags, f => f & ElementFlags.Required ? "#" : f & ElementFlags.Optional ? "?" : f & ElementFlags.Rest ? "." : "*").join() +
1607016075
(readonly ? "R" : "") +
16071-
(namedMemberDeclarations && namedMemberDeclarations.length ? "," + map(namedMemberDeclarations, getNodeId).join(",") : "");
16076+
(memberIds.length ? "," + memberIds.join(",") : "");
1607216077
let type = tupleTypes.get(key);
1607316078
if (!type) {
1607416079
tupleTypes.set(key, type = createTupleTargetType(elementFlags, readonly, namedMemberDeclarations));
@@ -16083,7 +16088,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1608316088
//
1608416089
// Note that the generic type created by this function has no symbol associated with it. The same
1608516090
// is true for each of the synthesized type parameters.
16086-
function createTupleTargetType(elementFlags: readonly ElementFlags[], readonly: boolean, namedMemberDeclarations: readonly (NamedTupleMember | ParameterDeclaration)[] | undefined): TupleType {
16091+
function createTupleTargetType(elementFlags: readonly ElementFlags[], readonly: boolean, namedMemberDeclarations: readonly (NamedTupleMember | ParameterDeclaration | undefined)[]): TupleType {
1608716092
const arity = elementFlags.length;
1608816093
const minLength = countWhere(elementFlags, f => !!(f & (ElementFlags.Required | ElementFlags.Variadic)));
1608916094
let typeParameters: TypeParameter[] | undefined;
@@ -16165,7 +16170,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1616516170
// In either layout, zero or more generic variadic elements may be present at any location.
1616616171
const expandedTypes: Type[] = [];
1616716172
const expandedFlags: ElementFlags[] = [];
16168-
let expandedDeclarations: (NamedTupleMember | ParameterDeclaration)[] | undefined = [];
16173+
const expandedDeclarations: (NamedTupleMember | ParameterDeclaration | undefined)[] = [];
1616916174
let lastRequiredIndex = -1;
1617016175
let firstRestIndex = -1;
1617116176
let lastOptionalOrRestIndex = -1;
@@ -16208,7 +16213,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1620816213
(t, i) => expandedFlags[firstRestIndex + i] & ElementFlags.Variadic ? getIndexedAccessType(t, numberType) : t));
1620916214
expandedTypes.splice(firstRestIndex + 1, lastOptionalOrRestIndex - firstRestIndex);
1621016215
expandedFlags.splice(firstRestIndex + 1, lastOptionalOrRestIndex - firstRestIndex);
16211-
expandedDeclarations?.splice(firstRestIndex + 1, lastOptionalOrRestIndex - firstRestIndex);
16216+
expandedDeclarations.splice(firstRestIndex + 1, lastOptionalOrRestIndex - firstRestIndex);
1621216217
}
1621316218
const tupleTarget = getTupleTargetType(expandedFlags, target.readonly, expandedDeclarations);
1621416219
return tupleTarget === emptyGenericType ? emptyObjectType :
@@ -16227,12 +16232,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1622716232
}
1622816233
expandedTypes.push(flags & ElementFlags.Optional ? addOptionality(type, /*isProperty*/ true) : type);
1622916234
expandedFlags.push(flags);
16230-
if (expandedDeclarations && declaration) {
16231-
expandedDeclarations.push(declaration);
16232-
}
16233-
else {
16234-
expandedDeclarations = undefined;
16235-
}
16235+
expandedDeclarations.push(declaration);
1623616236
}
1623716237
}
1623816238

@@ -34828,7 +34828,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3482834828
return type;
3482934829
}
3483034830

34831-
function getTupleElementLabel(d: ParameterDeclaration | NamedTupleMember) {
34831+
function getTupleElementLabel(d: ParameterDeclaration | NamedTupleMember): __String;
34832+
function getTupleElementLabel(d: ParameterDeclaration | NamedTupleMember | undefined, index: number, restParameterName?: __String): __String;
34833+
function getTupleElementLabel(d: ParameterDeclaration | NamedTupleMember | undefined, index?: number, restParameterName = "arg" as __String) {
34834+
if (!d) {
34835+
return `${restParameterName}_${index}` as __String;
34836+
}
3483234837
Debug.assert(isIdentifier(d.name)); // Parameter declarations could be binding patterns, but we only allow identifier names
3483334838
return d.name.escapedText;
3483434839
}
@@ -34843,7 +34848,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3484334848
if (isTupleType(restType)) {
3484434849
const associatedNames = ((restType as TypeReference).target as TupleType).labeledElementDeclarations;
3484534850
const index = pos - paramCount;
34846-
return associatedNames && getTupleElementLabel(associatedNames[index]) || restParameter.escapedName + "_" + index as __String;
34851+
return getTupleElementLabel(associatedNames?.[index], index, restParameter.escapedName);
3484734852
}
3484834853
return restParameter.escapedName;
3484934854
}
@@ -38870,12 +38875,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3887038875
const elementTypes = node.elements;
3887138876
let seenOptionalElement = false;
3887238877
let seenRestElement = false;
38873-
const hasNamedElement = some(elementTypes, isNamedTupleMember);
3887438878
for (const e of elementTypes) {
38875-
if (e.kind !== SyntaxKind.NamedTupleMember && hasNamedElement) {
38876-
grammarErrorOnNode(e, Diagnostics.Tuple_members_must_all_have_names_or_all_not_have_names);
38877-
break;
38878-
}
3887938879
const flags = getTupleElementFlags(e);
3888038880
if (flags & ElementFlags.Variadic) {
3888138881
const type = getTypeFromTypeNode((e as RestTypeNode | NamedTupleMember).type);

src/compiler/diagnosticMessages.json

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4277,10 +4277,6 @@
42774277
"category": "Error",
42784278
"code": 5083
42794279
},
4280-
"Tuple members must all have names or all not have names.": {
4281-
"category": "Error",
4282-
"code": 5084
4283-
},
42844280
"A tuple member cannot be both optional and rest.": {
42854281
"category": "Error",
42864282
"code": 5085

src/compiler/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6434,7 +6434,7 @@ export interface TupleType extends GenericType {
64346434
hasRestElement: boolean;
64356435
combinedFlags: ElementFlags;
64366436
readonly: boolean;
6437-
labeledElementDeclarations?: readonly (NamedTupleMember | ParameterDeclaration)[];
6437+
labeledElementDeclarations?: readonly (NamedTupleMember | ParameterDeclaration | undefined)[];
64386438
}
64396439

64406440
export interface TupleTypeReference extends TypeReference {

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6951,7 +6951,7 @@ declare namespace ts {
69516951
hasRestElement: boolean;
69526952
combinedFlags: ElementFlags;
69536953
readonly: boolean;
6954-
labeledElementDeclarations?: readonly (NamedTupleMember | ParameterDeclaration)[];
6954+
labeledElementDeclarations?: readonly (NamedTupleMember | ParameterDeclaration | undefined)[];
69556955
}
69566956
interface TupleTypeReference extends TypeReference {
69576957
target: TupleType;

tests/baselines/reference/api/typescript.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2898,7 +2898,7 @@ declare namespace ts {
28982898
hasRestElement: boolean;
28992899
combinedFlags: ElementFlags;
29002900
readonly: boolean;
2901-
labeledElementDeclarations?: readonly (NamedTupleMember | ParameterDeclaration)[];
2901+
labeledElementDeclarations?: readonly (NamedTupleMember | ParameterDeclaration | undefined)[];
29022902
}
29032903
interface TupleTypeReference extends TypeReference {
29042904
target: TupleType;

tests/baselines/reference/genericRestParameters2.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,7 @@ type T05<T extends any[]> = Parameters<(x: string, ...args: T) => void>;
444444
>args : T
445445

446446
type T06 = T05<[number, ...boolean[]]>;
447-
>T06 : [string, number, ...boolean[]]
447+
>T06 : [x: string, number, ...boolean[]]
448448

449449
type P1<T extends Function> = T extends (head: infer A, ...tail: infer B) => any ? { head: A, tail: B } : any[];
450450
>P1 : P1<T>

tests/baselines/reference/genericRestParameters3.types

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -264,19 +264,19 @@ ff2 = ff1;
264264
function ff3<A extends unknown[]>(s1: (...args: [x: string, ...rest: A | [number]]) => void, s2: (x: string, ...rest: A | [number]) => void) {
265265
>ff3 : <A extends unknown[]>(s1: (...args: [x: string, ...rest: A | [number]]) => void, s2: (x: string, ...rest: A | [number]) => void) => void
266266
>s1 : (...args: [x: string, ...rest: A | [number]]) => void
267-
>args : [string, number] | [x: string, ...rest: A]
267+
>args : [x: string, number] | [x: string, ...rest: A]
268268
>s2 : (x: string, ...rest: A | [number]) => void
269269
>x : string
270270
>rest : [number] | A
271271

272272
s1 = s2;
273273
>s1 = s2 : (x: string, ...rest: [number] | A) => void
274-
>s1 : (...args: [string, number] | [x: string, ...rest: A]) => void
274+
>s1 : (...args: [x: string, number] | [x: string, ...rest: A]) => void
275275
>s2 : (x: string, ...rest: [number] | A) => void
276276

277277
s2 = s1;
278-
>s2 = s1 : (...args: [string, number] | [x: string, ...rest: A]) => void
278+
>s2 = s1 : (...args: [x: string, number] | [x: string, ...rest: A]) => void
279279
>s2 : (x: string, ...rest: [number] | A) => void
280-
>s1 : (...args: [string, number] | [x: string, ...rest: A]) => void
280+
>s1 : (...args: [x: string, number] | [x: string, ...rest: A]) => void
281281
}
282282

tests/baselines/reference/inferTypesWithFixedTupleExtendsAtVariadicPosition.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ type SubTup2VariadicAndRest<T extends unknown[]> = T extends [
4040
: never;
4141

4242
type SubTup2VariadicAndRestTest = SubTup2VariadicAndRest<[a: 0, b: 1, ...c: 2[]]>;
43-
>SubTup2VariadicAndRestTest : [0, 1, 2]
43+
>SubTup2VariadicAndRestTest : [a: 0, b: 1, 2]
4444

4545
type SubTup2TrailingVariadic<T extends unknown[]> = T extends [
4646
>SubTup2TrailingVariadic : SubTup2TrailingVariadic<T>
@@ -67,7 +67,7 @@ type SubTup2RestAndTrailingVariadic2<T extends unknown[]> = T extends [
6767
: never;
6868

6969
type SubTup2RestAndTrailingVariadic2Test = SubTup2RestAndTrailingVariadic2<[...a: 0[], b: 1, c: 2]>;
70-
>SubTup2RestAndTrailingVariadic2Test : [0, 1, 2]
70+
>SubTup2RestAndTrailingVariadic2Test : [0, b: 1, c: 2]
7171

7272
type SubTup2VariadicWithLeadingFixedElements<T extends unknown[]> = T extends [
7373
>SubTup2VariadicWithLeadingFixedElements : SubTup2VariadicWithLeadingFixedElements<T>

0 commit comments

Comments
 (0)