用 ast.Inspect 遍历 *ast.FuncDecl 节点可提取顶层函数名、参数和返回值;需通过 f.Recv == nil 过滤方法,且 f.Type.Params/f.Type.Results 为 *ast.FieldList,须遍历 field.Names 和 field.Type 解析。

用 ast.Inspect 遍历函数声明节点最直接
Go 的 AST 不提供“按名称查函数”的 API,必须手动遍历。核心是识别 *ast.FuncDecl 节点,它代表顶层函数定义(不包括方法或闭包)。ast.Inspect 比 ast.Walk 更轻量,适合只关心特定节点类型的场景。
常见错误是把 *ast.FuncLit(匿名函数字面量)或 *ast.FuncType(类型定义)误认为函数定义——它们不是可导出的、具名的函数声明。
- 确保传入的是已解析的
*ast.File,不是源文件路径或字符串 - 在
ast.Inspect回调中用if f, ok := node.(*ast.FuncDecl); ok类型断言提取 -
f.Name.Name是函数名,f.Type.Params和f.Type.Results分别是参数与返回值列表 - 若需源码位置,用
f.Pos()配合fileSet.Position()转换为行列号
获取函数参数和返回值要用 ast.FieldList 展开
f.Type.Params 和 f.Type.Results 都是 *ast.FieldList,不是简单切片。每个 *ast.Field 可能含多个标识符(如 a, b int),也可能无名字(匿名参数)。
容易忽略的是:字段的 Names 可能为 nil(如 func(int, string)),此时需跳过名字提取;而 Type 字段才是类型表达式,需进一步递归解析(如 *ast.StarExpr 表示指针)。
- 遍历
field.Names获取参数名,field.Type获取类型 AST 节点 - 用
ast.Print临时调试类型结构(例如ast.Print(fileSet, field.Type)) - 不要假设
field.Type一定是*ast.Ident—— 它可能是*ast.ArrayType、*ast.MapType等
区分顶层函数和方法,得看 f.Recv 是否为空
*ast.FuncDecl 的 Recv 字段非空,说明这是方法(receiver 存在),否则才是普通函数。但注意:接口方法声明也走 *ast.FuncDecl,只是其 Recv 是 *ast.FieldList 含 receiver 字段。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
典型误判是把 func (t T) M() 当作普通函数处理,导致后续解析 receiver 类型失败。实际中,如果只关心“可直接调用的函数”,应显式过滤掉 f.Recv != nil 的节点。
-
f.Recv == nil→ 顶层函数(func Foo()) -
f.Recv != nil && len(f.Recv.List) == 1→ 方法(receiver 存在且只有一个字段) - receiver 字段的
Names是形参名,Type是接收者类型(如*ast.Ident或*ast.StarExpr)
跨文件或导入函数时,ast 包完全无能为力
go/ast 只解析单个文件的语法树,不涉及类型检查、符号解析或导入路径解析。它无法告诉你某个函数是否来自 fmt.Println 还是当前包,更不能展开 import 声明去读其他文件。
如果你需要知道函数是否导出、是否被调用、参数是否匹配,必须升级到 golang.org/x/tools/go/loader 或 golang.org/x/tools/go/types —— 但那是类型检查阶段的事,和纯 AST 无关。
一个常被踩的坑:试图从 ast.CallExpr 的 Fun 字段反推函数定义位置,结果发现它只是个 *ast.Ident 或 *ast.SelectorExpr,没有指向声明的指针。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










