🤖 Pick PR #52984 (Check for strict subtypes and then ...) into release-5.0 (#53085)

Co-authored-by: Anders Hejlsberg <andersh@microsoft.com>
This commit is contained in:
TypeScript Bot
2023-03-06 17:17:49 -08:00
committed by GitHub
co-authored by Anders Hejlsberg
parent b0afbd6aaa
commit 79bdd93ec5
9 changed files with 874 additions and 60 deletions
+5 -1
View File
@@ -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 ?
@@ -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<string, unknown>;
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<string, unknown>;
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<string, unknown> | 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;
@@ -58,11 +58,11 @@ if (isObject2(obj3)) {
>obj3 : Record<string, unknown>
obj3;
>obj3 : {}
>obj3 : Record<string, unknown>
obj3['attr'];
>obj3['attr'] : any
>obj3 : {}
>obj3['attr'] : unknown
>obj3 : Record<string, unknown>
>'attr' : "attr"
}
// check type after conditional block
@@ -78,11 +78,11 @@ if (isObject2(obj4)) {
>obj4 : Record<string, unknown> | undefined
obj4;
>obj4 : {}
>obj4 : Record<string, unknown>
obj4['attr'];
>obj4['attr'] : any
>obj4 : {}
>obj4['attr'] : unknown
>obj4 : Record<string, unknown>
>'attr' : "attr"
}
// check type after conditional block
@@ -123,11 +123,11 @@ function gg2(x: Record<string, unknown>) {
>x : Record<string, unknown>
x; // {}
>x : {}
>x : Record<string, unknown>
}
else {
x; // Record<string, unknown>
>x : Record<string, unknown>
>x : never
}
x; // Record<string, unknown>
>x : Record<string, unknown>
@@ -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> = T extends unknown ? keyof T : never;
type NarrowByKeyValue<ObjT, KeyT extends PropertyKey, ValueT> = ObjT extends unknown
? KeyT extends keyof ObjT
? ValueT extends ObjT[KeyT]
? ObjT & Readonly<Record<KeyT, ValueT>>
: never
: never
: never;
type NarrowByDeepValue<ObjT, DeepPathT, ValueT> = DeepPathT extends readonly [
infer Head extends DistributedKeyOf<ObjT>,
]
? NarrowByKeyValue<ObjT, Head, ValueT>
: DeepPathT extends readonly [infer Head extends DistributedKeyOf<ObjT>, ...infer Rest]
? NarrowByKeyValue<ObjT, Head, NarrowByDeepValue<NonNullable<ObjT[Head]>, Rest, ValueT>>
: never;
declare function doesValueAtDeepPathSatisfy<
ObjT extends object,
const DeepPathT extends ReadonlyArray<number | string>,
ValueT,
>(
obj: ObjT,
deepPath: DeepPathT,
predicate: (arg: unknown) => arg is ValueT,
): obj is NarrowByDeepValue<ObjT, DeepPathT, ValueT>;
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;
}
};
@@ -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> = T extends unknown ? keyof T : never;
type NarrowByKeyValue<ObjT, KeyT extends PropertyKey, ValueT> = ObjT extends unknown
? KeyT extends keyof ObjT
? ValueT extends ObjT[KeyT]
? ObjT & Readonly<Record<KeyT, ValueT>>
: never
: never
: never;
type NarrowByDeepValue<ObjT, DeepPathT, ValueT> = DeepPathT extends readonly [
infer Head extends DistributedKeyOf<ObjT>,
]
? NarrowByKeyValue<ObjT, Head, ValueT>
: DeepPathT extends readonly [infer Head extends DistributedKeyOf<ObjT>, ...infer Rest]
? NarrowByKeyValue<ObjT, Head, NarrowByDeepValue<NonNullable<ObjT[Head]>, Rest, ValueT>>
: never;
declare function doesValueAtDeepPathSatisfy<
ObjT extends object,
const DeepPathT extends ReadonlyArray<number | string>,
ValueT,
>(
obj: ObjT,
deepPath: DeepPathT,
predicate: (arg: unknown) => arg is ValueT,
): obj is NarrowByDeepValue<ObjT, DeepPathT, ValueT>;
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;
}
};
@@ -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> = 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, KeyT extends PropertyKey, ValueT> = 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<Record<KeyT, ValueT>>
>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<ObjT, DeepPathT, ValueT> = 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<ObjT>,
>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<ObjT, Head, ValueT>
>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<ObjT>, ...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<ObjT, Head, NarrowByDeepValue<NonNullable<ObjT[Head]>, 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<number | string>,
>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<ObjT, DeepPathT, ValueT>;
>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))
}
};
@@ -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> = T extends unknown ? keyof T : never;
>DistributedKeyOf : DistributedKeyOf<T>
type NarrowByKeyValue<ObjT, KeyT extends PropertyKey, ValueT> = ObjT extends unknown
>NarrowByKeyValue : NarrowByKeyValue<ObjT, KeyT, ValueT>
? KeyT extends keyof ObjT
? ValueT extends ObjT[KeyT]
? ObjT & Readonly<Record<KeyT, ValueT>>
: never
: never
: never;
type NarrowByDeepValue<ObjT, DeepPathT, ValueT> = DeepPathT extends readonly [
>NarrowByDeepValue : NarrowByDeepValue<ObjT, DeepPathT, ValueT>
infer Head extends DistributedKeyOf<ObjT>,
]
? NarrowByKeyValue<ObjT, Head, ValueT>
: DeepPathT extends readonly [infer Head extends DistributedKeyOf<ObjT>, ...infer Rest]
? NarrowByKeyValue<ObjT, Head, NarrowByDeepValue<NonNullable<ObjT[Head]>, Rest, ValueT>>
: never;
declare function doesValueAtDeepPathSatisfy<
>doesValueAtDeepPathSatisfy : <ObjT extends object, const DeepPathT extends readonly (string | number)[], ValueT>(obj: ObjT, deepPath: DeepPathT, predicate: (arg: unknown) => arg is ValueT) => obj is NarrowByDeepValue<ObjT, DeepPathT, ValueT>
ObjT extends object,
const DeepPathT extends ReadonlyArray<number | string>,
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<ObjT, DeepPathT, ValueT>;
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 : <ObjT extends object, const DeepPathT extends readonly (string | number)[], ValueT>(obj: ObjT, deepPath: DeepPathT, predicate: (arg: unknown) => arg is ValueT) => obj is NarrowByDeepValue<ObjT, DeepPathT, ValueT>
>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 : <ObjT extends object, const DeepPathT extends readonly (string | number)[], ValueT>(obj: ObjT, deepPath: DeepPathT, predicate: (arg: unknown) => arg is ValueT) => obj is NarrowByDeepValue<ObjT, DeepPathT, ValueT>
>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
}
};
@@ -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> = T extends unknown ? keyof T : never;
type NarrowByKeyValue<ObjT, KeyT extends PropertyKey, ValueT> = ObjT extends unknown
? KeyT extends keyof ObjT
? ValueT extends ObjT[KeyT]
? ObjT & Readonly<Record<KeyT, ValueT>>
: never
: never
: never;
type NarrowByDeepValue<ObjT, DeepPathT, ValueT> = DeepPathT extends readonly [
infer Head extends DistributedKeyOf<ObjT>,
]
? NarrowByKeyValue<ObjT, Head, ValueT>
: DeepPathT extends readonly [infer Head extends DistributedKeyOf<ObjT>, ...infer Rest]
? NarrowByKeyValue<ObjT, Head, NarrowByDeepValue<NonNullable<ObjT[Head]>, Rest, ValueT>>
: never;
declare function doesValueAtDeepPathSatisfy<
ObjT extends object,
const DeepPathT extends ReadonlyArray<number | string>,
ValueT,
>(
obj: ObjT,
deepPath: DeepPathT,
predicate: (arg: unknown) => arg is ValueT,
): obj is NarrowByDeepValue<ObjT, DeepPathT, ValueT>;
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;
}
};