diff --git a/_test/issue-1515.go b/_test/issue-1515.go new file mode 100644 index 000000000..07657184e --- /dev/null +++ b/_test/issue-1515.go @@ -0,0 +1,43 @@ +package main + +type I1 interface { + I2 + Wrap() *S3 +} + +type I2 interface { + F() +} + +type S2 struct { + I2 +} + +func newS2(i2 I2) I1 { + return &S2{i2} +} + +type S3 struct { + base *S2 +} + +func (s *S2) Wrap() *S3 { + i2 := s + return &S3{i2} +} + +type T struct { + name string +} + +func (t *T) F() { println("in F", t.name) } + +func main() { + t := &T{"test"} + s2 := newS2(t) + s3 := s2.Wrap() + s3.base.F() +} + +// Output: +// in F test diff --git a/interp/cfg.go b/interp/cfg.go index 47e646c66..e293e1659 100644 --- a/interp/cfg.go +++ b/interp/cfg.go @@ -1898,103 +1898,7 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string tryMethods: fallthrough default: - // Find a matching method. - // TODO (marc): simplify the following if/elseif blocks. - if n.typ.cat == valueT || n.typ.cat == errorT { - switch method, ok := n.typ.rtype.MethodByName(n.child[1].ident); { - case ok: - hasRecvType := n.typ.TypeOf().Kind() != reflect.Interface - n.val = method.Index - n.gen = getIndexBinMethod - n.action = aGetMethod - n.recv = &receiver{node: n.child[0]} - n.typ = valueTOf(method.Type, isBinMethod()) - if hasRecvType { - n.typ.recv = n.typ - } - case n.typ.TypeOf().Kind() == reflect.Ptr: - if field, ok := n.typ.rtype.Elem().FieldByName(n.child[1].ident); ok { - n.typ = valueTOf(field.Type) - n.val = field.Index - n.gen = getPtrIndexSeq - break - } - err = n.cfgErrorf("undefined field or method: %s", n.child[1].ident) - case n.typ.TypeOf().Kind() == reflect.Struct: - if field, ok := n.typ.rtype.FieldByName(n.child[1].ident); ok { - n.typ = valueTOf(field.Type) - n.val = field.Index - n.gen = getIndexSeq - break - } - fallthrough - default: - // method lookup failed on type, now lookup on pointer to type - pt := reflect.PtrTo(n.typ.rtype) - if m2, ok2 := pt.MethodByName(n.child[1].ident); ok2 { - n.val = m2.Index - n.gen = getIndexBinPtrMethod - n.typ = valueTOf(m2.Type, isBinMethod(), withRecv(valueTOf(pt))) - n.recv = &receiver{node: n.child[0]} - n.action = aGetMethod - break - } - err = n.cfgErrorf("undefined field or method: %s", n.child[1].ident) - } - } else if n.typ.cat == ptrT && (n.typ.val.cat == valueT || n.typ.val.cat == errorT) { - // Handle pointer on object defined in runtime - if method, ok := n.typ.val.rtype.MethodByName(n.child[1].ident); ok { - n.val = method.Index - n.typ = valueTOf(method.Type, isBinMethod(), withRecv(n.typ)) - n.recv = &receiver{node: n.child[0]} - n.gen = getIndexBinElemMethod - n.action = aGetMethod - } else if method, ok := reflect.PtrTo(n.typ.val.rtype).MethodByName(n.child[1].ident); ok { - n.val = method.Index - n.gen = getIndexBinMethod - n.typ = valueTOf(method.Type, withRecv(valueTOf(reflect.PtrTo(n.typ.val.rtype), isBinMethod()))) - n.recv = &receiver{node: n.child[0]} - n.action = aGetMethod - } else if field, ok := n.typ.val.rtype.FieldByName(n.child[1].ident); ok { - n.typ = valueTOf(field.Type) - n.val = field.Index - n.gen = getPtrIndexSeq - } else { - err = n.cfgErrorf("undefined selector: %s", n.child[1].ident) - } - } else if m, lind := n.typ.lookupMethod(n.child[1].ident); m != nil { - n.action = aGetMethod - if n.child[0].isType(sc) { - // Handle method as a function with receiver in 1st argument. - n.val = m - n.findex = notInFrame - n.gen = nop - n.typ = &itype{} - *n.typ = *m.typ - n.typ.arg = append([]*itype{n.child[0].typ}, m.typ.arg...) - } else { - // Handle method with receiver. - n.gen = getMethod - n.val = m - n.typ = m.typ - n.recv = &receiver{node: n.child[0], index: lind} - } - } else if m, lind, isPtr, ok := n.typ.lookupBinMethod(n.child[1].ident); ok { - n.action = aGetMethod - switch { - case isPtr && n.typ.fieldSeq(lind).cat != ptrT: - n.gen = getIndexSeqPtrMethod - case isInterfaceSrc(n.typ): - n.gen = getMethodByName - default: - n.gen = getIndexSeqMethod - } - n.recv = &receiver{node: n.child[0], index: lind} - n.val = append([]int{m.Index}, lind...) - n.typ = valueTOf(m.Type, isBinMethod(), withRecv(n.child[0].typ)) - } else { - err = n.cfgErrorf("undefined selector: %s", n.child[1].ident) - } + err = matchSelectorMethod(sc, n) } if err == nil && n.findex != -1 && n.typ.cat != genericT { n.findex = sc.add(n.typ) @@ -3024,6 +2928,124 @@ func compositeGenerator(n *node, typ *itype, rtyp reflect.Type) (gen bltnGenerat return gen } +// matchSelectorMethod, given that n represents a selector for a method, tries +// to find the corresponding method, and populates n accordingly. +func matchSelectorMethod(sc *scope, n *node) (err error) { + name := n.child[1].ident + if n.typ.cat == valueT || n.typ.cat == errorT { + switch method, ok := n.typ.rtype.MethodByName(name); { + case ok: + hasRecvType := n.typ.TypeOf().Kind() != reflect.Interface + n.val = method.Index + n.gen = getIndexBinMethod + n.action = aGetMethod + n.recv = &receiver{node: n.child[0]} + n.typ = valueTOf(method.Type, isBinMethod()) + if hasRecvType { + n.typ.recv = n.typ + } + case n.typ.TypeOf().Kind() == reflect.Ptr: + if field, ok := n.typ.rtype.Elem().FieldByName(name); ok { + n.typ = valueTOf(field.Type) + n.val = field.Index + n.gen = getPtrIndexSeq + break + } + err = n.cfgErrorf("undefined method: %s", name) + case n.typ.TypeOf().Kind() == reflect.Struct: + if field, ok := n.typ.rtype.FieldByName(name); ok { + n.typ = valueTOf(field.Type) + n.val = field.Index + n.gen = getIndexSeq + break + } + fallthrough + default: + // method lookup failed on type, now lookup on pointer to type + pt := reflect.PtrTo(n.typ.rtype) + if m2, ok2 := pt.MethodByName(name); ok2 { + n.val = m2.Index + n.gen = getIndexBinPtrMethod + n.typ = valueTOf(m2.Type, isBinMethod(), withRecv(valueTOf(pt))) + n.recv = &receiver{node: n.child[0]} + n.action = aGetMethod + break + } + err = n.cfgErrorf("undefined method: %s", name) + } + return err + } + + if n.typ.cat == ptrT && (n.typ.val.cat == valueT || n.typ.val.cat == errorT) { + // Handle pointer on object defined in runtime + if method, ok := n.typ.val.rtype.MethodByName(name); ok { + n.val = method.Index + n.typ = valueTOf(method.Type, isBinMethod(), withRecv(n.typ)) + n.recv = &receiver{node: n.child[0]} + n.gen = getIndexBinElemMethod + n.action = aGetMethod + } else if method, ok := reflect.PtrTo(n.typ.val.rtype).MethodByName(name); ok { + n.val = method.Index + n.gen = getIndexBinMethod + n.typ = valueTOf(method.Type, withRecv(valueTOf(reflect.PtrTo(n.typ.val.rtype), isBinMethod()))) + n.recv = &receiver{node: n.child[0]} + n.action = aGetMethod + } else if field, ok := n.typ.val.rtype.FieldByName(name); ok { + n.typ = valueTOf(field.Type) + n.val = field.Index + n.gen = getPtrIndexSeq + } else { + err = n.cfgErrorf("undefined selector: %s", name) + } + return err + } + + if m, lind := n.typ.lookupMethod(name); m != nil { + n.action = aGetMethod + if n.child[0].isType(sc) { + // Handle method as a function with receiver in 1st argument. + n.val = m + n.findex = notInFrame + n.gen = nop + n.typ = &itype{} + *n.typ = *m.typ + n.typ.arg = append([]*itype{n.child[0].typ}, m.typ.arg...) + } else { + // Handle method with receiver. + n.gen = getMethod + n.val = m + n.typ = m.typ + n.recv = &receiver{node: n.child[0], index: lind} + } + return nil + } + + if m, lind, isPtr, ok := n.typ.lookupBinMethod(name); ok { + n.action = aGetMethod + switch { + case isPtr && n.typ.fieldSeq(lind).cat != ptrT: + n.gen = getIndexSeqPtrMethod + case isInterfaceSrc(n.typ): + n.gen = getMethodByName + default: + n.gen = getIndexSeqMethod + } + n.recv = &receiver{node: n.child[0], index: lind} + n.val = append([]int{m.Index}, lind...) + n.typ = valueTOf(m.Type, isBinMethod(), withRecv(n.child[0].typ)) + return nil + } + + if typ := n.typ.interfaceMethod(name); typ != nil { + n.typ = typ + n.action = aGetMethod + n.gen = getMethodByName + return nil + } + + return n.cfgErrorf("undefined selector: %s", name) +} + // arrayTypeLen returns the node's array length. If the expression is an // array variable it is determined from the value's type, otherwise it is // computed from the source definition. diff --git a/interp/run.go b/interp/run.go index a8cc80b7b..86eb1645d 100644 --- a/interp/run.go +++ b/interp/run.go @@ -1146,23 +1146,10 @@ func checkFieldIndex(typ reflect.Type, index []int) bool { func call(n *node) { goroutine := n.anc.kind == goStmt - var method bool c0 := n.child[0] value := genValue(c0) var values []func(*frame) reflect.Value - recvIndexLater := false - switch { - case c0.recv != nil: - // Compute method receiver value. - values = append(values, genValueRecv(c0)) - method = true - case c0.action == aMethod: - // Add a place holder for interface method receiver. - values = append(values, nil) - method = true - } - numRet := len(c0.typ.ret) variadic := variadicPos(n) child := n.child[1:] @@ -1262,10 +1249,6 @@ func call(n *node) { if n.anc.kind == deferStmt { // Store function call in frame for deferred execution. value = genFunctionWrapper(c0) - if method { - // The receiver is already passed in the function wrapper, skip it. - values = values[1:] - } n.exec = func(f *frame) bltn { val := make([]reflect.Value, len(values)+1) val[0] = value(f) @@ -1301,11 +1284,6 @@ func call(n *node) { callf = func(in []reflect.Value) []reflect.Value { return bf.Call(in) } } - if method && len(values) > bf.Type().NumIn() { - // The receiver is already passed in the function wrapper, skip it. - values = values[1:] - } - if goroutine { // Goroutine's arguments should be copied. in := make([]reflect.Value, len(values)) @@ -1358,55 +1336,15 @@ func call(n *node) { } // Init variadic argument vector - varIndex := variadic if variadic >= 0 { - if method { - vararg = nf.data[numRet+variadic+1] - varIndex++ - } else { - vararg = nf.data[numRet+variadic] - } + vararg = nf.data[numRet+variadic] } // Copy input parameters from caller if dest := nf.data[numRet:]; len(dest) > 0 { for i, v := range values { switch { - case method && i == 0: - // compute receiver - var src reflect.Value - if v == nil { - src = def.recv.val - } else { - src = v(f) - for src.IsValid() { - // traverse interface indirections to find out concrete type - vi, ok := src.Interface().(valueInterface) - if !ok { - break - } - src = vi.value - } - } - if recvIndexLater && def.recv != nil && len(def.recv.index) > 0 { - if src.Kind() == reflect.Ptr { - src = src.Elem().FieldByIndex(def.recv.index) - } else { - src = src.FieldByIndex(def.recv.index) - } - } - // Accommodate to receiver type - d := dest[0] - if ks, kd := src.Kind(), d.Kind(); ks != kd { - if kd == reflect.Ptr { - d.Set(src.Addr()) - } else { - d.Set(src.Elem()) - } - } else { - d.Set(src) - } - case variadic >= 0 && i >= varIndex: + case variadic >= 0 && i >= variadic: if v(f).Type() == vararg.Type() { vararg.Set(v(f)) } else { @@ -1985,7 +1923,30 @@ func getMethodByName(n *node) { l := n.level n.exec = func(f *frame) bltn { - val := value0(f).Interface().(valueInterface) + // The interface object must be directly accessible, or embedded in a struct (exported anonymous field). + val0 := value0(f) + val, ok := value0(f).Interface().(valueInterface) + if !ok { + // Search the first embedded valueInterface. + for val0.Kind() == reflect.Ptr { + val0 = val0.Elem() + } + for i := 0; i < val0.NumField(); i++ { + fld := val0.Type().Field(i) + if !fld.Anonymous || !fld.IsExported() { + continue + } + if val, ok = val0.Field(i).Interface().(valueInterface); ok { + break + // TODO: should we keep track of all the the vals that are indeed valueInterface, + // so that later on we can call MethodByName on all of them until one matches? + } + } + if !ok { + panic(n.cfgErrorf("invalid interface value %v", val0)) + } + } + // Traverse nested interface values to get the concrete value. for { v, ok := val.value.Interface().(valueInterface) if !ok { @@ -2001,7 +1962,7 @@ func getMethodByName(n *node) { typ := val.node.typ if typ.node == nil && typ.cat == valueT { - // happens with a var of empty interface type, that has value of concrete type + // It happens with a var of empty interface type, that has value of concrete type // from runtime, being asserted to "user-defined" interface. if _, ok := typ.rtype.MethodByName(name); !ok { panic(n.cfgErrorf("method not found: %s", name)) @@ -2009,27 +1970,12 @@ func getMethodByName(n *node) { return next } - m, li := typ.lookupMethod(name) - - // Try harder to find a matching embedded valueInterface. - // TODO (marc): make sure it works for arbitrary depth and breadth. - if m == nil && isStruct(val.node.typ) { - v := val.value - for v.Type().Kind() == reflect.Ptr { - v = v.Elem() - } - nf := v.NumField() - for i := 0; i < nf; i++ { - var ok bool - if val, ok = v.Field(i).Interface().(valueInterface); !ok { - continue - } - if m, li = val.node.typ.lookupMethod(name); m != nil { - break - } - } + // Finally search method recursively in embedded valueInterfaces. + r, m, li := lookupMethodValue(val, name) + if r.IsValid() { + getFrame(f, l).data[i] = r + return next } - if m == nil { panic(n.cfgErrorf("method not found: %s", name)) } @@ -2044,6 +1990,37 @@ func getMethodByName(n *node) { } } +// lookupMethodValue recursively looks within val for the method with the given +// name. If a runtime value is found, it is returned in r, otherwise it is returned +// in m, with li as the list of recursive field indexes. +func lookupMethodValue(val valueInterface, name string) (r reflect.Value, m *node, li []int) { + if r = val.value.MethodByName(name); r.IsValid() { + return + } + if m, li = val.node.typ.lookupMethod(name); m != nil { + return + } + if !isStruct(val.node.typ) { + return + } + v := val.value + for v.Type().Kind() == reflect.Ptr { + v = v.Elem() + } + nf := v.NumField() + for i := 0; i < nf; i++ { + vi, ok := v.Field(i).Interface().(valueInterface) + if !ok { + continue + } + if r, m, li = lookupMethodValue(vi, name); m != nil { + li = append([]int{i}, li...) + return + } + } + return +} + func getIndexSeq(n *node) { value := genValue(n.child[0]) index := n.val.([]int) diff --git a/interp/type.go b/interp/type.go index 7b036f9cb..14ea071e9 100644 --- a/interp/type.go +++ b/interp/type.go @@ -1705,6 +1705,7 @@ func (t *itype) fieldSeq(seq []int) *itype { func (t *itype) lookupField(name string) []int { seen := map[*itype]bool{} var lookup func(*itype) []int + tias := isStruct(t) lookup = func(typ *itype) []int { if seen[typ] { @@ -1723,6 +1724,11 @@ func (t *itype) lookupField(name string) []int { for i, f := range typ.field { switch f.typ.cat { case ptrT, structT, interfaceT, linkedT: + if tias != isStruct(f.typ) { + // Interface fields are not valid embedded struct fields. + // Struct fields are not valid interface fields. + break + } if index2 := lookup(f.typ); len(index2) > 0 { return append([]int{i}, index2...) } @@ -1832,6 +1838,39 @@ func (t *itype) lookupMethod2(name string, seen map[*itype]bool) (*node, []int) return m, index } +// interfaceMethod returns type of method matching an interface method name (not as a concrete method). +func (t *itype) interfaceMethod(name string) *itype { + return t.interfaceMethod2(name, nil) +} + +func (t *itype) interfaceMethod2(name string, seen map[*itype]bool) *itype { + if seen == nil { + seen = map[*itype]bool{} + } + if seen[t] { + return nil + } + seen[t] = true + if t.cat == ptrT { + return t.val.interfaceMethod2(name, seen) + } + for _, f := range t.field { + if f.name == name && isInterface(t) { + return f.typ + } + if !f.embed { + continue + } + if typ := f.typ.interfaceMethod2(name, seen); typ != nil { + return typ + } + } + if t.cat == linkedT || isInterfaceSrc(t) && t.val != nil { + return t.val.interfaceMethod2(name, seen) + } + return nil +} + // methodDepth returns a depth greater or equal to 0, or -1 if no match. func (t *itype) methodDepth(name string) int { if m, lint := t.lookupMethod(name); m != nil { diff --git a/interp/value.go b/interp/value.go index 93ff0f5f2..e179b7508 100644 --- a/interp/value.go +++ b/interp/value.go @@ -55,10 +55,19 @@ func genValueRecv(n *node) func(*frame) reflect.Value { return func(f *frame) reflect.Value { r := v(f) - if r.Kind() == reflect.Ptr { - r = r.Elem() + for _, i := range fi { + if r.Kind() == reflect.Ptr { + r = r.Elem() + } + // Note that we can't use reflect FieldByIndex method, as we may + // traverse valueInterface wrappers to access the embedded receiver. + r = r.Field(i) + vi, ok := r.Interface().(valueInterface) + if ok { + r = vi.value + } } - return r.FieldByIndex(fi) + return r } }