为了账号安全,请及时绑定邮箱和手机立即绑定

go/packages.Load() 返回不同的类型。为相同的代码命名

go/packages.Load() 返回不同的类型。为相同的代码命名

Go
犯罪嫌疑人X 2023-01-03 15:52:45
我试图确定两种类型是否与 相同go/types.Identical,而且令人惊讶的是,不同packages.Load调用返回的同一段代码的类型总是不同的。我是否对这些 API 做出了错误的假设?package mainimport (    "fmt"    "go/types"    "golang.org/x/tools/go/packages")func getTimeTime() *types.Named {    pkgs, err := packages.Load(&packages.Config{        Mode: packages.NeedImports | packages.NeedSyntax | packages.NeedTypes | packages.NeedDeps | packages.NeedTypesInfo,        Overlay: map[string][]byte{            "/t1.go": []byte(`package t            import "time"            var x time.Time`),        },    }, "file=/t1.go")    if err != nil {        panic(err)    }    for _, v := range pkgs[0].TypesInfo.Types {        return v.Type.(*types.Named) // named type of time.Time    }    panic("unreachable")}func main() {    t1, t2 := getTimeTime(), getTimeTime()    if !types.Identical(t1, t2) {        fmt.Println(t1, t2, "are different")    }}
查看完整描述

1 回答

?
慕标5832272

TA贡献1966条经验 获得超4个赞

显然,有一个隐藏文档解释了所有这些(它没有附加任何东西,所以它不在 godoc 上):https://cs.opensource.google/go/x/tools/+/master:go/packages/doc .go;l=75

动机和设计考虑

新包的设计解决了两个现有包所解决的问题:定位和描述包的 go/build,以及加载、解析和类型检查它们的 golang.org/x/tools/go/loader。go/build.Package 结构编码了太多组织项目的“go build”方式,使我们需要一种数据类型来描述独立于底层构建系统的 Go 源代码包。我们想要一些与 go build 和 vgo 以及其他构建系统(如 Bazel 和 Blaze)同样适用的东西,从而可以构建适用于所有这些环境的分析工具。errcheck 和 staticcheck 等工具在 Google 的 Go 社区基本上是不可用的,并且 Google 的一些内部 Go 工具在外部是不可用的。这个新包提供了一种统一的方法来通过查询每个构建系统来获取包元数据,并可选择支持它们首选的包命令行符号,以便工具与用户的构建环境巧妙地集成。元数据查询功能执行适合当前工作区的外部查询工具。

加载包总是“一直向下”返回完整的导入图,即使你想要的只是关于单个包的信息,因为我们目前支持的所有构建系统的查询机制({go,vgo} 列表和 blaze/ bazel aspect-based query) 不能在不访问其所有依赖项的情况下提供有关一个包的详细信息,因此提供传递信息没有额外的渐近成本。(此属性可能不适用于假设的第 5 次构建系统。)

在对 TypeCheck 的调用中,所有初始包以及任何可传递地依赖于其中之一的包都必须从源加载。考虑 A->B->C->D->E:如果 A、C 是初始值,则 A、B、C 必须从源加载;D 可能会从导出数据中加载,而 E 可能根本不会加载(尽管 D 的导出数据可能会提到它,因此可能会为它创建并公开一个 types.Package。)

旧的加载器有一个特性,可以在每个包的基础上抑制函数体的类型检查,主要是为了减少为导入的包获取类型信息的工作。既然进口已经满足出口数据,那么优化似乎不再是必要的。

尽管有一些早期的尝试,旧的加载器并没有利用导出数据,而是始终使用等效的 WholeProgram 模式。这是由于混合源和导出数据包的复杂性(现在通过上面提到的向上遍历解决),并且因为导出数据文件几乎总是丢失或过时。现在'go build'支持缓存,所有底层构建系统都可以保证在合理的(摊销的)时间内产生导出数据。

由构建系统合成的测试“主”包现在被报告为一流的包,避免了客户端(如 go/ssa)重新发明这一生成逻辑的需要。

go/packages 比旧加载程序更简单的一种方式是处理包内测试。包内测试是由被测库的所有文件以及测试文件组成的包。旧的加载器通过称为“增强”的两阶段变异过程构建包内测试:首先它会构建并类型检查所有普通的库包,然后对依赖它们的包进行类型检查;然后它会向包中添加更多(测试)文件并再次进行类型检查。这种两阶段方法有四个主要问题:1)在处理测试时,加载程序修改了库包,客户端应用程序无法同时看到测试包和库包;一个会变异成另一个。2) 因为测试文件可以在包的库部分中定义的类型上声明其他方法,所以库部分中方法调用的调度受到测试文件存在的影响。这应该是包在逻辑上不同的线索。3) 这种“增强”模型假定每个库包最多进行一次包内测试,这适用于使用“go build”的项目,但不适用于其他构建系统。4) 由于测试处理的两阶段性质,所有导入库包的包都必须在扩充之前处理,
强制使用“一次性”API 并阻止客户端
按顺序多次调用 Load,这在 WholeProgram 模式下现在是可能的。(出于不同的原因,TypeCheck 模式具有类似的一次性限制。)

该软件包的早期草稿支持“多镜头”操作。尽管它允许客户端对 Load 进行一系列调用(或并发调用),逐步构建包图,但它的价值很小:它使 API 复杂化(因为它允许某些选项在调用之间变化,但不允许其他选项变化) ,它使实现变得复杂,不能使它在类型模式下工作,如上所述,并且它比进行一次组合调用(如果可能)效率低。在我们检查过的客户中,没有一个对加载进行了多次调用,但无法轻松且令人满意地修改为仅进行一次调用。但是,可能需要更改应用程序。例如,ssadump 命令加载用户指定的包以及运行时包。简单地附加“运行时”很诱人 添加到用户提供的列表中,但如果用户指定了一个临时包,例如 [a.go b.go],那将不起作用。相反,ssadump 不再请求运行时包,而是在用户指定包的依赖项中寻找它,如果找不到则发出错误。


查看完整回答
反对 回复 2023-01-03
  • 1 回答
  • 0 关注
  • 198 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信