diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 92a989a2bc707..89946fa40660a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -27275,6 +27275,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const isOuterVariable = flowContainer !== declarationContainer; const isSpreadDestructuringAssignmentTarget = node.parent && node.parent.parent && isSpreadAssignment(node.parent) && isDestructuringAssignmentTarget(node.parent.parent); const isModuleExports = symbol.flags & SymbolFlags.ModuleExports; + const typeIsAutomatic = type === autoType || type === autoArrayType; + const isAutomaticTypeInNonNull = typeIsAutomatic && node.parent.kind === SyntaxKind.NonNullExpression; // When the control flow originates in a function expression or arrow function and we are referencing // a const variable or parameter from an outer function, we extend the origin of the control flow // analysis to include the immediately enclosing function. @@ -27292,10 +27294,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { node.parent.kind === SyntaxKind.NonNullExpression || declaration.kind === SyntaxKind.VariableDeclaration && (declaration as VariableDeclaration).exclamationToken || declaration.flags & NodeFlags.Ambient; - const initialType = assumeInitialized ? (isParameter ? removeOptionalityFromDeclaredType(type, declaration as VariableLikeDeclaration) : type) : - type === autoType || type === autoArrayType ? undefinedType : - getOptionalType(type); - const flowType = getFlowTypeOfReference(node, type, initialType, flowContainer); + const initialType = isAutomaticTypeInNonNull ? undefinedType : + assumeInitialized ? (isParameter ? removeOptionalityFromDeclaredType(type, declaration as VariableLikeDeclaration) : type) : + typeIsAutomatic ? undefinedType : getOptionalType(type); + const flowType = isAutomaticTypeInNonNull ? getNonNullableType(getFlowTypeOfReference(node, type, initialType, flowContainer)) : + getFlowTypeOfReference(node, type, initialType, flowContainer); // A variable is considered uninitialized when it is possible to analyze the entire control flow graph // from declaration to use, and when the variable's declared type doesn't include undefined but the // control flow based type does include undefined. diff --git a/tests/baselines/reference/nonNullFullInference.js b/tests/baselines/reference/nonNullFullInference.js new file mode 100644 index 0000000000000..0acc9c7f35f9d --- /dev/null +++ b/tests/baselines/reference/nonNullFullInference.js @@ -0,0 +1,63 @@ +//// [nonNullFullInference.ts] +// https://github.com/microsoft/TypeScript/issues/19577 + +function testNonNullInference(numbers: number[]) { + let last; + + for (const n of numbers) { + if (n % 2) { + return n; + } + + last = n; + } + + last; + last!; +} + +function testNonNullInferenceWithArrays(numbers: number[]) { + let result; + const arr = []; + + for (const n of numbers) { + if (n % 2) { + return [n]; + } + + arr.push(n); + result = arr; + } + + result; + result!; +} + +//// [nonNullFullInference.js] +// https://github.com/microsoft/TypeScript/issues/19577 +function testNonNullInference(numbers) { + var last; + for (var _i = 0, numbers_1 = numbers; _i < numbers_1.length; _i++) { + var n = numbers_1[_i]; + if (n % 2) { + return n; + } + last = n; + } + last; + last; +} +function testNonNullInferenceWithArrays(numbers) { + var result; + var arr = []; + for (var _i = 0, numbers_2 = numbers; _i < numbers_2.length; _i++) { + var n = numbers_2[_i]; + if (n % 2) { + return [n]; + } + arr.push(n); + result = arr; + } + result; + result; +} diff --git a/tests/baselines/reference/nonNullFullInference.symbols b/tests/baselines/reference/nonNullFullInference.symbols new file mode 100644 index 0000000000000..a377529bad2fc --- /dev/null +++ b/tests/baselines/reference/nonNullFullInference.symbols @@ -0,0 +1,71 @@ +=== tests/cases/compiler/nonNullFullInference.ts === +// https://github.com/microsoft/TypeScript/issues/19577 + +function testNonNullInference(numbers: number[]) { +>testNonNullInference : Symbol(testNonNullInference, Decl(nonNullFullInference.ts, 0, 0)) +>numbers : Symbol(numbers, Decl(nonNullFullInference.ts, 2, 30)) + + let last; +>last : Symbol(last, Decl(nonNullFullInference.ts, 3, 7)) + + for (const n of numbers) { +>n : Symbol(n, Decl(nonNullFullInference.ts, 5, 14)) +>numbers : Symbol(numbers, Decl(nonNullFullInference.ts, 2, 30)) + + if (n % 2) { +>n : Symbol(n, Decl(nonNullFullInference.ts, 5, 14)) + + return n; +>n : Symbol(n, Decl(nonNullFullInference.ts, 5, 14)) + } + + last = n; +>last : Symbol(last, Decl(nonNullFullInference.ts, 3, 7)) +>n : Symbol(n, Decl(nonNullFullInference.ts, 5, 14)) + } + + last; +>last : Symbol(last, Decl(nonNullFullInference.ts, 3, 7)) + + last!; +>last : Symbol(last, Decl(nonNullFullInference.ts, 3, 7)) +} + +function testNonNullInferenceWithArrays(numbers: number[]) { +>testNonNullInferenceWithArrays : Symbol(testNonNullInferenceWithArrays, Decl(nonNullFullInference.ts, 15, 1)) +>numbers : Symbol(numbers, Decl(nonNullFullInference.ts, 17, 40)) + + let result; +>result : Symbol(result, Decl(nonNullFullInference.ts, 18, 7)) + + const arr = []; +>arr : Symbol(arr, Decl(nonNullFullInference.ts, 19, 9)) + + for (const n of numbers) { +>n : Symbol(n, Decl(nonNullFullInference.ts, 21, 14)) +>numbers : Symbol(numbers, Decl(nonNullFullInference.ts, 17, 40)) + + if (n % 2) { +>n : Symbol(n, Decl(nonNullFullInference.ts, 21, 14)) + + return [n]; +>n : Symbol(n, Decl(nonNullFullInference.ts, 21, 14)) + } + + arr.push(n); +>arr.push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --)) +>arr : Symbol(arr, Decl(nonNullFullInference.ts, 19, 9)) +>push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --)) +>n : Symbol(n, Decl(nonNullFullInference.ts, 21, 14)) + + result = arr; +>result : Symbol(result, Decl(nonNullFullInference.ts, 18, 7)) +>arr : Symbol(arr, Decl(nonNullFullInference.ts, 19, 9)) + } + + result; +>result : Symbol(result, Decl(nonNullFullInference.ts, 18, 7)) + + result!; +>result : Symbol(result, Decl(nonNullFullInference.ts, 18, 7)) +} diff --git a/tests/baselines/reference/nonNullFullInference.types b/tests/baselines/reference/nonNullFullInference.types new file mode 100644 index 0000000000000..b12dd82259a24 --- /dev/null +++ b/tests/baselines/reference/nonNullFullInference.types @@ -0,0 +1,82 @@ +=== tests/cases/compiler/nonNullFullInference.ts === +// https://github.com/microsoft/TypeScript/issues/19577 + +function testNonNullInference(numbers: number[]) { +>testNonNullInference : (numbers: number[]) => number +>numbers : number[] + + let last; +>last : any + + for (const n of numbers) { +>n : number +>numbers : number[] + + if (n % 2) { +>n % 2 : number +>n : number +>2 : 2 + + return n; +>n : number + } + + last = n; +>last = n : number +>last : any +>n : number + } + + last; +>last : number + + last!; +>last! : number +>last : number +} + +function testNonNullInferenceWithArrays(numbers: number[]) { +>testNonNullInferenceWithArrays : (numbers: number[]) => number[] +>numbers : number[] + + let result; +>result : any + + const arr = []; +>arr : any[] +>[] : undefined[] + + for (const n of numbers) { +>n : number +>numbers : number[] + + if (n % 2) { +>n % 2 : number +>n : number +>2 : 2 + + return [n]; +>[n] : number[] +>n : number + } + + arr.push(n); +>arr.push(n) : number +>arr.push : (...items: any[]) => number +>arr : any[] +>push : (...items: any[]) => number +>n : number + + result = arr; +>result = arr : number[] +>result : any +>arr : number[] + } + + result; +>result : number[] + + result!; +>result! : number[] +>result : number[] +} diff --git a/tests/cases/compiler/nonNullFullInference.ts b/tests/cases/compiler/nonNullFullInference.ts new file mode 100644 index 0000000000000..1efda6f1374d7 --- /dev/null +++ b/tests/cases/compiler/nonNullFullInference.ts @@ -0,0 +1,34 @@ +// @noImplicitAny: true +// https://github.com/microsoft/TypeScript/issues/19577 + +function testNonNullInference(numbers: number[]) { + let last; + + for (const n of numbers) { + if (n % 2) { + return n; + } + + last = n; + } + + last; + last!; +} + +function testNonNullInferenceWithArrays(numbers: number[]) { + let result; + const arr = []; + + for (const n of numbers) { + if (n % 2) { + return [n]; + } + + arr.push(n); + result = arr; + } + + result; + result!; +} \ No newline at end of file