Уточнение типа помеченного объединения

#typescript

#typescript

Вопрос:

Я пытаюсь написать функцию, которая вернет значение помеченного объединения, но я хочу, чтобы возвращаемый тип уточнялся в зависимости от тега. Например,

 type Token = {kind: " "} | {kind: "var", text: string};

let tokens: Token[] = [{kind: "var", text: "x"}, {kind: " "}, {kind: "var", text: "y"}];

function pullToken<Kind extends Token["kind"]>(kind: Kind): Extract<Token, {kind: Kind}> {
    let token = tokens.shift();
    if (typeof token === "undefined" || token.kind !== kind) {
        throw "oops";
    }
    return token;
}

let token = pullToken("var");
console.log(token.text);
  

выдает мне следующую ошибку

 TS2322: Type 'Token' is not assignable to type 'Extract<{ kind: " "; }, { kind: Kind; }> | Extract<{ kind: "var"; text: string; }, { kind: Kind; }>'.
  Type '{ kind: " "; }' is not assignable to type 'Extract<{ kind: " "; }, { kind: Kind; }> | Extract<{ kind: "var"; text: string; }, { kind: Kind; }>'.
    Type '{ kind: " "; }' is not assignable to type 'Extract<{ kind: "var"; text: string; }, { kind: Kind; }>'.
  

Я ожидал, что token условие в if инструкции будет уточнено, но это не так. Есть ли способ добиться этого?

Ответ №1:

Известной проблемой TypeScript является то, что вы не можете использовать анализ потока управления для сужения параметров универсального типа (см. microsoft / TypeScript#24085) или значений, тип которых зависит от параметров универсального типа (см. microsoft / TypeScript # 13995).

Обычно, если у вас есть значение типа объединения (типа Token ) и вы выполняете для него защиту типа, тогда компилятор будет использовать анализ потока управления, чтобы сузить кажущийся тип этого значения до некоторого элемента или членов исходного объединения (типа {kind: "var", text: string} ). Это все хорошо, когда значение относится к неродовому типу.

Но если ваше значение (like token ) имеет тип, который зависит от параметра универсального типа, ограниченного объединением (like Kind extends Token ), то этого не произойдет. Это не сужает тип значения, и это определенно не сужает параметр типа (например Kind ) сам по себе.

Это сложная проблема для решения, поскольку во многих случаях компилятору было бы некорректно сужать параметр type, и даже там, где это могло бы быть правильным, это потребовало бы, чтобы компилятор потратил больше времени на выполнение некоторого анализа типов более высокого порядка. Возможно, в конечном итоге язык позволит вашему коду просто «работать». На данный момент вам нужно обойти это.


Компилятор не может проверить, что это token относится к типу Extract<Token, {kind: Kind}> . Но вы написали код, который должен (я надеюсь) сделать это правдой. Поскольку вы знаете что-то, чего не знает компилятор о типе token , вы можете использовать утверждение типа, чтобы просто сообщить об этом:

 return token as Extract<Token, {kind: Kind}>;
  

Это приведет к отключению ошибки. Обратите внимание, что бремя проверки безопасности типов внутри реализации pullToken() теперь лежит на вас; если вы допустили ошибку или солгали (например, return {kind: " "} as Extract<Token, {kind: Kind}>; компилятор не поймает это. Так что будьте осторожны.


Ссылка на игровую площадку