【发布时间】:2018-05-07 14:09:26
【问题描述】:
types.Implements(impl1, iface)返回false,当接口定义签名使用某种类型时,可能实现在另一个包中,需要导入该类型。
项目结构
awesome
├── main.go
├── pkg1
│ └── file.go
└── pkg2
└── file.go
也就是说,main 文件夹中有包 awesome。
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 而发生的。当我将Method 的参数类型更改为string、int、byte 或删除它时,它工作正常并在第二种情况下返回true。我做错了什么?
@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 以便检查过程处理一次导入的类型(表示为对象),而不是将其再次注册为新对象。
【问题讨论】:
-
我已经复制并运行了您的代码,两者都返回 false(不像您的输出那样是 true 和 false),这是正确的,因为 context.Context 不是 pkg1.Arg。请提供您正在运行的确切代码,否则我们无法判断您做错了什么。
-
澄清一下,impl1 和 impl2 都不实现 pkg1.Interface,因为它们的 Method 方法的参数类型与 Interface 中指定的不同。
-
@mkopriva 对不起,我从另一个示例中得到了这种行为,但后来发现导入类型的行为相同。固定示例。
标签: go