如何通过Go AST包从*ast.SelectorExpr获取调用方结构体及求学习资料
Go AST解析:获取结构体到调用方法的映射
问题描述
示例代码
package main import ( "context" "service" ) type LockService struct {} func main() { s := &LockService{} s.LockCouponService(ctx, couponID) }
需求
获取代码中LockService对象调用的LockCouponService方法,最终得到结构体类型到方法名的映射(例如LockService => LockCouponService),结构体可能来自其他包。
尝试的解析代码(未成功)
var structToMethods = map[string][]string{} var normalMethods = map[string][]string{} func main() { for _, f := range files { pkg := f.Name.Name fmt.Println("pkg: ", pkg) ast.Inspect(f, func(n ast.Node) bool { fun, ok := n.(*ast.FuncDecl) if !ok { return true } fmt.Println("====================") name := "" if fun.Recv != nil { for _, v := range fun.Recv.List { switch xv := v.Type.(type) { case *ast.StarExpr: if si, ok := xv.X.(*ast.Ident); ok { name = si.Name } case *ast.Ident: name = xv.Name } fmt.Println("=====fun.Recv name", name) } } callFuns := []string{} for _, l := range fun.Body.List { fmt.Printf("l type: %v\n", reflect.TypeOf(l)) e, ok := l.(*ast.ExprStmt) if ok { cc, ok := e.X.(*ast.CallExpr) if ok { fmt.Println("cc.Fun type: ", reflect.TypeOf(cc.Fun)) se, ok := cc.Fun.(*ast.SelectorExpr) if ok { callFuns = append(callFuns, se.Sel.String()) fmt.Printf("pkg or more: %s, type: %s, fun_name: %s\n", se.X, reflect.TypeOf(se.X), se.Sel) _, ok := se.X.(*ast.Ident) if ok { } } } } } fmt.Printf("fun recv, method, call functions: %v, %v, %v\n", name, fun.Name.String(), callFuns) return true }) } b, _ := json.MarshalIndent(structToMethods, "", "\t") fmt.Println("structToMethods", string(b)) b, _ = json.MarshalIndent(normalMethods, "", "\t") fmt.Println("normalMethods", string(b)) }
咨询内容
如何通过Go的ast包从*ast.SelectorExpr中获取调用方法的结构体,实现结构体到方法的映射?同时求相关的学习文章。
解决方案
仅靠ast包无法完成需求,因为AST只记录变量名(比如示例中的s),不知道其对应的结构体类型。必须结合types包的类型检查能力,关联变量与类型信息。
核心步骤
- 配置类型检查:使用
types.Config和types.Info收集类型信息,Info.Objects会记录每个标识符对应的类型对象。 - 遍历AST解析调用:
- 识别
*ast.CallExpr,提取对应的*ast.SelectorExpr(即x.Method()结构)。 - 从调用者节点获取标识符,通过
types.Info查询变量的类型。 - 处理指针类型,解析出实际的结构体类型。
- 识别
- 构建映射关系:将结构体全名(含包名)和调用的方法名存入映射表,避免同名结构体冲突。
可运行示例代码
package main import ( "encoding/json" "fmt" "go/ast" "go/parser" "go/token" "go/types" "os" ) var structToMethods = map[string][]string{} func main() { fset := token.NewFileSet() files, err := parser.ParseDir(fset, "./", nil, parser.AllErrors) if err != nil { fmt.Fprintf(os.Stderr, "parse error: %v\n", err) return } conf := types.Config{} info := &types.Info{ Objects: make(map[*ast.Ident]types.Object), } for pkgName, pkgFiles := range files { var astFiles []*ast.File for _, f := range pkgFiles { astFiles = append(astFiles, f) } _, err := conf.Check(pkgName, fset, astFiles, info) if err != nil { fmt.Fprintf(os.Stderr, "check error: %v\n", err) continue } for _, f := range astFiles { ast.Inspect(f, func(n ast.Node) bool { callExpr, ok := n.(*ast.CallExpr) if !ok { return true } selExpr, ok := callExpr.Fun.(*ast.SelectorExpr) if !ok { return true } ident, ok := selExpr.X.(*ast.Ident) if !ok { return true } obj := info.Objects[ident] if obj == nil { return true } // 处理指针类型,获取实际结构体类型 var typ types.Type switch v := obj.Type().(type) { case *types.Pointer: typ = v.Elem() default: typ = v } _, ok = typ.(*types.Struct) if !ok { return true } structName := typ.String() methodName := selExpr.Sel.Name // 方法名去重 found := false for _, m := range structToMethods[structName] { if m == methodName { found = true break } } if !found { structToMethods[structName] = append(structToMethods[structName], methodName) } return true }) } } b, _ := json.MarshalIndent(structToMethods, "", "\t") fmt.Println("结构体到方法的映射:") fmt.Println(string(b)) }
关键说明
types.Info的作用:解决AST只知变量名不知类型的问题,在类型检查时自动记录标识符对应的类型对象。- 结构体全名:
typ.String()返回含包名的类型(如main.LockService),避免跨包同名结构体混淆。 - 指针类型处理:通过
types.Pointer.Elem()获取指针指向的实际结构体类型。
学习资源推荐
- 官方文档:
go/ast包文档:详细介绍AST节点结构与遍历逻辑。go/types包文档:讲解类型检查核心API与概念。
- 实战指南:
- Go多包分析工具编写教程:重点讲解AST遍历与类型检查的结合使用。
- Go静态分析入门:从基础AST节点识别到完整类型检查流程的入门指导。
内容的提问来源于stack exchange,提问作者benston
相关产品推荐
相关产品推荐

