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

时间:2018-05-06 08:58:12

标签: go

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。它工作正常并在第二种情况下返回true,当我将Method的参数类型更改为stringintbyte时,无论是什么或只是删除它。我做错了什么?

@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,以便检查过程处理一次导入的类型(表示为对象),而不是再次将其注册为新对象。

1 个答案:

答案 0 :(得分:0)

我不确定这是否是预期的行为或是否是一个错误但是,如果你仔细查看来源,你会发现types.Implements“失败”在这里:https://github.com/golang/go/blob/master/src/go/types/predicates.go#L282-L287

从评论中可以看出,只有在

时,比较才会返回true
  

类型名称源自相同的类型声明

但如果您在那里添加print语句来检查x,y值,您会看到它将两个不同指针types.Named相同类型的值context.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]

    // ...
}