Skip to content

Commit b030f73

Browse files
committed
Fixes for control-flow analysis
1 parent 8c572b1 commit b030f73

10 files changed

+137
-21
lines changed

src/compiler/binder.ts

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,7 +1015,8 @@ namespace ts {
10151015
else {
10161016
return node.kind === SyntaxKind.BinaryExpression && (
10171017
(<BinaryExpression>node).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken ||
1018-
(<BinaryExpression>node).operatorToken.kind === SyntaxKind.BarBarToken);
1018+
(<BinaryExpression>node).operatorToken.kind === SyntaxKind.BarBarToken ||
1019+
(<BinaryExpression>node).operatorToken.kind === SyntaxKind.QuestionQuestionToken);
10191020
}
10201021
}
10211022
}
@@ -1440,14 +1441,6 @@ namespace ts {
14401441
bindCondition(node.right, trueTarget, falseTarget);
14411442
}
14421443

1443-
function bindNullishCoalescingExpression(node: BinaryExpression, trueTarget: FlowLabel, falseTarget: FlowLabel) {
1444-
const notNullLabel = createBranchLabel();
1445-
bindCondition(node.left, trueTarget, notNullLabel);
1446-
currentFlow = finishFlowLabel(notNullLabel);
1447-
bind(node.operatorToken);
1448-
bindCondition(node.right, trueTarget, falseTarget);
1449-
}
1450-
14511444
function bindPrefixUnaryExpressionFlow(node: PrefixUnaryExpression) {
14521445
if (node.operator === SyntaxKind.ExclamationToken) {
14531446
const saveTrueTarget = currentTrueTarget;
@@ -1480,9 +1473,6 @@ namespace ts {
14801473
bindLogicalExpression(node, postExpressionLabel, postExpressionLabel);
14811474
currentFlow = finishFlowLabel(postExpressionLabel);
14821475
}
1483-
else if (operator === SyntaxKind.QuestionQuestionToken) {
1484-
bindNullishCoalescingExpression(node, currentTrueTarget!, currentFalseTarget!);
1485-
}
14861476
else {
14871477
bindLogicalExpression(node, currentTrueTarget!, currentFalseTarget!);
14881478
}

src/compiler/checker.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19697,7 +19697,8 @@ namespace ts {
1969719697
// will be a subtype or the same type as the argument.
1969819698
function narrowType(type: Type, expr: Expression, assumeTrue: boolean): Type {
1969919699
// for `a?.b`, we emulate a synthetic `a !== null && a !== undefined` condition for `a`
19700-
if (isOptionalChainRoot(expr.parent)) {
19700+
if (isOptionalChainRoot(expr.parent) ||
19701+
isBinaryExpression(expr.parent) && expr.parent.operatorToken.kind === SyntaxKind.QuestionQuestionToken && expr.parent.left === expr) {
1970119702
return narrowTypeByOptionality(type, expr, assumeTrue);
1970219703
}
1970319704
switch (expr.kind) {
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
tests/cases/conformance/controlFlow/controlFlowNullishCoalesce.ts(4,1): error TS2454: Variable 'a' is used before being assigned.
2+
3+
4+
==== tests/cases/conformance/controlFlow/controlFlowNullishCoalesce.ts (1 errors) ====
5+
// assignments in shortcutting rhs
6+
let a: number;
7+
o ?? (a = 1);
8+
a.toString();
9+
~
10+
!!! error TS2454: Variable 'a' is used before being assigned.
11+
12+
// assignment flow
13+
declare const o: { x: number } | undefined;
14+
let x: { x: number } | boolean;
15+
if (x = o ?? true) {
16+
x;
17+
}
18+
19+
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//// [controlFlowNullishCoalesce.ts]
2+
// assignments in shortcutting rhs
3+
let a: number;
4+
o ?? (a = 1);
5+
a.toString();
6+
7+
// assignment flow
8+
declare const o: { x: number } | undefined;
9+
let x: { x: number } | boolean;
10+
if (x = o ?? true) {
11+
x;
12+
}
13+
14+
15+
16+
//// [controlFlowNullishCoalesce.js]
17+
"use strict";
18+
// assignments in shortcutting rhs
19+
var a;
20+
(o !== null && o !== void 0 ? o : (a = 1));
21+
a.toString();
22+
var x;
23+
if (x = (o !== null && o !== void 0 ? o : true)) {
24+
x;
25+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
=== tests/cases/conformance/controlFlow/controlFlowNullishCoalesce.ts ===
2+
// assignments in shortcutting rhs
3+
let a: number;
4+
>a : Symbol(a, Decl(controlFlowNullishCoalesce.ts, 1, 3))
5+
6+
o ?? (a = 1);
7+
>o : Symbol(o, Decl(controlFlowNullishCoalesce.ts, 6, 13))
8+
>a : Symbol(a, Decl(controlFlowNullishCoalesce.ts, 1, 3))
9+
10+
a.toString();
11+
>a.toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --))
12+
>a : Symbol(a, Decl(controlFlowNullishCoalesce.ts, 1, 3))
13+
>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --))
14+
15+
// assignment flow
16+
declare const o: { x: number } | undefined;
17+
>o : Symbol(o, Decl(controlFlowNullishCoalesce.ts, 6, 13))
18+
>x : Symbol(x, Decl(controlFlowNullishCoalesce.ts, 6, 18))
19+
20+
let x: { x: number } | boolean;
21+
>x : Symbol(x, Decl(controlFlowNullishCoalesce.ts, 7, 3))
22+
>x : Symbol(x, Decl(controlFlowNullishCoalesce.ts, 7, 8))
23+
24+
if (x = o ?? true) {
25+
>x : Symbol(x, Decl(controlFlowNullishCoalesce.ts, 7, 3))
26+
>o : Symbol(o, Decl(controlFlowNullishCoalesce.ts, 6, 13))
27+
28+
x;
29+
>x : Symbol(x, Decl(controlFlowNullishCoalesce.ts, 7, 3))
30+
}
31+
32+
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
=== tests/cases/conformance/controlFlow/controlFlowNullishCoalesce.ts ===
2+
// assignments in shortcutting rhs
3+
let a: number;
4+
>a : number
5+
6+
o ?? (a = 1);
7+
>o ?? (a = 1) : { x: number; } | 1
8+
>o : { x: number; } | undefined
9+
>(a = 1) : 1
10+
>a = 1 : 1
11+
>a : number
12+
>1 : 1
13+
14+
a.toString();
15+
>a.toString() : string
16+
>a.toString : (radix?: number | undefined) => string
17+
>a : number
18+
>toString : (radix?: number | undefined) => string
19+
20+
// assignment flow
21+
declare const o: { x: number } | undefined;
22+
>o : { x: number; } | undefined
23+
>x : number
24+
25+
let x: { x: number } | boolean;
26+
>x : boolean | { x: number; }
27+
>x : number
28+
29+
if (x = o ?? true) {
30+
>x = o ?? true : true | { x: number; }
31+
>x : boolean | { x: number; }
32+
>o ?? true : true | { x: number; }
33+
>o : { x: number; } | undefined
34+
>true : true
35+
36+
x;
37+
>x : true | { x: number; }
38+
}
39+
40+
Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
11
tests/cases/conformance/expressions/nullishCoalescingOperator/nullishCoalescingOperator11.ts(3,18): error TS2533: Object is possibly 'null' or 'undefined'.
2-
tests/cases/conformance/expressions/nullishCoalescingOperator/nullishCoalescingOperator11.ts(3,22): error TS2339: Property 'toFixed' does not exist on type '"" | 0'.
3-
Property 'toFixed' does not exist on type '""'.
42

53

6-
==== tests/cases/conformance/expressions/nullishCoalescingOperator/nullishCoalescingOperator11.ts (2 errors) ====
4+
==== tests/cases/conformance/expressions/nullishCoalescingOperator/nullishCoalescingOperator11.ts (1 errors) ====
75
declare const f11: 1 | 0 | '' | null | undefined;
86

97
let g11 = f11 ?? f11.toFixed()
108
~~~
119
!!! error TS2533: Object is possibly 'null' or 'undefined'.
12-
~~~~~~~
13-
!!! error TS2339: Property 'toFixed' does not exist on type '"" | 0'.
14-
!!! error TS2339: Property 'toFixed' does not exist on type '""'.
1510

1611

1712

tests/baselines/reference/nullishCoalescingOperator11.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ let g11 = f11 ?? f11.toFixed()
99
>f11 : "" | 0 | 1 | null | undefined
1010
>f11.toFixed() : any
1111
>f11.toFixed : any
12-
>f11 : "" | 0 | null | undefined
12+
>f11 : null | undefined
1313
>toFixed : any
1414

1515

tests/baselines/reference/nullishCoalescingOperator5.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ a ?? b || c;
2020
a || b ?? c;
2121
>a || b ?? c : string | undefined
2222
>a || b : string | undefined
23-
>a : string
23+
>a : string | undefined
2424
>b : string | undefined
2525
>c : string | undefined
2626

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// @strict: true
2+
3+
// assignments in shortcutting rhs
4+
let a: number;
5+
o ?? (a = 1);
6+
a.toString();
7+
8+
// assignment flow
9+
declare const o: { x: number } | undefined;
10+
let x: { x: number } | boolean;
11+
if (x = o ?? true) {
12+
x;
13+
}
14+

0 commit comments

Comments
 (0)