Skip to content

Commit b50b20a

Browse files
ahejlsbergtypescript-bot
authored andcommitted
Cherry-pick PR microsoft#52984 into release-5.0
Component commits: b3d3ec9 Use strictSubtypeRelation in getNarrowedType and narrow only for pure subtypes 0325f9d Accept new baselines 3df807f First check for strict subtypes, then check for regular subtypes d737eee Accept new baselines 9b2d602 Add tests 9ea8a55 Accept new baselines 8bb30e2 Add another repro
1 parent b0afbd6 commit b50b20a

9 files changed

+874
-60
lines changed

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 ?

tests/baselines/reference/controlFlowFavorAssertedTypeThroughTypePredicate.errors.txt

-51
This file was deleted.

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

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>

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

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)