Files
moxa/interp/interp_export_test.go
Marc Vertes e02621577f interp: improve handling of composed interfaces wrappers
This change implements a workaround to better support composed
interfaces in yaegi and let the interpreter define objects which
implement multiple interfaces at once.

We use the existing MapTypes to store what possible composed interface
wrapper could be used for some interfaces. When generating an interface
wrapper, the wrapper with the highest number of implemented methods is
chosen.

This is still an imperfect solution but it improves the accuracy of
interpreter in some critical cases.

This workaround could be removed in future if/when golang/go#15924
is resolved.

Fixes #1425.
2022-08-25 10:44:11 +02:00

133 lines
2.7 KiB
Go

package interp_test
import (
"reflect"
"testing"
"github.com/traefik/yaegi/interp"
)
type Helloer interface {
Hello()
}
func Hi(h Helloer) {
println("In Hi:")
h.Hello()
}
// A Wrap represents the wrapper which allows to use objects created by
// the interpreter as Go interfaces (despite limitations in reflect which
// forbid dynamic method creation).
//
// All the struct fields are functions, where the fied name corresponds to
// the method name prefixed by "Do". The function signature must be the
// same as the interface one.
//
// A corresponding Wrap method Xyz which satisfies the interface must exist and
// must invoke the DoXyz function.
//
// To be usable, the interpreter should return a Wrap instance with the relevant
// function fields filled. The application can then invoke methods on it.
// The method calls will be forwarded to the interpreter.
//
// Only the Wrap type definition needs to be exported to the interpreter (not
// the interfaces and methods definitions).
type Wrap struct {
DoHello func() // related to the Hello() method.
// Other interface method wrappers...
}
func (w Wrap) Hello() { w.DoHello() }
func TestExportsSemantics(t *testing.T) {
Foo := &struct{}{}
t.Run("Correct", func(t *testing.T) {
t.Skip()
i := interp.New(interp.Options{})
err := i.Use(interp.Exports{
"foo/foo": {"Foo": reflect.ValueOf(Foo)},
})
if err != nil {
t.Fatal(err)
}
i.ImportUsed()
res, err := i.Eval("foo.Foo")
if err != nil {
t.Fatal(err)
}
if res.Interface() != Foo {
t.Fatalf("expected foo.Foo to equal local Foo")
}
})
t.Run("Incorrect", func(t *testing.T) {
i := interp.New(interp.Options{})
err := i.Use(interp.Exports{
"foo": {"Foo": reflect.ValueOf(Foo)},
})
if err == nil {
t.Fatal("expected error for incorrect Use semantics")
}
})
}
func TestInterface(t *testing.T) {
i := interp.New(interp.Options{})
// export the Wrap type to the interpreter under virtual "wrap" package
err := i.Use(interp.Exports{
"wrap/wrap": {
"Wrap": reflect.ValueOf((*Wrap)(nil)),
},
})
if err != nil {
t.Fatal(err)
}
eval(t, i, `
import "wrap"
type MyInt int
func (m MyInt) Hello() { println("hello from Myint", m) }
func NewMyInt(i int) wrap.Wrap {
m := MyInt(i)
return wrap.Wrap{DoHello: m.Hello}
}
`)
NewMyInt := eval(t, i, "NewMyInt").Interface().(func(int) Wrap)
w := NewMyInt(4)
Hi(w)
}
type T struct{}
func (t T) Bar(s ...string) {}
func TestCallBinVariadicMethod(t *testing.T) {
i := interp.New(interp.Options{})
err := i.Use(interp.Exports{
"mypkg/mypkg": {
"T": reflect.ValueOf((*T)(nil)),
},
})
if err != nil {
t.Fatal(err)
}
eval(t, i, `
package p
import "mypkg"
func Foo(x mypkg.T) { x.Bar("s") }
`)
v := eval(t, i, "p.Foo")
bar := v.Interface().(func(t T))
bar(T{})
}