Skip to content

Commit d6fde0c

Browse files
authored
Merge pull request #13483 from Microsoft/operatorsAndNullableTypes
Improved checking of nullable operands in expressions
2 parents ed13dde + 221c0d7 commit d6fde0c

File tree

63 files changed

+3138
-1104
lines changed

Some content is hidden

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

63 files changed

+3138
-1104
lines changed

src/compiler/checker.ts

Lines changed: 25 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -12342,16 +12342,18 @@ namespace ts {
1234212342
}
1234312343

1234412344
function checkNonNullExpression(node: Expression | QualifiedName) {
12345-
const type = checkExpression(node);
12346-
if (strictNullChecks) {
12347-
const kind = getFalsyFlags(type) & TypeFlags.Nullable;
12348-
if (kind) {
12349-
error(node, kind & TypeFlags.Undefined ? kind & TypeFlags.Null ?
12350-
Diagnostics.Object_is_possibly_null_or_undefined :
12351-
Diagnostics.Object_is_possibly_undefined :
12352-
Diagnostics.Object_is_possibly_null);
12353-
}
12354-
return getNonNullableType(type);
12345+
return checkNonNullType(checkExpression(node), node);
12346+
}
12347+
12348+
function checkNonNullType(type: Type, errorNode: Node): Type {
12349+
const kind = (strictNullChecks ? getFalsyFlags(type) : type.flags) & TypeFlags.Nullable;
12350+
if (kind) {
12351+
error(errorNode, kind & TypeFlags.Undefined ? kind & TypeFlags.Null ?
12352+
Diagnostics.Object_is_possibly_null_or_undefined :
12353+
Diagnostics.Object_is_possibly_undefined :
12354+
Diagnostics.Object_is_possibly_null);
12355+
const t = getNonNullableType(type);
12356+
return t.flags & (TypeFlags.Nullable | TypeFlags.Never) ? unknownType : t;
1235512357
}
1235612358
return type;
1235712359
}
@@ -14491,6 +14493,7 @@ namespace ts {
1449114493
case SyntaxKind.PlusToken:
1449214494
case SyntaxKind.MinusToken:
1449314495
case SyntaxKind.TildeToken:
14496+
checkNonNullType(operandType, node.operand);
1449414497
if (maybeTypeOfKind(operandType, TypeFlags.ESSymbol)) {
1449514498
error(node.operand, Diagnostics.The_0_operator_cannot_be_applied_to_type_symbol, tokenToString(node.operator));
1449614499
}
@@ -14502,7 +14505,7 @@ namespace ts {
1450214505
booleanType;
1450314506
case SyntaxKind.PlusPlusToken:
1450414507
case SyntaxKind.MinusMinusToken:
14505-
const ok = checkArithmeticOperandType(node.operand, getNonNullableType(operandType),
14508+
const ok = checkArithmeticOperandType(node.operand, checkNonNullType(operandType, node.operand),
1450614509
Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_or_an_enum_type);
1450714510
if (ok) {
1450814511
// run check only if former checks succeeded to avoid reporting cascading errors
@@ -14518,7 +14521,7 @@ namespace ts {
1451814521
if (operandType === silentNeverType) {
1451914522
return silentNeverType;
1452014523
}
14521-
const ok = checkArithmeticOperandType(node.operand, getNonNullableType(operandType),
14524+
const ok = checkArithmeticOperandType(node.operand, checkNonNullType(operandType, node.operand),
1452214525
Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_or_an_enum_type);
1452314526
if (ok) {
1452414527
// run check only if former checks succeeded to avoid reporting cascading errors
@@ -14593,7 +14596,6 @@ namespace ts {
1459314596
}
1459414597
// NOTE: do not raise error if right is unknown as related error was already reported
1459514598
if (!(isTypeAny(rightType) ||
14596-
rightType.flags & TypeFlags.Nullable ||
1459714599
getSignaturesOfType(rightType, SignatureKind.Call).length ||
1459814600
getSignaturesOfType(rightType, SignatureKind.Construct).length ||
1459914601
isTypeSubtypeOf(rightType, globalFunctionType))) {
@@ -14606,6 +14608,8 @@ namespace ts {
1460614608
if (leftType === silentNeverType || rightType === silentNeverType) {
1460714609
return silentNeverType;
1460814610
}
14611+
leftType = checkNonNullType(leftType, left);
14612+
rightType = checkNonNullType(rightType, right);
1460914613
// TypeScript 1.0 spec (April 2014): 4.15.5
1461014614
// The in operator requires the left operand to be of type Any, the String primitive type, or the Number primitive type,
1461114615
// and the right operand to be of type Any, an object type, or a type parameter type.
@@ -14890,17 +14894,9 @@ namespace ts {
1489014894
if (leftType === silentNeverType || rightType === silentNeverType) {
1489114895
return silentNeverType;
1489214896
}
14893-
// TypeScript 1.0 spec (April 2014): 4.19.1
14894-
// These operators require their operands to be of type Any, the Number primitive type,
14895-
// or an enum type. Operands of an enum type are treated
14896-
// as having the primitive type Number. If one operand is the null or undefined value,
14897-
// it is treated as having the type of the other operand.
14898-
// The result is always of the Number primitive type.
14899-
if (leftType.flags & TypeFlags.Nullable) leftType = rightType;
14900-
if (rightType.flags & TypeFlags.Nullable) rightType = leftType;
1490114897

14902-
leftType = getNonNullableType(leftType);
14903-
rightType = getNonNullableType(rightType);
14898+
leftType = checkNonNullType(leftType, left);
14899+
rightType = checkNonNullType(rightType, right);
1490414900

1490514901
let suggestedOperator: SyntaxKind;
1490614902
// if a user tries to apply a bitwise operator to 2 boolean operands
@@ -14925,16 +14921,11 @@ namespace ts {
1492514921
if (leftType === silentNeverType || rightType === silentNeverType) {
1492614922
return silentNeverType;
1492714923
}
14928-
// TypeScript 1.0 spec (April 2014): 4.19.2
14929-
// The binary + operator requires both operands to be of the Number primitive type or an enum type,
14930-
// or at least one of the operands to be of type Any or the String primitive type.
14931-
14932-
// If one operand is the null or undefined value, it is treated as having the type of the other operand.
14933-
if (leftType.flags & TypeFlags.Nullable) leftType = rightType;
14934-
if (rightType.flags & TypeFlags.Nullable) rightType = leftType;
1493514924

14936-
leftType = getNonNullableType(leftType);
14937-
rightType = getNonNullableType(rightType);
14925+
if (!isTypeOfKind(leftType, TypeFlags.Any | TypeFlags.StringLike) && !isTypeOfKind(rightType, TypeFlags.Any | TypeFlags.StringLike)) {
14926+
leftType = checkNonNullType(leftType, left);
14927+
rightType = checkNonNullType(rightType, right);
14928+
}
1493814929

1493914930
let resultType: Type;
1494014931
if (isTypeOfKind(leftType, TypeFlags.NumberLike) && isTypeOfKind(rightType, TypeFlags.NumberLike)) {
@@ -14973,8 +14964,8 @@ namespace ts {
1497314964
case SyntaxKind.LessThanEqualsToken:
1497414965
case SyntaxKind.GreaterThanEqualsToken:
1497514966
if (checkForDisallowedESSymbolOperand(operator)) {
14976-
leftType = getBaseTypeOfLiteralType(leftType);
14977-
rightType = getBaseTypeOfLiteralType(rightType);
14967+
leftType = getBaseTypeOfLiteralType(checkNonNullType(leftType, left));
14968+
rightType = getBaseTypeOfLiteralType(checkNonNullType(rightType, right));
1497814969
if (!isTypeComparableTo(leftType, rightType) && !isTypeComparableTo(rightType, leftType)) {
1497914970
reportOperatorError();
1498014971
}
Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithNullValueAndInvalidOperator.ts(11,10): error TS2365: Operator '+' cannot be applied to types 'boolean' and 'boolean'.
2-
tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithNullValueAndInvalidOperator.ts(12,10): error TS2365: Operator '+' cannot be applied to types 'Object' and 'Object'.
3-
tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithNullValueAndInvalidOperator.ts(13,10): error TS2365: Operator '+' cannot be applied to types 'void' and 'void'.
4-
tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithNullValueAndInvalidOperator.ts(14,10): error TS2365: Operator '+' cannot be applied to types 'boolean' and 'boolean'.
5-
tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithNullValueAndInvalidOperator.ts(15,10): error TS2365: Operator '+' cannot be applied to types 'Object' and 'Object'.
6-
tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithNullValueAndInvalidOperator.ts(16,10): error TS2365: Operator '+' cannot be applied to types 'void' and 'void'.
7-
tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithNullValueAndInvalidOperator.ts(19,10): error TS2365: Operator '+' cannot be applied to types 'Number' and 'Number'.
8-
tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithNullValueAndInvalidOperator.ts(20,10): error TS2365: Operator '+' cannot be applied to types 'true' and 'true'.
9-
tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithNullValueAndInvalidOperator.ts(21,10): error TS2365: Operator '+' cannot be applied to types '{ a: string; }' and '{ a: string; }'.
10-
tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithNullValueAndInvalidOperator.ts(22,11): error TS2365: Operator '+' cannot be applied to types 'void' and 'void'.
11-
tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithNullValueAndInvalidOperator.ts(23,11): error TS2365: Operator '+' cannot be applied to types '() => void' and '() => void'.
1+
tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithNullValueAndInvalidOperator.ts(11,10): error TS2531: Object is possibly 'null'.
2+
tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithNullValueAndInvalidOperator.ts(12,10): error TS2531: Object is possibly 'null'.
3+
tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithNullValueAndInvalidOperator.ts(13,10): error TS2531: Object is possibly 'null'.
4+
tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithNullValueAndInvalidOperator.ts(14,14): error TS2531: Object is possibly 'null'.
5+
tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithNullValueAndInvalidOperator.ts(15,14): error TS2531: Object is possibly 'null'.
6+
tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithNullValueAndInvalidOperator.ts(16,10): error TS2531: Object is possibly 'null'.
7+
tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithNullValueAndInvalidOperator.ts(19,10): error TS2531: Object is possibly 'null'.
8+
tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithNullValueAndInvalidOperator.ts(20,10): error TS2531: Object is possibly 'null'.
9+
tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithNullValueAndInvalidOperator.ts(21,10): error TS2531: Object is possibly 'null'.
10+
tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithNullValueAndInvalidOperator.ts(22,11): error TS2531: Object is possibly 'null'.
11+
tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithNullValueAndInvalidOperator.ts(23,11): error TS2531: Object is possibly 'null'.
1212

1313

1414
==== tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithNullValueAndInvalidOperator.ts (11 errors) ====
@@ -23,37 +23,37 @@ tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOpe
2323

2424
// null + boolean/Object
2525
var r1 = null + a;
26-
~~~~~~~~
27-
!!! error TS2365: Operator '+' cannot be applied to types 'boolean' and 'boolean'.
26+
~~~~
27+
!!! error TS2531: Object is possibly 'null'.
2828
var r2 = null + b;
29-
~~~~~~~~
30-
!!! error TS2365: Operator '+' cannot be applied to types 'Object' and 'Object'.
29+
~~~~
30+
!!! error TS2531: Object is possibly 'null'.
3131
var r3 = null + c;
32-
~~~~~~~~
33-
!!! error TS2365: Operator '+' cannot be applied to types 'void' and 'void'.
32+
~~~~
33+
!!! error TS2531: Object is possibly 'null'.
3434
var r4 = a + null;
35-
~~~~~~~~
36-
!!! error TS2365: Operator '+' cannot be applied to types 'boolean' and 'boolean'.
35+
~~~~
36+
!!! error TS2531: Object is possibly 'null'.
3737
var r5 = b + null;
38-
~~~~~~~~
39-
!!! error TS2365: Operator '+' cannot be applied to types 'Object' and 'Object'.
38+
~~~~
39+
!!! error TS2531: Object is possibly 'null'.
4040
var r6 = null + c;
41-
~~~~~~~~
42-
!!! error TS2365: Operator '+' cannot be applied to types 'void' and 'void'.
41+
~~~~
42+
!!! error TS2531: Object is possibly 'null'.
4343

4444
// other cases
4545
var r7 = null + d;
46-
~~~~~~~~
47-
!!! error TS2365: Operator '+' cannot be applied to types 'Number' and 'Number'.
46+
~~~~
47+
!!! error TS2531: Object is possibly 'null'.
4848
var r8 = null + true;
49-
~~~~~~~~~~~
50-
!!! error TS2365: Operator '+' cannot be applied to types 'true' and 'true'.
49+
~~~~
50+
!!! error TS2531: Object is possibly 'null'.
5151
var r9 = null + { a: '' };
52-
~~~~~~~~~~~~~~~~
53-
!!! error TS2365: Operator '+' cannot be applied to types '{ a: string; }' and '{ a: string; }'.
52+
~~~~
53+
!!! error TS2531: Object is possibly 'null'.
5454
var r10 = null + foo();
55-
~~~~~~~~~~~~
56-
!!! error TS2365: Operator '+' cannot be applied to types 'void' and 'void'.
55+
~~~~
56+
!!! error TS2531: Object is possibly 'null'.
5757
var r11 = null + (() => { });
58-
~~~~~~~~~~~~~~~~~~
59-
!!! error TS2365: Operator '+' cannot be applied to types '() => void' and '() => void'.
58+
~~~~
59+
!!! error TS2531: Object is possibly 'null'.
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithNullValueAndValidOperator.ts(15,10): error TS2531: Object is possibly 'null'.
2+
tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithNullValueAndValidOperator.ts(16,10): error TS2531: Object is possibly 'null'.
3+
tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithNullValueAndValidOperator.ts(17,10): error TS2531: Object is possibly 'null'.
4+
tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithNullValueAndValidOperator.ts(18,10): error TS2531: Object is possibly 'null'.
5+
tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithNullValueAndValidOperator.ts(19,10): error TS2531: Object is possibly 'null'.
6+
tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithNullValueAndValidOperator.ts(20,14): error TS2531: Object is possibly 'null'.
7+
tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithNullValueAndValidOperator.ts(21,14): error TS2531: Object is possibly 'null'.
8+
tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithNullValueAndValidOperator.ts(22,15): error TS2531: Object is possibly 'null'.
9+
tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithNullValueAndValidOperator.ts(23,17): error TS2531: Object is possibly 'null'.
10+
tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithNullValueAndValidOperator.ts(24,20): error TS2531: Object is possibly 'null'.
11+
12+
13+
==== tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithNullValueAndValidOperator.ts (10 errors) ====
14+
// If one operand is the null or undefined value, it is treated as having the type of the other operand.
15+
16+
enum E { a, b, c }
17+
18+
var a: any;
19+
var b: number;
20+
var c: E;
21+
var d: string;
22+
23+
// null + any
24+
var r1: any = null + a;
25+
var r2: any = a + null;
26+
27+
// null + number/enum
28+
var r3 = null + b;
29+
~~~~
30+
!!! error TS2531: Object is possibly 'null'.
31+
var r4 = null + 1;
32+
~~~~
33+
!!! error TS2531: Object is possibly 'null'.
34+
var r5 = null + c;
35+
~~~~
36+
!!! error TS2531: Object is possibly 'null'.
37+
var r6 = null + E.a;
38+
~~~~
39+
!!! error TS2531: Object is possibly 'null'.
40+
var r7 = null + E['a'];
41+
~~~~
42+
!!! error TS2531: Object is possibly 'null'.
43+
var r8 = b + null;
44+
~~~~
45+
!!! error TS2531: Object is possibly 'null'.
46+
var r9 = 1 + null;
47+
~~~~
48+
!!! error TS2531: Object is possibly 'null'.
49+
var r10 = c + null
50+
~~~~
51+
!!! error TS2531: Object is possibly 'null'.
52+
var r11 = E.a + null;
53+
~~~~
54+
!!! error TS2531: Object is possibly 'null'.
55+
var r12 = E['a'] + null;
56+
~~~~
57+
!!! error TS2531: Object is possibly 'null'.
58+
59+
// null + string
60+
var r13 = null + d;
61+
var r14 = null + '';
62+
var r15 = d + null;
63+
var r16 = '' + null;

0 commit comments

Comments
 (0)