Skip to content

Commit e6decb5

Browse files
committed
Type parameters extending unknown narrow like unknown itself
1 parent 6ee5490 commit e6decb5

10 files changed

+351
-12
lines changed

Diff for: src/compiler/checker.ts

+53-2
Original file line numberDiff line numberDiff line change
@@ -19224,6 +19224,21 @@ namespace ts {
1922419224
return result;
1922519225
}
1922619226

19227+
function removeIntersectionMembersWithoutKeys(type: Type) {
19228+
if (!(type.flags & TypeFlags.Intersection)) {
19229+
return type;
19230+
}
19231+
return getIntersectionType(filter((type as IntersectionType).types, t => !isKeylessType(t)));
19232+
}
19233+
19234+
function isKeylessType(type: Type) {
19235+
return !!(getIndexType(type).flags & TypeFlags.Never);
19236+
}
19237+
19238+
function isConditionalFilteringKeylessTypes(type: Type) {
19239+
return !!(type.flags & TypeFlags.Conditional) && (type as ConditionalType).root.isDistributive && everyType((type as ConditionalType).extendsType, isKeylessType);
19240+
}
19241+
1922719242
function structuredTypeRelatedTo(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary {
1922819243
if (intersectionState & IntersectionState.PropertyCheck) {
1922919244
return propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined, IntersectionState.None);
@@ -19366,9 +19381,34 @@ namespace ts {
1936619381
const targetType = (target as IndexType).type;
1936719382
// A keyof S is related to a keyof T if T is related to S.
1936819383
if (sourceFlags & TypeFlags.Index) {
19369-
if (result = isRelatedTo(targetType, (source as IndexType).type, RecursionFlags.Both, /*reportErrors*/ false)) {
19384+
let sourceType = (source as IndexType).type;
19385+
if (result = isRelatedTo(targetType, sourceType, RecursionFlags.Both, /*reportErrors*/ false)) {
1937019386
return result;
1937119387
}
19388+
// If the source is a filtering conditional that removes only keyless sources, eg, `NonNullable<T>`,
19389+
// then it doesn't affect the `keyof` query, and we can unwrap the conditional and relate the unwrapped source and target.
19390+
// There may be multiple stacked conditionals, such as `T extends null ? never : T extends undefined ? never : T : T`, so
19391+
// we need to repeat the unwrapping process.
19392+
while (isConditionalFilteringKeylessTypes(sourceType)) {
19393+
const lastSource = sourceType;
19394+
sourceType = getDefaultConstraintOfConditionalType(sourceType as ConditionalType);
19395+
if (sourceType === lastSource) {
19396+
break;
19397+
}
19398+
if (result = isRelatedTo(targetType, sourceType, RecursionFlags.Both, /*reportErrors*/ false)) {
19399+
return result;
19400+
}
19401+
const simplifiedSource = sourceType;
19402+
// In addition, `keyof (T & U)` is equivalent to `keyof T | keyof U`, so if `keyof U` is always `never`, we can omit
19403+
// it from the relationship. This allows, eg, `keyof (T & object)` to be related to `keyof T`.
19404+
sourceType = removeIntersectionMembersWithoutKeys(sourceType);
19405+
if (sourceType === simplifiedSource) {
19406+
continue;
19407+
}
19408+
if (result = isRelatedTo(targetType, sourceType, RecursionFlags.Both, /*reportErrors*/ false)) {
19409+
return result;
19410+
}
19411+
}
1937219412
}
1937319413
if (isTupleType(targetType)) {
1937419414
// An index type can have a tuple type target when the tuple type contains variadic elements.
@@ -21330,11 +21370,20 @@ namespace ts {
2133021370
return result;
2133121371
}
2133221372

21373+
function getApparentIntersectionTypes(type: IntersectionType): Type[] {
21374+
const apparent = getApparentType(type);
21375+
if (apparent.flags & TypeFlags.Intersection) {
21376+
return (apparent as IntersectionType).types;
21377+
}
21378+
return [apparent];
21379+
}
21380+
2133321381
// Returns the String, Number, Boolean, StringLiteral, NumberLiteral, BooleanLiteral, Void, Undefined, or Null
2133421382
// flags for the string, number, boolean, "", 0, false, void, undefined, or null types respectively. Returns
2133521383
// no flags for all other types (including non-falsy literal types).
2133621384
function getFalsyFlags(type: Type): TypeFlags {
2133721385
return type.flags & TypeFlags.Union ? getFalsyFlagsOfTypes((type as UnionType).types) :
21386+
type.flags & TypeFlags.Intersection ? reduceLeft(getApparentIntersectionTypes(type as IntersectionType), (memo, type) => memo | getFalsyFlags(type), 0 as TypeFacts) :
2133821387
type.flags & TypeFlags.StringLiteral ? (type as StringLiteralType).value === "" ? TypeFlags.StringLiteral : 0 :
2133921388
type.flags & TypeFlags.NumberLiteral ? (type as NumberLiteralType).value === 0 ? TypeFlags.NumberLiteral : 0 :
2134021389
type.flags & TypeFlags.BigIntLiteral ? isZeroBigInt(type as BigIntLiteralType) ? TypeFlags.BigIntLiteral : 0 :
@@ -25019,7 +25068,9 @@ namespace ts {
2501925068
case "function":
2502025069
return type.flags & TypeFlags.Any ? type : globalFunctionType;
2502125070
case "object":
25022-
return type.flags & TypeFlags.Unknown ? getUnionType([nonPrimitiveType, nullType]) : type;
25071+
const isUnknownish = type.flags & TypeFlags.Unknown ||
25072+
(type.flags & TypeFlags.InstantiableNonPrimitive && (getBaseConstraintOfType(type) || unknownType).flags & TypeFlags.Unknown);
25073+
return isUnknownish ? getUnionType([nonPrimitiveType, nullType]) : type;
2502325074
default:
2502425075
return typeofTypesByName.get(text);
2502525076
}
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//// [keyofNonNullableAssignments.ts]
2+
type MyNonNullable<T> = T extends null ? never : T extends undefined ? never : T;
3+
4+
function f<T>(x: T) {
5+
const a: keyof T = (null as any as keyof NonNullable<T>);
6+
const b: keyof T = (null as any as keyof NonNullable<T & object>);
7+
const c: keyof T = (null as any as keyof MyNonNullable<T>);
8+
const d: keyof T = (null as any as keyof MyNonNullable<T & object>);
9+
const e: keyof T = (null as any as keyof NonNullable<T | undefined>);
10+
const f: keyof T = (null as any as keyof NonNullable<(T | undefined) & object>);
11+
const g: keyof T = (null as any as keyof MyNonNullable<T | undefined>);
12+
const h: keyof T = (null as any as keyof MyNonNullable<(T | undefined) & object>);
13+
}
14+
15+
//// [keyofNonNullableAssignments.js]
16+
"use strict";
17+
function f(x) {
18+
var a = null;
19+
var b = null;
20+
var c = null;
21+
var d = null;
22+
var e = null;
23+
var f = null;
24+
var g = null;
25+
var h = null;
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
=== tests/cases/compiler/keyofNonNullableAssignments.ts ===
2+
type MyNonNullable<T> = T extends null ? never : T extends undefined ? never : T;
3+
>MyNonNullable : Symbol(MyNonNullable, Decl(keyofNonNullableAssignments.ts, 0, 0))
4+
>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 0, 19))
5+
>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 0, 19))
6+
>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 0, 19))
7+
>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 0, 19))
8+
9+
function f<T>(x: T) {
10+
>f : Symbol(f, Decl(keyofNonNullableAssignments.ts, 0, 81))
11+
>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 2, 11))
12+
>x : Symbol(x, Decl(keyofNonNullableAssignments.ts, 2, 14))
13+
>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 2, 11))
14+
15+
const a: keyof T = (null as any as keyof NonNullable<T>);
16+
>a : Symbol(a, Decl(keyofNonNullableAssignments.ts, 3, 9))
17+
>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 2, 11))
18+
>NonNullable : Symbol(NonNullable, Decl(lib.es5.d.ts, --, --))
19+
>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 2, 11))
20+
21+
const b: keyof T = (null as any as keyof NonNullable<T & object>);
22+
>b : Symbol(b, Decl(keyofNonNullableAssignments.ts, 4, 9))
23+
>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 2, 11))
24+
>NonNullable : Symbol(NonNullable, Decl(lib.es5.d.ts, --, --))
25+
>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 2, 11))
26+
27+
const c: keyof T = (null as any as keyof MyNonNullable<T>);
28+
>c : Symbol(c, Decl(keyofNonNullableAssignments.ts, 5, 9))
29+
>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 2, 11))
30+
>MyNonNullable : Symbol(MyNonNullable, Decl(keyofNonNullableAssignments.ts, 0, 0))
31+
>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 2, 11))
32+
33+
const d: keyof T = (null as any as keyof MyNonNullable<T & object>);
34+
>d : Symbol(d, Decl(keyofNonNullableAssignments.ts, 6, 9))
35+
>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 2, 11))
36+
>MyNonNullable : Symbol(MyNonNullable, Decl(keyofNonNullableAssignments.ts, 0, 0))
37+
>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 2, 11))
38+
39+
const e: keyof T = (null as any as keyof NonNullable<T | undefined>);
40+
>e : Symbol(e, Decl(keyofNonNullableAssignments.ts, 7, 9))
41+
>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 2, 11))
42+
>NonNullable : Symbol(NonNullable, Decl(lib.es5.d.ts, --, --))
43+
>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 2, 11))
44+
45+
const f: keyof T = (null as any as keyof NonNullable<(T | undefined) & object>);
46+
>f : Symbol(f, Decl(keyofNonNullableAssignments.ts, 8, 9))
47+
>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 2, 11))
48+
>NonNullable : Symbol(NonNullable, Decl(lib.es5.d.ts, --, --))
49+
>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 2, 11))
50+
51+
const g: keyof T = (null as any as keyof MyNonNullable<T | undefined>);
52+
>g : Symbol(g, Decl(keyofNonNullableAssignments.ts, 9, 9))
53+
>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 2, 11))
54+
>MyNonNullable : Symbol(MyNonNullable, Decl(keyofNonNullableAssignments.ts, 0, 0))
55+
>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 2, 11))
56+
57+
const h: keyof T = (null as any as keyof MyNonNullable<(T | undefined) & object>);
58+
>h : Symbol(h, Decl(keyofNonNullableAssignments.ts, 10, 9))
59+
>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 2, 11))
60+
>MyNonNullable : Symbol(MyNonNullable, Decl(keyofNonNullableAssignments.ts, 0, 0))
61+
>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 2, 11))
62+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
=== tests/cases/compiler/keyofNonNullableAssignments.ts ===
2+
type MyNonNullable<T> = T extends null ? never : T extends undefined ? never : T;
3+
>MyNonNullable : MyNonNullable<T>
4+
>null : null
5+
6+
function f<T>(x: T) {
7+
>f : <T>(x: T) => void
8+
>x : T
9+
10+
const a: keyof T = (null as any as keyof NonNullable<T>);
11+
>a : keyof T
12+
>(null as any as keyof NonNullable<T>) : keyof NonNullable<T>
13+
>null as any as keyof NonNullable<T> : keyof NonNullable<T>
14+
>null as any : any
15+
>null : null
16+
17+
const b: keyof T = (null as any as keyof NonNullable<T & object>);
18+
>b : keyof T
19+
>(null as any as keyof NonNullable<T & object>) : keyof NonNullable<T & object>
20+
>null as any as keyof NonNullable<T & object> : keyof NonNullable<T & object>
21+
>null as any : any
22+
>null : null
23+
24+
const c: keyof T = (null as any as keyof MyNonNullable<T>);
25+
>c : keyof T
26+
>(null as any as keyof MyNonNullable<T>) : keyof MyNonNullable<T>
27+
>null as any as keyof MyNonNullable<T> : keyof MyNonNullable<T>
28+
>null as any : any
29+
>null : null
30+
31+
const d: keyof T = (null as any as keyof MyNonNullable<T & object>);
32+
>d : keyof T
33+
>(null as any as keyof MyNonNullable<T & object>) : keyof MyNonNullable<T & object>
34+
>null as any as keyof MyNonNullable<T & object> : keyof MyNonNullable<T & object>
35+
>null as any : any
36+
>null : null
37+
38+
const e: keyof T = (null as any as keyof NonNullable<T | undefined>);
39+
>e : keyof T
40+
>(null as any as keyof NonNullable<T | undefined>) : keyof NonNullable<T>
41+
>null as any as keyof NonNullable<T | undefined> : keyof NonNullable<T>
42+
>null as any : any
43+
>null : null
44+
45+
const f: keyof T = (null as any as keyof NonNullable<(T | undefined) & object>);
46+
>f : keyof T
47+
>(null as any as keyof NonNullable<(T | undefined) & object>) : keyof NonNullable<T & object>
48+
>null as any as keyof NonNullable<(T | undefined) & object> : keyof NonNullable<T & object>
49+
>null as any : any
50+
>null : null
51+
52+
const g: keyof T = (null as any as keyof MyNonNullable<T | undefined>);
53+
>g : keyof T
54+
>(null as any as keyof MyNonNullable<T | undefined>) : keyof MyNonNullable<T>
55+
>null as any as keyof MyNonNullable<T | undefined> : keyof MyNonNullable<T>
56+
>null as any : any
57+
>null : null
58+
59+
const h: keyof T = (null as any as keyof MyNonNullable<(T | undefined) & object>);
60+
>h : keyof T
61+
>(null as any as keyof MyNonNullable<(T | undefined) & object>) : keyof MyNonNullable<T & object>
62+
>null as any as keyof MyNonNullable<(T | undefined) & object> : keyof MyNonNullable<T & object>
63+
>null as any : any
64+
>null : null
65+
}

Diff for: tests/baselines/reference/mappedTypes4.types

+10-10
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,19 @@ function boxify<T>(obj: T): Boxified<T> {
2626
>{} : {}
2727

2828
for (let k in obj) {
29-
>k : Extract<keyof T, string>
30-
>obj : T
29+
>k : Extract<keyof NonNullable<T & object>, string>
30+
>obj : T & (object | null)
3131

3232
result[k] = { value: obj[k] };
33-
>result[k] = { value: obj[k] } : { value: T[Extract<keyof T, string>]; }
34-
>result[k] : Boxified<T>[Extract<keyof T, string>]
33+
>result[k] = { value: obj[k] } : { value: NonNullable<T & object>[Extract<keyof NonNullable<T & object>, string>]; }
34+
>result[k] : Boxified<T>[Extract<keyof NonNullable<T & object>, string>]
3535
>result : Boxified<T>
36-
>k : Extract<keyof T, string>
37-
>{ value: obj[k] } : { value: T[Extract<keyof T, string>]; }
38-
>value : T[Extract<keyof T, string>]
39-
>obj[k] : T[Extract<keyof T, string>]
40-
>obj : T
41-
>k : Extract<keyof T, string>
36+
>k : Extract<keyof NonNullable<T & object>, string>
37+
>{ value: obj[k] } : { value: NonNullable<T & object>[Extract<keyof NonNullable<T & object>, string>]; }
38+
>value : NonNullable<T & object>[Extract<keyof NonNullable<T & object>, string>]
39+
>obj[k] : NonNullable<T & object>[Extract<keyof NonNullable<T & object>, string>]
40+
>obj : NonNullable<T & object>
41+
>k : Extract<keyof NonNullable<T & object>, string>
4242
}
4343
return result;
4444
>result : Boxified<T>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//// [unconstrainedTypeParameterNarrowing.ts]
2+
function f1<T>(x: T) {
3+
if (typeof x === "object" && x) {
4+
g(x);
5+
}
6+
}
7+
8+
function f2<T extends unknown>(x: T) {
9+
if (typeof x === "object" && x) {
10+
g(x);
11+
}
12+
}
13+
14+
function g(x: object) {}
15+
16+
//// [unconstrainedTypeParameterNarrowing.js]
17+
"use strict";
18+
function f1(x) {
19+
if (typeof x === "object" && x) {
20+
g(x);
21+
}
22+
}
23+
function f2(x) {
24+
if (typeof x === "object" && x) {
25+
g(x);
26+
}
27+
}
28+
function g(x) { }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
=== tests/cases/compiler/unconstrainedTypeParameterNarrowing.ts ===
2+
function f1<T>(x: T) {
3+
>f1 : Symbol(f1, Decl(unconstrainedTypeParameterNarrowing.ts, 0, 0))
4+
>T : Symbol(T, Decl(unconstrainedTypeParameterNarrowing.ts, 0, 12))
5+
>x : Symbol(x, Decl(unconstrainedTypeParameterNarrowing.ts, 0, 15))
6+
>T : Symbol(T, Decl(unconstrainedTypeParameterNarrowing.ts, 0, 12))
7+
8+
if (typeof x === "object" && x) {
9+
>x : Symbol(x, Decl(unconstrainedTypeParameterNarrowing.ts, 0, 15))
10+
>x : Symbol(x, Decl(unconstrainedTypeParameterNarrowing.ts, 0, 15))
11+
12+
g(x);
13+
>g : Symbol(g, Decl(unconstrainedTypeParameterNarrowing.ts, 10, 1))
14+
>x : Symbol(x, Decl(unconstrainedTypeParameterNarrowing.ts, 0, 15))
15+
}
16+
}
17+
18+
function f2<T extends unknown>(x: T) {
19+
>f2 : Symbol(f2, Decl(unconstrainedTypeParameterNarrowing.ts, 4, 1))
20+
>T : Symbol(T, Decl(unconstrainedTypeParameterNarrowing.ts, 6, 12))
21+
>x : Symbol(x, Decl(unconstrainedTypeParameterNarrowing.ts, 6, 31))
22+
>T : Symbol(T, Decl(unconstrainedTypeParameterNarrowing.ts, 6, 12))
23+
24+
if (typeof x === "object" && x) {
25+
>x : Symbol(x, Decl(unconstrainedTypeParameterNarrowing.ts, 6, 31))
26+
>x : Symbol(x, Decl(unconstrainedTypeParameterNarrowing.ts, 6, 31))
27+
28+
g(x);
29+
>g : Symbol(g, Decl(unconstrainedTypeParameterNarrowing.ts, 10, 1))
30+
>x : Symbol(x, Decl(unconstrainedTypeParameterNarrowing.ts, 6, 31))
31+
}
32+
}
33+
34+
function g(x: object) {}
35+
>g : Symbol(g, Decl(unconstrainedTypeParameterNarrowing.ts, 10, 1))
36+
>x : Symbol(x, Decl(unconstrainedTypeParameterNarrowing.ts, 12, 11))
37+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
=== tests/cases/compiler/unconstrainedTypeParameterNarrowing.ts ===
2+
function f1<T>(x: T) {
3+
>f1 : <T>(x: T) => void
4+
>x : T
5+
6+
if (typeof x === "object" && x) {
7+
>typeof x === "object" && x : false | (T & (object | null))
8+
>typeof x === "object" : boolean
9+
>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
10+
>x : T
11+
>"object" : "object"
12+
>x : T & (object | null)
13+
14+
g(x);
15+
>g(x) : void
16+
>g : (x: object) => void
17+
>x : T & object
18+
}
19+
}
20+
21+
function f2<T extends unknown>(x: T) {
22+
>f2 : <T extends unknown>(x: T) => void
23+
>x : T
24+
25+
if (typeof x === "object" && x) {
26+
>typeof x === "object" && x : false | (T & (object | null))
27+
>typeof x === "object" : boolean
28+
>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
29+
>x : T
30+
>"object" : "object"
31+
>x : T & (object | null)
32+
33+
g(x);
34+
>g(x) : void
35+
>g : (x: object) => void
36+
>x : T & object
37+
}
38+
}
39+
40+
function g(x: object) {}
41+
>g : (x: object) => void
42+
>x : object
43+

0 commit comments

Comments
 (0)