在TypeScript中自引用映射类型

在TypeScript中自引用映射类型,typescript,Typescript,我正在用Typescript为一种小型语言建模AST。我正在尝试转换这种类型: type Lang={ 文字:{value:string | number | boolean}, BinOp:{op:string,lhs:T,rhs:T}, UnOp:{op:string,arg:T}, //…更多领域 }; 为此: 类型ASTNode= {type:'Literal',value:string | number | boolean}| {type:'BinOp',op:string,lhs:

我正在用Typescript为一种小型语言建模AST。我正在尝试转换这种类型:

type Lang={
文字:{value:string | number | boolean},
BinOp:{op:string,lhs:T,rhs:T},
UnOp:{op:string,arg:T},
//…更多领域
};
为此:

类型ASTNode=
{type:'Literal',value:string | number | boolean}|
{type:'BinOp',op:string,lhs:ASTNode,rhs:ASTNode}|
{type:'UnOp',op:string,arg:ASTNode}
// ... 更多类型
;
我认为解决办法大致如下:

类型ASTNodeAux={
[K-in-keyof-Lang]:{type:K}&Lang[K]
};

键入ASTNode=ASTNodeAux)。我能够在Flow()中实现这一点(以一种有点不完美的方式)。

更新:在经过您的操场之后,您更清楚地了解到为什么需要以这种方式指定的
语言。下面是一些使用您自己示例的子集的代码,其中
渲染
运行
折叠
似乎使用以下定义成功键入:

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 }

Lang
到指定的联合类型:

名称空间或其他名称空间{
/*类型ASTNode=
{type:'Literal',value:string | number | boolean}|
{type:'BinOp',op:string,lhs:ASTNode,rhs:ASTNode}|
{type:'UnOp',op:string,arg:ASTNode}
*/
类型Lang={
文字?:{value:string | number | boolean},
BinOp?:{op:string,lhs:Lang,rhs:Lang},
UnOp?:{op:string,arg:Lang},
} 
类型替换={
[P in keyof T]:T[P]扩展了一个?ASTNode:T[P]
}
类型对={[TKey in keyof T]:{type:TKey}&Replace}
类型ASTNode=Pairs[keyof Lang]
常量a1:ASTNode={type:'Literal',value:'dsda}
常量a2:ASTNode={type:'BinOp',op:“+”,lhs:a1,rhs:a1}
//这些会产生类型错误(如预期的那样)
常量a4:ASTNode={type:'BinOp',op:“+”,lhs:3,rhs:a1}
常量a5:ASTNode={type:'Literal',op:“-”}
}

你能往相反的方向走吗?通过更改源类型来避免多种类型?我愿意更改源
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 } }
}
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: "-" }
}