Skip to content

Make for..in expressions allowed to be null/undefined #28348

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 6, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4736,7 +4736,7 @@ namespace ts {
// A variable declared in a for..in statement is of type string, or of type keyof T when the
// right hand expression is of a type parameter type.
if (isVariableDeclaration(declaration) && declaration.parent.parent.kind === SyntaxKind.ForInStatement) {
const indexType = getIndexType(checkNonNullExpression(declaration.parent.parent.expression));
const indexType = getIndexType(getNonNullableTypeIfNeeded(checkExpression(declaration.parent.parent.expression)));
return indexType.flags & (TypeFlags.TypeParameter | TypeFlags.Index) ? getExtractStringType(indexType) : stringType;
}

Expand Down Expand Up @@ -15091,6 +15091,10 @@ namespace ts {
}
return declaredType;
}
// for (const _ in ref) acts as a nonnull on ref
if (isVariableDeclaration(node) && node.parent.parent.kind === SyntaxKind.ForInStatement && isMatchingReference(reference, node.parent.parent.expression)) {
return getNonNullableTypeIfNeeded(getTypeFromFlowType(getTypeAtFlowNode(flow.antecedent)));
}
// Assignment doesn't affect reference
return undefined;
}
Expand Down Expand Up @@ -18404,6 +18408,14 @@ namespace ts {
);
}

function getNonNullableTypeIfNeeded(type: Type) {
const kind = (strictNullChecks ? getFalsyFlags(type) : type.flags) & TypeFlags.Nullable;
if (kind) {
return getNonNullableType(type);
}
return type;
}

function checkNonNullType(
type: Type,
node: Node,
Expand Down Expand Up @@ -25142,7 +25154,7 @@ namespace ts {
// Grammar checking
checkGrammarForInOrForOfStatement(node);

const rightType = checkNonNullExpression(node.expression);
const rightType = getNonNullableTypeIfNeeded(checkExpression(node.expression));
// TypeScript 1.0 spec (April 2014): 5.4
// In a 'for-in' statement of the form
// for (let VarDecl in Expr) Statement
Expand Down
5 changes: 1 addition & 4 deletions tests/baselines/reference/ambientWithStatements.errors.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
tests/cases/compiler/ambientWithStatements.ts(2,5): error TS1036: Statements are not allowed in ambient contexts.
tests/cases/compiler/ambientWithStatements.ts(3,5): error TS1104: A 'continue' statement can only be used within an enclosing iteration statement.
tests/cases/compiler/ambientWithStatements.ts(7,15): error TS2531: Object is possibly 'null'.
tests/cases/compiler/ambientWithStatements.ts(11,5): error TS1108: A 'return' statement can only be used within a function body.
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'.


==== tests/cases/compiler/ambientWithStatements.ts (5 errors) ====
==== tests/cases/compiler/ambientWithStatements.ts (4 errors) ====
declare module M {
break;
~~~~~
Expand All @@ -17,8 +16,6 @@ tests/cases/compiler/ambientWithStatements.ts(25,5): error TS2410: The 'with' st
do { } while (true);
var x;
for (x in null) { }
~~~~
!!! error TS2531: Object is possibly 'null'.
if (true) { } else { }
1;
L: var y;
Expand Down
12 changes: 12 additions & 0 deletions tests/baselines/reference/forInStrictNullChecksNoError.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
tests/cases/compiler/forInStrictNullChecksNoError.ts(5,5): error TS2533: Object is possibly 'null' or 'undefined'.


==== tests/cases/compiler/forInStrictNullChecksNoError.ts (1 errors) ====
function f(x: { [key: string]: number; } | null | undefined) {
for (const key in x) { // 1
console.log(x[key]); // 2
}
x["no"]; // should still error
~
!!! error TS2533: Object is possibly 'null' or 'undefined'.
}
15 changes: 15 additions & 0 deletions tests/baselines/reference/forInStrictNullChecksNoError.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//// [forInStrictNullChecksNoError.ts]
function f(x: { [key: string]: number; } | null | undefined) {
for (const key in x) { // 1
console.log(x[key]); // 2
}
x["no"]; // should still error
}

//// [forInStrictNullChecksNoError.js]
function f(x) {
for (var key in x) { // 1
console.log(x[key]); // 2
}
x["no"]; // should still error
}
20 changes: 20 additions & 0 deletions tests/baselines/reference/forInStrictNullChecksNoError.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
=== tests/cases/compiler/forInStrictNullChecksNoError.ts ===
function f(x: { [key: string]: number; } | null | undefined) {
>f : Symbol(f, Decl(forInStrictNullChecksNoError.ts, 0, 0))
>x : Symbol(x, Decl(forInStrictNullChecksNoError.ts, 0, 11))
>key : Symbol(key, Decl(forInStrictNullChecksNoError.ts, 0, 17))

for (const key in x) { // 1
>key : Symbol(key, Decl(forInStrictNullChecksNoError.ts, 1, 14))
>x : Symbol(x, Decl(forInStrictNullChecksNoError.ts, 0, 11))

console.log(x[key]); // 2
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>x : Symbol(x, Decl(forInStrictNullChecksNoError.ts, 0, 11))
>key : Symbol(key, Decl(forInStrictNullChecksNoError.ts, 1, 14))
}
x["no"]; // should still error
>x : Symbol(x, Decl(forInStrictNullChecksNoError.ts, 0, 11))
}
25 changes: 25 additions & 0 deletions tests/baselines/reference/forInStrictNullChecksNoError.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
=== tests/cases/compiler/forInStrictNullChecksNoError.ts ===
function f(x: { [key: string]: number; } | null | undefined) {
>f : (x: { [key: string]: number; } | null | undefined) => void
>x : { [key: string]: number; } | null | undefined
>key : string
>null : null

for (const key in x) { // 1
>key : string
>x : { [key: string]: number; } | null | undefined

console.log(x[key]); // 2
>console.log(x[key]) : void
>console.log : (message?: any, ...optionalParams: any[]) => void
>console : Console
>log : (message?: any, ...optionalParams: any[]) => void
>x[key] : number
>x : { [key: string]: number; }
>key : string
}
x["no"]; // should still error
>x["no"] : number
>x : { [key: string]: number; } | null | undefined
>"no" : "no"
}
5 changes: 1 addition & 4 deletions tests/baselines/reference/widenedTypes.errors.txt
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
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.
tests/cases/compiler/widenedTypes.ts(4,1): error TS2531: Object is possibly 'null'.
tests/cases/compiler/widenedTypes.ts(5,7): error TS2531: Object is possibly 'null'.
tests/cases/compiler/widenedTypes.ts(7,15): error TS2531: Object is possibly 'null'.
tests/cases/compiler/widenedTypes.ts(9,14): error TS2695: Left side of comma operator is unused and has no side effects.
tests/cases/compiler/widenedTypes.ts(10,1): error TS2322: Type '""' is not assignable to type 'number'.
tests/cases/compiler/widenedTypes.ts(17,1): error TS2322: Type '""' is not assignable to type 'number'.
tests/cases/compiler/widenedTypes.ts(22,22): error TS2322: Type 'number' is not assignable to type 'string'.
tests/cases/compiler/widenedTypes.ts(23,39): error TS2322: Type 'number' is not assignable to type 'string'.


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

for (var a in null) { }
~~~~
!!! error TS2531: Object is possibly 'null'.

var t = [3, (3, null)];
~
Expand Down
7 changes: 7 additions & 0 deletions tests/cases/compiler/forInStrictNullChecksNoError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// @strictNullChecks: true
function f(x: { [key: string]: number; } | null | undefined) {
for (const key in x) { // 1
console.log(x[key]); // 2
}
x["no"]; // should still error
}