parser.parsefile调用前必须配齐fset、文件名、mode和src四要素:fset须为token.newfileset()创建的非nil值,文件名可为空字符串但磁盘读取建议用绝对路径,mode至少含parsecomments或allerrors,src为nil则按路径读取、非nil则解析字符串内容,且必须检查err避免panic。

parser.ParseFile 调用前必须配齐这四样东西
不设对 token.FileSet、mode、src,或忽略 err 检查,90% 的 panic 都发生在这一步。
-
fset必须是token.NewFileSet()创建的非 nil 值;漏了会导致所有node.Pos()返回 0,后续定位行号全失效 - 第二个参数(文件名)可传空字符串
"",但若从磁盘读取,建议传绝对路径;若走内存解析(src非 nil),它只是占位符,不校验是否存在 -
mode不能为 0 或 nil:要读注释就得加parser.ParseComments;想收集全部语法错误而非遇到第一个就 panic,必须加parser.AllErrors -
src决定读取方式:传nil表示按文件路径加载;传字符串(如os.ReadFile结果)则必须非 nil,否则解析器直接跳过
典型 panic:panic: interface conversion: ast.Node is nil, not *ast.File,基本等于你没检查 err != nil 就直接访问 f。
遍历函数声明时为什么取不到 Name.Name 或 panic 在 Params.List
*ast.FuncDecl 字段命名和语义脱节,直接解引用必崩——不是结构设计反直觉,而是 Go 允许无参数、无返回值、匿名参数等合法写法,对应字段就是 nil,不是空切片。
- 函数名在
f.Name.Name,不是f.Name;后者是*ast.Ident节点,未判空就取会 panic -
f.Type.Params和f.Type.Results可能为nil(比如func hello() {}或func init()),必须先判空再访问.List - 每个参数是
*ast.Field,名字存在field.Names([]*ast.Ident),匿名参数(如func(int))该切片为空,取[0]必 panic - 省略号
...对应的是*ast.Ellipsis,不是*ast.ArrayType,需单独断言类型
ast.Inspect 回调里怎么安全提取信息又不踩坑
它比手写递归稳,但用法有硬约束:回调签名固定为 func(node ast.Node) bool,返回 false 可跳过整棵子树(比如找到第一个 main 就停),但别指望它提供父节点或作用域上下文。
- 类型断言必须带
ok判断:if f, ok := node.(*ast.FuncDecl); ok { ... };用==做指针比较恒为 false,因为 AST 节点没实现== - 别在回调里修改
node字段:AST 是只读副本,改了不影响原始结构,还可能干扰后续遍历 - 同一
*ast.Ident可能在表达式和类型中重复出现(如var x int中的int和x),逻辑必须幂等 - 需要父节点或当前作用域?
ast.Inspect不提供,得自己维护栈;真要改 AST,得用golang.org/x/tools/go/ast/astutil
为什么不用手写递归遍历 Decls,而坚持用 ast.Inspect
手写 switch node.(type) 容易漏掉新版本引入的节点类型,比如 Go 1.18 加入的 ast.TypeSpec.TypeParams,或 Go 1.21 的 ast.FieldList 新字段;ast.Inspect 底层遍历的是所有实现了 ast.Node 接口的结构体,无需你手动枚举。
- 它自动跳过
nil字段,避免你在f.Decls或field.Type上反复判空 - 支持中途退出:返回
false可跳过整棵子树(例如跳过*ast.FuncDecl.Body,省掉函数体内部遍历) - 兼容 Go 版本升级:新增节点类型(如
ast.InterfaceType.Methods)会被自动覆盖,不用改代码 - 但它不保证访问顺序,也不保证同一节点只出现一次,别依赖“第一次见到就是定义处”这类假设
真正容易被忽略的是:你没法靠 ast.Inspect 知道当前节点在哪个函数体内——那个 fmt.Println 调用,到底是顶层还是某个 for 循环里?得自己压栈维护作用域链。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











