How can I tell if a method/interface is promoted from an embedded struct (in other words: a struct "inherits" a method instead of directly implementing it)?
For example: is there a way to determine Main
does not directly implement the interface Interface
given this?:
type Interface interface {
Method()
}
type Embedded struct{}
func (t *Embedded) Method() {}
type Main struct {
Embedded
}
... as opposed to the case where it does directly implement the interface:
type Main struct {
Embedded
}
func (t *Main) Method() {} // this is directly implemented
Tried:
Standard type assertion in go
results in Main
having the Method
in both examples, implementing Interface
, as expected:
main := &Main{}
_, implementsInterface := main.(Interface) // implementsInterface is true
Reflection also indicates an instance of Main
has the method in both examples:
main := &Main{}
mainVal := reflect.ValueOf(main)
mainMethod, hasMainMethod := mainVal.Type().MethodByName("Method") // hasMainMethod is true
embedded := val.Elem().Field(0)
embeddedMethod, hasEmbeddedMethod := embedded.Type().MethodByName("Method") // hasEmbeddedMethod is true
// mainMethod != embeddedMethod
// pointers in the methods are not the same
// is there some other way to determine these methods are "the same"?
The compiler will create a wrapper for a promoted method. So in the first place, the promoted method is different from the original method.
On the other hand, this fact gives us a not-so-reliable workaround: if a method is a wrapper, it could be a promoted method. And a still not-so-reliable way to check if a method is a compiler-generated wrapper is to check the file name of the source code corresponding to the method. For a wrapper, the file name will be <autogenerated>
.
Let's see a demo first:
package main
import (
"fmt"
"reflect"
"runtime"
)
type inner struct{}
func (t *inner) Method() {}
type outer1 struct{ inner }
type outer2 struct{ inner }
func (o *outer2) Method() {}
func isPromoted(s any, methodName string) (bool, error) {
v := reflect.ValueOf(s)
m, ok := v.Type().MethodByName(methodName)
if !ok {
return false, fmt.Errorf("method sets of s does not include the method")
}
p := m.Func.Pointer()
f := runtime.FuncForPC(p)
fileName, _ := f.FileLine(f.Entry())
promoted := fileName == "<autogenerated>"
return promoted, nil
}
func main() {
i := &inner{}
o1 := &outer1{inner: *i}
o2 := &outer2{inner: *i}
fmt.Println("i - is [Method] promoted?")
fmt.Println(isPromoted(i, "Method"))
fmt.Println("o1 - is [Method] promoted?")
fmt.Println(isPromoted(o1, "Method"))
fmt.Println("o2 - is [Method] promoted?")
fmt.Println(isPromoted(o2, "Method"))
}
And the output (see https://go.dev/play/p/XUIE0ws3eFN):
i - is [Method] promoted?
false <nil>
o1 - is [Method] promoted?
true <nil>
o2 - is [Method] promoted?
false <nil>
This workaround is not-so-reliable because:
a wrapper is not necessary a promoted method. For example, when passing i.Method
as a parameter to a function, a wrapper is created (follow the link above to the playground to see a demo). I'm not sure whether there are other cases.
the demo uses (v Value).Pointer
. According to the doc:
If v's Kind is
Func
, the returned pointer is an underlying code pointer, but not necessarily enough to identify a single function uniquely.
And the comment in the implementation:
As the doc comment says, the returned pointer is an underlying code pointer but not necessarily enough to identify a single function uniquely. All method expressions created via
reflect
have the same underlying code pointer, so their Pointers are equal.
Does isPromoted
implemented in the demo work for methods created via reflect
? It's not tested.
<autogenerated>
is an implementation detail and is not documented. It could be changed any time.