Skip to content

Commit 9297918

Browse files
authored
Make for..in expressions allowed to be null/undefined (#28348)
1 parent 85dbc04 commit 9297918

8 files changed

+95
-10
lines changed

src/compiler/checker.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4766,7 +4766,7 @@ namespace ts {
47664766
// A variable declared in a for..in statement is of type string, or of type keyof T when the
47674767
// right hand expression is of a type parameter type.
47684768
if (isVariableDeclaration(declaration) && declaration.parent.parent.kind === SyntaxKind.ForInStatement) {
4769-
const indexType = getIndexType(checkNonNullExpression(declaration.parent.parent.expression));
4769+
const indexType = getIndexType(getNonNullableTypeIfNeeded(checkExpression(declaration.parent.parent.expression)));
47704770
return indexType.flags & (TypeFlags.TypeParameter | TypeFlags.Index) ? getExtractStringType(indexType) : stringType;
47714771
}
47724772

@@ -15175,6 +15175,10 @@ namespace ts {
1517515175
}
1517615176
return declaredType;
1517715177
}
15178+
// for (const _ in ref) acts as a nonnull on ref
15179+
if (isVariableDeclaration(node) && node.parent.parent.kind === SyntaxKind.ForInStatement && isMatchingReference(reference, node.parent.parent.expression)) {
15180+
return getNonNullableTypeIfNeeded(getTypeFromFlowType(getTypeAtFlowNode(flow.antecedent)));
15181+
}
1517815182
// Assignment doesn't affect reference
1517915183
return undefined;
1518015184
}
@@ -18489,6 +18493,14 @@ namespace ts {
1848918493
);
1849018494
}
1849118495

18496+
function getNonNullableTypeIfNeeded(type: Type) {
18497+
const kind = (strictNullChecks ? getFalsyFlags(type) : type.flags) & TypeFlags.Nullable;
18498+
if (kind) {
18499+
return getNonNullableType(type);
18500+
}
18501+
return type;
18502+
}
18503+
1849218504
function checkNonNullType(
1849318505
type: Type,
1849418506
node: Node,
@@ -25286,7 +25298,7 @@ namespace ts {
2528625298
// Grammar checking
2528725299
checkGrammarForInOrForOfStatement(node);
2528825300

25289-
const rightType = checkNonNullExpression(node.expression);
25301+
const rightType = getNonNullableTypeIfNeeded(checkExpression(node.expression));
2529025302
// TypeScript 1.0 spec (April 2014): 5.4
2529125303
// In a 'for-in' statement of the form
2529225304
// for (let VarDecl in Expr) Statement

tests/baselines/reference/ambientWithStatements.errors.txt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
tests/cases/compiler/ambientWithStatements.ts(2,5): error TS1036: Statements are not allowed in ambient contexts.
22
tests/cases/compiler/ambientWithStatements.ts(3,5): error TS1104: A 'continue' statement can only be used within an enclosing iteration statement.
3-
tests/cases/compiler/ambientWithStatements.ts(7,15): error TS2531: Object is possibly 'null'.
43
tests/cases/compiler/ambientWithStatements.ts(11,5): error TS1108: A 'return' statement can only be used within a function body.
54
tests/cases/compiler/ambientWithStatements.ts(25,5): error TS2410: The 'with' statement is not supported. All symbols in a 'with' block will have type 'any'.
65

76

8-
==== tests/cases/compiler/ambientWithStatements.ts (5 errors) ====
7+
==== tests/cases/compiler/ambientWithStatements.ts (4 errors) ====
98
declare module M {
109
break;
1110
~~~~~
@@ -17,8 +16,6 @@ tests/cases/compiler/ambientWithStatements.ts(25,5): error TS2410: The 'with' st
1716
do { } while (true);
1817
var x;
1918
for (x in null) { }
20-
~~~~
21-
!!! error TS2531: Object is possibly 'null'.
2219
if (true) { } else { }
2320
1;
2421
L: var y;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
tests/cases/compiler/forInStrictNullChecksNoError.ts(5,5): error TS2533: Object is possibly 'null' or 'undefined'.
2+
3+
4+
==== tests/cases/compiler/forInStrictNullChecksNoError.ts (1 errors) ====
5+
function f(x: { [key: string]: number; } | null | undefined) {
6+
for (const key in x) { // 1
7+
console.log(x[key]); // 2
8+
}
9+
x["no"]; // should still error
10+
~
11+
!!! error TS2533: Object is possibly 'null' or 'undefined'.
12+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//// [forInStrictNullChecksNoError.ts]
2+
function f(x: { [key: string]: number; } | null | undefined) {
3+
for (const key in x) { // 1
4+
console.log(x[key]); // 2
5+
}
6+
x["no"]; // should still error
7+
}
8+
9+
//// [forInStrictNullChecksNoError.js]
10+
function f(x) {
11+
for (var key in x) { // 1
12+
console.log(x[key]); // 2
13+
}
14+
x["no"]; // should still error
15+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
=== tests/cases/compiler/forInStrictNullChecksNoError.ts ===
2+
function f(x: { [key: string]: number; } | null | undefined) {
3+
>f : Symbol(f, Decl(forInStrictNullChecksNoError.ts, 0, 0))
4+
>x : Symbol(x, Decl(forInStrictNullChecksNoError.ts, 0, 11))
5+
>key : Symbol(key, Decl(forInStrictNullChecksNoError.ts, 0, 17))
6+
7+
for (const key in x) { // 1
8+
>key : Symbol(key, Decl(forInStrictNullChecksNoError.ts, 1, 14))
9+
>x : Symbol(x, Decl(forInStrictNullChecksNoError.ts, 0, 11))
10+
11+
console.log(x[key]); // 2
12+
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
13+
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
14+
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
15+
>x : Symbol(x, Decl(forInStrictNullChecksNoError.ts, 0, 11))
16+
>key : Symbol(key, Decl(forInStrictNullChecksNoError.ts, 1, 14))
17+
}
18+
x["no"]; // should still error
19+
>x : Symbol(x, Decl(forInStrictNullChecksNoError.ts, 0, 11))
20+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
=== tests/cases/compiler/forInStrictNullChecksNoError.ts ===
2+
function f(x: { [key: string]: number; } | null | undefined) {
3+
>f : (x: { [key: string]: number; } | null | undefined) => void
4+
>x : { [key: string]: number; } | null | undefined
5+
>key : string
6+
>null : null
7+
8+
for (const key in x) { // 1
9+
>key : string
10+
>x : { [key: string]: number; } | null | undefined
11+
12+
console.log(x[key]); // 2
13+
>console.log(x[key]) : void
14+
>console.log : (message?: any, ...optionalParams: any[]) => void
15+
>console : Console
16+
>log : (message?: any, ...optionalParams: any[]) => void
17+
>x[key] : number
18+
>x : { [key: string]: number; }
19+
>key : string
20+
}
21+
x["no"]; // should still error
22+
>x["no"] : number
23+
>x : { [key: string]: number; } | null | undefined
24+
>"no" : "no"
25+
}

tests/baselines/reference/widenedTypes.errors.txt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
tests/cases/compiler/widenedTypes.ts(1,1): error TS2358: The left-hand side of an 'instanceof' expression must be of type 'any', an object type or a type parameter.
22
tests/cases/compiler/widenedTypes.ts(4,1): error TS2531: Object is possibly 'null'.
33
tests/cases/compiler/widenedTypes.ts(5,7): error TS2531: Object is possibly 'null'.
4-
tests/cases/compiler/widenedTypes.ts(7,15): error TS2531: Object is possibly 'null'.
54
tests/cases/compiler/widenedTypes.ts(9,14): error TS2695: Left side of comma operator is unused and has no side effects.
65
tests/cases/compiler/widenedTypes.ts(10,1): error TS2322: Type '""' is not assignable to type 'number'.
76
tests/cases/compiler/widenedTypes.ts(17,1): error TS2322: Type '""' is not assignable to type 'number'.
87
tests/cases/compiler/widenedTypes.ts(22,22): error TS2322: Type 'number' is not assignable to type 'string'.
98
tests/cases/compiler/widenedTypes.ts(23,39): error TS2322: Type 'number' is not assignable to type 'string'.
109

1110

12-
==== tests/cases/compiler/widenedTypes.ts (9 errors) ====
11+
==== tests/cases/compiler/widenedTypes.ts (8 errors) ====
1312
null instanceof (() => { });
1413
~~~~
1514
!!! error TS2358: The left-hand side of an 'instanceof' expression must be of type 'any', an object type or a type parameter.
@@ -23,8 +22,6 @@ tests/cases/compiler/widenedTypes.ts(23,39): error TS2322: Type 'number' is not
2322
!!! error TS2531: Object is possibly 'null'.
2423

2524
for (var a in null) { }
26-
~~~~
27-
!!! error TS2531: Object is possibly 'null'.
2825

2926
var t = [3, (3, null)];
3027
~
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// @strictNullChecks: true
2+
function f(x: { [key: string]: number; } | null | undefined) {
3+
for (const key in x) { // 1
4+
console.log(x[key]); // 2
5+
}
6+
x["no"]; // should still error
7+
}

0 commit comments

Comments
 (0)