diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index aab919a844c..be2e6f9d8ab 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -87,8 +87,8 @@ namespace ts { NEUndefinedOrNull = 1 << 21, // x != undefined / x != null Truthy = 1 << 22, // x Falsy = 1 << 23, // !x - IsUndefined = 1 << 24, // Exactly undefined - IsNull = 1 << 25, // Exactly null + IsUndefined = 1 << 24, // Contains undefined or intersection with undefined + IsNull = 1 << 25, // Contains null or intersection with null IsUndefinedOrNull = IsUndefined | IsNull, All = (1 << 27) - 1, // The following members encode facts about particular kinds of types for use in the getTypeFacts function. @@ -18166,6 +18166,10 @@ namespace ts { return false; } + function containsUndefinedType(type: Type) { + return !!((type.flags & TypeFlags.Union ? (type as UnionType).types[0] : type).flags & TypeFlags.Undefined); + } + function isStringIndexSignatureOnlyType(type: Type): boolean { return type.flags & TypeFlags.Object && !isGenericMappedType(type) && getPropertiesOfType(type).length === 0 && getIndexInfosOfType(type).length === 1 && !!getIndexInfoOfType(type, stringType) || type.flags & TypeFlags.UnionOrIntersection && every((type as UnionOrIntersectionType).types, isStringIndexSignatureOnlyType) || @@ -25911,7 +25915,7 @@ namespace ts { return convertAutoToAny(flowType); } } - else if (!assumeInitialized && !(getTypeFacts(type) & TypeFacts.IsUndefined) && getTypeFacts(flowType) & TypeFacts.IsUndefined) { + else if (!assumeInitialized && !containsUndefinedType(type) && containsUndefinedType(flowType)) { error(node, Diagnostics.Variable_0_is_used_before_being_assigned, symbolToString(symbol)); // Return the declared type to reduce follow-on errors return type; @@ -29268,7 +29272,7 @@ namespace ts { assumeUninitialized = true; } const flowType = getFlowTypeOfReference(node, propType, assumeUninitialized ? getOptionalType(propType) : propType); - if (assumeUninitialized && !(getTypeFacts(propType) & TypeFacts.IsUndefined) && getTypeFacts(flowType) & TypeFacts.IsUndefined) { + if (assumeUninitialized && !containsUndefinedType(propType) && containsUndefinedType(flowType)) { error(errorNode, Diagnostics.Property_0_is_used_before_being_assigned, symbolToString(prop!)); // TODO: GH#18217 // Return the declared type to reduce follow-on errors return propType; @@ -40255,7 +40259,7 @@ namespace ts { const propName = (member as PropertyDeclaration).name; if (isIdentifier(propName) || isPrivateIdentifier(propName) || isComputedPropertyName(propName)) { const type = getTypeOfSymbol(getSymbolOfNode(member)); - if (!(type.flags & TypeFlags.AnyOrUnknown || getTypeFacts(type) & TypeFacts.IsUndefined)) { + if (!(type.flags & TypeFlags.AnyOrUnknown || containsUndefinedType(type))) { if (!constructor || !isPropertyInitializedInConstructor(propName, type, constructor)) { error(member.name, Diagnostics.Property_0_has_no_initializer_and_is_not_definitely_assigned_in_the_constructor, declarationNameToString(propName)); } @@ -40281,7 +40285,7 @@ namespace ts { setParent(reference, staticBlock); reference.flowNode = staticBlock.returnFlowNode; const flowType = getFlowTypeOfReference(reference, propType, getOptionalType(propType)); - if (!(getTypeFacts(flowType) & TypeFacts.IsUndefined)) { + if (!containsUndefinedType(flowType)) { return true; } } @@ -40297,7 +40301,7 @@ namespace ts { setParent(reference, constructor); reference.flowNode = constructor.returnFlowNode; const flowType = getFlowTypeOfReference(reference, propType, getOptionalType(propType)); - return !(getTypeFacts(flowType) & TypeFacts.IsUndefined); + return !containsUndefinedType(flowType); } diff --git a/tests/baselines/reference/unknownControlFlow.errors.txt b/tests/baselines/reference/unknownControlFlow.errors.txt index 540c0134e2d..65108ba2201 100644 --- a/tests/baselines/reference/unknownControlFlow.errors.txt +++ b/tests/baselines/reference/unknownControlFlow.errors.txt @@ -263,4 +263,13 @@ tests/cases/conformance/types/unknown/unknownControlFlow.ts(18,9): error TS2322: } return true; } + + // Repro from #49386 + + function foo(x: T | null) { + let y = x; + if (y !== null) { + y; + } + } \ No newline at end of file diff --git a/tests/baselines/reference/unknownControlFlow.js b/tests/baselines/reference/unknownControlFlow.js index 0cc3d1d0d77..d665377767c 100644 --- a/tests/baselines/reference/unknownControlFlow.js +++ b/tests/baselines/reference/unknownControlFlow.js @@ -258,6 +258,15 @@ function deepEquals(a: T, b: T): boolean { } return true; } + +// Repro from #49386 + +function foo(x: T | null) { + let y = x; + if (y !== null) { + y; + } +} //// [unknownControlFlow.js] @@ -485,6 +494,13 @@ function deepEquals(a, b) { } return true; } +// Repro from #49386 +function foo(x) { + var y = x; + if (y !== null) { + y; + } +} //// [unknownControlFlow.d.ts] @@ -524,3 +540,4 @@ declare function f40(a: string | undefined, b: number | null | undefined): void; declare type QQ = NonNullable>>; declare function f41(a: T): void; declare function deepEquals(a: T, b: T): boolean; +declare function foo(x: T | null): void; diff --git a/tests/baselines/reference/unknownControlFlow.symbols b/tests/baselines/reference/unknownControlFlow.symbols index eb938f3d406..176df97b145 100644 --- a/tests/baselines/reference/unknownControlFlow.symbols +++ b/tests/baselines/reference/unknownControlFlow.symbols @@ -616,3 +616,23 @@ function deepEquals(a: T, b: T): boolean { return true; } +// Repro from #49386 + +function foo(x: T | null) { +>foo : Symbol(foo, Decl(unknownControlFlow.ts, 258, 1)) +>T : Symbol(T, Decl(unknownControlFlow.ts, 262, 13)) +>x : Symbol(x, Decl(unknownControlFlow.ts, 262, 16)) +>T : Symbol(T, Decl(unknownControlFlow.ts, 262, 13)) + + let y = x; +>y : Symbol(y, Decl(unknownControlFlow.ts, 263, 7)) +>x : Symbol(x, Decl(unknownControlFlow.ts, 262, 16)) + + if (y !== null) { +>y : Symbol(y, Decl(unknownControlFlow.ts, 263, 7)) + + y; +>y : Symbol(y, Decl(unknownControlFlow.ts, 263, 7)) + } +} + diff --git a/tests/baselines/reference/unknownControlFlow.types b/tests/baselines/reference/unknownControlFlow.types index 13633b8c7fb..32b0c7293c1 100644 --- a/tests/baselines/reference/unknownControlFlow.types +++ b/tests/baselines/reference/unknownControlFlow.types @@ -697,3 +697,24 @@ function deepEquals(a: T, b: T): boolean { >true : true } +// Repro from #49386 + +function foo(x: T | null) { +>foo : (x: T | null) => void +>x : T | null +>null : null + + let y = x; +>y : T | null +>x : T | null + + if (y !== null) { +>y !== null : boolean +>y : T | null +>null : null + + y; +>y : T & ({} | undefined) + } +} + diff --git a/tests/cases/conformance/types/unknown/unknownControlFlow.ts b/tests/cases/conformance/types/unknown/unknownControlFlow.ts index 92a0f279e5e..5bf8f0f2946 100644 --- a/tests/cases/conformance/types/unknown/unknownControlFlow.ts +++ b/tests/cases/conformance/types/unknown/unknownControlFlow.ts @@ -260,3 +260,12 @@ function deepEquals(a: T, b: T): boolean { } return true; } + +// Repro from #49386 + +function foo(x: T | null) { + let y = x; + if (y !== null) { + y; + } +}