Skip to content

Commit 5ed6f30

Browse files
committed
Merge pull request #8010 from Microsoft/controlFlowTypes
Control flow based type analysis
2 parents 497e810 + 0dee5ad commit 5ed6f30

File tree

198 files changed

+8050
-3595
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

198 files changed

+8050
-3595
lines changed

src/compiler/binder.ts

+538-277
Large diffs are not rendered by default.

src/compiler/checker.ts

+539-499
Large diffs are not rendered by default.

src/compiler/diagnosticMessages.json

+9-1
Original file line numberDiff line numberDiff line change
@@ -1739,10 +1739,18 @@
17391739
"category": "Error",
17401740
"code": 2530
17411741
},
1742-
"Object is possibly 'null' or 'undefined'.": {
1742+
"Object is possibly 'null'.": {
17431743
"category": "Error",
17441744
"code": 2531
17451745
},
1746+
"Object is possibly 'undefined'.": {
1747+
"category": "Error",
1748+
"code": 2532
1749+
},
1750+
"Object is possibly 'null' or 'undefined'.": {
1751+
"category": "Error",
1752+
"code": 2533
1753+
},
17461754
"JSX element attributes type '{0}' may not be a union type.": {
17471755
"category": "Error",
17481756
"code": 2600

src/compiler/parser.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1811,7 +1811,7 @@ namespace ts {
18111811
function parseEntityName(allowReservedWords: boolean, diagnosticMessage?: DiagnosticMessage): EntityName {
18121812
let entity: EntityName = parseIdentifier(diagnosticMessage);
18131813
while (parseOptional(SyntaxKind.DotToken)) {
1814-
const node = <QualifiedName>createNode(SyntaxKind.QualifiedName, entity.pos);
1814+
const node: QualifiedName = <QualifiedName>createNode(SyntaxKind.QualifiedName, entity.pos); // !!!
18151815
node.left = entity;
18161816
node.right = parseRightSideOfDot(allowReservedWords);
18171817
entity = finishNode(node);
@@ -3643,7 +3643,7 @@ namespace ts {
36433643
let elementName: EntityName = parseIdentifierName();
36443644
while (parseOptional(SyntaxKind.DotToken)) {
36453645
scanJsxIdentifier();
3646-
const node = <QualifiedName>createNode(SyntaxKind.QualifiedName, elementName.pos);
3646+
const node: QualifiedName = <QualifiedName>createNode(SyntaxKind.QualifiedName, elementName.pos); // !!!
36473647
node.left = elementName;
36483648
node.right = parseIdentifierName();
36493649
elementName = finishNode(node);

src/compiler/types.ts

+35-12
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,7 @@ namespace ts {
450450
/* @internal */ locals?: SymbolTable; // Locals associated with node (initialized by binding)
451451
/* @internal */ nextContainer?: Node; // Next container in declaration order (initialized by binding)
452452
/* @internal */ localSymbol?: Symbol; // Local symbol declared by node (initialized by binding only for exported nodes)
453+
/* @internal */ flowNode?: FlowNode; // Associated FlowNode (initialized by binding)
453454
}
454455

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

481-
// Transient identifier node (marked by id === -1)
482-
export interface TransientIdentifier extends Identifier {
483-
resolvedSymbol: Symbol;
484-
}
485-
486482
// @kind(SyntaxKind.QualifiedName)
487483
export interface QualifiedName extends Node {
488484
// Must have same layout as PropertyAccess
@@ -1519,6 +1515,39 @@ namespace ts {
15191515
isBracketed: boolean;
15201516
}
15211517

1518+
export const enum FlowKind {
1519+
Unreachable,
1520+
Start,
1521+
Label,
1522+
Assignment,
1523+
Condition
1524+
}
1525+
1526+
export interface FlowNode {
1527+
kind: FlowKind; // Node kind
1528+
id?: number; // Node id used by flow type cache in checker
1529+
}
1530+
1531+
// FlowLabel represents a junction with multiple possible preceding control flows.
1532+
export interface FlowLabel extends FlowNode {
1533+
antecedents: FlowNode[];
1534+
}
1535+
1536+
// FlowAssignment represents a node that assigns a value to a narrowable reference,
1537+
// i.e. an identifier or a dotted name that starts with an identifier or 'this'.
1538+
export interface FlowAssignment extends FlowNode {
1539+
node: Expression | VariableDeclaration | BindingElement;
1540+
antecedent: FlowNode;
1541+
}
1542+
1543+
// FlowCondition represents a condition that is known to be true or false at the
1544+
// node's location in the control flow.
1545+
export interface FlowCondition extends FlowNode {
1546+
expression: Expression;
1547+
assumeTrue: boolean;
1548+
antecedent: FlowNode;
1549+
}
1550+
15221551
export interface AmdDependency {
15231552
path: string;
15241553
name: string;
@@ -2054,8 +2083,6 @@ namespace ts {
20542083
isDeclarationWithCollidingName?: boolean; // True if symbol is block scoped redeclaration
20552084
bindingElement?: BindingElement; // Binding element associated with property symbol
20562085
exportsSomeValue?: boolean; // True if module exports some value (not just types)
2057-
firstAssignmentChecked?: boolean; // True if first assignment node has been computed
2058-
firstAssignment?: Node; // First assignment node (undefined if no assignments)
20592086
}
20602087

20612088
/* @internal */
@@ -2089,18 +2116,13 @@ namespace ts {
20892116
/* @internal */
20902117
export interface NodeLinks {
20912118
resolvedType?: Type; // Cached type of type node
2092-
resolvedAwaitedType?: Type; // Cached awaited type of type node
20932119
resolvedSignature?: Signature; // Cached signature of signature node or call expression
20942120
resolvedSymbol?: Symbol; // Cached name resolution result
20952121
resolvedIndexInfo?: IndexInfo; // Cached indexing info resolution result
20962122
flags?: NodeCheckFlags; // Set of flags specific to Node
20972123
enumMemberValue?: number; // Constant value of enum member
20982124
isVisible?: boolean; // Is this node visible
2099-
generatedName?: string; // Generated name for module, enum, or import declaration
2100-
generatedNames?: Map<string>; // Generated names table for source file
2101-
assignmentMap?: Map<boolean>; // Cached map of references assigned within this node
21022125
hasReportedStatementInAmbientContext?: boolean; // Cache boolean if we report statements in ambient context
2103-
importOnRightSide?: Symbol; // for import declarations - import that appear on the right side
21042126
jsxFlags?: JsxFlags; // flags for knowing what kind of element/attributes we're dealing with
21052127
resolvedJsxType?: Type; // resolved element attributes type of a JSX openinglike element
21062128
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.
@@ -2152,6 +2174,7 @@ namespace ts {
21522174
ObjectType = Class | Interface | Reference | Tuple | Anonymous,
21532175
UnionOrIntersection = Union | Intersection,
21542176
StructuredType = ObjectType | Union | Intersection,
2177+
Narrowable = Any | ObjectType | Union | TypeParameter,
21552178
/* @internal */
21562179
RequiresWidening = ContainsUndefinedOrNull | ContainsObjectLiteral,
21572180
/* @internal */

src/compiler/utilities.ts

+35-1
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,15 @@ namespace ts {
840840
}
841841
}
842842

843+
export function getContainingFunctionOrModule(node: Node): Node {
844+
while (true) {
845+
node = node.parent;
846+
if (isFunctionLike(node) || node.kind === SyntaxKind.ModuleDeclaration || node.kind === SyntaxKind.SourceFile) {
847+
return node;
848+
}
849+
}
850+
}
851+
843852
export function getContainingClass(node: Node): ClassLikeDeclaration {
844853
while (true) {
845854
node = node.parent;
@@ -1415,6 +1424,31 @@ namespace ts {
14151424
return !!node && (node.kind === SyntaxKind.ArrayBindingPattern || node.kind === SyntaxKind.ObjectBindingPattern);
14161425
}
14171426

1427+
// A node is an assignment target if it is on the left hand side of an '=' token, if it is parented by a property
1428+
// assignment in an object literal that is an assignment target, or if it is parented by an array literal that is
1429+
// an assignment target. Examples include 'a = xxx', '{ p: a } = xxx', '[{ p: a}] = xxx'.
1430+
export function isAssignmentTarget(node: Node): boolean {
1431+
while (node.parent.kind === SyntaxKind.ParenthesizedExpression) {
1432+
node = node.parent;
1433+
}
1434+
while (true) {
1435+
const parent = node.parent;
1436+
if (parent.kind === SyntaxKind.ArrayLiteralExpression || parent.kind === SyntaxKind.SpreadElementExpression) {
1437+
node = parent;
1438+
continue;
1439+
}
1440+
if (parent.kind === SyntaxKind.PropertyAssignment || parent.kind === SyntaxKind.ShorthandPropertyAssignment) {
1441+
node = parent.parent;
1442+
continue;
1443+
}
1444+
return parent.kind === SyntaxKind.BinaryExpression &&
1445+
(<BinaryExpression>parent).operatorToken.kind === SyntaxKind.EqualsToken &&
1446+
(<BinaryExpression>parent).left === node ||
1447+
(parent.kind === SyntaxKind.ForInStatement || parent.kind === SyntaxKind.ForOfStatement) &&
1448+
(<ForInStatement | ForOfStatement>parent).initializer === node;
1449+
}
1450+
}
1451+
14181452
export function isNodeDescendentOf(node: Node, ancestor: Node): boolean {
14191453
while (node) {
14201454
if (node === ancestor) return true;
@@ -1511,7 +1545,7 @@ namespace ts {
15111545
}
15121546

15131547
// True if the given identifier, string literal, or number literal is the name of a declaration node
1514-
export function isDeclarationName(name: Node): name is Identifier | StringLiteral | LiteralExpression {
1548+
export function isDeclarationName(name: Node): boolean {
15151549
if (name.kind !== SyntaxKind.Identifier && name.kind !== SyntaxKind.StringLiteral && name.kind !== SyntaxKind.NumericLiteral) {
15161550
return false;
15171551
}

src/harness/loggedIO.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ namespace Playback {
149149
recordLog = createEmptyLog();
150150

151151
if (typeof underlying.args !== "function") {
152-
recordLog.arguments = <string[]>underlying.args;
152+
recordLog.arguments = underlying.args;
153153
}
154154
};
155155

tests/baselines/reference/TypeGuardWithEnumUnion.types

+5-5
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ function f2(x: Color | string | string[]) {
5555
if (typeof x === "number") {
5656
>typeof x === "number" : boolean
5757
>typeof x : string
58-
>x : Color | string | string[]
58+
>x : string[] | Color | string
5959
>"number" : string
6060

6161
var z = x;
@@ -68,16 +68,16 @@ function f2(x: Color | string | string[]) {
6868
}
6969
else {
7070
var w = x;
71-
>w : string | string[]
72-
>x : string | string[]
71+
>w : string[] | string
72+
>x : string[] | string
7373

7474
var w: string | string[];
75-
>w : string | string[]
75+
>w : string[] | string
7676
}
7777
if (typeof x === "string") {
7878
>typeof x === "string" : boolean
7979
>typeof x : string
80-
>x : Color | string | string[]
80+
>x : Color | string[] | string
8181
>"string" : string
8282

8383
var a = x;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//// [assignmentTypeNarrowing.ts]
2+
let x: string | number | boolean | RegExp;
3+
4+
x = "";
5+
x; // string
6+
7+
[x] = [true];
8+
x; // boolean
9+
10+
[x = ""] = [1];
11+
x; // string | number
12+
13+
({x} = {x: true});
14+
x; // boolean
15+
16+
({y: x} = {y: 1});
17+
x; // number
18+
19+
({x = ""} = {x: true});
20+
x; // string | boolean
21+
22+
({y: x = /a/} = {y: 1});
23+
x; // number | RegExp
24+
25+
let a: string[];
26+
27+
for (x of a) {
28+
x; // string
29+
}
30+
31+
32+
//// [assignmentTypeNarrowing.js]
33+
var x;
34+
x = "";
35+
x; // string
36+
x = [true][0];
37+
x; // boolean
38+
_a = [1][0], x = _a === void 0 ? "" : _a;
39+
x; // string | number
40+
(_b = { x: true }, x = _b.x, _b);
41+
x; // boolean
42+
(_c = { y: 1 }, x = _c.y, _c);
43+
x; // number
44+
(_d = { x: true }, _e = _d.x, x = _e === void 0 ? "" : _e, _d);
45+
x; // string | boolean
46+
(_f = { y: 1 }, _g = _f.y, x = _g === void 0 ? /a/ : _g, _f);
47+
x; // number | RegExp
48+
var a;
49+
for (var _i = 0, a_1 = a; _i < a_1.length; _i++) {
50+
x = a_1[_i];
51+
x; // string
52+
}
53+
var _a, _b, _c, _d, _e, _f, _g;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
=== tests/cases/conformance/expressions/assignmentOperator/assignmentTypeNarrowing.ts ===
2+
let x: string | number | boolean | RegExp;
3+
>x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 0, 3))
4+
>RegExp : Symbol(RegExp, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
5+
6+
x = "";
7+
>x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 0, 3))
8+
9+
x; // string
10+
>x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 0, 3))
11+
12+
[x] = [true];
13+
>x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 0, 3))
14+
15+
x; // boolean
16+
>x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 0, 3))
17+
18+
[x = ""] = [1];
19+
>x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 0, 3))
20+
21+
x; // string | number
22+
>x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 0, 3))
23+
24+
({x} = {x: true});
25+
>x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 11, 2))
26+
>x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 11, 8))
27+
28+
x; // boolean
29+
>x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 0, 3))
30+
31+
({y: x} = {y: 1});
32+
>y : Symbol(y, Decl(assignmentTypeNarrowing.ts, 14, 2))
33+
>x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 0, 3))
34+
>y : Symbol(y, Decl(assignmentTypeNarrowing.ts, 14, 11))
35+
36+
x; // number
37+
>x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 0, 3))
38+
39+
({x = ""} = {x: true});
40+
>x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 17, 2))
41+
>x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 17, 13))
42+
43+
x; // string | boolean
44+
>x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 0, 3))
45+
46+
({y: x = /a/} = {y: 1});
47+
>y : Symbol(y, Decl(assignmentTypeNarrowing.ts, 20, 2))
48+
>x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 0, 3))
49+
>y : Symbol(y, Decl(assignmentTypeNarrowing.ts, 20, 17))
50+
51+
x; // number | RegExp
52+
>x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 0, 3))
53+
54+
let a: string[];
55+
>a : Symbol(a, Decl(assignmentTypeNarrowing.ts, 23, 3))
56+
57+
for (x of a) {
58+
>x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 0, 3))
59+
>a : Symbol(a, Decl(assignmentTypeNarrowing.ts, 23, 3))
60+
61+
x; // string
62+
>x : Symbol(x, Decl(assignmentTypeNarrowing.ts, 0, 3))
63+
}
64+

0 commit comments

Comments
 (0)