当前位置:首页 > 文章列表 > Golang > Go问答 > Go go/types.Info.Selections 怎样找到嵌入字段的真实对象

Go go/types.Info.Selections 怎样找到嵌入字段的真实对象

来源:17golang原创 2026-10-25 16:49:52 0浏览 收藏

做 Go 静态分析时,很多人会先从 types.Info.Types 查选择器的类型,结果却拿不到嵌入字段真正对应的对象。关键在于:x.f 这类非限定选择器由 types.Info.Selections 记录,值是 *types.Selection;再结合 Obj、Type 和 Index,才能把提升字段还原出来。

官方地址:https://pkg.go.dev/go/types

需要找到选择器最终指向的字段或方法时,初始化 Info.Selections,完成类型检查后按 *ast.SelectorExpr 查询;嵌入层级看 Selection.Index(),最终声明对象看 Selection.Obj(),表达式自身的类型看 Selection.Type()。

为什么只查 Info.Types 会漏掉真实对象

go/types.Info 把不同种类的结果放在不同映射里。Types 面向表达式类型和常量值,Selections 专门记录不属于限定标识符的选择器表达式,例如 p.Name 或 p.Method()。当 Name 来自嵌入字段时,表达式类型和声明对象并不是同一个观察角度。

尤其要注意,pkg.Name 这样的限定标识符不进入 Selections。它属于包名加标识符的解析场景,应该从 Uses 等信息查看;不要看到所有带点的语法都去查 Selections。

SelectorExpr、Info.Types、Info.Selections 与 Selection 对象之间的静态关系说明图
图1:go/types 映射关系说明图,区分表达式类型与选择器解析结果。

先把 Selections 映射交给类型检查器

下面的代码只做三件事:准备一段带嵌入字段的源码,创建 types.Info,再遍历语法树查询选择器。示例把 Selections、Types 和 Uses 都初始化出来,便于同时观察表达式类型与对象解析结果。

package main

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

const source = `
package demo

type Identity struct { ID int }
type User struct { Identity }

func use(u User) int {
    return u.ID
}
`

func main() {
    // FileSet 统一保存源码位置,parser 负责把文本变成 AST。
    fset := token.NewFileSet()
    file, err := parser.ParseFile(fset, "demo.go", source, 0)
    if err != nil {
        panic(err)
    }

    info := &types.Info{
        // Selections 保存 u.ID 这类非限定选择器的解析结果。
        Selections: make(map[&ast.SelectorExpr]*types.Selection),
        // Types 让后面能观察选择器表达式自身的类型。
        Types:      make(map[ast.Expr]types.TypeAndValue),
        // Uses 用于观察普通标识符引用到的声明对象。
        Uses:       make(map[*ast.Ident]types.Object),
    }
    conf := &types.Config{Importer: importer.Default()}
    if _, err := conf.Check("demo", fset, []*ast.File{file}, info); err != nil {
        // 类型检查失败时,Info 可能只包含不完整的已收集信息。
        panic(err)
    }

    ast.Inspect(file, func(node ast.Node) bool {
        selector, ok := node.(*ast.SelectorExpr)
        if !ok {
            return true
        }
        selection := info.Selections[selector]
        if selection == nil {
            // 限定标识符或未完成的检查结果不一定有 Selection。
            return true
        }
        // Obj 是最终声明对象,Type 是 selector 表达式的类型。
        fmt.Printf("%s -> %s / %s\\n", selector.Sel.Name,
            selection.Obj().Name(), selection.Type().String())
        return true
    })
}

这里最容易漏掉的是 Selections: make(...)。Info 只会收集调用方提供了映射的结果;映射为 nil 时,即使类型检查成功,也不要期待 Selections 自动填充。

用 Selection 的几个方法拆开“真实对象”

拿到 *types.Selection 后,不要把所有信息都当成字段声明本身。Obj() 返回选择器最终指向的 *types.Var 或 *types.Func;Type() 返回 x.f 这个表达式的类型;Recv() 描述选择器左侧接收者的类型。

Kind() 可以进一步区分字段值、方法值和方法表达式。对嵌入字段问题来说,最重要的是 Index():最后一项是声明字段或方法在声明类型中的索引,前面的项描述为到达它而经过的嵌入字段索引。

func describeSelection(sel *types.Selection) {
    // Index 的前置项描述嵌入字段链,最后一项定位声明类型中的成员。
    indexes := sel.Index()
    fmt.Printf("kind=%s recv=%s type=%s indirect=%t\\n",
        sel.Kind(), sel.Recv(), sel.Type(), sel.Indirect())

    // Obj 指向最终字段或方法;空值保护避免错误输入导致解引用失败。
    if obj := sel.Obj(); obj != nil {
        fmt.Printf("object=%s index=%v\\n", obj.Name(), indexes)
    }
}

例如 User 嵌入 Identity 后使用 u.ID,Obj() 指向 Identity 中声明的 ID,而不是凭空生成一个名为 ID 的新字段。Index() 让静态分析器知道中间经过了哪一层嵌入。

SelectorExpr p.Name、Selection.Index、嵌入字段链与最终对象属性的静态关系说明图
图2:Selection.Index 路径说明图,查看嵌入字段链与最终对象的对应边界。

空映射、限定标识符和 Indirect 怎么排查

如果查询不到选择结果,先按下面的顺序缩小范围:

  • 确认节点类型。只有 *ast.SelectorExpr 才能作为 Selections 的键,pkg.Name 这类限定标识符不在这个映射里。
  • 确认映射已初始化。Selections、Types 等字段是按需收集的,未提供 map 不会自动补齐。
  • 确认检查错误。类型错误会让收集结果不完整;调试工具可以展示错误,同时把已收集对象标记为可能不完整。
  • 确认读取的是同一个 AST 节点。重新解析或复制节点后,指针键已经不同,不能用另一个 AST 的 SelectorExpr 查询。
  • 单独解释 Indirect。Indirect() 只说明从接收者到成员的路径是否发生指针间接,不等于字段一定来自嵌入,也不替代 Index()。

这几项可以放进静态分析器的诊断信息中:先打印选择器文本,再打印 Kind、Obj、Type、Index 和 Indirect。这样比只输出一个对象名更容易定位“字段存在但路径判断错了”的问题。

写静态分析规则时该取哪一项

最终可以按任务选择观察面:

要回答的问题优先读取原因
选择器最终指向哪个声明Selection.Obj()返回字段或方法对象
x.f 表达式的类型是什么Selection.Type()反映选择器表达式自身的类型
中间经过哪些嵌入字段Selection.Index()前置索引描述嵌入路径,末项定位成员
接收者从什么类型开始Selection.Recv()描述选择器的接收者类型
是否发生指针间接Selection.Indirect()补充访问路径属性,不代替对象定位

如果只需要判断某个选择器是否来自嵌入层,读取 Index() 的长度和前置项即可;如果还要生成可跳转的声明位置,再把 Obj() 转成具体的 *types.Var 或 *types.Func 并关联其位置。

常见问题

为什么 Info.Types 里能看到类型,却没有 Selection?

因为这两个映射职责不同,而且 Info 只收集调用方提供的字段。检查 Selections 是否初始化,并确认当前 AST 节点确实是非限定选择器。

Selection.Obj() 和 Selection.Type() 为什么可能不同?

Obj() 是声明处的字段或方法对象,Type() 是当前选择表达式的类型;经过嵌入、方法值绑定或指针接收者处理后,两者承担的语义自然不同。

小结

查找 Go 选择器的真实对象,核心不是把所有带点语法都塞进一个映射,而是先区分限定标识符与非限定选择器,再从 Info.Selections 取得 Selection。对象看 Obj,表达式类型看 Type,嵌入路径看 Index,接收者和指针边界分别看 Recv 与 Indirect。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go jsontext Decoder SkipValue 怎样跳过大字段而不保留完整内容Go jsontext Decoder SkipValue 怎样跳过大字段而不保留完整内容
上一篇
Go jsontext Decoder SkipValue 怎样跳过大字段而不保留完整内容
Go Transport.CloseIdleConnections 清理空闲连接的使用边界
下一篇
Go Transport.CloseIdleConnections 清理空闲连接的使用边界
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    686次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    702次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    758次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    721次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    561次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码