Go 如何使类型。实现与使用导入类型的签名一起正常工作?

Go 如何使类型。实现与使用导入类型的签名一起正常工作?,go,Go,types.Implements(impl1,iface)返回false,此时接口定义签名使用某种类型,并且可能的实现位于另一个包中,需要导入该类型 项目结构 awesome ├── main.go ├── pkg1 │   └── file.go └── pkg2 └── file.go i、 例如,在awesome文件夹中有包main awesome/pkg1/file.go package pkg1 import ( "context" ) // Interface

types.Implements(impl1,iface)
返回
false
,此时接口定义签名使用某种类型,并且可能的实现位于另一个包中,需要导入该类型

项目结构

awesome
├── main.go
├── pkg1
│   └── file.go
└── pkg2
    └── file.go
i、 例如,在
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 ""
}
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))
}
package pkg3

import (
    _ "awesome/pkg1"
    _ "awesome/pkg2"
)
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 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 ""
}
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))
}
package pkg3

import (
    _ "awesome/pkg1"
    _ "awesome/pkg2"
)
程序输出:

$ go install awesome
$ awesome
Implementation1 implements Interface true
Implementation2 implements Interface false
发生这种情况是因为导入了类型
context.context
。在第二种情况下,当我将
Method
的参数类型更改为
string
int
byte
时,它工作正常并返回
true
,无论是什么,还是删除它。我做错了什么

@mkopriva的回答实际上把我引向了正确的方向:不同的类型检查产生不同的类型,这就是为什么
实现了
在非基本类型上失败的原因。我们只需要重用对象进行类型检查:这个
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
,检查过程就可以处理一次导入的类型(表示为对象),而不是将其再次注册为新对象。

我不确定这是有意的行为还是一个bug,但是,如果您仔细查看源代码,您会发现
类型.Implements
在此处“失败”:

从注释中可以看出,只有当

类型名称源自同一类型声明

但是如果您在那里添加print语句来检查x,y值,您将看到它将两个不同的指针与
类型进行比较。命名为
相同类型的值
context.context
。类型信息被分配了两次,这一事实相当于不是源自同一声明的命名类型。您有两个相同命名类型的实例的原因是因为您分别解析和检查这两个包

因此,解决方案是一起解析和检查这两个包。我不确定这对您来说是否是一个可行的解决方案,但您可以做的一件事是声明一个导入这两个包的第三个包,并解析和检查这第三个包

例如:

awesome
├── main.go
├── pkg1
│   └── file.go
├── pkg2
│   └── file.go
└── pkg3
    └── file.go
那么pkg3内容将如下所示:

awesome/pkg3/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 ""
}
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))
}
package pkg3

import (
    _ "awesome/pkg1"
    _ "awesome/pkg2"
)
你的主要目标是:

awesome/main.go
(我只添加了需要对原始版本进行的更改)


我复制并运行了您的代码,两者都返回false(与您的输出不同,不是true和false),这是正确的,因为context.context不是pkg1.Arg。请提供您正在运行的确切代码,否则我们无法判断您做错了什么。为了澄清,impl1和impl2都没有实现pkg1.Interface,因为它们对方法的参数类型与接口中指定的参数类型不同。@mkopriva很抱歉,我从另一个例子中得到了这种行为,但随后发现导入类型也有同样的行为。固定的例子。