ОБНОВЛЕНИЕ: После прохождения вашей игровой площадки становится более понятно, почему вы хотите указать 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: "-" }
}