Skip to content

Commit 4fa9605

Browse files
kpdonnsandersn
authored andcommitted
Make T[never] = never instead of erroring or being any (#22787)
* Add tests showing existing behavior for indexing types with never. * Make T[never] = never instead of erroring or being any. And update the baselines for the tests for this change. * Add test case for indexing an expression with never showing existing behavior. * Make indexing an object with never expression result in never. And update baseline to reflect new behavior.
1 parent dcbc478 commit 4fa9605

File tree

5 files changed

+1028
-0
lines changed

5 files changed

+1028
-0
lines changed

src/compiler/checker.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8151,6 +8151,9 @@ namespace ts {
81518151
}
81528152
return indexInfo.type;
81538153
}
8154+
if (indexType.flags & TypeFlags.Never) {
8155+
return neverType;
8156+
}
81548157
if (accessExpression && !isConstEnumObjectType(objectType)) {
81558158
if (noImplicitAny && !compilerOptions.suppressImplicitAnyIndexErrors) {
81568159
if (getIndexTypeOfType(objectType, IndexKind.Number)) {
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
//// [indexingTypesWithNever.ts]
2+
type TestObj = {
3+
a: string;
4+
b: number;
5+
};
6+
7+
// Should be never but without an error
8+
type Result1 = TestObj[never];
9+
10+
type EmptyObj = {};
11+
12+
// Should be never but without an error
13+
type Result2 = EmptyObj[keyof EmptyObj];
14+
15+
declare function genericFn1<T>(obj: T): T[never];
16+
17+
// Should be never
18+
const result3 = genericFn1({ c: "ctest", d: "dtest" });
19+
20+
declare function genericFn2<T extends { [ind: string]: string }>(
21+
obj: T
22+
): T[never];
23+
24+
// Should be never
25+
const result4 = genericFn2({ e: "etest", f: "ftest" });
26+
27+
declare function genericFn3<
28+
T extends { [K in keyof T]: T[K] },
29+
U extends keyof T,
30+
V extends keyof T
31+
>(obj: T, u: U, v: V): T[U & V];
32+
33+
// Should be never
34+
const result5 = genericFn3({ g: "gtest", h: "htest" }, "g", "h"); // 'g' & 'h' will reduce to never
35+
36+
37+
declare const obj: {a: string, b: number}
38+
declare const key: never
39+
40+
const result6 = obj[key]
41+
42+
// Expanded examples from https://github.com/Microsoft/TypeScript/issues/21988
43+
type RequiredPropNames<T> = {
44+
[P in keyof T]-?: undefined extends T[P] ? never : P
45+
}[keyof T];
46+
47+
type OptionalPropNames<T> = {
48+
[P in keyof T]-?: undefined extends T[P] ? P : never
49+
}[keyof T];
50+
51+
type RequiredProps<T> = { [P in RequiredPropNames<T>]: T[P] };
52+
type OptionalProps<T> = { [P in OptionalPropNames<T>]?: T[P] };
53+
54+
type Match<Exp, Act> = [Exp] extends [Act]
55+
? ([Act] extends [Exp] ? "Match" : "Did not match 2")
56+
: "Did not match 1";
57+
58+
type ExpectType<Exp, Act> = Match<Exp, Act> extends "Match"
59+
? ({} extends Exp ? Match<Required<Exp>, Required<Act>> : "Match")
60+
: "Did not match";
61+
62+
type P3 = { a: string; b: number; c?: boolean };
63+
type P2 = { a: string; c?: boolean };
64+
type P1 = { c?: boolean };
65+
type P0 = {};
66+
67+
type P3Names = RequiredPropNames<P3>; // expect 'a' | 'b'
68+
type P2Names = RequiredPropNames<P2>; // expect 'a'
69+
type P1Names = RequiredPropNames<P1>; // expect never
70+
type P0Names = RequiredPropNames<P0>; // expect never
71+
72+
declare const p3NameTest: ExpectType<"a" | "b", P3Names>;
73+
declare const p2NameTest: ExpectType<"a", P2Names>;
74+
declare const p1NameTest: ExpectType<never, P1Names>;
75+
declare const p0NameTest: ExpectType<never, P0Names>;
76+
77+
type P3Props = RequiredProps<P3>; // expect { a: string; b: number }
78+
type P2Props = RequiredProps<P2>; // expect { a: string; }
79+
type P1Props = RequiredProps<P1>; // expect {}
80+
type P0Props = RequiredProps<P0>; // expect {}
81+
82+
declare const p3Test: ExpectType<{ a: string; b: number }, P3Props>;
83+
declare const p2Test: ExpectType<{ a: string }, P2Props>;
84+
declare const p1Test: ExpectType<{}, P1Props>;
85+
declare const p0Test: ExpectType<{}, P0Props>;
86+
87+
type O3 = { a?: string; b?: number; c: boolean };
88+
type O2 = { a?: string; c: boolean };
89+
type O1 = { c: boolean };
90+
type O0 = {};
91+
92+
type O3Names = OptionalPropNames<O3>; // expect 'a' | 'b'
93+
type O2Names = OptionalPropNames<O2>; // expect 'a'
94+
type O1Names = OptionalPropNames<O1>; // expect never
95+
type O0Names = OptionalPropNames<O0>; // expect never
96+
97+
declare const o3NameTest: ExpectType<"a" | "b", O3Names>;
98+
declare const o2NameTest: ExpectType<"a", O2Names>;
99+
declare const o1NameTest: ExpectType<never, O1Names>;
100+
declare const o0NameTest: ExpectType<never, O0Names>;
101+
102+
type O3Props = OptionalProps<O3>; // expect { a?: string | undefined; b?: number | undefined }
103+
type O2Props = OptionalProps<O2>; // expect { a?: string | undefined; }
104+
type O1Props = OptionalProps<O1>; // expect {}
105+
type O0Props = OptionalProps<O0>; // expect {}
106+
107+
declare const o3Test: ExpectType<{ a?: string; b?: number }, O3Props>;
108+
declare const o2Test: ExpectType<{ a?: string }, O2Props>;
109+
declare const o1Test: ExpectType<{}, O1Props>;
110+
declare const o0Test: ExpectType<{}, O0Props>;
111+
112+
113+
//// [indexingTypesWithNever.js]
114+
"use strict";
115+
// Should be never
116+
var result3 = genericFn1({ c: "ctest", d: "dtest" });
117+
// Should be never
118+
var result4 = genericFn2({ e: "etest", f: "ftest" });
119+
// Should be never
120+
var result5 = genericFn3({ g: "gtest", h: "htest" }, "g", "h"); // 'g' & 'h' will reduce to never
121+
var result6 = obj[key];

0 commit comments

Comments
 (0)