Go types.Named Origin 怎样获取泛型类型的原始定义
我在写 Go 静态分析规则时,第一次遇到 Box[int] 这类类型,最容易把“当前实例”“泛型声明”和“底层结构”混成一个对象。go/types.Named.Origin 解决的正是其中一个很具体的问题:从实例化后的 *types.Named 找回产生它的泛型原始定义。
如果当前类型是由泛型Named实例化得到的,Origin()返回原始泛型*Named;如果当前类型没有实例化关系,它就返回当前对象自身。需要类型实参看TypeArgs(),需要类型形参看TypeParams(),不要用Underlying()代替它们。
我为什么会需要 Origin
静态分析器拿到的类型通常来自 go/types.Info。当源码里写的是 Box[int],类型检查结果仍然是一个 *types.Named,但它已经带有具体类型实参。此时如果规则的目标是“所有名为 Box 的泛型声明”,直接把当前类型的字符串交给规则并不稳妥:字符串适合展示,不适合表达定义与实例之间的关系。
Origin() 给出一个稳定的回到原始定义的入口。它不负责展开字段,也不负责返回 int 这样的实参;它只回答“这个 Named 是从哪个泛型 Named 来的”。下面的结构图把检查结果、实例对象和泛型关系放在同一张图里,图中关系是静态说明,不是运行截图。

Origin 返回的到底是什么
官方 API 对它的定义可以压缩成两句话:
- 当前
Named是实例化类型时,返回产生它的泛型原始*Named。 - 当前
Named不是实例化类型时,返回当前*Named。
假设源码里有 type Box[T any] struct { Value T },再使用 Box[int]。当前变量的类型可以理解为实例 Box[int],而 Origin() 指向泛型定义 Box[T]。这两个对象在分析时用途不同:前者告诉你当前代码用了什么,后者告诉你代码使用的是哪个泛型声明。
这里的“原始”不是底层匿名结构体。Box[T] 仍是命名类型,只有调用 Underlying() 才会进入结构体等底层表示。
Origin、Obj、TypeArgs 和 Underlying 怎么选
我实际使用时会先问规则要回答哪一个问题,再选方法。几个 API 名字都挂在 *types.Named 上,但它们的语义并不重叠。
| 想知道什么 | 优先使用 | 得到的内容 |
|---|---|---|
| 实例来自哪个泛型声明 | Origin() | 原始泛型 *types.Named |
| 声明对象叫什么、位于哪里 | Obj() | 对应的 *types.TypeName |
| 实例使用了哪些具体类型 | TypeArgs() | 类型实参列表;未实例化时通常为 nil |
| 泛型声明定义了哪些形参 | TypeParams() | 类型形参列表 |
| 底层结构是什么 | Underlying() | 底层 types.Type,不是原始 Named |
Obj() 在实例化类型上仍然对应原始声明的类型名,所以它适合取名字和源码对象;但如果规则需要明确区分“泛型声明”和“当前实例”,我会同时保留 named 与 named.Origin(),再分别读取参数。

从 go/types.Info 里拿到原始泛型定义
下面的示例只展示静态分析的取值路径:解析一段含有泛型声明和实例变量的源码,从 Info.Defs 找到变量,再把变量类型断言成 *types.Named。示例中的输出用于说明字段关系,不代表本文在本机执行过。
package main
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"go/types"
)
func main() {
const source = `package demo
type Box[T any] struct {
Value T
}
var current Box[int]
`
// 解析源码并让 go/types 生成声明对象与类型信息。
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, "demo.go", source, 0)
if err != nil {
panic(err)
}
info := &types.Info{
Defs: make(map[*ast.Ident]types.Object),
}
conf := types.Config{}
if _, err := conf.Check("demo", fset, []*ast.File{file}, info); err != nil {
panic(err)
}
for ident, object := range info.Defs {
if ident.Name != "current" {
continue
}
variable, ok := object.(*types.Var)
if !ok {
continue
}
// 当前类型保留实例实参,Origin 则回到泛型原始定义。
named, ok := variable.Type().(*types.Named)
if !ok {
continue
}
origin := named.Origin()
fmt.Println("当前类型:", named)
fmt.Println("原始定义:", origin)
fmt.Println("类型实参:", named.TypeArgs())
fmt.Println("类型形参:", origin.TypeParams())
}
}
这段逻辑里最重要的是保留两个变量:
named表示当前代码位置看到的 Named,适合读取TypeArgs()。origin表示泛型声明,适合读取TypeParams()、Obj()或声明级规则。
如果分析对象来自 Info.Types 而不是 Info.Defs,取法相同:先拿到 types.Type,再做 *types.Named 断言。不要假设每个类型都是 Named,基础类型、指针、切片和接口都可能需要先按实际类型处理。
几个容易误判的边界
非泛型 Named 不会凭空产生另一个对象
对普通的 type UserID int,当前对象没有泛型实例化来源,UserID.Origin() 的语义就是返回它自己。代码因此可以无条件调用 Origin(),但不能无条件假设 TypeArgs() 非空。
TypeParams 不等于当前使用的参数
TypeParams() 描述声明端的形参,例如 T;TypeArgs() 描述使用端的实参,例如 int。在实例化类型上,原始泛型声明的形参信息仍然有价值,所以需要比较“参数个数”时应明确比较哪一边。
Underlying 不适合判断泛型来源
Underlying() 会把命名类型的视角转到底层结构。两个不同的命名类型可能拥有相同或相似的底层类型,因此用它来追溯泛型声明会丢掉命名身份。只有当规则确实关心字段、方法集或底层接口时,才继续向下查询。
String 只适合展示和日志
String() 对排查很方便,可以让日志显示 Box[int] 与 Box[T] 的差异。但字符串格式不是声明身份的替代品;规则匹配应优先使用 Obj()、Origin() 和参数列表。
我的选择结论
如果任务是“这个类型实例来自哪个泛型声明”,直接用 named.Origin();如果还要知道它是用什么类型实例化的,再读取 named.TypeArgs()。声明端的形参、名称和底层表示分别交给 TypeParams()、Obj() 与 Underlying()。
| 分析任务 | 推荐组合 | 不建议的替代 |
|---|---|---|
| 匹配所有 Box 的实例 | Origin() + Obj() | 直接比较字符串 |
| 记录 Box[int] 的具体化信息 | Origin() + TypeArgs() | 只读 Underlying() |
| 检查泛型声明是否有形参 | Origin().TypeParams() | 把 TypeArgs() 当成声明信息 |
| 检查字段或底层接口 | Underlying() | 用 Origin() 代替结构查询 |
记住这条最短路径就够了:Named 是当前类型视角,Origin 是泛型来源,TypeArgs 是实例输入,TypeParams 是声明输入,Underlying 是底层表示。把五个问题分开,静态分析规则就不容易在泛型实例和原始定义之间走错。
相关问题
Origin 会返回 nil 吗?
对有效的 *types.Named,它返回当前 Named 或其原始 Named,不是用来表示“没有泛型”的 nil 哨兵。
怎么拿到 Box[int] 里的 int?
从当前实例读取 TypeArgs(),再按索引取得对应的 types.Type;不要从 Origin() 读取,因为 Origin 代表声明端。
Origin 能把底层类型恢复成 Named 吗?
不能。Origin 只处理 Named 的泛型来源关系;底层结构应通过 Underlying() 查询,二者解决的是不同层次的问题。
Go HTTP_PROXY 环境变量为什么改变了客户端请求路径
- 上一篇
- Go HTTP_PROXY 环境变量为什么改变了客户端请求路径
- 下一篇
- Go TLS 最低版本设置后旧客户端为什么握手失败
-
- Golang · Go问答 | 30分钟前 |
- Go Scan NULL 到普通字符串为什么报错
- 244浏览 收藏
-
- Golang · Go问答 | 32分钟前 |
- Go 事务 defer Rollback 为什么不会覆盖 Commit
- 324浏览 收藏
-
- Golang · Go问答 | 38分钟前 |
- Go database/sql DB.Stats MaxIdleClosed 为什么持续增加
- 182浏览 收藏
-
- Golang · Go问答 | 41分钟前 |
- Go HTTP Server IdleTimeout 为什么仍有慢连接
- 372浏览 收藏
-
- Golang · Go问答 | 43分钟前 |
- Go tls.Config.GetClientCertificate 怎样按请求选择客户端证书
- 310浏览 收藏
-
- Golang · Go问答 | 46分钟前 |
- Go TLS 最低版本设置后旧客户端为什么握手失败
- 327浏览 收藏
-
- Golang · Go问答 | 54分钟前 |
- Go HTTP_PROXY 环境变量为什么改变了客户端请求路径
- 233浏览 收藏
-
- Golang · Go问答 | 54分钟前 |
- Go ServeMux 方法模式冲突为什么在启动时 panic
- 461浏览 收藏
-
- Golang · Go问答 | 59分钟前 |
- Go fs.Sub 目录不存在时为什么没有立刻报错
- 367浏览 收藏
-
- Golang · Go问答 | 1小时前 | go · net/http · Go net/http Referer http.Request 请求来源
- Go http.Request Referer 怎样获取请求来源
- 314浏览 收藏
-
- Golang · Go问答 | 1小时前 |
- Go os.Stat 跟踪符号链接为什么结果不符合预期
- 500浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 835次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 834次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 917次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 893次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 715次使用
-
- 用Nginx反向代理部署go写的网站。
- 2023-01-17 502浏览
-
- GoLand调式动态执行代码
- 2023-01-13 502浏览
-
- Go generate 未执行指令的注释与目录检查
- 2026-10-28 501浏览
-
- Go RWMutex 写锁等待过久的竞争分析
- 2026-10-28 501浏览
-
- Go os.File.ReadAt 为什么到文件末尾仍返回部分数据
- 2026-10-26 501浏览

