diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d92d32116f7c9..2d6c0d5aac0cd 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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; } @@ -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; } @@ -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, @@ -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 diff --git a/tests/baselines/reference/ambientWithStatements.errors.txt b/tests/baselines/reference/ambientWithStatements.errors.txt index 3e134970a1f24..c7cb28a0f4675 100644 --- a/tests/baselines/reference/ambientWithStatements.errors.txt +++ b/tests/baselines/reference/ambientWithStatements.errors.txt @@ -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; ~~~~~ @@ -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; diff --git a/tests/baselines/reference/forInStrictNullChecksNoError.errors.txt b/tests/baselines/reference/forInStrictNullChecksNoError.errors.txt new file mode 100644 index 0000000000000..fd6bd4c8d8f8a --- /dev/null +++ b/tests/baselines/reference/forInStrictNullChecksNoError.errors.txt @@ -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'. + } \ No newline at end of file diff --git a/tests/baselines/reference/forInStrictNullChecksNoError.js b/tests/baselines/reference/forInStrictNullChecksNoError.js new file mode 100644 index 0000000000000..d6a036ec1d44f --- /dev/null +++ b/tests/baselines/reference/forInStrictNullChecksNoError.js @@ -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 +} diff --git a/tests/baselines/reference/forInStrictNullChecksNoError.symbols b/tests/baselines/reference/forInStrictNullChecksNoError.symbols new file mode 100644 index 0000000000000..861856fab0f37 --- /dev/null +++ b/tests/baselines/reference/forInStrictNullChecksNoError.symbols @@ -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)) +} diff --git a/tests/baselines/reference/forInStrictNullChecksNoError.types b/tests/baselines/reference/forInStrictNullChecksNoError.types new file mode 100644 index 0000000000000..ffbc4af929dc3 --- /dev/null +++ b/tests/baselines/reference/forInStrictNullChecksNoError.types @@ -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" +} diff --git a/tests/baselines/reference/widenedTypes.errors.txt b/tests/baselines/reference/widenedTypes.errors.txt index fb6779ff0eb0d..1c3efb3a624aa 100644 --- a/tests/baselines/reference/widenedTypes.errors.txt +++ b/tests/baselines/reference/widenedTypes.errors.txt @@ -1,7 +1,6 @@ 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'. @@ -9,7 +8,7 @@ tests/cases/compiler/widenedTypes.ts(22,22): error TS2322: Type 'number' is not 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. @@ -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)]; ~ diff --git a/tests/cases/compiler/forInStrictNullChecksNoError.ts b/tests/cases/compiler/forInStrictNullChecksNoError.ts new file mode 100644 index 0000000000000..61e4f665d49ed --- /dev/null +++ b/tests/cases/compiler/forInStrictNullChecksNoError.ts @@ -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 +} \ No newline at end of file