From 79bdd93ec5d6197f978e44c314b2f8f15c112c59 Mon Sep 17 00:00:00 2001 From: TypeScript Bot Date: Mon, 6 Mar 2023 17:17:49 -0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=A4=96=20Pick=20PR=20#52984=20(Check=20fo?= =?UTF-8?q?r=20strict=20subtypes=20and=20then=20...)=20into=20release-5.0?= =?UTF-8?q?=20(#53085)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Anders Hejlsberg --- src/compiler/checker.ts | 6 +- ...ssertedTypeThroughTypePredicate.errors.txt | 51 --- ...avorAssertedTypeThroughTypePredicate.types | 12 +- .../reference/narrowingMutualSubtypes.types | 4 +- .../strictSubtypeAndNarrowing.errors.txt | 100 ++++++ .../reference/strictSubtypeAndNarrowing.js | 140 +++++++++ .../strictSubtypeAndNarrowing.symbols | 290 ++++++++++++++++++ .../reference/strictSubtypeAndNarrowing.types | 231 ++++++++++++++ .../compiler/strictSubtypeAndNarrowing.ts | 100 ++++++ 9 files changed, 874 insertions(+), 60 deletions(-) delete mode 100644 tests/baselines/reference/controlFlowFavorAssertedTypeThroughTypePredicate.errors.txt diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0bc1cd61fcfab..5e374efbfba36 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -19108,6 +19108,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return isTypeRelatedTo(source, target, subtypeRelation); } + function isTypeStrictSubtypeOf(source: Type, target: Type): boolean { + return isTypeRelatedTo(source, target, strictSubtypeRelation); + } + function isTypeAssignableTo(source: Type, target: Type): boolean { return isTypeRelatedTo(source, target, assignableRelation); } @@ -27262,7 +27266,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // prototype object types. const directlyRelated = mapType(matching || type, checkDerived ? t => isTypeDerivedFrom(t, c) ? t : isTypeDerivedFrom(c, t) ? c : neverType : - t => isTypeSubtypeOf(c, t) && !isTypeIdenticalTo(c, t) ? c : isTypeSubtypeOf(t, c) ? t : neverType); + t => isTypeStrictSubtypeOf(t, c) ? t : isTypeStrictSubtypeOf(c, t) ? c : isTypeSubtypeOf(t, c) ? t : isTypeSubtypeOf(c, t) ? c : neverType); // If no constituents are directly related, create intersections for any generic constituents that // are related by constraint. return directlyRelated.flags & TypeFlags.Never ? diff --git a/tests/baselines/reference/controlFlowFavorAssertedTypeThroughTypePredicate.errors.txt b/tests/baselines/reference/controlFlowFavorAssertedTypeThroughTypePredicate.errors.txt deleted file mode 100644 index 4ae99c4108d14..0000000000000 --- a/tests/baselines/reference/controlFlowFavorAssertedTypeThroughTypePredicate.errors.txt +++ /dev/null @@ -1,51 +0,0 @@ -tests/cases/compiler/controlFlowFavorAssertedTypeThroughTypePredicate.ts(26,5): error TS7053: Element implicitly has an 'any' type because expression of type '"attr"' can't be used to index type '{}'. - Property 'attr' does not exist on type '{}'. -tests/cases/compiler/controlFlowFavorAssertedTypeThroughTypePredicate.ts(34,5): error TS7053: Element implicitly has an 'any' type because expression of type '"attr"' can't be used to index type '{}'. - Property 'attr' does not exist on type '{}'. - - -==== tests/cases/compiler/controlFlowFavorAssertedTypeThroughTypePredicate.ts (2 errors) ==== - // repro 49988#issuecomment-1192016929 - - declare function isObject1(value: unknown): value is Record; - - declare const obj1: {}; - if (isObject1(obj1)) { - obj1; - obj1['attr']; - } - // check type after conditional block - obj1; - - declare const obj2: {} | undefined; - if (isObject1(obj2)) { - obj2; - obj2['attr']; - } - // check type after conditional block - obj2; - - declare function isObject2(value: unknown): value is {}; - - declare const obj3: Record; - if (isObject2(obj3)) { - obj3; - obj3['attr']; - ~~~~~~~~~~~~ -!!! error TS7053: Element implicitly has an 'any' type because expression of type '"attr"' can't be used to index type '{}'. -!!! error TS7053: Property 'attr' does not exist on type '{}'. - } - // check type after conditional block - obj3; - - declare const obj4: Record | undefined; - if (isObject2(obj4)) { - obj4; - obj4['attr']; - ~~~~~~~~~~~~ -!!! error TS7053: Element implicitly has an 'any' type because expression of type '"attr"' can't be used to index type '{}'. -!!! error TS7053: Property 'attr' does not exist on type '{}'. - } - // check type after conditional block - obj4; - \ No newline at end of file diff --git a/tests/baselines/reference/controlFlowFavorAssertedTypeThroughTypePredicate.types b/tests/baselines/reference/controlFlowFavorAssertedTypeThroughTypePredicate.types index f79e73f03e4d6..ef09dea6f7795 100644 --- a/tests/baselines/reference/controlFlowFavorAssertedTypeThroughTypePredicate.types +++ b/tests/baselines/reference/controlFlowFavorAssertedTypeThroughTypePredicate.types @@ -58,11 +58,11 @@ if (isObject2(obj3)) { >obj3 : Record obj3; ->obj3 : {} +>obj3 : Record obj3['attr']; ->obj3['attr'] : any ->obj3 : {} +>obj3['attr'] : unknown +>obj3 : Record >'attr' : "attr" } // check type after conditional block @@ -78,11 +78,11 @@ if (isObject2(obj4)) { >obj4 : Record | undefined obj4; ->obj4 : {} +>obj4 : Record obj4['attr']; ->obj4['attr'] : any ->obj4 : {} +>obj4['attr'] : unknown +>obj4 : Record >'attr' : "attr" } // check type after conditional block diff --git a/tests/baselines/reference/narrowingMutualSubtypes.types b/tests/baselines/reference/narrowingMutualSubtypes.types index 7cf817697aafb..7ed2f5955d678 100644 --- a/tests/baselines/reference/narrowingMutualSubtypes.types +++ b/tests/baselines/reference/narrowingMutualSubtypes.types @@ -123,11 +123,11 @@ function gg2(x: Record) { >x : Record x; // {} ->x : {} +>x : Record } else { x; // Record ->x : Record +>x : never } x; // Record >x : Record diff --git a/tests/baselines/reference/strictSubtypeAndNarrowing.errors.txt b/tests/baselines/reference/strictSubtypeAndNarrowing.errors.txt index 1dd63fe6e0f77..12d23ddb7b5b7 100644 --- a/tests/baselines/reference/strictSubtypeAndNarrowing.errors.txt +++ b/tests/baselines/reference/strictSubtypeAndNarrowing.errors.txt @@ -146,4 +146,104 @@ tests/cases/compiler/strictSubtypeAndNarrowing.ts(129,26): error TS2322: Type '{ !!! error TS2322: Type '{ x: number; y: number; }' is not assignable to type '{ x?: number | undefined; }'. !!! error TS2322: Object literal may only specify known properties, and 'y' does not exist in type '{ x?: number | undefined; }'. } + + // Repros from #52827 + + declare function isArrayLike(value: any): value is { length: number }; + + function ff1(value: { [index: number]: boolean, length: number } | undefined) { + if (isArrayLike(value)) { + value; + } else { + value; + } + value; + } + + function ff2(value: { [index: number]: boolean, length: number } | string) { + if (isArrayLike(value)) { + value; + } else { + value; + } + value; + } + + function ff3(value: string | string[] | { [index: number]: boolean, length: number } | [number, boolean] | number | { length: string } | { a: string } | null | undefined) { + if (isArrayLike(value)) { + value; + } else { + value; + } + value; + } + + // Repro from comment in #52984 + + type DistributedKeyOf = T extends unknown ? keyof T : never; + + type NarrowByKeyValue = ObjT extends unknown + ? KeyT extends keyof ObjT + ? ValueT extends ObjT[KeyT] + ? ObjT & Readonly> + : never + : never + : never; + + type NarrowByDeepValue = DeepPathT extends readonly [ + infer Head extends DistributedKeyOf, + ] + ? NarrowByKeyValue + : DeepPathT extends readonly [infer Head extends DistributedKeyOf, ...infer Rest] + ? NarrowByKeyValue, Rest, ValueT>> + : never; + + + declare function doesValueAtDeepPathSatisfy< + ObjT extends object, + const DeepPathT extends ReadonlyArray, + ValueT, + >( + obj: ObjT, + deepPath: DeepPathT, + predicate: (arg: unknown) => arg is ValueT, + ): obj is NarrowByDeepValue; + + + type Foo = {value: {type: 'A'}; a?: number} | {value: {type: 'B'}; b?: number}; + + declare function isA(arg: unknown): arg is 'A'; + declare function isB(arg: unknown): arg is 'B'; + + declare function assert(condition: boolean): asserts condition; + + function test1(foo: Foo): {value: {type: 'A'}; a?: number} { + assert(doesValueAtDeepPathSatisfy(foo, ['value', 'type'], isA)); + return foo; + } + + function test2(foo: Foo): {value: {type: 'A'}; a?: number} { + assert(!doesValueAtDeepPathSatisfy(foo, ['value', 'type'], isB)); + return foo; + } + + // Repro from #53063 + + interface Free { + premium: false; + } + + interface Premium { + premium: true; + } + + type Union = { premium: false } | { premium: true }; + + declare const checkIsPremium: (a: Union) => a is Union & Premium; + + const f = (value: Union) => { + if (!checkIsPremium(value)) { + value.premium; + } + }; \ No newline at end of file diff --git a/tests/baselines/reference/strictSubtypeAndNarrowing.js b/tests/baselines/reference/strictSubtypeAndNarrowing.js index 3e135c3af6fc1..ec143633beba9 100644 --- a/tests/baselines/reference/strictSubtypeAndNarrowing.js +++ b/tests/baselines/reference/strictSubtypeAndNarrowing.js @@ -129,6 +129,106 @@ function fx11(): { x?: number } { let obj: { x?: number, y?: number }; return obj = { x: 1, y: 2 }; } + +// Repros from #52827 + +declare function isArrayLike(value: any): value is { length: number }; + +function ff1(value: { [index: number]: boolean, length: number } | undefined) { + if (isArrayLike(value)) { + value; + } else { + value; + } + value; +} + +function ff2(value: { [index: number]: boolean, length: number } | string) { + if (isArrayLike(value)) { + value; + } else { + value; + } + value; +} + +function ff3(value: string | string[] | { [index: number]: boolean, length: number } | [number, boolean] | number | { length: string } | { a: string } | null | undefined) { + if (isArrayLike(value)) { + value; + } else { + value; + } + value; +} + +// Repro from comment in #52984 + +type DistributedKeyOf = T extends unknown ? keyof T : never; + +type NarrowByKeyValue = ObjT extends unknown + ? KeyT extends keyof ObjT + ? ValueT extends ObjT[KeyT] + ? ObjT & Readonly> + : never + : never + : never; + +type NarrowByDeepValue = DeepPathT extends readonly [ + infer Head extends DistributedKeyOf, +] + ? NarrowByKeyValue + : DeepPathT extends readonly [infer Head extends DistributedKeyOf, ...infer Rest] + ? NarrowByKeyValue, Rest, ValueT>> + : never; + + +declare function doesValueAtDeepPathSatisfy< + ObjT extends object, + const DeepPathT extends ReadonlyArray, + ValueT, +>( + obj: ObjT, + deepPath: DeepPathT, + predicate: (arg: unknown) => arg is ValueT, +): obj is NarrowByDeepValue; + + +type Foo = {value: {type: 'A'}; a?: number} | {value: {type: 'B'}; b?: number}; + +declare function isA(arg: unknown): arg is 'A'; +declare function isB(arg: unknown): arg is 'B'; + +declare function assert(condition: boolean): asserts condition; + +function test1(foo: Foo): {value: {type: 'A'}; a?: number} { + assert(doesValueAtDeepPathSatisfy(foo, ['value', 'type'], isA)); + return foo; +} + +function test2(foo: Foo): {value: {type: 'A'}; a?: number} { + assert(!doesValueAtDeepPathSatisfy(foo, ['value', 'type'], isB)); + return foo; +} + +// Repro from #53063 + +interface Free { + premium: false; +} + +interface Premium { + premium: true; +} + +type Union = { premium: false } | { premium: true }; + +declare const checkIsPremium: (a: Union) => a is Union & Premium; + +const f = (value: Union) => { + if (!checkIsPremium(value)) { + value.premium; + } +}; //// [strictSubtypeAndNarrowing.js] @@ -226,3 +326,43 @@ function fx11() { var obj; return obj = { x: 1, y: 2 }; } +function ff1(value) { + if (isArrayLike(value)) { + value; + } + else { + value; + } + value; +} +function ff2(value) { + if (isArrayLike(value)) { + value; + } + else { + value; + } + value; +} +function ff3(value) { + if (isArrayLike(value)) { + value; + } + else { + value; + } + value; +} +function test1(foo) { + assert(doesValueAtDeepPathSatisfy(foo, ['value', 'type'], isA)); + return foo; +} +function test2(foo) { + assert(!doesValueAtDeepPathSatisfy(foo, ['value', 'type'], isB)); + return foo; +} +var f = function (value) { + if (!checkIsPremium(value)) { + value.premium; + } +}; diff --git a/tests/baselines/reference/strictSubtypeAndNarrowing.symbols b/tests/baselines/reference/strictSubtypeAndNarrowing.symbols index 2bdf3b7777cae..99dde89722e12 100644 --- a/tests/baselines/reference/strictSubtypeAndNarrowing.symbols +++ b/tests/baselines/reference/strictSubtypeAndNarrowing.symbols @@ -311,3 +311,293 @@ function fx11(): { x?: number } { >y : Symbol(y, Decl(strictSubtypeAndNarrowing.ts, 128, 24)) } +// Repros from #52827 + +declare function isArrayLike(value: any): value is { length: number }; +>isArrayLike : Symbol(isArrayLike, Decl(strictSubtypeAndNarrowing.ts, 129, 1)) +>value : Symbol(value, Decl(strictSubtypeAndNarrowing.ts, 133, 29)) +>value : Symbol(value, Decl(strictSubtypeAndNarrowing.ts, 133, 29)) +>length : Symbol(length, Decl(strictSubtypeAndNarrowing.ts, 133, 52)) + +function ff1(value: { [index: number]: boolean, length: number } | undefined) { +>ff1 : Symbol(ff1, Decl(strictSubtypeAndNarrowing.ts, 133, 70)) +>value : Symbol(value, Decl(strictSubtypeAndNarrowing.ts, 135, 13)) +>index : Symbol(index, Decl(strictSubtypeAndNarrowing.ts, 135, 23)) +>length : Symbol(length, Decl(strictSubtypeAndNarrowing.ts, 135, 47)) + + if (isArrayLike(value)) { +>isArrayLike : Symbol(isArrayLike, Decl(strictSubtypeAndNarrowing.ts, 129, 1)) +>value : Symbol(value, Decl(strictSubtypeAndNarrowing.ts, 135, 13)) + + value; +>value : Symbol(value, Decl(strictSubtypeAndNarrowing.ts, 135, 13)) + + } else { + value; +>value : Symbol(value, Decl(strictSubtypeAndNarrowing.ts, 135, 13)) + } + value; +>value : Symbol(value, Decl(strictSubtypeAndNarrowing.ts, 135, 13)) +} + +function ff2(value: { [index: number]: boolean, length: number } | string) { +>ff2 : Symbol(ff2, Decl(strictSubtypeAndNarrowing.ts, 142, 1)) +>value : Symbol(value, Decl(strictSubtypeAndNarrowing.ts, 144, 13)) +>index : Symbol(index, Decl(strictSubtypeAndNarrowing.ts, 144, 23)) +>length : Symbol(length, Decl(strictSubtypeAndNarrowing.ts, 144, 47)) + + if (isArrayLike(value)) { +>isArrayLike : Symbol(isArrayLike, Decl(strictSubtypeAndNarrowing.ts, 129, 1)) +>value : Symbol(value, Decl(strictSubtypeAndNarrowing.ts, 144, 13)) + + value; +>value : Symbol(value, Decl(strictSubtypeAndNarrowing.ts, 144, 13)) + + } else { + value; +>value : Symbol(value, Decl(strictSubtypeAndNarrowing.ts, 144, 13)) + } + value; +>value : Symbol(value, Decl(strictSubtypeAndNarrowing.ts, 144, 13)) +} + +function ff3(value: string | string[] | { [index: number]: boolean, length: number } | [number, boolean] | number | { length: string } | { a: string } | null | undefined) { +>ff3 : Symbol(ff3, Decl(strictSubtypeAndNarrowing.ts, 151, 1)) +>value : Symbol(value, Decl(strictSubtypeAndNarrowing.ts, 153, 13)) +>index : Symbol(index, Decl(strictSubtypeAndNarrowing.ts, 153, 43)) +>length : Symbol(length, Decl(strictSubtypeAndNarrowing.ts, 153, 67)) +>length : Symbol(length, Decl(strictSubtypeAndNarrowing.ts, 153, 117)) +>a : Symbol(a, Decl(strictSubtypeAndNarrowing.ts, 153, 138)) + + if (isArrayLike(value)) { +>isArrayLike : Symbol(isArrayLike, Decl(strictSubtypeAndNarrowing.ts, 129, 1)) +>value : Symbol(value, Decl(strictSubtypeAndNarrowing.ts, 153, 13)) + + value; +>value : Symbol(value, Decl(strictSubtypeAndNarrowing.ts, 153, 13)) + + } else { + value; +>value : Symbol(value, Decl(strictSubtypeAndNarrowing.ts, 153, 13)) + } + value; +>value : Symbol(value, Decl(strictSubtypeAndNarrowing.ts, 153, 13)) +} + +// Repro from comment in #52984 + +type DistributedKeyOf = T extends unknown ? keyof T : never; +>DistributedKeyOf : Symbol(DistributedKeyOf, Decl(strictSubtypeAndNarrowing.ts, 160, 1)) +>T : Symbol(T, Decl(strictSubtypeAndNarrowing.ts, 164, 22)) +>T : Symbol(T, Decl(strictSubtypeAndNarrowing.ts, 164, 22)) +>T : Symbol(T, Decl(strictSubtypeAndNarrowing.ts, 164, 22)) + +type NarrowByKeyValue = ObjT extends unknown +>NarrowByKeyValue : Symbol(NarrowByKeyValue, Decl(strictSubtypeAndNarrowing.ts, 164, 63)) +>ObjT : Symbol(ObjT, Decl(strictSubtypeAndNarrowing.ts, 166, 22)) +>KeyT : Symbol(KeyT, Decl(strictSubtypeAndNarrowing.ts, 166, 27)) +>PropertyKey : Symbol(PropertyKey, Decl(lib.es5.d.ts, --, --)) +>ValueT : Symbol(ValueT, Decl(strictSubtypeAndNarrowing.ts, 166, 53)) +>ObjT : Symbol(ObjT, Decl(strictSubtypeAndNarrowing.ts, 166, 22)) + + ? KeyT extends keyof ObjT +>KeyT : Symbol(KeyT, Decl(strictSubtypeAndNarrowing.ts, 166, 27)) +>ObjT : Symbol(ObjT, Decl(strictSubtypeAndNarrowing.ts, 166, 22)) + + ? ValueT extends ObjT[KeyT] +>ValueT : Symbol(ValueT, Decl(strictSubtypeAndNarrowing.ts, 166, 53)) +>ObjT : Symbol(ObjT, Decl(strictSubtypeAndNarrowing.ts, 166, 22)) +>KeyT : Symbol(KeyT, Decl(strictSubtypeAndNarrowing.ts, 166, 27)) + + ? ObjT & Readonly> +>ObjT : Symbol(ObjT, Decl(strictSubtypeAndNarrowing.ts, 166, 22)) +>Readonly : Symbol(Readonly, Decl(lib.es5.d.ts, --, --)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>KeyT : Symbol(KeyT, Decl(strictSubtypeAndNarrowing.ts, 166, 27)) +>ValueT : Symbol(ValueT, Decl(strictSubtypeAndNarrowing.ts, 166, 53)) + + : never + : never + : never; + +type NarrowByDeepValue = DeepPathT extends readonly [ +>NarrowByDeepValue : Symbol(NarrowByDeepValue, Decl(strictSubtypeAndNarrowing.ts, 172, 12)) +>ObjT : Symbol(ObjT, Decl(strictSubtypeAndNarrowing.ts, 174, 23)) +>DeepPathT : Symbol(DeepPathT, Decl(strictSubtypeAndNarrowing.ts, 174, 28)) +>ValueT : Symbol(ValueT, Decl(strictSubtypeAndNarrowing.ts, 174, 39)) +>DeepPathT : Symbol(DeepPathT, Decl(strictSubtypeAndNarrowing.ts, 174, 28)) + + infer Head extends DistributedKeyOf, +>Head : Symbol(Head, Decl(strictSubtypeAndNarrowing.ts, 175, 9)) +>DistributedKeyOf : Symbol(DistributedKeyOf, Decl(strictSubtypeAndNarrowing.ts, 160, 1)) +>ObjT : Symbol(ObjT, Decl(strictSubtypeAndNarrowing.ts, 174, 23)) + +] + ? NarrowByKeyValue +>NarrowByKeyValue : Symbol(NarrowByKeyValue, Decl(strictSubtypeAndNarrowing.ts, 164, 63)) +>ObjT : Symbol(ObjT, Decl(strictSubtypeAndNarrowing.ts, 174, 23)) +>Head : Symbol(Head, Decl(strictSubtypeAndNarrowing.ts, 175, 9)) +>ValueT : Symbol(ValueT, Decl(strictSubtypeAndNarrowing.ts, 174, 39)) + + : DeepPathT extends readonly [infer Head extends DistributedKeyOf, ...infer Rest] +>DeepPathT : Symbol(DeepPathT, Decl(strictSubtypeAndNarrowing.ts, 174, 28)) +>Head : Symbol(Head, Decl(strictSubtypeAndNarrowing.ts, 178, 39)) +>DistributedKeyOf : Symbol(DistributedKeyOf, Decl(strictSubtypeAndNarrowing.ts, 160, 1)) +>ObjT : Symbol(ObjT, Decl(strictSubtypeAndNarrowing.ts, 174, 23)) +>Rest : Symbol(Rest, Decl(strictSubtypeAndNarrowing.ts, 178, 85)) + + ? NarrowByKeyValue, Rest, ValueT>> +>NarrowByKeyValue : Symbol(NarrowByKeyValue, Decl(strictSubtypeAndNarrowing.ts, 164, 63)) +>ObjT : Symbol(ObjT, Decl(strictSubtypeAndNarrowing.ts, 174, 23)) +>Head : Symbol(Head, Decl(strictSubtypeAndNarrowing.ts, 178, 39)) +>NarrowByDeepValue : Symbol(NarrowByDeepValue, Decl(strictSubtypeAndNarrowing.ts, 172, 12)) +>NonNullable : Symbol(NonNullable, Decl(lib.es5.d.ts, --, --)) +>ObjT : Symbol(ObjT, Decl(strictSubtypeAndNarrowing.ts, 174, 23)) +>Head : Symbol(Head, Decl(strictSubtypeAndNarrowing.ts, 178, 39)) +>Rest : Symbol(Rest, Decl(strictSubtypeAndNarrowing.ts, 178, 85)) +>ValueT : Symbol(ValueT, Decl(strictSubtypeAndNarrowing.ts, 174, 39)) + + : never; + + +declare function doesValueAtDeepPathSatisfy< +>doesValueAtDeepPathSatisfy : Symbol(doesValueAtDeepPathSatisfy, Decl(strictSubtypeAndNarrowing.ts, 180, 12)) + + ObjT extends object, +>ObjT : Symbol(ObjT, Decl(strictSubtypeAndNarrowing.ts, 183, 44)) + + const DeepPathT extends ReadonlyArray, +>DeepPathT : Symbol(DeepPathT, Decl(strictSubtypeAndNarrowing.ts, 184, 24)) +>ReadonlyArray : Symbol(ReadonlyArray, Decl(lib.es5.d.ts, --, --)) + + ValueT, +>ValueT : Symbol(ValueT, Decl(strictSubtypeAndNarrowing.ts, 185, 59)) + +>( + obj: ObjT, +>obj : Symbol(obj, Decl(strictSubtypeAndNarrowing.ts, 187, 2)) +>ObjT : Symbol(ObjT, Decl(strictSubtypeAndNarrowing.ts, 183, 44)) + + deepPath: DeepPathT, +>deepPath : Symbol(deepPath, Decl(strictSubtypeAndNarrowing.ts, 188, 14)) +>DeepPathT : Symbol(DeepPathT, Decl(strictSubtypeAndNarrowing.ts, 184, 24)) + + predicate: (arg: unknown) => arg is ValueT, +>predicate : Symbol(predicate, Decl(strictSubtypeAndNarrowing.ts, 189, 24)) +>arg : Symbol(arg, Decl(strictSubtypeAndNarrowing.ts, 190, 16)) +>arg : Symbol(arg, Decl(strictSubtypeAndNarrowing.ts, 190, 16)) +>ValueT : Symbol(ValueT, Decl(strictSubtypeAndNarrowing.ts, 185, 59)) + +): obj is NarrowByDeepValue; +>obj : Symbol(obj, Decl(strictSubtypeAndNarrowing.ts, 187, 2)) +>NarrowByDeepValue : Symbol(NarrowByDeepValue, Decl(strictSubtypeAndNarrowing.ts, 172, 12)) +>ObjT : Symbol(ObjT, Decl(strictSubtypeAndNarrowing.ts, 183, 44)) +>DeepPathT : Symbol(DeepPathT, Decl(strictSubtypeAndNarrowing.ts, 184, 24)) +>ValueT : Symbol(ValueT, Decl(strictSubtypeAndNarrowing.ts, 185, 59)) + + +type Foo = {value: {type: 'A'}; a?: number} | {value: {type: 'B'}; b?: number}; +>Foo : Symbol(Foo, Decl(strictSubtypeAndNarrowing.ts, 191, 53)) +>value : Symbol(value, Decl(strictSubtypeAndNarrowing.ts, 194, 12)) +>type : Symbol(type, Decl(strictSubtypeAndNarrowing.ts, 194, 20)) +>a : Symbol(a, Decl(strictSubtypeAndNarrowing.ts, 194, 31)) +>value : Symbol(value, Decl(strictSubtypeAndNarrowing.ts, 194, 47)) +>type : Symbol(type, Decl(strictSubtypeAndNarrowing.ts, 194, 55)) +>b : Symbol(b, Decl(strictSubtypeAndNarrowing.ts, 194, 66)) + +declare function isA(arg: unknown): arg is 'A'; +>isA : Symbol(isA, Decl(strictSubtypeAndNarrowing.ts, 194, 79)) +>arg : Symbol(arg, Decl(strictSubtypeAndNarrowing.ts, 196, 21)) +>arg : Symbol(arg, Decl(strictSubtypeAndNarrowing.ts, 196, 21)) + +declare function isB(arg: unknown): arg is 'B'; +>isB : Symbol(isB, Decl(strictSubtypeAndNarrowing.ts, 196, 47)) +>arg : Symbol(arg, Decl(strictSubtypeAndNarrowing.ts, 197, 21)) +>arg : Symbol(arg, Decl(strictSubtypeAndNarrowing.ts, 197, 21)) + +declare function assert(condition: boolean): asserts condition; +>assert : Symbol(assert, Decl(strictSubtypeAndNarrowing.ts, 197, 47)) +>condition : Symbol(condition, Decl(strictSubtypeAndNarrowing.ts, 199, 24)) +>condition : Symbol(condition, Decl(strictSubtypeAndNarrowing.ts, 199, 24)) + +function test1(foo: Foo): {value: {type: 'A'}; a?: number} { +>test1 : Symbol(test1, Decl(strictSubtypeAndNarrowing.ts, 199, 63)) +>foo : Symbol(foo, Decl(strictSubtypeAndNarrowing.ts, 201, 15)) +>Foo : Symbol(Foo, Decl(strictSubtypeAndNarrowing.ts, 191, 53)) +>value : Symbol(value, Decl(strictSubtypeAndNarrowing.ts, 201, 27)) +>type : Symbol(type, Decl(strictSubtypeAndNarrowing.ts, 201, 35)) +>a : Symbol(a, Decl(strictSubtypeAndNarrowing.ts, 201, 46)) + + assert(doesValueAtDeepPathSatisfy(foo, ['value', 'type'], isA)); +>assert : Symbol(assert, Decl(strictSubtypeAndNarrowing.ts, 197, 47)) +>doesValueAtDeepPathSatisfy : Symbol(doesValueAtDeepPathSatisfy, Decl(strictSubtypeAndNarrowing.ts, 180, 12)) +>foo : Symbol(foo, Decl(strictSubtypeAndNarrowing.ts, 201, 15)) +>isA : Symbol(isA, Decl(strictSubtypeAndNarrowing.ts, 194, 79)) + + return foo; +>foo : Symbol(foo, Decl(strictSubtypeAndNarrowing.ts, 201, 15)) +} + +function test2(foo: Foo): {value: {type: 'A'}; a?: number} { +>test2 : Symbol(test2, Decl(strictSubtypeAndNarrowing.ts, 204, 1)) +>foo : Symbol(foo, Decl(strictSubtypeAndNarrowing.ts, 206, 15)) +>Foo : Symbol(Foo, Decl(strictSubtypeAndNarrowing.ts, 191, 53)) +>value : Symbol(value, Decl(strictSubtypeAndNarrowing.ts, 206, 27)) +>type : Symbol(type, Decl(strictSubtypeAndNarrowing.ts, 206, 35)) +>a : Symbol(a, Decl(strictSubtypeAndNarrowing.ts, 206, 46)) + + assert(!doesValueAtDeepPathSatisfy(foo, ['value', 'type'], isB)); +>assert : Symbol(assert, Decl(strictSubtypeAndNarrowing.ts, 197, 47)) +>doesValueAtDeepPathSatisfy : Symbol(doesValueAtDeepPathSatisfy, Decl(strictSubtypeAndNarrowing.ts, 180, 12)) +>foo : Symbol(foo, Decl(strictSubtypeAndNarrowing.ts, 206, 15)) +>isB : Symbol(isB, Decl(strictSubtypeAndNarrowing.ts, 196, 47)) + + return foo; +>foo : Symbol(foo, Decl(strictSubtypeAndNarrowing.ts, 206, 15)) +} + +// Repro from #53063 + +interface Free { +>Free : Symbol(Free, Decl(strictSubtypeAndNarrowing.ts, 209, 1)) + + premium: false; +>premium : Symbol(Free.premium, Decl(strictSubtypeAndNarrowing.ts, 213, 16)) +} + +interface Premium { +>Premium : Symbol(Premium, Decl(strictSubtypeAndNarrowing.ts, 215, 1)) + + premium: true; +>premium : Symbol(Premium.premium, Decl(strictSubtypeAndNarrowing.ts, 217, 19)) +} + +type Union = { premium: false } | { premium: true }; +>Union : Symbol(Union, Decl(strictSubtypeAndNarrowing.ts, 219, 1)) +>premium : Symbol(premium, Decl(strictSubtypeAndNarrowing.ts, 221, 14)) +>premium : Symbol(premium, Decl(strictSubtypeAndNarrowing.ts, 221, 35)) + +declare const checkIsPremium: (a: Union) => a is Union & Premium; +>checkIsPremium : Symbol(checkIsPremium, Decl(strictSubtypeAndNarrowing.ts, 223, 13)) +>a : Symbol(a, Decl(strictSubtypeAndNarrowing.ts, 223, 31)) +>Union : Symbol(Union, Decl(strictSubtypeAndNarrowing.ts, 219, 1)) +>a : Symbol(a, Decl(strictSubtypeAndNarrowing.ts, 223, 31)) +>Union : Symbol(Union, Decl(strictSubtypeAndNarrowing.ts, 219, 1)) +>Premium : Symbol(Premium, Decl(strictSubtypeAndNarrowing.ts, 215, 1)) + +const f = (value: Union) => { +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 225, 5)) +>value : Symbol(value, Decl(strictSubtypeAndNarrowing.ts, 225, 11)) +>Union : Symbol(Union, Decl(strictSubtypeAndNarrowing.ts, 219, 1)) + + if (!checkIsPremium(value)) { +>checkIsPremium : Symbol(checkIsPremium, Decl(strictSubtypeAndNarrowing.ts, 223, 13)) +>value : Symbol(value, Decl(strictSubtypeAndNarrowing.ts, 225, 11)) + + value.premium; +>value.premium : Symbol(premium, Decl(strictSubtypeAndNarrowing.ts, 221, 14)) +>value : Symbol(value, Decl(strictSubtypeAndNarrowing.ts, 225, 11)) +>premium : Symbol(premium, Decl(strictSubtypeAndNarrowing.ts, 221, 14)) + } +}; + diff --git a/tests/baselines/reference/strictSubtypeAndNarrowing.types b/tests/baselines/reference/strictSubtypeAndNarrowing.types index 8f2e6a2cd8c83..1dcee9598a60a 100644 --- a/tests/baselines/reference/strictSubtypeAndNarrowing.types +++ b/tests/baselines/reference/strictSubtypeAndNarrowing.types @@ -326,3 +326,234 @@ function fx11(): { x?: number } { >2 : 2 } +// Repros from #52827 + +declare function isArrayLike(value: any): value is { length: number }; +>isArrayLike : (value: any) => value is { length: number; } +>value : any +>length : number + +function ff1(value: { [index: number]: boolean, length: number } | undefined) { +>ff1 : (value: { [index: number]: boolean; length: number; } | undefined) => void +>value : { [index: number]: boolean; length: number; } | undefined +>index : number +>length : number + + if (isArrayLike(value)) { +>isArrayLike(value) : boolean +>isArrayLike : (value: any) => value is { length: number; } +>value : { [index: number]: boolean; length: number; } | undefined + + value; +>value : { [index: number]: boolean; length: number; } + + } else { + value; +>value : undefined + } + value; +>value : { [index: number]: boolean; length: number; } | undefined +} + +function ff2(value: { [index: number]: boolean, length: number } | string) { +>ff2 : (value: string | { [index: number]: boolean; length: number; }) => void +>value : string | { [index: number]: boolean; length: number; } +>index : number +>length : number + + if (isArrayLike(value)) { +>isArrayLike(value) : boolean +>isArrayLike : (value: any) => value is { length: number; } +>value : string | { [index: number]: boolean; length: number; } + + value; +>value : string | { [index: number]: boolean; length: number; } + + } else { + value; +>value : never + } + value; +>value : string | { [index: number]: boolean; length: number; } +} + +function ff3(value: string | string[] | { [index: number]: boolean, length: number } | [number, boolean] | number | { length: string } | { a: string } | null | undefined) { +>ff3 : (value: string | number | { [index: number]: boolean; length: number; } | [number, boolean] | { length: string; } | { a: string; } | string[] | null | undefined) => void +>value : string | number | { [index: number]: boolean; length: number; } | [number, boolean] | { length: string; } | { a: string; } | string[] | null | undefined +>index : number +>length : number +>length : string +>a : string +>null : null + + if (isArrayLike(value)) { +>isArrayLike(value) : boolean +>isArrayLike : (value: any) => value is { length: number; } +>value : string | number | { [index: number]: boolean; length: number; } | [number, boolean] | { length: string; } | { a: string; } | string[] | null | undefined + + value; +>value : string | { [index: number]: boolean; length: number; } | [number, boolean] | string[] + + } else { + value; +>value : number | { length: string; } | { a: string; } | null | undefined + } + value; +>value : string | number | { [index: number]: boolean; length: number; } | [number, boolean] | { length: string; } | { a: string; } | string[] | null | undefined +} + +// Repro from comment in #52984 + +type DistributedKeyOf = T extends unknown ? keyof T : never; +>DistributedKeyOf : DistributedKeyOf + +type NarrowByKeyValue = ObjT extends unknown +>NarrowByKeyValue : NarrowByKeyValue + + ? KeyT extends keyof ObjT + ? ValueT extends ObjT[KeyT] + ? ObjT & Readonly> + : never + : never + : never; + +type NarrowByDeepValue = DeepPathT extends readonly [ +>NarrowByDeepValue : NarrowByDeepValue + + infer Head extends DistributedKeyOf, +] + ? NarrowByKeyValue + : DeepPathT extends readonly [infer Head extends DistributedKeyOf, ...infer Rest] + ? NarrowByKeyValue, Rest, ValueT>> + : never; + + +declare function doesValueAtDeepPathSatisfy< +>doesValueAtDeepPathSatisfy : (obj: ObjT, deepPath: DeepPathT, predicate: (arg: unknown) => arg is ValueT) => obj is NarrowByDeepValue + + ObjT extends object, + const DeepPathT extends ReadonlyArray, + ValueT, +>( + obj: ObjT, +>obj : ObjT + + deepPath: DeepPathT, +>deepPath : DeepPathT + + predicate: (arg: unknown) => arg is ValueT, +>predicate : (arg: unknown) => arg is ValueT +>arg : unknown + +): obj is NarrowByDeepValue; + + +type Foo = {value: {type: 'A'}; a?: number} | {value: {type: 'B'}; b?: number}; +>Foo : { value: { type: 'A';}; a?: number | undefined; } | { value: { type: 'B';}; b?: number | undefined; } +>value : { type: 'A'; } +>type : "A" +>a : number | undefined +>value : { type: 'B'; } +>type : "B" +>b : number | undefined + +declare function isA(arg: unknown): arg is 'A'; +>isA : (arg: unknown) => arg is "A" +>arg : unknown + +declare function isB(arg: unknown): arg is 'B'; +>isB : (arg: unknown) => arg is "B" +>arg : unknown + +declare function assert(condition: boolean): asserts condition; +>assert : (condition: boolean) => asserts condition +>condition : boolean + +function test1(foo: Foo): {value: {type: 'A'}; a?: number} { +>test1 : (foo: Foo) => { value: { type: 'A'; }; a?: number;} +>foo : Foo +>value : { type: 'A'; } +>type : "A" +>a : number | undefined + + assert(doesValueAtDeepPathSatisfy(foo, ['value', 'type'], isA)); +>assert(doesValueAtDeepPathSatisfy(foo, ['value', 'type'], isA)) : void +>assert : (condition: boolean) => asserts condition +>doesValueAtDeepPathSatisfy(foo, ['value', 'type'], isA) : boolean +>doesValueAtDeepPathSatisfy : (obj: ObjT, deepPath: DeepPathT, predicate: (arg: unknown) => arg is ValueT) => obj is NarrowByDeepValue +>foo : Foo +>['value', 'type'] : ["value", "type"] +>'value' : "value" +>'type' : "type" +>isA : (arg: unknown) => arg is "A" + + return foo; +>foo : { value: { type: "A"; }; a?: number | undefined; } +} + +function test2(foo: Foo): {value: {type: 'A'}; a?: number} { +>test2 : (foo: Foo) => { value: { type: 'A'; }; a?: number;} +>foo : Foo +>value : { type: 'A'; } +>type : "A" +>a : number | undefined + + assert(!doesValueAtDeepPathSatisfy(foo, ['value', 'type'], isB)); +>assert(!doesValueAtDeepPathSatisfy(foo, ['value', 'type'], isB)) : void +>assert : (condition: boolean) => asserts condition +>!doesValueAtDeepPathSatisfy(foo, ['value', 'type'], isB) : boolean +>doesValueAtDeepPathSatisfy(foo, ['value', 'type'], isB) : boolean +>doesValueAtDeepPathSatisfy : (obj: ObjT, deepPath: DeepPathT, predicate: (arg: unknown) => arg is ValueT) => obj is NarrowByDeepValue +>foo : Foo +>['value', 'type'] : ["value", "type"] +>'value' : "value" +>'type' : "type" +>isB : (arg: unknown) => arg is "B" + + return foo; +>foo : { value: { type: "A"; }; a?: number | undefined; } +} + +// Repro from #53063 + +interface Free { + premium: false; +>premium : false +>false : false +} + +interface Premium { + premium: true; +>premium : true +>true : true +} + +type Union = { premium: false } | { premium: true }; +>Union : { premium: false; } | { premium: true; } +>premium : false +>false : false +>premium : true +>true : true + +declare const checkIsPremium: (a: Union) => a is Union & Premium; +>checkIsPremium : (a: Union) => a is { premium: true; } & Premium +>a : Union + +const f = (value: Union) => { +>f : (value: Union) => void +>(value: Union) => { if (!checkIsPremium(value)) { value.premium; }} : (value: Union) => void +>value : Union + + if (!checkIsPremium(value)) { +>!checkIsPremium(value) : boolean +>checkIsPremium(value) : boolean +>checkIsPremium : (a: Union) => a is { premium: true; } & Premium +>value : Union + + value.premium; +>value.premium : false +>value : { premium: false; } +>premium : false + } +}; + diff --git a/tests/cases/compiler/strictSubtypeAndNarrowing.ts b/tests/cases/compiler/strictSubtypeAndNarrowing.ts index 6239072f97efe..58a7629ff4307 100644 --- a/tests/cases/compiler/strictSubtypeAndNarrowing.ts +++ b/tests/cases/compiler/strictSubtypeAndNarrowing.ts @@ -130,3 +130,103 @@ function fx11(): { x?: number } { let obj: { x?: number, y?: number }; return obj = { x: 1, y: 2 }; } + +// Repros from #52827 + +declare function isArrayLike(value: any): value is { length: number }; + +function ff1(value: { [index: number]: boolean, length: number } | undefined) { + if (isArrayLike(value)) { + value; + } else { + value; + } + value; +} + +function ff2(value: { [index: number]: boolean, length: number } | string) { + if (isArrayLike(value)) { + value; + } else { + value; + } + value; +} + +function ff3(value: string | string[] | { [index: number]: boolean, length: number } | [number, boolean] | number | { length: string } | { a: string } | null | undefined) { + if (isArrayLike(value)) { + value; + } else { + value; + } + value; +} + +// Repro from comment in #52984 + +type DistributedKeyOf = T extends unknown ? keyof T : never; + +type NarrowByKeyValue = ObjT extends unknown + ? KeyT extends keyof ObjT + ? ValueT extends ObjT[KeyT] + ? ObjT & Readonly> + : never + : never + : never; + +type NarrowByDeepValue = DeepPathT extends readonly [ + infer Head extends DistributedKeyOf, +] + ? NarrowByKeyValue + : DeepPathT extends readonly [infer Head extends DistributedKeyOf, ...infer Rest] + ? NarrowByKeyValue, Rest, ValueT>> + : never; + + +declare function doesValueAtDeepPathSatisfy< + ObjT extends object, + const DeepPathT extends ReadonlyArray, + ValueT, +>( + obj: ObjT, + deepPath: DeepPathT, + predicate: (arg: unknown) => arg is ValueT, +): obj is NarrowByDeepValue; + + +type Foo = {value: {type: 'A'}; a?: number} | {value: {type: 'B'}; b?: number}; + +declare function isA(arg: unknown): arg is 'A'; +declare function isB(arg: unknown): arg is 'B'; + +declare function assert(condition: boolean): asserts condition; + +function test1(foo: Foo): {value: {type: 'A'}; a?: number} { + assert(doesValueAtDeepPathSatisfy(foo, ['value', 'type'], isA)); + return foo; +} + +function test2(foo: Foo): {value: {type: 'A'}; a?: number} { + assert(!doesValueAtDeepPathSatisfy(foo, ['value', 'type'], isB)); + return foo; +} + +// Repro from #53063 + +interface Free { + premium: false; +} + +interface Premium { + premium: true; +} + +type Union = { premium: false } | { premium: true }; + +declare const checkIsPremium: (a: Union) => a is Union & Premium; + +const f = (value: Union) => { + if (!checkIsPremium(value)) { + value.premium; + } +};