Skip to content
Prev Previous commit
Next Next commit
Change spread type syntax to spread(T, U)
`spread(T)` is also allowed, for unary spreads
  • Loading branch information
sandersn committed Jan 12, 2017
commit 268969bfc390c5f906f9b1017280f056439b4fd9
149 changes: 39 additions & 110 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2440,45 +2440,25 @@ namespace ts {
}
}

function writeSpreadType(type: SpreadType) {
writePunctuation(writer, SyntaxKind.OpenBraceToken);
writer.writeLine();
writer.increaseIndent();

writeSpreadTypeWorker(type, /*atEnd*/true);

writer.decreaseIndent();
writePunctuation(writer, SyntaxKind.CloseBraceToken);
}

function writeSpreadTypeWorker(type: SpreadType, atEnd: boolean): void {
if (type.left.flags & TypeFlags.Spread) {
writeSpreadTypeWorker(type.left as SpreadType, /*atEnd*/false);
}
else {
const saveInObjectTypeLiteral = inObjectTypeLiteral;
inObjectTypeLiteral = true;
writeObjectLiteralType(resolveStructuredTypeMembers(type.left as ResolvedType));
inObjectTypeLiteral = saveInObjectTypeLiteral;
}
if (type.right.flags & TypeFlags.Object) {
// if type.right is an object type, don't surround with ...{ }.
// this gives { a: number, ... T } instead of { ...{ a: number }, ...T }
const saveInObjectTypeLiteral = inObjectTypeLiteral;
inObjectTypeLiteral = true;
writeObjectLiteralType(resolveStructuredTypeMembers(type.right as ResolvedType));
inObjectTypeLiteral = saveInObjectTypeLiteral;
function writeSpreadType(type: SpreadType, nested?: boolean) {
if (nested && type.left === emptyObjectType) {
writeType(type.right, TypeFormatFlags.None);
}
else {
writePunctuation(writer, SyntaxKind.DotDotDotToken);
writeType(type.right, TypeFormatFlags.None);
if (atEnd) {
writer.writeKeyword("spread");
writePunctuation(writer, SyntaxKind.OpenParenToken);
if (type.left !== emptyObjectType) {
if (type.left.flags & TypeFlags.Spread) {
writeSpreadType(type.left as SpreadType, /*nested*/ true);
}
else {
writeType(type.left, TypeFormatFlags.None);
}
writePunctuation(writer, SyntaxKind.CommaToken);
writeSpace(writer);
}
else {
writePunctuation(writer, SyntaxKind.SemicolonToken);
writer.writeLine();
}
writeType(type.right, TypeFormatFlags.None);
writePunctuation(writer, SyntaxKind.CloseParenToken);
}
}

Expand Down Expand Up @@ -6080,6 +6060,16 @@ namespace ts {
return links.resolvedType;
}

function getTypeFromSpreadTypeNode(node: SpreadTypeNode): Type {
const links = getNodeLinks(node);
if (!links.resolvedType) {
links.resolvedType = getSpreadType(
node.right ? getTypeFromTypeNode(node.left) : emptyObjectType,
getTypeFromTypeNode(node.right || node.left));
}
return links.resolvedType;
}

function getIndexTypeForGenericType(type: TypeVariable | UnionOrIntersectionType) {
if (!type.resolvedIndexType) {
type.resolvedIndexType = <IndexType>createType(TypeFlags.Index);
Expand Down Expand Up @@ -6264,12 +6254,6 @@ namespace ts {
function getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node: TypeNode): Type {
const links = getNodeLinks(node);
if (!links.resolvedType) {
const hasSpread = (node.kind === SyntaxKind.TypeLiteral &&
find((node as TypeLiteralNode).members, elt => elt.kind === SyntaxKind.SpreadTypeAssignment));
if (hasSpread) {
return getTypeFromSpreadTypeLiteral(node as TypeLiteralNode);
}

// Deferred resolution of members is handled by resolveObjectTypeMembers
const aliasSymbol = getAliasSymbolForTypeNode(node);
if (isEmpty(node.symbol.members) && !aliasSymbol) {
Expand All @@ -6285,50 +6269,6 @@ namespace ts {
return links.resolvedType;
}

function getTypeFromSpreadTypeLiteral(node: TypeLiteralNode): Type {
let spread: Type = emptyObjectType;
let members: Map<Symbol>;
let stringIndexInfo: IndexInfo;
let numberIndexInfo: IndexInfo;
for (const member of node.members) {
if (member.kind === SyntaxKind.SpreadTypeAssignment) {
if (members) {
const type = createAnonymousType(node.symbol, members, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo);
spread = getSpreadType(spread, type);
members = undefined;
stringIndexInfo = undefined;
numberIndexInfo = undefined;
}
const type = getTypeFromTypeNode((member as SpreadTypeAssignment).type);
spread = getSpreadType(spread, type);
}
else if (member.kind !== SyntaxKind.IndexSignature &&
member.kind !== SyntaxKind.CallSignature &&
member.kind !== SyntaxKind.ConstructSignature) {
// it is an error for spread types to include index, call or construct signatures
const flags = SymbolFlags.Property | SymbolFlags.Transient | (member.questionToken ? SymbolFlags.Optional : 0);
const text = getTextOfPropertyName(member.name);
const symbol = <TransientSymbol>createSymbol(flags, text);
symbol.declarations = [member];
symbol.valueDeclaration = member;
symbol.type = getTypeFromTypeNode((member as IndexSignatureDeclaration | PropertySignature | MethodSignature).type);
if (!members) {
members = createMap<Symbol>();
}
members[symbol.name] = symbol;
}
}
if (members || stringIndexInfo || numberIndexInfo) {
const type = createAnonymousType(node.symbol, members || emptySymbols, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo);
spread = getSpreadType(spread, type);
}
if (spread.flags & TypeFlags.Object) {
// only set the symbol if this is a (fresh) object type
spread.symbol = node.symbol;
}
return spread;
}

function getAliasSymbolForTypeNode(node: TypeNode) {
return node.parent.kind === SyntaxKind.TypeAliasDeclaration ? getSymbolOfNode(node.parent) : undefined;
}
Expand All @@ -6339,9 +6279,9 @@ namespace ts {
}

/**
* Since the source of spread types are object literals, which are not binary,
* this function should be called in a left folding style, with left = previous result of getSpreadType
* and right = the new element to be spread.
* Since spread types are binary and object literals are not,
* checkObjectLiteral calls getSpreadType in a left-folding way.
* If a nested spread type literal is not left-deep, getSpreadType will transform it to left-deep form.
*
* If getSpreadType returns a spread type, the following properties hold:
* 1. Left-deep: spread.left is always another spread type (the recursive case)
Expand Down Expand Up @@ -6394,6 +6334,10 @@ namespace ts {
return left;
}

if (!(left.flags & (TypeFlags.Spread | TypeFlags.Object))) {
// put an emptyObjectType terminator on the left
left = getSpreadType(emptyObjectType, left);
}
const simplified = simplifySpreadType(left, right);
if (simplified) {
return simplified;
Expand All @@ -6403,8 +6347,6 @@ namespace ts {
return createSpreadType(left, right);
}
const spread = spreadTypes[id] = createType(TypeFlags.Spread) as SpreadType;
Debug.assert(!!(left.flags & (TypeFlags.Spread | TypeFlags.Object)), "Left flags: " + left.flags.toString(2));
Debug.assert(!!(right.flags & (TypeFlags.TypeParameter | TypeFlags.Intersection | TypeFlags.Index | TypeFlags.IndexedAccess | TypeFlags.Object)), "Right flags: " + right.flags.toString(2));
spread.left = left as SpreadType | ResolvedType;
spread.right = right as TypeParameter | IntersectionType | IndexType | IndexedAccessType | ResolvedType;
return spread;
Expand Down Expand Up @@ -6646,6 +6588,8 @@ namespace ts {
return getTypeFromUnionTypeNode(<UnionTypeNode>node);
case SyntaxKind.IntersectionType:
return getTypeFromIntersectionTypeNode(<IntersectionTypeNode>node);
case SyntaxKind.SpreadType:
return getTypeFromSpreadTypeNode(node as SpreadTypeNode);
case SyntaxKind.ParenthesizedType:
case SyntaxKind.JSDocNullableType:
case SyntaxKind.JSDocNonNullableType:
Expand Down Expand Up @@ -7687,8 +7631,7 @@ namespace ts {
}
else if (target.flags & TypeFlags.Spread) {
// T is assignable to ...T
if (source.symbol === (target as SpreadType).right.symbol
&& (target as SpreadType).left === emptyObjectType) {
if (source === (target as SpreadType).right && (target as SpreadType).left === emptyObjectType) {
return Ternary.True;
}
}
Expand Down Expand Up @@ -16265,19 +16208,10 @@ namespace ts {
function checkTypeLiteral(node: TypeLiteralNode) {
forEach(node.members, checkSourceElement);
if (produceDiagnostics) {
if (find(node.members, p => p.kind === SyntaxKind.SpreadTypeAssignment)) {
for (const signature of filter(node.members, p => p.kind === SyntaxKind.IndexSignature ||
p.kind === SyntaxKind.CallSignature ||
p.kind === SyntaxKind.ConstructSignature)) {
error(signature, Diagnostics.Type_literals_with_spreads_cannot_contain_index_call_or_constructor_signatures);
}
}
else {
const type = getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node);
checkIndexConstraints(type);
checkTypeForDuplicateIndexSignatures(node);
checkObjectTypeForDuplicateDeclarations(node);
}
const type = getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node);
checkIndexConstraints(type);
checkTypeForDuplicateIndexSignatures(node);
checkObjectTypeForDuplicateDeclarations(node);
}
}

Expand Down Expand Up @@ -21660,11 +21594,6 @@ namespace ts {
}
}

let result: TypeElement;
if (result = find(node.members, e => e.kind === SyntaxKind.SpreadTypeAssignment)) {
return grammarErrorOnNode(result, Diagnostics.Interface_declaration_cannot_contain_a_spread_property);
}

return false;
}

Expand Down
14 changes: 8 additions & 6 deletions src/compiler/declarationEmitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,8 @@ namespace ts {
return emitUnionType(<UnionTypeNode>type);
case SyntaxKind.IntersectionType:
return emitIntersectionType(<IntersectionTypeNode>type);
case SyntaxKind.SpreadType:
return emitSpreadType(type as SpreadTypeNode);
case SyntaxKind.ParenthesizedType:
return emitParenType(<ParenthesizedTypeNode>type);
case SyntaxKind.TypeOperator:
Expand Down Expand Up @@ -1174,10 +1176,12 @@ namespace ts {
writeLine();
}

function emitSpreadTypeAssignment(type: SpreadTypeAssignment) {
write("...");
emitType(type.type);
write(";");
function emitSpreadType(type: SpreadTypeNode) {
write("spread(");
emitType(type.left);
write(",")
emitType(type.right);
write(")");
writeLine();
}

Expand Down Expand Up @@ -1771,8 +1775,6 @@ namespace ts {
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.PropertySignature:
return emitPropertyDeclaration(<PropertyDeclaration>node);
case SyntaxKind.SpreadTypeAssignment:
return emitSpreadTypeAssignment(node as SpreadTypeAssignment);
case SyntaxKind.EnumMember:
return emitEnumMemberDeclaration(<EnumMember>node);
case SyntaxKind.ExportAssignment:
Expand Down
8 changes: 0 additions & 8 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -2015,10 +2015,6 @@
"category": "Error",
"code": 2698
},
"Type literals with spreads cannot contain index, call or constructor signatures.": {
"category": "Error",
"code": 2699
},
"Rest types may only be created from object types.": {
"category": "Error",
"code": 2700
Expand All @@ -2039,10 +2035,6 @@
"category": "Error",
"code": 2704
},
"Interface declaration cannot contain a spread property.": {
"category": "Error",
"code": 2705
},

"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",
Expand Down
54 changes: 29 additions & 25 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,9 @@ namespace ts {
visitNode(cbNode, (<ShorthandPropertyAssignment>node).objectAssignmentInitializer);
case SyntaxKind.SpreadAssignment:
return visitNode(cbNode, (<SpreadAssignment>node).expression);
case SyntaxKind.SpreadTypeAssignment:
return visitNode(cbNode, (node as SpreadTypeAssignment).type);
case SyntaxKind.SpreadType:
return visitNode(cbNode, (node as SpreadTypeNode).left) ||
visitNode(cbNode, (node as SpreadTypeNode).right);
case SyntaxKind.Parameter:
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.PropertySignature:
Expand Down Expand Up @@ -2377,9 +2378,6 @@ namespace ts {
if (token() === SyntaxKind.OpenBracketToken) {
return true;
}
if (token() === SyntaxKind.DotDotDotToken) {
return true;
}
// Try to get the first property-like token following all modifiers
if (isLiteralPropertyName()) {
idToken = token();
Expand All @@ -2399,17 +2397,11 @@ namespace ts {
}

function parseTypeMember(): TypeElement {
switch (token()) {
case SyntaxKind.OpenParenToken:
case SyntaxKind.LessThanToken:
return parseSignatureMember(SyntaxKind.CallSignature);
case SyntaxKind.NewKeyword:
if (lookAhead(isStartOfConstructSignature)) {
return parseSignatureMember(SyntaxKind.ConstructSignature);
}
break;
case SyntaxKind.DotDotDotToken:
return parseSpreadTypeAssignment();
if (token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken) {
return parseSignatureMember(SyntaxKind.CallSignature);
}
if (token() === SyntaxKind.NewKeyword && lookAhead(isStartOfConstructSignature)) {
return parseSignatureMember(SyntaxKind.ConstructSignature);
}
const fullStart = getNodePos();
const modifiers = parseModifiers();
Expand All @@ -2419,14 +2411,6 @@ namespace ts {
return parsePropertyOrMethodSignature(fullStart, modifiers);
}

function parseSpreadTypeAssignment() {
const element = createNode(SyntaxKind.SpreadTypeAssignment, scanner.getStartPos()) as SpreadTypeAssignment;
parseTokenNode<Node>(); // parse `...`
element.type = parseType();
parseTypeMemberSemicolon();
return finishNode(element);
}

function isStartOfConstructSignature() {
nextToken();
return token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken;
Expand Down Expand Up @@ -2626,6 +2610,26 @@ namespace ts {
return type;
}

function parseSpreadType(): TypeNode {
const node = createNode(SyntaxKind.SpreadType) as SpreadTypeNode;
parseExpected(SyntaxKind.SpreadKeyword);
parseExpected(SyntaxKind.OpenParenToken);
node.left = parseType();
if(parseOptional(SyntaxKind.CommaToken)) {
node.right = parseType();
}
parseExpected(SyntaxKind.CloseParenToken);
return finishNode(node);
}

function parseSpreadTypeOrHigher() {
switch (token()) {
case SyntaxKind.SpreadKeyword:
return parseSpreadType();
}
return parseArrayTypeOrHigher();
}

function parseTypeOperator(operator: SyntaxKind.KeyOfKeyword) {
const node = <TypeOperatorNode>createNode(SyntaxKind.TypeOperator);
parseExpected(operator);
Expand All @@ -2639,7 +2643,7 @@ namespace ts {
case SyntaxKind.KeyOfKeyword:
return parseTypeOperator(SyntaxKind.KeyOfKeyword);
}
return parseArrayTypeOrHigher();
return parseSpreadTypeOrHigher();
}

function parseUnionOrIntersectionType(kind: SyntaxKind, parseConstituentType: () => TypeNode, operator: SyntaxKind): TypeNode {
Expand Down
1 change: 1 addition & 0 deletions src/compiler/scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ namespace ts {
"global": SyntaxKind.GlobalKeyword,
"return": SyntaxKind.ReturnKeyword,
"set": SyntaxKind.SetKeyword,
"spread": SyntaxKind.SpreadKeyword,
"static": SyntaxKind.StaticKeyword,
"string": SyntaxKind.StringKeyword,
"super": SyntaxKind.SuperKeyword,
Expand Down
Loading