From 2e68b7614bcab17a0f7d89fb9f513d4cdae997aa Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 22 Jun 2024 14:05:06 +0200 Subject: [PATCH 1/3] Handle partially overlapping discriminants --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b7220fb89543d..4869380761e19 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -24177,7 +24177,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { for (let i = 0; i < types.length; i++) { if (include[i]) { const targetType = getTypeOfPropertyOrIndexSignatureOfType(types[i], propertyName); - if (targetType && related(getDiscriminatingType(), targetType)) { + if (targetType && someType(getDiscriminatingType(), t => !!related(t, targetType))) { matched = true; } else { From c994e9ff702dffd9d91c0e47a79630e884e08c03 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 22 Jun 2024 14:05:25 +0200 Subject: [PATCH 2/3] Accept new baselines --- ...entCompatWithDiscriminatedUnion.errors.txt | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.errors.txt b/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.errors.txt index e815b2a2ccb24..5a09377844cf8 100644 --- a/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.errors.txt +++ b/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.errors.txt @@ -1,13 +1,16 @@ assignmentCompatWithDiscriminatedUnion.ts(44,5): error TS2322: Type 'S' is not assignable to type 'T'. - Type 'S' is not assignable to type '{ a: 0; b: 4 | 1; } | { a: 1; b: 2 | 4; }'. - Type 'S' is not assignable to type '{ a: 1; b: 2 | 4; }'. - Types of property 'a' are incompatible. - Type '0 | 2' is not assignable to type '1'. - Type '0' is not assignable to type '1'. + Type 'S' is not assignable to type '{ a: 0; b: 4 | 1; }'. + Types of property 'a' are incompatible. + Type '0 | 2' is not assignable to type '0'. + Type '2' is not assignable to type '0'. assignmentCompatWithDiscriminatedUnion.ts(58,5): error TS2322: Type 'S' is not assignable to type 'T'. Type 'S' is not assignable to type '{ a: 0; b: 4 | 1; } | { a: 2; b: 4 | 3; c: string; }'. Property 'c' is missing in type 'S' but required in type '{ a: 2; b: 4 | 3; c: string; }'. assignmentCompatWithDiscriminatedUnion.ts(82,5): error TS2322: Type 'S' is not assignable to type 'T'. + Type 'S' is not assignable to type '{ a: N; b: N; c: 2; }'. + Types of property 'c' are incompatible. + Type 'N' is not assignable to type '2'. + Type '0' is not assignable to type '2'. ==== assignmentCompatWithDiscriminatedUnion.ts (3 errors) ==== @@ -57,11 +60,10 @@ assignmentCompatWithDiscriminatedUnion.ts(82,5): error TS2322: Type 'S' is not a t = s; ~ !!! error TS2322: Type 'S' is not assignable to type 'T'. -!!! error TS2322: Type 'S' is not assignable to type '{ a: 0; b: 4 | 1; } | { a: 1; b: 2 | 4; }'. -!!! error TS2322: Type 'S' is not assignable to type '{ a: 1; b: 2 | 4; }'. -!!! error TS2322: Types of property 'a' are incompatible. -!!! error TS2322: Type '0 | 2' is not assignable to type '1'. -!!! error TS2322: Type '0' is not assignable to type '1'. +!!! error TS2322: Type 'S' is not assignable to type '{ a: 0; b: 4 | 1; }'. +!!! error TS2322: Types of property 'a' are incompatible. +!!! error TS2322: Type '0 | 2' is not assignable to type '0'. +!!! error TS2322: Type '2' is not assignable to type '0'. } // Unmatched non-discriminants @@ -107,6 +109,10 @@ assignmentCompatWithDiscriminatedUnion.ts(82,5): error TS2322: Type 'S' is not a t = s; ~ !!! error TS2322: Type 'S' is not assignable to type 'T'. +!!! error TS2322: Type 'S' is not assignable to type '{ a: N; b: N; c: 2; }'. +!!! error TS2322: Types of property 'c' are incompatible. +!!! error TS2322: Type 'N' is not assignable to type '2'. +!!! error TS2322: Type '0' is not assignable to type '2'. } // https://github.com/Microsoft/TypeScript/issues/14865 From 559d8e7fc25ef14c3c9f1cc82dc7ea176fda555b Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 23 Jun 2024 09:43:40 +0200 Subject: [PATCH 3/3] Add tests --- ...entCompatWithDiscriminatedUnion.errors.txt | 24 ++++ .../assignmentCompatWithDiscriminatedUnion.js | 41 ++++++ ...gnmentCompatWithDiscriminatedUnion.symbols | 80 +++++++++++ ...signmentCompatWithDiscriminatedUnion.types | 126 ++++++++++++++++++ .../assignmentCompatWithDiscriminatedUnion.ts | 24 ++++ 5 files changed, 295 insertions(+) diff --git a/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.errors.txt b/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.errors.txt index 5a09377844cf8..91f79a95e46a5 100644 --- a/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.errors.txt +++ b/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.errors.txt @@ -232,4 +232,28 @@ assignmentCompatWithDiscriminatedUnion.ts(82,5): error TS2322: Type 'S' is not a declare const b: B; const a: A = b === "a" || b === "b" ? [b, 1] : ["c", ""]; } + + // https://github.com/microsoft/TypeScript/issues/58603 + namespace GH58603 { + enum MyEnum { A = 1, B = 2 } + + type TypeA = { kind: MyEnum.A, id?: number }; + + type TypeB = { kind: MyEnum.B } & ({ id?: undefined } | { id: number }); + + type MyType = TypeA | TypeB; + + function something(a: MyType): void {} + + function indirect(kind: MyEnum, id?: number): void { + something({ kind, id }); + } + + type Foo = { kind: "a" | "b", value: number } | { kind: "a", value: undefined } | { kind: "b", value: undefined }; + + function test(obj: { kind: "a" | "b", value: number | undefined }) { + let x1: Foo = obj; + let x2: Foo = { kind: obj.kind, value: obj.value }; + } + } \ No newline at end of file diff --git a/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.js b/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.js index 5a2635075402a..97fb4a9d2b1a6 100644 --- a/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.js +++ b/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.js @@ -202,6 +202,30 @@ namespace GH39357 { declare const b: B; const a: A = b === "a" || b === "b" ? [b, 1] : ["c", ""]; } + +// https://github.com/microsoft/TypeScript/issues/58603 +namespace GH58603 { + enum MyEnum { A = 1, B = 2 } + + type TypeA = { kind: MyEnum.A, id?: number }; + + type TypeB = { kind: MyEnum.B } & ({ id?: undefined } | { id: number }); + + type MyType = TypeA | TypeB; + + function something(a: MyType): void {} + + function indirect(kind: MyEnum, id?: number): void { + something({ kind, id }); + } + + type Foo = { kind: "a" | "b", value: number } | { kind: "a", value: undefined } | { kind: "b", value: undefined }; + + function test(obj: { kind: "a" | "b", value: number | undefined }) { + let x1: Foo = obj; + let x2: Foo = { kind: obj.kind, value: obj.value }; + } +} //// [assignmentCompatWithDiscriminatedUnion.js] @@ -305,3 +329,20 @@ var GH39357; (function (GH39357) { var a = b === "a" || b === "b" ? [b, 1] : ["c", ""]; })(GH39357 || (GH39357 = {})); +// https://github.com/microsoft/TypeScript/issues/58603 +var GH58603; +(function (GH58603) { + var MyEnum; + (function (MyEnum) { + MyEnum[MyEnum["A"] = 1] = "A"; + MyEnum[MyEnum["B"] = 2] = "B"; + })(MyEnum || (MyEnum = {})); + function something(a) { } + function indirect(kind, id) { + something({ kind: kind, id: id }); + } + function test(obj) { + var x1 = obj; + var x2 = { kind: obj.kind, value: obj.value }; + } +})(GH58603 || (GH58603 = {})); diff --git a/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.symbols b/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.symbols index 926f52af3e0d1..e52ea8f834394 100644 --- a/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.symbols +++ b/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.symbols @@ -517,3 +517,83 @@ namespace GH39357 { >b : Symbol(b, Decl(assignmentCompatWithDiscriminatedUnion.ts, 198, 17)) } +// https://github.com/microsoft/TypeScript/issues/58603 +namespace GH58603 { +>GH58603 : Symbol(GH58603, Decl(assignmentCompatWithDiscriminatedUnion.ts, 200, 1)) + + enum MyEnum { A = 1, B = 2 } +>MyEnum : Symbol(MyEnum, Decl(assignmentCompatWithDiscriminatedUnion.ts, 203, 19)) +>A : Symbol(MyEnum.A, Decl(assignmentCompatWithDiscriminatedUnion.ts, 204, 17)) +>B : Symbol(MyEnum.B, Decl(assignmentCompatWithDiscriminatedUnion.ts, 204, 24)) + + type TypeA = { kind: MyEnum.A, id?: number }; +>TypeA : Symbol(TypeA, Decl(assignmentCompatWithDiscriminatedUnion.ts, 204, 32)) +>kind : Symbol(kind, Decl(assignmentCompatWithDiscriminatedUnion.ts, 206, 18)) +>MyEnum : Symbol(MyEnum, Decl(assignmentCompatWithDiscriminatedUnion.ts, 203, 19)) +>A : Symbol(MyEnum.A, Decl(assignmentCompatWithDiscriminatedUnion.ts, 204, 17)) +>id : Symbol(id, Decl(assignmentCompatWithDiscriminatedUnion.ts, 206, 34)) + + type TypeB = { kind: MyEnum.B } & ({ id?: undefined } | { id: number }); +>TypeB : Symbol(TypeB, Decl(assignmentCompatWithDiscriminatedUnion.ts, 206, 49)) +>kind : Symbol(kind, Decl(assignmentCompatWithDiscriminatedUnion.ts, 208, 18)) +>MyEnum : Symbol(MyEnum, Decl(assignmentCompatWithDiscriminatedUnion.ts, 203, 19)) +>B : Symbol(MyEnum.B, Decl(assignmentCompatWithDiscriminatedUnion.ts, 204, 24)) +>id : Symbol(id, Decl(assignmentCompatWithDiscriminatedUnion.ts, 208, 40)) +>id : Symbol(id, Decl(assignmentCompatWithDiscriminatedUnion.ts, 208, 61)) + + type MyType = TypeA | TypeB; +>MyType : Symbol(MyType, Decl(assignmentCompatWithDiscriminatedUnion.ts, 208, 76)) +>TypeA : Symbol(TypeA, Decl(assignmentCompatWithDiscriminatedUnion.ts, 204, 32)) +>TypeB : Symbol(TypeB, Decl(assignmentCompatWithDiscriminatedUnion.ts, 206, 49)) + + function something(a: MyType): void {} +>something : Symbol(something, Decl(assignmentCompatWithDiscriminatedUnion.ts, 210, 32)) +>a : Symbol(a, Decl(assignmentCompatWithDiscriminatedUnion.ts, 212, 23)) +>MyType : Symbol(MyType, Decl(assignmentCompatWithDiscriminatedUnion.ts, 208, 76)) + + function indirect(kind: MyEnum, id?: number): void { +>indirect : Symbol(indirect, Decl(assignmentCompatWithDiscriminatedUnion.ts, 212, 42)) +>kind : Symbol(kind, Decl(assignmentCompatWithDiscriminatedUnion.ts, 214, 22)) +>MyEnum : Symbol(MyEnum, Decl(assignmentCompatWithDiscriminatedUnion.ts, 203, 19)) +>id : Symbol(id, Decl(assignmentCompatWithDiscriminatedUnion.ts, 214, 35)) + + something({ kind, id }); +>something : Symbol(something, Decl(assignmentCompatWithDiscriminatedUnion.ts, 210, 32)) +>kind : Symbol(kind, Decl(assignmentCompatWithDiscriminatedUnion.ts, 215, 19)) +>id : Symbol(id, Decl(assignmentCompatWithDiscriminatedUnion.ts, 215, 25)) + } + + type Foo = { kind: "a" | "b", value: number } | { kind: "a", value: undefined } | { kind: "b", value: undefined }; +>Foo : Symbol(Foo, Decl(assignmentCompatWithDiscriminatedUnion.ts, 216, 5)) +>kind : Symbol(kind, Decl(assignmentCompatWithDiscriminatedUnion.ts, 218, 16)) +>value : Symbol(value, Decl(assignmentCompatWithDiscriminatedUnion.ts, 218, 33)) +>kind : Symbol(kind, Decl(assignmentCompatWithDiscriminatedUnion.ts, 218, 53)) +>value : Symbol(value, Decl(assignmentCompatWithDiscriminatedUnion.ts, 218, 64)) +>kind : Symbol(kind, Decl(assignmentCompatWithDiscriminatedUnion.ts, 218, 87)) +>value : Symbol(value, Decl(assignmentCompatWithDiscriminatedUnion.ts, 218, 98)) + + function test(obj: { kind: "a" | "b", value: number | undefined }) { +>test : Symbol(test, Decl(assignmentCompatWithDiscriminatedUnion.ts, 218, 118)) +>obj : Symbol(obj, Decl(assignmentCompatWithDiscriminatedUnion.ts, 220, 18)) +>kind : Symbol(kind, Decl(assignmentCompatWithDiscriminatedUnion.ts, 220, 24)) +>value : Symbol(value, Decl(assignmentCompatWithDiscriminatedUnion.ts, 220, 41)) + + let x1: Foo = obj; +>x1 : Symbol(x1, Decl(assignmentCompatWithDiscriminatedUnion.ts, 221, 11)) +>Foo : Symbol(Foo, Decl(assignmentCompatWithDiscriminatedUnion.ts, 216, 5)) +>obj : Symbol(obj, Decl(assignmentCompatWithDiscriminatedUnion.ts, 220, 18)) + + let x2: Foo = { kind: obj.kind, value: obj.value }; +>x2 : Symbol(x2, Decl(assignmentCompatWithDiscriminatedUnion.ts, 222, 11)) +>Foo : Symbol(Foo, Decl(assignmentCompatWithDiscriminatedUnion.ts, 216, 5)) +>kind : Symbol(kind, Decl(assignmentCompatWithDiscriminatedUnion.ts, 222, 23)) +>obj.kind : Symbol(kind, Decl(assignmentCompatWithDiscriminatedUnion.ts, 220, 24)) +>obj : Symbol(obj, Decl(assignmentCompatWithDiscriminatedUnion.ts, 220, 18)) +>kind : Symbol(kind, Decl(assignmentCompatWithDiscriminatedUnion.ts, 220, 24)) +>value : Symbol(value, Decl(assignmentCompatWithDiscriminatedUnion.ts, 222, 39)) +>obj.value : Symbol(value, Decl(assignmentCompatWithDiscriminatedUnion.ts, 220, 41)) +>obj : Symbol(obj, Decl(assignmentCompatWithDiscriminatedUnion.ts, 220, 18)) +>value : Symbol(value, Decl(assignmentCompatWithDiscriminatedUnion.ts, 220, 41)) + } +} + diff --git a/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.types b/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.types index ae1f923e573f2..be6617683be64 100644 --- a/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.types +++ b/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.types @@ -723,3 +723,129 @@ namespace GH39357 { > : ^^ } +// https://github.com/microsoft/TypeScript/issues/58603 +namespace GH58603 { +>GH58603 : typeof GH58603 +> : ^^^^^^^^^^^^^^ + + enum MyEnum { A = 1, B = 2 } +>MyEnum : MyEnum +> : ^^^^^^ +>A : MyEnum.A +> : ^^^^^^^^ +>1 : 1 +> : ^ +>B : MyEnum.B +> : ^^^^^^^^ +>2 : 2 +> : ^ + + type TypeA = { kind: MyEnum.A, id?: number }; +>TypeA : TypeA +> : ^^^^^ +>kind : MyEnum.A +> : ^^^^^^^^ +>MyEnum : any +> : ^^^ +>id : number +> : ^^^^^^ + + type TypeB = { kind: MyEnum.B } & ({ id?: undefined } | { id: number }); +>TypeB : TypeB +> : ^^^^^ +>kind : MyEnum.B +> : ^^^^^^^^ +>MyEnum : any +> : ^^^ +>id : undefined +> : ^^^^^^^^^ +>id : number +> : ^^^^^^ + + type MyType = TypeA | TypeB; +>MyType : MyType +> : ^^^^^^ + + function something(a: MyType): void {} +>something : (a: MyType) => void +> : ^ ^^ ^^^^^ +>a : MyType +> : ^^^^^^ + + function indirect(kind: MyEnum, id?: number): void { +>indirect : (kind: MyEnum, id?: number) => void +> : ^ ^^ ^^ ^^^ ^^^^^ +>kind : MyEnum +> : ^^^^^^ +>id : number +> : ^^^^^^ + + something({ kind, id }); +>something({ kind, id }) : void +> : ^^^^ +>something : (a: MyType) => void +> : ^ ^^ ^^^^^ +>{ kind, id } : { kind: MyEnum; id: number; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>kind : MyEnum +> : ^^^^^^ +>id : number +> : ^^^^^^ + } + + type Foo = { kind: "a" | "b", value: number } | { kind: "a", value: undefined } | { kind: "b", value: undefined }; +>Foo : Foo +> : ^^^ +>kind : "a" | "b" +> : ^^^^^^^^^ +>value : number +> : ^^^^^^ +>kind : "a" +> : ^^^ +>value : undefined +> : ^^^^^^^^^ +>kind : "b" +> : ^^^ +>value : undefined +> : ^^^^^^^^^ + + function test(obj: { kind: "a" | "b", value: number | undefined }) { +>test : (obj: { kind: "a" | "b"; value: number | undefined; }) => void +> : ^ ^^ ^^^^^^^^^ +>obj : { kind: "a" | "b"; value: number | undefined; } +> : ^^^^^^^^ ^^^^^^^^^ ^^^ +>kind : "a" | "b" +> : ^^^^^^^^^ +>value : number +> : ^^^^^^ + + let x1: Foo = obj; +>x1 : Foo +> : ^^^ +>obj : { kind: "a" | "b"; value: number | undefined; } +> : ^^^^^^^^ ^^^^^^^^^ ^^^ + + let x2: Foo = { kind: obj.kind, value: obj.value }; +>x2 : Foo +> : ^^^ +>{ kind: obj.kind, value: obj.value } : { kind: "a" | "b"; value: number; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>kind : "a" | "b" +> : ^^^^^^^^^ +>obj.kind : "a" | "b" +> : ^^^^^^^^^ +>obj : { kind: "a" | "b"; value: number | undefined; } +> : ^^^^^^^^ ^^^^^^^^^ ^^^ +>kind : "a" | "b" +> : ^^^^^^^^^ +>value : number +> : ^^^^^^ +>obj.value : number +> : ^^^^^^ +>obj : { kind: "a" | "b"; value: number | undefined; } +> : ^^^^^^^^ ^^^^^^^^^ ^^^ +>value : number +> : ^^^^^^ + } +} + diff --git a/tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithDiscriminatedUnion.ts b/tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithDiscriminatedUnion.ts index bea0b5ef78ffb..a717a1c718d80 100644 --- a/tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithDiscriminatedUnion.ts +++ b/tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithDiscriminatedUnion.ts @@ -199,3 +199,27 @@ namespace GH39357 { declare const b: B; const a: A = b === "a" || b === "b" ? [b, 1] : ["c", ""]; } + +// https://github.com/microsoft/TypeScript/issues/58603 +namespace GH58603 { + enum MyEnum { A = 1, B = 2 } + + type TypeA = { kind: MyEnum.A, id?: number }; + + type TypeB = { kind: MyEnum.B } & ({ id?: undefined } | { id: number }); + + type MyType = TypeA | TypeB; + + function something(a: MyType): void {} + + function indirect(kind: MyEnum, id?: number): void { + something({ kind, id }); + } + + type Foo = { kind: "a" | "b", value: number } | { kind: "a", value: undefined } | { kind: "b", value: undefined }; + + function test(obj: { kind: "a" | "b", value: number | undefined }) { + let x1: Foo = obj; + let x2: Foo = { kind: obj.kind, value: obj.value }; + } +}