interp: fix assignable check

The assignable check used to be too strict as it lacked the property that
if an untyped const can be represented as a T, then it is assignable to T.

And we can now use that fixed check to add a missing check: in a return
statement, we now make sure that any of the returned elements are
assignable to what the signature tells us they should be.
This commit is contained in:
mpl
2020-10-21 10:06:03 +02:00
committed by GitHub
parent c74d050c5a
commit c0eaab0891
5 changed files with 53 additions and 8 deletions

10
_test/fun24.go Normal file
View File

@@ -0,0 +1,10 @@
package main
func f(x int) (int, int) { return x, "foo" }
func main() {
print("hello")
}
// Error:
// cannot use "foo" (type stringT) as type intT in return argument

10
_test/fun25.go Normal file
View File

@@ -0,0 +1,10 @@
package main
func f(x string) (a int, b int) { return x, 5 }
func main() {
print("hello")
}
// Error:
// cannot use x (type stringT) as type intT in return argument

View File

@@ -1302,7 +1302,8 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
err = n.cfgErrorf("too many arguments to return")
break
}
if mustReturnValue(sc.def.child[2]) {
returnSig := sc.def.child[2]
if mustReturnValue(returnSig) {
nret := len(n.child)
if nret == 1 && isCall(n.child[0]) {
nret = n.child[0].child[0].typ.numOut()
@@ -1316,13 +1317,19 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
n.tnext = nil
n.val = sc.def
for i, c := range n.child {
var typ *itype
typ, err = nodeType(interp, sc, returnSig.child[1].fieldType(i))
if err != nil {
return
}
// TODO(mpl): move any of that code to typecheck?
c.typ.node = c
if !c.typ.assignableTo(typ) {
err = fmt.Errorf("cannot use %v (type %v) as type %v in return argument", c.ident, c.typ.cat, typ.cat)
return
}
if c.typ.cat == nilT {
// nil: Set node value to zero of return type
f := sc.def
var typ *itype
if typ, err = nodeType(interp, sc, f.child[2].child[1].fieldType(i)); err != nil {
return
}
if typ.cat == funcT {
// Wrap the typed nil value in a node, as per other interpreter functions
c.rval = reflect.ValueOf(&node{kind: basicLit, rval: reflect.New(typ.TypeOf()).Elem()})

View File

@@ -44,6 +44,8 @@ func TestInterpConsistencyBuild(t *testing.T) {
file.Name() == "fun21.go" || // expect error
file.Name() == "fun22.go" || // expect error
file.Name() == "fun23.go" || // expect error
file.Name() == "fun24.go" || // expect error
file.Name() == "fun25.go" || // expect error
file.Name() == "if2.go" || // expect error
file.Name() == "import6.go" || // expect error
file.Name() == "init1.go" || // expect error

View File

@@ -923,7 +923,23 @@ func (t *itype) assignableTo(o *itype) bool {
if t.isNil() && o.hasNil() || o.isNil() && t.hasNil() {
return true
}
return t.TypeOf().AssignableTo(o.TypeOf())
if t.TypeOf().AssignableTo(o.TypeOf()) {
return true
}
n := t.node
if n == nil || !n.rval.IsValid() {
return false
}
con, ok := n.rval.Interface().(constant.Value)
if !ok {
return false
}
if con == nil || !isConstType(o) {
return false
}
return representableConst(con, o.TypeOf())
}
// convertibleTo returns true if t is convertible to o.
@@ -932,7 +948,7 @@ func (t *itype) convertibleTo(o *itype) bool {
return true
}
// unsafe checkes
// unsafe checks
tt, ot := t.TypeOf(), o.TypeOf()
if (tt.Kind() == reflect.Ptr || tt.Kind() == reflect.Uintptr) && ot.Kind() == reflect.UnsafePointer {
return true