types.Implements(impl1, iface)
returns false
, when interface definition signature uses some type, and possible implementation lies in another package and needs to import that type.
Project structure
awesome
├── main.go
├── pkg1
│ └── file.go
└── pkg2
└── file.go
i.e., there's package main
in the awesome
folder.
awesome/pkg1/file.go
package pkg1
import (
"context"
)
// Interface generic object
type Interface interface {
Method(context.Context) string
}
// Implementation1 implementation of Interface
type Implementation1 struct{}
// Method ...
func (Implementation1) Method(context.Context) string {
return ""
}
awesome/pkg2/file.go
looks like
package pkg2
import (
"context"
)
// Implementation2 implementation for pkg1.Interface
type Implementation2 struct{}
// Method ...
func (Implementation2) Method(context.Context) string {
return ""
}
And now awesome/main.go
package main
import (
"go/ast"
"go/importer"
"go/parser"
"go/token"
"go/types"
"os"
"os/user"
"path/filepath"
"fmt"
)
var fset = token.NewFileSet()
func getPath() string {
gopath := os.Getenv("GOPATH")
if len(gopath) == 0 {
usr, err := user.Current()
if err != nil {
panic(err)
}
gopath = filepath.Join(usr.HomeDir, "go")
}
return filepath.Join(gopath, "src")
}
func getTypes(path string) *types.Package {
fullpath := filepath.Join(getPath(), path)
pkgs, err := parser.ParseDir(fset, fullpath, nil, parser.ParseComments)
if err != nil {
panic(err)
}
for _, pkg := range pkgs {
config := &types.Config{
Importer: importer.Default(),
}
info := types.Info{
Types: map[ast.Expr]types.TypeAndValue{},
}
var files []*ast.File
for _, file := range pkg.Files {
files = append(files, file)
}
typeInfo, err := config.Check(path, fset, files, &info)
if err != nil {
panic(err)
}
return typeInfo
}
return nil
}
func main() {
p1 := getTypes("awesome/pkg1")
p2 := getTypes("awesome/pkg2")
iface := p1.Scope().Lookup("Interface").(*types.TypeName).Type().(*types.Named).Underlying().(*types.Interface)
impl1 := p1.Scope().Lookup("Implementation1").Type()
impl2 := p2.Scope().Lookup("Implementation2").Type()
fmt.Println("Implementation1 implements Interface", types.Implements(impl1, iface))
fmt.Println("Implementation2 implements Interface", types.Implements(impl2, iface))
}
Program output:
$ go install awesome
$ awesome
Implementation1 implements Interface true
Implementation2 implements Interface false
It happens because of imported type context.Context
. It works fine and returns true
in the second case when I change Method
's argument type to string
, int
, byte
, whatever or just remove it. What am I doing wrong?
@mkopriva answer actually put me into right direction: different types checking produces different types, that's why Implements
fails on non-basic types. We just need to reuse objects for type checking: this main.go
actually works.
package main
import (
"fmt"
"go/ast"
"go/importer"
"go/parser"
"go/token"
"go/types"
"os"
"os/user"
"path/filepath"
)
var fset = token.NewFileSet()
var config = &types.Config{
Importer: importer.Default(),
}
var typeInfo = &types.Info{
Types: map[ast.Expr]types.TypeAndValue{},
Defs: nil,
Uses: nil,
Implicits: nil,
Selections: nil,
Scopes: nil,
InitOrder: nil,
}
func getPath() string {
gopath := os.Getenv("GOPATH")
if len(gopath) == 0 {
usr, err := user.Current()
if err != nil {
panic(err)
}
gopath = filepath.Join(usr.HomeDir, "go")
}
return filepath.Join(gopath, "src")
}
func getTree(path string) *ast.Package {
fullpath := filepath.Join(getPath(), path)
pkgs, err := parser.ParseDir(fset, fullpath, nil, parser.ParseComments)
if err != nil {
panic(err)
}
for _, pkg := range pkgs {
return pkg
}
return nil
}
func getTypes(pkg *ast.Package, path string) *types.Package {
var files []*ast.File
for _, file := range pkg.Files {
files = append(files, file)
}
typeInfo, err := config.Check(path, fset, files, typeInfo)
if err != nil {
panic(err)
}
return typeInfo
}
func main() {
const pkg1Path = "awesome/pkg1"
t1 := getTree(pkg1Path)
p1 := getTypes(t1, pkg1Path)
const pkg2Path = "awesome/pkg2"
t2 := getTree(pkg2Path)
p2 := getTypes(t2, pkg2Path)
iface := p1.Scope().Lookup("Interface").(*types.TypeName).Type().(*types.Named).Underlying().(*types.Interface)
impl1 := p1.Scope().Lookup("Implementation1").Type()
fmt.Printf("%s
", impl1.(*types.Named).Method(0).Name())
impl2 := p2.Scope().Lookup("Implementation2").Type()
fmt.Println("Implementation1 implements Interface", types.Implements(impl1, iface))
fmt.Println("Implementation2 implements Interface", types.Implements(impl2, iface))
}
We just need to share *types.Config
and *types.Info
in order for the checking procedure to deal with once imported type (which is represented as an object) rather than to register it again as a new object.
I'm not sure if this is intended behaviour or if it's a bug but, if you dig through the source you'll find that the types.Implements
"fails" here: https://github.com/golang/go/blob/master/src/go/types/predicates.go#L282-L287
As you can see from the comment the comparison will return true only if the
type names originate in the same type declaration
but if you add print statements there to check the x, y values you'll see that it compares two different pointers to types.Named
value of the same type context.Context
. The fact that the type info is allocated twice is equivalent to the named types not originating in the same declaration. The reason that you have two instances of the same named type is becuase you're parsing and checking the two packages separately.
So the solution is to parse and check both of the packages together. I'm not sure if this is a viable solution for you or not but one thing you can do is to declare a third package that imports the two packages and parse and check this third package.
For example:
awesome
├── main.go
├── pkg1
│ └── file.go
├── pkg2
│ └── file.go
└── pkg3
└── file.go
Then the pkg3 contents would look like this:
awesome/pkg3/file.go
package pkg3
import (
_ "awesome/pkg1"
_ "awesome/pkg2"
)
And your main like this:
awesome/main.go
(I've added only the changes that need to be made to the original)
func getTypes(path string) *types.Package {
// ...
for _, pkg := range pkgs {
config := &types.Config{
Importer: importer.For("source", nil),
}
// ...
}
return nil
}
// ...
func main() {
p3 := getTypes("awesome/pkg3")
p1 := p3.Imports()[0]
p2 := p3.Imports()[1]
// ...
}