Skip to content

Commit 57a3e46

Browse files
allow spread in tuple types
1 parent d9951cb commit 57a3e46

16 files changed

+219
-7
lines changed

src/compiler/binder.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3351,6 +3351,7 @@ namespace ts {
33513351
case SyntaxKind.TypeLiteral:
33523352
case SyntaxKind.ArrayType:
33533353
case SyntaxKind.TupleType:
3354+
case SyntaxKind.TypeSpread:
33543355
case SyntaxKind.UnionType:
33553356
case SyntaxKind.IntersectionType:
33563357
case SyntaxKind.ParenthesizedType:

src/compiler/checker.ts

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2555,6 +2555,10 @@ namespace ts {
25552555
const indexTypeNode = typeToTypeNodeHelper((<IndexedAccessType>type).indexType, context);
25562556
return createIndexedAccessTypeNode(objectTypeNode, indexTypeNode);
25572557
}
2558+
if (type.flags & TypeFlags.SpreadTuple) {
2559+
const typeNodes = map((<SpreadTupleType>type).elements, (tp: Type) => typeToTypeNodeHelper(tp, context));
2560+
return createTupleTypeNode(typeNodes, (<SpreadTupleType>type).idxIsSpread);
2561+
}
25582562

25592563
Debug.fail("Should be unreachable.");
25602564

@@ -3324,6 +3328,12 @@ namespace ts {
33243328
writeType((<IndexedAccessType>type).indexType, TypeFormatFlags.None);
33253329
writePunctuation(writer, SyntaxKind.CloseBracketToken);
33263330
}
3331+
else if (type.flags & TypeFlags.SpreadTuple) {
3332+
writePunctuation(writer, SyntaxKind.OpenBracketToken);
3333+
writePunctuation(writer, SyntaxKind.DotDotDotToken);
3334+
writeTypeList((<SpreadTupleType>type).elements, SyntaxKind.CommaToken, (<SpreadTupleType>type).idxIsSpread);
3335+
writePunctuation(writer, SyntaxKind.CloseBracketToken);
3336+
}
33273337
else {
33283338
// Should never get here
33293339
// { ... }
@@ -3336,7 +3346,7 @@ namespace ts {
33363346
}
33373347

33383348

3339-
function writeTypeList(types: Type[], delimiter: SyntaxKind) {
3349+
function writeTypeList(types: Type[], delimiter: SyntaxKind, idxIsSpread: boolean[] = []) {
33403350
for (let i = 0; i < types.length; i++) {
33413351
if (i > 0) {
33423352
if (delimiter !== SyntaxKind.CommaToken) {
@@ -3345,6 +3355,9 @@ namespace ts {
33453355
writePunctuation(writer, delimiter);
33463356
writeSpace(writer);
33473357
}
3358+
if (idxIsSpread[i]) {
3359+
writePunctuation(writer, SyntaxKind.DotDotDotToken);
3360+
}
33483361
writeType(types[i], delimiter === SyntaxKind.CommaToken ? TypeFormatFlags.None : TypeFormatFlags.InElementType);
33493362
}
33503363
}
@@ -7262,11 +7275,67 @@ namespace ts {
72627275
function getTypeFromTupleTypeNode(node: TupleTypeNode): Type {
72637276
const links = getNodeLinks(node);
72647277
if (!links.resolvedType) {
7265-
links.resolvedType = createTupleType(map(node.elementTypes, getTypeFromTypeNode));
7278+
links.resolvedType = getTypeForTupleNode(node);
72667279
}
72677280
return links.resolvedType;
72687281
}
72697282

7283+
function getSpreadTupleTypes(elements: Type[], idxIsSpread: boolean[]): Type {
7284+
return createTupleType(flatMap(elements, (tp: Type, i: number) => idxIsSpread[i] ? getTypeSpreadTypes(tp) : tp));
7285+
}
7286+
7287+
function getTypeSpreadTypes(tuple: Type): Type[] {
7288+
if (isTupleLikeType(tuple)) {
7289+
return getTupleTypeElementTypes(tuple);
7290+
}
7291+
else {
7292+
// console.error("not a tuple, don't resolve?");
7293+
return [];
7294+
}
7295+
}
7296+
7297+
function isGenericTupleType(type: Type): boolean {
7298+
return type.flags & TypeFlags.TypeVariable ? true :
7299+
type.flags & TypeFlags.UnionOrIntersection ? forEach((<UnionOrIntersectionType>type).types, isGenericTupleType) :
7300+
false;
7301+
}
7302+
7303+
function getTypeForTupleNode(node: TupleTypeNode): Type {
7304+
if (some(node.elementTypes, (n: TypeNode) => n.kind === SyntaxKind.TypeSpread &&
7305+
isGenericTupleType(getTypeFromTypeNode((n as TypeSpreadTypeNode).type)))) {
7306+
const elements = map(node.elementTypes, (n: TypeNode) => getTypeFromTypeNode(n.kind === SyntaxKind.TypeSpread ? (n as TypeSpreadTypeNode).type : n));
7307+
const idxIsSpread = map(node.elementTypes, (n: TypeNode) => n.kind === SyntaxKind.TypeSpread);
7308+
return createTupleSpreadType(elements, idxIsSpread);
7309+
}
7310+
else {
7311+
return createTupleType(flatMap(node.elementTypes, getTypeFromTupleElement));
7312+
}
7313+
}
7314+
7315+
function getTupleTypeElementTypes(type: Type): Type[] {
7316+
Debug.assert(isTupleLikeType(type));
7317+
const types = [];
7318+
let idx = 0;
7319+
let symbol: Symbol;
7320+
while (symbol = getPropertyOfObjectType(type, idx++ + "" as __String)) {
7321+
types.push(getTypeOfSymbol(symbol));
7322+
}
7323+
return types;
7324+
}
7325+
7326+
function getTypeFromTupleElement(node: TypeNode | TypeSpreadTypeNode): Type | Type[] {
7327+
if (node.kind === SyntaxKind.TypeSpread) {
7328+
const links = getNodeLinks(node);
7329+
if (!links.resolvedType) {
7330+
links.resolvedType = getTypeFromTypeNode((node as TypeSpreadTypeNode).type);
7331+
}
7332+
return getTypeSpreadTypes(links.resolvedType);
7333+
}
7334+
else {
7335+
return getTypeFromTypeNode(node as TypeNode);
7336+
}
7337+
}
7338+
72707339
interface TypeSet extends Array<Type> {
72717340
containsAny?: boolean;
72727341
containsUndefined?: boolean;
@@ -7630,6 +7699,13 @@ namespace ts {
76307699
return type;
76317700
}
76327701

7702+
function createTupleSpreadType(elements: Type[], idxIsSpread: boolean[]) {
7703+
const type = <SpreadTupleType>createType(TypeFlags.SpreadTuple);
7704+
type.elements = elements;
7705+
type.idxIsSpread = idxIsSpread;
7706+
return type;
7707+
}
7708+
76337709
function getPropertyTypeForIndexType(objectType: Type, indexType: Type, accessNode: ElementAccessExpression | IndexedAccessTypeNode, cacheSymbol: boolean) {
76347710
const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? <ElementAccessExpression>accessNode : undefined;
76357711
const propName = indexType.flags & TypeFlags.StringOrNumberLiteral ?
@@ -8366,6 +8442,9 @@ namespace ts {
83668442
return getIndexedAccessType(instantiateType((<IndexedAccessType>type).objectType, mapper), instantiateType((<IndexedAccessType>type).indexType, mapper));
83678443
}
83688444
}
8445+
if (type.flags & TypeFlags.SpreadTuple) {
8446+
return getSpreadTupleTypes(instantiateTypes((<SpreadTupleType>type).elements, mapper), (<SpreadTupleType>type).idxIsSpread);
8447+
}
83698448
return type;
83708449
}
83718450

@@ -18930,6 +19009,14 @@ namespace ts {
1893019009
forEach(node.elementTypes, checkSourceElement);
1893119010
}
1893219011

19012+
function checkTypeSpreadTypeNode(node: TypeSpreadTypeNode) {
19013+
checkSourceElement(node.type);
19014+
const type = getApparentType(getTypeFromTypeNode(node.type));
19015+
if (!isArrayLikeType(type)) { // isTupleLikeType
19016+
grammarErrorOnNode(node, Diagnostics.Tuple_type_spreads_may_only_be_created_from_tuple_types);
19017+
}
19018+
}
19019+
1893319020
function checkUnionOrIntersectionType(node: UnionOrIntersectionTypeNode) {
1893419021
forEach(node.types, checkSourceElement);
1893519022
}
@@ -22494,6 +22581,8 @@ namespace ts {
2249422581
return checkArrayType(<ArrayTypeNode>node);
2249522582
case SyntaxKind.TupleType:
2249622583
return checkTupleType(<TupleTypeNode>node);
22584+
case SyntaxKind.TypeSpread:
22585+
return checkTypeSpreadTypeNode(<TypeSpreadTypeNode>node);
2249722586
case SyntaxKind.UnionType:
2249822587
case SyntaxKind.IntersectionType:
2249922588
return checkUnionOrIntersectionType(<UnionOrIntersectionTypeNode>node);

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2216,6 +2216,10 @@
22162216
"category": "Error",
22172217
"code": 2714
22182218
},
2219+
"Tuple type spreads may only be created from tuple types.": {
2220+
"category": "Error",
2221+
"code": 2715
2222+
},
22192223

22202224
"Import declaration '{0}' is using private name '{1}'.": {
22212225
"category": "Error",

src/compiler/emitter.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -712,6 +712,8 @@ namespace ts {
712712
return emitShorthandPropertyAssignment(<ShorthandPropertyAssignment>node);
713713
case SyntaxKind.SpreadAssignment:
714714
return emitSpreadAssignment(node as SpreadAssignment);
715+
case SyntaxKind.TypeSpread:
716+
return emitTypeSpread(node as TypeSpreadTypeNode);
715717

716718
// Enum
717719
case SyntaxKind.EnumMember:
@@ -2183,6 +2185,13 @@ namespace ts {
21832185
}
21842186
}
21852187

2188+
function emitTypeSpread(node: TypeSpreadTypeNode) {
2189+
if (node.type) {
2190+
write("...");
2191+
emit(node.type);
2192+
}
2193+
}
2194+
21862195
//
21872196
// Enum
21882197
//

src/compiler/factory.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -674,9 +674,10 @@ namespace ts {
674674
: node;
675675
}
676676

677-
export function createTupleTypeNode(elementTypes: ReadonlyArray<TypeNode>) {
677+
export function createTupleTypeNode(elementTypes: ReadonlyArray<TypeNode>, idxIsSpread: boolean[] = []) {
678678
const node = createSynthesizedNode(SyntaxKind.TupleType) as TupleTypeNode;
679-
node.elementTypes = createNodeArray(elementTypes);
679+
const elements = map(elementTypes, (type: TypeNode, idx: number) => idxIsSpread[idx] ? createTypeSpread(type) : type);
680+
node.elementTypes = createNodeArray(elements);
680681
return node;
681682
}
682683

@@ -2235,6 +2236,18 @@ namespace ts {
22352236
: node;
22362237
}
22372238

2239+
export function createTypeSpread(type: TypeNode) {
2240+
const node = <TypeSpreadTypeNode>createSynthesizedNode(SyntaxKind.TypeSpread);
2241+
node.type = type !== undefined ? parenthesizeElementTypeMember(type) : undefined;
2242+
return node;
2243+
}
2244+
2245+
export function updateTypeSpread(node: TypeSpreadTypeNode, type: TypeNode) {
2246+
return node.type !== type
2247+
? updateNode(createTypeSpread(type), node)
2248+
: node;
2249+
}
2250+
22382251
// Enum
22392252

22402253
export function createEnumMember(name: string | PropertyName, initializer?: Expression) {

src/compiler/parser.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ namespace ts {
8585
visitNode(cbNode, (<ShorthandPropertyAssignment>node).objectAssignmentInitializer);
8686
case SyntaxKind.SpreadAssignment:
8787
return visitNode(cbNode, (<SpreadAssignment>node).expression);
88+
case SyntaxKind.TypeSpread:
89+
return visitNode(cbNode, (<TypeSpreadTypeNode>node).type);
8890
case SyntaxKind.Parameter:
8991
case SyntaxKind.PropertyDeclaration:
9092
case SyntaxKind.PropertySignature:
@@ -1380,8 +1382,9 @@ namespace ts {
13801382
case ParsingContext.Parameters:
13811383
return isStartOfParameter();
13821384
case ParsingContext.TypeArguments:
1383-
case ParsingContext.TupleElementTypes:
13841385
return token() === SyntaxKind.CommaToken || isStartOfType();
1386+
case ParsingContext.TupleElementTypes:
1387+
return token() === SyntaxKind.CommaToken || token() === SyntaxKind.DotDotDotToken || isStartOfType();
13851388
case ParsingContext.HeritageClauses:
13861389
return isHeritageClause();
13871390
case ParsingContext.ImportOrExportSpecifiers:
@@ -2583,7 +2586,7 @@ namespace ts {
25832586

25842587
function parseTupleType(): TupleTypeNode {
25852588
const node = <TupleTypeNode>createNode(SyntaxKind.TupleType);
2586-
node.elementTypes = parseBracketedList(ParsingContext.TupleElementTypes, parseType, SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken);
2589+
node.elementTypes = parseBracketedList(ParsingContext.TupleElementTypes, parseTupleElement, SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken);
25872590
return finishNode(node);
25882591
}
25892592

@@ -5157,6 +5160,19 @@ namespace ts {
51575160
return finishNode(node);
51585161
}
51595162

5163+
function parseTupleElement(): TypeSpreadTypeNode | TypeNode {
5164+
return (token() === SyntaxKind.DotDotDotToken) ?
5165+
parseTypeSpread() :
5166+
parseType();
5167+
}
5168+
5169+
function parseTypeSpread(): TypeSpreadTypeNode {
5170+
const node = <TypeSpreadTypeNode>createNode(SyntaxKind.TypeSpread);
5171+
parseExpected(SyntaxKind.DotDotDotToken);
5172+
node.type = parseTypeOperatorOrHigher();
5173+
return finishNode(node);
5174+
}
5175+
51605176
function parseObjectBindingElement(): BindingElement {
51615177
const node = <BindingElement>createNode(SyntaxKind.BindingElement);
51625178
node.dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken);

src/compiler/types.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,7 @@ namespace ts {
343343
PropertyAssignment,
344344
ShorthandPropertyAssignment,
345345
SpreadAssignment,
346+
TypeSpread,
346347

347348
// Enum
348349
EnumMember,
@@ -1007,7 +1008,12 @@ namespace ts {
10071008

10081009
export interface TupleTypeNode extends TypeNode {
10091010
kind: SyntaxKind.TupleType;
1010-
elementTypes: NodeArray<TypeNode>;
1011+
elementTypes: NodeArray<TypeNode | TypeSpreadTypeNode>;
1012+
}
1013+
1014+
export interface TypeSpreadTypeNode extends TypeNode {
1015+
kind: SyntaxKind.TypeSpread;
1016+
type: TypeNode;
10111017
}
10121018

10131019
export type UnionOrIntersectionTypeNode = UnionTypeNode | IntersectionTypeNode;
@@ -3215,6 +3221,7 @@ namespace ts {
32153221
NonPrimitive = 1 << 24, // intrinsic object type
32163222
/* @internal */
32173223
JsxAttributes = 1 << 25, // Jsx attributes type
3224+
SpreadTuple = 1 << 26, // tuple types containing spreads
32183225

32193226
/* @internal */
32203227
Nullable = Undefined | Null,
@@ -3463,6 +3470,12 @@ namespace ts {
34633470
constraint?: Type;
34643471
}
34653472

3473+
// spread tuple types (TypeFlags.SpreadTuple)
3474+
export interface SpreadTupleType extends Type {
3475+
elements: Type[];
3476+
idxIsSpread: boolean[];
3477+
}
3478+
34663479
// keyof T types (TypeFlags.Index)
34673480
export interface IndexType extends Type {
34683481
type: TypeVariable | UnionOrIntersectionType;

src/compiler/utilities.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4695,6 +4695,10 @@ namespace ts {
46954695
return node.kind === SyntaxKind.SpreadAssignment;
46964696
}
46974697

4698+
export function isTypeSpread(node: Node): node is TypeSpreadTypeNode {
4699+
return node.kind === SyntaxKind.TypeSpread;
4700+
}
4701+
46984702
// Enum
46994703

47004704
export function isEnumMember(node: Node): node is EnumMember {

src/compiler/visitor.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -872,6 +872,10 @@ namespace ts {
872872
return updateSpreadAssignment(<SpreadAssignment>node,
873873
visitNode((<SpreadAssignment>node).expression, visitor, isExpression));
874874

875+
case SyntaxKind.TypeSpread:
876+
return updateTypeSpread(<TypeSpreadTypeNode>node,
877+
visitNode((<TypeSpreadTypeNode>node).type, visitor, isTypeNode));
878+
875879
// Enum
876880
case SyntaxKind.EnumMember:
877881
return updateEnumMember(<EnumMember>node,
@@ -1394,6 +1398,10 @@ namespace ts {
13941398
result = reduceNode((<SpreadAssignment>node).expression, cbNode, result);
13951399
break;
13961400

1401+
case SyntaxKind.TypeSpread:
1402+
result = reduceNode((<TypeSpreadTypeNode>node).type, cbNode, result);
1403+
break;
1404+
13971405
// Enum
13981406
case SyntaxKind.EnumMember:
13991407
result = reduceNode((<EnumMember>node).name, cbNode, result);
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
//// [tupleTypeSpread.ts]
2+
type a = [1, ...[2]];
3+
type Combine<Head, Tail extends any[]> = [Head, ...Tail];
4+
type b = Combine<1, [2, 3]>;
5+
6+
7+
//// [tupleTypeSpread.js]
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
=== tests/cases/compiler/tupleTypeSpread.ts ===
2+
type a = [1, ...[2]];
3+
>a : Symbol(a, Decl(tupleTypeSpread.ts, 0, 0))
4+
5+
type Combine<Head, Tail extends any[]> = [Head, ...Tail];
6+
>Combine : Symbol(Combine, Decl(tupleTypeSpread.ts, 0, 21))
7+
>Head : Symbol(Head, Decl(tupleTypeSpread.ts, 1, 13))
8+
>Tail : Symbol(Tail, Decl(tupleTypeSpread.ts, 1, 18))
9+
>Head : Symbol(Head, Decl(tupleTypeSpread.ts, 1, 13))
10+
>Tail : Symbol(Tail, Decl(tupleTypeSpread.ts, 1, 18))
11+
12+
type b = Combine<1, [2, 3]>;
13+
>b : Symbol(b, Decl(tupleTypeSpread.ts, 1, 57))
14+
>Combine : Symbol(Combine, Decl(tupleTypeSpread.ts, 0, 21))
15+
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
=== tests/cases/compiler/tupleTypeSpread.ts ===
2+
type a = [1, ...[2]];
3+
>a : [1, 2]
4+
5+
type Combine<Head, Tail extends any[]> = [Head, ...Tail];
6+
>Combine : [Head, ...Tail]
7+
>Head : Head
8+
>Tail : Tail
9+
>Head : Head
10+
>Tail : Tail
11+
12+
type b = Combine<1, [2, 3]>;
13+
>b : [1, 2, 3]
14+
>Combine : [Head, ...Tail]
15+

0 commit comments

Comments
 (0)