Skip to content

Commit

Permalink
fix: make interpreter methods discoverable by runtime
Browse files Browse the repository at this point in the history
When generating an interface wrapper, lookup existing wrappers by method
to get the one with the biggest set of methods implemented by interpreter.

A string method is also added to wrappers, in order to provide a string
representation of the interpreter value rather than the wrapper itself
(at least for %s and %v verbs).

This allows the runtime to pickup an interpreter method automatically
even if the conversion to the interface is not specified in the script. As
in Go spec, it is enough for the type to implement the required methods.

A current limitation is that only single wrappers can be instantiated,
not allowing to compose interfaces.

This limitation can be removed when the Go reflect issue
golang/go#15924 is fixed.

Fixes #435.
  • Loading branch information
mvertes committed Jun 28, 2020
1 parent c11d361 commit ef3339d
Show file tree
Hide file tree
Showing 181 changed files with 2,040 additions and 43 deletions.
25 changes: 25 additions & 0 deletions _test/interface44.go
@@ -0,0 +1,25 @@
package main

import (
"fmt"
"strconv"
)

type Foo int

func (f Foo) String() string {
return "foo-" + strconv.Itoa(int(f))
}

func print1(arg interface{}) {
fmt.Println(arg)
}

func main() {
var arg Foo = 3
var f = print1
f(arg)
}

// Output:
// foo-3
19 changes: 15 additions & 4 deletions cmd/goexports/goexports.go
Expand Up @@ -88,10 +88,14 @@ func init() {
{{range $key, $value := .Wrap -}}
// {{$value.Name}} is an interface wrapper for {{$key}} type
type {{$value.Name}} struct {
Val interface{}
{{range $m := $value.Method -}}
W{{$m.Name}} func{{$m.Param}} {{$m.Result}}
{{end}}
}
{{- if $value.NeedString}}
func (W {{$value.Name}}) String() string { return fmt.Sprint(W.Val) }
{{end}}
{{range $m := $value.Method -}}
func (W {{$value.Name}}) {{$m.Name}}{{$m.Param}} {{$m.Result}} { {{$m.Ret}} W.W{{$m.Name}}{{$m.Arg}} }
{{end}}
Expand All @@ -111,8 +115,9 @@ type Method struct {

// Wrap store information for generating interface wrapper.
type Wrap struct {
Name string
Method []Method
Name string
NeedString bool
Method []Method
}

func genContent(dest, pkgName, license string) ([]byte, error) {
Expand Down Expand Up @@ -163,6 +168,7 @@ func genContent(dest, pkgName, license string) ([]byte, error) {
typ[name] = pname
if t, ok := o.Type().Underlying().(*types.Interface); ok {
var methods []Method
needString := true
for i := 0; i < t.NumMethods(); i++ {
f := t.Method(i)
if !f.Exported() {
Expand Down Expand Up @@ -193,10 +199,15 @@ func genContent(dest, pkgName, license string) ([]byte, error) {
if sign.Results().Len() > 0 {
ret = "return"
}

if f.Name() == "String" {
needString = false
}
methods = append(methods, Method{f.Name(), param, result, arg, ret})
}
wrap[name] = Wrap{prefix + name, methods}
if needString {
imports["fmt"] = true
}
wrap[name] = Wrap{prefix + name, needString, methods}
}
}
}
Expand Down
51 changes: 48 additions & 3 deletions interp/interp.go
Expand Up @@ -14,6 +14,7 @@ import (
"runtime"
"runtime/debug"
"strconv"
"strings"
"sync"
"sync/atomic"
)
Expand Down Expand Up @@ -102,6 +103,9 @@ type Exports map[string]map[string]reflect.Value
// imports stores the map of source packages per package path.
type imports map[string]map[string]*symbol

// binWrap stores the map of binary interface wrappers indexed per method signature.
type binWrap map[string][]reflect.Type

// opt stores interpreter options.
type opt struct {
astDot bool // display AST graph (debug)
Expand Down Expand Up @@ -131,13 +135,15 @@ type Interpreter struct {
binPkg Exports // binary packages used in interpreter, indexed by path
rdir map[string]bool // for src import cycle detection

mutex sync.RWMutex
mutex sync.RWMutex
done chan struct{} // for cancellation of channel operations

frame *frame // program data storage during execution
universe *scope // interpreter global level scope
scopes map[string]*scope // package level scopes, indexed by package name
srcPkg imports // source packages used in interpreter, indexed by path
pkgNames map[string]string // package names, indexed by path
done chan struct{} // for cancellation of channel operations
binWrap binWrap // binary wrappers indexed by method signature

hooks *hooks // symbol hooks
}
Expand All @@ -161,10 +167,16 @@ func init() { Symbols[selfPath]["Symbols"] = reflect.ValueOf(Symbols) }

// _error is a wrapper of error interface type.
type _error struct {
Val interface{}
WError func() string
}

func (w _error) Error() string { return w.WError() }
func (w _error) Error() string {
if w.WError != nil {
return w.WError()
}
return fmt.Sprint(w.Val)
}

// Panic is an error recovered from a panic call in interpreted code.
type Panic struct {
Expand Down Expand Up @@ -218,6 +230,7 @@ func New(options Options) *Interpreter {
srcPkg: imports{},
pkgNames: map[string]string{},
rdir: map[string]bool{},
binWrap: binWrap{},
hooks: &hooks{},
}

Expand Down Expand Up @@ -457,6 +470,23 @@ func (interp *Interpreter) stop() {

func (interp *Interpreter) runid() uint64 { return atomic.LoadUint64(&interp.id) }

// getWrapperType returns the wrapper type which implements the highest number of methods of t.
func (interp *Interpreter) getWrapperType(t *itype) reflect.Type {
methods := t.methods()
var nmw int
var wt reflect.Type
// build a list of wrapper type candidates
for k, v := range methods {
for _, it := range interp.binWrap[k+v] {
if methods.containsR(it) && it.NumMethod() > nmw {
nmw = it.NumMethod()
wt = it
}
}
}
return wt
}

// getWrapper returns the wrapper type of the corresponding interface, or nil if not found.
func (interp *Interpreter) getWrapper(t reflect.Type) reflect.Type {
if p, ok := interp.binPkg[t.PkgPath()]; ok {
Expand All @@ -475,6 +505,21 @@ func (interp *Interpreter) Use(values Exports) {
}

interp.binPkg[k] = v

// Register binary interface wrappers.
for id, val := range v {
if !strings.HasPrefix(id, "_") {
continue
}
t := val.Type().Elem()
it := v[strings.TrimPrefix(id, "_")].Type().Elem()
nm := it.NumMethod()
for i := 0; i < nm; i++ {
m := it.Method(i)
name := m.Name + m.Type.String()
interp.binWrap[name] = append(interp.binWrap[name], t)
}
}
}
}

Expand Down
74 changes: 44 additions & 30 deletions interp/run.go
Expand Up @@ -377,7 +377,7 @@ func assign(n *node) {
case dest.typ.cat == interfaceT:
svalue[i] = genValueInterface(src)
case (dest.typ.cat == valueT || dest.typ.cat == errorT) && dest.typ.rtype.Kind() == reflect.Interface:
svalue[i] = genInterfaceWrapper(src, dest.typ.rtype)
svalue[i], _ = genInterfaceWrapper(src, nil)
case src.typ.cat == funcT && dest.typ.cat == valueT:
svalue[i] = genFunctionWrapper(src)
case src.typ.cat == funcT && isField(dest):
Expand Down Expand Up @@ -624,7 +624,11 @@ func genFunctionWrapper(n *node) func(*frame) reflect.Value {
if rcvr != nil {
src, dest := rcvr(f), d[numRet]
if src.Type().Kind() != dest.Type().Kind() {
dest.Set(src.Addr())
if vi, ok := src.Interface().(valueInterface); ok {
dest.Set(vi.value)
} else {
dest.Set(src.Addr())
}
} else {
dest.Set(src)
}
Expand Down Expand Up @@ -661,13 +665,24 @@ func genFunctionWrapper(n *node) func(*frame) reflect.Value {
}
}

func genInterfaceWrapper(n *node, typ reflect.Type) func(*frame) reflect.Value {
func genInterfaceWrapper(n *node, t reflect.Type) (func(*frame) reflect.Value, bool) {
value := genValue(n)
if typ == nil || typ.Kind() != reflect.Interface || typ.NumMethod() == 0 || n.typ.cat == valueT {
return value
var typ reflect.Type
switch n.typ.cat {
case valueT:
return value, false
default:
if t != nil {
if nt := n.typ.TypeOf(); nt != nil && nt.Kind() == reflect.Interface {
return value, false
}
typ = n.interp.getWrapper(t)
} else {
typ = n.interp.getWrapperType(n.typ)
}
}
if nt := n.typ.TypeOf(); nt != nil && nt.Kind() == reflect.Interface {
return value
if typ == nil {
return value, false
}
mn := typ.NumMethod()
names := make([]string, mn)
Expand All @@ -681,41 +696,47 @@ func genInterfaceWrapper(n *node, typ reflect.Type) func(*frame) reflect.Value {
_, indexes[i], _, _ = n.typ.lookupBinMethod(names[i])
}
}
wrap := n.interp.getWrapper(typ)

return func(f *frame) reflect.Value {
v := value(f)
vv := v
switch v.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
if v.IsNil() {
return reflect.New(typ).Elem()
}
if v.Kind() == reflect.Ptr {
vv = v.Elem()
return reflect.New(v.Type()).Elem()
}
}
w := reflect.New(wrap).Elem()
if v.Kind() == reflect.Ptr {
vv = v.Elem()
}
if vi, ok := v.Interface().(valueInterface); ok {
v = vi.value
}
w := reflect.New(typ).Elem()
w.Field(0).Set(v)
for i, m := range methods {
if m == nil {
if names[i] == "String" {
continue
}
if r := v.MethodByName(names[i]); r.IsValid() {
w.Field(i).Set(r)
w.FieldByName("W" + names[i]).Set(r)
continue
}
o := vv.FieldByIndex(indexes[i])
if r := o.MethodByName(names[i]); r.IsValid() {
w.Field(i).Set(r)
w.FieldByName("W" + names[i]).Set(r)
} else {
log.Println(n.cfgErrorf("genInterfaceWrapper error, no method %s", names[i]))
}
continue
}
nod := *m
nod.recv = &receiver{n, v, indexes[i]}
w.Field(i).Set(genFunctionWrapper(&nod)(f))
w.FieldByName("W" + names[i]).Set(genFunctionWrapper(&nod)(f))
}
return w
}
}, true
}

func call(n *node) {
Expand Down Expand Up @@ -961,14 +982,6 @@ func call(n *node) {
}
}

// pindex returns definition parameter index for function call.
func pindex(i, variadic int) int {
if variadic < 0 || i <= variadic {
return i
}
return variadic
}

// Callbin calls a function from a bin import, accessible through reflect.
func callBin(n *node) {
tnext := getExec(n.tnext)
Expand Down Expand Up @@ -996,7 +1009,6 @@ func callBin(n *node) {
}

for i, c := range child {
defType := funcType.In(pindex(i, variadic))
switch {
case isBinCall(c):
// Handle nested function calls: pass returned values as arguments
Expand Down Expand Up @@ -1035,10 +1047,12 @@ func callBin(n *node) {
case interfaceT:
values = append(values, genValueInterfaceArray(c))
default:
values = append(values, genInterfaceWrapper(c, defType))
v, _ := genInterfaceWrapper(c, nil)
values = append(values, v)
}
default:
values = append(values, genInterfaceWrapper(c, defType))
v, _ := genInterfaceWrapper(c, nil)
values = append(values, v)
}
}
}
Expand Down Expand Up @@ -1713,7 +1727,7 @@ func _return(n *node) {
for i, c := range child {
switch t := def.typ.ret[i]; t.cat {
case errorT:
values[i] = genInterfaceWrapper(c, t.TypeOf())
values[i], _ = genInterfaceWrapper(c, t.TypeOf())
case aliasT:
if isInterfaceSrc(t) {
values[i] = genValueInterface(c)
Expand All @@ -1726,7 +1740,7 @@ func _return(n *node) {
values[i] = genValueInterface(c)
case valueT:
if t.rtype.Kind() == reflect.Interface {
values[i] = genInterfaceWrapper(c, t.rtype)
values[i], _ = genInterfaceWrapper(c, nil)
break
}
fallthrough
Expand Down

0 comments on commit ef3339d

Please sign in to comment.