golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
120.1k stars 17.24k forks source link

go/types: Check complain about type is not the same #67131

Closed bluecivet closed 1 week ago

bluecivet commented 2 weeks ago

Go version

go1.21 linux/arm64

Output of go env in your module/workspace:

GO111MODULE=''
GOARCH='amd64'
GOBIN=''
GOCACHE='/home/zhilong/.cache/go-build'
GOENV='/home/zhilong/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMODCACHE='/home/zhilong/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/home/zhilong/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/lib/go-1.21'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr/lib/go-1.21/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.21.4'
GCCGO='gccgo'
GOAMD64='v1'
AR='ar'
CC='gcc'
CXX='g++'
CGO_ENABLED='1'
GOMOD='/home/zhilong/goExperiment/testAST/pa/go.mod'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build2290222581=/tmp/go-build -gno-record-gcc-switches'

What did you do?

I have the following project

.:
     pa  pb  testType

./pa:
     aaa  bbb  go.mod

./pa/aaa:
     outter.go

./pa/bbb:
     constant.go  inner_linux.go  inner_linux_zos.go

./pb:
     go.mod  inner  main.go  pb

./pb/inner:
     inner.go

./testType:
     go.mod  go.sum  main.go

pa/bbb/constant.go

package bbb

const RANDOM=10

type IntAlias int

pa/bbb/inner_linux.go

package bbb

type Info struct {
        Abc int
        Efg int
        Inner InnerInfo
}

type InnerInfo struct {
        Innera int
        Innerb int
}

func (info *Info) String() string {
//      b, _ := json.MarshalIndent(info, "", " ")
//return string(b)
return ""
}

func Add(a IntAlias, b IntAlias, info *Info) {
        println("this is pa aaa Add() a = ", a, " b = ", b, " info = ", info.String())
}

func Sub(a int , b int, info *Info) {
        println("this is pa aaa Sub() a = ", a, " b = ", b, " info = ", info.String())
}

func Mul(a int, info Info) {
        println("this is pa aaa Mul(), a = ", a, " info = ", info.String())
}

pa/bbb/inner_linux_zos.go

//go:build zos

// Tags altered by Wharf (added zos)
//
package bbb

// import "encoding/json"

type Info struct {
        Abc   int
        Efg   int
        Inner InnerInfo
}

type InnerInfo struct {
        Innera int
        Innerb int
}

func (info *Info) String() string {

    return ""
}

func Add(a IntAlias, b IntAlias, info *Info) {
      println("this is pa aaa Add() a = ", a, " b = ", b, " info = ", info.String())
}

func Sub(a int, b int, info *Info) {
        println("this is pa aaa Sub() a = ", a, " b = ", b, " info = ", info.String())
}

func Mul(a int, info Info) {
        println("this is pa aaa Mul(), a = ", a, " info = ", info.String())
}

pa/aaa/outter.go

//go:build zos

package bbb

type Info struct {
        Abc   int
        Efg   int
        Inner InnerInfo
}

type InnerInfo struct {
        Innera int
        Innerb int
}

func (info *Info) String() string {
    return ""
}

//func Add(a IntAlias, b IntAlias, info *Info) {
//      println("this is pa aaa Add() a = ", a, " b = ", b, " info = ", info.String())
// }

func Sub(a int, b int, info *Info) {
        println("this is pa aaa Sub() a = ", a, " b = ", b, " info = ", info.String())
}

func Mul(a int, info Info) {
        println("this is pa aaa Mul(), a = ", a, " info = ", info.String())
}

pb/inner/inner.go

package inner

import "pa/aaa"
import "pa/bbb"

func CallToAdd(a int, b int, info *bbb.Info) {
        println("pb inner CallToAdd()")
        aaa.Add(a, b, info)
}

func CallToSub(a int, b int, info *bbb.Info) {
        println("pb inner CallToSub")
        aaa.Sub(a, b, info)
}

func CallToMul(a int, info bbb.Info) {
        println("pb inner CallToMul()")
        aaa.Mul(a, info)
}

pa/main.go

package main

import "pb/inner"
import "pa/bbb"

func main() {
        println("start main")
        var info1 = &bbb.Info{
                Abc: 123,
                Efg: 456,
                Inner: bbb.InnerInfo{
                        Innera: 111,
                        Innerb: 222,
                },
        }

        var info2 = bbb.Info{
                Abc: 132,
                Efg: 564,
                Inner: bbb.InnerInfo{
                        Innera: 555,
                        Innerb: 666,
                },
        }

        inner.CallToAdd(1, 2, info1)
        inner.CallToSub(1, 2, info1)
        inner.CallToMul(1, info2)

        println("finish main")
}

testType/main.go

package main

import (
        "fmt"
//      "reflect"
        "go/types"
        "go/token"
        "go/ast"
        "go/parser"

        "golang.org/x/tools/go/packages"

)

var globalpkgs []*packages.Package
type MyImporter struct{}

func (importer MyImporter) Import(path string)(*types.Package, error) {
        for _, pkg := range globalpkgs{
//              fmt.Println("pkg = ", pkg.ID, " ", pkg)
                if pkg.ID == path {
                        return pkg.Types, nil
                }
        }
        return nil, nil
}

func setPkgsType(id string, types *types.Package) {
        for _, pkg := range globalpkgs {
                if pkg.ID == id {
                        fmt.Println("find id = ", id, " setting type: ", types)
                        pkg.Types = types
                }
        }
}

func main() {
        cfg := &packages.Config{Mode: packages.NeedFiles | packages.NeedSyntax | packages.NeedDeps | packages.NeedImports | packages.NeedTypes | packages.NeedName}
        pkgs, err := packages.Load(cfg, "pa/aaa", "pa/bbb", "pb/inner")
        fmt.Println("load err = ", err, "pkgs = ", pkgs)
        globalpkgs = pkgs

        fmt.Println("pkgs[0] = ", pkgs[0].Name, " pkgs[0].Types = ", pkgs[0].Types, "type name = ",pkgs[0].Types.Name)

        fset := token.NewFileSet()
        aaaoutterAST, err := parser.ParseFile(fset,"/home/zhilong/goExperiment/testAST/pa/aaa/outter.go", nil, 0)
        fmt.Println("parseFile err = ", err, " fset = ", fset)
        bbbinnerAST, err := parser.ParseFile(fset,"/home/zhilong/goExperiment/testAST/pa/bbb/inner_linux.go", nil, 0)
        fmt.Println("parseFile err = ", err, " fset = ", fset)

        bbbinnerZosAST, err := parser.ParseFile(fset,"/home/zhilong/goExperiment/testAST/pa/bbb/inner_linux_zos.go", nil, 0)
        bbbConstantAST, err := parser.ParseFile(fset, "/home/zhilong/goExperiment/testAST/pa/bbb/constant.go", nil, 0)
        fmt.Println("bbb/constant.go parseFile err = ", err, " fset = ", fset)

        pbfset := token.NewFileSet()
        pbinnerAST, err := parser.ParseFile(pbfset,"/home/zhilong/goExperiment/testAST/pb/inner/inner.go", nil, 0)
        fmt.Println("parseFile err = ", err, " fset = ", fset)
        mainAST, err := parser.ParseFile(pbfset, "/home/zhilong/goExperiment/testAST/pb/main.go", nil,  0)
        fmt.Println("parseFile err = ", err, " fset = ", fset)
        fmt.Println("mainAST = ", mainAST)

        var importer MyImporter

        conf := &types.Config {
                Importer: importer,

                Error: func(err error) {
                        fmt.Println("typecheck err = ", err)
                },
        }

//      pkg, err := conf.Check("cmd/package", fset, []*ast.File{mainAST, aaaoutterAST, bbbinnerAST, pbinnerAST}, nil)
        paAAApkg, err := conf.Check("pa/aaa", fset, []*ast.File{aaaoutterAST}, nil)
        setPkgsType("pa/aaa", paAAApkg)
        paBBBpkg, err := conf.Check("pa/bbb", fset, []*ast.File{bbbinnerAST, bbbConstantAST}, nil)
        setPkgsType("pa/bbb", paBBBpkg)

        fmt.Println("after set type")
        paAAApkg, err = conf.Check("pa/aaa", fset, []*ast.File{aaaoutterAST}, nil)

        fmt.Println("finish pkg = ", paAAApkg, " err = ", err)

        pbInnerpkg, err := conf.Check("pb/inner", pbfset, []*ast.File{pbinnerAST}, nil)
        setPkgsType("pb/inner", pbInnerpkg)
        fmt.Println("after set pb/inner err = ", err)

        pbMainPkg, err := conf.Check("pb/main", pbfset, []*ast.File{mainAST}, nil)
        setPkgsType("pb/main", pbMainPkg)

        fmt.Println("finish setup pb ", err)

        paBBBpkg, err = conf.Check("pa/bbb", fset, []*ast.File{bbbinnerZosAST, bbbConstantAST}, nil)
        setPkgsType("pa/bbb", paBBBpkg)
        fmt.Println("check main again")
        pbMainPkg, err = conf.Check("pb/main", pbfset, []*ast.File{mainAST}, nil)
}

What did you see happen?

Output:

load err =  <nil> pkgs =  [pa/aaa pa/bbb pb/inner]
pkgs[0] =    pkgs[0].Types =  package  ("pa/aaa") type name =  0x5f5a80
parseFile err =  <nil>  fset =  &{{{0 0} 0 0 {{} 0} {{} 0}} 474 [0xc00007e240] {[] {} 0xc00007e240}}
parseFile err =  <nil>  fset =  &{{{0 0} 0 0 {{} 0} {{} 0}} 1068 [0xc00007e240 0xc00007e420] {[] {} 0xc00007e420}}
bbb/constant.go parseFile err =  <nil>  fset =  &{{{0 0} 0 0 {{} 0} {{} 0}} 1791 [0xc00007e240 0xc00007e420 0xc00007e600 0xc00007e7e0] {[] {} 0xc00007e7e0}}
parseFile err =  <nil>  fset =  &{{{0 0} 0 0 {{} 0} {{} 0}} 1791 [0xc00007e240 0xc00007e420 0xc00007e600 0xc00007e7e0] {[] {} 0xc00007e7e0}}
parseFile err =  <nil>  fset =  &{{{0 0} 0 0 {{} 0} {{} 0}} 1791 [0xc00007e240 0xc00007e420 0xc00007e600 0xc00007e7e0] {[] {} 0xc00007e7e0}}
mainAST =  &{<nil> 350 main [0xc0000a7d00 0xc0000a7d40 0xc00009d800] 350 776 scope 0xc000014c50 {
        func main
}
 [0xc00009d560 0xc00009d590] [println bbb bbb bbb bbb inner inner inner println] [] }
typecheck err =  /home/zhilong/goExperiment/testAST/pa/aaa/outter.go:3:8: could not import pa/bbb (invalid package name: "")
find id =  pa/aaa  setting type:  package aaa ("pa/aaa")
find id =  pa/bbb  setting type:  package bbb ("pa/bbb")
after set type
finish pkg =  package aaa ("pa/aaa")  err =  <nil>
typecheck err =  /home/zhilong/goExperiment/testAST/pb/inner/inner.go:8:16: cannot use info (variable of type *bbb.Info) as *invalid type value in argument to aaa.Add
find id =  pb/inner  setting type:  package inner ("pb/inner")
after set pb/inner err =  /home/zhilong/goExperiment/testAST/pb/inner/inner.go:8:16: cannot use info (variable of type *bbb.Info) as *invalid type value in argument to aaa.Add
finish setup pb  <nil>
find id =  pa/bbb  setting type:  package bbb ("pa/bbb")
check main again
typecheck err =  /home/zhilong/goExperiment/testAST/pb/main.go:26:24: cannot use info1 (variable of type *bbb.Info) as *bbb.Info value in argument to inner.CallToAdd
typecheck err =  /home/zhilong/goExperiment/testAST/pb/main.go:27:24: cannot use info1 (variable of type *bbb.Info) as *bbb.Info value in argument to inner.CallToSub
typecheck err =  /home/zhilong/goExperiment/testAST/pb/main.go:28:21: cannot use info2 (variable of type bbb.Info) as bbb.Info value in argument to inner.CallToMul

What did you expect to see?

The very last few lines of the output

check main again
typecheck err =  /home/zhilong/goExperiment/testAST/pb/main.go:26:24: cannot use info1 (variable of type *bbb.Info) as *bbb.Info value in argument to inner.CallToAdd
typecheck err =  /home/zhilong/goExperiment/testAST/pb/main.go:27:24: cannot use info1 (variable of type *bbb.Info) as *bbb.Info value in argument to inner.CallToSub
typecheck err =  /home/zhilong/goExperiment/testAST/pb/main.go:28:21: cannot use info2 (variable of type bbb.Info) as bbb.Info value in argument to inner.CallToMul

The variable type is the same but the compiler is complaining that they are different.

There should be no error at the last step of the Check function

bluecivet commented 2 weeks ago

A copy of a discussion from the Wharf issue could be helpful: https://github.com/ZOSOpenTools/wharf/issues/38

The issue arises within the type checking process, specifically in the func AssignableTo(V, T Type) bool function, which fails to provide accurate type checks for Named type. . The function relies on indenticalOrigin to verify the original type. However, the problem lies in its use of the == operator to compare Name structs that include pointers. This comparison method can lead to inaccurate results as it checks memory addresses rather than the actual content of the structs. Compounding this issue is Wharf's behavior of creating a new package instance each time it filters a configuration. Consequently, pointers within Name structs may differ between package instances.

timothy-king commented 1 week ago

Duplicate of #64477

This is WAI. Each call to conf.Check returns a new types.Package and each type.Named within that copy of the package is considered distinct. (As for why? A lot gets pinned to memory in the implementation or speed and simplicity, and there are issues of temporal consistency.) The expectation is that if a user of go/types retype checks package, e.g. "pa/bbb", they also retype all packages that transitively import that package, e.g. "pa/main" and "pb/inner". This is what gopls does.

The documentation could be improved https://github.com/golang/go/issues/53914. There is also a proposal to change this behavior https://github.com/golang/go/issues/57497.