You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过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包的类型检查能力,关联变量与类型信息。

核心步骤

  1. 配置类型检查:使用types.Config和types.Info收集类型信息,Info.Objects会记录每个标识符对应的类型对象。
  2. 遍历AST解析调用:
    • 识别*ast.CallExpr,提取对应的*ast.SelectorExpr(即x.Method()结构)。
    • 从调用者节点获取标识符,通过types.Info查询变量的类型。
    • 处理指针类型,解析出实际的结构体类型。
  3. 构建映射关系:将结构体全名(含包名)和调用的方法名存入映射表,避免同名结构体冲突。

可运行示例代码

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()获取指针指向的实际结构体类型。

学习资源推荐

  1. 官方文档:
    • go/ast包文档:详细介绍AST节点结构与遍历逻辑。
    • go/types包文档:讲解类型检查核心API与概念。
  2. 实战指南:
    • Go多包分析工具编写教程:重点讲解AST遍历与类型检查的结合使用。
    • Go静态分析入门:从基础AST节点识别到完整类型检查流程的入门指导。

内容的提问来源于stack exchange,提问作者benston

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 23:03:11