在TypeScript中自引用映射类型
我正在用Typescript为一种小型语言建模AST。我正在尝试转换这种类型:在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:
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: "-" }
}