dtkjthe4025 2018-05-06 08:58 采纳率: 0%
浏览 49

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

types.Implements(impl1, iface) returns false, when interface definition signature uses some type, and possible implementation lies in another package and needs to import that type.

Project structure

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

i.e., there's package main in the awesome folder.

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 looks like

package pkg2

import (
    "context"
)

// Implementation2 implementation for pkg1.Interface
type Implementation2 struct{}

// Method ...
func (Implementation2) Method(context.Context) string {
    return ""
}

And now 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))
}

Program output:

$ go install awesome
$ awesome
Implementation1 implements Interface true
Implementation2 implements Interface false

It happens because of imported type context.Context. It works fine and returns true in the second case when I change Method's argument type to string, int, byte, whatever or just remove it. What am I doing wrong?

@mkopriva answer actually put me into right direction: different types checking produces different types, that's why Implements fails on non-basic types. We just need to reuse objects for type checking: this main.go actually works.

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
", 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))
}

We just need to share *types.Config and *types.Info in order for the checking procedure to deal with once imported type (which is represented as an object) rather than to register it again as a new object.

  • 写回答

1条回答 默认 最新

  • duanqian8867 2018-05-06 13:03
    关注

    I'm not sure if this is intended behaviour or if it's a bug but, if you dig through the source you'll find that the types.Implements "fails" here: https://github.com/golang/go/blob/master/src/go/types/predicates.go#L282-L287

    As you can see from the comment the comparison will return true only if the

    type names originate in the same type declaration

    but if you add print statements there to check the x, y values you'll see that it compares two different pointers to types.Named value of the same type context.Context. The fact that the type info is allocated twice is equivalent to the named types not originating in the same declaration. The reason that you have two instances of the same named type is becuase you're parsing and checking the two packages separately.

    So the solution is to parse and check both of the packages together. I'm not sure if this is a viable solution for you or not but one thing you can do is to declare a third package that imports the two packages and parse and check this third package.

    For example:

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

    Then the pkg3 contents would look like this:

    awesome/pkg3/file.go

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

    And your main like this:

    awesome/main.go (I've added only the changes that need to be made to the original)

    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]
    
        // ...
    }
    
    评论

报告相同问题?

悬赏问题

  • ¥20 易康econgnition精度验证
  • ¥15 线程问题判断多次进入
  • ¥15 msix packaging tool打包问题
  • ¥28 微信小程序开发页面布局没问题,真机调试的时候页面布局就乱了
  • ¥15 python的qt5界面
  • ¥15 无线电能传输系统MATLAB仿真问题
  • ¥50 如何用脚本实现输入法的热键设置
  • ¥20 我想使用一些网络协议或者部分协议也行,主要想实现类似于traceroute的一定步长内的路由拓扑功能
  • ¥30 深度学习,前后端连接
  • ¥15 孟德尔随机化结果不一致