Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tuple type spread #17884

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3351,6 +3351,7 @@ namespace ts {
case SyntaxKind.TypeLiteral:
case SyntaxKind.ArrayType:
case SyntaxKind.TupleType:
case SyntaxKind.TypeSpread:
case SyntaxKind.UnionType:
case SyntaxKind.IntersectionType:
case SyntaxKind.ParenthesizedType:
Expand Down
93 changes: 91 additions & 2 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2555,6 +2555,10 @@ namespace ts {
const indexTypeNode = typeToTypeNodeHelper((<IndexedAccessType>type).indexType, context);
return createIndexedAccessTypeNode(objectTypeNode, indexTypeNode);
}
if (type.flags & TypeFlags.SpreadTuple) {
const typeNodes = map((<SpreadTupleType>type).elements, (tp: Type) => typeToTypeNodeHelper(tp, context));
return createTupleTypeNode(typeNodes, (<SpreadTupleType>type).idxIsSpread);
}

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

Expand Down Expand Up @@ -3324,6 +3328,12 @@ namespace ts {
writeType((<IndexedAccessType>type).indexType, TypeFormatFlags.None);
writePunctuation(writer, SyntaxKind.CloseBracketToken);
}
else if (type.flags & TypeFlags.SpreadTuple) {
writePunctuation(writer, SyntaxKind.OpenBracketToken);
writePunctuation(writer, SyntaxKind.DotDotDotToken);
writeTypeList((<SpreadTupleType>type).elements, SyntaxKind.CommaToken, (<SpreadTupleType>type).idxIsSpread);
writePunctuation(writer, SyntaxKind.CloseBracketToken);
}
else {
// Should never get here
// { ... }
Expand All @@ -3336,7 +3346,7 @@ namespace ts {
}


function writeTypeList(types: Type[], delimiter: SyntaxKind) {
function writeTypeList(types: Type[], delimiter: SyntaxKind, idxIsSpread: boolean[] = []) {
for (let i = 0; i < types.length; i++) {
if (i > 0) {
if (delimiter !== SyntaxKind.CommaToken) {
Expand All @@ -3345,6 +3355,9 @@ namespace ts {
writePunctuation(writer, delimiter);
writeSpace(writer);
}
if (idxIsSpread[i]) {
writePunctuation(writer, SyntaxKind.DotDotDotToken);
}
writeType(types[i], delimiter === SyntaxKind.CommaToken ? TypeFormatFlags.None : TypeFormatFlags.InElementType);
}
}
Expand Down Expand Up @@ -7262,11 +7275,67 @@ namespace ts {
function getTypeFromTupleTypeNode(node: TupleTypeNode): Type {
const links = getNodeLinks(node);
if (!links.resolvedType) {
links.resolvedType = createTupleType(map(node.elementTypes, getTypeFromTypeNode));
links.resolvedType = getTypeForTupleNode(node);
}
return links.resolvedType;
}

function getSpreadTupleTypes(elements: Type[], idxIsSpread: boolean[]): Type {
return createTupleType(flatMap(elements, (tp: Type, i: number) => idxIsSpread[i] ? getTypeSpreadTypes(tp) : tp));
}

function getTypeSpreadTypes(tuple: Type): Type[] {
if (isTupleLikeType(tuple)) {
return getTupleTypeElementTypes(tuple);
}
else {
// console.error("not a tuple, don't resolve?");
return [];
}
}

function isGenericTupleType(type: Type): boolean {
return type.flags & TypeFlags.TypeVariable ? true :
type.flags & TypeFlags.UnionOrIntersection ? forEach((<UnionOrIntersectionType>type).types, isGenericTupleType) :
false;
}

function getTypeForTupleNode(node: TupleTypeNode): Type {
if (some(node.elementTypes, (n: TypeNode) => n.kind === SyntaxKind.TypeSpread &&
isGenericTupleType(getTypeFromTypeNode((n as TypeSpreadTypeNode).type)))) {
const elements = map(node.elementTypes, (n: TypeNode) => getTypeFromTypeNode(n.kind === SyntaxKind.TypeSpread ? (n as TypeSpreadTypeNode).type : n));
const idxIsSpread = map(node.elementTypes, (n: TypeNode) => n.kind === SyntaxKind.TypeSpread);
return createTupleSpreadType(elements, idxIsSpread);
}
else {
return createTupleType(flatMap(node.elementTypes, getTypeFromTupleElement));
}
}

function getTupleTypeElementTypes(type: Type): Type[] {
Debug.assert(isTupleLikeType(type));
const types = [];
let idx = 0;
let symbol: Symbol;
while (symbol = getPropertyOfObjectType(type, idx++ + "" as __String)) {
types.push(getTypeOfSymbol(symbol));
}
return types;
}

function getTypeFromTupleElement(node: TypeNode | TypeSpreadTypeNode): Type | Type[] {
if (node.kind === SyntaxKind.TypeSpread) {
const links = getNodeLinks(node);
if (!links.resolvedType) {
links.resolvedType = getTypeFromTypeNode((node as TypeSpreadTypeNode).type);
}
return getTypeSpreadTypes(links.resolvedType);
}
else {
return getTypeFromTypeNode(node as TypeNode);
}
}

interface TypeSet extends Array<Type> {
containsAny?: boolean;
containsUndefined?: boolean;
Expand Down Expand Up @@ -7630,6 +7699,13 @@ namespace ts {
return type;
}

function createTupleSpreadType(elements: Type[], idxIsSpread: boolean[]) {
const type = <SpreadTupleType>createType(TypeFlags.SpreadTuple);
type.elements = elements;
type.idxIsSpread = idxIsSpread;
return type;
}

function getPropertyTypeForIndexType(objectType: Type, indexType: Type, accessNode: ElementAccessExpression | IndexedAccessTypeNode, cacheSymbol: boolean) {
const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? <ElementAccessExpression>accessNode : undefined;
const propName = indexType.flags & TypeFlags.StringOrNumberLiteral ?
Expand Down Expand Up @@ -8366,6 +8442,9 @@ namespace ts {
return getIndexedAccessType(instantiateType((<IndexedAccessType>type).objectType, mapper), instantiateType((<IndexedAccessType>type).indexType, mapper));
}
}
if (type.flags & TypeFlags.SpreadTuple) {
return getSpreadTupleTypes(instantiateTypes((<SpreadTupleType>type).elements, mapper), (<SpreadTupleType>type).idxIsSpread);
}
return type;
}

Expand Down Expand Up @@ -18930,6 +19009,14 @@ namespace ts {
forEach(node.elementTypes, checkSourceElement);
}

function checkTypeSpreadTypeNode(node: TypeSpreadTypeNode) {
checkSourceElement(node.type);
const type = getApparentType(getTypeFromTypeNode(node.type));
if (!isArrayLikeType(type)) { // isTupleLikeType
grammarErrorOnNode(node, Diagnostics.Tuple_type_spreads_may_only_be_created_from_tuple_types);
}
}

function checkUnionOrIntersectionType(node: UnionOrIntersectionTypeNode) {
forEach(node.types, checkSourceElement);
}
Expand Down Expand Up @@ -22494,6 +22581,8 @@ namespace ts {
return checkArrayType(<ArrayTypeNode>node);
case SyntaxKind.TupleType:
return checkTupleType(<TupleTypeNode>node);
case SyntaxKind.TypeSpread:
return checkTypeSpreadTypeNode(<TypeSpreadTypeNode>node);
case SyntaxKind.UnionType:
case SyntaxKind.IntersectionType:
return checkUnionOrIntersectionType(<UnionOrIntersectionTypeNode>node);
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -2216,6 +2216,10 @@
"category": "Error",
"code": 2714
},
"Tuple type spreads may only be created from tuple types.": {
"category": "Error",
"code": 2715
},

"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",
Expand Down
9 changes: 9 additions & 0 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,8 @@ namespace ts {
return emitShorthandPropertyAssignment(<ShorthandPropertyAssignment>node);
case SyntaxKind.SpreadAssignment:
return emitSpreadAssignment(node as SpreadAssignment);
case SyntaxKind.TypeSpread:
return emitTypeSpread(node as TypeSpreadTypeNode);

// Enum
case SyntaxKind.EnumMember:
Expand Down Expand Up @@ -2183,6 +2185,13 @@ namespace ts {
}
}

function emitTypeSpread(node: TypeSpreadTypeNode) {
if (node.type) {
write("...");
emit(node.type);
}
}

//
// Enum
//
Expand Down
17 changes: 15 additions & 2 deletions src/compiler/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -674,9 +674,10 @@ namespace ts {
: node;
}

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

Expand Down Expand Up @@ -2235,6 +2236,18 @@ namespace ts {
: node;
}

export function createTypeSpread(type: TypeNode) {
const node = <TypeSpreadTypeNode>createSynthesizedNode(SyntaxKind.TypeSpread);
node.type = type !== undefined ? parenthesizeElementTypeMember(type) : undefined;
return node;
}

export function updateTypeSpread(node: TypeSpreadTypeNode, type: TypeNode) {
return node.type !== type
? updateNode(createTypeSpread(type), node)
: node;
}

// Enum

export function createEnumMember(name: string | PropertyName, initializer?: Expression) {
Expand Down
20 changes: 18 additions & 2 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ namespace ts {
visitNode(cbNode, (<ShorthandPropertyAssignment>node).objectAssignmentInitializer);
case SyntaxKind.SpreadAssignment:
return visitNode(cbNode, (<SpreadAssignment>node).expression);
case SyntaxKind.TypeSpread:
return visitNode(cbNode, (<TypeSpreadTypeNode>node).type);
case SyntaxKind.Parameter:
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.PropertySignature:
Expand Down Expand Up @@ -1380,8 +1382,9 @@ namespace ts {
case ParsingContext.Parameters:
return isStartOfParameter();
case ParsingContext.TypeArguments:
case ParsingContext.TupleElementTypes:
return token() === SyntaxKind.CommaToken || isStartOfType();
case ParsingContext.TupleElementTypes:
return token() === SyntaxKind.CommaToken || token() === SyntaxKind.DotDotDotToken || isStartOfType();
case ParsingContext.HeritageClauses:
return isHeritageClause();
case ParsingContext.ImportOrExportSpecifiers:
Expand Down Expand Up @@ -2583,7 +2586,7 @@ namespace ts {

function parseTupleType(): TupleTypeNode {
const node = <TupleTypeNode>createNode(SyntaxKind.TupleType);
node.elementTypes = parseBracketedList(ParsingContext.TupleElementTypes, parseType, SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken);
node.elementTypes = parseBracketedList(ParsingContext.TupleElementTypes, parseTupleElement, SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken);
return finishNode(node);
}

Expand Down Expand Up @@ -5157,6 +5160,19 @@ namespace ts {
return finishNode(node);
}

function parseTupleElement(): TypeSpreadTypeNode | TypeNode {
return (token() === SyntaxKind.DotDotDotToken) ?
parseTypeSpread() :
parseType();
}

function parseTypeSpread(): TypeSpreadTypeNode {
const node = <TypeSpreadTypeNode>createNode(SyntaxKind.TypeSpread);
parseExpected(SyntaxKind.DotDotDotToken);
node.type = parseTypeOperatorOrHigher();
return finishNode(node);
}

function parseObjectBindingElement(): BindingElement {
const node = <BindingElement>createNode(SyntaxKind.BindingElement);
node.dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken);
Expand Down
15 changes: 14 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ namespace ts {
PropertyAssignment,
ShorthandPropertyAssignment,
SpreadAssignment,
TypeSpread,

// Enum
EnumMember,
Expand Down Expand Up @@ -1007,7 +1008,12 @@ namespace ts {

export interface TupleTypeNode extends TypeNode {
kind: SyntaxKind.TupleType;
elementTypes: NodeArray<TypeNode>;
elementTypes: NodeArray<TypeNode | TypeSpreadTypeNode>;
}

export interface TypeSpreadTypeNode extends TypeNode {
kind: SyntaxKind.TypeSpread;
type: TypeNode;
}

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

/* @internal */
Nullable = Undefined | Null,
Expand Down Expand Up @@ -3463,6 +3470,12 @@ namespace ts {
constraint?: Type;
}

// spread tuple types (TypeFlags.SpreadTuple)
export interface SpreadTupleType extends Type {
elements: Type[];
idxIsSpread: boolean[];
}

// keyof T types (TypeFlags.Index)
export interface IndexType extends Type {
type: TypeVariable | UnionOrIntersectionType;
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4695,6 +4695,10 @@ namespace ts {
return node.kind === SyntaxKind.SpreadAssignment;
}

export function isTypeSpread(node: Node): node is TypeSpreadTypeNode {
return node.kind === SyntaxKind.TypeSpread;
}

// Enum

export function isEnumMember(node: Node): node is EnumMember {
Expand Down
8 changes: 8 additions & 0 deletions src/compiler/visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -872,6 +872,10 @@ namespace ts {
return updateSpreadAssignment(<SpreadAssignment>node,
visitNode((<SpreadAssignment>node).expression, visitor, isExpression));

case SyntaxKind.TypeSpread:
return updateTypeSpread(<TypeSpreadTypeNode>node,
visitNode((<TypeSpreadTypeNode>node).type, visitor, isTypeNode));

// Enum
case SyntaxKind.EnumMember:
return updateEnumMember(<EnumMember>node,
Expand Down Expand Up @@ -1394,6 +1398,10 @@ namespace ts {
result = reduceNode((<SpreadAssignment>node).expression, cbNode, result);
break;

case SyntaxKind.TypeSpread:
result = reduceNode((<TypeSpreadTypeNode>node).type, cbNode, result);
break;

// Enum
case SyntaxKind.EnumMember:
result = reduceNode((<EnumMember>node).name, cbNode, result);
Expand Down
7 changes: 7 additions & 0 deletions tests/baselines/reference/tupleTypeSpread.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//// [tupleTypeSpread.ts]
type a = [1, ...[2]];
type Combine<Head, Tail extends any[]> = [Head, ...Tail];
type b = Combine<1, [2, 3]>;


//// [tupleTypeSpread.js]
15 changes: 15 additions & 0 deletions tests/baselines/reference/tupleTypeSpread.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
=== tests/cases/compiler/tupleTypeSpread.ts ===
type a = [1, ...[2]];
>a : Symbol(a, Decl(tupleTypeSpread.ts, 0, 0))

type Combine<Head, Tail extends any[]> = [Head, ...Tail];
>Combine : Symbol(Combine, Decl(tupleTypeSpread.ts, 0, 21))
>Head : Symbol(Head, Decl(tupleTypeSpread.ts, 1, 13))
>Tail : Symbol(Tail, Decl(tupleTypeSpread.ts, 1, 18))
>Head : Symbol(Head, Decl(tupleTypeSpread.ts, 1, 13))
>Tail : Symbol(Tail, Decl(tupleTypeSpread.ts, 1, 18))

type b = Combine<1, [2, 3]>;
>b : Symbol(b, Decl(tupleTypeSpread.ts, 1, 57))
>Combine : Symbol(Combine, Decl(tupleTypeSpread.ts, 0, 21))

15 changes: 15 additions & 0 deletions tests/baselines/reference/tupleTypeSpread.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
=== tests/cases/compiler/tupleTypeSpread.ts ===
type a = [1, ...[2]];
>a : [1, 2]

type Combine<Head, Tail extends any[]> = [Head, ...Tail];
>Combine : [Head, ...Tail]
>Head : Head
>Tail : Tail
>Head : Head
>Tail : Tail

type b = Combine<1, [2, 3]>;
>b : [1, 2, 3]
>Combine : [Head, ...Tail]

Loading