Skip to content

Commit

Permalink
🤖 Pick PR #52984 (Check for strict subtypes and then ...) into releas…
Browse files Browse the repository at this point in the history
…e-5.0 (#53085)

Co-authored-by: Anders Hejlsberg <andersh@microsoft.com>
  • Loading branch information
TypeScript Bot and ahejlsberg authored Mar 7, 2023
1 parent b0afbd6 commit 79bdd93
Show file tree
Hide file tree
Showing 9 changed files with 874 additions and 60 deletions.
6 changes: 5 additions & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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 ?
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/narrowingMutualSubtypes.types
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
100 changes: 100 additions & 0 deletions tests/baselines/reference/strictSubtypeAndNarrowing.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
};

140 changes: 140 additions & 0 deletions tests/baselines/reference/strictSubtypeAndNarrowing.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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;
}
};
Loading

0 comments on commit 79bdd93

Please sign in to comment.