Самостоятельная ссылка на сопоставленный тип в TypeScript - PullRequest
1 голос
/ 14 апреля 2020

Я моделирую AST для небольшого языка в Typescript. Я пытаюсь преобразовать этот тип:

type Lang<T> = {
  Literal: { value: string | number | boolean },
  BinOp: { op: string, lhs: T, rhs: T },
  UnOp: { op: string, arg: T },
  // ...more fields
};

В это:

type ASTNode =
  { type: 'Literal', value: string | number | boolean } |
  { type: 'BinOp', op: string, lhs: ASTNode, rhs: ASTNode } |
  { type: 'UnOp', op: string, arg: ASTNode }
  // ... more types
;

Я думаю, что решение это что-то вроде этого:

type ASTNodeAux<T> = {
    [K in keyof Lang<T>]: { type: K } & Lang<T>[K]
};
type ASTNode = ASTNodeAux<ASTNode>[keyof ASTNodeAux<ASTNode>];

Но это не принимается TypeScript, так как ASTNode ссылается на себя. Из того, что я видел, обходной путь обычно заключается в использовании интерфейсов вместо этого, но я не вижу, как я могу сделать это вместе с отображенными типами. Есть ли способ достичь этого?

Для дополнительного контекста я пытаюсь избежать необходимости многократного написания типов типов, предоставляя точные сигнатуры типов для узлов AST и аргументы fold, forEach и другие комбинаторы ( ссылка на игровую площадку ). Я смог добиться этого (несколько несовершенным образом) в Flow ( площадка для игр ).

1 Ответ

1 голос
/ 23 апреля 2020

ОБНОВЛЕНИЕ: После прохождения вашей игровой площадки становится более понятно, почему вы хотите указать Lang<T> таким образом. Вот некоторый код на детской площадке , использующий подмножество вашего собственного примера и где render, run и fold, кажется, вводятся успешно, используя следующие определения:

type Replace<T, B> = { [P in keyof T]: T[P] extends SSNode ? B : T[P] }
type ASTNodeMatch <K, T> = Omit<Extract<Replace<SSNode, T>, { type: K }>, 'type'>
type SSAction<T, U> = { [K in SSNode['type']]: (x: ASTNodeMatch<K, T>) => U }

Я и Андре Рестиво работали в течение последних 4 часов над этим :) Мы предлагаем два ответа, в зависимости от того, в каком направлении вы хотите go. Тем не менее, мы думаем, что ваш Lang<T> плохо сформирован. Что именно должно быть T? Может быть, значения терминала? Если это так, то lhs и rhs должны указывать на Lang<T>, а Literal должен иметь тип T, а не string | number | boolean. Другое дело, что все ваши свойства Lang должны быть необязательными (иначе любой экземпляр Lang заставит его предоставить Literal, UnOp, BinOp и т. Д.) Мы доверяем вам исправит код в соответствии с вашей семантикой языка ...

PS Существует множество «промежуточных» типов, от которых вы можете избавиться, но мы считаем, что это облегчает следование обоснованию.


Переход от описанного типа объединения к Lang:

namespace OneWay {
    /* type Lang<T> = {
    Literal?: { value: string | number | boolean },
    BinOp?: { op: string, lhs: T, rhs: T },
    UnOp?: { op: string, arg: T },
    } */

    type ASTNode =
        { type: 'Literal', value: string | number | boolean } |
        { type: 'BinOp', op: string, lhs: ASTNode, rhs: ASTNode } |
        { type: 'UnOp', op: string, arg: ASTNode }

    type Replace<T, A, B> = {
        [P in keyof T]: T[P] extends A ? B : T[P]
    }

    type Lang<T extends ASTNode> = {
        [K in T['type']]?: ASTNodeMatch<K>
    }

    type ASTNodeMatchReplaced<T> = Replace<T, ASTNode, Lang<ASTNode>>
    type ASTNodeMatch<T> = Omit<Extract<ASTNodeMatchReplaced<ASTNode>, { type: T }>, 'type'>

    const node: ASTNode = { type: 'Literal', value: "hello" }
    const node2: Lang<ASTNode> = { Literal: { value: "string" } }
    const node3: Lang<ASTNode> = { BinOp: { op: "+", lhs: node2, rhs: node2 } }
}

Переход от Lang к описанному типу объединения :

namespace OrAnother {
    /* type ASTNode =
        { type: 'Literal', value: string | number | boolean } |
        { type: 'BinOp', op: string, lhs: ASTNode, rhs: ASTNode } |
        { type: 'UnOp', op: string, arg: ASTNode }
    */

    type Lang = {
        Literal?: { value: string | number | boolean },
        BinOp?: { op: string, lhs: Lang, rhs: Lang },
        UnOp?: { op: string, arg: Lang },
    } 

    type Replace<T, A> = {
        [P in keyof T]: T[P] extends A ? ASTNode : T[P]
    }

    type Pairs<T> = { [TKey in keyof T]: { type: TKey } & Replace<T[TKey], T> }
    type ASTNode = Pairs<Lang>[keyof Lang]

    const a1: ASTNode = { type: 'Literal', value: "dsda" }
    const a2: ASTNode = { type: 'BinOp', op: "+", lhs: a1, rhs: a1 }

    // These produce Type Errors (as expected)
    const a4: ASTNode = { type: 'BinOp', op: "+", lhs: 3, rhs: a1 } 
    const a5: ASTNode = { type: 'Literal', op: "-" }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...