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 analysis #8010

Merged
merged 70 commits into from
Apr 22, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
e67d15a
Initial implementation of control flow based type analysis
ahejlsberg Mar 22, 2016
afa1714
Add type annotations to suppress circularity errors
ahejlsberg Mar 22, 2016
7c45c7b
Fixing tests
ahejlsberg Mar 22, 2016
80c2e5e
Accepting new baselines
ahejlsberg Mar 22, 2016
33985b2
Adding a few optimizations
ahejlsberg Mar 24, 2016
ed5002c
Handle assignment of union types in getAssignmentReducedType
ahejlsberg Mar 25, 2016
6d25a42
Remove incorrect type predicate (could be true even when result is fa…
ahejlsberg Mar 25, 2016
bf78470
Fix overly aggressive optimization
ahejlsberg Mar 25, 2016
4f936c4
Add control flow tests
ivogabe Mar 25, 2016
7f02357
Merge pull request #7690 from ivogabe/controlFlowTypesTest
ahejlsberg Mar 25, 2016
9e965d4
Fix issues in analysis of do..while and for..in/for..of
ahejlsberg Mar 26, 2016
9de0a5d
Fix comment in test
ahejlsberg Mar 26, 2016
560bc3f
Accepting new baselines
ahejlsberg Mar 26, 2016
0820249
Fixing some tests
ahejlsberg Mar 26, 2016
5a5d89a
Accepting new baselines
ahejlsberg Mar 26, 2016
424074b
Use type {} for vacuous type guards / New getTypeWithFacts function
ahejlsberg Mar 30, 2016
c6f4de3
Remove unnecessary cast
ahejlsberg Mar 30, 2016
e53f390
Fix some tests
ahejlsberg Mar 30, 2016
a38d863
Accepting new baselines
ahejlsberg Mar 30, 2016
3d0fa31
Delete removeNullableKind, use getTypeWithFacts instead
ahejlsberg Mar 30, 2016
ce81ba5
Support unknown types (host object names) in typeof type guards
ahejlsberg Mar 31, 2016
354fd10
Separate error messages for 'null', 'undefined', or both.
ahejlsberg Apr 1, 2016
5179dd6
Flow analysis of &&, ||, and destructuring assignments
ahejlsberg Apr 8, 2016
019f5bd
Accepting new baselines
ahejlsberg Apr 8, 2016
f13c92f
Handle shorthand property assignments
ahejlsberg Apr 8, 2016
b03d087
Accepting new baselines
ahejlsberg Apr 8, 2016
7a32129
Support destructuring declarations in control flow analysis
ahejlsberg Apr 9, 2016
6fb9424
Accepting new baselines
ahejlsberg Apr 9, 2016
e45bac8
Adding test
ahejlsberg Apr 10, 2016
7dfcad6
Fixing fourslash tests
ahejlsberg Apr 10, 2016
92df029
Fixing tests
ahejlsberg Apr 10, 2016
32e6464
Accepting new baselines
ahejlsberg Apr 10, 2016
560e768
Fix linting errors
ahejlsberg Apr 10, 2016
b1e9f43
Merge branch 'master' into controlFlowTypes
ahejlsberg Apr 10, 2016
4c250d0
Accepting new baselines
ahejlsberg Apr 10, 2016
7c7a1c0
A few cosmetic changes
ahejlsberg Apr 12, 2016
df62fa0
Merge branch 'master' into controlFlowTypes
ahejlsberg Apr 12, 2016
586ac55
Fix finishFlow function and rename to finishFlowLabel
ahejlsberg Apr 12, 2016
cd88f1e
Adding regression test
ahejlsberg Apr 12, 2016
472ab7c
Accepting new baselines
ahejlsberg Apr 12, 2016
b689c07
Improving error reporting as suggested in code review
ahejlsberg Apr 13, 2016
1ed9871
Fix typo
ahejlsberg Apr 13, 2016
921efec
Improved handing of evolving types in iteration statements
ahejlsberg Apr 16, 2016
9aea708
Adding tests
ahejlsberg Apr 16, 2016
10889a0
Accepting new baselines
ahejlsberg Apr 16, 2016
2595f04
Removing unused properties
ahejlsberg Apr 17, 2016
b83dc88
Improve expression type caching to ensure consistent results
ahejlsberg Apr 18, 2016
538e22a
Adding tests
ahejlsberg Apr 18, 2016
b5104cb
Accepting new baselines
ahejlsberg Apr 18, 2016
87f55fa
Only evaluate assigned type when declared type is a union type
ahejlsberg Apr 18, 2016
9defdde
Accepting new baselines
ahejlsberg Apr 18, 2016
d28a4fe
typeof x === "function" type guards include Function interface
ahejlsberg Apr 18, 2016
d735b7a
Variables from different source files default to their declared type
ahejlsberg Apr 19, 2016
c8bf6d8
Variables from different module declarations default to their declare…
ahejlsberg Apr 19, 2016
ea96dfd
Support comma operator in type guards
ahejlsberg Apr 20, 2016
33e359f
Adding test
ahejlsberg Apr 20, 2016
bab8ef4
Accepting new baselines
ahejlsberg Apr 20, 2016
e9a7d3d
Removing unused logic
ahejlsberg Apr 20, 2016
a0101c0
Improve consistency of instanceof and user defined type guards
ahejlsberg Apr 21, 2016
729dfce
Fix incorrect user defined type guard function in compiler
ahejlsberg Apr 21, 2016
3045cf5
Add regression test
ahejlsberg Apr 21, 2016
06928b6
Accepting new baselines
ahejlsberg Apr 21, 2016
8a0bc3b
Support assignments in truthiness type guards
ahejlsberg Apr 21, 2016
d2b89be
Adding test
ahejlsberg Apr 21, 2016
ab4b039
Removing unused logic
ahejlsberg Apr 21, 2016
e12b2a7
Correct issue with exported variables in code flow analysis
ahejlsberg Apr 21, 2016
4fb31ac
Update fourslash test
ahejlsberg Apr 21, 2016
f06d3f6
Only narrow to {} in getNarrowedType when types are completely unrelated
ahejlsberg Apr 22, 2016
42e3fc4
Revert previous change
ahejlsberg Apr 22, 2016
0dee5ad
Accepting new baselines
ahejlsberg Apr 22, 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
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); // !!!
Copy link
Member

Choose a reason for hiding this comment

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

What's the new type annotation and // !!! comment for?

Copy link
Contributor

Choose a reason for hiding this comment

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

Since Entity is a type alias of a union, it will be narrowed after its assignment. This creates a circular dependency during the type analysis. Because of this, node would be typed as any if the type annotation was not given.

@ahejlsberg Would it be possible to give the inference of the type of the initializer precedence over the narrowing after assignments?

Copy link
Member Author

Choose a reason for hiding this comment

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

The exact problem here is the following: We infer the type of node from the call to createNode. To evaluate that call we need to know the type of entity so we can find the type of the pos property. To find the type of entity we look at the preceding code paths. One leads from the top of the function. In that code path, entity has type Identifier, which is less than its full declared type (Identifier | QualifiedName). Therefore we need to also analyze the second code path that comes from the bottom of the while statement (the loop around case). In that code path, we have the assignment entity = finishNode(node) which requires us to know the type of node. Boom! Circularity. Not exactly clear what rule we'd introduce to break that circularity.

Copy link
Member

Choose a reason for hiding this comment

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

Would just assuming that entity has its original declared type be good enough?

Copy link
Contributor

Choose a reason for hiding this comment

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

One other idea is that while doing the analysis, if you hit a type assertion, ignore the RHS, and just use the type associated with the type assertion.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes that's a good point. It's not a simple matter of using the declared type. But I think the overloaded next function wouldn't even accept a union type as an argument.

Copy link
Member

Choose a reason for hiding this comment

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

No, it wouldn't with how TS resolves signatures at present - at least not without another signature declaring it as taking a parameter of type any or a union of the other types. (Which, for some reason feels off to me - if every member of a union of argument types can be fulfilled by an overload and there's no better match, why can't the return be the union of those overloads' return values? Question for another time.)

Copy link
Contributor

Choose a reason for hiding this comment

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

(Answer for another time ;)

Copy link
Member Author

Choose a reason for hiding this comment

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

@weswigham @JsonFreeman There are actually several issues being debated here:

  • Circularities occurring because we're more eager than we need to when resolving (as opposed to checking) the type of an expression. As Jason points out, that's not a new issue. We could make progress on this issue by making a distinction between resolving and checking an expression, and only evaluate as much as we need to when resolving. Indeed, we could solve the actual cases in the compiler by doing this for type assertion expressions.
  • Inability for symbols to have evolving declared types. This one is much harder. I really don't want to go there unless we absolutely have to.
  • Compute narrowed types by iterating to a fixed point. We only do this in limited form today and, as Wes' example shows, we may need to do more here. I'm not super concerned with crazy examples that walk up a ladder of overloads, but I can indeed imagine real world examples where it occurs without overloading. Although I haven't actually seen any in the wild yet. I will continue to think about this issue.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks Anders, I think that's a great breakdown. I'll add that (if I'm not mistaken), this separation of resolution and checking is in place for bodies of function expressions. The idea here would be to generalize it for other expressions.

I agree that iterating to a fixed point may be necessary for loops in which the variable is reassigned in the loop body.

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); // !!!
Copy link
Member

Choose a reason for hiding this comment

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

Same comment as above:

What's the new type annotation and // !!! comment for?

Copy link
Member Author

Choose a reason for hiding this comment

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

Same issue.

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 @@ -2049,8 +2078,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 @@ -2084,18 +2111,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 @@ -2147,6 +2169,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 @@ -1408,6 +1417,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 @@ -1504,7 +1538,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 {
Copy link
Member

Choose a reason for hiding this comment

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

Why was the type-guardiness of this function removed?

Copy link
Contributor

Choose a reason for hiding this comment

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

Probably because this function can also return true if the node is some identifier, but not used as a declaration. With the control flow checks, the code after an if with a return will be considered to be the else block, which might have caused some issues.

Copy link
Member

Choose a reason for hiding this comment

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

If that was the case, then wouldn't the name of the function be misleading?

Copy link
Contributor

Choose a reason for hiding this comment

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

Not in my opinion, it checks whether a node is a declaration name, but not every identifier is a declaration name. So the type annotation was wrong, not the name.

Copy link
Member

Choose a reason for hiding this comment

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

Seems fair.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, @ivogabe is exactly right about why I removed the type predicate annotation.

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