From 8212c962cdc8286ed9067c8446898bb0dc618c92 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Wed, 18 Oct 2017 17:39:05 -0700 Subject: [PATCH] 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 --- src/compiler/checker.ts | 10 +++ .../strictNullNotNullIndexTypeShouldWork.js | 60 +++++++++++++ ...rictNullNotNullIndexTypeShouldWork.symbols | 86 ++++++++++++++++++ ...strictNullNotNullIndexTypeShouldWork.types | 89 +++++++++++++++++++ .../strictNullNotNullIndexTypeShouldWork.ts | 33 +++++++ 5 files changed, 278 insertions(+) create mode 100644 tests/baselines/reference/strictNullNotNullIndexTypeShouldWork.js create mode 100644 tests/baselines/reference/strictNullNotNullIndexTypeShouldWork.symbols create mode 100644 tests/baselines/reference/strictNullNotNullIndexTypeShouldWork.types create mode 100644 tests/cases/compiler/strictNullNotNullIndexTypeShouldWork.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5c4db4b945..09cddc53c1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11376,6 +11376,16 @@ namespace ts { } function getTypeWithFacts(type: Type, include: TypeFacts) { + if (type.flags & TypeFlags.IndexedAccess) { + // 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 + // - See discussion in https://github.com/Microsoft/TypeScript/pull/19275 for details, and test `strictNullNotNullIndexTypeShouldWork` for current behavior + const baseConstraint = getBaseConstraintOfType(type) || emptyObjectType; + const result = filterType(baseConstraint, t => (getTypeFacts(t) & include) !== 0); + if (result !== baseConstraint) { + return result; + } + return type; + } return filterType(type, t => (getTypeFacts(t) & include) !== 0); } diff --git a/tests/baselines/reference/strictNullNotNullIndexTypeShouldWork.js b/tests/baselines/reference/strictNullNotNullIndexTypeShouldWork.js new file mode 100644 index 0000000000..e04bab8003 --- /dev/null +++ b/tests/baselines/reference/strictNullNotNullIndexTypeShouldWork.js @@ -0,0 +1,60 @@ +//// [strictNullNotNullIndexTypeShouldWork.ts] +interface A { + params?: { name: string; }; +} + +class Test { + attrs: Readonly; + + m() { + this.attrs.params!.name; + } +} + +interface Foo { + foo?: number; +} + +class FooClass

{ + properties: Readonly

; + + foo(): number { + const { foo = 42 } = this.properties; + return foo; + } +} + +class Test2 { + attrs: Readonly; + + m() { + return this.attrs.params!; // Return type should maintain relationship with `T` after being not-null-asserted, ideally + } +} + +//// [strictNullNotNullIndexTypeShouldWork.js] +var Test = /** @class */ (function () { + function Test() { + } + Test.prototype.m = function () { + this.attrs.params.name; + }; + return Test; +}()); +var FooClass = /** @class */ (function () { + function FooClass() { + } + FooClass.prototype.foo = function () { + var _a = this.properties.foo, foo = _a === void 0 ? 42 : _a; + return foo; + }; + return FooClass; +}()); +var Test2 = /** @class */ (function () { + function Test2() { + } + Test2.prototype.m = function () { + return this.attrs.params; // Return type should maintain relationship with `T` after being not-null-asserted, ideally + }; + return Test2; +}()); diff --git a/tests/baselines/reference/strictNullNotNullIndexTypeShouldWork.symbols b/tests/baselines/reference/strictNullNotNullIndexTypeShouldWork.symbols new file mode 100644 index 0000000000..bcc1b400be --- /dev/null +++ b/tests/baselines/reference/strictNullNotNullIndexTypeShouldWork.symbols @@ -0,0 +1,86 @@ +=== tests/cases/compiler/strictNullNotNullIndexTypeShouldWork.ts === +interface A { +>A : Symbol(A, Decl(strictNullNotNullIndexTypeShouldWork.ts, 0, 0)) + + params?: { name: string; }; +>params : Symbol(A.params, Decl(strictNullNotNullIndexTypeShouldWork.ts, 0, 13)) +>name : Symbol(name, Decl(strictNullNotNullIndexTypeShouldWork.ts, 1, 14)) +} + +class Test { +>Test : Symbol(Test, Decl(strictNullNotNullIndexTypeShouldWork.ts, 2, 1)) +>T : Symbol(T, Decl(strictNullNotNullIndexTypeShouldWork.ts, 4, 11)) +>A : Symbol(A, Decl(strictNullNotNullIndexTypeShouldWork.ts, 0, 0)) + + attrs: Readonly; +>attrs : Symbol(Test.attrs, Decl(strictNullNotNullIndexTypeShouldWork.ts, 4, 25)) +>Readonly : Symbol(Readonly, Decl(lib.d.ts, --, --)) +>T : Symbol(T, Decl(strictNullNotNullIndexTypeShouldWork.ts, 4, 11)) + + m() { +>m : Symbol(Test.m, Decl(strictNullNotNullIndexTypeShouldWork.ts, 5, 23)) + + this.attrs.params!.name; +>this.attrs.params!.name : Symbol(name, Decl(strictNullNotNullIndexTypeShouldWork.ts, 1, 14)) +>this.attrs.params : Symbol(params, Decl(strictNullNotNullIndexTypeShouldWork.ts, 0, 13)) +>this.attrs : Symbol(Test.attrs, Decl(strictNullNotNullIndexTypeShouldWork.ts, 4, 25)) +>this : Symbol(Test, Decl(strictNullNotNullIndexTypeShouldWork.ts, 2, 1)) +>attrs : Symbol(Test.attrs, Decl(strictNullNotNullIndexTypeShouldWork.ts, 4, 25)) +>params : Symbol(params, Decl(strictNullNotNullIndexTypeShouldWork.ts, 0, 13)) +>name : Symbol(name, Decl(strictNullNotNullIndexTypeShouldWork.ts, 1, 14)) + } +} + +interface Foo { +>Foo : Symbol(Foo, Decl(strictNullNotNullIndexTypeShouldWork.ts, 10, 1)) + + foo?: number; +>foo : Symbol(Foo.foo, Decl(strictNullNotNullIndexTypeShouldWork.ts, 12, 15)) +} + +class FooClass

{ +>FooClass : Symbol(FooClass, Decl(strictNullNotNullIndexTypeShouldWork.ts, 14, 1)) +>P : Symbol(P, Decl(strictNullNotNullIndexTypeShouldWork.ts, 16, 15)) +>Foo : Symbol(Foo, Decl(strictNullNotNullIndexTypeShouldWork.ts, 10, 1)) +>Foo : Symbol(Foo, Decl(strictNullNotNullIndexTypeShouldWork.ts, 10, 1)) + + properties: Readonly

; +>properties : Symbol(FooClass.properties, Decl(strictNullNotNullIndexTypeShouldWork.ts, 16, 37)) +>Readonly : Symbol(Readonly, Decl(lib.d.ts, --, --)) +>P : Symbol(P, Decl(strictNullNotNullIndexTypeShouldWork.ts, 16, 15)) + + foo(): number { +>foo : Symbol(FooClass.foo, Decl(strictNullNotNullIndexTypeShouldWork.ts, 17, 28)) + + const { foo = 42 } = this.properties; +>foo : Symbol(foo, Decl(strictNullNotNullIndexTypeShouldWork.ts, 20, 15)) +>this.properties : Symbol(FooClass.properties, Decl(strictNullNotNullIndexTypeShouldWork.ts, 16, 37)) +>this : Symbol(FooClass, Decl(strictNullNotNullIndexTypeShouldWork.ts, 14, 1)) +>properties : Symbol(FooClass.properties, Decl(strictNullNotNullIndexTypeShouldWork.ts, 16, 37)) + + return foo; +>foo : Symbol(foo, Decl(strictNullNotNullIndexTypeShouldWork.ts, 20, 15)) + } +} + +class Test2 { +>Test2 : Symbol(Test2, Decl(strictNullNotNullIndexTypeShouldWork.ts, 23, 1)) +>T : Symbol(T, Decl(strictNullNotNullIndexTypeShouldWork.ts, 25, 12)) +>A : Symbol(A, Decl(strictNullNotNullIndexTypeShouldWork.ts, 0, 0)) + + attrs: Readonly; +>attrs : Symbol(Test2.attrs, Decl(strictNullNotNullIndexTypeShouldWork.ts, 25, 26)) +>Readonly : Symbol(Readonly, Decl(lib.d.ts, --, --)) +>T : Symbol(T, Decl(strictNullNotNullIndexTypeShouldWork.ts, 25, 12)) + + m() { +>m : Symbol(Test2.m, Decl(strictNullNotNullIndexTypeShouldWork.ts, 26, 23)) + + return this.attrs.params!; // Return type should maintain relationship with `T` after being not-null-asserted, ideally +>this.attrs.params : Symbol(params, Decl(strictNullNotNullIndexTypeShouldWork.ts, 0, 13)) +>this.attrs : Symbol(Test2.attrs, Decl(strictNullNotNullIndexTypeShouldWork.ts, 25, 26)) +>this : Symbol(Test2, Decl(strictNullNotNullIndexTypeShouldWork.ts, 23, 1)) +>attrs : Symbol(Test2.attrs, Decl(strictNullNotNullIndexTypeShouldWork.ts, 25, 26)) +>params : Symbol(params, Decl(strictNullNotNullIndexTypeShouldWork.ts, 0, 13)) + } +} diff --git a/tests/baselines/reference/strictNullNotNullIndexTypeShouldWork.types b/tests/baselines/reference/strictNullNotNullIndexTypeShouldWork.types new file mode 100644 index 0000000000..76515fbdd5 --- /dev/null +++ b/tests/baselines/reference/strictNullNotNullIndexTypeShouldWork.types @@ -0,0 +1,89 @@ +=== tests/cases/compiler/strictNullNotNullIndexTypeShouldWork.ts === +interface A { +>A : A + + params?: { name: string; }; +>params : { name: string; } | undefined +>name : string +} + +class Test { +>Test : Test +>T : T +>A : A + + attrs: Readonly; +>attrs : Readonly +>Readonly : Readonly +>T : T + + m() { +>m : () => void + + this.attrs.params!.name; +>this.attrs.params!.name : string +>this.attrs.params! : { name: string; } +>this.attrs.params : T["params"] +>this.attrs : Readonly +>this : this +>attrs : Readonly +>params : T["params"] +>name : string + } +} + +interface Foo { +>Foo : Foo + + foo?: number; +>foo : number | undefined +} + +class FooClass

{ +>FooClass : FooClass

+>P : P +>Foo : Foo +>Foo : Foo + + properties: Readonly

; +>properties : Readonly

+>Readonly : Readonly +>P : P + + foo(): number { +>foo : () => number + + const { foo = 42 } = this.properties; +>foo : number +>42 : 42 +>this.properties : Readonly

+>this : this +>properties : Readonly

+ + return foo; +>foo : number + } +} + +class Test2 { +>Test2 : Test2 +>T : T +>A : A + + attrs: Readonly; +>attrs : Readonly +>Readonly : Readonly +>T : T + + m() { +>m : () => { name: string; } + + return this.attrs.params!; // Return type should maintain relationship with `T` after being not-null-asserted, ideally +>this.attrs.params! : { name: string; } +>this.attrs.params : T["params"] +>this.attrs : Readonly +>this : this +>attrs : Readonly +>params : T["params"] + } +} diff --git a/tests/cases/compiler/strictNullNotNullIndexTypeShouldWork.ts b/tests/cases/compiler/strictNullNotNullIndexTypeShouldWork.ts new file mode 100644 index 0000000000..954eaee454 --- /dev/null +++ b/tests/cases/compiler/strictNullNotNullIndexTypeShouldWork.ts @@ -0,0 +1,33 @@ +// @strictNullChecks: true +interface A { + params?: { name: string; }; +} + +class Test { + attrs: Readonly; + + m() { + this.attrs.params!.name; + } +} + +interface Foo { + foo?: number; +} + +class FooClass

{ + properties: Readonly

; + + foo(): number { + const { foo = 42 } = this.properties; + return foo; + } +} + +class Test2 { + attrs: Readonly; + + m() { + return this.attrs.params!; // Return type should maintain relationship with `T` after being not-null-asserted, ideally + } +} \ No newline at end of file