【问题标题】:How to make types.Implements work properly with the signature that uses imported types?如何使 types.Implements 与使用导入类型的签名一起正常工作?
【发布时间】: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 的参数类型更改为stringintbyte 或删除它时,它工作正常并在第二种情况下返回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


【解决方案1】:

我不确定这是预期行为还是错误,但如果您深入研究源代码,您会发现 types.Implements 在此处“失败”:https://github.com/golang/go/blob/master/src/go/types/predicates.go#L282-L287

正如您从评论中看到的那样,只有当

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

但如果您在此处添加打印语句来检查 x、y 值,您会看到它将两个不同的指针与相同类型的types.Namedcontext.Context 进行比较。类型信息被分配两次的事实等同于源自同一声明的命名类型not。您有两个具有相同命名类型的实例的原因是因为您要分别解析和检查这两个包。

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

例如:

awesome
├── main.go
├── pkg1
│   └── file.go
├── pkg2
│   └── file.go
└── pkg3
    └── file.go

那么 pkg3 的内容将如下所示:

awesome/pkg3/file.go

package pkg3

import (
    _ "awesome/pkg1"
    _ "awesome/pkg2"
)

你的主要是这样的:

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

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]

    // ...
}

【讨论】:

    猜你喜欢
    • 2013-02-08
    • 2020-06-09
    • 2020-06-16
    • 2014-09-09
    • 2021-03-14
    • 1970-01-01
    • 2021-12-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多