I'm writing a specification for a toy package I'm writing, and while most of the spec is guaranteeing that various structs in the package satisfy the primary public interface, I am also interested in specifying the methods that the interface must require; I know this is more than a little pedantic, but I figured it would be a neat experiment, and would apply pressure to keep the public interface stable.
This is my first try:
type Roller interface {
Min() int
}
type minS struct {}
func (m minS) Min() int {return 0}
func (m minS) Max() int {return 0}
func RollerSpec(c gospec.Context) {
var r Roller = minS{}
c.Specify("has a minimum value.", func() {
_, ok := r.(interface{Min() int})
c.Expect(ok, Equals, true)
})
c.Specify("has a maximum value.", func() {
_, ok := r.(interface{Max() int})
c.Expect(ok, Equals, true)
})
c.Specify("has an expected value.", func() {
_, ok := r.(interface{Exp() int})
c.Expect(ok, Equals, true)
})
c.Specify("can be rolled.", func() {
_, ok := r.(interface{Roll() int})
c.Expect(ok, Equals, true)
})
}
As you can see, my Roller
interface only requires Min()
but minS
implements both Min()
and Max()
. I pass the first two specs, even though Runner
doesn't satisfy interface{Max() int}
because the dummy type I use to test it does. Likewise, declaring r without a base type causes it to fail all specs.
It is obvious why Go would have type assertion between interfaces work on the actual stored type, but it's not what I'm looking for here. I've also checked the reflect package, but it seems like it only inspects structs as well. Is there a way to programmatically check whether an interface requires a given method without pulling in the parse package myself and crawling the parse tree for the method name?
Simply put you can't. There is no way to store just an interface since it's not a concrete type and reflection only works on concrete types. See the reflect package and it's documentation there.
What you want to do seems unnecessary anyway. The interface is your spec. What it looks like you want to do is write a spec to describe your spec and once you do that the big question is where do you stop. It's turtles all the way down :-)
Simple type assertions with individual interfaces should do it.
type minRoller interface {
Min() int
}
type maxRoller interface {
Max() int
}
And in your test:
if _, ok := r.(minRoller); ok {
print("has a min value")
}
if _, ok := r.(maxRoller); ok {
print("has a max value")
}
Your Roller interface can embed the smaller ones:
type MinRoller interface {
minRoller
}
type MinMaxRoller interface {
minRoller
maxRoller
}
Of course there is a way - the reflect.Type
of an interface contains reflect.Method
which correspond to the interface methods (don't try to call these, though! See my question Go reflection with interface embedded in struct - how to detect “real” functions?)
Using this, it's (almost) trivial to loop through the methods of an interface and check that those methods also exist in another interface:
func InterfaceExtendsInterface(parent reflect.Type, child reflect.Type) bool {
if parent.Kind() != reflect.Interface || child.Kind() != reflect.Interface {
return false
}
for i := 0; i < parent.NumMethod(); i++ {
m := parent.Method(i)
if n, has := child.MethodByName(m.Name); !has {
return false
} else if !reflect.DeepEqual(m,n) {
return false
}
}
return true
}
Because of the way interface "extension" (embedding of interface in interface) works, I don't really see a difference between
type Foo interface {...}
type Bar interface { Foo; ... }
type Baz struct { ... } // reflect.TypeOf(Baz{}).CovertibleTo(reflect.TypeOf([]Bar{}).Elem())
and the slightly weaker
type Foo interface {...}
type Bar interface { ... }
type Baz struct { ... } // reflect.TypeOf(Baz{}).CovertibleTo(reflect.TypeOf([]Bar{}).Elem()) &&
// reflect.TypeOf(Baz{}).CovertibleTo(reflect.TypeOf([]Foo{}).Elem())
to the point where I can't even think of any case where the two would ever imply different semantics for non-reflective calls on instances of Baz
... (other than having to convert to Foo
or not directly if Bar
does not embed it, of course)