From f53fad6cf32efcd44b28bd83237ce76796b01da7 Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Tue, 9 Oct 2018 14:21:16 +0100 Subject: [PATCH] Allow nullable discriminants --- src/compiler/checker.ts | 22 +- .../reference/nullableDiscriminant.errors.txt | 118 ++++++++ .../reference/nullableDiscriminant.js | 154 ++++++++++ .../reference/nullableDiscriminant.symbols | 276 ++++++++++++++++++ .../reference/nullableDiscriminant.types | 265 +++++++++++++++++ .../objectLiteralNormalization.errors.txt | 10 +- tests/cases/compiler/nullableDiscriminant.ts | 106 +++++++ 7 files changed, 944 insertions(+), 7 deletions(-) create mode 100644 tests/baselines/reference/nullableDiscriminant.errors.txt create mode 100644 tests/baselines/reference/nullableDiscriminant.js create mode 100644 tests/baselines/reference/nullableDiscriminant.symbols create mode 100644 tests/baselines/reference/nullableDiscriminant.types create mode 100644 tests/cases/compiler/nullableDiscriminant.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index fe2ce42652af4..afa7181c2eda2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -12919,6 +12919,24 @@ namespace ts { isUnitType(type); } + function isNullableDiscriminable(type: Type): boolean { + if (type.flags & TypeFlags.Union) { + let hasDiscriminant = false; + for (const current of (type).types) { + if (current.flags & TypeFlags.Nullable) { + hasDiscriminant = true; + continue; + } + else if (current.flags & (TypeFlags.DisjointDomains | TypeFlags.Object)) { + continue; + } + return false; + } + return hasDiscriminant; + } + return false; + } + function getBaseTypeOfLiteralType(type: Type): Type { return type.flags & TypeFlags.EnumLiteral ? getBaseTypeOfEnumLiteralType(type) : type.flags & TypeFlags.StringLiteral ? stringType : @@ -14217,7 +14235,9 @@ namespace ts { const prop = getUnionOrIntersectionProperty(type, name); if (prop && getCheckFlags(prop) & CheckFlags.SyntheticProperty) { if ((prop).isDiscriminantProperty === undefined) { - (prop).isDiscriminantProperty = !!((prop).checkFlags & CheckFlags.HasNonUniformType) && isLiteralType(getTypeOfSymbol(prop)); + let propType: Type; + (prop).isDiscriminantProperty = !!((prop).checkFlags & CheckFlags.HasNonUniformType) && + (propType = getTypeOfSymbol(prop), (isLiteralType(propType) || isNullableDiscriminable(propType))); } return !!(prop).isDiscriminantProperty; } diff --git a/tests/baselines/reference/nullableDiscriminant.errors.txt b/tests/baselines/reference/nullableDiscriminant.errors.txt new file mode 100644 index 0000000000000..98da064e6bc9c --- /dev/null +++ b/tests/baselines/reference/nullableDiscriminant.errors.txt @@ -0,0 +1,118 @@ +tests/cases/compiler/nullableDiscriminant.ts(62,25): error TS2339: Property 'message' does not exist on type 'ErrorOrSuccess'. + Property 'message' does not exist on type '{ value: T; }'. +tests/cases/compiler/nullableDiscriminant.ts(90,13): error TS2339: Property 'bar' does not exist on type 'A | C'. + Property 'bar' does not exist on type 'C'. + + +==== tests/cases/compiler/nullableDiscriminant.ts (2 errors) ==== + // Repro. from #24193 + + interface WithError { + error: Error + data: null + } + + interface WithoutError { + error: null + data: Data + } + + type DataCarrier = WithError | WithoutError + + function test(carrier: DataCarrier) { + if (carrier.error === null) { + const error: null = carrier.error + const data: Data = carrier.data + } + else { + const error: Error = carrier.error + const data: null = carrier.data + } + } + + // Repro. from #24479 + + export interface Errored { + error: Error + value: null + } + + export interface Succeeded { + error: null + value: Value + } + + type Result = Succeeded | Errored; + + declare function getVal(x :T): Result + + let x = getVal("hello"); + + if (x.error === null) { + x.value.toUpperCase(); + } + + type ErrorOrSuccess = | { value: null, message: string } | { value: T }; + + declare function getErrorOrSuccess(x :T): ErrorOrSuccess + let y = getErrorOrSuccess({y: "foo"}); + + if(y.value === null) { + // ok + "message: " + y.message; + } + + function genericNarrow(x: T): true { + let y = getErrorOrSuccess(x); + if(y.value === null) { + // not ok because T could be null + "message: " + y.message; + ~~~~~~~ +!!! error TS2339: Property 'message' does not exist on type 'ErrorOrSuccess'. +!!! error TS2339: Property 'message' does not exist on type '{ value: T; }'. + } + return true; + } + + interface A { + f?: number; + bar: string; + } + + interface B { + f: number; + } + + declare const aOrB: A | B; + if (aOrB.f === undefined) { + // ok + aOrB.bar + } + + interface C { + f: null; + baz: string; + } + + declare const aOrBorC: A | B | C; + if (aOrBorC.f == null) { + // not ok + aOrBorC.bar + ~~~ +!!! error TS2339: Property 'bar' does not exist on type 'A | C'. +!!! error TS2339: Property 'bar' does not exist on type 'C'. + } + + if (aOrBorC.f === null) { + // ok + aOrBorC.baz + } + + if (aOrBorC.f === undefined) { + // ok + aOrBorC.bar + } + + + + \ No newline at end of file diff --git a/tests/baselines/reference/nullableDiscriminant.js b/tests/baselines/reference/nullableDiscriminant.js new file mode 100644 index 0000000000000..4c9d2a9fe3bd6 --- /dev/null +++ b/tests/baselines/reference/nullableDiscriminant.js @@ -0,0 +1,154 @@ +//// [nullableDiscriminant.ts] +// Repro. from #24193 + +interface WithError { + error: Error + data: null +} + +interface WithoutError { + error: null + data: Data +} + +type DataCarrier = WithError | WithoutError + +function test(carrier: DataCarrier) { + if (carrier.error === null) { + const error: null = carrier.error + const data: Data = carrier.data + } + else { + const error: Error = carrier.error + const data: null = carrier.data + } +} + +// Repro. from #24479 + +export interface Errored { + error: Error + value: null +} + +export interface Succeeded { + error: null + value: Value +} + +type Result = Succeeded | Errored; + +declare function getVal(x :T): Result + +let x = getVal("hello"); + +if (x.error === null) { + x.value.toUpperCase(); +} + +type ErrorOrSuccess = | { value: null, message: string } | { value: T }; + +declare function getErrorOrSuccess(x :T): ErrorOrSuccess +let y = getErrorOrSuccess({y: "foo"}); + +if(y.value === null) { + // ok + "message: " + y.message; +} + +function genericNarrow(x: T): true { + let y = getErrorOrSuccess(x); + if(y.value === null) { + // not ok because T could be null + "message: " + y.message; + } + return true; +} + +interface A { + f?: number; + bar: string; +} + +interface B { + f: number; +} + +declare const aOrB: A | B; +if (aOrB.f === undefined) { + // ok + aOrB.bar +} + +interface C { + f: null; + baz: string; +} + +declare const aOrBorC: A | B | C; +if (aOrBorC.f == null) { + // not ok + aOrBorC.bar +} + +if (aOrBorC.f === null) { + // ok + aOrBorC.baz +} + +if (aOrBorC.f === undefined) { + // ok + aOrBorC.bar +} + + + + + +//// [nullableDiscriminant.js] +"use strict"; +// Repro. from #24193 +exports.__esModule = true; +function test(carrier) { + if (carrier.error === null) { + var error = carrier.error; + var data = carrier.data; + } + else { + var error = carrier.error; + var data = carrier.data; + } +} +var x = getVal("hello"); +if (x.error === null) { + x.value.toUpperCase(); +} +var y = getErrorOrSuccess({ y: "foo" }); +if (y.value === null) { + // ok + "message: " + y.message; +} +function genericNarrow(x) { + var y = getErrorOrSuccess(x); + if (y.value === null) { + // not ok because T could be null + "message: " + y.message; + } + return true; +} +if (aOrB.f === undefined) { + // ok + aOrB.bar; +} +if (aOrBorC.f == null) { + // not ok + aOrBorC.bar; +} +if (aOrBorC.f === null) { + // ok + aOrBorC.baz; +} +if (aOrBorC.f === undefined) { + // ok + aOrBorC.bar; +} diff --git a/tests/baselines/reference/nullableDiscriminant.symbols b/tests/baselines/reference/nullableDiscriminant.symbols new file mode 100644 index 0000000000000..68cfff7dd6844 --- /dev/null +++ b/tests/baselines/reference/nullableDiscriminant.symbols @@ -0,0 +1,276 @@ +=== tests/cases/compiler/nullableDiscriminant.ts === +// Repro. from #24193 + +interface WithError { +>WithError : Symbol(WithError, Decl(nullableDiscriminant.ts, 0, 0)) + + error: Error +>error : Symbol(WithError.error, Decl(nullableDiscriminant.ts, 2, 21)) +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + + data: null +>data : Symbol(WithError.data, Decl(nullableDiscriminant.ts, 3, 16)) +} + +interface WithoutError { +>WithoutError : Symbol(WithoutError, Decl(nullableDiscriminant.ts, 5, 1)) +>Data : Symbol(Data, Decl(nullableDiscriminant.ts, 7, 23)) + + error: null +>error : Symbol(WithoutError.error, Decl(nullableDiscriminant.ts, 7, 30)) + + data: Data +>data : Symbol(WithoutError.data, Decl(nullableDiscriminant.ts, 8, 15)) +>Data : Symbol(Data, Decl(nullableDiscriminant.ts, 7, 23)) +} + +type DataCarrier = WithError | WithoutError +>DataCarrier : Symbol(DataCarrier, Decl(nullableDiscriminant.ts, 10, 1)) +>Data : Symbol(Data, Decl(nullableDiscriminant.ts, 12, 17)) +>WithError : Symbol(WithError, Decl(nullableDiscriminant.ts, 0, 0)) +>WithoutError : Symbol(WithoutError, Decl(nullableDiscriminant.ts, 5, 1)) +>Data : Symbol(Data, Decl(nullableDiscriminant.ts, 12, 17)) + +function test(carrier: DataCarrier) { +>test : Symbol(test, Decl(nullableDiscriminant.ts, 12, 55)) +>Data : Symbol(Data, Decl(nullableDiscriminant.ts, 14, 14)) +>carrier : Symbol(carrier, Decl(nullableDiscriminant.ts, 14, 20)) +>DataCarrier : Symbol(DataCarrier, Decl(nullableDiscriminant.ts, 10, 1)) +>Data : Symbol(Data, Decl(nullableDiscriminant.ts, 14, 14)) + + if (carrier.error === null) { +>carrier.error : Symbol(error, Decl(nullableDiscriminant.ts, 2, 21), Decl(nullableDiscriminant.ts, 7, 30)) +>carrier : Symbol(carrier, Decl(nullableDiscriminant.ts, 14, 20)) +>error : Symbol(error, Decl(nullableDiscriminant.ts, 2, 21), Decl(nullableDiscriminant.ts, 7, 30)) + + const error: null = carrier.error +>error : Symbol(error, Decl(nullableDiscriminant.ts, 16, 13)) +>carrier.error : Symbol(WithoutError.error, Decl(nullableDiscriminant.ts, 7, 30)) +>carrier : Symbol(carrier, Decl(nullableDiscriminant.ts, 14, 20)) +>error : Symbol(WithoutError.error, Decl(nullableDiscriminant.ts, 7, 30)) + + const data: Data = carrier.data +>data : Symbol(data, Decl(nullableDiscriminant.ts, 17, 13)) +>Data : Symbol(Data, Decl(nullableDiscriminant.ts, 14, 14)) +>carrier.data : Symbol(WithoutError.data, Decl(nullableDiscriminant.ts, 8, 15)) +>carrier : Symbol(carrier, Decl(nullableDiscriminant.ts, 14, 20)) +>data : Symbol(WithoutError.data, Decl(nullableDiscriminant.ts, 8, 15)) + } + else { + const error: Error = carrier.error +>error : Symbol(error, Decl(nullableDiscriminant.ts, 20, 13)) +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>carrier.error : Symbol(WithError.error, Decl(nullableDiscriminant.ts, 2, 21)) +>carrier : Symbol(carrier, Decl(nullableDiscriminant.ts, 14, 20)) +>error : Symbol(WithError.error, Decl(nullableDiscriminant.ts, 2, 21)) + + const data: null = carrier.data +>data : Symbol(data, Decl(nullableDiscriminant.ts, 21, 13)) +>carrier.data : Symbol(WithError.data, Decl(nullableDiscriminant.ts, 3, 16)) +>carrier : Symbol(carrier, Decl(nullableDiscriminant.ts, 14, 20)) +>data : Symbol(WithError.data, Decl(nullableDiscriminant.ts, 3, 16)) + } +} + +// Repro. from #24479 + +export interface Errored { +>Errored : Symbol(Errored, Decl(nullableDiscriminant.ts, 23, 1)) + + error: Error +>error : Symbol(Errored.error, Decl(nullableDiscriminant.ts, 27, 26)) +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + + value: null +>value : Symbol(Errored.value, Decl(nullableDiscriminant.ts, 28, 16)) +} + +export interface Succeeded { +>Succeeded : Symbol(Succeeded, Decl(nullableDiscriminant.ts, 30, 1)) +>Value : Symbol(Value, Decl(nullableDiscriminant.ts, 32, 27)) + + error: null +>error : Symbol(Succeeded.error, Decl(nullableDiscriminant.ts, 32, 35)) + + value: Value +>value : Symbol(Succeeded.value, Decl(nullableDiscriminant.ts, 33, 15)) +>Value : Symbol(Value, Decl(nullableDiscriminant.ts, 32, 27)) +} + +type Result = Succeeded | Errored; +>Result : Symbol(Result, Decl(nullableDiscriminant.ts, 35, 1)) +>T : Symbol(T, Decl(nullableDiscriminant.ts, 37, 12)) +>Succeeded : Symbol(Succeeded, Decl(nullableDiscriminant.ts, 30, 1)) +>T : Symbol(T, Decl(nullableDiscriminant.ts, 37, 12)) +>Errored : Symbol(Errored, Decl(nullableDiscriminant.ts, 23, 1)) + +declare function getVal(x :T): Result +>getVal : Symbol(getVal, Decl(nullableDiscriminant.ts, 37, 40)) +>T : Symbol(T, Decl(nullableDiscriminant.ts, 39, 24)) +>x : Symbol(x, Decl(nullableDiscriminant.ts, 39, 27)) +>T : Symbol(T, Decl(nullableDiscriminant.ts, 39, 24)) +>Result : Symbol(Result, Decl(nullableDiscriminant.ts, 35, 1)) +>T : Symbol(T, Decl(nullableDiscriminant.ts, 39, 24)) + +let x = getVal("hello"); +>x : Symbol(x, Decl(nullableDiscriminant.ts, 41, 3)) +>getVal : Symbol(getVal, Decl(nullableDiscriminant.ts, 37, 40)) + +if (x.error === null) { +>x.error : Symbol(error, Decl(nullableDiscriminant.ts, 27, 26), Decl(nullableDiscriminant.ts, 32, 35)) +>x : Symbol(x, Decl(nullableDiscriminant.ts, 41, 3)) +>error : Symbol(error, Decl(nullableDiscriminant.ts, 27, 26), Decl(nullableDiscriminant.ts, 32, 35)) + + x.value.toUpperCase(); +>x.value.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>x.value : Symbol(Succeeded.value, Decl(nullableDiscriminant.ts, 33, 15)) +>x : Symbol(x, Decl(nullableDiscriminant.ts, 41, 3)) +>value : Symbol(Succeeded.value, Decl(nullableDiscriminant.ts, 33, 15)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +} + +type ErrorOrSuccess = | { value: null, message: string } | { value: T }; +>ErrorOrSuccess : Symbol(ErrorOrSuccess, Decl(nullableDiscriminant.ts, 45, 1)) +>T : Symbol(T, Decl(nullableDiscriminant.ts, 47, 20)) +>value : Symbol(value, Decl(nullableDiscriminant.ts, 47, 28)) +>message : Symbol(message, Decl(nullableDiscriminant.ts, 47, 41)) +>value : Symbol(value, Decl(nullableDiscriminant.ts, 47, 63)) +>T : Symbol(T, Decl(nullableDiscriminant.ts, 47, 20)) + +declare function getErrorOrSuccess(x :T): ErrorOrSuccess +>getErrorOrSuccess : Symbol(getErrorOrSuccess, Decl(nullableDiscriminant.ts, 47, 75)) +>T : Symbol(T, Decl(nullableDiscriminant.ts, 49, 35)) +>x : Symbol(x, Decl(nullableDiscriminant.ts, 49, 38)) +>T : Symbol(T, Decl(nullableDiscriminant.ts, 49, 35)) +>ErrorOrSuccess : Symbol(ErrorOrSuccess, Decl(nullableDiscriminant.ts, 45, 1)) +>T : Symbol(T, Decl(nullableDiscriminant.ts, 49, 35)) + +let y = getErrorOrSuccess({y: "foo"}); +>y : Symbol(y, Decl(nullableDiscriminant.ts, 50, 3)) +>getErrorOrSuccess : Symbol(getErrorOrSuccess, Decl(nullableDiscriminant.ts, 47, 75)) +>y : Symbol(y, Decl(nullableDiscriminant.ts, 50, 27)) + +if(y.value === null) { +>y.value : Symbol(value, Decl(nullableDiscriminant.ts, 47, 28), Decl(nullableDiscriminant.ts, 47, 63)) +>y : Symbol(y, Decl(nullableDiscriminant.ts, 50, 3)) +>value : Symbol(value, Decl(nullableDiscriminant.ts, 47, 28), Decl(nullableDiscriminant.ts, 47, 63)) + + // ok + "message: " + y.message; +>y.message : Symbol(message, Decl(nullableDiscriminant.ts, 47, 41)) +>y : Symbol(y, Decl(nullableDiscriminant.ts, 50, 3)) +>message : Symbol(message, Decl(nullableDiscriminant.ts, 47, 41)) +} + +function genericNarrow(x: T): true { +>genericNarrow : Symbol(genericNarrow, Decl(nullableDiscriminant.ts, 55, 1)) +>T : Symbol(T, Decl(nullableDiscriminant.ts, 57, 23)) +>x : Symbol(x, Decl(nullableDiscriminant.ts, 57, 26)) +>T : Symbol(T, Decl(nullableDiscriminant.ts, 57, 23)) + + let y = getErrorOrSuccess(x); +>y : Symbol(y, Decl(nullableDiscriminant.ts, 58, 7)) +>getErrorOrSuccess : Symbol(getErrorOrSuccess, Decl(nullableDiscriminant.ts, 47, 75)) +>x : Symbol(x, Decl(nullableDiscriminant.ts, 57, 26)) + + if(y.value === null) { +>y.value : Symbol(value, Decl(nullableDiscriminant.ts, 47, 28), Decl(nullableDiscriminant.ts, 47, 63)) +>y : Symbol(y, Decl(nullableDiscriminant.ts, 58, 7)) +>value : Symbol(value, Decl(nullableDiscriminant.ts, 47, 28), Decl(nullableDiscriminant.ts, 47, 63)) + + // not ok because T could be null + "message: " + y.message; +>y : Symbol(y, Decl(nullableDiscriminant.ts, 58, 7)) + } + return true; +} + +interface A { +>A : Symbol(A, Decl(nullableDiscriminant.ts, 64, 1)) + + f?: number; +>f : Symbol(A.f, Decl(nullableDiscriminant.ts, 66, 13)) + + bar: string; +>bar : Symbol(A.bar, Decl(nullableDiscriminant.ts, 67, 15)) +} + +interface B { +>B : Symbol(B, Decl(nullableDiscriminant.ts, 69, 1)) + + f: number; +>f : Symbol(B.f, Decl(nullableDiscriminant.ts, 71, 13)) +} + +declare const aOrB: A | B; +>aOrB : Symbol(aOrB, Decl(nullableDiscriminant.ts, 75, 13)) +>A : Symbol(A, Decl(nullableDiscriminant.ts, 64, 1)) +>B : Symbol(B, Decl(nullableDiscriminant.ts, 69, 1)) + +if (aOrB.f === undefined) { +>aOrB.f : Symbol(f, Decl(nullableDiscriminant.ts, 66, 13), Decl(nullableDiscriminant.ts, 71, 13)) +>aOrB : Symbol(aOrB, Decl(nullableDiscriminant.ts, 75, 13)) +>f : Symbol(f, Decl(nullableDiscriminant.ts, 66, 13), Decl(nullableDiscriminant.ts, 71, 13)) +>undefined : Symbol(undefined) + + // ok + aOrB.bar +>aOrB.bar : Symbol(A.bar, Decl(nullableDiscriminant.ts, 67, 15)) +>aOrB : Symbol(aOrB, Decl(nullableDiscriminant.ts, 75, 13)) +>bar : Symbol(A.bar, Decl(nullableDiscriminant.ts, 67, 15)) +} + +interface C { +>C : Symbol(C, Decl(nullableDiscriminant.ts, 79, 1)) + + f: null; +>f : Symbol(C.f, Decl(nullableDiscriminant.ts, 81, 13)) + + baz: string; +>baz : Symbol(C.baz, Decl(nullableDiscriminant.ts, 82, 12)) +} + +declare const aOrBorC: A | B | C; +>aOrBorC : Symbol(aOrBorC, Decl(nullableDiscriminant.ts, 86, 13)) +>A : Symbol(A, Decl(nullableDiscriminant.ts, 64, 1)) +>B : Symbol(B, Decl(nullableDiscriminant.ts, 69, 1)) +>C : Symbol(C, Decl(nullableDiscriminant.ts, 79, 1)) + +if (aOrBorC.f == null) { +>aOrBorC.f : Symbol(f, Decl(nullableDiscriminant.ts, 66, 13), Decl(nullableDiscriminant.ts, 71, 13), Decl(nullableDiscriminant.ts, 81, 13)) +>aOrBorC : Symbol(aOrBorC, Decl(nullableDiscriminant.ts, 86, 13)) +>f : Symbol(f, Decl(nullableDiscriminant.ts, 66, 13), Decl(nullableDiscriminant.ts, 71, 13), Decl(nullableDiscriminant.ts, 81, 13)) + + // not ok + aOrBorC.bar +>aOrBorC : Symbol(aOrBorC, Decl(nullableDiscriminant.ts, 86, 13)) +} + +if (aOrBorC.f === null) { +>aOrBorC.f : Symbol(f, Decl(nullableDiscriminant.ts, 66, 13), Decl(nullableDiscriminant.ts, 71, 13), Decl(nullableDiscriminant.ts, 81, 13)) +>aOrBorC : Symbol(aOrBorC, Decl(nullableDiscriminant.ts, 86, 13)) +>f : Symbol(f, Decl(nullableDiscriminant.ts, 66, 13), Decl(nullableDiscriminant.ts, 71, 13), Decl(nullableDiscriminant.ts, 81, 13)) + + // ok + aOrBorC.baz +>aOrBorC.baz : Symbol(C.baz, Decl(nullableDiscriminant.ts, 82, 12)) +>aOrBorC : Symbol(aOrBorC, Decl(nullableDiscriminant.ts, 86, 13)) +>baz : Symbol(C.baz, Decl(nullableDiscriminant.ts, 82, 12)) +} + +if (aOrBorC.f === undefined) { +>aOrBorC.f : Symbol(f, Decl(nullableDiscriminant.ts, 66, 13), Decl(nullableDiscriminant.ts, 71, 13), Decl(nullableDiscriminant.ts, 81, 13)) +>aOrBorC : Symbol(aOrBorC, Decl(nullableDiscriminant.ts, 86, 13)) +>f : Symbol(f, Decl(nullableDiscriminant.ts, 66, 13), Decl(nullableDiscriminant.ts, 71, 13), Decl(nullableDiscriminant.ts, 81, 13)) +>undefined : Symbol(undefined) + + // ok + aOrBorC.bar +>aOrBorC.bar : Symbol(A.bar, Decl(nullableDiscriminant.ts, 67, 15)) +>aOrBorC : Symbol(aOrBorC, Decl(nullableDiscriminant.ts, 86, 13)) +>bar : Symbol(A.bar, Decl(nullableDiscriminant.ts, 67, 15)) +} + + + + diff --git a/tests/baselines/reference/nullableDiscriminant.types b/tests/baselines/reference/nullableDiscriminant.types new file mode 100644 index 0000000000000..f84b0a6e9d99f --- /dev/null +++ b/tests/baselines/reference/nullableDiscriminant.types @@ -0,0 +1,265 @@ +=== tests/cases/compiler/nullableDiscriminant.ts === +// Repro. from #24193 + +interface WithError { + error: Error +>error : Error + + data: null +>data : null +>null : null +} + +interface WithoutError { + error: null +>error : null +>null : null + + data: Data +>data : Data +} + +type DataCarrier = WithError | WithoutError +>DataCarrier : DataCarrier + +function test(carrier: DataCarrier) { +>test : (carrier: DataCarrier) => void +>carrier : DataCarrier + + if (carrier.error === null) { +>carrier.error === null : boolean +>carrier.error : Error | null +>carrier : DataCarrier +>error : Error | null +>null : null + + const error: null = carrier.error +>error : null +>null : null +>carrier.error : null +>carrier : WithoutError +>error : null + + const data: Data = carrier.data +>data : Data +>carrier.data : Data +>carrier : WithoutError +>data : Data + } + else { + const error: Error = carrier.error +>error : Error +>carrier.error : Error +>carrier : WithError +>error : Error + + const data: null = carrier.data +>data : null +>null : null +>carrier.data : null +>carrier : WithError +>data : null + } +} + +// Repro. from #24479 + +export interface Errored { + error: Error +>error : Error + + value: null +>value : null +>null : null +} + +export interface Succeeded { + error: null +>error : null +>null : null + + value: Value +>value : Value +} + +type Result = Succeeded | Errored; +>Result : Result + +declare function getVal(x :T): Result +>getVal : (x: T) => Result +>x : T + +let x = getVal("hello"); +>x : Result +>getVal("hello") : Result +>getVal : (x: T) => Result +>"hello" : "hello" + +if (x.error === null) { +>x.error === null : boolean +>x.error : Error | null +>x : Result +>error : Error | null +>null : null + + x.value.toUpperCase(); +>x.value.toUpperCase() : string +>x.value.toUpperCase : () => string +>x.value : string +>x : Succeeded +>value : string +>toUpperCase : () => string +} + +type ErrorOrSuccess = | { value: null, message: string } | { value: T }; +>ErrorOrSuccess : ErrorOrSuccess +>value : null +>null : null +>message : string +>value : T + +declare function getErrorOrSuccess(x :T): ErrorOrSuccess +>getErrorOrSuccess : (x: T) => ErrorOrSuccess +>x : T + +let y = getErrorOrSuccess({y: "foo"}); +>y : ErrorOrSuccess<{ y: string; }> +>getErrorOrSuccess({y: "foo"}) : ErrorOrSuccess<{ y: string; }> +>getErrorOrSuccess : (x: T) => ErrorOrSuccess +>{y: "foo"} : { y: string; } +>y : string +>"foo" : "foo" + +if(y.value === null) { +>y.value === null : boolean +>y.value : { y: string; } | null +>y : ErrorOrSuccess<{ y: string; }> +>value : { y: string; } | null +>null : null + + // ok + "message: " + y.message; +>"message: " + y.message : string +>"message: " : "message: " +>y.message : string +>y : { value: null; message: string; } +>message : string +} + +function genericNarrow(x: T): true { +>genericNarrow : (x: T) => true +>x : T +>true : true + + let y = getErrorOrSuccess(x); +>y : ErrorOrSuccess +>getErrorOrSuccess(x) : ErrorOrSuccess +>getErrorOrSuccess : (x: T) => ErrorOrSuccess +>x : T + + if(y.value === null) { +>y.value === null : boolean +>y.value : T | null +>y : ErrorOrSuccess +>value : T | null +>null : null + + // not ok because T could be null + "message: " + y.message; +>"message: " + y.message : string +>"message: " : "message: " +>y.message : any +>y : ErrorOrSuccess +>message : any + } + return true; +>true : true +} + +interface A { + f?: number; +>f : number | undefined + + bar: string; +>bar : string +} + +interface B { + f: number; +>f : number +} + +declare const aOrB: A | B; +>aOrB : A | B + +if (aOrB.f === undefined) { +>aOrB.f === undefined : boolean +>aOrB.f : number | undefined +>aOrB : A | B +>f : number | undefined +>undefined : undefined + + // ok + aOrB.bar +>aOrB.bar : string +>aOrB : A +>bar : string +} + +interface C { + f: null; +>f : null +>null : null + + baz: string; +>baz : string +} + +declare const aOrBorC: A | B | C; +>aOrBorC : A | B | C + +if (aOrBorC.f == null) { +>aOrBorC.f == null : boolean +>aOrBorC.f : number | null | undefined +>aOrBorC : A | B | C +>f : number | null | undefined +>null : null + + // not ok + aOrBorC.bar +>aOrBorC.bar : any +>aOrBorC : A | C +>bar : any +} + +if (aOrBorC.f === null) { +>aOrBorC.f === null : boolean +>aOrBorC.f : number | null | undefined +>aOrBorC : A | B | C +>f : number | null | undefined +>null : null + + // ok + aOrBorC.baz +>aOrBorC.baz : string +>aOrBorC : C +>baz : string +} + +if (aOrBorC.f === undefined) { +>aOrBorC.f === undefined : boolean +>aOrBorC.f : number | null | undefined +>aOrBorC : A | B | C +>f : number | null | undefined +>undefined : undefined + + // ok + aOrBorC.bar +>aOrBorC.bar : string +>aOrBorC : A +>bar : string +} + + + + diff --git a/tests/baselines/reference/objectLiteralNormalization.errors.txt b/tests/baselines/reference/objectLiteralNormalization.errors.txt index 3cdda885277eb..2a09dd3b4df50 100644 --- a/tests/baselines/reference/objectLiteralNormalization.errors.txt +++ b/tests/baselines/reference/objectLiteralNormalization.errors.txt @@ -10,9 +10,8 @@ tests/cases/conformance/expressions/objectLiterals/objectLiteralNormalization.ts Types of property 'a' are incompatible. Type 'string' is not assignable to type 'undefined'. tests/cases/conformance/expressions/objectLiterals/objectLiteralNormalization.ts(18,1): error TS2322: Type '{ a: number; }' is not assignable to type '{ a: number; b: number; } | { a: string; b?: undefined; } | { a?: undefined; b?: undefined; }'. - Type '{ a: number; }' is not assignable to type '{ a?: undefined; b?: undefined; }'. - Types of property 'a' are incompatible. - Type 'number' is not assignable to type 'undefined'. + Type '{ a: number; }' is not assignable to type '{ a: number; b: number; }'. + Property 'b' is missing in type '{ a: number; }'. ==== tests/cases/conformance/expressions/objectLiterals/objectLiteralNormalization.ts (5 errors) ==== @@ -52,9 +51,8 @@ tests/cases/conformance/expressions/objectLiterals/objectLiteralNormalization.ts a2 = { a: 1 }; // Error ~~ !!! error TS2322: Type '{ a: number; }' is not assignable to type '{ a: number; b: number; } | { a: string; b?: undefined; } | { a?: undefined; b?: undefined; }'. -!!! error TS2322: Type '{ a: number; }' is not assignable to type '{ a?: undefined; b?: undefined; }'. -!!! error TS2322: Types of property 'a' are incompatible. -!!! error TS2322: Type 'number' is not assignable to type 'undefined'. +!!! error TS2322: Type '{ a: number; }' is not assignable to type '{ a: number; b: number; }'. +!!! error TS2322: Property 'b' is missing in type '{ a: number; }'. // Object literals containing spreads are not normalized declare let b1: { a: string, b: string } | { b: string, c: string }; diff --git a/tests/cases/compiler/nullableDiscriminant.ts b/tests/cases/compiler/nullableDiscriminant.ts new file mode 100644 index 0000000000000..f6a10cc156b5f --- /dev/null +++ b/tests/cases/compiler/nullableDiscriminant.ts @@ -0,0 +1,106 @@ +// @strictNullChecks: true + +// Repro. from #24193 + +interface WithError { + error: Error + data: null +} + +interface WithoutError { + error: null + data: Data +} + +type DataCarrier = WithError | WithoutError + +function test(carrier: DataCarrier) { + if (carrier.error === null) { + const error: null = carrier.error + const data: Data = carrier.data + } + else { + const error: Error = carrier.error + const data: null = carrier.data + } +} + +// Repro. from #24479 + +export interface Errored { + error: Error + value: null +} + +export interface Succeeded { + error: null + value: Value +} + +type Result = Succeeded | Errored; + +declare function getVal(x :T): Result + +let x = getVal("hello"); + +if (x.error === null) { + x.value.toUpperCase(); +} + +type ErrorOrSuccess = | { value: null, message: string } | { value: T }; + +declare function getErrorOrSuccess(x :T): ErrorOrSuccess +let y = getErrorOrSuccess({y: "foo"}); + +if(y.value === null) { + // ok + "message: " + y.message; +} + +function genericNarrow(x: T): true { + let y = getErrorOrSuccess(x); + if(y.value === null) { + // not ok because T could be null + "message: " + y.message; + } + return true; +} + +interface A { + f?: number; + bar: string; +} + +interface B { + f: number; +} + +declare const aOrB: A | B; +if (aOrB.f === undefined) { + // ok + aOrB.bar +} + +interface C { + f: null; + baz: string; +} + +declare const aOrBorC: A | B | C; +if (aOrBorC.f == null) { + // not ok + aOrBorC.bar +} + +if (aOrBorC.f === null) { + // ok + aOrBorC.baz +} + +if (aOrBorC.f === undefined) { + // ok + aOrBorC.bar +} + + +