Fix definite assignment analysis issue caused by x !== null checks (#49387)

* Check for non-intersected `undefined` type in definite assignment analysis

* Add regression test
This commit is contained in:
Anders Hejlsberg 2022-06-06 09:51:13 -07:00 committed by GitHub
parent 6c77996337
commit 565249fbbe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 87 additions and 7 deletions

View File

@ -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);
}

View File

@ -263,4 +263,13 @@ tests/cases/conformance/types/unknown/unknownControlFlow.ts(18,9): error TS2322:
}
return true;
}
// Repro from #49386
function foo<T>(x: T | null) {
let y = x;
if (y !== null) {
y;
}
}

View File

@ -258,6 +258,15 @@ function deepEquals<T>(a: T, b: T): boolean {
}
return true;
}
// Repro from #49386
function foo<T>(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<T> = NonNullable<NonNullable<NonNullable<T>>>;
declare function f41<T>(a: T): void;
declare function deepEquals<T>(a: T, b: T): boolean;
declare function foo<T>(x: T | null): void;

View File

@ -616,3 +616,23 @@ function deepEquals<T>(a: T, b: T): boolean {
return true;
}
// Repro from #49386
function foo<T>(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))
}
}

View File

@ -697,3 +697,24 @@ function deepEquals<T>(a: T, b: T): boolean {
>true : true
}
// Repro from #49386
function foo<T>(x: T | null) {
>foo : <T>(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)
}
}

View File

@ -260,3 +260,12 @@ function deepEquals<T>(a: T, b: T): boolean {
}
return true;
}
// Repro from #49386
function foo<T>(x: T | null) {
let y = x;
if (y !== null) {
y;
}
}