Skip to content

Commit 8212c96

Browse files
authored
Workaround for nonnull operator on indexed accesses (#19275)
* Quick and dirty workaround * Add third case to show current behavior * Rename variable, replace elaboration from comment with links
1 parent d8373c3 commit 8212c96

5 files changed

+278
-0
lines changed

src/compiler/checker.ts

+10
Original file line numberDiff line numberDiff line change
@@ -11376,6 +11376,16 @@ namespace ts {
1137611376
}
1137711377

1137811378
function getTypeWithFacts(type: Type, include: TypeFacts) {
11379+
if (type.flags & TypeFlags.IndexedAccess) {
11380+
// TODO (weswig): This is a substitute for a lazy negated type to remove the types indicated by the TypeFacts from the (potential) union the IndexedAccess refers to
11381+
// - See discussion in https://github.com/Microsoft/TypeScript/pull/19275 for details, and test `strictNullNotNullIndexTypeShouldWork` for current behavior
11382+
const baseConstraint = getBaseConstraintOfType(type) || emptyObjectType;
11383+
const result = filterType(baseConstraint, t => (getTypeFacts(t) & include) !== 0);
11384+
if (result !== baseConstraint) {
11385+
return result;
11386+
}
11387+
return type;
11388+
}
1137911389
return filterType(type, t => (getTypeFacts(t) & include) !== 0);
1138011390
}
1138111391

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
//// [strictNullNotNullIndexTypeShouldWork.ts]
2+
interface A {
3+
params?: { name: string; };
4+
}
5+
6+
class Test<T extends A> {
7+
attrs: Readonly<T>;
8+
9+
m() {
10+
this.attrs.params!.name;
11+
}
12+
}
13+
14+
interface Foo {
15+
foo?: number;
16+
}
17+
18+
class FooClass<P extends Foo = Foo> {
19+
properties: Readonly<P>;
20+
21+
foo(): number {
22+
const { foo = 42 } = this.properties;
23+
return foo;
24+
}
25+
}
26+
27+
class Test2<T extends A> {
28+
attrs: Readonly<T>;
29+
30+
m() {
31+
return this.attrs.params!; // Return type should maintain relationship with `T` after being not-null-asserted, ideally
32+
}
33+
}
34+
35+
//// [strictNullNotNullIndexTypeShouldWork.js]
36+
var Test = /** @class */ (function () {
37+
function Test() {
38+
}
39+
Test.prototype.m = function () {
40+
this.attrs.params.name;
41+
};
42+
return Test;
43+
}());
44+
var FooClass = /** @class */ (function () {
45+
function FooClass() {
46+
}
47+
FooClass.prototype.foo = function () {
48+
var _a = this.properties.foo, foo = _a === void 0 ? 42 : _a;
49+
return foo;
50+
};
51+
return FooClass;
52+
}());
53+
var Test2 = /** @class */ (function () {
54+
function Test2() {
55+
}
56+
Test2.prototype.m = function () {
57+
return this.attrs.params; // Return type should maintain relationship with `T` after being not-null-asserted, ideally
58+
};
59+
return Test2;
60+
}());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
=== tests/cases/compiler/strictNullNotNullIndexTypeShouldWork.ts ===
2+
interface A {
3+
>A : Symbol(A, Decl(strictNullNotNullIndexTypeShouldWork.ts, 0, 0))
4+
5+
params?: { name: string; };
6+
>params : Symbol(A.params, Decl(strictNullNotNullIndexTypeShouldWork.ts, 0, 13))
7+
>name : Symbol(name, Decl(strictNullNotNullIndexTypeShouldWork.ts, 1, 14))
8+
}
9+
10+
class Test<T extends A> {
11+
>Test : Symbol(Test, Decl(strictNullNotNullIndexTypeShouldWork.ts, 2, 1))
12+
>T : Symbol(T, Decl(strictNullNotNullIndexTypeShouldWork.ts, 4, 11))
13+
>A : Symbol(A, Decl(strictNullNotNullIndexTypeShouldWork.ts, 0, 0))
14+
15+
attrs: Readonly<T>;
16+
>attrs : Symbol(Test.attrs, Decl(strictNullNotNullIndexTypeShouldWork.ts, 4, 25))
17+
>Readonly : Symbol(Readonly, Decl(lib.d.ts, --, --))
18+
>T : Symbol(T, Decl(strictNullNotNullIndexTypeShouldWork.ts, 4, 11))
19+
20+
m() {
21+
>m : Symbol(Test.m, Decl(strictNullNotNullIndexTypeShouldWork.ts, 5, 23))
22+
23+
this.attrs.params!.name;
24+
>this.attrs.params!.name : Symbol(name, Decl(strictNullNotNullIndexTypeShouldWork.ts, 1, 14))
25+
>this.attrs.params : Symbol(params, Decl(strictNullNotNullIndexTypeShouldWork.ts, 0, 13))
26+
>this.attrs : Symbol(Test.attrs, Decl(strictNullNotNullIndexTypeShouldWork.ts, 4, 25))
27+
>this : Symbol(Test, Decl(strictNullNotNullIndexTypeShouldWork.ts, 2, 1))
28+
>attrs : Symbol(Test.attrs, Decl(strictNullNotNullIndexTypeShouldWork.ts, 4, 25))
29+
>params : Symbol(params, Decl(strictNullNotNullIndexTypeShouldWork.ts, 0, 13))
30+
>name : Symbol(name, Decl(strictNullNotNullIndexTypeShouldWork.ts, 1, 14))
31+
}
32+
}
33+
34+
interface Foo {
35+
>Foo : Symbol(Foo, Decl(strictNullNotNullIndexTypeShouldWork.ts, 10, 1))
36+
37+
foo?: number;
38+
>foo : Symbol(Foo.foo, Decl(strictNullNotNullIndexTypeShouldWork.ts, 12, 15))
39+
}
40+
41+
class FooClass<P extends Foo = Foo> {
42+
>FooClass : Symbol(FooClass, Decl(strictNullNotNullIndexTypeShouldWork.ts, 14, 1))
43+
>P : Symbol(P, Decl(strictNullNotNullIndexTypeShouldWork.ts, 16, 15))
44+
>Foo : Symbol(Foo, Decl(strictNullNotNullIndexTypeShouldWork.ts, 10, 1))
45+
>Foo : Symbol(Foo, Decl(strictNullNotNullIndexTypeShouldWork.ts, 10, 1))
46+
47+
properties: Readonly<P>;
48+
>properties : Symbol(FooClass.properties, Decl(strictNullNotNullIndexTypeShouldWork.ts, 16, 37))
49+
>Readonly : Symbol(Readonly, Decl(lib.d.ts, --, --))
50+
>P : Symbol(P, Decl(strictNullNotNullIndexTypeShouldWork.ts, 16, 15))
51+
52+
foo(): number {
53+
>foo : Symbol(FooClass.foo, Decl(strictNullNotNullIndexTypeShouldWork.ts, 17, 28))
54+
55+
const { foo = 42 } = this.properties;
56+
>foo : Symbol(foo, Decl(strictNullNotNullIndexTypeShouldWork.ts, 20, 15))
57+
>this.properties : Symbol(FooClass.properties, Decl(strictNullNotNullIndexTypeShouldWork.ts, 16, 37))
58+
>this : Symbol(FooClass, Decl(strictNullNotNullIndexTypeShouldWork.ts, 14, 1))
59+
>properties : Symbol(FooClass.properties, Decl(strictNullNotNullIndexTypeShouldWork.ts, 16, 37))
60+
61+
return foo;
62+
>foo : Symbol(foo, Decl(strictNullNotNullIndexTypeShouldWork.ts, 20, 15))
63+
}
64+
}
65+
66+
class Test2<T extends A> {
67+
>Test2 : Symbol(Test2, Decl(strictNullNotNullIndexTypeShouldWork.ts, 23, 1))
68+
>T : Symbol(T, Decl(strictNullNotNullIndexTypeShouldWork.ts, 25, 12))
69+
>A : Symbol(A, Decl(strictNullNotNullIndexTypeShouldWork.ts, 0, 0))
70+
71+
attrs: Readonly<T>;
72+
>attrs : Symbol(Test2.attrs, Decl(strictNullNotNullIndexTypeShouldWork.ts, 25, 26))
73+
>Readonly : Symbol(Readonly, Decl(lib.d.ts, --, --))
74+
>T : Symbol(T, Decl(strictNullNotNullIndexTypeShouldWork.ts, 25, 12))
75+
76+
m() {
77+
>m : Symbol(Test2.m, Decl(strictNullNotNullIndexTypeShouldWork.ts, 26, 23))
78+
79+
return this.attrs.params!; // Return type should maintain relationship with `T` after being not-null-asserted, ideally
80+
>this.attrs.params : Symbol(params, Decl(strictNullNotNullIndexTypeShouldWork.ts, 0, 13))
81+
>this.attrs : Symbol(Test2.attrs, Decl(strictNullNotNullIndexTypeShouldWork.ts, 25, 26))
82+
>this : Symbol(Test2, Decl(strictNullNotNullIndexTypeShouldWork.ts, 23, 1))
83+
>attrs : Symbol(Test2.attrs, Decl(strictNullNotNullIndexTypeShouldWork.ts, 25, 26))
84+
>params : Symbol(params, Decl(strictNullNotNullIndexTypeShouldWork.ts, 0, 13))
85+
}
86+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
=== tests/cases/compiler/strictNullNotNullIndexTypeShouldWork.ts ===
2+
interface A {
3+
>A : A
4+
5+
params?: { name: string; };
6+
>params : { name: string; } | undefined
7+
>name : string
8+
}
9+
10+
class Test<T extends A> {
11+
>Test : Test<T>
12+
>T : T
13+
>A : A
14+
15+
attrs: Readonly<T>;
16+
>attrs : Readonly<T>
17+
>Readonly : Readonly<T>
18+
>T : T
19+
20+
m() {
21+
>m : () => void
22+
23+
this.attrs.params!.name;
24+
>this.attrs.params!.name : string
25+
>this.attrs.params! : { name: string; }
26+
>this.attrs.params : T["params"]
27+
>this.attrs : Readonly<T>
28+
>this : this
29+
>attrs : Readonly<T>
30+
>params : T["params"]
31+
>name : string
32+
}
33+
}
34+
35+
interface Foo {
36+
>Foo : Foo
37+
38+
foo?: number;
39+
>foo : number | undefined
40+
}
41+
42+
class FooClass<P extends Foo = Foo> {
43+
>FooClass : FooClass<P>
44+
>P : P
45+
>Foo : Foo
46+
>Foo : Foo
47+
48+
properties: Readonly<P>;
49+
>properties : Readonly<P>
50+
>Readonly : Readonly<T>
51+
>P : P
52+
53+
foo(): number {
54+
>foo : () => number
55+
56+
const { foo = 42 } = this.properties;
57+
>foo : number
58+
>42 : 42
59+
>this.properties : Readonly<P>
60+
>this : this
61+
>properties : Readonly<P>
62+
63+
return foo;
64+
>foo : number
65+
}
66+
}
67+
68+
class Test2<T extends A> {
69+
>Test2 : Test2<T>
70+
>T : T
71+
>A : A
72+
73+
attrs: Readonly<T>;
74+
>attrs : Readonly<T>
75+
>Readonly : Readonly<T>
76+
>T : T
77+
78+
m() {
79+
>m : () => { name: string; }
80+
81+
return this.attrs.params!; // Return type should maintain relationship with `T` after being not-null-asserted, ideally
82+
>this.attrs.params! : { name: string; }
83+
>this.attrs.params : T["params"]
84+
>this.attrs : Readonly<T>
85+
>this : this
86+
>attrs : Readonly<T>
87+
>params : T["params"]
88+
}
89+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// @strictNullChecks: true
2+
interface A {
3+
params?: { name: string; };
4+
}
5+
6+
class Test<T extends A> {
7+
attrs: Readonly<T>;
8+
9+
m() {
10+
this.attrs.params!.name;
11+
}
12+
}
13+
14+
interface Foo {
15+
foo?: number;
16+
}
17+
18+
class FooClass<P extends Foo = Foo> {
19+
properties: Readonly<P>;
20+
21+
foo(): number {
22+
const { foo = 42 } = this.properties;
23+
return foo;
24+
}
25+
}
26+
27+
class Test2<T extends A> {
28+
attrs: Readonly<T>;
29+
30+
m() {
31+
return this.attrs.params!; // Return type should maintain relationship with `T` after being not-null-asserted, ideally
32+
}
33+
}

0 commit comments

Comments
 (0)