From 6055f1d4ce882c5ead490412739ce54856e776ee Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Sat, 7 Jun 2025 09:06:02 -0700 Subject: [PATCH] Port "Improve type discrimination algorithm" from tsgo --- src/compiler/checker.ts | 12 +- ...discriminateWithMissingProperty.errors.txt | 22 ---- .../reference/missingDiscriminants.errors.txt | 28 +++++ .../reference/missingDiscriminants.symbols | 64 ++++++++++ .../reference/missingDiscriminants.types | 117 ++++++++++++++++++ tests/cases/compiler/missingDiscriminants.ts | 21 ++++ 6 files changed, 237 insertions(+), 27 deletions(-) delete mode 100644 tests/baselines/reference/discriminateWithMissingProperty.errors.txt create mode 100644 tests/baselines/reference/missingDiscriminants.errors.txt create mode 100644 tests/baselines/reference/missingDiscriminants.symbols create mode 100644 tests/baselines/reference/missingDiscriminants.types create mode 100644 tests/cases/compiler/missingDiscriminants.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 14b8e477a9eed..4ee34daaf6209 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -24768,11 +24768,13 @@ 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 && someType(getDiscriminatingType(), t => !!related(t, targetType))) { - matched = true; - } - else { - include[i] = Ternary.Maybe; + if (targetType) { + if (someType(getDiscriminatingType(), t => !!related(t, targetType))) { + matched = true; + } + else { + include[i] = Ternary.Maybe; + } } } } diff --git a/tests/baselines/reference/discriminateWithMissingProperty.errors.txt b/tests/baselines/reference/discriminateWithMissingProperty.errors.txt deleted file mode 100644 index 52ec4ca622cf9..0000000000000 --- a/tests/baselines/reference/discriminateWithMissingProperty.errors.txt +++ /dev/null @@ -1,22 +0,0 @@ -discriminateWithMissingProperty.ts(12,5): error TS2345: Argument of type '{ mode: "numeric"; data: Uint8Array; }' is not assignable to parameter of type 'Arg'. - Types of property 'data' are incompatible. - Type 'Uint8Array' is not assignable to type 'number'. - - -==== discriminateWithMissingProperty.ts (1 errors) ==== - type Arg = { - mode: "numeric", - data: number, - } | { - mode: "alphabetic", - data: string, - } | { - data: string | Uint8Array; - } - - declare function foo(arg: Arg): void; - foo({ mode: "numeric", data: new Uint8Array([30]) }); // Should error - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS2345: Argument of type '{ mode: "numeric"; data: Uint8Array; }' is not assignable to parameter of type 'Arg'. -!!! error TS2345: Types of property 'data' are incompatible. -!!! error TS2345: Type 'Uint8Array' is not assignable to type 'number'. \ No newline at end of file diff --git a/tests/baselines/reference/missingDiscriminants.errors.txt b/tests/baselines/reference/missingDiscriminants.errors.txt new file mode 100644 index 0000000000000..33f05ee45490f --- /dev/null +++ b/tests/baselines/reference/missingDiscriminants.errors.txt @@ -0,0 +1,28 @@ +missingDiscriminants.ts(17,23): error TS2353: Object literal may only specify known properties, and 'subkind' does not exist in type '{ kind: "b"; }'. +missingDiscriminants.ts(18,34): error TS2353: Object literal may only specify known properties, and 'subkind' does not exist in type '{ kind: "b"; }'. + + +==== missingDiscriminants.ts (2 errors) ==== + // https://github.com/microsoft/typescript-go/issues/1020 + + type Thing = + | { str: "a", num: 0 } + | { str: "b" } + | { num: 1 } + + const thing1: Thing = { str: "a", num: 0 } + const thing2: Thing = { str: "b", num: 1 } // Shouldn't be error + const thing3: Thing = { num: 1, str: "b" } // Shouldn't be error + + type Item = + | { kind: "a", subkind: 0, value: string } + | { kind: "a", subkind: 1, value: number } + | { kind: "b" } + + const item1: Item = { subkind: 1, kind: "b" } // Error, type "b" not assignable to type "a" + ~~~~~~~ +!!! error TS2353: Object literal may only specify known properties, and 'subkind' does not exist in type '{ kind: "b"; }'. + const item2: Item = { kind: "b", subkind: 1 } // Error, 'subkind' isn't a known property + ~~~~~~~ +!!! error TS2353: Object literal may only specify known properties, and 'subkind' does not exist in type '{ kind: "b"; }'. + \ No newline at end of file diff --git a/tests/baselines/reference/missingDiscriminants.symbols b/tests/baselines/reference/missingDiscriminants.symbols new file mode 100644 index 0000000000000..53fdcf109667e --- /dev/null +++ b/tests/baselines/reference/missingDiscriminants.symbols @@ -0,0 +1,64 @@ +//// [tests/cases/compiler/missingDiscriminants.ts] //// + +=== missingDiscriminants.ts === +// https://github.com/microsoft/typescript-go/issues/1020 + +type Thing = +>Thing : Symbol(Thing, Decl(missingDiscriminants.ts, 0, 0)) + + | { str: "a", num: 0 } +>str : Symbol(str, Decl(missingDiscriminants.ts, 3, 5)) +>num : Symbol(num, Decl(missingDiscriminants.ts, 3, 15)) + + | { str: "b" } +>str : Symbol(str, Decl(missingDiscriminants.ts, 4, 5)) + + | { num: 1 } +>num : Symbol(num, Decl(missingDiscriminants.ts, 5, 5)) + +const thing1: Thing = { str: "a", num: 0 } +>thing1 : Symbol(thing1, Decl(missingDiscriminants.ts, 7, 5)) +>Thing : Symbol(Thing, Decl(missingDiscriminants.ts, 0, 0)) +>str : Symbol(str, Decl(missingDiscriminants.ts, 7, 23)) +>num : Symbol(num, Decl(missingDiscriminants.ts, 7, 33)) + +const thing2: Thing = { str: "b", num: 1 } // Shouldn't be error +>thing2 : Symbol(thing2, Decl(missingDiscriminants.ts, 8, 5)) +>Thing : Symbol(Thing, Decl(missingDiscriminants.ts, 0, 0)) +>str : Symbol(str, Decl(missingDiscriminants.ts, 8, 23)) +>num : Symbol(num, Decl(missingDiscriminants.ts, 8, 33)) + +const thing3: Thing = { num: 1, str: "b" } // Shouldn't be error +>thing3 : Symbol(thing3, Decl(missingDiscriminants.ts, 9, 5)) +>Thing : Symbol(Thing, Decl(missingDiscriminants.ts, 0, 0)) +>num : Symbol(num, Decl(missingDiscriminants.ts, 9, 23)) +>str : Symbol(str, Decl(missingDiscriminants.ts, 9, 31)) + +type Item = +>Item : Symbol(Item, Decl(missingDiscriminants.ts, 9, 42)) + + | { kind: "a", subkind: 0, value: string } +>kind : Symbol(kind, Decl(missingDiscriminants.ts, 12, 5)) +>subkind : Symbol(subkind, Decl(missingDiscriminants.ts, 12, 16)) +>value : Symbol(value, Decl(missingDiscriminants.ts, 12, 28)) + + | { kind: "a", subkind: 1, value: number } +>kind : Symbol(kind, Decl(missingDiscriminants.ts, 13, 5)) +>subkind : Symbol(subkind, Decl(missingDiscriminants.ts, 13, 16)) +>value : Symbol(value, Decl(missingDiscriminants.ts, 13, 28)) + + | { kind: "b" } +>kind : Symbol(kind, Decl(missingDiscriminants.ts, 14, 5)) + +const item1: Item = { subkind: 1, kind: "b" } // Error, type "b" not assignable to type "a" +>item1 : Symbol(item1, Decl(missingDiscriminants.ts, 16, 5)) +>Item : Symbol(Item, Decl(missingDiscriminants.ts, 9, 42)) +>subkind : Symbol(subkind, Decl(missingDiscriminants.ts, 16, 21)) +>kind : Symbol(kind, Decl(missingDiscriminants.ts, 16, 33)) + +const item2: Item = { kind: "b", subkind: 1 } // Error, 'subkind' isn't a known property +>item2 : Symbol(item2, Decl(missingDiscriminants.ts, 17, 5)) +>Item : Symbol(Item, Decl(missingDiscriminants.ts, 9, 42)) +>kind : Symbol(kind, Decl(missingDiscriminants.ts, 17, 21)) +>subkind : Symbol(subkind, Decl(missingDiscriminants.ts, 17, 32)) + diff --git a/tests/baselines/reference/missingDiscriminants.types b/tests/baselines/reference/missingDiscriminants.types new file mode 100644 index 0000000000000..0d4589461f58d --- /dev/null +++ b/tests/baselines/reference/missingDiscriminants.types @@ -0,0 +1,117 @@ +//// [tests/cases/compiler/missingDiscriminants.ts] //// + +=== missingDiscriminants.ts === +// https://github.com/microsoft/typescript-go/issues/1020 + +type Thing = +>Thing : Thing +> : ^^^^^ + + | { str: "a", num: 0 } +>str : "a" +> : ^^^ +>num : 0 +> : ^ + + | { str: "b" } +>str : "b" +> : ^^^ + + | { num: 1 } +>num : 1 +> : ^ + +const thing1: Thing = { str: "a", num: 0 } +>thing1 : Thing +> : ^^^^^ +>{ str: "a", num: 0 } : { str: "a"; num: 0; } +> : ^^^^^^^^^^^^^^^^^^^^^ +>str : "a" +> : ^^^ +>"a" : "a" +> : ^^^ +>num : 0 +> : ^ +>0 : 0 +> : ^ + +const thing2: Thing = { str: "b", num: 1 } // Shouldn't be error +>thing2 : Thing +> : ^^^^^ +>{ str: "b", num: 1 } : { str: "b"; num: 1; } +> : ^^^^^^^^^^^^^^^^^^^^^ +>str : "b" +> : ^^^ +>"b" : "b" +> : ^^^ +>num : 1 +> : ^ +>1 : 1 +> : ^ + +const thing3: Thing = { num: 1, str: "b" } // Shouldn't be error +>thing3 : Thing +> : ^^^^^ +>{ num: 1, str: "b" } : { num: 1; str: "b"; } +> : ^^^^^^^^^^^^^^^^^^^^^ +>num : 1 +> : ^ +>1 : 1 +> : ^ +>str : "b" +> : ^^^ +>"b" : "b" +> : ^^^ + +type Item = +>Item : Item +> : ^^^^ + + | { kind: "a", subkind: 0, value: string } +>kind : "a" +> : ^^^ +>subkind : 0 +> : ^ +>value : string +> : ^^^^^^ + + | { kind: "a", subkind: 1, value: number } +>kind : "a" +> : ^^^ +>subkind : 1 +> : ^ +>value : number +> : ^^^^^^ + + | { kind: "b" } +>kind : "b" +> : ^^^ + +const item1: Item = { subkind: 1, kind: "b" } // Error, type "b" not assignable to type "a" +>item1 : Item +> : ^^^^ +>{ subkind: 1, kind: "b" } : { subkind: number; kind: "b"; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>subkind : number +> : ^^^^^^ +>1 : 1 +> : ^ +>kind : "b" +> : ^^^ +>"b" : "b" +> : ^^^ + +const item2: Item = { kind: "b", subkind: 1 } // Error, 'subkind' isn't a known property +>item2 : Item +> : ^^^^ +>{ kind: "b", subkind: 1 } : { kind: "b"; subkind: number; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>kind : "b" +> : ^^^ +>"b" : "b" +> : ^^^ +>subkind : number +> : ^^^^^^ +>1 : 1 +> : ^ + diff --git a/tests/cases/compiler/missingDiscriminants.ts b/tests/cases/compiler/missingDiscriminants.ts new file mode 100644 index 0000000000000..15c648a38e96f --- /dev/null +++ b/tests/cases/compiler/missingDiscriminants.ts @@ -0,0 +1,21 @@ +// @strict: true +// @noEmit: true + +// https://github.com/microsoft/typescript-go/issues/1020 + +type Thing = + | { str: "a", num: 0 } + | { str: "b" } + | { num: 1 } + +const thing1: Thing = { str: "a", num: 0 } +const thing2: Thing = { str: "b", num: 1 } // Shouldn't be error +const thing3: Thing = { num: 1, str: "b" } // Shouldn't be error + +type Item = + | { kind: "a", subkind: 0, value: string } + | { kind: "a", subkind: 1, value: number } + | { kind: "b" } + +const item1: Item = { subkind: 1, kind: "b" } // Error, type "b" not assignable to type "a" +const item2: Item = { kind: "b", subkind: 1 } // Error, 'subkind' isn't a known property