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.