From 0ced684b68e9dc772ea09352c37183bdface42d8 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 12 Sep 2022 09:47:28 -0700 Subject: [PATCH 1/4] Fox equality narrowing and comparable relation for intersections with {} --- src/compiler/checker.ts | 40 ++++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 862fb23fa361e..b1c5bc1b26872 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -19191,11 +19191,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); } } } @@ -19516,7 +19520,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); } @@ -25294,25 +25298,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 ? @@ -25321,10 +25311,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))); From 82948945e8b3f3abc0e19beac624f77151ee7025 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 12 Sep 2022 09:48:07 -0700 Subject: [PATCH 2/4] Accept new baselines --- tests/baselines/reference/controlFlowOptionalChain.types | 4 ++-- tests/baselines/reference/intersectionNarrowing.types | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/baselines/reference/controlFlowOptionalChain.types b/tests/baselines/reference/controlFlowOptionalChain.types index b16f6972ef4e2..f10d0082f7a8e 100644 --- a/tests/baselines/reference/controlFlowOptionalChain.types +++ b/tests/baselines/reference/controlFlowOptionalChain.types @@ -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 } } diff --git a/tests/baselines/reference/intersectionNarrowing.types b/tests/baselines/reference/intersectionNarrowing.types index 7cf2c0dfda0cc..049e6c976eb6d 100644 --- a/tests/baselines/reference/intersectionNarrowing.types +++ b/tests/baselines/reference/intersectionNarrowing.types @@ -59,11 +59,11 @@ function f4(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 From a992a68deb6167edbffb036d764c20b46faac6ea Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 12 Sep 2022 11:38:01 -0700 Subject: [PATCH 3/4] Add tests --- .../types/unknown/unknownControlFlow.ts | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/tests/cases/conformance/types/unknown/unknownControlFlow.ts b/tests/cases/conformance/types/unknown/unknownControlFlow.ts index 55072dfddc470..da9bb6fe65e8e 100644 --- a/tests/cases/conformance/types/unknown/unknownControlFlow.ts +++ b/tests/cases/conformance/types/unknown/unknownControlFlow.ts @@ -301,3 +301,104 @@ type Foo = { [key: string]: unknown }; type NullableFoo = Foo | undefined; type Bar = NonNullable[string]; + +// Generics and intersections with {} + +function fx0(value: T & ({} | null)) { + if (value === 42) { + value; // T & {} + } + else { + value; // T & ({} | null) + } +} + +function fx1(value: T & ({} | null)) { + if (value === 42) { + value; // T & {} + } + else { + value; // T & ({} | null) + } +} + +function fx2(value: T & ({} | null)) { + if (value === 42) { + value; // T & {} + } + else { + value; // T & ({} | null) + } +} + +function fx3(value: T & ({} | null)) { + if (value === 42) { + value; // T & {} + } + else { + value; // T & ({} | null) + } +} + +function fx4(value: T & ({} | null)) { + if (value === 42) { + value; // T & {} + } + else { + value; // T & ({} | null) + } +} + +function fx5(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(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; + } +} From 7d9924d52631e6443e3744f4cd4927eb830e01ad Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 12 Sep 2022 11:38:08 -0700 Subject: [PATCH 4/4] Accept new baselines --- .../reference/unknownControlFlow.errors.txt | 101 ++++++++ .../baselines/reference/unknownControlFlow.js | 200 ++++++++++++++++ .../reference/unknownControlFlow.symbols | 202 ++++++++++++++++ .../reference/unknownControlFlow.types | 223 ++++++++++++++++++ 4 files changed, 726 insertions(+) diff --git a/tests/baselines/reference/unknownControlFlow.errors.txt b/tests/baselines/reference/unknownControlFlow.errors.txt index 7c6db9cbd2cae..b49b386e32c5e 100644 --- a/tests/baselines/reference/unknownControlFlow.errors.txt +++ b/tests/baselines/reference/unknownControlFlow.errors.txt @@ -316,4 +316,105 @@ tests/cases/conformance/types/unknown/unknownControlFlow.ts(293,5): error TS2345 type NullableFoo = Foo | undefined; type Bar = NonNullable[string]; + + // Generics and intersections with {} + + function fx0(value: T & ({} | null)) { + if (value === 42) { + value; // T & {} + } + else { + value; // T & ({} | null) + } + } + + function fx1(value: T & ({} | null)) { + if (value === 42) { + value; // T & {} + } + else { + value; // T & ({} | null) + } + } + + function fx2(value: T & ({} | null)) { + if (value === 42) { + value; // T & {} + } + else { + value; // T & ({} | null) + } + } + + function fx3(value: T & ({} | null)) { + if (value === 42) { + value; // T & {} + } + else { + value; // T & ({} | null) + } + } + + function fx4(value: T & ({} | null)) { + if (value === 42) { + value; // T & {} + } + else { + value; // T & ({} | null) + } + } + + function fx5(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(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; + } + } \ No newline at end of file diff --git a/tests/baselines/reference/unknownControlFlow.js b/tests/baselines/reference/unknownControlFlow.js index 8b4681eee071e..a67326b14b1af 100644 --- a/tests/baselines/reference/unknownControlFlow.js +++ b/tests/baselines/reference/unknownControlFlow.js @@ -299,6 +299,107 @@ type Foo = { [key: string]: unknown }; type NullableFoo = Foo | undefined; type Bar = NonNullable[string]; + +// Generics and intersections with {} + +function fx0(value: T & ({} | null)) { + if (value === 42) { + value; // T & {} + } + else { + value; // T & ({} | null) + } +} + +function fx1(value: T & ({} | null)) { + if (value === 42) { + value; // T & {} + } + else { + value; // T & ({} | null) + } +} + +function fx2(value: T & ({} | null)) { + if (value === 42) { + value; // T & {} + } + else { + value; // T & ({} | null) + } +} + +function fx3(value: T & ({} | null)) { + if (value === 42) { + value; // T & {} + } + else { + value; // T & ({} | null) + } +} + +function fx4(value: T & ({} | null)) { + if (value === 42) { + value; // T & {} + } + else { + value; // T & ({} | null) + } +} + +function fx5(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(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 = NonNullable[string]; +declare function fx0(value: T & ({} | null)): void; +declare function fx1(value: T & ({} | null)): void; +declare function fx2(value: T & ({} | null)): void; +declare function fx3(value: T & ({} | null)): void; +declare function fx4(value: T & ({} | null)): void; +declare function fx5(value: T & ({} | null)): void; +declare function fx10(x: string | number, y: number): void; +declare function SendBlob(encoding: unknown): void; +declare function doSomething1(value: T): T; +declare function doSomething2(value: unknown): void; diff --git a/tests/baselines/reference/unknownControlFlow.symbols b/tests/baselines/reference/unknownControlFlow.symbols index 47b6d4411a530..1644306428ae7 100644 --- a/tests/baselines/reference/unknownControlFlow.symbols +++ b/tests/baselines/reference/unknownControlFlow.symbols @@ -721,3 +721,205 @@ type Bar = NonNullable[string]; >NonNullable : Symbol(NonNullable, Decl(lib.es5.d.ts, --, --)) >T : Symbol(T, Decl(unknownControlFlow.ts, 299, 9)) +// Generics and intersections with {} + +function fx0(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(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(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(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(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(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(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)) + } +} + diff --git a/tests/baselines/reference/unknownControlFlow.types b/tests/baselines/reference/unknownControlFlow.types index bd1d851c5b3a9..b2c646f56d4b5 100644 --- a/tests/baselines/reference/unknownControlFlow.types +++ b/tests/baselines/reference/unknownControlFlow.types @@ -802,3 +802,226 @@ type NullableFoo = Foo | undefined; type Bar = NonNullable[string]; >Bar : Bar +// Generics and intersections with {} + +function fx0(value: T & ({} | null)) { +>fx0 : (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(value: T & ({} | null)) { +>fx1 : (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(value: T & ({} | null)) { +>fx2 : (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(value: T & ({} | null)) { +>fx3 : (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(value: T & ({} | null)) { +>fx4 : (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(value: T & ({} | null)) { +>fx5 : (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(value: T): T { +>doSomething1 : (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 + } +} +