Fix equality narrowing and comparable relation for intersections with {} (#50735)

* Fox equality narrowing and comparable relation for intersections with {}

* Accept new baselines

* Add tests

* Accept new baselines
This commit is contained in:
Anders Hejlsberg 2022-09-14 09:19:36 -07:00 committed by GitHub
parent b23f1d6b59
commit 4110b80fbb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 849 additions and 26 deletions

View File

@ -19216,11 +19216,15 @@ namespace ts {
// parameter 'T extends 1 | 2', the intersection 'T & 1' should be reduced to '1' such that it doesn't
// appear to be comparable to '2'.
if (relation === comparableRelation && target.flags & TypeFlags.Primitive) {
const constraints = sameMap((source as IntersectionType).types, getBaseConstraintOrType);
const constraints = sameMap((source as IntersectionType).types, t => t.flags & TypeFlags.Instantiable ? getBaseConstraintOfType(t) || unknownType : t);
if (constraints !== (source as IntersectionType).types) {
source = getIntersectionType(constraints);
if (source.flags & TypeFlags.Never) {
return Ternary.False;
}
if (!(source.flags & TypeFlags.Intersection)) {
return isRelatedTo(source, target, RecursionFlags.Source, /*reportErrors*/ false);
return isRelatedTo(source, target, RecursionFlags.Source, /*reportErrors*/ false) ||
isRelatedTo(target, source, RecursionFlags.Source, /*reportErrors*/ false);
}
}
}
@ -19541,7 +19545,7 @@ namespace ts {
// the type param can be compared with itself in the target (with the influence of its constraint to match other parts)
// For example, if `T extends 1 | 2` and `U extends 2 | 3` and we compare `T & U` to `T & U & (1 | 2 | 3)`
const constraint = getEffectiveConstraintOfIntersection(source.flags & TypeFlags.Intersection ? (source as IntersectionType).types: [source], !!(target.flags & TypeFlags.Union));
if (constraint && everyType(constraint, c => c !== source)) { // Skip comparison if expansion contains the source itself
if (constraint && !(constraint.flags & TypeFlags.Never) && everyType(constraint, c => c !== source)) { // Skip comparison if expansion contains the source itself
// TODO: Stack errors so we get a pyramid for the "normal" comparison above, _and_ a second for this
result = isRelatedTo(constraint, target, RecursionFlags.Source, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState);
}
@ -25322,25 +25326,11 @@ namespace ts {
assumeTrue = !assumeTrue;
}
const valueType = getTypeOfExpression(value);
if (((type.flags & TypeFlags.Unknown) || isEmptyAnonymousObjectType(type) && !(valueType.flags & TypeFlags.Nullable)) &&
assumeTrue &&
(operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken)
) {
if (valueType.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive)) {
return valueType;
}
if (valueType.flags & TypeFlags.Object) {
return nonPrimitiveType;
}
if (type.flags & TypeFlags.Unknown) {
return type;
}
}
const doubleEquals = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken;
if (valueType.flags & TypeFlags.Nullable) {
if (!strictNullChecks) {
return type;
}
const doubleEquals = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken;
const facts = doubleEquals ?
assumeTrue ? TypeFacts.EQUndefinedOrNull : TypeFacts.NEUndefinedOrNull :
valueType.flags & TypeFlags.Null ?
@ -25349,10 +25339,16 @@ namespace ts {
return getAdjustedTypeWithFacts(type, facts);
}
if (assumeTrue) {
const filterFn: (t: Type) => boolean = operator === SyntaxKind.EqualsEqualsToken ?
t => areTypesComparable(t, valueType) || isCoercibleUnderDoubleEquals(t, valueType) :
t => areTypesComparable(t, valueType);
return replacePrimitivesWithLiterals(filterType(type, filterFn), valueType);
if (!doubleEquals && (type.flags & TypeFlags.Unknown || someType(type, isEmptyAnonymousObjectType))) {
if (valueType.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive) || isEmptyAnonymousObjectType(valueType)) {
return valueType;
}
if (valueType.flags & TypeFlags.Object) {
return nonPrimitiveType;
}
}
const filteredType = filterType(type, t => areTypesComparable(t, valueType) || doubleEquals && isCoercibleUnderDoubleEquals(t, valueType));
return replacePrimitivesWithLiterals(filteredType, valueType);
}
if (isUnitType(valueType)) {
return filterType(type, t => !(isUnitLikeType(t) && areTypesComparable(t, valueType)));

View File

@ -1243,9 +1243,9 @@ function f15(o: Thing | undefined, value: number) {
}
else {
o.foo;
>o.foo : number
>o.foo : string | number
>o : Thing
>foo : number
>foo : string | number
}
}

View File

@ -59,11 +59,11 @@ function f4<T>(x: T & 1 | T & 2) {
case 1: x; break; // T & 1
>1 : 1
>x : (T & 1) | (T & 2)
>x : T & 1
case 2: x; break; // T & 2
>2 : 2
>x : (T & 1) | (T & 2)
>x : T & 2
default: x; // Should narrow to never
>x : never

View File

@ -316,4 +316,105 @@ tests/cases/conformance/types/unknown/unknownControlFlow.ts(293,5): error TS2345
type NullableFoo = Foo | undefined;
type Bar<T extends NullableFoo> = NonNullable<T>[string];
// Generics and intersections with {}
function fx0<T>(value: T & ({} | null)) {
if (value === 42) {
value; // T & {}
}
else {
value; // T & ({} | null)
}
}
function fx1<T extends unknown>(value: T & ({} | null)) {
if (value === 42) {
value; // T & {}
}
else {
value; // T & ({} | null)
}
}
function fx2<T extends {}>(value: T & ({} | null)) {
if (value === 42) {
value; // T & {}
}
else {
value; // T & ({} | null)
}
}
function fx3<T extends {} | undefined>(value: T & ({} | null)) {
if (value === 42) {
value; // T & {}
}
else {
value; // T & ({} | null)
}
}
function fx4<T extends {} | null>(value: T & ({} | null)) {
if (value === 42) {
value; // T & {}
}
else {
value; // T & ({} | null)
}
}
function fx5<T extends {} | null | undefined>(value: T & ({} | null)) {
if (value === 42) {
value; // T & {}
}
else {
value; // T & ({} | null)
}
}
// Double-equals narrowing
function fx10(x: string | number, y: number) {
if (x == y) {
x; // string | number
}
else {
x; // string | number
}
if (x != y) {
x; // string | number
}
else {
x; // string | number
}
}
// Repros from #50706
function SendBlob(encoding: unknown) {
if (encoding !== undefined && encoding !== 'utf8') {
throw new Error('encoding');
}
encoding;
};
function doSomething1<T extends unknown>(value: T): T {
if (value === undefined) {
return value;
}
if (value === 42) {
throw Error('Meaning of life value');
}
return value;
}
function doSomething2(value: unknown): void {
if (value === undefined) {
return;
}
if (value === 42) {
value;
}
}

View File

@ -299,6 +299,107 @@ type Foo = { [key: string]: unknown };
type NullableFoo = Foo | undefined;
type Bar<T extends NullableFoo> = NonNullable<T>[string];
// Generics and intersections with {}
function fx0<T>(value: T & ({} | null)) {
if (value === 42) {
value; // T & {}
}
else {
value; // T & ({} | null)
}
}
function fx1<T extends unknown>(value: T & ({} | null)) {
if (value === 42) {
value; // T & {}
}
else {
value; // T & ({} | null)
}
}
function fx2<T extends {}>(value: T & ({} | null)) {
if (value === 42) {
value; // T & {}
}
else {
value; // T & ({} | null)
}
}
function fx3<T extends {} | undefined>(value: T & ({} | null)) {
if (value === 42) {
value; // T & {}
}
else {
value; // T & ({} | null)
}
}
function fx4<T extends {} | null>(value: T & ({} | null)) {
if (value === 42) {
value; // T & {}
}
else {
value; // T & ({} | null)
}
}
function fx5<T extends {} | null | undefined>(value: T & ({} | null)) {
if (value === 42) {
value; // T & {}
}
else {
value; // T & ({} | null)
}
}
// Double-equals narrowing
function fx10(x: string | number, y: number) {
if (x == y) {
x; // string | number
}
else {
x; // string | number
}
if (x != y) {
x; // string | number
}
else {
x; // string | number
}
}
// Repros from #50706
function SendBlob(encoding: unknown) {
if (encoding !== undefined && encoding !== 'utf8') {
throw new Error('encoding');
}
encoding;
};
function doSomething1<T extends unknown>(value: T): T {
if (value === undefined) {
return value;
}
if (value === 42) {
throw Error('Meaning of life value');
}
return value;
}
function doSomething2(value: unknown): void {
if (value === undefined) {
return;
}
if (value === 42) {
value;
}
}
//// [unknownControlFlow.js]
@ -552,6 +653,95 @@ ff1(null, 'foo'); // Error
ff2(null, 'foo'); // Error
ff3(null, 'foo');
ff4(null, 'foo'); // Error
// Generics and intersections with {}
function fx0(value) {
if (value === 42) {
value; // T & {}
}
else {
value; // T & ({} | null)
}
}
function fx1(value) {
if (value === 42) {
value; // T & {}
}
else {
value; // T & ({} | null)
}
}
function fx2(value) {
if (value === 42) {
value; // T & {}
}
else {
value; // T & ({} | null)
}
}
function fx3(value) {
if (value === 42) {
value; // T & {}
}
else {
value; // T & ({} | null)
}
}
function fx4(value) {
if (value === 42) {
value; // T & {}
}
else {
value; // T & ({} | null)
}
}
function fx5(value) {
if (value === 42) {
value; // T & {}
}
else {
value; // T & ({} | null)
}
}
// Double-equals narrowing
function fx10(x, y) {
if (x == y) {
x; // string | number
}
else {
x; // string | number
}
if (x != y) {
x; // string | number
}
else {
x; // string | number
}
}
// Repros from #50706
function SendBlob(encoding) {
if (encoding !== undefined && encoding !== 'utf8') {
throw new Error('encoding');
}
encoding;
}
;
function doSomething1(value) {
if (value === undefined) {
return value;
}
if (value === 42) {
throw Error('Meaning of life value');
}
return value;
}
function doSomething2(value) {
if (value === undefined) {
return;
}
if (value === 42) {
value;
}
}
//// [unknownControlFlow.d.ts]
@ -601,3 +791,13 @@ type Foo = {
};
type NullableFoo = Foo | undefined;
type Bar<T extends NullableFoo> = NonNullable<T>[string];
declare function fx0<T>(value: T & ({} | null)): void;
declare function fx1<T extends unknown>(value: T & ({} | null)): void;
declare function fx2<T extends {}>(value: T & ({} | null)): void;
declare function fx3<T extends {} | undefined>(value: T & ({} | null)): void;
declare function fx4<T extends {} | null>(value: T & ({} | null)): void;
declare function fx5<T extends {} | null | undefined>(value: T & ({} | null)): void;
declare function fx10(x: string | number, y: number): void;
declare function SendBlob(encoding: unknown): void;
declare function doSomething1<T extends unknown>(value: T): T;
declare function doSomething2(value: unknown): void;

View File

@ -721,3 +721,205 @@ type Bar<T extends NullableFoo> = NonNullable<T>[string];
>NonNullable : Symbol(NonNullable, Decl(lib.es5.d.ts, --, --))
>T : Symbol(T, Decl(unknownControlFlow.ts, 299, 9))
// Generics and intersections with {}
function fx0<T>(value: T & ({} | null)) {
>fx0 : Symbol(fx0, Decl(unknownControlFlow.ts, 299, 57))
>T : Symbol(T, Decl(unknownControlFlow.ts, 303, 13))
>value : Symbol(value, Decl(unknownControlFlow.ts, 303, 16))
>T : Symbol(T, Decl(unknownControlFlow.ts, 303, 13))
if (value === 42) {
>value : Symbol(value, Decl(unknownControlFlow.ts, 303, 16))
value; // T & {}
>value : Symbol(value, Decl(unknownControlFlow.ts, 303, 16))
}
else {
value; // T & ({} | null)
>value : Symbol(value, Decl(unknownControlFlow.ts, 303, 16))
}
}
function fx1<T extends unknown>(value: T & ({} | null)) {
>fx1 : Symbol(fx1, Decl(unknownControlFlow.ts, 310, 1))
>T : Symbol(T, Decl(unknownControlFlow.ts, 312, 13))
>value : Symbol(value, Decl(unknownControlFlow.ts, 312, 32))
>T : Symbol(T, Decl(unknownControlFlow.ts, 312, 13))
if (value === 42) {
>value : Symbol(value, Decl(unknownControlFlow.ts, 312, 32))
value; // T & {}
>value : Symbol(value, Decl(unknownControlFlow.ts, 312, 32))
}
else {
value; // T & ({} | null)
>value : Symbol(value, Decl(unknownControlFlow.ts, 312, 32))
}
}
function fx2<T extends {}>(value: T & ({} | null)) {
>fx2 : Symbol(fx2, Decl(unknownControlFlow.ts, 319, 1))
>T : Symbol(T, Decl(unknownControlFlow.ts, 321, 13))
>value : Symbol(value, Decl(unknownControlFlow.ts, 321, 27))
>T : Symbol(T, Decl(unknownControlFlow.ts, 321, 13))
if (value === 42) {
>value : Symbol(value, Decl(unknownControlFlow.ts, 321, 27))
value; // T & {}
>value : Symbol(value, Decl(unknownControlFlow.ts, 321, 27))
}
else {
value; // T & ({} | null)
>value : Symbol(value, Decl(unknownControlFlow.ts, 321, 27))
}
}
function fx3<T extends {} | undefined>(value: T & ({} | null)) {
>fx3 : Symbol(fx3, Decl(unknownControlFlow.ts, 328, 1))
>T : Symbol(T, Decl(unknownControlFlow.ts, 330, 13))
>value : Symbol(value, Decl(unknownControlFlow.ts, 330, 39))
>T : Symbol(T, Decl(unknownControlFlow.ts, 330, 13))
if (value === 42) {
>value : Symbol(value, Decl(unknownControlFlow.ts, 330, 39))
value; // T & {}
>value : Symbol(value, Decl(unknownControlFlow.ts, 330, 39))
}
else {
value; // T & ({} | null)
>value : Symbol(value, Decl(unknownControlFlow.ts, 330, 39))
}
}
function fx4<T extends {} | null>(value: T & ({} | null)) {
>fx4 : Symbol(fx4, Decl(unknownControlFlow.ts, 337, 1))
>T : Symbol(T, Decl(unknownControlFlow.ts, 339, 13))
>value : Symbol(value, Decl(unknownControlFlow.ts, 339, 34))
>T : Symbol(T, Decl(unknownControlFlow.ts, 339, 13))
if (value === 42) {
>value : Symbol(value, Decl(unknownControlFlow.ts, 339, 34))
value; // T & {}
>value : Symbol(value, Decl(unknownControlFlow.ts, 339, 34))
}
else {
value; // T & ({} | null)
>value : Symbol(value, Decl(unknownControlFlow.ts, 339, 34))
}
}
function fx5<T extends {} | null | undefined>(value: T & ({} | null)) {
>fx5 : Symbol(fx5, Decl(unknownControlFlow.ts, 346, 1))
>T : Symbol(T, Decl(unknownControlFlow.ts, 348, 13))
>value : Symbol(value, Decl(unknownControlFlow.ts, 348, 46))
>T : Symbol(T, Decl(unknownControlFlow.ts, 348, 13))
if (value === 42) {
>value : Symbol(value, Decl(unknownControlFlow.ts, 348, 46))
value; // T & {}
>value : Symbol(value, Decl(unknownControlFlow.ts, 348, 46))
}
else {
value; // T & ({} | null)
>value : Symbol(value, Decl(unknownControlFlow.ts, 348, 46))
}
}
// Double-equals narrowing
function fx10(x: string | number, y: number) {
>fx10 : Symbol(fx10, Decl(unknownControlFlow.ts, 355, 1))
>x : Symbol(x, Decl(unknownControlFlow.ts, 359, 14))
>y : Symbol(y, Decl(unknownControlFlow.ts, 359, 33))
if (x == y) {
>x : Symbol(x, Decl(unknownControlFlow.ts, 359, 14))
>y : Symbol(y, Decl(unknownControlFlow.ts, 359, 33))
x; // string | number
>x : Symbol(x, Decl(unknownControlFlow.ts, 359, 14))
}
else {
x; // string | number
>x : Symbol(x, Decl(unknownControlFlow.ts, 359, 14))
}
if (x != y) {
>x : Symbol(x, Decl(unknownControlFlow.ts, 359, 14))
>y : Symbol(y, Decl(unknownControlFlow.ts, 359, 33))
x; // string | number
>x : Symbol(x, Decl(unknownControlFlow.ts, 359, 14))
}
else {
x; // string | number
>x : Symbol(x, Decl(unknownControlFlow.ts, 359, 14))
}
}
// Repros from #50706
function SendBlob(encoding: unknown) {
>SendBlob : Symbol(SendBlob, Decl(unknownControlFlow.ts, 372, 1))
>encoding : Symbol(encoding, Decl(unknownControlFlow.ts, 376, 18))
if (encoding !== undefined && encoding !== 'utf8') {
>encoding : Symbol(encoding, Decl(unknownControlFlow.ts, 376, 18))
>undefined : Symbol(undefined)
>encoding : Symbol(encoding, Decl(unknownControlFlow.ts, 376, 18))
throw new Error('encoding');
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
}
encoding;
>encoding : Symbol(encoding, Decl(unknownControlFlow.ts, 376, 18))
};
function doSomething1<T extends unknown>(value: T): T {
>doSomething1 : Symbol(doSomething1, Decl(unknownControlFlow.ts, 381, 2))
>T : Symbol(T, Decl(unknownControlFlow.ts, 383, 22))
>value : Symbol(value, Decl(unknownControlFlow.ts, 383, 41))
>T : Symbol(T, Decl(unknownControlFlow.ts, 383, 22))
>T : Symbol(T, Decl(unknownControlFlow.ts, 383, 22))
if (value === undefined) {
>value : Symbol(value, Decl(unknownControlFlow.ts, 383, 41))
>undefined : Symbol(undefined)
return value;
>value : Symbol(value, Decl(unknownControlFlow.ts, 383, 41))
}
if (value === 42) {
>value : Symbol(value, Decl(unknownControlFlow.ts, 383, 41))
throw Error('Meaning of life value');
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
}
return value;
>value : Symbol(value, Decl(unknownControlFlow.ts, 383, 41))
}
function doSomething2(value: unknown): void {
>doSomething2 : Symbol(doSomething2, Decl(unknownControlFlow.ts, 391, 1))
>value : Symbol(value, Decl(unknownControlFlow.ts, 393, 22))
if (value === undefined) {
>value : Symbol(value, Decl(unknownControlFlow.ts, 393, 22))
>undefined : Symbol(undefined)
return;
}
if (value === 42) {
>value : Symbol(value, Decl(unknownControlFlow.ts, 393, 22))
value;
>value : Symbol(value, Decl(unknownControlFlow.ts, 393, 22))
}
}

View File

@ -802,3 +802,226 @@ type NullableFoo = Foo | undefined;
type Bar<T extends NullableFoo> = NonNullable<T>[string];
>Bar : Bar<T>
// Generics and intersections with {}
function fx0<T>(value: T & ({} | null)) {
>fx0 : <T>(value: T & ({} | null)) => void
>value : T & ({} | null)
>null : null
if (value === 42) {
>value === 42 : boolean
>value : T & ({} | null)
>42 : 42
value; // T & {}
>value : T & {}
}
else {
value; // T & ({} | null)
>value : T & ({} | null)
}
}
function fx1<T extends unknown>(value: T & ({} | null)) {
>fx1 : <T extends unknown>(value: T & ({} | null)) => void
>value : T & ({} | null)
>null : null
if (value === 42) {
>value === 42 : boolean
>value : T & ({} | null)
>42 : 42
value; // T & {}
>value : T & {}
}
else {
value; // T & ({} | null)
>value : T & ({} | null)
}
}
function fx2<T extends {}>(value: T & ({} | null)) {
>fx2 : <T extends {}>(value: T & ({} | null)) => void
>value : T & ({} | null)
>null : null
if (value === 42) {
>value === 42 : boolean
>value : T & ({} | null)
>42 : 42
value; // T & {}
>value : T & {}
}
else {
value; // T & ({} | null)
>value : T & ({} | null)
}
}
function fx3<T extends {} | undefined>(value: T & ({} | null)) {
>fx3 : <T extends {} | undefined>(value: T & ({} | null)) => void
>value : T & ({} | null)
>null : null
if (value === 42) {
>value === 42 : boolean
>value : T & ({} | null)
>42 : 42
value; // T & {}
>value : T & {}
}
else {
value; // T & ({} | null)
>value : T & ({} | null)
}
}
function fx4<T extends {} | null>(value: T & ({} | null)) {
>fx4 : <T extends {} | null>(value: T & ({} | null)) => void
>null : null
>value : T & ({} | null)
>null : null
if (value === 42) {
>value === 42 : boolean
>value : T & ({} | null)
>42 : 42
value; // T & {}
>value : T & {}
}
else {
value; // T & ({} | null)
>value : T & ({} | null)
}
}
function fx5<T extends {} | null | undefined>(value: T & ({} | null)) {
>fx5 : <T extends {} | null | undefined>(value: T & ({} | null)) => void
>null : null
>value : T & ({} | null)
>null : null
if (value === 42) {
>value === 42 : boolean
>value : T & ({} | null)
>42 : 42
value; // T & {}
>value : T & {}
}
else {
value; // T & ({} | null)
>value : T & ({} | null)
}
}
// Double-equals narrowing
function fx10(x: string | number, y: number) {
>fx10 : (x: string | number, y: number) => void
>x : string | number
>y : number
if (x == y) {
>x == y : boolean
>x : string | number
>y : number
x; // string | number
>x : string | number
}
else {
x; // string | number
>x : string | number
}
if (x != y) {
>x != y : boolean
>x : string | number
>y : number
x; // string | number
>x : string | number
}
else {
x; // string | number
>x : string | number
}
}
// Repros from #50706
function SendBlob(encoding: unknown) {
>SendBlob : (encoding: unknown) => void
>encoding : unknown
if (encoding !== undefined && encoding !== 'utf8') {
>encoding !== undefined && encoding !== 'utf8' : boolean
>encoding !== undefined : boolean
>encoding : unknown
>undefined : undefined
>encoding !== 'utf8' : boolean
>encoding : {} | null
>'utf8' : "utf8"
throw new Error('encoding');
>new Error('encoding') : Error
>Error : ErrorConstructor
>'encoding' : "encoding"
}
encoding;
>encoding : "utf8" | undefined
};
function doSomething1<T extends unknown>(value: T): T {
>doSomething1 : <T extends unknown>(value: T) => T
>value : T
if (value === undefined) {
>value === undefined : boolean
>value : T
>undefined : undefined
return value;
>value : T
}
if (value === 42) {
>value === 42 : boolean
>value : T & ({} | null)
>42 : 42
throw Error('Meaning of life value');
>Error('Meaning of life value') : Error
>Error : ErrorConstructor
>'Meaning of life value' : "Meaning of life value"
}
return value;
>value : T & ({} | null)
}
function doSomething2(value: unknown): void {
>doSomething2 : (value: unknown) => void
>value : unknown
if (value === undefined) {
>value === undefined : boolean
>value : unknown
>undefined : undefined
return;
}
if (value === 42) {
>value === 42 : boolean
>value : {} | null
>42 : 42
value;
>value : 42
}
}

View File

@ -301,3 +301,104 @@ type Foo = { [key: string]: unknown };
type NullableFoo = Foo | undefined;
type Bar<T extends NullableFoo> = NonNullable<T>[string];
// Generics and intersections with {}
function fx0<T>(value: T & ({} | null)) {
if (value === 42) {
value; // T & {}
}
else {
value; // T & ({} | null)
}
}
function fx1<T extends unknown>(value: T & ({} | null)) {
if (value === 42) {
value; // T & {}
}
else {
value; // T & ({} | null)
}
}
function fx2<T extends {}>(value: T & ({} | null)) {
if (value === 42) {
value; // T & {}
}
else {
value; // T & ({} | null)
}
}
function fx3<T extends {} | undefined>(value: T & ({} | null)) {
if (value === 42) {
value; // T & {}
}
else {
value; // T & ({} | null)
}
}
function fx4<T extends {} | null>(value: T & ({} | null)) {
if (value === 42) {
value; // T & {}
}
else {
value; // T & ({} | null)
}
}
function fx5<T extends {} | null | undefined>(value: T & ({} | null)) {
if (value === 42) {
value; // T & {}
}
else {
value; // T & ({} | null)
}
}
// Double-equals narrowing
function fx10(x: string | number, y: number) {
if (x == y) {
x; // string | number
}
else {
x; // string | number
}
if (x != y) {
x; // string | number
}
else {
x; // string | number
}
}
// Repros from #50706
function SendBlob(encoding: unknown) {
if (encoding !== undefined && encoding !== 'utf8') {
throw new Error('encoding');
}
encoding;
};
function doSomething1<T extends unknown>(value: T): T {
if (value === undefined) {
return value;
}
if (value === 42) {
throw Error('Meaning of life value');
}
return value;
}
function doSomething2(value: unknown): void {
if (value === undefined) {
return;
}
if (value === 42) {
value;
}
}