Дискриминатор не работает должным образом при использовании исключений и | - PullRequest
0 голосов
/ 17 апреля 2020

Я не совсем знаю, как сформулировать вопрос, но наткнулся на особую проблему. Я пытаюсь различить различные Thingy с помощью свойства foo, которое имеет тип Kind.

interface AKind {
    kind: 'A';
}

interface BKind {
    kind: 'B';
}

interface CKind {
    kind: 'C';
}

type Kind = AKind | BKind | CKind;

type ExCThingy = {
    foo: Exclude<Kind, CKind>,
    something: 'test';
}

type CThingy = {
    foo: CKind,
    somethingelse: 'test2';
}

type Foo = {
    foo: Kind
}

function DoSomething(args: ExCThingy | CThingy) {
    function isCThingy(args: ExCThingy | CThingy): args is CThingy {
        return args.foo.kind === 'C';
    }

    if (isCThingy(args)) {
        //Do C thing
        return;
    }

    //else Do ExC thing
    return;
}

const a: AKind = {
    kind: 'A',
}

const b: BKind = {
    kind: 'B',
}

const c: CKind = {
    kind: 'C',
}


const fooArray: Foo[] = [{
    foo: a,
},
{
    foo: b,
},
{
    foo: c,
    }];

fooArray.map(e => DoSomething({
    foo: e.foo,
    something: 'test',
    somethingelse: 'test2'
}));

Компилятор ts жалуется:

Argument of type '{ foo: Kind; something: "test"; somethingelse: "test2"; }' is not assignable to parameter of type 'ExCThingy | CThingy'.
  Type '{ foo: Kind; something: "test"; somethingelse: "test2"; }' is not assignable to type 'CThingy'.
    Types of property 'foo' are incompatible.
      Type 'Kind' is not assignable to type 'CKind'.
        Type 'AKind' is not assignable to type 'CKind'.
          Types of property 'kind' are incompatible.
            Type '"A"' is not assignable to type '"C"'.(2345)

1 Ответ

2 голосов
/ 17 апреля 2020

Несколько вещей приводят к тому, что это происходит. Основная проблема здесь заключается в том, что TypeScript не поддерживает вложенные разграниченные объединения. Есть довольно старое открытое предложение на microsoft / TypeScript # 18758 ; если вам это небезразлично, вы можете указать go и указать ? или описать свой вариант использования, если он особенно убедителен. Однако сейчас это не часть языка. Это означает, что код, подобный этому, успешно выполняется:

type Discrim = { a: 0, c: string } | { a: 1, c: number };
declare const d: Discrim;
d.a === 0 ? d.c.toUpperCase() : d.c.toFixed(); // okay

, но код, подобный этому, не выполняется:

type NestedDiscrim = { a: { b: 0 }, c: string } | { a: { b: 1 }, c: number };
declare const n: NestedDiscrim;
n.a.b === 0 ? n.c.toUpperCase() : n.c.toFixed(); // error!

, потому что в первом случае Discrim рассматривается компилятором как дискриминационное объединение с свойство a как дискриминант, но в последнем случае NestedDiscrim это , а не рассматривается как дискриминируемый союз со свойством a в качестве дискриминанта.

Аналогично, в вашем case, Kind является дискриминационным объединением, а ExCThingy | CThingy - нет.


Для типов, которые компилятор видит как распознаваемое объединение, начиная с TypeScript 3.5, добавлена ​​поддержка для назначений, подобных следующему:

const k: Kind = { kind: Math.random() < 0.5 ? "A" : Math.random() < 0.5 ? "B" : "C" }; // okay

Объект, присвоенный k, имеет тип {kind: "A" | "B" | "C"}, который технически не может быть назначен ни одному отдельному члену различимого объединения Kind Это была ошибка, которую вы получили в TS3.4 и ниже:

// Type '{ kind: "A" | "B" | "C"; }' is not assignable to type 'Kind'.

Но в TS3.5 и выше компилятор выполняет дополнительную проверку, чтобы взять один тип объекта со свойством дискриминанта типа объединения и распространить союз upwa в объединение типов объектов с однотипными дискриминантными свойствами. И так это компилируется.

К сожалению, как мы упоминали, ExCThingy | CThingy не является дискриминационным объединением в соответствии с компилятором. И вышеупомянутая поддержка распространяется только на дискриминационные союзы. Для недискриминационных объединений вы получаете ту же ошибку:

type NotDiscrim = { a: string } | { a: number };
const x: NotDiscrim = { a: Math.random() < 0.5 ? "" : 1 }; // error in all versions of TS
// Type '{ a: string | number; }' is not assignable to type 'NotDiscrim'

Компилятор просто не выполняет анализ распространения объединения на недискриминационных объединениях. А поскольку ExCThingy | CThingy не считается дискриминационным объединением, значение типа { foo: Kind, something: 'test', somethingelse: 'test2' } не считается типом ExCThingy | CThingy.


Так вот что происходит. Чтобы продолжить здесь, вы можете использовать утверждение типа , чтобы сообщить компилятору, что вы уверены, что то, что вы делаете, безопасно:

fooArray.map(e => DoSomething({
    foo: e.foo,
    something: 'test',
    somethingelse: 'test2'
} as ExCThingy | CThingy)); // no error

Либо так, либо вы разбили один объект в объединение, которое на самом деле может проверить компилятор, например:

fooArray.map(e => DoSomething(e.foo.kind === "C" ?
    { foo: e.foo, somethingelse: 'test2' } : { foo: e.foo, something: 'test' }
));

Хорошо, надеюсь, это поможет; удачи!

Детская площадка ссылка на код

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...