Skip to content

Commit 2dda8ac

Browse files
Allowed tuples to have both named and anonymous members
1 parent 905a0b4 commit 2dda8ac

14 files changed

+195
-88
lines changed

src/compiler/checker.ts

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,7 @@ import {
800800
LiteralTypeNode,
801801
mangleScopedPackageName,
802802
map,
803+
mapAllOrFail,
803804
mapDefined,
804805
MappedSymbol,
805806
MappedType,
@@ -6884,21 +6885,21 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
68846885
const arity = getTypeReferenceArity(type);
68856886
const tupleConstituentNodes = mapToTypeNodes(typeArguments.slice(0, arity), context);
68866887
if (tupleConstituentNodes) {
6887-
if ((type.target as TupleType).labeledElementDeclarations) {
6888-
for (let i = 0; i < tupleConstituentNodes.length; i++) {
6889-
const flags = (type.target as TupleType).elementFlags[i];
6888+
const { labeledElementDeclarations } = type.target as TupleType;
6889+
for (let i = 0; i < tupleConstituentNodes.length; i++) {
6890+
const flags = (type.target as TupleType).elementFlags[i];
6891+
const labeledElementDeclaration = labeledElementDeclarations?.[i];
6892+
6893+
if (labeledElementDeclaration) {
68906894
tupleConstituentNodes[i] = factory.createNamedTupleMember(
68916895
flags & ElementFlags.Variable ? factory.createToken(SyntaxKind.DotDotDotToken) : undefined,
6892-
factory.createIdentifier(unescapeLeadingUnderscores(getTupleElementLabel((type.target as TupleType).labeledElementDeclarations![i]))),
6896+
factory.createIdentifier(unescapeLeadingUnderscores(getTupleElementLabel((type.target as TupleType).labeledElementDeclarations![i]!))),
68936897
flags & ElementFlags.Optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined,
68946898
flags & ElementFlags.Rest ? factory.createArrayTypeNode(tupleConstituentNodes[i]) :
68956899
tupleConstituentNodes[i]
68966900
);
68976901
}
6898-
}
6899-
else {
6900-
for (let i = 0; i < Math.min(arity, tupleConstituentNodes.length); i++) {
6901-
const flags = (type.target as TupleType).elementFlags[i];
6902+
else {
69026903
tupleConstituentNodes[i] =
69036904
flags & ElementFlags.Variable ? factory.createRestTypeNode(flags & ElementFlags.Rest ? factory.createArrayTypeNode(tupleConstituentNodes[i]) : tupleConstituentNodes[i]) :
69046905
flags & ElementFlags.Optional ? factory.createOptionalTypeNode(tupleConstituentNodes[i]) :
@@ -12643,19 +12644,20 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1264312644
function getExpandedParameters(sig: Signature, skipUnionExpanding?: boolean): readonly (readonly Symbol[])[] {
1264412645
if (signatureHasRestParameter(sig)) {
1264512646
const restIndex = sig.parameters.length - 1;
12647+
const restName = sig.parameters[restIndex].escapedName;
1264612648
const restType = getTypeOfSymbol(sig.parameters[restIndex]);
1264712649
if (isTupleType(restType)) {
12648-
return [expandSignatureParametersWithTupleMembers(restType, restIndex)];
12650+
return [expandSignatureParametersWithTupleMembers(restType, restIndex, restName)];
1264912651
}
1265012652
else if (!skipUnionExpanding && restType.flags & TypeFlags.Union && every((restType as UnionType).types, isTupleType)) {
12651-
return map((restType as UnionType).types, t => expandSignatureParametersWithTupleMembers(t as TupleTypeReference, restIndex));
12653+
return map((restType as UnionType).types, t => expandSignatureParametersWithTupleMembers(t as TupleTypeReference, restIndex, restName));
1265212654
}
1265312655
}
1265412656
return [sig.parameters];
1265512657

12656-
function expandSignatureParametersWithTupleMembers(restType: TupleTypeReference, restIndex: number) {
12658+
function expandSignatureParametersWithTupleMembers(restType: TupleTypeReference, restIndex: number, restName: __String) {
1265712659
const elementTypes = getTypeArguments(restType);
12658-
const associatedNames = getUniqAssociatedNamesFromTupleType(restType);
12660+
const associatedNames = getUniqAssociatedNamesFromTupleType(restType, restName);
1265912661
const restParams = map(elementTypes, (t, i) => {
1266012662
// Lookup the label from the individual tuple passed in before falling back to the signature `rest` parameter name
1266112663
const name = associatedNames && associatedNames[i] ? associatedNames[i] :
@@ -12670,10 +12672,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1267012672
return concatenate(sig.parameters.slice(0, restIndex), restParams);
1267112673
}
1267212674

12673-
function getUniqAssociatedNamesFromTupleType(type: TupleTypeReference) {
12675+
function getUniqAssociatedNamesFromTupleType(type: TupleTypeReference, restName: __String) {
1267412676
const associatedNamesMap = new Map<__String, number>();
12675-
return map(type.target.labeledElementDeclarations, labeledElement => {
12676-
const name = getTupleElementLabel(labeledElement);
12677+
return map(type.target.labeledElementDeclarations, (labeledElement, i) => {
12678+
const name = getTupleElementLabel(labeledElement, i, restName);
1267712679
const prevCounter = associatedNamesMap.get(name);
1267812680
if (prevCounter === undefined) {
1267912681
associatedNamesMap.set(name, 1);
@@ -15890,8 +15892,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1589015892
return readonly ? globalReadonlyArrayType : globalArrayType;
1589115893
}
1589215894
const elementFlags = map((node as TupleTypeNode).elements, getTupleElementFlags);
15893-
const missingName = some((node as TupleTypeNode).elements, e => e.kind !== SyntaxKind.NamedTupleMember);
15894-
return getTupleTargetType(elementFlags, readonly, /*associatedNames*/ missingName ? undefined : (node as TupleTypeNode).elements as readonly NamedTupleMember[]);
15895+
return getTupleTargetType(elementFlags, readonly, map((node as TupleTypeNode).elements, memberIfLabeledElementDeclaration));
15896+
}
15897+
15898+
function memberIfLabeledElementDeclaration(member: Node): NamedTupleMember | ParameterDeclaration | undefined{
15899+
return isNamedTupleMember(member) || isParameter(member) ? member : undefined;
1589515900
}
1589615901

1589715902
// Return true if the given type reference node is directly aliased or if it needs to be deferred
@@ -15981,21 +15986,22 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1598115986
return isTypeOperatorNode(node) && node.operator === SyntaxKind.ReadonlyKeyword;
1598215987
}
1598315988

15984-
function createTupleType(elementTypes: readonly Type[], elementFlags?: readonly ElementFlags[], readonly = false, namedMemberDeclarations?: readonly (NamedTupleMember | ParameterDeclaration)[]) {
15989+
function createTupleType(elementTypes: readonly Type[], elementFlags?: readonly ElementFlags[], readonly = false, namedMemberDeclarations?: readonly (NamedTupleMember | ParameterDeclaration | undefined)[]) {
1598515990
const tupleTarget = getTupleTargetType(elementFlags || map(elementTypes, _ => ElementFlags.Required), readonly, namedMemberDeclarations);
1598615991
return tupleTarget === emptyGenericType ? emptyObjectType :
1598715992
elementTypes.length ? createNormalizedTypeReference(tupleTarget, elementTypes) :
1598815993
tupleTarget;
1598915994
}
1599015995

15991-
function getTupleTargetType(elementFlags: readonly ElementFlags[], readonly: boolean, namedMemberDeclarations?: readonly (NamedTupleMember | ParameterDeclaration)[]): GenericType {
15996+
function getTupleTargetType(elementFlags: readonly ElementFlags[], readonly: boolean, namedMemberDeclarations?: readonly (NamedTupleMember | ParameterDeclaration | undefined)[]): GenericType {
1599215997
if (elementFlags.length === 1 && elementFlags[0] & ElementFlags.Rest) {
1599315998
// [...X[]] is equivalent to just X[]
1599415999
return readonly ? globalReadonlyArrayType : globalArrayType;
1599516000
}
16001+
const memberIds = namedMemberDeclarations?.length && mapAllOrFail(namedMemberDeclarations, node => node ? getNodeId(node) : undefined);
1599616002
const key = map(elementFlags, f => f & ElementFlags.Required ? "#" : f & ElementFlags.Optional ? "?" : f & ElementFlags.Rest ? "." : "*").join() +
1599716003
(readonly ? "R" : "") +
15998-
(namedMemberDeclarations && namedMemberDeclarations.length ? "," + map(namedMemberDeclarations, getNodeId).join(",") : "");
16004+
(memberIds ? "," + memberIds.join(",") : "");
1599916005
let type = tupleTypes.get(key);
1600016006
if (!type) {
1600116007
tupleTypes.set(key, type = createTupleTargetType(elementFlags, readonly, namedMemberDeclarations));
@@ -16010,7 +16016,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1601016016
//
1601116017
// Note that the generic type created by this function has no symbol associated with it. The same
1601216018
// is true for each of the synthesized type parameters.
16013-
function createTupleTargetType(elementFlags: readonly ElementFlags[], readonly: boolean, namedMemberDeclarations: readonly (NamedTupleMember | ParameterDeclaration)[] | undefined): TupleType {
16019+
function createTupleTargetType(elementFlags: readonly ElementFlags[], readonly: boolean, namedMemberDeclarations: readonly (NamedTupleMember | ParameterDeclaration | undefined)[] | undefined): TupleType {
1601416020
const arity = elementFlags.length;
1601516021
const minLength = countWhere(elementFlags, f => !!(f & (ElementFlags.Required | ElementFlags.Variadic)));
1601616022
let typeParameters: TypeParameter[] | undefined;
@@ -34513,7 +34519,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3451334519
return type;
3451434520
}
3451534521

34516-
function getTupleElementLabel(d: ParameterDeclaration | NamedTupleMember) {
34522+
function getTupleElementLabel(d: ParameterDeclaration | NamedTupleMember): __String;
34523+
function getTupleElementLabel(d: ParameterDeclaration | NamedTupleMember | undefined, index: number, restParameterName?: __String): __String;
34524+
function getTupleElementLabel(d: ParameterDeclaration | NamedTupleMember | undefined, index?: number, restParameterName = "arg" as __String) {
34525+
if (!d) {
34526+
return `${restParameterName}_${index}` as __String;
34527+
}
3451734528
Debug.assert(isIdentifier(d.name)); // Parameter declarations could be binding patterns, but we only allow identifier names
3451834529
return d.name.escapedText;
3451934530
}
@@ -34528,7 +34539,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3452834539
if (isTupleType(restType)) {
3452934540
const associatedNames = ((restType as TypeReference).target as TupleType).labeledElementDeclarations;
3453034541
const index = pos - paramCount;
34531-
return associatedNames && getTupleElementLabel(associatedNames[index]) || restParameter.escapedName + "_" + index as __String;
34542+
return getTupleElementLabel(associatedNames?.[index], index, restParameter.escapedName);
3453234543
}
3453334544
return restParameter.escapedName;
3453434545
}
@@ -38531,12 +38542,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3853138542
const elementTypes = node.elements;
3853238543
let seenOptionalElement = false;
3853338544
let seenRestElement = false;
38534-
const hasNamedElement = some(elementTypes, isNamedTupleMember);
3853538545
for (const e of elementTypes) {
38536-
if (e.kind !== SyntaxKind.NamedTupleMember && hasNamedElement) {
38537-
grammarErrorOnNode(e, Diagnostics.Tuple_members_must_all_have_names_or_all_not_have_names);
38538-
break;
38539-
}
3854038546
const flags = getTupleElementFlags(e);
3854138547
if (flags & ElementFlags.Variadic) {
3854238548
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
@@ -4233,10 +4233,6 @@
42334233
"category": "Error",
42344234
"code": 5083
42354235
},
4236-
"Tuple members must all have names or all not have names.": {
4237-
"category": "Error",
4238-
"code": 5084
4239-
},
42404236
"A tuple member cannot be both optional and rest.": {
42414237
"category": "Error",
42424238
"code": 5085

src/compiler/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6399,7 +6399,7 @@ export interface TupleType extends GenericType {
63996399
hasRestElement: boolean;
64006400
combinedFlags: ElementFlags;
64016401
readonly: boolean;
6402-
labeledElementDeclarations?: readonly (NamedTupleMember | ParameterDeclaration)[];
6402+
labeledElementDeclarations?: readonly (NamedTupleMember | ParameterDeclaration | undefined)[];
64036403
}
64046404

64056405
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
@@ -6830,7 +6830,7 @@ declare namespace ts {
68306830
hasRestElement: boolean;
68316831
combinedFlags: ElementFlags;
68326832
readonly: boolean;
6833-
labeledElementDeclarations?: readonly (NamedTupleMember | ParameterDeclaration)[];
6833+
labeledElementDeclarations?: readonly (NamedTupleMember | ParameterDeclaration | undefined)[];
68346834
}
68356835
interface TupleTypeReference extends TypeReference {
68366836
target: TupleType;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2887,7 +2887,7 @@ declare namespace ts {
28872887
hasRestElement: boolean;
28882888
combinedFlags: ElementFlags;
28892889
readonly: boolean;
2890-
labeledElementDeclarations?: readonly (NamedTupleMember | ParameterDeclaration)[];
2890+
labeledElementDeclarations?: readonly (NamedTupleMember | ParameterDeclaration | undefined)[];
28912891
}
28922892
interface TupleTypeReference extends TypeReference {
28932893
target: TupleType;

tests/baselines/reference/namedTupleMembersErrors.errors.txt

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
1-
tests/cases/conformance/types/tuple/named/namedTupleMembersErrors.ts(1,41): error TS5084: Tuple members must all have names or all not have names.
2-
tests/cases/conformance/types/tuple/named/namedTupleMembersErrors.ts(2,25): error TS5084: Tuple members must all have names or all not have names.
3-
tests/cases/conformance/types/tuple/named/namedTupleMembersErrors.ts(4,32): error TS5084: Tuple members must all have names or all not have names.
4-
tests/cases/conformance/types/tuple/named/namedTupleMembersErrors.ts(5,22): error TS5084: Tuple members must all have names or all not have names.
5-
tests/cases/conformance/types/tuple/named/namedTupleMembersErrors.ts(7,32): error TS5084: Tuple members must all have names or all not have names.
6-
tests/cases/conformance/types/tuple/named/namedTupleMembersErrors.ts(8,22): error TS5084: Tuple members must all have names or all not have names.
71
tests/cases/conformance/types/tuple/named/namedTupleMembersErrors.ts(10,29): error TS5086: A labeled tuple element is declared as optional with a question mark after the name and before the colon, rather than after the type.
82
tests/cases/conformance/types/tuple/named/namedTupleMembersErrors.ts(12,46): error TS5087: A labeled tuple element is declared as rest with a '...' before the name, rather than before the type.
93
tests/cases/conformance/types/tuple/named/namedTupleMembersErrors.ts(14,49): error TS5087: A labeled tuple element is declared as rest with a '...' before the name, rather than before the type.
@@ -14,27 +8,15 @@ tests/cases/conformance/types/tuple/named/namedTupleMembersErrors.ts(20,13): err
148
tests/cases/conformance/types/tuple/named/namedTupleMembersErrors.ts(21,13): error TS2456: Type alias 'RecusiveRest' circularly references itself.
159

1610

17-
==== tests/cases/conformance/types/tuple/named/namedTupleMembersErrors.ts (14 errors) ====
18-
export type Segment1 = [length: number, number]; // partially named, disallowed
19-
~~~~~~
20-
!!! error TS5084: Tuple members must all have names or all not have names.
21-
export type Segment2 = [number, size: number]; // partially named, disallowed
22-
~~~~~~
23-
!!! error TS5084: Tuple members must all have names or all not have names.
11+
==== tests/cases/conformance/types/tuple/named/namedTupleMembersErrors.ts (8 errors) ====
12+
export type Segment1 = [length: number, number];
13+
export type Segment2 = [number, size: number];
2414

25-
export type List = [item: any, ...any]; // partially named, disallowed
26-
~~~~~~
27-
!!! error TS5084: Tuple members must all have names or all not have names.
28-
export type List2 = [any, ...remainder: any]; // partially named, disallowed
29-
~~~
30-
!!! error TS5084: Tuple members must all have names or all not have names.
15+
export type List = [item: any, ...any];
16+
export type List2 = [any, ...remainder: any];
3117

32-
export type Pair = [item: any, any?]; // partially named, disallowed
33-
~~~~
34-
!!! error TS5084: Tuple members must all have names or all not have names.
35-
export type Pair2 = [any, last?: any]; // partially named, disallowed
36-
~~~
37-
!!! error TS5084: Tuple members must all have names or all not have names.
18+
export type Pair = [item: any, any?];
19+
export type Pair2 = [any, last?: any];
3820

3921
export type Opt = [element: string?]; // question mark on element disallowed
4022
~~~~~~~

tests/baselines/reference/namedTupleMembersErrors.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
//// [namedTupleMembersErrors.ts]
2-
export type Segment1 = [length: number, number]; // partially named, disallowed
3-
export type Segment2 = [number, size: number]; // partially named, disallowed
2+
export type Segment1 = [length: number, number];
3+
export type Segment2 = [number, size: number];
44

5-
export type List = [item: any, ...any]; // partially named, disallowed
6-
export type List2 = [any, ...remainder: any]; // partially named, disallowed
5+
export type List = [item: any, ...any];
6+
export type List2 = [any, ...remainder: any];
77

8-
export type Pair = [item: any, any?]; // partially named, disallowed
9-
export type Pair2 = [any, last?: any]; // partially named, disallowed
8+
export type Pair = [item: any, any?];
9+
export type Pair2 = [any, last?: any];
1010

1111
export type Opt = [element: string?]; // question mark on element disallowed
1212

tests/baselines/reference/namedTupleMembersErrors.symbols

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
=== tests/cases/conformance/types/tuple/named/namedTupleMembersErrors.ts ===
2-
export type Segment1 = [length: number, number]; // partially named, disallowed
2+
export type Segment1 = [length: number, number];
33
>Segment1 : Symbol(Segment1, Decl(namedTupleMembersErrors.ts, 0, 0))
44

5-
export type Segment2 = [number, size: number]; // partially named, disallowed
5+
export type Segment2 = [number, size: number];
66
>Segment2 : Symbol(Segment2, Decl(namedTupleMembersErrors.ts, 0, 48))
77

8-
export type List = [item: any, ...any]; // partially named, disallowed
8+
export type List = [item: any, ...any];
99
>List : Symbol(List, Decl(namedTupleMembersErrors.ts, 1, 46))
1010

11-
export type List2 = [any, ...remainder: any]; // partially named, disallowed
11+
export type List2 = [any, ...remainder: any];
1212
>List2 : Symbol(List2, Decl(namedTupleMembersErrors.ts, 3, 39))
1313

14-
export type Pair = [item: any, any?]; // partially named, disallowed
14+
export type Pair = [item: any, any?];
1515
>Pair : Symbol(Pair, Decl(namedTupleMembersErrors.ts, 4, 45))
1616

17-
export type Pair2 = [any, last?: any]; // partially named, disallowed
17+
export type Pair2 = [any, last?: any];
1818
>Pair2 : Symbol(Pair2, Decl(namedTupleMembersErrors.ts, 6, 37))
1919

2020
export type Opt = [element: string?]; // question mark on element disallowed

0 commit comments

Comments
 (0)