Skip to content

Commit 3fde17e

Browse files
Merge pull request #26234 from RyanCavanaugh/noVoidTest
Disallow expressions of type `void` to be used in truthiness checks
2 parents 937afab + ca10b7a commit 3fde17e

9 files changed

+560
-7
lines changed

src/compiler/checker.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20956,6 +20956,7 @@ namespace ts {
2095620956
}
2095720957
return numberType;
2095820958
case SyntaxKind.ExclamationToken:
20959+
checkTruthinessExpression(node.operand);
2095920960
const facts = getTypeFacts(operandType) & (TypeFacts.Truthy | TypeFacts.Falsy);
2096020961
return facts === TypeFacts.Truthy ? falseType :
2096120962
facts === TypeFacts.Falsy ? trueType :
@@ -21323,7 +21324,14 @@ namespace ts {
2132321324
if (operator === SyntaxKind.EqualsToken && (left.kind === SyntaxKind.ObjectLiteralExpression || left.kind === SyntaxKind.ArrayLiteralExpression)) {
2132421325
return checkDestructuringAssignment(left, checkExpression(right, checkMode), checkMode);
2132521326
}
21326-
let leftType = checkExpression(left, checkMode);
21327+
let leftType: Type;
21328+
if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken) {
21329+
leftType = checkTruthinessExpression(left, checkMode);
21330+
}
21331+
else {
21332+
leftType = checkExpression(left, checkMode);
21333+
}
21334+
2132721335
let rightType = checkExpression(right, checkMode);
2132821336
switch (operator) {
2132921337
case SyntaxKind.AsteriskToken:
@@ -21656,7 +21664,7 @@ namespace ts {
2165621664
}
2165721665

2165821666
function checkConditionalExpression(node: ConditionalExpression, checkMode?: CheckMode): Type {
21659-
checkExpression(node.condition);
21667+
checkTruthinessExpression(node.condition);
2166021668
const type1 = checkExpression(node.whenTrue, checkMode);
2166121669
const type2 = checkExpression(node.whenFalse, checkMode);
2166221670
return getUnionType([type1, type2], UnionReduction.Subtype);
@@ -24520,7 +24528,7 @@ namespace ts {
2452024528
// Grammar checking
2452124529
checkGrammarStatementInAmbientContext(node);
2452224530

24523-
checkExpression(node.expression);
24531+
checkTruthinessExpression(node.expression);
2452424532
checkSourceElement(node.thenStatement);
2452524533

2452624534
if (node.thenStatement.kind === SyntaxKind.EmptyStatement) {
@@ -24535,17 +24543,25 @@ namespace ts {
2453524543
checkGrammarStatementInAmbientContext(node);
2453624544

2453724545
checkSourceElement(node.statement);
24538-
checkExpression(node.expression);
24546+
checkTruthinessExpression(node.expression);
2453924547
}
2454024548

2454124549
function checkWhileStatement(node: WhileStatement) {
2454224550
// Grammar checking
2454324551
checkGrammarStatementInAmbientContext(node);
2454424552

24545-
checkExpression(node.expression);
24553+
checkTruthinessExpression(node.expression);
2454624554
checkSourceElement(node.statement);
2454724555
}
2454824556

24557+
function checkTruthinessExpression(node: Expression, checkMode?: CheckMode) {
24558+
const type = checkExpression(node, checkMode);
24559+
if (type.flags & TypeFlags.Void) {
24560+
error(node, Diagnostics.An_expression_of_type_void_cannot_be_tested_for_truthiness);
24561+
}
24562+
return type;
24563+
}
24564+
2454924565
function checkForStatement(node: ForStatement) {
2455024566
// Grammar checking
2455124567
if (!checkGrammarStatementInAmbientContext(node)) {
@@ -24563,7 +24579,7 @@ namespace ts {
2456324579
}
2456424580
}
2456524581

24566-
if (node.condition) checkExpression(node.condition);
24582+
if (node.condition) checkTruthinessExpression(node.condition);
2456724583
if (node.incrementor) checkExpression(node.incrementor);
2456824584
checkSourceElement(node.statement);
2456924585
if (node.locals) {

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -987,6 +987,10 @@
987987
"category": "Error",
988988
"code": 1344
989989
},
990+
"An expression of type 'void' cannot be tested for truthiness": {
991+
"category": "Error",
992+
"code": 1345
993+
},
990994

991995
"Duplicate identifier '{0}'.": {
992996
"category": "Error",

src/server/scriptInfo.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,12 +459,15 @@ namespace ts.server {
459459
if (this.isDynamicOrHasMixedContent()) {
460460
this.textStorage.reload("");
461461
this.markContainingProjectsAsDirty();
462+
return true;
462463
}
463464
else {
464465
if (this.textStorage.reloadWithFileText(tempFileName)) {
465466
this.markContainingProjectsAsDirty();
467+
return true;
466468
}
467469
}
470+
return false;
468471
}
469472

470473
/*@internal*/

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8114,7 +8114,7 @@ declare namespace ts.server {
81148114
setOptions(formatSettings: FormatCodeSettings, preferences: UserPreferences | undefined): void;
81158115
getLatestVersion(): string;
81168116
saveTo(fileName: string): void;
8117-
reloadFromFile(tempFileName?: NormalizedPath): void;
8117+
reloadFromFile(tempFileName?: NormalizedPath): boolean;
81188118
editContent(start: number, end: number, newText: string): void;
81198119
markContainingProjectsAsDirty(): void;
81208120
isOrphan(): boolean;
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
tests/cases/conformance/expressions/conditonalOperator/conditionalOperatorConditionIsObjectType.ts(36,1): error TS1345: An expression of type 'void' cannot be tested for truthiness
2+
tests/cases/conformance/expressions/conditonalOperator/conditionalOperatorConditionIsObjectType.ts(39,1): error TS1345: An expression of type 'void' cannot be tested for truthiness
3+
tests/cases/conformance/expressions/conditonalOperator/conditionalOperatorConditionIsObjectType.ts(58,20): error TS1345: An expression of type 'void' cannot be tested for truthiness
4+
tests/cases/conformance/expressions/conditonalOperator/conditionalOperatorConditionIsObjectType.ts(61,23): error TS1345: An expression of type 'void' cannot be tested for truthiness
5+
tests/cases/conformance/expressions/conditonalOperator/conditionalOperatorConditionIsObjectType.ts(63,32): error TS1345: An expression of type 'void' cannot be tested for truthiness
6+
7+
8+
==== tests/cases/conformance/expressions/conditonalOperator/conditionalOperatorConditionIsObjectType.ts (5 errors) ====
9+
//Cond ? Expr1 : Expr2, Cond is of object type, Expr1 and Expr2 have the same type
10+
var condObject: Object;
11+
12+
var exprAny1: any;
13+
var exprBoolean1: boolean;
14+
var exprNumber1: number;
15+
var exprString1: string;
16+
var exprIsObject1: Object;
17+
18+
var exprAny2: any;
19+
var exprBoolean2: boolean;
20+
var exprNumber2: number;
21+
var exprString2: string;
22+
var exprIsObject2: Object;
23+
24+
function foo() { };
25+
class C { static doIt: () => void };
26+
27+
//Cond is an object type variable
28+
condObject ? exprAny1 : exprAny2;
29+
condObject ? exprBoolean1 : exprBoolean2;
30+
condObject ? exprNumber1 : exprNumber2;
31+
condObject ? exprString1 : exprString2;
32+
condObject ? exprIsObject1 : exprIsObject2;
33+
condObject ? exprString1 : exprBoolean1; // union
34+
35+
//Cond is an object type literal
36+
((a: string) => a.length) ? exprAny1 : exprAny2;
37+
((a: string) => a.length) ? exprBoolean1 : exprBoolean2;
38+
({}) ? exprNumber1 : exprNumber2;
39+
({ a: 1, b: "s" }) ? exprString1 : exprString2;
40+
({ a: 1, b: "s" }) ? exprIsObject1 : exprIsObject2;
41+
({ a: 1, b: "s" }) ? exprString1: exprBoolean1; // union
42+
43+
//Cond is an object type expression
44+
foo() ? exprAny1 : exprAny2;
45+
~~~~~
46+
!!! error TS1345: An expression of type 'void' cannot be tested for truthiness
47+
new Date() ? exprBoolean1 : exprBoolean2;
48+
new C() ? exprNumber1 : exprNumber2;
49+
C.doIt() ? exprString1 : exprString2;
50+
~~~~~~~~
51+
!!! error TS1345: An expression of type 'void' cannot be tested for truthiness
52+
condObject.valueOf() ? exprIsObject1 : exprIsObject2;
53+
new Date() ? exprString1 : exprBoolean1; // union
54+
55+
//Results shoud be same as Expr1 and Expr2
56+
var resultIsAny1 = condObject ? exprAny1 : exprAny2;
57+
var resultIsBoolean1 = condObject ? exprBoolean1 : exprBoolean2;
58+
var resultIsNumber1 = condObject ? exprNumber1 : exprNumber2;
59+
var resultIsString1 = condObject ? exprString1 : exprString2;
60+
var resultIsObject1 = condObject ? exprIsObject1 : exprIsObject2;
61+
var resultIsStringOrBoolean1 = condObject ? exprString1 : exprBoolean1; // union
62+
63+
var resultIsAny2 = ((a: string) => a.length) ? exprAny1 : exprAny2;
64+
var resultIsBoolean2 = ((a: string) => a.length) ? exprBoolean1 : exprBoolean2;
65+
var resultIsNumber2 = ({}) ? exprNumber1 : exprNumber2;
66+
var resultIsString2 = ({ a: 1, b: "s" }) ? exprString1 : exprString2;
67+
var resultIsObject2 = ({ a: 1, b: "s" }) ? exprIsObject1 : exprIsObject2;
68+
var resultIsStringOrBoolean2 = ({ a: 1, b: "s" }) ? exprString1 : exprBoolean1; // union
69+
70+
var resultIsAny3 = foo() ? exprAny1 : exprAny2;
71+
~~~~~
72+
!!! error TS1345: An expression of type 'void' cannot be tested for truthiness
73+
var resultIsBoolean3 = new Date() ? exprBoolean1 : exprBoolean2;
74+
var resultIsNumber3 = new C() ? exprNumber1 : exprNumber2;
75+
var resultIsString3 = C.doIt() ? exprString1 : exprString2;
76+
~~~~~~~~
77+
!!! error TS1345: An expression of type 'void' cannot be tested for truthiness
78+
var resultIsObject3 = condObject.valueOf() ? exprIsObject1 : exprIsObject2;
79+
var resultIsStringOrBoolean3 = C.doIt() ? exprString1 : exprBoolean1; // union
80+
~~~~~~~~
81+
!!! error TS1345: An expression of type 'void' cannot be tested for truthiness
82+
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
tests/cases/conformance/expressions/binaryOperators/logicalAndOperator/logicalAndOperatorStrictMode.ts(46,12): error TS1345: An expression of type 'void' cannot be tested for truthiness
2+
tests/cases/conformance/expressions/binaryOperators/logicalAndOperator/logicalAndOperatorStrictMode.ts(47,12): error TS1345: An expression of type 'void' cannot be tested for truthiness
3+
tests/cases/conformance/expressions/binaryOperators/logicalAndOperator/logicalAndOperatorStrictMode.ts(48,12): error TS1345: An expression of type 'void' cannot be tested for truthiness
4+
tests/cases/conformance/expressions/binaryOperators/logicalAndOperator/logicalAndOperatorStrictMode.ts(49,12): error TS1345: An expression of type 'void' cannot be tested for truthiness
5+
tests/cases/conformance/expressions/binaryOperators/logicalAndOperator/logicalAndOperatorStrictMode.ts(50,12): error TS1345: An expression of type 'void' cannot be tested for truthiness
6+
tests/cases/conformance/expressions/binaryOperators/logicalAndOperator/logicalAndOperatorStrictMode.ts(51,12): error TS1345: An expression of type 'void' cannot be tested for truthiness
7+
tests/cases/conformance/expressions/binaryOperators/logicalAndOperator/logicalAndOperatorStrictMode.ts(52,12): error TS1345: An expression of type 'void' cannot be tested for truthiness
8+
tests/cases/conformance/expressions/binaryOperators/logicalAndOperator/logicalAndOperatorStrictMode.ts(53,12): error TS1345: An expression of type 'void' cannot be tested for truthiness
9+
10+
11+
==== tests/cases/conformance/expressions/binaryOperators/logicalAndOperator/logicalAndOperatorStrictMode.ts (8 errors) ====
12+
const a = [0];
13+
const s = "";
14+
const x = 0;
15+
const b = false;
16+
const v: void = undefined;
17+
const u = undefined;
18+
const n = null;
19+
const z = s || x || u;
20+
21+
const a1 = a && a;
22+
const a2 = a && s;
23+
const a3 = a && x;
24+
const a4 = a && b;
25+
const a5 = a && v;
26+
const a6 = a && u;
27+
const a7 = a && n;
28+
const a8 = a && z;
29+
30+
const s1 = s && a;
31+
const s2 = s && s;
32+
const s3 = s && x;
33+
const s4 = s && b;
34+
const s5 = s && v;
35+
const s6 = s && u;
36+
const s7 = s && n;
37+
const s8 = s && z;
38+
39+
const x1 = x && a;
40+
const x2 = x && s;
41+
const x3 = x && x;
42+
const x4 = x && b;
43+
const x5 = x && v;
44+
const x6 = x && u;
45+
const x7 = x && n;
46+
const x8 = x && z;
47+
48+
const b1 = b && a;
49+
const b2 = b && s;
50+
const b3 = b && x;
51+
const b4 = b && b;
52+
const b5 = b && v;
53+
const b6 = b && u;
54+
const b7 = b && n;
55+
const b8 = b && z;
56+
57+
const v1 = v && a;
58+
~
59+
!!! error TS1345: An expression of type 'void' cannot be tested for truthiness
60+
const v2 = v && s;
61+
~
62+
!!! error TS1345: An expression of type 'void' cannot be tested for truthiness
63+
const v3 = v && x;
64+
~
65+
!!! error TS1345: An expression of type 'void' cannot be tested for truthiness
66+
const v4 = v && b;
67+
~
68+
!!! error TS1345: An expression of type 'void' cannot be tested for truthiness
69+
const v5 = v && v;
70+
~
71+
!!! error TS1345: An expression of type 'void' cannot be tested for truthiness
72+
const v6 = v && u;
73+
~
74+
!!! error TS1345: An expression of type 'void' cannot be tested for truthiness
75+
const v7 = v && n;
76+
~
77+
!!! error TS1345: An expression of type 'void' cannot be tested for truthiness
78+
const v8 = v && z;
79+
~
80+
!!! error TS1345: An expression of type 'void' cannot be tested for truthiness
81+
82+
const u1 = u && a;
83+
const u2 = u && s;
84+
const u3 = u && x;
85+
const u4 = u && b;
86+
const u5 = u && v;
87+
const u6 = u && u;
88+
const u7 = u && n;
89+
const u8 = u && z;
90+
91+
const n1 = n && a;
92+
const n2 = n && s;
93+
const n3 = n && x;
94+
const n4 = n && b;
95+
const n5 = n && v;
96+
const n6 = n && u;
97+
const n7 = n && n;
98+
const n8 = n && z;
99+
100+
const z1 = z && a;
101+
const z2 = z && s;
102+
const z3 = z && x;
103+
const z4 = z && b;
104+
const z5 = z && v;
105+
const z6 = z && u;
106+
const z7 = z && n;
107+
const z8 = z && z;

0 commit comments

Comments
 (0)