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

Intersection types #3622

Merged
merged 14 commits into from
Jul 6, 2015
Merged
320 changes: 209 additions & 111 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions src/compiler/declarationEmitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,8 @@ namespace ts {
return emitTupleType(<TupleTypeNode>type);
case SyntaxKind.UnionType:
return emitUnionType(<UnionTypeNode>type);
case SyntaxKind.IntersectionType:
return emitIntersectionType(<IntersectionTypeNode>type);
case SyntaxKind.ParenthesizedType:
return emitParenType(<ParenthesizedTypeNode>type);
case SyntaxKind.FunctionType:
Expand Down Expand Up @@ -417,6 +419,10 @@ namespace ts {
emitSeparatedList(type.types, " | ", emitType);
}

function emitIntersectionType(type: IntersectionTypeNode) {
emitSeparatedList(type.types, " & ", emitType);
}

function emitParenType(type: ParenthesizedTypeNode) {
write("(");
emitType(type.type);
Expand Down
23 changes: 16 additions & 7 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,8 @@ namespace ts {
case SyntaxKind.TupleType:
return visitNodes(cbNodes, (<TupleTypeNode>node).elementTypes);
case SyntaxKind.UnionType:
return visitNodes(cbNodes, (<UnionTypeNode>node).types);
case SyntaxKind.IntersectionType:
return visitNodes(cbNodes, (<UnionOrIntersectionTypeNode>node).types);
case SyntaxKind.ParenthesizedType:
return visitNode(cbNode, (<ParenthesizedTypeNode>node).type);
case SyntaxKind.ObjectBindingPattern:
Expand Down Expand Up @@ -2353,22 +2354,30 @@ namespace ts {
return type;
}

function parseUnionTypeOrHigher(): TypeNode {
let type = parseArrayTypeOrHigher();
if (token === SyntaxKind.BarToken) {
function parseUnionOrIntersectionType(kind: SyntaxKind, parseConstituentType: () => TypeNode, operator: SyntaxKind): TypeNode {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice reuse!

let type = parseConstituentType();
if (token === operator) {
let types = <NodeArray<TypeNode>>[type];
types.pos = type.pos;
while (parseOptional(SyntaxKind.BarToken)) {
types.push(parseArrayTypeOrHigher());
while (parseOptional(operator)) {
types.push(parseConstituentType());
}
types.end = getNodeEnd();
let node = <UnionTypeNode>createNode(SyntaxKind.UnionType, type.pos);
let node = <UnionOrIntersectionTypeNode>createNode(kind, type.pos);
node.types = types;
type = finishNode(node);
}
return type;
}

function parseIntersectionTypeOrHigher(): TypeNode {
return parseUnionOrIntersectionType(SyntaxKind.IntersectionType, parseArrayTypeOrHigher, SyntaxKind.AmpersandToken);
}

function parseUnionTypeOrHigher(): TypeNode {
return parseUnionOrIntersectionType(SyntaxKind.UnionType, parseIntersectionTypeOrHigher, SyntaxKind.BarToken);
}

function isStartOfFunctionType(): boolean {
if (token === SyntaxKind.LessThanToken) {
return true;
Expand Down
40 changes: 26 additions & 14 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ namespace ts {
ArrayType,
TupleType,
UnionType,
IntersectionType,
ParenthesizedType,
// Binding patterns
ObjectBindingPattern,
Expand Down Expand Up @@ -634,10 +635,14 @@ namespace ts {
elementTypes: NodeArray<TypeNode>;
}

export interface UnionTypeNode extends TypeNode {
export interface UnionOrIntersectionTypeNode extends TypeNode {
types: NodeArray<TypeNode>;
}

export interface UnionTypeNode extends UnionOrIntersectionTypeNode { }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add brands so that these UnionTypeNode and IntersectionTypeNode are distinguished and not assignable to each other.


export interface IntersectionTypeNode extends UnionOrIntersectionTypeNode { }

export interface ParenthesizedTypeNode extends TypeNode {
type: TypeNode;
}
Expand Down Expand Up @@ -1479,7 +1484,7 @@ namespace ts {
Merged = 0x02000000, // Merged symbol (created during program binding)
Transient = 0x04000000, // Transient symbol (created during type check)
Prototype = 0x08000000, // Prototype property (no source representation)
UnionProperty = 0x10000000, // Property in union type
SyntheticProperty = 0x10000000, // Property in union or intersection type
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we use "UnionOrIntersection" elsewhere. It seems like another place we'd want to use them. i.e. "UnionOrIntersectionProperty". "Synthetic" is odd, as it's a term we don't use consistently for this concept elsewhere.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with @CyrusNajmabadi. After reading the code, it will be much easier to read if it is name UnionOrIntersectionProperty

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, also instantiated properties are just as synthetic as union/intersection properties.

Optional = 0x20000000, // Optional property
ExportStar = 0x40000000, // Export * declaration

Expand Down Expand Up @@ -1558,7 +1563,7 @@ namespace ts {
instantiations?: Map<Type>; // Instantiations of generic type alias (undefined if non-generic)
mapper?: TypeMapper; // Type mapper for instantiation alias
referenced?: boolean; // True if alias symbol has been referenced as a value
unionType?: UnionType; // Containing union type for union property
containingType?: UnionOrIntersectionType; // Containing union or intersection type for synthetic property
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

align comments.

resolvedExports?: SymbolTable; // Resolved exports of module
exportsChecked?: boolean; // True if exports of external module have been checked
isNestedRedeclaration?: boolean; // True if symbol is block scoped redeclaration
Expand Down Expand Up @@ -1620,17 +1625,18 @@ namespace ts {
Interface = 0x00000800, // Interface
Reference = 0x00001000, // Generic type reference
Tuple = 0x00002000, // Tuple
Union = 0x00004000, // Union
Anonymous = 0x00008000, // Anonymous
Instantiated = 0x00010000, // Instantiated anonymous type
Union = 0x00004000, // Union (T | U)
Intersection = 0x00008000, // Intersection (T & U)
Anonymous = 0x00010000, // Anonymous
Instantiated = 0x00020000, // Instantiated anonymous type
/* @internal */
FromSignature = 0x00020000, // Created for signature assignment check
ObjectLiteral = 0x00040000, // Originates in an object literal
FromSignature = 0x00040000, // Created for signature assignment check
ObjectLiteral = 0x00080000, // Originates in an object literal
/* @internal */
ContainsUndefinedOrNull = 0x00080000, // Type is or contains Undefined or Null type
ContainsUndefinedOrNull = 0x00100000, // Type is or contains Undefined or Null type
/* @internal */
ContainsObjectLiteral = 0x00100000, // Type is or contains object literal type
ESSymbol = 0x00200000, // Type of symbol primitive introduced in ES6
ContainsObjectLiteral = 0x00200000, // Type is or contains object literal type
ESSymbol = 0x00400000, // Type of symbol primitive introduced in ES6

/* @internal */
Intrinsic = Any | String | Number | Boolean | ESSymbol | Void | Undefined | Null,
Expand All @@ -1639,6 +1645,8 @@ namespace ts {
StringLike = String | StringLiteral,
NumberLike = Number | Enum,
ObjectType = Class | Interface | Reference | Tuple | Anonymous,
UnionOrIntersection = Union | Intersection,
StructuredType = ObjectType | Union | Intersection,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment explaining why these are considered structured?

/* @internal */
RequiresWidening = ContainsUndefinedOrNull | ContainsObjectLiteral
}
Expand Down Expand Up @@ -1698,17 +1706,21 @@ namespace ts {
baseArrayType: TypeReference; // Array<T> where T is best common type of element types
}

export interface UnionType extends Type {
export interface UnionOrIntersectionType extends Type {
types: Type[]; // Constituent types
/* @internal */
reducedType: Type; // Reduced union type (all subtypes removed)
/* @internal */
resolvedProperties: SymbolTable; // Cache of resolved properties
}

export interface UnionType extends UnionOrIntersectionType { }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add brands.


export interface IntersectionType extends UnionOrIntersectionType { }

/* @internal */
// Resolved object or union type
export interface ResolvedType extends ObjectType, UnionType {
// Resolved object, union, or intersection type
export interface ResolvedType extends ObjectType, UnionOrIntersectionType {
members: SymbolTable; // Properties by name
properties: Symbol[]; // Properties
callSignatures: Signature[]; // Call signatures of type
Expand Down
4 changes: 2 additions & 2 deletions src/services/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3576,7 +3576,7 @@ namespace ts {
if (flags & SymbolFlags.Constructor) return ScriptElementKind.constructorImplementationElement;

if (flags & SymbolFlags.Property) {
if (flags & SymbolFlags.UnionProperty) {
if (flags & SymbolFlags.SyntheticProperty) {
// If union property is result of union of non method (property/accessors/variables), it is labeled as property
let unionPropertyKind = forEach(typeChecker.getRootSymbols(symbol), rootSymbol => {
let rootSymbolFlags = rootSymbol.getFlags();
Expand Down Expand Up @@ -5078,7 +5078,7 @@ namespace ts {

// if this symbol is visible from its parent container, e.g. exported, then bail out
// if symbol correspond to the union property - bail out
if (symbol.parent || (symbol.flags & SymbolFlags.UnionProperty)) {
if (symbol.parent || (symbol.flags & SymbolFlags.SyntheticProperty)) {
return undefined;
}

Expand Down
20 changes: 10 additions & 10 deletions tests/baselines/reference/APISample_linter.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,26 +75,26 @@ function delint(sourceFile) {
delintNode(sourceFile);
function delintNode(node) {
switch (node.kind) {
case 189 /* ForStatement */:
case 190 /* ForInStatement */:
case 188 /* WhileStatement */:
case 187 /* DoStatement */:
if (node.statement.kind !== 182 /* Block */) {
case 190 /* ForStatement */:
case 191 /* ForInStatement */:
case 189 /* WhileStatement */:
case 188 /* DoStatement */:
if (node.statement.kind !== 183 /* Block */) {
report(node, "A looping statement's contents should be wrapped in a block body.");
}
break;
case 186 /* IfStatement */:
case 187 /* IfStatement */:
var ifStatement = node;
if (ifStatement.thenStatement.kind !== 182 /* Block */) {
if (ifStatement.thenStatement.kind !== 183 /* Block */) {
report(ifStatement.thenStatement, "An if statement's contents should be wrapped in a block body.");
}
if (ifStatement.elseStatement &&
ifStatement.elseStatement.kind !== 182 /* Block */ &&
ifStatement.elseStatement.kind !== 186 /* IfStatement */) {
ifStatement.elseStatement.kind !== 183 /* Block */ &&
ifStatement.elseStatement.kind !== 187 /* IfStatement */) {
report(ifStatement.elseStatement, "An else statement's contents should be wrapped in a block body.");
}
break;
case 172 /* BinaryExpression */:
case 173 /* BinaryExpression */:
var op = node.operatorToken.kind;
if (op === 28 /* EqualsEqualsToken */ || op == 29 /* ExclamationEqualsToken */) {
report(node, "Use '===' and '!=='.");
Expand Down
Loading