go/ast是源码解析出的只读结构,不可手动构造;使用parser.ParseFile需确保fset非nil、mode正确、src合法,并检查err;遍历时须防nil panic,优先用ast.Inspect而非手写递归。

go/ast 不是用来“手写”或“构造”的,它是从源码解析出来的只读结构。你几乎不会手动 new 一个 *ast.File,但必须清楚它怎么来、哪些字段会 nil、遍历时怎么防 panic。
parser.ParseFile 总是返回 nil 或 panic?检查这四件事
最常见错误不是代码写错,而是调用前没配齐上下文:
-
fset必须非 nil:用token.NewFileSet()创建,漏了会导致所有node.Pos()返回 0,后续定位失效 - 第二个参数(文件名)可传空字符串
"",但若用于磁盘读取,建议传绝对路径;若用于字符串解析,它只是占位符,不校验是否存在 - 第四个参数
mode不能为 nil:想拿注释就得加parser.ParseComments;想容忍语法错误继续解析,加parser.AllErrors -
src参数决定读取方式:传nil表示从文件路径读;传字符串(如os.ReadFile结果)则必须非 nil,否则解析器直接忽略
典型 panic 场景:panic: interface conversion: ast.Node is nil, not *ast.File,基本等于你传了空字符串、非法 UTF-8 或没检查 err != nil 就直接访问 f。
遍历函数声明时为什么找不到 func main 或参数名?
*ast.FuncDecl 结构不直观,字段名和语义脱节:
- 函数名在
f.Name.Name,不是f.Name—— 后者是*ast.Ident节点,直接取会 panic - 参数列表藏在
f.Type.Params,但它可能为 nil(比如func hello() {}),必须先判空再访问.List - 每个参数是
*ast.Field,名字在field.Names([]*ast.Ident),匿名参数(如func(int))该切片为空,取[0]必 panic -
f.Doc是函数上方的注释,但f.Comments是整个文件的注释列表,且仅当mode含ParseComments才非 nil
别手写递归遍历 f.Decls,优先用 ast.Inspect,它自动覆盖 *ast.FuncDecl、*ast.GenDecl、*ast.TypeSpec 等所有节点类型,避免漏掉接口或常量声明。
立即学习“go语言免费学习笔记(深入)”;
怎么安全提取接口里的方法签名?
接口不是顶层声明,它藏在类型定义链里:
- 先遍历
*ast.GenDecl,检查g.Tok == token.TYPE - 再遍历其
g.Specs,对每个*ast.TypeSpec做类型断言:ts.Type.(*ast.InterfaceType) -
*ast.InterfaceType.Methods是*ast.FieldList,每个*ast.Field的Names[0].Name是方法名,Field.Type是方法签名(通常是*ast.FuncType) - 注意:
Methods可能为 nil(空接口interface{}),也可能是空切片(有接口但没方法),都得判空
别指望 go/ast 给你真实类型(比如 int 的底层是 int64 还是 int32),它只管语法,不管语义 —— 那是 go/types 的事。
为什么 ast.Inspect 回调里改 node 没用?
ast.Inspect 提供的是 AST 的只读副本,所有节点都是不可变的:
- 你在回调里给
node.(*ast.FuncDecl).Name.Name = "newname",不会影响原始 AST,也不会触发重写 - 真要修改或生成代码,得用
golang.org/x/tools/go/ast/astutil(比如astutil.AddImport),或手动构造新节点 +printer.Fprint输出 - 如果需要父节点、作用域或变量真实类型,
ast.Inspect不提供 —— 它不维护上下文,得自己压栈,或换用go/types做语义补全
最容易被忽略的一点:AST 节点没有实现 ==,在回调里写 if node == someNode 永远是 false,别这么比。


















