types.Implements(impl1, iface)
возвращает false
, когда сигнатура определения интерфейса использует какой-либо тип, и возможная реализация находится в другом пакете и нуждается в импорте этого типа.
Структура проекта
awesome
├── main.go
├── pkg1
│ └── file.go
└── pkg2
└── file.go
То есть, в папке awesome
есть пакет main
.
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
выглядит как
package pkg2
import (
"context"
)
// Implementation2 implementation for pkg1.Interface
type Implementation2 struct{}
// Method ...
func (Implementation2) Method(context.Context) string {
return ""
}
А сейчас 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))
}
Вывод программы:
$ go install awesome
$ awesome
Implementation1 implements Interface true
Implementation2 implements Interface false
Это происходит из-за импортированного типа context.Context
. Он работает нормально и возвращает true
во втором случае, когда я изменяю тип аргумента Method
на string
, int
, byte
, что угодно или просто удаляю его. Что я делаю не так?
@ mkopriva ответ на самом деле направляет меня в правильном направлении: проверка разных типов приводит к разным типам, поэтому Implements
не справляется с неосновными типами. Нам просто нужно повторно использовать объекты для проверки типов: это main.go
на самом деле работает.
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\n", 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))
}
Нам просто нужно поделиться *types.Config
и *types.Info
, чтобы процедура проверки работала с импортированным типом (который представлен как объект), а не регистрировала его снова как новый объект.