登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go问答

Go types.Info.TypeOf 怎样读取表达式类型

来源:17golang原创

时间:2026-10-25 07:17:06 247浏览 收藏

在做 Go 源码分析时,拿到一个 ast.Expr 只是看到了语法节点,还不知道它代表 int、结构体还是函数签名。正确做法是先让 go/types 完成类型检查,再用 types.Info.TypeOf(expr) 查询该表达式的静态类型。

官方文档:https://pkg.go.dev/go/types#Info.TypeOf

Info.TypeOf 返回表达式的类型;如果表达式没有被记录则返回 nil。它的前提是 Types、Uses 和 Defs 映射已经由类型检查过程填充。

先把类型检查结果准备好

TypeOf 不是独立的 AST 推断器,它读取的是 Info 中已经生成的结果。因此要初始化需要的映射,并把同一个 Info 传给 Config.Check。下面的示例从内存源码解析包,再定位函数返回语句里的二元表达式。

package main

import (
	"fmt"
	"go/ast"
	"go/importer"
	"go/parser"
	"go/token"
	"go/types"
	"strings"
)

func main() {
	// 保留源码位置,后面可用 token.Pos 回到文件和行列信息。
	fset := token.NewFileSet()
	src := `package demo
func Sum(a int, b int) int { return a + b }`
	file, err := parser.ParseFile(fset, "demo.go", strings.NewReader(src), 0)
	if err != nil {
		panic(err)
	}

	// 只初始化要查询的映射;TypeOf 的前提由这些映射提供。
	info := &types.Info{
		Types: make(map[ast.Expr]types.TypeAndValue),
		Uses:  make(map[*ast.Ident]types.Object),
		Defs:  make(map[*ast.Ident]types.Object),
	}
	conf := types.Config{Importer: importer.Default()}
	if _, err := conf.Check("demo", fset, []*ast.File{file}, info); err != nil {
		panic(err)
	}

	// 遍历只负责找到表达式,类型结论交给 Info.TypeOf。
	ast.Inspect(file, func(node ast.Node) bool {
		binary, ok := node.(*ast.BinaryExpr)
		if !ok {
			return true
		}
		typ := info.TypeOf(binary)
		fmt.Println("表达式类型:", typ.String())
		return false
	})
}

这里的关键关系是:parser.ParseFile 产生语法树,Config.Check 填充 Info,TypeOf 只查询指定表达式的结果。不要把 ast.Inspect 找到节点误认为已经完成了类型推断。

go types Config Check Info Types ast.Expr TypeOf 和 types.Type 的静态关系图
图1:类型检查结果、AST 表达式与 Info.TypeOf 查询之间的静态关系说明图,不是运行截图。

用 TypeOf 查询表达式的静态类型

调用方式很直接:把目标表达式节点传入 info.TypeOf(expr),返回值是 types.Type 接口。需要展示类型时可以调用 String();需要继续判断底层类型时,再根据业务使用 Underlying() 或类型断言。

func expressionType(info *types.Info, expr ast.Expr) (string, bool) {
	// TypeOf 返回 nil 代表该表达式没有可用的类型记录。
	typ := info.TypeOf(expr)
	if typ == nil {
		return "", false
	}
	// String 是展示类型的便捷方式,不应拿字符串代替类型比较。
	return typ.String(), true
}

在示例中,a + b 是函数返回表达式,经过完整检查后通常可以得到 int。如果查询的是常量表达式,返回类型也可能是未定类型,例如 untyped int;这说明表达式仍处在语言的常量类型语义中,不要只按最终赋值后的默认类型理解。

当需要比较两个类型是否相同,应使用 types.Identical 等类型 API;不要用 typ.String() 的文本相等替代类型身份判断。图中连线只表示数据职责和查询关系,不表示已经执行过某段代码。

Info TypeOf Types Defs Uses Selections nil 和 types.Type 查询边界静态关系图
图2:Types、Defs、Uses、Selections 与 TypeOf 返回边界的静态关系说明图,不是运行结果或界面截图。

为什么有些标识符查不到类型

Info.Types 并不记录每一个标识符。它主要记录可以作为任意表达式出现的 ast.Expr。官方文档特别区分了几类节点:

  • 选择器 x.f 中的字段标识符,相关信息通常在 Selections 中;
  • var z int 中声明变量的标识符,相关对象在 Defs 中;
  • 限定标识符 p.X 中代表包名的标识符,相关对象在 Uses 中。

所以,遇到 TypeOf 返回 nil 时,先确认传入的是表达式节点,再确认对应的 Info 映射是否在检查前初始化。若目标是“这个名字绑定了什么对象”,应改用 ObjectOf 或直接读取 Defs、Uses,不要反复调用 TypeOf。

nil 和不完整检查要分开处理

TypeOf 的 nil 只说明当前表达式没有可用记录,可能是传入节点不在本次检查的文件集合中,也可能是该节点属于 Types 不记录的范围。另一方面,Config.Check 返回错误时,Info 中已经收集的结果可能不完整,不能把部分结果当成全包结论。

func safeTypeName(info *types.Info, expr ast.Expr) string {
	// 先判空,避免对缺失记录直接调用 String 导致 panic。
	typ := info.TypeOf(expr)
	if typ == nil {
		return "未记录表达式类型"
	}
	return typ.String()
}

实际工具可以同时保留 Check 返回的错误、文件位置和目标表达式文本:有错误时提示“类型信息可能不完整”,无错误时再把 TypeOf 结果作为稳定查询结果。这样既能处理正常源码分析,也能避免静态检查工具在损坏输入上给出过度确定的结论。

常见问题

TypeOf 返回 nil 是 API 调用失败吗?

不一定。它的语义是没有找到该表达式的类型记录,应检查节点类型、Info 映射和本次 Check 的文件范围。

可以用 TypeOf 查询变量声明的类型吗?

声明名通常应从 Defs 获取对象,再读取对象的 Type();不要假设声明标识符一定存在于 Types。

Check 返回错误后还可以读 TypeOf 吗?

可以读已经收集到的结果,但官方说明这些信息可能不完整。工具应保留错误并降低结论确定性。

总结:先用 Config.Check 填充 Info,再把真实的 ast.Expr 交给 TypeOf;得到 nil 时区分“未记录”和“检查不完整”,需要查标识符绑定时则切换到 Defs、Uses 或 Selections。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>