gotypesalias=1 does not seem to get used correctly for deps on Go 1.23¶
Recently, I have help to troubleshooting the Go type alias issue inside packages.Load
, which relates x/tools
, go list
and cmd/compile
. My original CL is correct, but I got confused when I found the exportdata contains all aliases regardless gotypealias
setting. Then, tim has shepherded my CL by another CL to fix it.
Feel a bit frustrated when I saw the email when I got up. But it helps me a lot to learn how go toolchain and cmd/compile
works. Think twice before you act!
Issue Details¶
When using pacakges.Load
to load packages, the GODEBUG=gotypesalias=1
won't take effects for dep package pkg2
. The type from pkg2
should be alias instead of the original type.
-- go.mod --
module example.com/ssa-example
go 1.23
require golang.org/x/tools v0.27.0
require (
golang.org/x/mod v0.22.0 // indirect
golang.org/x/sync v0.9.0 // indirect
)
-- pkg1/pkg1.go --
package pkg1
import "example.com/ssa-example/pkg2"
type Alias1 = int32
var Var1 Alias1
var Var2 pkg2.Alias2
-- pkg2/pkg2.go --
package pkg2
type Alias2 = int64
Packages.Load relies on 'go list'¶
The packages.Load
relies on 'go list' to get pacakge information. It's reasonable because resuing code from go toolchain. When loading packages, it will run the following 'go list' command(depends on the modes).
go list -e -json=Name,ImportPath,Error,Dir,GoFiles,IgnoredGoFiles,IgnoredOtherFiles,CFiles,CgoFiles,CXXFiles,MFiles,HFiles,FFiles,SFiles,SwigFiles,SwigCXXFiles,SysoFiles,CompiledGoFiles,DepOnly,Imports,ImportMap,Export,Module -compiled=true -test=false -export=true -deps=true -find=false -pgo=off -- ./pkg1
go list
tries to reuse the exported data. # don't use 'rm $(go env GOCACHE)/*'
# as it might delete your /*
go env GOCACHE
rm -r ~/Library/Caches/go-build/*
The -json
field information could be found by go help list
. The Export
filed is the file path which contains exportdata of the package.
go list output example
{
"Dir": "/Users/yuchen.xie/workspace/CD/ssa-example/pkg2",
"ImportPath": "example.com/ssa-example/pkg2",
"Name": "pkg2",
"Export": "/Users/yuchen.xie/Library/Caches/go-build/00/007812fbe54f26bd3164a1006097bc8e585ca6d7e2c2dc09d000699ebae90327-d",
"Module": {
"Path": "example.com/ssa-example",
"Main": true,
"Dir": "/Users/yuchen.xie/workspace/CD/ssa-example",
"GoMod": "/Users/yuchen.xie/workspace/CD/ssa-example/go.mod",
"GoVersion": "1.23"
},
"DepOnly": true,
"GoFiles": [
"pkg2.go"
],
"CompiledGoFiles": [
"pkg2.go"
],
"Imports": [
"example.com/ssa-example/pkg3"
]
}
{
"Dir": "/Users/yuchen.xie/workspace/CD/ssa-example/pkg1",
"ImportPath": "example.com/ssa-example/pkg1",
"Name": "pkg1",
"Export": "/Users/yuchen.xie/Library/Caches/go-build/27/2763ca28b8e616cc654df2738afcbfd5429187ed2422061bb197d1726b8c3ffe-d",
"Module": {
"Path": "example.com/ssa-example",
"Main": true,
"Dir": "/Users/yuchen.xie/workspace/CD/ssa-example",
"GoMod": "/Users/yuchen.xie/workspace/CD/ssa-example/go.mod",
"GoVersion": "1.23"
},
"GoFiles": [
"pkg.go"
],
"CompiledGoFiles": [
"pkg.go"
],
"Imports": [
"example.com/ssa-example/pkg2"
]
}
The exportdata is the source of truth for packages loading, so there might be something inside it. By parsing them, I found the alias type information is missing in exportdata generated by go1.23. Hence, the issue is caused by the component who generates the exportdata file.
!<arch>
__.PKGDEF 0 0 0 644 609 `
go object darwin arm64 go1.23.3 GOARM64=v8.0 X:regabiwrappers,regabiargs,coverageredesign
build id "AApY-RhG_Ow7G2a6MwG8/gEGDXHQZLVRsYsnuUtfx"
$$B
u
$@v|?????????????????
kg1example.com/ssa-example/pkg2pkg2example.com/ssa-example/pkg1/Users/yuchen.xie/workspace/CD/ssa-example/pkg1/pkg.goAlias1Var1Var2 ?k????
$$
_go_.o 0 0 0 644 1067 `
go object darwin arm64 go1.23.3 GOARM64=v8.0 X:regabiwrappers,regabiargs,coverageredesign
build id "AApY-RhG_Ow7G2a6MwG8/gEGDXHQZLVRsYsnuUtfx"
!
go120ld?k????~??????$NNNNj???"??example.com/ssa-example/pkg2go:cuinfo.producer.example.com/ssa-example/pkg1go:cuinfo.packagename.example.com/ssa-example/pkg1example.com/ssa-example/pkg1.Var1go:info.int32example.com/ssa-example/pkg1.Var2go:i2!96H!?`4<autogenerated>/Users/yuchen.xie/workspace/CD/ssa-example/pkg1/pkg.go`?SԻ!?
,22drv???-??????-?????????????????
example.com/ssa-example/pkg1.Var1
example.com/ssa-example/pkg1.Var2 -shared regabipkg1
!<arch>
__.PKGDEF 0 0 0 644 799 `
go object darwin arm64 devel go1.24-d20a4c2 Thu Oct 10 20:07:10 2024 +0000 GOARM64=v8.0 X:regabiwrappers,regabiargs,coverageredesign,aliastypeparams
build id "Q--gnzNdp1xe3K8whwha/w_XeWYiN9Jhbq_XEO-mM"
$$B
u
!%% $@v|??????????
#),7BMXY[]^fnv~pkg1example.com/ssa-example/pkg2pkg2example.com/ssa-example/pkg1/Users/yuchen.xie/workspace/CD/ssa-example/pkg1/pkg.goAlias1Var1/Users/yuchen.xie/workspace/CD/ssa-example/pkg2/pkg2.goAlias2Var2
̊v- ?
$$
_go_.o 0 0 0 644 1189 `
go object darwin arm64 devel go1.24-d20a4c2 Thu Oct 10 20:07:10 2024 +0000 GOARM64=v8.0 X:regabiwrappers,regabiargs,coverageredesign,aliastypeparams
build id "Q--gnzNdp1xe3K8whwha/w_XeWYiN9Jhbq_XEO-mM"
!
go120ld̊v- ?????999c???????=a??example.com/ssa-example/pkg2go:cuinfo.producer.example.com/ssa-example/pkg1go:cuinfo.packagename.example.com/ssa-example/pkg1example.com/ssa-example/pkg1.Var1go:info.int32example.com/ssa-example/pkg1.Var2go:info.int64<autogenerated>/Users/yuchen.xie/workspace/CD/ssa-example/pkg1/pkg.go/Users/yuchen.xie/workspace/CD/ssa-exa2!le/pkg2/pkg2.go`??l?q<E`96H7~!?`
,22drv???-??????-?????????????????
example.com/ssa-example/pkg1.Var1
example.com/ssa-example/pkg1.Var2 -shared regabipkg1
cmd/compile¶
By checking the code, I found it is go tool compile
who generates the exportdata file, as the readme reads:
In addition to writing a file of object code for the linker, the compiler also writes a file of "export data" for downstream compilation units
// src/cmd/compile/internal/gc/obj.go
func dumpCompilerObj(bout *bio.Writer) {
printObjHeader(bout)
noder.WriteExports(bout)
}
writePkgStub¶
Then as I don't understand how go tool compile
works, I tried to insert some println.
Then I have found the Var2
is resolved as int64
instead of Alias2
. It means that the issue happened before writing.
collectObjects¶
Then, I tried to print the variables during typecheck, which checks the types for the AST parsing from the original files.
func (check *Checker) collectObjects() {
values := syntax.UnpackListExpr(s.Values)
for i, name := range s.NameList {
obj := NewVar(name.Pos(), pkg, name.Value, nil)
+ fmt.Printf("resolver: declare var %s, type %s\n", name.Value, obj.Type())
@ @
check.declarePkgObj(name, obj, d)
+ fmt.Printf("resolver: check type %s\n", obj.Type())
# example.com/ssa-example/pkg2
resolver: Alias2 Alias2 pkg2example.com/ssa-example/pkg2.Alias2 Alias2
# example.com/ssa-example/pkg1
resolver: Alias1 Alias1 pkg1resolver: declare var Var1, type %!s(<nil>)
resolver: check type %!s(<nil>)
resolver: declare var Var2, type %!s(<nil>)
resolver: check type %!s(<nil>)
example.com/ssa-example/pkg1.Alias1 Alias1
example.com/ssa-example/pkg1.Alias1 Var1
int64 Var2
The result shows the Var2
has int64
type, which means when associating type with var, it doesn't respect the type alias.
varDecl¶
Then, I tried to insert a println during variable declaration.
if typ != nil {
obj.typ = check.varType(typ)
+ if tp, ok := typ.(*syntax.Name); ok {
+ fmt.Println("decl: syntax.Name", tp.Value)
+ }
+ fmt.Println("decl: varDecl", obj.name, obj.typ.String())
+ if se, ok := typ.(*syntax.SelectorExpr); ok {
+ info := se.X.GetTypeInfo()
+ tps := ""
+ if info.Type != nil {
+ tps = info.Type.String()
+ }
+ fmt.Println(se.Sel.Value, tps, info.Value, "end")
+ }
+ if obj.name == "Var2" {
+ fmt.Println(reflect.TypeOf(typ))
+ }
The println change in varDecl
doesn't convey new useful information as the type here is wrong as well. Hence, I need to check the typeDecl
to see whether there is something wrong.
typeDecl¶
func (check *Checker) typeDecl(obj *TypeName, tdecl *syntax.TypeDecl, def *TypeName) {
assert(obj.typ == nil)
// Only report a version error if we have not reported one already.
versionErr := false
var rhs Type
check.later(func() {
if t := asNamed(obj.typ); t != nil { // type may be invalid
check.validType(t)
}
// If typ is local, an error was already reported where typ is specified/defined.
_ = !versionErr && check.isImportedConstraint(rhs) && check.verifyVersionf(tdecl.Type, go1_18, "using type constraint %s", rhs)
}).describef(obj, "validType(%s)", obj.Name())
+ fmt.Println("typeDecl: ", obj.Name(), reflect.TypeOf(tdecl.Type), tdecl.Alias)
+ if strings.Contains(obj.Name(), "Alias") {
+ if n, ok := tdecl.Type.(*syntax.Name); ok {
+ fmt.Println(n.Value)
+ }
+ }
# example.com/ssa-example/pkg2
typeDecl: A *syntax.Name false
typeDecl: Alias2 *syntax.Name true
int64
# example.com/ssa-example/pkg1
typeDecl: Alias1 *syntax.Name true
int32
1.obj type: Var1 example.com/ssa-example/pkg1.Alias1
decl: &{Alias1 {{{0x14000109ec0 7 10}} {{0x140004229c0 <nil> 2}}}} <nil>
1.obj type: Var2 int64
decl: &{0x14000424aa0 0x14000424b40 {{{0x14000109ec0 9 14}} {{0x1034de140 <nil> 2}}}} <nil>
example.com/ssa-example/pkg2
example.com/ssa-example/pkg1
The output shows during type declaration , the type is wrong as well. As a result, we need to find its caller.
selector¶
The Alias2
is accessed via pkg2.Alias2
, so it's a selector type at the beginning. Try to add some println in selecotr part sounds great.
@@ -712,6 +713,19 @@ func (check *Checker) selector(x *operand, e *syntax.SelectorExpr, def *TypeName
check.objDecl(exp, nil)
} else {
exp = pkg.scope.Lookup(sel)
+ if strings.Contains(exp.Name(), "Alias") {
+ if tn, ok := exp.(*TypeName); ok {
+ fmt.Printf(`selector:
+ from pkg scope: %s
+ loop up sel name: %s
+ exp name: %s
+ isAlias: %t
+ found obj type: %s
+ found obj underlying type: %s\n`,
+ pkg.name, sel, exp.Name(),
+ tn.IsAlias(), tn.typ, tn.typ.Underlying())
+ }
+ }
From here we can know that during selector, the type is wrong as well. The value should come from the caller side as well.
newAlias¶
When I tried to add println in alias, I have found that both Alias1
and Alias2
have been defined as alias type correctly. This really confused me, if the Alias2
is defined as an alias, why in the following usages(as the output shows above) ALL use a wrong type(underlying type instead of alias)?
func (check *Checker) newAlias(obj *TypeName, rhs Type) *Alias {
check.needsCleanup(a)
}
+ println("assign alias type to object", obj.name)
+ println("newAlias", reflect.TypeOf(rhs).String())
+ if tn, ok := rhs.(*Basic); ok {
+ fmt.Printf(`newAlias:
+ obj.type: %s
+ name: %s,
+ info: %d,
+ kind: %d
+ `,
+ obj.typ.String(),
+ tn.name,
+ tn.info,
+ tn.kind,
+ )
assign alias type to object Alias2
newAlias *types2.Basic
newAlias:
obj.type: example.com/ssa-example/pkg2.Alias2
name: invalid type,
info: 0,
kind: 0
assign alias type to object Alias1
newAlias *types2.Basic
newAlias:
obj.type: example.com/ssa-example/pkg1.Alias1
name: invalid type,
info: 0,
kind: 0
As a result, there must be something wrong between type check and resolving variable types. Let's verify what's the Alias2 type after typecheck, and it shows that the types are all resolved correctly.
pkg, err := conf.Check(base.Ctxt.Pkgpath, files, info)
+ {
+ println(pkg.String())
+ a2 := pkg.Scope().Lookup("Alias2")
+ if a2 != nil {
+ if a, ok := a2.(*types2.TypeName); ok {
+ println("++ Alias2 is", a.IsAlias(), a.Type().String(), a.Type().Underlying().String())
+ }
+ println("++ check Alias2 type after check", a2.Type().String())
+ }
+ a1 := pkg.Scope().Lookup("Alias1")
+ if a1 != nil {
+ if a, ok := a1.(*types2.TypeName); ok {
+ println("++ Alias1 is", a.IsAlias(), a.Type().String(), a.Type().Underlying().String())
+ }
+ println("++ check Alias1 type after check", a1.Type().String())
+ }
+ }
This is quite weird, why the type is correct after typecheck but wrong during type resolving. This shouldn't happen, and I feel very confused.
Root Cause¶
By chance, I have checked the log and found that the order of printed messages are not expected. Then, I released the compile tool is triggered twice to compile pkg1 and pkg2! The correct type of Alias2
happens during the compilation for pkg2, and the failure happens during the compilation for pkg1 because it doesn't read the exportdata of pkg2
correctly.
Then I found inside the reader the enableAlias
field is disabled for previous issue. Enabling it fixes the issue.
func ReadPackage(ctxt *types2.Context, imports map[string]*types2.Package, input pkgbits.PkgDecoder) *types2.Package {
pr := pkgReader{
PkgDecoder: input,
// Currently, the compiler panics when using Alias types.
// TODO(gri) set to true once this is fixed (issue #66873)
- enableAlias: true,
+ enableAlias: true,
What confused me and haven't persisted the correct fix?¶
I have changed my fix because I found that the writer(types2) generates the alias inside exportdata regardless gotypesalias setting. Then I feel it's necessary to check it in reader
, otherwise the reader won't respect the gotypesalias when it's disabled. Tool compile will generate exportdata with alias from imported packages as well.
However, it doesn't matter for this issue because x/tools packages.Load
is the final reader and it resolves the gotypesalias properly, so it will decide to respect or skip the alias inside the exportdata. The correct direction is that the exportdata contains information to reflect the details of original packages as many as possible. Package loader could load the package information based on its configuration.
I'm a bit frustrated that I missed this chance to submit a fix commit, and this should blame my own knowledge. I should always focus on the original goal, and then step further to see how x/tools works, and I could win.
Good luck and be pataient next time!