From d0698751ce06814d740b22511c363d2a88a4a1bc Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 9 Oct 2018 18:41:58 -0700 Subject: [PATCH 1/8] Allow discriminant property to contain some non-unit types --- src/compiler/checker.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index fe2ce42652af4..9578f7a106b2a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -12919,6 +12919,10 @@ namespace ts { isUnitType(type); } + function maybeUnitType(type: Type): boolean { + return type.flags & TypeFlags.Union ? some((type).types, isUnitType) : isUnitType(type); + } + function getBaseTypeOfLiteralType(type: Type): Type { return type.flags & TypeFlags.EnumLiteral ? getBaseTypeOfEnumLiteralType(type) : type.flags & TypeFlags.StringLiteral ? stringType : @@ -14217,7 +14221,7 @@ 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)); + (prop).isDiscriminantProperty = !!((prop).checkFlags & CheckFlags.HasNonUniformType) && maybeUnitType(getTypeOfSymbol(prop)); } return !!(prop).isDiscriminantProperty; } From c110f57deb0c423c4e160ab5bce74c35187cb039 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 10 Oct 2018 15:11:35 -0700 Subject: [PATCH 2/8] Discriminant must include at least one unit type and no instantiable types --- src/compiler/checker.ts | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9578f7a106b2a..795b893f69eba 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -12919,10 +12919,6 @@ namespace ts { isUnitType(type); } - function maybeUnitType(type: Type): boolean { - return type.flags & TypeFlags.Union ? some((type).types, isUnitType) : isUnitType(type); - } - function getBaseTypeOfLiteralType(type: Type): Type { return type.flags & TypeFlags.EnumLiteral ? getBaseTypeOfEnumLiteralType(type) : type.flags & TypeFlags.StringLiteral ? stringType : @@ -14216,12 +14212,26 @@ namespace ts { return undefined; } + function isDiscriminantType(type: Type): boolean { + if (type.flags & TypeFlags.Union) { + if (type.flags & (TypeFlags.Boolean | TypeFlags.EnumLiteral)) { + return true; + } + let combined = 0; + for (const t of (type).types) combined |= t.flags; + if (combined & TypeFlags.Unit && !(combined & TypeFlags.Instantiable)) { + return true; + } + } + return false; + } + function isDiscriminantProperty(type: Type | undefined, name: __String) { if (type && type.flags & TypeFlags.Union) { const prop = getUnionOrIntersectionProperty(type, name); if (prop && getCheckFlags(prop) & CheckFlags.SyntheticProperty) { if ((prop).isDiscriminantProperty === undefined) { - (prop).isDiscriminantProperty = !!((prop).checkFlags & CheckFlags.HasNonUniformType) && maybeUnitType(getTypeOfSymbol(prop)); + (prop).isDiscriminantProperty = !!((prop).checkFlags & CheckFlags.HasNonUniformType) && isDiscriminantType(getTypeOfSymbol(prop)); } return !!(prop).isDiscriminantProperty; } From 7737cd5f1fe07d47b00b1f5e189a73e060c78c19 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 10 Oct 2018 15:11:57 -0700 Subject: [PATCH 3/8] Accept new baselines --- .../reference/objectLiteralNormalization.errors.txt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) 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 }; From 22907bfb0701284f83fea13b9050c2c95ec63924 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 10 Oct 2018 15:40:12 -0700 Subject: [PATCH 4/8] Add tests --- .../types/union/discriminatedUnionTypes2.ts | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 tests/cases/conformance/types/union/discriminatedUnionTypes2.ts diff --git a/tests/cases/conformance/types/union/discriminatedUnionTypes2.ts b/tests/cases/conformance/types/union/discriminatedUnionTypes2.ts new file mode 100644 index 0000000000000..789bc263c7ebc --- /dev/null +++ b/tests/cases/conformance/types/union/discriminatedUnionTypes2.ts @@ -0,0 +1,74 @@ +// @strict: true + +function f10(x : { kind: false, a: string } | { kind: true, b: string } | { kind: string, c: string }) { + if (x.kind === false) { + x.a; + } + else if (x.kind === true) { + x.b; + } + else { + x.c; + } +} + +function f11(x : { kind: false, a: string } | { kind: true, b: string } | { kind: string, c: string }) { + switch (x.kind) { + case false: + x.a; + break; + case true: + x.b; + break; + default: + x.c; + } +} + +function f13(x: { a: null; b: string } | { a: string, c: number }) { + x = { a: null, b: "foo", c: 4}; // Error +} + +function f14(x: { a: 0; b: string } | { a: T, c: number }) { + if (x.a === 0) { + x.b; // Error + } +} + +type Result = { error?: undefined, value: T } | { error: Error }; + +function f15(x: Result) { + if (!x.error) { + x.value; + } + else { + x.error.message; + } +} + +f15({ value: 10 }); +f15({ error: new Error("boom") }); + +// Repro from #24193 + +interface WithError { + error: Error + data: null +} + +interface WithoutError { + error: null + data: Data +} + +type DataCarrier = WithError | WithoutError + +function f20(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 + } +} From 6cdba6d3516a32e36458ef68e2f785a914ffd91f Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 10 Oct 2018 15:40:19 -0700 Subject: [PATCH 5/8] Accept new baselines --- .../discriminatedUnionTypes2.errors.txt | 86 +++++++ .../reference/discriminatedUnionTypes2.js | 128 ++++++++++ .../discriminatedUnionTypes2.symbols | 227 +++++++++++++++++ .../reference/discriminatedUnionTypes2.types | 241 ++++++++++++++++++ 4 files changed, 682 insertions(+) create mode 100644 tests/baselines/reference/discriminatedUnionTypes2.errors.txt create mode 100644 tests/baselines/reference/discriminatedUnionTypes2.js create mode 100644 tests/baselines/reference/discriminatedUnionTypes2.symbols create mode 100644 tests/baselines/reference/discriminatedUnionTypes2.types diff --git a/tests/baselines/reference/discriminatedUnionTypes2.errors.txt b/tests/baselines/reference/discriminatedUnionTypes2.errors.txt new file mode 100644 index 0000000000000..c50f7a2767aac --- /dev/null +++ b/tests/baselines/reference/discriminatedUnionTypes2.errors.txt @@ -0,0 +1,86 @@ +tests/cases/conformance/types/union/discriminatedUnionTypes2.ts(27,30): error TS2322: Type '{ a: null; b: string; c: number; }' is not assignable to type '{ a: null; b: string; } | { a: string; c: number; }'. + Object literal may only specify known properties, and 'c' does not exist in type '{ a: null; b: string; }'. +tests/cases/conformance/types/union/discriminatedUnionTypes2.ts(32,11): error TS2339: Property 'b' does not exist on type '{ a: 0; b: string; } | { a: T; c: number; }'. + Property 'b' does not exist on type '{ a: T; c: number; }'. + + +==== tests/cases/conformance/types/union/discriminatedUnionTypes2.ts (2 errors) ==== + function f10(x : { kind: false, a: string } | { kind: true, b: string } | { kind: string, c: string }) { + if (x.kind === false) { + x.a; + } + else if (x.kind === true) { + x.b; + } + else { + x.c; + } + } + + function f11(x : { kind: false, a: string } | { kind: true, b: string } | { kind: string, c: string }) { + switch (x.kind) { + case false: + x.a; + break; + case true: + x.b; + break; + default: + x.c; + } + } + + function f13(x: { a: null; b: string } | { a: string, c: number }) { + x = { a: null, b: "foo", c: 4}; // Error + ~~~~ +!!! error TS2322: Type '{ a: null; b: string; c: number; }' is not assignable to type '{ a: null; b: string; } | { a: string; c: number; }'. +!!! error TS2322: Object literal may only specify known properties, and 'c' does not exist in type '{ a: null; b: string; }'. + } + + function f14(x: { a: 0; b: string } | { a: T, c: number }) { + if (x.a === 0) { + x.b; // Error + ~ +!!! error TS2339: Property 'b' does not exist on type '{ a: 0; b: string; } | { a: T; c: number; }'. +!!! error TS2339: Property 'b' does not exist on type '{ a: T; c: number; }'. + } + } + + type Result = { error?: undefined, value: T } | { error: Error }; + + function f15(x: Result) { + if (!x.error) { + x.value; + } + else { + x.error.message; + } + } + + f15({ value: 10 }); + f15({ error: new Error("boom") }); + + // Repro from #24193 + + interface WithError { + error: Error + data: null + } + + interface WithoutError { + error: null + data: Data + } + + type DataCarrier = WithError | WithoutError + + function f20(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 + } + } + \ No newline at end of file diff --git a/tests/baselines/reference/discriminatedUnionTypes2.js b/tests/baselines/reference/discriminatedUnionTypes2.js new file mode 100644 index 0000000000000..e2b5d99ddcd84 --- /dev/null +++ b/tests/baselines/reference/discriminatedUnionTypes2.js @@ -0,0 +1,128 @@ +//// [discriminatedUnionTypes2.ts] +function f10(x : { kind: false, a: string } | { kind: true, b: string } | { kind: string, c: string }) { + if (x.kind === false) { + x.a; + } + else if (x.kind === true) { + x.b; + } + else { + x.c; + } +} + +function f11(x : { kind: false, a: string } | { kind: true, b: string } | { kind: string, c: string }) { + switch (x.kind) { + case false: + x.a; + break; + case true: + x.b; + break; + default: + x.c; + } +} + +function f13(x: { a: null; b: string } | { a: string, c: number }) { + x = { a: null, b: "foo", c: 4}; // Error +} + +function f14(x: { a: 0; b: string } | { a: T, c: number }) { + if (x.a === 0) { + x.b; // Error + } +} + +type Result = { error?: undefined, value: T } | { error: Error }; + +function f15(x: Result) { + if (!x.error) { + x.value; + } + else { + x.error.message; + } +} + +f15({ value: 10 }); +f15({ error: new Error("boom") }); + +// Repro from #24193 + +interface WithError { + error: Error + data: null +} + +interface WithoutError { + error: null + data: Data +} + +type DataCarrier = WithError | WithoutError + +function f20(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 + } +} + + +//// [discriminatedUnionTypes2.js] +"use strict"; +function f10(x) { + if (x.kind === false) { + x.a; + } + else if (x.kind === true) { + x.b; + } + else { + x.c; + } +} +function f11(x) { + switch (x.kind) { + case false: + x.a; + break; + case true: + x.b; + break; + default: + x.c; + } +} +function f13(x) { + x = { a: null, b: "foo", c: 4 }; // Error +} +function f14(x) { + if (x.a === 0) { + x.b; // Error + } +} +function f15(x) { + if (!x.error) { + x.value; + } + else { + x.error.message; + } +} +f15({ value: 10 }); +f15({ error: new Error("boom") }); +function f20(carrier) { + if (carrier.error === null) { + var error = carrier.error; + var data = carrier.data; + } + else { + var error = carrier.error; + var data = carrier.data; + } +} diff --git a/tests/baselines/reference/discriminatedUnionTypes2.symbols b/tests/baselines/reference/discriminatedUnionTypes2.symbols new file mode 100644 index 0000000000000..38cfcb0b23ab4 --- /dev/null +++ b/tests/baselines/reference/discriminatedUnionTypes2.symbols @@ -0,0 +1,227 @@ +=== tests/cases/conformance/types/union/discriminatedUnionTypes2.ts === +function f10(x : { kind: false, a: string } | { kind: true, b: string } | { kind: string, c: string }) { +>f10 : Symbol(f10, Decl(discriminatedUnionTypes2.ts, 0, 0)) +>x : Symbol(x, Decl(discriminatedUnionTypes2.ts, 0, 13)) +>kind : Symbol(kind, Decl(discriminatedUnionTypes2.ts, 0, 18)) +>a : Symbol(a, Decl(discriminatedUnionTypes2.ts, 0, 31)) +>kind : Symbol(kind, Decl(discriminatedUnionTypes2.ts, 0, 47)) +>b : Symbol(b, Decl(discriminatedUnionTypes2.ts, 0, 59)) +>kind : Symbol(kind, Decl(discriminatedUnionTypes2.ts, 0, 75)) +>c : Symbol(c, Decl(discriminatedUnionTypes2.ts, 0, 89)) + + if (x.kind === false) { +>x.kind : Symbol(kind, Decl(discriminatedUnionTypes2.ts, 0, 18), Decl(discriminatedUnionTypes2.ts, 0, 47), Decl(discriminatedUnionTypes2.ts, 0, 75)) +>x : Symbol(x, Decl(discriminatedUnionTypes2.ts, 0, 13)) +>kind : Symbol(kind, Decl(discriminatedUnionTypes2.ts, 0, 18), Decl(discriminatedUnionTypes2.ts, 0, 47), Decl(discriminatedUnionTypes2.ts, 0, 75)) + + x.a; +>x.a : Symbol(a, Decl(discriminatedUnionTypes2.ts, 0, 31)) +>x : Symbol(x, Decl(discriminatedUnionTypes2.ts, 0, 13)) +>a : Symbol(a, Decl(discriminatedUnionTypes2.ts, 0, 31)) + } + else if (x.kind === true) { +>x.kind : Symbol(kind, Decl(discriminatedUnionTypes2.ts, 0, 47), Decl(discriminatedUnionTypes2.ts, 0, 75)) +>x : Symbol(x, Decl(discriminatedUnionTypes2.ts, 0, 13)) +>kind : Symbol(kind, Decl(discriminatedUnionTypes2.ts, 0, 47), Decl(discriminatedUnionTypes2.ts, 0, 75)) + + x.b; +>x.b : Symbol(b, Decl(discriminatedUnionTypes2.ts, 0, 59)) +>x : Symbol(x, Decl(discriminatedUnionTypes2.ts, 0, 13)) +>b : Symbol(b, Decl(discriminatedUnionTypes2.ts, 0, 59)) + } + else { + x.c; +>x.c : Symbol(c, Decl(discriminatedUnionTypes2.ts, 0, 89)) +>x : Symbol(x, Decl(discriminatedUnionTypes2.ts, 0, 13)) +>c : Symbol(c, Decl(discriminatedUnionTypes2.ts, 0, 89)) + } +} + +function f11(x : { kind: false, a: string } | { kind: true, b: string } | { kind: string, c: string }) { +>f11 : Symbol(f11, Decl(discriminatedUnionTypes2.ts, 10, 1)) +>x : Symbol(x, Decl(discriminatedUnionTypes2.ts, 12, 13)) +>kind : Symbol(kind, Decl(discriminatedUnionTypes2.ts, 12, 18)) +>a : Symbol(a, Decl(discriminatedUnionTypes2.ts, 12, 31)) +>kind : Symbol(kind, Decl(discriminatedUnionTypes2.ts, 12, 47)) +>b : Symbol(b, Decl(discriminatedUnionTypes2.ts, 12, 59)) +>kind : Symbol(kind, Decl(discriminatedUnionTypes2.ts, 12, 75)) +>c : Symbol(c, Decl(discriminatedUnionTypes2.ts, 12, 89)) + + switch (x.kind) { +>x.kind : Symbol(kind, Decl(discriminatedUnionTypes2.ts, 12, 18), Decl(discriminatedUnionTypes2.ts, 12, 47), Decl(discriminatedUnionTypes2.ts, 12, 75)) +>x : Symbol(x, Decl(discriminatedUnionTypes2.ts, 12, 13)) +>kind : Symbol(kind, Decl(discriminatedUnionTypes2.ts, 12, 18), Decl(discriminatedUnionTypes2.ts, 12, 47), Decl(discriminatedUnionTypes2.ts, 12, 75)) + + case false: + x.a; +>x.a : Symbol(a, Decl(discriminatedUnionTypes2.ts, 12, 31)) +>x : Symbol(x, Decl(discriminatedUnionTypes2.ts, 12, 13)) +>a : Symbol(a, Decl(discriminatedUnionTypes2.ts, 12, 31)) + + break; + case true: + x.b; +>x.b : Symbol(b, Decl(discriminatedUnionTypes2.ts, 12, 59)) +>x : Symbol(x, Decl(discriminatedUnionTypes2.ts, 12, 13)) +>b : Symbol(b, Decl(discriminatedUnionTypes2.ts, 12, 59)) + + break; + default: + x.c; +>x.c : Symbol(c, Decl(discriminatedUnionTypes2.ts, 12, 89)) +>x : Symbol(x, Decl(discriminatedUnionTypes2.ts, 12, 13)) +>c : Symbol(c, Decl(discriminatedUnionTypes2.ts, 12, 89)) + } +} + +function f13(x: { a: null; b: string } | { a: string, c: number }) { +>f13 : Symbol(f13, Decl(discriminatedUnionTypes2.ts, 23, 1)) +>x : Symbol(x, Decl(discriminatedUnionTypes2.ts, 25, 13)) +>a : Symbol(a, Decl(discriminatedUnionTypes2.ts, 25, 17)) +>b : Symbol(b, Decl(discriminatedUnionTypes2.ts, 25, 26)) +>a : Symbol(a, Decl(discriminatedUnionTypes2.ts, 25, 42)) +>c : Symbol(c, Decl(discriminatedUnionTypes2.ts, 25, 53)) + + x = { a: null, b: "foo", c: 4}; // Error +>x : Symbol(x, Decl(discriminatedUnionTypes2.ts, 25, 13)) +>a : Symbol(a, Decl(discriminatedUnionTypes2.ts, 26, 9)) +>b : Symbol(b, Decl(discriminatedUnionTypes2.ts, 26, 18)) +>c : Symbol(c, Decl(discriminatedUnionTypes2.ts, 26, 28)) +} + +function f14(x: { a: 0; b: string } | { a: T, c: number }) { +>f14 : Symbol(f14, Decl(discriminatedUnionTypes2.ts, 27, 1)) +>T : Symbol(T, Decl(discriminatedUnionTypes2.ts, 29, 13)) +>x : Symbol(x, Decl(discriminatedUnionTypes2.ts, 29, 16)) +>a : Symbol(a, Decl(discriminatedUnionTypes2.ts, 29, 20)) +>b : Symbol(b, Decl(discriminatedUnionTypes2.ts, 29, 26)) +>a : Symbol(a, Decl(discriminatedUnionTypes2.ts, 29, 42)) +>T : Symbol(T, Decl(discriminatedUnionTypes2.ts, 29, 13)) +>c : Symbol(c, Decl(discriminatedUnionTypes2.ts, 29, 48)) + + if (x.a === 0) { +>x.a : Symbol(a, Decl(discriminatedUnionTypes2.ts, 29, 20), Decl(discriminatedUnionTypes2.ts, 29, 42)) +>x : Symbol(x, Decl(discriminatedUnionTypes2.ts, 29, 16)) +>a : Symbol(a, Decl(discriminatedUnionTypes2.ts, 29, 20), Decl(discriminatedUnionTypes2.ts, 29, 42)) + + x.b; // Error +>x : Symbol(x, Decl(discriminatedUnionTypes2.ts, 29, 16)) + } +} + +type Result = { error?: undefined, value: T } | { error: Error }; +>Result : Symbol(Result, Decl(discriminatedUnionTypes2.ts, 33, 1)) +>T : Symbol(T, Decl(discriminatedUnionTypes2.ts, 35, 12)) +>error : Symbol(error, Decl(discriminatedUnionTypes2.ts, 35, 18)) +>value : Symbol(value, Decl(discriminatedUnionTypes2.ts, 35, 37)) +>T : Symbol(T, Decl(discriminatedUnionTypes2.ts, 35, 12)) +>error : Symbol(error, Decl(discriminatedUnionTypes2.ts, 35, 52)) +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + +function f15(x: Result) { +>f15 : Symbol(f15, Decl(discriminatedUnionTypes2.ts, 35, 68)) +>x : Symbol(x, Decl(discriminatedUnionTypes2.ts, 37, 13)) +>Result : Symbol(Result, Decl(discriminatedUnionTypes2.ts, 33, 1)) + + if (!x.error) { +>x.error : Symbol(error, Decl(discriminatedUnionTypes2.ts, 35, 52), Decl(discriminatedUnionTypes2.ts, 35, 18)) +>x : Symbol(x, Decl(discriminatedUnionTypes2.ts, 37, 13)) +>error : Symbol(error, Decl(discriminatedUnionTypes2.ts, 35, 52), Decl(discriminatedUnionTypes2.ts, 35, 18)) + + x.value; +>x.value : Symbol(value, Decl(discriminatedUnionTypes2.ts, 35, 37)) +>x : Symbol(x, Decl(discriminatedUnionTypes2.ts, 37, 13)) +>value : Symbol(value, Decl(discriminatedUnionTypes2.ts, 35, 37)) + } + else { + x.error.message; +>x.error.message : Symbol(Error.message, Decl(lib.es5.d.ts, --, --)) +>x.error : Symbol(error, Decl(discriminatedUnionTypes2.ts, 35, 52)) +>x : Symbol(x, Decl(discriminatedUnionTypes2.ts, 37, 13)) +>error : Symbol(error, Decl(discriminatedUnionTypes2.ts, 35, 52)) +>message : Symbol(Error.message, Decl(lib.es5.d.ts, --, --)) + } +} + +f15({ value: 10 }); +>f15 : Symbol(f15, Decl(discriminatedUnionTypes2.ts, 35, 68)) +>value : Symbol(value, Decl(discriminatedUnionTypes2.ts, 46, 5)) + +f15({ error: new Error("boom") }); +>f15 : Symbol(f15, Decl(discriminatedUnionTypes2.ts, 35, 68)) +>error : Symbol(error, Decl(discriminatedUnionTypes2.ts, 47, 5)) +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + +// Repro from #24193 + +interface WithError { +>WithError : Symbol(WithError, Decl(discriminatedUnionTypes2.ts, 47, 34)) + + error: Error +>error : Symbol(WithError.error, Decl(discriminatedUnionTypes2.ts, 51, 21)) +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + + data: null +>data : Symbol(WithError.data, Decl(discriminatedUnionTypes2.ts, 52, 16)) +} + +interface WithoutError { +>WithoutError : Symbol(WithoutError, Decl(discriminatedUnionTypes2.ts, 54, 1)) +>Data : Symbol(Data, Decl(discriminatedUnionTypes2.ts, 56, 23)) + + error: null +>error : Symbol(WithoutError.error, Decl(discriminatedUnionTypes2.ts, 56, 30)) + + data: Data +>data : Symbol(WithoutError.data, Decl(discriminatedUnionTypes2.ts, 57, 15)) +>Data : Symbol(Data, Decl(discriminatedUnionTypes2.ts, 56, 23)) +} + +type DataCarrier = WithError | WithoutError +>DataCarrier : Symbol(DataCarrier, Decl(discriminatedUnionTypes2.ts, 59, 1)) +>Data : Symbol(Data, Decl(discriminatedUnionTypes2.ts, 61, 17)) +>WithError : Symbol(WithError, Decl(discriminatedUnionTypes2.ts, 47, 34)) +>WithoutError : Symbol(WithoutError, Decl(discriminatedUnionTypes2.ts, 54, 1)) +>Data : Symbol(Data, Decl(discriminatedUnionTypes2.ts, 61, 17)) + +function f20(carrier: DataCarrier) { +>f20 : Symbol(f20, Decl(discriminatedUnionTypes2.ts, 61, 55)) +>Data : Symbol(Data, Decl(discriminatedUnionTypes2.ts, 63, 13)) +>carrier : Symbol(carrier, Decl(discriminatedUnionTypes2.ts, 63, 19)) +>DataCarrier : Symbol(DataCarrier, Decl(discriminatedUnionTypes2.ts, 59, 1)) +>Data : Symbol(Data, Decl(discriminatedUnionTypes2.ts, 63, 13)) + + if (carrier.error === null) { +>carrier.error : Symbol(error, Decl(discriminatedUnionTypes2.ts, 51, 21), Decl(discriminatedUnionTypes2.ts, 56, 30)) +>carrier : Symbol(carrier, Decl(discriminatedUnionTypes2.ts, 63, 19)) +>error : Symbol(error, Decl(discriminatedUnionTypes2.ts, 51, 21), Decl(discriminatedUnionTypes2.ts, 56, 30)) + + const error: null = carrier.error +>error : Symbol(error, Decl(discriminatedUnionTypes2.ts, 65, 13)) +>carrier.error : Symbol(WithoutError.error, Decl(discriminatedUnionTypes2.ts, 56, 30)) +>carrier : Symbol(carrier, Decl(discriminatedUnionTypes2.ts, 63, 19)) +>error : Symbol(WithoutError.error, Decl(discriminatedUnionTypes2.ts, 56, 30)) + + const data: Data = carrier.data +>data : Symbol(data, Decl(discriminatedUnionTypes2.ts, 66, 13)) +>Data : Symbol(Data, Decl(discriminatedUnionTypes2.ts, 63, 13)) +>carrier.data : Symbol(WithoutError.data, Decl(discriminatedUnionTypes2.ts, 57, 15)) +>carrier : Symbol(carrier, Decl(discriminatedUnionTypes2.ts, 63, 19)) +>data : Symbol(WithoutError.data, Decl(discriminatedUnionTypes2.ts, 57, 15)) + + } else { + const error: Error = carrier.error +>error : Symbol(error, Decl(discriminatedUnionTypes2.ts, 68, 13)) +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>carrier.error : Symbol(WithError.error, Decl(discriminatedUnionTypes2.ts, 51, 21)) +>carrier : Symbol(carrier, Decl(discriminatedUnionTypes2.ts, 63, 19)) +>error : Symbol(WithError.error, Decl(discriminatedUnionTypes2.ts, 51, 21)) + + const data: null = carrier.data +>data : Symbol(data, Decl(discriminatedUnionTypes2.ts, 69, 13)) +>carrier.data : Symbol(WithError.data, Decl(discriminatedUnionTypes2.ts, 52, 16)) +>carrier : Symbol(carrier, Decl(discriminatedUnionTypes2.ts, 63, 19)) +>data : Symbol(WithError.data, Decl(discriminatedUnionTypes2.ts, 52, 16)) + } +} + diff --git a/tests/baselines/reference/discriminatedUnionTypes2.types b/tests/baselines/reference/discriminatedUnionTypes2.types new file mode 100644 index 0000000000000..b654fc1a78d5c --- /dev/null +++ b/tests/baselines/reference/discriminatedUnionTypes2.types @@ -0,0 +1,241 @@ +=== tests/cases/conformance/types/union/discriminatedUnionTypes2.ts === +function f10(x : { kind: false, a: string } | { kind: true, b: string } | { kind: string, c: string }) { +>f10 : (x: { kind: false; a: string; } | { kind: true; b: string; } | { kind: string; c: string; }) => void +>x : { kind: false; a: string; } | { kind: true; b: string; } | { kind: string; c: string; } +>kind : false +>false : false +>a : string +>kind : true +>true : true +>b : string +>kind : string +>c : string + + if (x.kind === false) { +>x.kind === false : boolean +>x.kind : string | boolean +>x : { kind: false; a: string; } | { kind: true; b: string; } | { kind: string; c: string; } +>kind : string | boolean +>false : false + + x.a; +>x.a : string +>x : { kind: false; a: string; } +>a : string + } + else if (x.kind === true) { +>x.kind === true : boolean +>x.kind : string | true +>x : { kind: true; b: string; } | { kind: string; c: string; } +>kind : string | true +>true : true + + x.b; +>x.b : string +>x : { kind: true; b: string; } +>b : string + } + else { + x.c; +>x.c : string +>x : { kind: string; c: string; } +>c : string + } +} + +function f11(x : { kind: false, a: string } | { kind: true, b: string } | { kind: string, c: string }) { +>f11 : (x: { kind: false; a: string; } | { kind: true; b: string; } | { kind: string; c: string; }) => void +>x : { kind: false; a: string; } | { kind: true; b: string; } | { kind: string; c: string; } +>kind : false +>false : false +>a : string +>kind : true +>true : true +>b : string +>kind : string +>c : string + + switch (x.kind) { +>x.kind : string | boolean +>x : { kind: false; a: string; } | { kind: true; b: string; } | { kind: string; c: string; } +>kind : string | boolean + + case false: +>false : false + + x.a; +>x.a : string +>x : { kind: false; a: string; } +>a : string + + break; + case true: +>true : true + + x.b; +>x.b : string +>x : { kind: true; b: string; } +>b : string + + break; + default: + x.c; +>x.c : string +>x : { kind: string; c: string; } +>c : string + } +} + +function f13(x: { a: null; b: string } | { a: string, c: number }) { +>f13 : (x: { a: null; b: string; } | { a: string; c: number; }) => void +>x : { a: null; b: string; } | { a: string; c: number; } +>a : null +>null : null +>b : string +>a : string +>c : number + + x = { a: null, b: "foo", c: 4}; // Error +>x = { a: null, b: "foo", c: 4} : { a: null; b: string; c: number; } +>x : { a: null; b: string; } | { a: string; c: number; } +>{ a: null, b: "foo", c: 4} : { a: null; b: string; c: number; } +>a : null +>null : null +>b : string +>"foo" : "foo" +>c : number +>4 : 4 +} + +function f14(x: { a: 0; b: string } | { a: T, c: number }) { +>f14 : (x: { a: 0; b: string; } | { a: T; c: number; }) => void +>x : { a: 0; b: string; } | { a: T; c: number; } +>a : 0 +>b : string +>a : T +>c : number + + if (x.a === 0) { +>x.a === 0 : boolean +>x.a : 0 | T +>x : { a: 0; b: string; } | { a: T; c: number; } +>a : 0 | T +>0 : 0 + + x.b; // Error +>x.b : any +>x : { a: 0; b: string; } | { a: T; c: number; } +>b : any + } +} + +type Result = { error?: undefined, value: T } | { error: Error }; +>Result : Result +>error : undefined +>value : T +>error : Error + +function f15(x: Result) { +>f15 : (x: Result) => void +>x : Result + + if (!x.error) { +>!x.error : boolean +>x.error : Error | undefined +>x : Result +>error : Error | undefined + + x.value; +>x.value : number +>x : { error?: undefined; value: number; } +>value : number + } + else { + x.error.message; +>x.error.message : string +>x.error : Error +>x : { error: Error; } +>error : Error +>message : string + } +} + +f15({ value: 10 }); +>f15({ value: 10 }) : void +>f15 : (x: Result) => void +>{ value: 10 } : { value: number; } +>value : number +>10 : 10 + +f15({ error: new Error("boom") }); +>f15({ error: new Error("boom") }) : void +>f15 : (x: Result) => void +>{ error: new Error("boom") } : { error: Error; } +>error : Error +>new Error("boom") : Error +>Error : ErrorConstructor +>"boom" : "boom" + +// 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 f20(carrier: DataCarrier) { +>f20 : (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 + } +} + From 709f5f2fc47eb9d28798f5933864d05711720dee Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 15 Oct 2018 10:18:54 -0700 Subject: [PATCH 6/8] Handle circular mapped type instantiations for arrays and tuples --- src/compiler/checker.ts | 12 +++++++++++- src/compiler/types.ts | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 795b893f69eba..4d397bcc71cf8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10365,7 +10365,15 @@ namespace ts { if (typeVariable) { const mappedTypeVariable = instantiateType(typeVariable, mapper); if (typeVariable !== mappedTypeVariable) { - return mapType(mappedTypeVariable, t => { + // If we are already in the process of creating an instantiation of this mapped type, + // return the error type. This situation only arises if we are instantiating the mapped + // type for an array or tuple type, as we then need to eagerly resolve the (possibly + // circular) element type(s). + if (type.instantiating) { + return errorType; + } + type.instantiating = true; + const result = mapType(mappedTypeVariable, t => { if (t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object | TypeFlags.Intersection) && t !== wildcardType) { const replacementMapper = createReplacementMapper(typeVariable, t, mapper); return isArrayType(t) ? createArrayType(instantiateMappedTypeTemplate(type, numberType, /*isOptional*/ true, replacementMapper)) : @@ -10375,6 +10383,8 @@ namespace ts { } return t; }); + type.instantiating = false; + return result; } } return instantiateAnonymousType(type, mapper); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 3bd103579f1b3..5763ab3eff0f3 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4074,6 +4074,7 @@ namespace ts { templateType?: Type; modifiersType?: Type; resolvedApparentType?: Type; + instantiating?: boolean; } export interface EvolvingArrayType extends ObjectType { From 0c3221c2205f50ec3f0c069ec919f35b7aebfb5a Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 15 Oct 2018 10:24:00 -0700 Subject: [PATCH 7/8] Add regression test --- .../conformance/types/mapped/recursiveMappedTypes.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/cases/conformance/types/mapped/recursiveMappedTypes.ts b/tests/cases/conformance/types/mapped/recursiveMappedTypes.ts index 7a78ad9dc4a62..188efa2f51a54 100644 --- a/tests/cases/conformance/types/mapped/recursiveMappedTypes.ts +++ b/tests/cases/conformance/types/mapped/recursiveMappedTypes.ts @@ -12,4 +12,13 @@ type Recurse1 = { type Recurse2 = { [K in keyof Recurse1]: Recurse1[K] -} \ No newline at end of file +} + +// Repro from #27881 + +export type Circular = {[P in keyof T]: Circular}; +type tup = [number, number, number, number]; + +function foo(arg: Circular): tup { + return arg; +} From 3930525678e921d3e2cad96e6424b129c7ada089 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 15 Oct 2018 10:24:10 -0700 Subject: [PATCH 8/8] Accept new baselines --- .../reference/recursiveMappedTypes.errors.txt | 12 ++++++++- .../reference/recursiveMappedTypes.js | 27 ++++++++++++------- .../reference/recursiveMappedTypes.symbols | 25 +++++++++++++++++ .../reference/recursiveMappedTypes.types | 17 ++++++++++++ 4 files changed, 71 insertions(+), 10 deletions(-) diff --git a/tests/baselines/reference/recursiveMappedTypes.errors.txt b/tests/baselines/reference/recursiveMappedTypes.errors.txt index 8279f4f60a65a..b702cf7e3169d 100644 --- a/tests/baselines/reference/recursiveMappedTypes.errors.txt +++ b/tests/baselines/reference/recursiveMappedTypes.errors.txt @@ -31,4 +31,14 @@ tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(12,11): error TS231 [K in keyof Recurse1]: Recurse1[K] ~~~~~~~~~~~~~~ !!! error TS2313: Type parameter 'K' has a circular constraint. - } \ No newline at end of file + } + + // Repro from #27881 + + export type Circular = {[P in keyof T]: Circular}; + type tup = [number, number, number, number]; + + function foo(arg: Circular): tup { + return arg; + } + \ No newline at end of file diff --git a/tests/baselines/reference/recursiveMappedTypes.js b/tests/baselines/reference/recursiveMappedTypes.js index 5061070866428..612c9d3fca4e8 100644 --- a/tests/baselines/reference/recursiveMappedTypes.js +++ b/tests/baselines/reference/recursiveMappedTypes.js @@ -11,19 +11,28 @@ type Recurse1 = { type Recurse2 = { [K in keyof Recurse1]: Recurse1[K] -} +} + +// Repro from #27881 + +export type Circular = {[P in keyof T]: Circular}; +type tup = [number, number, number, number]; + +function foo(arg: Circular): tup { + return arg; +} + //// [recursiveMappedTypes.js] +"use strict"; // Recursive mapped types simply appear empty +exports.__esModule = true; +function foo(arg) { + return arg; +} //// [recursiveMappedTypes.d.ts] -declare type Recurse = { - [K in keyof Recurse]: Recurse[K]; -}; -declare type Recurse1 = { - [K in keyof Recurse2]: Recurse2[K]; -}; -declare type Recurse2 = { - [K in keyof Recurse1]: Recurse1[K]; +export declare type Circular = { + [P in keyof T]: Circular; }; diff --git a/tests/baselines/reference/recursiveMappedTypes.symbols b/tests/baselines/reference/recursiveMappedTypes.symbols index 20ebbedc00abb..7c0e2ffaa2341 100644 --- a/tests/baselines/reference/recursiveMappedTypes.symbols +++ b/tests/baselines/reference/recursiveMappedTypes.symbols @@ -30,3 +30,28 @@ type Recurse2 = { >Recurse1 : Symbol(Recurse1, Decl(recursiveMappedTypes.ts, 4, 1)) >K : Symbol(K, Decl(recursiveMappedTypes.ts, 11, 5)) } + +// Repro from #27881 + +export type Circular = {[P in keyof T]: Circular}; +>Circular : Symbol(Circular, Decl(recursiveMappedTypes.ts, 12, 1)) +>T : Symbol(T, Decl(recursiveMappedTypes.ts, 16, 21)) +>P : Symbol(P, Decl(recursiveMappedTypes.ts, 16, 28)) +>T : Symbol(T, Decl(recursiveMappedTypes.ts, 16, 21)) +>Circular : Symbol(Circular, Decl(recursiveMappedTypes.ts, 12, 1)) +>T : Symbol(T, Decl(recursiveMappedTypes.ts, 16, 21)) + +type tup = [number, number, number, number]; +>tup : Symbol(tup, Decl(recursiveMappedTypes.ts, 16, 56)) + +function foo(arg: Circular): tup { +>foo : Symbol(foo, Decl(recursiveMappedTypes.ts, 17, 44)) +>arg : Symbol(arg, Decl(recursiveMappedTypes.ts, 19, 13)) +>Circular : Symbol(Circular, Decl(recursiveMappedTypes.ts, 12, 1)) +>tup : Symbol(tup, Decl(recursiveMappedTypes.ts, 16, 56)) +>tup : Symbol(tup, Decl(recursiveMappedTypes.ts, 16, 56)) + + return arg; +>arg : Symbol(arg, Decl(recursiveMappedTypes.ts, 19, 13)) +} + diff --git a/tests/baselines/reference/recursiveMappedTypes.types b/tests/baselines/reference/recursiveMappedTypes.types index aae3280b60580..5741f711e9d6f 100644 --- a/tests/baselines/reference/recursiveMappedTypes.types +++ b/tests/baselines/reference/recursiveMappedTypes.types @@ -18,3 +18,20 @@ type Recurse2 = { [K in keyof Recurse1]: Recurse1[K] } + +// Repro from #27881 + +export type Circular = {[P in keyof T]: Circular}; +>Circular : Circular + +type tup = [number, number, number, number]; +>tup : [number, number, number, number] + +function foo(arg: Circular): tup { +>foo : (arg: [any, any, any, any]) => [number, number, number, number] +>arg : [any, any, any, any] + + return arg; +>arg : [any, any, any, any] +} +