Skip to content

Commit 79bdd93

Browse files
TypeScript Botahejlsberg
TypeScript Bot
andauthored
🤖 Pick PR #52984 (Check for strict subtypes and then ...) into release-5.0 (#53085)
Co-authored-by: Anders Hejlsberg <[email protected]>
1 parent b0afbd6 commit 79bdd93

9 files changed

+874
-60
lines changed

Diff for: ‎src/compiler/checker.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -19108,6 +19108,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1910819108
return isTypeRelatedTo(source, target, subtypeRelation);
1910919109
}
1911019110

19111+
function isTypeStrictSubtypeOf(source: Type, target: Type): boolean {
19112+
return isTypeRelatedTo(source, target, strictSubtypeRelation);
19113+
}
19114+
1911119115
function isTypeAssignableTo(source: Type, target: Type): boolean {
1911219116
return isTypeRelatedTo(source, target, assignableRelation);
1911319117
}
@@ -27262,7 +27266,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2726227266
// prototype object types.
2726327267
const directlyRelated = mapType(matching || type, checkDerived ?
2726427268
t => isTypeDerivedFrom(t, c) ? t : isTypeDerivedFrom(c, t) ? c : neverType :
27265-
t => isTypeSubtypeOf(c, t) && !isTypeIdenticalTo(c, t) ? c : isTypeSubtypeOf(t, c) ? t : neverType);
27269+
t => isTypeStrictSubtypeOf(t, c) ? t : isTypeStrictSubtypeOf(c, t) ? c : isTypeSubtypeOf(t, c) ? t : isTypeSubtypeOf(c, t) ? c : neverType);
2726627270
// If no constituents are directly related, create intersections for any generic constituents that
2726727271
// are related by constraint.
2726827272
return directlyRelated.flags & TypeFlags.Never ?

Diff for: ‎tests/baselines/reference/controlFlowFavorAssertedTypeThroughTypePredicate.errors.txt

-51
This file was deleted.

Diff for: ‎tests/baselines/reference/controlFlowFavorAssertedTypeThroughTypePredicate.types

+6-6
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,11 @@ if (isObject2(obj3)) {
5858
>obj3 : Record<string, unknown>
5959

6060
obj3;
61-
>obj3 : {}
61+
>obj3 : Record<string, unknown>
6262

6363
obj3['attr'];
64-
>obj3['attr'] : any
65-
>obj3 : {}
64+
>obj3['attr'] : unknown
65+
>obj3 : Record<string, unknown>
6666
>'attr' : "attr"
6767
}
6868
// check type after conditional block
@@ -78,11 +78,11 @@ if (isObject2(obj4)) {
7878
>obj4 : Record<string, unknown> | undefined
7979

8080
obj4;
81-
>obj4 : {}
81+
>obj4 : Record<string, unknown>
8282

8383
obj4['attr'];
84-
>obj4['attr'] : any
85-
>obj4 : {}
84+
>obj4['attr'] : unknown
85+
>obj4 : Record<string, unknown>
8686
>'attr' : "attr"
8787
}
8888
// check type after conditional block

Diff for: ‎tests/baselines/reference/narrowingMutualSubtypes.types

+2-2
Original file line numberDiff line numberDiff line change
@@ -123,11 +123,11 @@ function gg2(x: Record<string, unknown>) {
123123
>x : Record<string, unknown>
124124

125125
x; // {}
126-
>x : {}
126+
>x : Record<string, unknown>
127127
}
128128
else {
129129
x; // Record<string, unknown>
130-
>x : Record<string, unknown>
130+
>x : never
131131
}
132132
x; // Record<string, unknown>
133133
>x : Record<string, unknown>

Diff for: ‎tests/baselines/reference/strictSubtypeAndNarrowing.errors.txt

+100
Original file line numberDiff line numberDiff line change
@@ -146,4 +146,104 @@ tests/cases/compiler/strictSubtypeAndNarrowing.ts(129,26): error TS2322: Type '{
146146
!!! error TS2322: Type '{ x: number; y: number; }' is not assignable to type '{ x?: number | undefined; }'.
147147
!!! error TS2322: Object literal may only specify known properties, and 'y' does not exist in type '{ x?: number | undefined; }'.
148148
}
149+
150+
// Repros from #52827
151+
152+
declare function isArrayLike(value: any): value is { length: number };
153+
154+
function ff1(value: { [index: number]: boolean, length: number } | undefined) {
155+
if (isArrayLike(value)) {
156+
value;
157+
} else {
158+
value;
159+
}
160+
value;
161+
}
162+
163+
function ff2(value: { [index: number]: boolean, length: number } | string) {
164+
if (isArrayLike(value)) {
165+
value;
166+
} else {
167+
value;
168+
}
169+
value;
170+
}
171+
172+
function ff3(value: string | string[] | { [index: number]: boolean, length: number } | [number, boolean] | number | { length: string } | { a: string } | null | undefined) {
173+
if (isArrayLike(value)) {
174+
value;
175+
} else {
176+
value;
177+
}
178+
value;
179+
}
180+
181+
// Repro from comment in #52984
182+
183+
type DistributedKeyOf<T> = T extends unknown ? keyof T : never;
184+
185+
type NarrowByKeyValue<ObjT, KeyT extends PropertyKey, ValueT> = ObjT extends unknown
186+
? KeyT extends keyof ObjT
187+
? ValueT extends ObjT[KeyT]
188+
? ObjT & Readonly<Record<KeyT, ValueT>>
189+
: never
190+
: never
191+
: never;
192+
193+
type NarrowByDeepValue<ObjT, DeepPathT, ValueT> = DeepPathT extends readonly [
194+
infer Head extends DistributedKeyOf<ObjT>,
195+
]
196+
? NarrowByKeyValue<ObjT, Head, ValueT>
197+
: DeepPathT extends readonly [infer Head extends DistributedKeyOf<ObjT>, ...infer Rest]
198+
? NarrowByKeyValue<ObjT, Head, NarrowByDeepValue<NonNullable<ObjT[Head]>, Rest, ValueT>>
199+
: never;
200+
201+
202+
declare function doesValueAtDeepPathSatisfy<
203+
ObjT extends object,
204+
const DeepPathT extends ReadonlyArray<number | string>,
205+
ValueT,
206+
>(
207+
obj: ObjT,
208+
deepPath: DeepPathT,
209+
predicate: (arg: unknown) => arg is ValueT,
210+
): obj is NarrowByDeepValue<ObjT, DeepPathT, ValueT>;
211+
212+
213+
type Foo = {value: {type: 'A'}; a?: number} | {value: {type: 'B'}; b?: number};
214+
215+
declare function isA(arg: unknown): arg is 'A';
216+
declare function isB(arg: unknown): arg is 'B';
217+
218+
declare function assert(condition: boolean): asserts condition;
219+
220+
function test1(foo: Foo): {value: {type: 'A'}; a?: number} {
221+
assert(doesValueAtDeepPathSatisfy(foo, ['value', 'type'], isA));
222+
return foo;
223+
}
224+
225+
function test2(foo: Foo): {value: {type: 'A'}; a?: number} {
226+
assert(!doesValueAtDeepPathSatisfy(foo, ['value', 'type'], isB));
227+
return foo;
228+
}
229+
230+
// Repro from #53063
231+
232+
interface Free {
233+
premium: false;
234+
}
235+
236+
interface Premium {
237+
premium: true;
238+
}
239+
240+
type Union = { premium: false } | { premium: true };
241+
242+
declare const checkIsPremium: (a: Union) => a is Union & Premium;
243+
244+
const f = (value: Union) => {
245+
if (!checkIsPremium(value)) {
246+
value.premium;
247+
}
248+
};
149249

Diff for: ‎tests/baselines/reference/strictSubtypeAndNarrowing.js

+140
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,106 @@ function fx11(): { x?: number } {
129129
let obj: { x?: number, y?: number };
130130
return obj = { x: 1, y: 2 };
131131
}
132+
133+
// Repros from #52827
134+
135+
declare function isArrayLike(value: any): value is { length: number };
136+
137+
function ff1(value: { [index: number]: boolean, length: number } | undefined) {
138+
if (isArrayLike(value)) {
139+
value;
140+
} else {
141+
value;
142+
}
143+
value;
144+
}
145+
146+
function ff2(value: { [index: number]: boolean, length: number } | string) {
147+
if (isArrayLike(value)) {
148+
value;
149+
} else {
150+
value;
151+
}
152+
value;
153+
}
154+
155+
function ff3(value: string | string[] | { [index: number]: boolean, length: number } | [number, boolean] | number | { length: string } | { a: string } | null | undefined) {
156+
if (isArrayLike(value)) {
157+
value;
158+
} else {
159+
value;
160+
}
161+
value;
162+
}
163+
164+
// Repro from comment in #52984
165+
166+
type DistributedKeyOf<T> = T extends unknown ? keyof T : never;
167+
168+
type NarrowByKeyValue<ObjT, KeyT extends PropertyKey, ValueT> = ObjT extends unknown
169+
? KeyT extends keyof ObjT
170+
? ValueT extends ObjT[KeyT]
171+
? ObjT & Readonly<Record<KeyT, ValueT>>
172+
: never
173+
: never
174+
: never;
175+
176+
type NarrowByDeepValue<ObjT, DeepPathT, ValueT> = DeepPathT extends readonly [
177+
infer Head extends DistributedKeyOf<ObjT>,
178+
]
179+
? NarrowByKeyValue<ObjT, Head, ValueT>
180+
: DeepPathT extends readonly [infer Head extends DistributedKeyOf<ObjT>, ...infer Rest]
181+
? NarrowByKeyValue<ObjT, Head, NarrowByDeepValue<NonNullable<ObjT[Head]>, Rest, ValueT>>
182+
: never;
183+
184+
185+
declare function doesValueAtDeepPathSatisfy<
186+
ObjT extends object,
187+
const DeepPathT extends ReadonlyArray<number | string>,
188+
ValueT,
189+
>(
190+
obj: ObjT,
191+
deepPath: DeepPathT,
192+
predicate: (arg: unknown) => arg is ValueT,
193+
): obj is NarrowByDeepValue<ObjT, DeepPathT, ValueT>;
194+
195+
196+
type Foo = {value: {type: 'A'}; a?: number} | {value: {type: 'B'}; b?: number};
197+
198+
declare function isA(arg: unknown): arg is 'A';
199+
declare function isB(arg: unknown): arg is 'B';
200+
201+
declare function assert(condition: boolean): asserts condition;
202+
203+
function test1(foo: Foo): {value: {type: 'A'}; a?: number} {
204+
assert(doesValueAtDeepPathSatisfy(foo, ['value', 'type'], isA));
205+
return foo;
206+
}
207+
208+
function test2(foo: Foo): {value: {type: 'A'}; a?: number} {
209+
assert(!doesValueAtDeepPathSatisfy(foo, ['value', 'type'], isB));
210+
return foo;
211+
}
212+
213+
// Repro from #53063
214+
215+
interface Free {
216+
premium: false;
217+
}
218+
219+
interface Premium {
220+
premium: true;
221+
}
222+
223+
type Union = { premium: false } | { premium: true };
224+
225+
declare const checkIsPremium: (a: Union) => a is Union & Premium;
226+
227+
const f = (value: Union) => {
228+
if (!checkIsPremium(value)) {
229+
value.premium;
230+
}
231+
};
132232

133233

134234
//// [strictSubtypeAndNarrowing.js]
@@ -226,3 +326,43 @@ function fx11() {
226326
var obj;
227327
return obj = { x: 1, y: 2 };
228328
}
329+
function ff1(value) {
330+
if (isArrayLike(value)) {
331+
value;
332+
}
333+
else {
334+
value;
335+
}
336+
value;
337+
}
338+
function ff2(value) {
339+
if (isArrayLike(value)) {
340+
value;
341+
}
342+
else {
343+
value;
344+
}
345+
value;
346+
}
347+
function ff3(value) {
348+
if (isArrayLike(value)) {
349+
value;
350+
}
351+
else {
352+
value;
353+
}
354+
value;
355+
}
356+
function test1(foo) {
357+
assert(doesValueAtDeepPathSatisfy(foo, ['value', 'type'], isA));
358+
return foo;
359+
}
360+
function test2(foo) {
361+
assert(!doesValueAtDeepPathSatisfy(foo, ['value', 'type'], isB));
362+
return foo;
363+
}
364+
var f = function (value) {
365+
if (!checkIsPremium(value)) {
366+
value.premium;
367+
}
368+
};

0 commit comments

Comments
 (0)