go/types 找出结构体是否实现了接口

huangapple go评论68阅读模式
英文:

go/types finding if struct implements interface

问题

好的,下面是翻译好的内容:

好的,我看到你想使用go/types、go/parser等来生成一些代码;但是需要找出所有实现特定接口的结构体,这一点我已经弄清楚了。然而,如果结构体函数的定义在结构体上,使用types.Implements就无法匹配。

下面是代码示例:

获取接口

package ifacepkg

const interfacePkg = `package ifacepkg

type MyInterface interface {
    MyFunction() error
}
`

func getIface() *types.Interface {

    fset := token.NewFileSet()

    f, err := parser.ParseFile(fset, "iface.go", interfacePkg, 0)
    if err != nil {
        panic(err)
    }

    config := &types.Config{
        Error: func(e error) {
            fmt.Println(e)
        },
        Importer: importer.Default(),
    }

    info := types.Info{
        Types: make(map[ast.Expr]types.TypeAndValue),
        Defs:  make(map[*ast.Ident]types.Object),
        Uses:  make(map[*ast.Ident]types.Object),
    }

    pkg, e := config.Check("genval", fset, []*ast.File{f}, &info)
    if e != nil {
        fmt.Println(e)
    }

    return pkg.Scope().Lookup("MyInterface").Type().Underlying().(*types.Interface)
}

测试文件

package test

import "ifacepkg"

// User 结构体
type User struct {
    FirstName string
    LastName  string
}

func (u User) MyFunction() error {
    return nil
}

var _ ifacepkg.MyInterface = &User{}

加载测试文件并尝试查看 User 是否实现了 MyInterface

fset := token.NewFileSet()

pkgs, e := parser.ParseDir(fset, "./test", nil, 0)
if e != nil {
    log.Fatal(e)
    // return
}

var astf []*ast.File

for _, pkg := range pkgs {
    fmt.Printf("package %v\n", pkg.Name)
    for fn, f := range pkg.Files {
        fmt.Printf("file %v\n", fn)
        astf = append(astf, f)
    }
}

config := &types.Config{
    Error: func(e error) {
        fmt.Println(e)
    },
    Importer: importer.Default(),
}

info := types.Info{
    Types: make(map[ast.Expr]types.TypeAndValue),
    Defs:  make(map[*ast.Ident]types.Object),
    Uses:  make(map[*ast.Ident]types.Object),
}

pkg, e := config.Check(path, fset, astf, &info)
if e != nil {
    fmt.Println(e)
}

vIface := getIface()
fmt.Println(vIface)

scope := pkg.Scope()

for _, name := range scope.Names() {

    obj := scope.Lookup(name)

    _, ok := obj.Type().Underlying().(*types.Struct)
    imp := types.Implements(obj.Type(), vIface)

    fmt.Println(obj.Name(), ok, imp)
}

好的,fmt.Println(obj.Name(), ok, imp) 打印出 User true true,一切正常!但是,如果我将源文件中的函数从

func (u User) MyFunction() error {

改为

func (u *User) MyFunction() error {

现在它打印出 User true false

所以函数types.Implements报告User没有实现MyInterface,这是不正确的。

所以我的问题是:type.Implements方法是否有问题,还是在调用该函数之前我需要做些什么。

回答

好的,为了解决我的问题,将代码的最后一部分更改为:

scope := pkg.Scope()

for _, name := range scope.Names() {

    obj := scope.Lookup(name)

    _, ok := obj.Type().Underlying().(*types.Struct)
    ptr := types.NewPointer(obj.Type())
    imp := types.Implements(ptr.Underlying(), vIface)

    fmt.Println(obj.Name(), ok, imp)
}

这样可以同时处理指针和非指针接收器。

英文:

OK so looking at using go/types, go/parser... an so forth to generate some code; but need to identify all structs that implement a specific interface which I have figured out, however, if the struct definition on the struct function it does not match using types.Implements.

OK the code examples:

Getting the interface

package ifacepkg

const interfacePkg = `package ifacepkg

type MyInterface interface {
	MyFunction() error
}
`

func getIface() *types.Interface {

fset := token.NewFileSet()

f, err := parser.ParseFile(fset, "iface.go", interfacePkg, 0)
if err != nil {
	panic(err)
}

config := &types.Config{
	Error: func(e error) {
		fmt.Println(e)
	},
	Importer: importer.Default(),
}

info := types.Info{
	Types: make(map[ast.Expr]types.TypeAndValue),
	Defs:  make(map[*ast.Ident]types.Object),
	Uses:  make(map[*ast.Ident]types.Object),
}

pkg, e := config.Check("genval", fset, []*ast.File{f}, &info)
if e != nil {
	fmt.Println(e)
}

return pkg.Scope().Lookup("MyInterface").Type().Underlying().(*types.Interface)
}

Test File

package test

import "ifacepkg"

// User struct
type User struct {
	FirstName string
	LastName  string
}

func (u User) MyFunction() error {
	return nil
}

var _ ifacepkg.MyInterface = &User{}

Loading Test File & trying to see if User implements MyInterface

fset := token.NewFileSet()

pkgs, e := parser.ParseDir(fset, "./test", nil, 0)
if e != nil {
	log.Fatal(e)
	// return
}

var astf []*ast.File

for _, pkg := range pkgs {
	fmt.Printf("package %v\n", pkg.Name)
	for fn, f := range pkg.Files {
		fmt.Printf("file %v\n", fn)
		astf = append(astf, f)
	}
}

config := &types.Config{
	Error: func(e error) {
		fmt.Println(e)
	},
	Importer: importer.Default(),
}

info := types.Info{
	Types: make(map[ast.Expr]types.TypeAndValue),
	Defs:  make(map[*ast.Ident]types.Object),
	Uses:  make(map[*ast.Ident]types.Object),
}

pkg, e := config.Check(path, fset, astf, &info)
if e != nil {
	fmt.Println(e)
}

vIface := getIface()
fmt.Println(vIface)

scope := pkg.Scope()

	for _, name := range scope.Names() {

		obj := scope.Lookup(name)

		_, ok := obj.Type().Underlying().(*types.Struct)
		imp := types.Implements(obj.Type(), vIface)

		fmt.Println(obj.Name(), ok, imp)
	}

OK so fmt.Println(obj.Name(), ok, imp) prints User true true all good! however if I change the source file function from

func (u User) MyFunction() error {

to

func (u *User) MyFunction() error {

it now prints User true false

so the function types.Implements is reporting that User does not implement MyInterface, which is not true.

So my question is: Is there an issue with the type.Implements method or is there something I have to do to my object prior to calling that function.

Answer

ok to solve my own issue changing final portion of code to

scope := pkg.Scope()

for _, name := range scope.Names() {

    obj := scope.Lookup(name)

    _, ok := obj.Type().Underlying().(*types.Struct)
    ptr := types.NewPointer(obj.Type())
    imp := types.Implements(ptr.Underlying(), vIface)

    fmt.Println(obj.Name(), ok, imp)
}

which works with both Pointer and non-pointer recievers

答案1

得分: 1

你的编译器告诉你的是真的。在Go语言中,*TypeType是不同的类型。如果你想让*User实现你的接口,那么方法的接收者必须是*User类型;如果你想让User实现接口,那么接收者必须是User类型。我不太知道如何解释得更清楚了... Go语言的类型系统是严格的,这两者并不相同。如果你在指针类型上定义了(u *User) MyFunction(),然后检查MyUserptr是否实现了接口,你会更直观地看到这一点:ptr会实现接口,而MyUser则不会。

英文:

What your compiler is telling you is true. *Type != Type in Go. If you want *User to implement your interface then the methods receiver has to be *User and if you want it to be User then it needs to be User. I don't really know how else to explain it... Go's type system is strict and those aren't the same. You can see this more practically if you have (u *User) MyFunction() defined for the pointer type do ptr := &MyUser then check if MyUser and ptr implement the interface, ptr will, MyUser will not.

huangapple
  • 本文由 发表于 2015年12月29日 01:20:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/34497840.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定