Skip to content

Commit

Permalink
interp: fix a panic when embedding an error interface
Browse files Browse the repository at this point in the history
This patch brings the following modifications:
- consider that an interface is assignable to another if the former
  implements the latter
- call TypeOf() method instead of rtype field when resolving methods, to
  handle first met types
- unwrap error interface inplace rather than embedding it in an
  interface definition, as lower case named embbeded interface may
  not be handled by reflect when lookup for a method.

Fixes #1063. Partially improves #1058.
  • Loading branch information
mvertes authored Apr 1, 2021
1 parent 2b1d6f0 commit aa2621f
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 9 deletions.
23 changes: 23 additions & 0 deletions _test/interface51.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package main

type Error interface {
error
Message() string
}

type T struct {
Msg string
}

func (t *T) Error() string { return t.Msg }
func (t *T) Message() string { return "message:" + t.Msg }

func newError() Error { return &T{"test"} }

func main() {
e := newError()
println(e.Error())
}

// Output:
// test
33 changes: 24 additions & 9 deletions interp/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,10 @@ func untypedInt() *itype { return &itype{cat: intT, name: "int", untyped: tr
func untypedFloat() *itype { return &itype{cat: float64T, name: "float64", untyped: true} }
func untypedComplex() *itype { return &itype{cat: complex128T, name: "complex128", untyped: true} }

func errorMethodType(sc *scope) *itype {
return &itype{cat: funcT, ret: []*itype{sc.getType("string")}}
}

// nodeType returns a type definition for the corresponding AST subtree.
func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
if n.typ != nil && !n.typ.incomplete {
Expand Down Expand Up @@ -512,21 +516,28 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
}
}
for _, field := range n.child[0].child {
f0 := field.child[0]
if len(field.child) == 1 {
typ, err := nodeType(interp, sc, field.child[0])
if err != nil {
return nil, err
if f0.ident == "error" {
// Unwrap error interface inplace rather than embedding it, because
// "error" is lower case which may cause problems with reflect for method lookup.
t.field = append(t.field, structField{name: "Error", typ: errorMethodType(sc)})
continue
}
t.field = append(t.field, structField{name: fieldName(field.child[0]), embed: true, typ: typ})
incomplete = incomplete || typ.incomplete
} else {
typ, err := nodeType(interp, sc, field.child[1])
typ, err := nodeType(interp, sc, f0)
if err != nil {
return nil, err
}
t.field = append(t.field, structField{name: field.child[0].ident, typ: typ})
t.field = append(t.field, structField{name: fieldName(f0), embed: true, typ: typ})
incomplete = incomplete || typ.incomplete
continue
}
typ, err := nodeType(interp, sc, field.child[1])
if err != nil {
return nil, err
}
t.field = append(t.field, structField{name: f0.ident, typ: typ})
incomplete = incomplete || typ.incomplete
}
t.incomplete = incomplete

Expand Down Expand Up @@ -968,6 +979,10 @@ func (t *itype) assignableTo(o *itype) bool {
return true
}

if isInterface(o) && t.implements(o) {
return true
}

n := t.node
if n == nil || !n.rval.IsValid() {
return false
Expand Down Expand Up @@ -1066,7 +1081,7 @@ func (t *itype) methods() methodSet {
}
case valueT, errorT:
// Get method from corresponding reflect.Type.
for i := typ.rtype.NumMethod() - 1; i >= 0; i-- {
for i := typ.TypeOf().NumMethod() - 1; i >= 0; i-- {
m := typ.rtype.Method(i)
res[m.Name] = m.Type.String()
}
Expand Down

0 comments on commit aa2621f

Please sign in to comment.