如何在 AST 解析器中将类型解析为原语

Posted

技术标签:

【中文标题】如何在 AST 解析器中将类型解析为原语【英文标题】:How to resolve types into a primitives in AST parser 【发布时间】:2021-11-24 02:53:11 【问题描述】:

我想提取函数的签名,以便能够在它们之上生成一些包装方法。为此,我正在使用 golang.org/x/tools/go/packages 这让我有机会阅读 AST。

例如,对于函数func MyFunc(param int) 的定义,您会收到一些

ast.FuncDecl
    Type: *FieldList
        List: []*Field
            
                Names: []*Ident /*...*/ ,
                Type:  nil, /*...*/
            ,
        ,
    ,

其中Type代表一个类型。

我想为所有 int 参数生成一些特殊代码,但 int 也可以通过某些类型声明来隐藏

type MyType int

如何将ast 类型转换为编译器拥有的真实类型?

【问题讨论】:

你能更精确一点吗?你想把什么转换成什么? “将“ast”类型转换为编译器拥有的真正类型”是什么意思? @mkopriva:我使用packages.Load 函数接收代码的 AST(我加载了一些包 - 在那里找到一些函数并在它们之上编写包装器)。所以在这一步我可以有一个函数签名列表。对于函数func MyFunc(param1 MyType),我将收到ast.Ident("MyType") 的类型(我称之为AST 类型)。但是我们在某处有另一个字符串:type MyType int - 这是我想要的:param1 有一个类型:“MyType”,可以解析为 int 类型。 使用packages.Load 加载的每个包都有一个TypesInfo 字段,如果您在加载模式下使用packages.NeedTypesInfo,则会填充此字段。 TypesInfo 字段的类型被声明为here,您可以将其Defs 字段与*ast.Ident("MyType") 一起使用来获取类型的定义。你问的是这个吗? 稍微修正我之前的评论...您还需要packages.NeedTypes 加载模式,并且您需要使用Types 字段而不是Defs 字段。尝试运行这个:play.golang.org/p/93-DNIxPoMk(在操场上由于“go.mod update”或其他原因它不起作用),在我的机器上执行输出如下:imgur.com/zyZ7pRM。 请您提供一个最小的可重现示例。加载一些代码、解析它并尝试使用结果来获取一些信息的东西。我的理解是你需要处理类型包而不是 ast。 pkg.go.dev/go/types 正如 mkopriva 建议的那样。 【参考方案1】:

packages.NeedTypespackages.NeedTypesInfo 添加到加载模式。这样,每个加载的包都将初始化其TypesInfo 字段,并且该字段的类型*types.Info 有一个名为Types 的字段,它将ast 表达式映射到类型。您可以通过以下方式使用它:

func main() 
    loadConfig := new(packages.Config)
    loadConfig.Mode = packages.NeedSyntax | packages.NeedTypes | packages.NeedTypesInfo
    loadConfig.Fset = token.NewFileSet()
    pkgs, err := packages.Load(loadConfig, "syscall")
    if err != nil 
        panic(err)
    

    for _, pkg := range pkgs 
        for _, syn := range pkg.Syntax 
            for _, dec := range syn.Decls 
                if fd, ok := dec.(*ast.FuncDecl); ok && fd.Name.Name == "Kill" 
                    x1 := fd.Type.Params.List[0].Type // int
                    x2 := fd.Type.Params.List[1].Type // syscall.Signal

                    tv1 := pkg.TypesInfo.Types[x1]
                    tv2 := pkg.TypesInfo.Types[x2]

                    if basic, ok := tv1.Type.(*types.Basic); ok 
                        fmt.Printf("%#v\n", basic) // int
                    

                    if named, ok := tv2.Type.(*types.Named); ok 
                        fmt.Printf("%v\n", named.Obj())         // *types.TypeName (Signal)
                        fmt.Printf("%#v\n", named.Underlying()) // *types.Basic (int)
                    
                
            
        
    

【讨论】:

我认为应该将 OP 引导到 pkg.go.dev/go/types#AssignableTo / pkg.go.dev/go/types#ConvertibleTo / pkg.go.dev/go/types#Comparable 和 pkg.go.dev/go/types#pkg-variables 以帮助处理其问题描述。但我承认我不太确定哪一个可以用来循环定义并测试它们,例如,提取所有~~int 参数。

以上是关于如何在 AST 解析器中将类型解析为原语的主要内容,如果未能解决你的问题,请参考以下文章

了解Haskell中已实现的递归下降解析器

Boost Spirit x3 -- 使用其他解析器参数化解析器

递归下降解析器

angr 文档翻译(3):解析器引擎——符号表达式和约束求解

C语言编译器开发之旅:解析器

用于 php 的独立语法和解析器