Skip to content

Commit

Permalink
Merge pull request #8010 from Microsoft/controlFlowTypes
Browse files Browse the repository at this point in the history
Control flow based type analysis
  • Loading branch information
ahejlsberg committed Apr 22, 2016
2 parents 497e810 + 0dee5ad commit 5ed6f30
Show file tree
Hide file tree
Showing 198 changed files with 8,050 additions and 3,595 deletions.
815 changes: 538 additions & 277 deletions src/compiler/binder.ts

Large diffs are not rendered by default.

1,038 changes: 539 additions & 499 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

10 changes: 9 additions & 1 deletion src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1739,10 +1739,18 @@
"category": "Error",
"code": 2530
},
"Object is possibly 'null' or 'undefined'.": {
"Object is possibly 'null'.": {
"category": "Error",
"code": 2531
},
"Object is possibly 'undefined'.": {
"category": "Error",
"code": 2532
},
"Object is possibly 'null' or 'undefined'.": {
"category": "Error",
"code": 2533
},
"JSX element attributes type '{0}' may not be a union type.": {
"category": "Error",
"code": 2600
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1811,7 +1811,7 @@ namespace ts {
function parseEntityName(allowReservedWords: boolean, diagnosticMessage?: DiagnosticMessage): EntityName {
let entity: EntityName = parseIdentifier(diagnosticMessage);
while (parseOptional(SyntaxKind.DotToken)) {
const node = <QualifiedName>createNode(SyntaxKind.QualifiedName, entity.pos);
const node: QualifiedName = <QualifiedName>createNode(SyntaxKind.QualifiedName, entity.pos); // !!!
node.left = entity;
node.right = parseRightSideOfDot(allowReservedWords);
entity = finishNode(node);
Expand Down Expand Up @@ -3643,7 +3643,7 @@ namespace ts {
let elementName: EntityName = parseIdentifierName();
while (parseOptional(SyntaxKind.DotToken)) {
scanJsxIdentifier();
const node = <QualifiedName>createNode(SyntaxKind.QualifiedName, elementName.pos);
const node: QualifiedName = <QualifiedName>createNode(SyntaxKind.QualifiedName, elementName.pos); // !!!
node.left = elementName;
node.right = parseIdentifierName();
elementName = finishNode(node);
Expand Down
47 changes: 35 additions & 12 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,7 @@ namespace ts {
/* @internal */ locals?: SymbolTable; // Locals associated with node (initialized by binding)
/* @internal */ nextContainer?: Node; // Next container in declaration order (initialized by binding)
/* @internal */ localSymbol?: Symbol; // Local symbol declared by node (initialized by binding only for exported nodes)
/* @internal */ flowNode?: FlowNode; // Associated FlowNode (initialized by binding)
}

export interface NodeArray<T> extends Array<T>, TextRange {
Expand Down Expand Up @@ -478,11 +479,6 @@ namespace ts {
originalKeywordKind?: SyntaxKind; // Original syntaxKind which get set so that we can report an error later
}

// Transient identifier node (marked by id === -1)
export interface TransientIdentifier extends Identifier {
resolvedSymbol: Symbol;
}

// @kind(SyntaxKind.QualifiedName)
export interface QualifiedName extends Node {
// Must have same layout as PropertyAccess
Expand Down Expand Up @@ -1519,6 +1515,39 @@ namespace ts {
isBracketed: boolean;
}

export const enum FlowKind {
Unreachable,
Start,
Label,
Assignment,
Condition
}

export interface FlowNode {
kind: FlowKind; // Node kind
id?: number; // Node id used by flow type cache in checker
}

// FlowLabel represents a junction with multiple possible preceding control flows.
export interface FlowLabel extends FlowNode {
antecedents: FlowNode[];
}

// FlowAssignment represents a node that assigns a value to a narrowable reference,
// i.e. an identifier or a dotted name that starts with an identifier or 'this'.
export interface FlowAssignment extends FlowNode {
node: Expression | VariableDeclaration | BindingElement;
antecedent: FlowNode;
}

// FlowCondition represents a condition that is known to be true or false at the
// node's location in the control flow.
export interface FlowCondition extends FlowNode {
expression: Expression;
assumeTrue: boolean;
antecedent: FlowNode;
}

export interface AmdDependency {
path: string;
name: string;
Expand Down Expand Up @@ -2054,8 +2083,6 @@ namespace ts {
isDeclarationWithCollidingName?: boolean; // True if symbol is block scoped redeclaration
bindingElement?: BindingElement; // Binding element associated with property symbol
exportsSomeValue?: boolean; // True if module exports some value (not just types)
firstAssignmentChecked?: boolean; // True if first assignment node has been computed
firstAssignment?: Node; // First assignment node (undefined if no assignments)
}

/* @internal */
Expand Down Expand Up @@ -2089,18 +2116,13 @@ namespace ts {
/* @internal */
export interface NodeLinks {
resolvedType?: Type; // Cached type of type node
resolvedAwaitedType?: Type; // Cached awaited type of type node
resolvedSignature?: Signature; // Cached signature of signature node or call expression
resolvedSymbol?: Symbol; // Cached name resolution result
resolvedIndexInfo?: IndexInfo; // Cached indexing info resolution result
flags?: NodeCheckFlags; // Set of flags specific to Node
enumMemberValue?: number; // Constant value of enum member
isVisible?: boolean; // Is this node visible
generatedName?: string; // Generated name for module, enum, or import declaration
generatedNames?: Map<string>; // Generated names table for source file
assignmentMap?: Map<boolean>; // Cached map of references assigned within this node
hasReportedStatementInAmbientContext?: boolean; // Cache boolean if we report statements in ambient context
importOnRightSide?: Symbol; // for import declarations - import that appear on the right side
jsxFlags?: JsxFlags; // flags for knowing what kind of element/attributes we're dealing with
resolvedJsxType?: Type; // resolved element attributes type of a JSX openinglike element
hasSuperCall?: boolean; // recorded result when we try to find super-call. We only try to find one if this flag is undefined, indicating that we haven't made an attempt.
Expand Down Expand Up @@ -2152,6 +2174,7 @@ namespace ts {
ObjectType = Class | Interface | Reference | Tuple | Anonymous,
UnionOrIntersection = Union | Intersection,
StructuredType = ObjectType | Union | Intersection,
Narrowable = Any | ObjectType | Union | TypeParameter,
/* @internal */
RequiresWidening = ContainsUndefinedOrNull | ContainsObjectLiteral,
/* @internal */
Expand Down
36 changes: 35 additions & 1 deletion src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -840,6 +840,15 @@ namespace ts {
}
}

export function getContainingFunctionOrModule(node: Node): Node {
while (true) {
node = node.parent;
if (isFunctionLike(node) || node.kind === SyntaxKind.ModuleDeclaration || node.kind === SyntaxKind.SourceFile) {
return node;
}
}
}

export function getContainingClass(node: Node): ClassLikeDeclaration {
while (true) {
node = node.parent;
Expand Down Expand Up @@ -1415,6 +1424,31 @@ namespace ts {
return !!node && (node.kind === SyntaxKind.ArrayBindingPattern || node.kind === SyntaxKind.ObjectBindingPattern);
}

// A node is an assignment target if it is on the left hand side of an '=' token, if it is parented by a property
// assignment in an object literal that is an assignment target, or if it is parented by an array literal that is
// an assignment target. Examples include 'a = xxx', '{ p: a } = xxx', '[{ p: a}] = xxx'.
export function isAssignmentTarget(node: Node): boolean {
while (node.parent.kind === SyntaxKind.ParenthesizedExpression) {
node = node.parent;
}
while (true) {
const parent = node.parent;
if (parent.kind === SyntaxKind.ArrayLiteralExpression || parent.kind === SyntaxKind.SpreadElementExpression) {
node = parent;
continue;
}
if (parent.kind === SyntaxKind.PropertyAssignment || parent.kind === SyntaxKind.ShorthandPropertyAssignment) {
node = parent.parent;
continue;
}
return parent.kind === SyntaxKind.BinaryExpression &&
(<BinaryExpression>parent).operatorToken.kind === SyntaxKind.EqualsToken &&
(<BinaryExpression>parent).left === node ||
(parent.kind === SyntaxKind.ForInStatement || parent.kind === SyntaxKind.ForOfStatement) &&
(<ForInStatement | ForOfStatement>parent).initializer === node;
}
}

export function isNodeDescendentOf(node: Node, ancestor: Node): boolean {
while (node) {
if (node === ancestor) return true;
Expand Down Expand Up @@ -1511,7 +1545,7 @@ namespace ts {
}

// True if the given identifier, string literal, or number literal is the name of a declaration node
export function isDeclarationName(name: Node): name is Identifier | StringLiteral | LiteralExpression {
export function isDeclarationName(name: Node): boolean {
if (name.kind !== SyntaxKind.Identifier && name.kind !== SyntaxKind.StringLiteral && name.kind !== SyntaxKind.NumericLiteral) {
return false;
}
Expand Down
2 changes: 1 addition & 1 deletion src/harness/loggedIO.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ namespace Playback {
recordLog = createEmptyLog();

if (typeof underlying.args !== "function") {
recordLog.arguments = <string[]>underlying.args;
recordLog.arguments = underlying.args;
}
};

Expand Down
10 changes: 5 additions & 5 deletions tests/baselines/reference/TypeGuardWithEnumUnion.types
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ function f2(x: Color | string | string[]) {
if (typeof x === "number") {
>typeof x === "number" : boolean
>typeof x : string
>x : Color | string | string[]
>x : string[] | Color | string
>"number" : string

var z = x;
Expand All @@ -68,16 +68,16 @@ function f2(x: Color | string | string[]) {
}
else {
var w = x;
>w : string | string[]
>x : string | string[]
>w : string[] | string
>x : string[] | string

var w: string | string[];
>w : string | string[]
>w : string[] | string
}
if (typeof x === "string") {
>typeof x === "string" : boolean
>typeof x : string
>x : Color | string | string[]
>x : Color | string[] | string
>"string" : string

var a = x;
Expand Down
53 changes: 53 additions & 0 deletions tests/baselines/reference/assignmentTypeNarrowing.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//// [assignmentTypeNarrowing.ts]
let x: string | number | boolean | RegExp;

x = "";
x; // string

[x] = [true];
x; // boolean

[x = ""] = [1];
x; // string | number

({x} = {x: true});
x; // boolean

({y: x} = {y: 1});
x; // number

({x = ""} = {x: true});
x; // string | boolean

({y: x = /a/} = {y: 1});
x; // number | RegExp

let a: string[];

for (x of a) {
x; // string
}


//// [assignmentTypeNarrowing.js]
var x;
x = "";
x; // string
x = [true][0];
x; // boolean
_a = [1][0], x = _a === void 0 ? "" : _a;
x; // string | number
(_b = { x: true }, x = _b.x, _b);
x; // boolean
(_c = { y: 1 }, x = _c.y, _c);
x; // number
(_d = { x: true }, _e = _d.x, x = _e === void 0 ? "" : _e, _d);
x; // string | boolean
(_f = { y: 1 }, _g = _f.y, x = _g === void 0 ? /a/ : _g, _f);
x; // number | RegExp
var a;
for (var _i = 0, a_1 = a; _i < a_1.length; _i++) {
x = a_1[_i];
x; // string
}
var _a, _b, _c, _d, _e, _f, _g;
64 changes: 64 additions & 0 deletions tests/baselines/reference/assignmentTypeNarrowing.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
=== tests/cases/conformance/expressions/assignmentOperator/assignmentTypeNarrowing.ts ===
let x: string | number | boolean | RegExp;
>x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 0, 3))
>RegExp : Symbol(RegExp, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))

x = "";
>x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 0, 3))

x; // string
>x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 0, 3))

[x] = [true];
>x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 0, 3))

x; // boolean
>x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 0, 3))

[x = ""] = [1];
>x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 0, 3))

x; // string | number
>x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 0, 3))

({x} = {x: true});
>x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 11, 2))
>x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 11, 8))

x; // boolean
>x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 0, 3))

({y: x} = {y: 1});
>y : Symbol(y, Decl(assignmentTypeNarrowing.ts, 14, 2))
>x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 0, 3))
>y : Symbol(y, Decl(assignmentTypeNarrowing.ts, 14, 11))

x; // number
>x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 0, 3))

({x = ""} = {x: true});
>x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 17, 2))
>x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 17, 13))

x; // string | boolean
>x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 0, 3))

({y: x = /a/} = {y: 1});
>y : Symbol(y, Decl(assignmentTypeNarrowing.ts, 20, 2))
>x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 0, 3))
>y : Symbol(y, Decl(assignmentTypeNarrowing.ts, 20, 17))

x; // number | RegExp
>x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 0, 3))

let a: string[];
>a : Symbol(a, Decl(assignmentTypeNarrowing.ts, 23, 3))

for (x of a) {
>x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 0, 3))
>a : Symbol(a, Decl(assignmentTypeNarrowing.ts, 23, 3))

x; // string
>x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 0, 3))
}

Loading

0 comments on commit 5ed6f30

Please sign in to comment.