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

Control flow based type guards #6959

Closed
wants to merge 67 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
42a8efd
Merge remote-tracking branch 'Microsoft/master'
ivogabe Aug 15, 2015
80f42ed
Merge remote-tracking branch 'Microsoft/master'
ivogabe Aug 19, 2015
57f5d5c
Merge branch 'master' of https://github.com/Microsoft/TypeScript
ivogabe Nov 18, 2015
163291f
Merge remote-tracking branch 'Microsoft/master' into guards
ivogabe Nov 30, 2015
441c999
Add flow markers to binder
ivogabe Dec 4, 2015
522a99c
Add BranchFlow for type guards
ivogabe Dec 4, 2015
78ca7b5
Add caching of identifier narrowing in binder
ivogabe Dec 6, 2015
1fb2db6
Implement flow based type guards in checker
ivogabe Dec 6, 2015
f1c9a82
Narrow assignments to unions only
ivogabe Dec 9, 2015
d3dd1fa
Make isDeclarationName a weak type guard for Identifiers
ivogabe Dec 9, 2015
702edac
Remove trailing space
ivogabe Dec 9, 2015
8670014
Make getNarrowedTypeOfSymbol work on all nodes again
ivogabe Dec 9, 2015
ecbf9e2
Merge remote-tracking branch 'Microsoft/master' into guards
ivogabe Dec 9, 2015
c1abed2
Merge remote-tracking branch 'Microsoft/master' into guards
ivogabe Dec 11, 2015
905a240
Bind flow of loops and named labels
ivogabe Feb 6, 2016
b8c0e2c
Fix flow based type guards in checker
ivogabe Feb 6, 2016
f6dc119
Merge remote-tracking branch 'Microsoft/master' into guards
ivogabe Feb 6, 2016
b385472
Fix errors after merge
ivogabe Feb 6, 2016
6f1d84e
Remove restriction on type narrowing with assignments in loops
ivogabe Feb 6, 2016
ffa8db3
Fix assignment checking
ivogabe Feb 6, 2016
844a8c1
Remove flowIndex, use getNodeId
ivogabe Feb 8, 2016
ff1b2d6
Remove PreviousOccurency interface
ivogabe Feb 8, 2016
6c7c678
Fix typo
ivogabe Feb 8, 2016
7d2daf9
Merge remote-tracking branch 'Microsoft/master' into guards
ivogabe Feb 8, 2016
035d973
Fix binding of condition less for statement
ivogabe Feb 8, 2016
b2e51a3
Handle narrowing in var declarations
ivogabe Feb 11, 2016
2747c98
Rename `showError` to `reportError`
ivogabe Feb 11, 2016
b58eb60
Merge remote-tracking branch 'Microsoft/master' into guards
ivogabe Feb 11, 2016
3f0c283
Fix typos after merge
ivogabe Feb 11, 2016
a30fa8f
Merge master
ivogabe Feb 11, 2016
ac2f547
Remove old assertion
ivogabe Feb 12, 2016
0a91991
Keep narrowed types in a loop
ivogabe Feb 12, 2016
a44de51
Merge remote-tracking branch 'Microsoft/master' into guards
ivogabe Feb 12, 2016
67913d8
Fix narrowing of union type assigned to a union type
ivogabe Feb 13, 2016
f15291f
Merge remote-tracking branch 'Microsoft/master' into guards
ivogabe Feb 13, 2016
26a4d37
Merge remote-tracking branch 'Microsoft/master' into guards
ivogabe Feb 26, 2016
074a05d
Add more caching
ivogabe Feb 26, 2016
2c57ee5
Use cache to speed up multiple paths to same node in flow graph
ivogabe Feb 26, 2016
927c562
Don't error when assigning to narrowed variable using destructuring
ivogabe Feb 29, 2016
a4b91f3
Merge remote-tracking branch 'Microsoft/master' into guards
ivogabe Feb 29, 2016
8673a46
Narrow after destructuring assignment
ivogabe Mar 7, 2016
09e4302
Rewrite recursion to loop, reduces check time
ivogabe Mar 9, 2016
1037efa
Merge remote-tracking branch 'Microsoft/master' into guards
ivogabe Mar 11, 2016
8331915
Fix issues with destructuring assignments
ivogabe Mar 12, 2016
8d4cea4
Merge remote-tracking branch 'Microsoft/master' into guards
ivogabe Mar 13, 2016
c128582
Fix narrowing of JSX tags
ivogabe Mar 13, 2016
19b3f90
Use initial type when narrowing resulted in empty type
ivogabe Mar 14, 2016
59b543f
Move narrowing info from node to NodeLinks
ivogabe Mar 14, 2016
c0061c0
Allow narrowing in nested functions
ivogabe Mar 14, 2016
096c70a
Narrow after destructuring assignments with initializers
ivogabe Mar 15, 2016
fa77df5
Merge remote-tracking branch 'Microsoft/master' into guards
ivogabe Mar 15, 2016
46786b3
Fix narrowing in for of statement
ivogabe Mar 15, 2016
3a7575b
Stop narrowing with guards that contain assignments
ivogabe Mar 15, 2016
b9f5b79
Add control flow tests
ivogabe Mar 15, 2016
21efd00
Add test for narrowing after assignment
ivogabe Mar 15, 2016
d5fbd4e
Modify existing tests for new type guards behavior
ivogabe Mar 15, 2016
f8a1827
Add more tests
ivogabe Mar 16, 2016
7cceb0c
Remove redundant bindFlowMarker calls
ivogabe Mar 16, 2016
bea32e2
Fix tests
ivogabe Mar 16, 2016
a659239
Accept baseline
ivogabe Mar 16, 2016
9e13d0f
Merge remote-tracking branch 'Microsoft/master' into guards
ivogabe Mar 16, 2016
3490979
Update tests
ivogabe Mar 16, 2016
a719858
Merge remote-tracking branch 'Microsoft/master' into guards
ivogabe Mar 22, 2016
90a678d
Implement flow based type guards for this expression and dotted names
ivogabe Mar 23, 2016
7646af1
Fix unresolved symbols, wrong caching and empty type issues
ivogabe Mar 23, 2016
4a76fe2
Accept new baselines
ivogabe Mar 23, 2016
16555dd
Stop narrowing of dotted name after assignment of parent type
ivogabe Mar 23, 2016
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
392 changes: 303 additions & 89 deletions src/compiler/binder.ts

Large diffs are not rendered by default.

462 changes: 386 additions & 76 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

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 @@ -3639,7 +3639,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
22 changes: 21 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,8 @@ namespace ts {
decorators?: NodeArray<Decorator>; // Array of decorators (in document order)
modifiers?: ModifiersArray; // Array of modifiers
/* @internal */ id?: number; // Unique id (used to look up NodeLinks)
parent?: Node; // Parent node (initialized by binding
parent?: Node; // Parent node (initialized by binding)
previous?: FlowMarkerTarget[]; // Previous occurencies of flowmarkers (initialized by binding)
/* @internal */ jsDocComment?: JSDocComment; // JSDoc for the node, if it has any. Only for .js files.
/* @internal */ symbol?: Symbol; // Symbol declared by node (initialized by binding)
/* @internal */ locals?: SymbolTable; // Locals associated with node (initialized by binding)
Expand All @@ -459,6 +460,16 @@ namespace ts {
flags: number;
}

export type FlowMarkerTarget = Node | BranchFlow;
export type BranchFlowNode = IfStatement | WhileStatement | ForStatement | DoStatement | ConditionalExpression | BinaryExpression
export interface BranchFlow {
id: number;
previous: FlowMarkerTarget[];
node: BranchFlowNode;
expression: Expression;
trueBranch: boolean;
}

// @kind(SyntaxKind.AbstractKeyword)
// @kind(SyntaxKind.AsyncKeyword)
// @kind(SyntaxKind.ConstKeyword)
Expand All @@ -471,6 +482,13 @@ namespace ts {
// @kind(SyntaxKind.StaticKeyword)
export interface Modifier extends Node { }

/* @internal */
export enum NarrowingState {
Uninitialized,
Narrowing,
Done,
Failed
}
// @kind(SyntaxKind.Identifier)
export interface Identifier extends PrimaryExpression {
text: string; // Text of identifier (with escapes converted to characters)
Expand Down Expand Up @@ -2095,6 +2113,8 @@ namespace ts {
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.
superCall?: ExpressionStatement; // Cached first super-call found in the constructor. Used in checking whether super is called before this-accessing
narrowingState?: NarrowingState;
localType?: Type;
}

export const enum TypeFlags {
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1412,7 +1412,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): name is (Identifier & { __weakTypeGuard: void; }) | StringLiteral | LiteralExpression {
Copy link
Member

Choose a reason for hiding this comment

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

I don't see this brand applied anywhere - is there a reason for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If a type guard returns false, it is expected that the argument is not of the specified type. However, isDeclarationName can return false when name is an identifier. This wasn't an issue, as isDeclarationName was never used in an if statement with an else. However, with control flow based type guards, this wasn't working anymore. The then-block of an if statement contained a return, thus the statements after the if were the else branch of the if. It might not be the best way to solve this, but it's working..

if (name.kind !== SyntaxKind.Identifier && name.kind !== SyntaxKind.StringLiteral && name.kind !== SyntaxKind.NumericLiteral) {
return false;
}
Expand Down
8 changes: 4 additions & 4 deletions tests/baselines/reference/TypeGuardWithEnumUnion.types
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ function f2(x: Color | string | string[]) {
if (typeof x === "string") {
>typeof x === "string" : boolean
>typeof x : string
>x : Color | string | string[]
>x : string | string[] | Color
>"string" : string

var a = x;
Expand All @@ -89,11 +89,11 @@ function f2(x: Color | string | string[]) {
}
else {
var b = x;
>b : Color | string[]
>x : Color | string[]
>b : string[] | Color
>x : string[] | Color

var b: Color | string[];
>b : Color | string[]
>b : string[] | Color
>Color : Color
}
}
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))
}

98 changes: 98 additions & 0 deletions tests/baselines/reference/assignmentTypeNarrowing.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
=== tests/cases/conformance/expressions/assignmentOperator/assignmentTypeNarrowing.ts ===
let x: string | number | boolean | RegExp;
>x : string | number | boolean | RegExp
>RegExp : RegExp

x = "";
>x = "" : string
>x : string | number | boolean | RegExp
>"" : string

x; // string
>x : string

[x] = [true];
>[x] = [true] : [boolean]
>[x] : [string | number | boolean | RegExp]
>x : string | number | boolean | RegExp
>[true] : [boolean]
>true : boolean

x; // boolean
>x : boolean

[x = ""] = [1];
>[x = ""] = [1] : [number]
>[x = ""] : [string]
>x = "" : string
>x : string | number | boolean | RegExp
>"" : string
>[1] : [number]
>1 : number

x; // string | number
>x : string | number

({x} = {x: true});
>({x} = {x: true}) : { x: boolean; }
>{x} = {x: true} : { x: boolean; }
>{x} : { x: string | number | boolean | RegExp; }
>x : string | number | boolean | RegExp
>{x: true} : { x: boolean; }
>x : boolean
>true : boolean

x; // boolean
>x : boolean

({y: x} = {y: 1});
>({y: x} = {y: 1}) : { y: number; }
>{y: x} = {y: 1} : { y: number; }
>{y: x} : { y: string | number | boolean | RegExp; }
>y : string | number | boolean | RegExp
>x : string | number | boolean | RegExp
>{y: 1} : { y: number; }
>y : number
>1 : number

x; // number
>x : number

({x = ""} = {x: true});
>({x = ""} = {x: true}) : { x?: boolean; }
>{x = ""} = {x: true} : { x?: boolean; }
>{x = ""} : { x?: string | number | boolean | RegExp; }
>x : string | number | boolean | RegExp
>{x: true} : { x?: boolean; }
>x : boolean
>true : boolean

x; // string | boolean
>x : string | boolean

({y: x = /a/} = {y: 1});
>({y: x = /a/} = {y: 1}) : { y?: number; }
>{y: x = /a/} = {y: 1} : { y?: number; }
>{y: x = /a/} : { y?: RegExp; }
>y : RegExp
>x = /a/ : RegExp
>x : string | number | boolean | RegExp
>/a/ : RegExp
>{y: 1} : { y?: number; }
>y : number
>1 : number

x; // number | RegExp
>x : number | RegExp

let a: string[];
>a : string[]

for (x of a) {
>x : string | number | boolean | RegExp
>a : string[]

x; // string
>x : string
}

22 changes: 22 additions & 0 deletions tests/baselines/reference/controlFlowAssignmentExpression.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//// [controlFlowAssignmentExpression.ts]
let x: string | boolean | number;
let obj: any;

x = "";
x = x.length;
x; // number

x = true;
(x = "", obj).foo = (x = x.length);
x; // number


//// [controlFlowAssignmentExpression.js]
var x;
var obj;
x = "";
x = x.length;
x; // number
x = true;
(x = "", obj).foo = (x = x.length);
x; // number
33 changes: 33 additions & 0 deletions tests/baselines/reference/controlFlowAssignmentExpression.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
=== tests/cases/conformance/controlFlow/controlFlowAssignmentExpression.ts ===
let x: string | boolean | number;
>x : Symbol(x, Decl(controlFlowAssignmentExpression.ts, 0, 3))

let obj: any;
>obj : Symbol(obj, Decl(controlFlowAssignmentExpression.ts, 1, 3))

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

x = x.length;
>x : Symbol(x, Decl(controlFlowAssignmentExpression.ts, 0, 3))
>x.length : Symbol(String.length, Decl(lib.d.ts, --, --))
>x : Symbol(x, Decl(controlFlowAssignmentExpression.ts, 0, 3))
>length : Symbol(String.length, Decl(lib.d.ts, --, --))

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

x = true;
>x : Symbol(x, Decl(controlFlowAssignmentExpression.ts, 0, 3))

(x = "", obj).foo = (x = x.length);
>x : Symbol(x, Decl(controlFlowAssignmentExpression.ts, 0, 3))
>obj : Symbol(obj, Decl(controlFlowAssignmentExpression.ts, 1, 3))
>x : Symbol(x, Decl(controlFlowAssignmentExpression.ts, 0, 3))
>x.length : Symbol(String.length, Decl(lib.d.ts, --, --))
>x : Symbol(x, Decl(controlFlowAssignmentExpression.ts, 0, 3))
>length : Symbol(String.length, Decl(lib.d.ts, --, --))

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

Loading