Skip to content

Commit

Permalink
allow spread in tuple types
Browse files Browse the repository at this point in the history
  • Loading branch information
KiaraGrouwstra committed Sep 21, 2017
1 parent d9951cb commit 57a3e46
Show file tree
Hide file tree
Showing 16 changed files with 219 additions and 7 deletions.
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

0 comments on commit 57a3e46

Please sign in to comment.