ast.Inspect 看不到函数体是因为 parser.ParseFile 默认跳过函数体以节省内存,需显式添加 parser.ParseFullFiles 模式;否则 FuncDecl.Body 为 nil,直接遍历会 panic。

用 go/ast 解析源码时,为什么 ast.Inspect 看不到函数体?
因为默认解析模式下,go/parser.ParseFile 会跳过函数体(节省内存),只保留声明结构。你看到的 *ast.FuncDecl 的 Body 字段是 nil,不是 bug,是设计如此。
- 必须显式传入
parser.Mode参数:用parser.ParseComments | parser.AllErrors不够,得加parser.ParseFullFiles - 如果只是想提取接口、结构体、方法签名,不依赖函数体,可以省略该 flag,解析更快、内存更少
- 常见报错:
panic: runtime error: invalid memory address往往是因为没检查funcDecl.Body != nil就直接遍历
生成 Go 文件时,text/template 和 go/format 怎么配合才不翻车?
模板输出的代码格式稍有偏差(比如多空行、缺换行、括号不对齐),go/format.Node 就可能失败或生成非法代码——它对语法树输入很敏感,但对字符串输入其实挺宽容;真正容易崩的是后续 go build 或 go vet。
- 模板里别手写缩进:用
{{- if .Cond }}...{{- end}}控制空白,避免模板自身引入非法换行 - 生成完先用
go/format.Source格式化字节流,再写入文件;不要依赖模板“看起来整齐” - 若模板中要插入变量名或类型名,务必用
{{.IdentName | printf "%s"}}这类转义,防止注入非法字符(比如字段名含type关键字)
go/types 和 go/ast 混用时,为什么 types.Info.Defs 找不到变量?
go/types 需要完整类型检查上下文,不是光有 AST 就能填满 types.Info。很多生成器只做 AST 遍历就去查 Defs,结果全是 nil。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 必须调用
types.NewPackage+checker.Files完成一次真实类型检查,不能跳过types.Config.Check -
types.Info.Defs只记录顶层定义(如var x int中的x),函数体内:=定义的变量在Uses里,不在Defs - 如果只是为了获取结构体字段类型字符串(比如生成 JSON tag 映射),直接从
*ast.StructType的Fields提取Field.Type并用ast.Print格式化更轻量、更稳
生成器跑在 CI 里失败,本地却正常,大概率卡在哪?
路径和模块根目录不一致。Go 的 go/parser 和 go/types 默认按当前工作目录找 go.mod,而 CI 常在子目录执行脚本,或用 go run ./cmd/gen 启动,导致解析器找不到包导入路径。
- 硬编码
gopath或GOROOT没用,关键是要让token.NewFileSet()和parser.ParseFile知道模块根在哪 - 用
filepath.Abs(".")不可靠;推荐在入口处调用exec.Command("go", "list", "-m", "-f", "{{.Dir}}").Output()拿真实模块根 - 如果生成器需跨 package 分析,记得把所有待处理文件统一传给
parser.ParseFiles,别单个解析——否则go/types无法建立跨文件引用关系
AST 解析本身不难,难的是每个环节都依赖前一步的上下文完整性。漏掉一个 ParseFullFiles,或错估 types.Info 的填充范围,生成出来的代码就可能编译不过,而且错误位置离你写的模板很远。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










