fix: improve use of explicit nil (#96)
Type category `UnsetT` is renamed in `NilT`. Catch invalid use of untyped nil in `:=` expression. Convert nil to output parameter type when used in `return` expression. Improve and add relevant unit tests.
This commit is contained in:
committed by
Ludovic Fernandez
parent
5677e0501e
commit
32e0be8b4e
@@ -295,6 +295,10 @@ func (interp *Interpreter) Cfg(root *Node) ([]*Node, error) {
|
||||
dest, src := n.child[0], n.lastChild()
|
||||
sym, level, _ := scope.lookup(dest.ident)
|
||||
if n.kind == Define {
|
||||
if src.typ.cat == NilT {
|
||||
err = src.cfgError("use of untyped nil")
|
||||
break
|
||||
}
|
||||
if len(n.child) == 3 {
|
||||
// type is provided in var declaration
|
||||
dest.typ, err = nodeType(interp, scope, n.child[1])
|
||||
@@ -320,6 +324,7 @@ func (interp *Interpreter) Cfg(root *Node) ([]*Node, error) {
|
||||
// Detect invalid float truncate
|
||||
if isInt(dest.typ) && isFloat(src.typ) {
|
||||
err = src.cfgError("invalid float truncate")
|
||||
break
|
||||
}
|
||||
n.findex = dest.findex
|
||||
n.val = dest.val
|
||||
@@ -835,6 +840,14 @@ func (interp *Interpreter) Cfg(root *Node) ([]*Node, error) {
|
||||
case ReturnStmt:
|
||||
wireChild(n)
|
||||
n.tnext = nil
|
||||
for i, c := range n.child {
|
||||
if c.typ.cat == NilT {
|
||||
// nil: Set node value to zero of return type
|
||||
f := getAncFunc(n)
|
||||
typ := f.child[2].child[1].child[i].lastChild().typ
|
||||
c.val, err = typ.zero()
|
||||
}
|
||||
}
|
||||
|
||||
case SelectorExpr:
|
||||
wireChild(n)
|
||||
@@ -1314,6 +1327,17 @@ func setExec(n *Node) {
|
||||
set(n)
|
||||
}
|
||||
|
||||
// getAncFunc returns the first node in ancestorship which is a FuncDecl or FuncLit
|
||||
func getAncFunc(n *Node) *Node {
|
||||
for anc := n.anc; anc != nil; anc = anc.anc {
|
||||
switch anc.kind {
|
||||
case FuncDecl, FuncLit:
|
||||
return anc
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getReturnedType(n *Node) *Type {
|
||||
switch n.typ.cat {
|
||||
case BuiltinT:
|
||||
|
||||
@@ -163,7 +163,7 @@ func initUniverse() *Scope {
|
||||
"iota": &Symbol{kind: Const, typ: &Type{cat: IntT}},
|
||||
|
||||
// predefined Go zero value
|
||||
"nil": &Symbol{typ: &Type{cat: UnsetT}},
|
||||
"nil": &Symbol{typ: &Type{cat: NilT}},
|
||||
|
||||
// predefined Go builtins
|
||||
"append": &Symbol{kind: Bltn, builtin: _append},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package interp_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/containous/dyngo/interp"
|
||||
@@ -8,15 +9,9 @@ import (
|
||||
|
||||
func TestEval0(t *testing.T) {
|
||||
i := interp.New(interp.Opt{})
|
||||
_, err := i.Eval(`var I int = 2`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
evalCheck(t, i, `var I int = 2`)
|
||||
|
||||
t1, err := i.Eval(`I`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t1 := evalCheck(t, i, `I`)
|
||||
if t1.Interface().(int) != 2 {
|
||||
t.Fatalf("expected 2, got %v", t1)
|
||||
}
|
||||
@@ -24,19 +19,15 @@ func TestEval0(t *testing.T) {
|
||||
|
||||
func TestEval1(t *testing.T) {
|
||||
i := interp.New(interp.Opt{})
|
||||
_, err := i.Eval(`func Hello() string { return "hello" }`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
evalCheck(t, i, `func Hello() string { return "hello" }`)
|
||||
|
||||
v := evalCheck(t, i, `Hello`)
|
||||
|
||||
v, err := i.Eval(`Hello`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f, ok := v.Interface().(func() string)
|
||||
if !ok {
|
||||
t.Fatal("conversion failed")
|
||||
}
|
||||
|
||||
if s := f(); s != "hello" {
|
||||
t.Fatalf("expected hello, got %v", s)
|
||||
}
|
||||
@@ -44,15 +35,9 @@ func TestEval1(t *testing.T) {
|
||||
|
||||
func TestEval2(t *testing.T) {
|
||||
i := interp.New(interp.Opt{})
|
||||
_, err := i.Eval(`package foo; var I int = 2`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
evalCheck(t, i, `package foo; var I int = 2`)
|
||||
|
||||
t1, err := i.Eval(`foo.I`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t1 := evalCheck(t, i, `foo.I`)
|
||||
if t1.Interface().(int) != 2 {
|
||||
t.Fatalf("expected 2, got %v", t1)
|
||||
}
|
||||
@@ -60,15 +45,9 @@ func TestEval2(t *testing.T) {
|
||||
|
||||
func TestEval3(t *testing.T) {
|
||||
i := interp.New(interp.Opt{})
|
||||
_, err := i.Eval(`package foo; func Hello() string { return "hello" }`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
evalCheck(t, i, `package foo; func Hello() string { return "hello" }`)
|
||||
|
||||
v, err := i.Eval(`foo.Hello`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
v := evalCheck(t, i, `foo.Hello`)
|
||||
f, ok := v.Interface().(func() string)
|
||||
if !ok {
|
||||
t.Fatal("conversion failed")
|
||||
@@ -77,3 +56,52 @@ func TestEval3(t *testing.T) {
|
||||
t.Fatalf("expected hello, got %v", s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvalNil0(t *testing.T) {
|
||||
i := interp.New(interp.Opt{})
|
||||
evalCheck(t, i, `func getNil() error { return nil }`)
|
||||
|
||||
v := evalCheck(t, i, `getNil()`)
|
||||
if !v.IsNil() {
|
||||
t.Fatalf("expected nil, got %v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvalNil1(t *testing.T) {
|
||||
i := interp.New(interp.Opt{})
|
||||
evalCheck(t, i, `
|
||||
package bar
|
||||
|
||||
func New() func(string) error {
|
||||
return func(v string) error {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
v := evalCheck(t, i, `bar.New()`)
|
||||
fn, ok := v.Interface().(func(string) error)
|
||||
if !ok {
|
||||
t.Fatal("conversion failed")
|
||||
}
|
||||
|
||||
if res := fn("hello"); res != nil {
|
||||
t.Fatalf("expected nil, got %v", res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvalNil2(t *testing.T) {
|
||||
i := interp.New(interp.Opt{})
|
||||
_, err := i.Eval(`a := nil`)
|
||||
if err.Error() != "1:27: use of untyped nil" {
|
||||
t.Fatal("should have failed")
|
||||
}
|
||||
}
|
||||
|
||||
func evalCheck(t *testing.T, i *interp.Interpreter, src string) reflect.Value {
|
||||
res, err := i.Eval(src)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
@@ -1116,8 +1116,8 @@ func _case(n *Node) {
|
||||
typ := types[0]
|
||||
n.exec = func(f *Frame) Builtin {
|
||||
if v := value(f); !v.IsValid() {
|
||||
// match zero value against interface{}
|
||||
if typ.cat == UnsetT {
|
||||
// match zero value against nil
|
||||
if typ.cat == NilT {
|
||||
return tnext
|
||||
}
|
||||
return fnext
|
||||
|
||||
@@ -10,7 +10,7 @@ type Cat uint
|
||||
|
||||
// Types for go language
|
||||
const (
|
||||
UnsetT Cat = iota
|
||||
NilT Cat = iota
|
||||
AliasT
|
||||
ArrayT
|
||||
BinT
|
||||
@@ -48,7 +48,7 @@ const (
|
||||
)
|
||||
|
||||
var cats = [...]string{
|
||||
UnsetT: "UnsetT",
|
||||
NilT: "NilT",
|
||||
AliasT: "AliasT",
|
||||
ArrayT: "ArrayT",
|
||||
BinT: "BinT",
|
||||
@@ -346,7 +346,7 @@ func init() {
|
||||
zeroValues[ByteT] = reflect.ValueOf(byte(0))
|
||||
zeroValues[Complex64T] = reflect.ValueOf(complex64(0))
|
||||
zeroValues[Complex128T] = reflect.ValueOf(complex128(0))
|
||||
zeroValues[ErrorT] = reflect.ValueOf(error(nil))
|
||||
zeroValues[ErrorT] = reflect.ValueOf(new(error)).Elem()
|
||||
zeroValues[Float32T] = reflect.ValueOf(float32(0))
|
||||
zeroValues[Float64T] = reflect.ValueOf(float64(0))
|
||||
zeroValues[IntT] = reflect.ValueOf(int(0))
|
||||
@@ -509,8 +509,7 @@ func (t *Type) TypeOf() reflect.Type {
|
||||
return reflect.ChanOf(reflect.BothDir, t.val.TypeOf())
|
||||
|
||||
case ErrorT:
|
||||
var e = new(error)
|
||||
return reflect.TypeOf(e).Elem()
|
||||
return reflect.TypeOf(new(error)).Elem()
|
||||
|
||||
case FuncT:
|
||||
in := make([]reflect.Type, len(t.arg))
|
||||
|
||||
Reference in New Issue
Block a user