英文:
go/packages.Load() returns different types.Named for identical code
问题
我正在尝试使用go/types.Identical
确定两种类型是否相同,但令人惊讶的是,通过不同的packages.Load
调用返回的相同代码片段的类型始终不同。
我对这些API做出了错误的假设吗?
package main
import (
"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")
}
}
英文:
I'm trying to determine if two types are identical with go/types.Identical
, and suprisingly enough, types of the same piece of code returned by different packages.Load
calls are always different.
Am I making a wrong assumption on those APIs?
package main
import (
"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
得分: 0
显然,有一篇隐藏的文档解释了所有这些内容(它没有附加到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包用于定位和描述包,golang.org/x/tools/go/loader包用于加载、解析和类型检查这些包。go/build.Package结构编码了太多与"go build"项目组织方式有关的信息,因此我们需要一种描述独立于底层构建系统的Go源代码包的数据类型。我们希望能够同时适用于go build、vgo以及其他构建系统(如Bazel和Blaze),从而可以构建在所有这些环境中都能工作的分析工具。在Google的Go社区中,像errcheck和staticcheck这样的工具基本上是不可用的,而且一些Google内部用于Go的工具也无法在外部使用。这个新包通过查询每个构建系统来提供一种统一的方式来获取包的元数据,可选择性地支持它们首选的命令行表示法,以便工具与用户的构建环境紧密集成。Metadata查询函数执行适用于当前工作区的外部查询工具。
加载包始终返回完整的导入图,即使您只想获取有关单个包的信息,因为我们当前支持的所有构建系统({go,vgo} list和blaze/bazel基于aspect的查询)的查询机制都无法在不访问其所有依赖项的情况下提供有关一个包的详细信息,因此提供传递信息没有额外的渐进成本。(这个属性对于假设的第五个构建系统可能不成立。)
在调用TypeCheck时,所有初始包以及任何传递依赖于其中之一的包都必须从源代码加载。考虑A->B->C->D->E的情况:如果A和C是初始包,则必须从源代码加载A、B和C;D可以从导出数据加载,E可能根本不加载(尽管D的导出数据可能提到它,因此可能为其创建并公开一个types.Package)。
旧的加载器具有按包抑制函数体类型检查的功能,主要用于减少获取导入包类型信息的工作量。现在,由于导入由导出数据满足,这种优化似乎不再必要。
尽管早期尝试过,但旧的加载器没有利用导出数据,而是始终使用等效的WholeProgram模式。这是由于混合源代码和导出数据包的复杂性(现在通过上述向上遍历解决了),以及导出数据文件几乎总是缺失或过时。现在,由于"go build"支持缓存,所有底层构建系统都可以保证在合理(摊销)的时间内生成导出数据。
构建系统合成的“main”测试包现在被报告为一流的包,避免了客户端(如go/ssa)重新发明此生成逻辑的需要。
go/packages在处理内部测试方面比旧的加载器更简单。内部测试是由测试文件加上库文件组成的包。旧的加载器通过称为“增强”的两阶段变异过程构建内部测试:首先构建和类型检查所有普通库包,并对依赖于它们的包进行类型检查;然后将更多(测试)文件添加到包中并再次进行类型检查。这种两阶段方法存在四个主要问题:1)在处理测试时,加载器修改了库包,没有办法让客户端应用程序同时看到测试包和库包;一个会变异为另一个。2)由于测试文件可以在包的库部分中声明对类型的额外方法,库部分中的方法调用分派受到测试文件的影响。这应该是这些包在逻辑上不同的线索。3)这种“增强”模型假设每个库包最多只有一个内部测试,这对于使用"go build"的项目是正确的,但对于其他构建系统则不是。4)由于测试处理的两阶段性质,必须在增强之前处理导入库包的所有包,从而强制使用“一次性”API,并阻止客户端在整个程序模式下多次调用Load。 (对于不同的原因,TypeCheck模式也有类似的一次性限制。)
这个包的早期草案支持“多次调用”的操作。尽管它允许客户端进行一系列调用(或并发调用)以逐步构建Packages的图形,但它的价值有限:它使API变得复杂(因为它允许某些选项在调用之间变化,但其他选项不变),它使实现变得复杂,它无法在Types模式下工作,如上所述,而且它比进行一次组合调用(当可能时)效率低下。在我们检查过的客户端中,没有一个进行了多次加载调用,而不能轻松且令人满意地修改为仅进行单次调用。但是,可能需要更改应用程序。例如,ssadump命令加载用户指定的包以及运行时包。简单地将“runtime”附加到用户提供的列表中是很诱人的,但如果用户指定了类似[a.go b.go]的临时包,这样做是行不通的。因此,ssadump不再请求运行时包,而是在用户指定的包的依赖项中寻找它,并在找不到时发出错误提示。
英文:
Apparently, there is a piece of hidden doc explaining all these (it attaches to nothing, so it's not on godoc): https://cs.opensource.google/go/x/tools/+/master:go/packages/doc.go;l=75
> Motivation and design considerations
>
> The new package's design solves problems addressed by two existing
> packages: go/build, which locates and describes packages, and
> golang.org/x/tools/go/loader, which loads, parses and type-checks
> them. The go/build.Package structure encodes too much of the 'go
> build' way of organizing projects, leaving us in need of a data type
> that describes a package of Go source code independent of the
> underlying build system. We wanted something that works equally well
> with go build and vgo, and also other build systems such as Bazel and
> Blaze, making it possible to construct analysis tools that work in all
> these environments. Tools such as errcheck and staticcheck were
> essentially unavailable to the Go community at Google, and some of
> Google's internal tools for Go are unavailable externally. This new
> package provides a uniform way to obtain package metadata by querying
> each of these build systems, optionally supporting their preferred
> command-line notations for packages, so that tools integrate neatly
> with users' build environments. The Metadata query function executes
> an external query tool appropriate to the current workspace.
>
> Loading packages always returns the complete import graph "all the way
> down", even if all you want is information about a single package,
> because the query mechanisms of all the build systems we currently
> support ({go,vgo} list, and blaze/bazel aspect-based query) cannot
> provide detailed information about one package without visiting all
> its dependencies too, so there is no additional asymptotic cost to
> providing transitive information. (This property might not be true of
> a hypothetical 5th build system.)
>
> In calls to TypeCheck, all initial packages, and any package that
> transitively depends on one of them, must be loaded from source.
> Consider A->B->C->D->E: if A,C are initial, A,B,C must be loaded from
> source; D may be loaded from export data, and E may not be loaded at
> all (though it's possible that D's export data mentions it, so a
> types.Package may be created for it and exposed.)
>
> The old loader had a feature to suppress type-checking of function
> bodies on a per-package basis, primarily intended to reduce the work
> of obtaining type information for imported packages. Now that imports
> are satisfied by export data, the optimization no longer seems
> necessary.
>
> Despite some early attempts, the old loader did not exploit export
> data, instead always using the equivalent of WholeProgram mode. This
> was due to the complexity of mixing source and export data packages
> (now resolved by the upward traversal mentioned above), and because
> export data files were nearly always missing or stale. Now that 'go
> build' supports caching, all the underlying build systems can
> guarantee to produce export data in a reasonable (amortized) time.
>
> Test "main" packages synthesized by the build system are now reported
> as first-class packages, avoiding the need for clients (such as
> go/ssa) to reinvent this generation logic.
>
> One way in which go/packages is simpler than the old loader is in its
> treatment of in-package tests. In-package tests are packages that
> consist of all the files of the library under test, plus the test
> files. The old loader constructed in-package tests by a two-phase
> process of mutation called "augmentation": first it would construct
> and type check all the ordinary library packages and type-check the
> packages that depend on them; then it would add more (test) files to
> the package and type-check again. This two-phase approach had four
> major problems: 1) in processing the tests, the loader modified the
> library package, leaving no way for a client application to see
> both the test package and the library package; one would mutate
> into the other. 2) because test files can declare additional methods
> on types defined in the library portion of the package, the
> dispatch of method calls in the library portion was affected by the
> presence of the test files. This should have been a clue that the
> packages were logically different. 3) this model of "augmentation"
> assumed at most one in-package test per library package, which is
> true of projects using 'go build', but not other build systems. 4)
> because of the two-phase nature of test processing, all packages that
> import the library package had to be processed before augmentation,
> forcing a "one-shot" API and preventing the client from calling Load
> in several times in sequence as is now possible in WholeProgram mode.
> (TypeCheck mode has a similar one-shot restriction for a different
> reason.)
>
> Early drafts of this package supported "multi-shot" operation.
> Although it allowed clients to make a sequence of calls (or concurrent
> calls) to Load, building up the graph of Packages incrementally, it
> was of marginal value: it complicated the API (since it allowed some
> options to vary across calls but not others), it complicated the
> implementation, it cannot be made to work in Types mode, as explained
> above, and it was less efficient than making one combined call (when
> this is possible). Among the clients we have inspected, none made
> multiple calls to load but could not be easily and satisfactorily
> modified to make only a single call. However, applications changes may
> be required. For example, the ssadump command loads the user-specified
> packages and in addition the runtime package. It is tempting to
> simply append "runtime" to the user-provided list, but that does not
> work if the user specified an ad-hoc package such as [a.go b.go].
> Instead, ssadump no longer requests the runtime package, but seeks it
> among the dependencies of the user-specified packages, and emits an
> error if it is not found.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论