ast.Inspect回调中安全取函数名和参数需先类型断言node.(*ast.FuncDecl),再判空f.Type.Params和f.Type.Results后访问List,函数名取f.Name.Name,init函数需特殊处理。

ast.Inspect 回调里怎么安全取函数名和参数
直接访问 funcDecl.Type.Params.List 是最常见 panic 来源——因为无参数函数的 Params 是 nil,不是空切片。同理,Results 也可能为 nil。
必须先判空再取 List:
- 用
if f, ok := node.(*ast.FuncDecl); ok做类型断言,别用指针比较(node == someNode恒为 false) -
if f.Type.Params != nil才遍历f.Type.Params.List;if f.Type.Results != nil才处理返回值 -
f.Name.Name == "init"表示是 init 函数,没有导出名,别当成普通函数提取签名 - 参数类型可能是
*ast.Ellipsis(对应...T),不是*ast.ArrayType,需单独断言
parser.ParseFile 必须设对 Mode 才能拿到关键信息
不传 Mode 或传错,90% 的 AST 信息就丢了:注释为空、错误只报一个、位置信息全为 0。
常用 Mode 组合:
立即学习“go语言免费学习笔记(深入)”;
- 要读注释:必须加
parser.ParseComments,否则file.Comments和funcDecl.Doc都是nil - 要收集全部语法错误:加
parser.AllErrors,否则遇到第一个错误就返回,err != nil但没告诉你还有多少错 - 要解析内存字符串(非文件路径):第四个参数传源码字符串,并加
parser.FromFile模式 -
token.FileSet必须传给ParseFile,否则所有Pos()/End()返回 0,fset.Position(pos)拿不到行号
为什么不该手写递归遍历 AST
手写递归看着自由,实则极易漏节点、崩栈、错顺序。比如只遍历 FuncDecl.Type.Params,却忘了 FuncType.Results 也是 *ast.FieldList;或忽略 GenDecl 里嵌套的 FuncLit(匿名函数)。
ast.Inspect 的优势是它已按 Go 语法规范完整覆盖所有字段路径:
- 自动跳过
nil子节点,不 panic - 字段访问顺序与 AST 定义一致(如先
FuncType.Func再FuncType.Params),和 parser 输出严格对齐 - 内部做了迭代优化,不会因嵌套过深导致栈溢出
- 兼容 Go 版本升级新增节点(如
ast.TypeSpec.TypeParams),无需你手动补 case
想提前退出或需要上下文时怎么办
ast.Inspect 回调返回 false 可跳过整棵子树,适合“找到第一个 main 就停”这种场景;返回 true 才继续往下走。
但它不提供父节点或作用域信息——这是最容易被忽略的限制:
- 如果要记录“当前在哪个函数体内”,得自己维护一个栈,在进入
*ast.FuncDecl时 push,退出时 pop - 如果要多次遍历同一棵树(比如先找未使用变量,再统计复杂度),
ast.Inspect每次都 DFS 全量,性能差;应换用golang.org/x/tools/go/ast/inspector,它预构建事件列表,实测快 2.5 倍 - 如果真要改 AST(比如替换函数调用),
ast.Inspect不行——所有节点都是只读副本,改了无效;得用ast.Walk+ 自定义 Visitor,且必须手动更新父节点引用


















