Skip to content

Commit 6104f74

Browse files
authored
Merge pull request #33831 from microsoft/falseAssertions
Code following truthiness assertion with false argument is unreachable
2 parents 161b90a + 92212ae commit 6104f74

File tree

6 files changed

+333
-140
lines changed

6 files changed

+333
-140
lines changed

src/compiler/checker.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18802,6 +18802,13 @@ namespace ts {
1880218802
return !(flow.flags & FlowFlags.PreFinally && (<PreFinallyFlow>flow).lock.locked) && isReachableFlowNodeWorker(flow, /*skipCacheCheck*/ false);
1880318803
}
1880418804

18805+
function isFalseExpression(expr: Expression): boolean {
18806+
const node = skipParentheses(expr);
18807+
return node.kind === SyntaxKind.FalseKeyword || node.kind === SyntaxKind.BinaryExpression && (
18808+
(<BinaryExpression>node).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken && (isFalseExpression((<BinaryExpression>node).left) || isFalseExpression((<BinaryExpression>node).right)) ||
18809+
(<BinaryExpression>node).operatorToken.kind === SyntaxKind.BarBarToken && isFalseExpression((<BinaryExpression>node).left) && isFalseExpression((<BinaryExpression>node).right));
18810+
}
18811+
1880518812
function isReachableFlowNodeWorker(flow: FlowNode, noCacheCheck: boolean): boolean {
1880618813
while (true) {
1880718814
if (flow === lastFlowNode) {
@@ -18821,8 +18828,17 @@ namespace ts {
1882118828
}
1882218829
else if (flags & FlowFlags.Call) {
1882318830
const signature = getEffectsSignature((<FlowCall>flow).node);
18824-
if (signature && getReturnTypeOfSignature(signature).flags & TypeFlags.Never) {
18825-
return false;
18831+
if (signature) {
18832+
const predicate = getTypePredicateOfSignature(signature);
18833+
if (predicate && predicate.kind === TypePredicateKind.AssertsIdentifier) {
18834+
const predicateArgument = (<FlowCall>flow).node.arguments[predicate.parameterIndex];
18835+
if (predicateArgument && isFalseExpression(predicateArgument)) {
18836+
return false;
18837+
}
18838+
}
18839+
if (getReturnTypeOfSignature(signature).flags & TypeFlags.Never) {
18840+
return false;
18841+
}
1882618842
}
1882718843
flow = (<FlowCall>flow).antecedent;
1882818844
}
@@ -19061,6 +19077,9 @@ namespace ts {
1906119077

1906219078
function narrowTypeByAssertion(type: Type, expr: Expression): Type {
1906319079
const node = skipParentheses(expr);
19080+
if (node.kind === SyntaxKind.FalseKeyword) {
19081+
return unreachableNeverType;
19082+
}
1906419083
if (node.kind === SyntaxKind.BinaryExpression) {
1906519084
if ((<BinaryExpression>node).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) {
1906619085
return narrowTypeByAssertion(narrowTypeByAssertion(type, (<BinaryExpression>node).left), (<BinaryExpression>node).right);
@@ -19080,7 +19099,7 @@ namespace ts {
1908019099
const flowType = getTypeAtFlowNode(flow.antecedent);
1908119100
const type = getTypeFromFlowType(flowType);
1908219101
const narrowedType = predicate.type ? narrowTypeByTypePredicate(type, predicate, flow.node, /*assumeTrue*/ true) :
19083-
predicate.kind === TypePredicateKind.AssertsIdentifier ? narrowTypeByAssertion(type, flow.node.arguments[predicate.parameterIndex]) :
19102+
predicate.kind === TypePredicateKind.AssertsIdentifier && predicate.parameterIndex >= 0 && predicate.parameterIndex < flow.node.arguments.length ? narrowTypeByAssertion(type, flow.node.arguments[predicate.parameterIndex]) :
1908419103
type;
1908519104
return narrowedType === type ? flowType : createFlowType(narrowedType, isIncomplete(flowType));
1908619105
}

tests/baselines/reference/assertionTypePredicates1.errors.txt

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
1-
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(116,37): error TS1228: A type predicate is only allowed in return type position for functions and methods.
2-
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(117,37): error TS1228: A type predicate is only allowed in return type position for functions and methods.
3-
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(118,37): error TS1228: A type predicate is only allowed in return type position for functions and methods.
4-
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(121,15): error TS1228: A type predicate is only allowed in return type position for functions and methods.
5-
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(122,15): error TS1228: A type predicate is only allowed in return type position for functions and methods.
6-
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(123,15): error TS1228: A type predicate is only allowed in return type position for functions and methods.
7-
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(124,15): error TS1228: A type predicate is only allowed in return type position for functions and methods.
8-
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(129,5): error TS2775: Assertions require every name in the call target to be declared with an explicit type annotation.
9-
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(131,5): error TS2776: Assertions require the call target to be an identifier or qualified name.
10-
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(133,5): error TS2775: Assertions require every name in the call target to be declared with an explicit type annotation.
1+
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(39,9): error TS7027: Unreachable code detected.
2+
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(43,9): error TS7027: Unreachable code detected.
3+
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(87,9): error TS7027: Unreachable code detected.
4+
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(122,9): error TS7027: Unreachable code detected.
5+
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(132,37): error TS1228: A type predicate is only allowed in return type position for functions and methods.
6+
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(133,37): error TS1228: A type predicate is only allowed in return type position for functions and methods.
7+
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(134,37): error TS1228: A type predicate is only allowed in return type position for functions and methods.
8+
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(137,15): error TS1228: A type predicate is only allowed in return type position for functions and methods.
9+
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(138,15): error TS1228: A type predicate is only allowed in return type position for functions and methods.
10+
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(139,15): error TS1228: A type predicate is only allowed in return type position for functions and methods.
11+
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(140,15): error TS1228: A type predicate is only allowed in return type position for functions and methods.
12+
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(145,5): error TS2775: Assertions require every name in the call target to be declared with an explicit type annotation.
13+
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(147,5): error TS2776: Assertions require the call target to be an identifier or qualified name.
14+
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(149,5): error TS2775: Assertions require every name in the call target to be declared with an explicit type annotation.
1115

1216

13-
==== tests/cases/conformance/controlFlow/assertionTypePredicates1.ts (10 errors) ====
17+
==== tests/cases/conformance/controlFlow/assertionTypePredicates1.ts (14 errors) ====
1418
declare function isString(value: unknown): value is string;
1519
declare function isArrayOfStrings(value: unknown): value is string[];
1620

@@ -47,6 +51,18 @@ tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(133,5): error TS
4751
assertDefined(x);
4852
x; // string
4953
}
54+
if (!!true) {
55+
assert(false);
56+
x; // Unreachable
57+
~~
58+
!!! error TS7027: Unreachable code detected.
59+
}
60+
if (!!true) {
61+
assert(false && x === undefined);
62+
x; // Unreachable
63+
~~
64+
!!! error TS7027: Unreachable code detected.
65+
}
5066
}
5167

5268
function f02(x: string | undefined) {
@@ -87,6 +103,12 @@ tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(133,5): error TS
87103
Debug.assertDefined(x);
88104
x.length;
89105
}
106+
if (!!true) {
107+
Debug.assert(false);
108+
x; // Unreachable
109+
~~
110+
!!! error TS7027: Unreachable code detected.
111+
}
90112
}
91113

92114
class Test {
@@ -118,6 +140,12 @@ tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(133,5): error TS
118140
this.assertIsTest2();
119141
this.z;
120142
}
143+
baz(x: number) {
144+
this.assert(false);
145+
x; // Unreachable
146+
~~
147+
!!! error TS7027: Unreachable code detected.
148+
}
121149
}
122150

123151
class Test2 extends Test {
@@ -156,7 +184,7 @@ tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(133,5): error TS
156184
assert(typeof x === "string"); // Error
157185
~~~~~~
158186
!!! error TS2775: Assertions require every name in the call target to be declared with an explicit type annotation.
159-
!!! related TS2728 tests/cases/conformance/controlFlow/assertionTypePredicates1.ts:128:11: 'assert' is declared here.
187+
!!! related TS2728 tests/cases/conformance/controlFlow/assertionTypePredicates1.ts:144:11: 'assert' is declared here.
160188
const a = [assert];
161189
a[0](typeof x === "string"); // Error
162190
~~~~
@@ -165,7 +193,7 @@ tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(133,5): error TS
165193
t1.assert(typeof x === "string"); // Error
166194
~~~~~~~~~
167195
!!! error TS2775: Assertions require every name in the call target to be declared with an explicit type annotation.
168-
!!! related TS2728 tests/cases/conformance/controlFlow/assertionTypePredicates1.ts:132:11: 't1' is declared here.
196+
!!! related TS2728 tests/cases/conformance/controlFlow/assertionTypePredicates1.ts:148:11: 't1' is declared here.
169197
const t2: Test = new Test();
170198
t2.assert(typeof x === "string");
171199
}

tests/baselines/reference/assertionTypePredicates1.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,14 @@ function f01(x: unknown) {
3535
assertDefined(x);
3636
x; // string
3737
}
38+
if (!!true) {
39+
assert(false);
40+
x; // Unreachable
41+
}
42+
if (!!true) {
43+
assert(false && x === undefined);
44+
x; // Unreachable
45+
}
3846
}
3947

4048
function f02(x: string | undefined) {
@@ -75,6 +83,10 @@ function f10(x: string | undefined) {
7583
Debug.assertDefined(x);
7684
x.length;
7785
}
86+
if (!!true) {
87+
Debug.assert(false);
88+
x; // Unreachable
89+
}
7890
}
7991

8092
class Test {
@@ -106,6 +118,10 @@ class Test {
106118
this.assertIsTest2();
107119
this.z;
108120
}
121+
baz(x: number) {
122+
this.assert(false);
123+
x; // Unreachable
124+
}
109125
}
110126

111127
class Test2 extends Test {
@@ -180,6 +196,14 @@ function f01(x) {
180196
assertDefined(x);
181197
x; // string
182198
}
199+
if (!!true) {
200+
assert(false);
201+
x; // Unreachable
202+
}
203+
if (!!true) {
204+
assert(false && x === undefined);
205+
x; // Unreachable
206+
}
183207
}
184208
function f02(x) {
185209
if (!!true) {
@@ -215,6 +239,10 @@ function f10(x) {
215239
Debug.assertDefined(x);
216240
x.length;
217241
}
242+
if (!!true) {
243+
Debug.assert(false);
244+
x; // Unreachable
245+
}
218246
}
219247
var Test = /** @class */ (function () {
220248
function Test() {
@@ -250,6 +278,10 @@ var Test = /** @class */ (function () {
250278
this.assertIsTest2();
251279
this.z;
252280
};
281+
Test.prototype.baz = function (x) {
282+
this.assert(false);
283+
x; // Unreachable
284+
};
253285
return Test;
254286
}());
255287
var Test2 = /** @class */ (function (_super) {
@@ -295,6 +327,7 @@ declare class Test {
295327
assertThis(): asserts this;
296328
bar(): void;
297329
foo(x: unknown): void;
330+
baz(x: number): void;
298331
}
299332
declare class Test2 extends Test {
300333
z: number;

0 commit comments

Comments
 (0)