Skip to content

Commit

Permalink
interp: default to () for non-valued statements
Browse files Browse the repository at this point in the history
Update AST to use EmptyResult instead of new(interface{}) as the default
val for a node. Update genValue to always return node.val for variable
declaration, definition, and assignment.
  • Loading branch information
firelizzard18 committed Aug 23, 2021
1 parent b5bf4ef commit 3df4420
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 67 deletions.
7 changes: 3 additions & 4 deletions interp/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -422,9 +422,8 @@ func (interp *Interpreter) ast(src, name string, inc bool) (string, *node, error
var pkgName string

addChild := func(root **node, anc astNode, pos token.Pos, kind nkind, act action) *node {
var i interface{}
nindex := atomic.AddInt64(&interp.nindex, 1)
n := &node{anc: anc.node, interp: interp, index: nindex, pos: pos, kind: kind, action: act, val: &i, gen: builtin[act]}
n := &node{anc: anc.node, interp: interp, index: nindex, pos: pos, kind: kind, action: act, val: EmptyResult, gen: builtin[act]}
n.start = n
if anc.node == nil {
*root = n
Expand All @@ -436,14 +435,14 @@ func (interp *Interpreter) ast(src, name string, inc bool) (string, *node, error
// All case clause children are collected.
// Split children in condition and body nodes to desambiguify the AST.
nindex = atomic.AddInt64(&interp.nindex, 1)
body := &node{anc: anc.node, interp: interp, index: nindex, pos: pos, kind: caseBody, action: aNop, val: &i, gen: nop}
body := &node{anc: anc.node, interp: interp, index: nindex, pos: pos, kind: caseBody, action: aNop, val: EmptyResult, gen: nop}

if ts := anc.node.anc.anc; ts.kind == typeSwitch && ts.child[1].action == aAssign {
// In type switch clause, if a switch guard is assigned, duplicate the switch guard symbol
// in each clause body, so a different guard type can be set in each clause
name := ts.child[1].child[0].ident
nindex = atomic.AddInt64(&interp.nindex, 1)
gn := &node{anc: body, interp: interp, ident: name, index: nindex, pos: pos, kind: identExpr, action: aNop, val: &i, gen: nop}
gn := &node{anc: body, interp: interp, ident: name, index: nindex, pos: pos, kind: identExpr, action: aNop, val: EmptyResult, gen: nop}
body.child = append(body.child, gn)
}

Expand Down
9 changes: 9 additions & 0 deletions interp/interp.go
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,14 @@ func isFile(filesystem fs.FS, path string) bool {
return err == nil && fi.Mode().IsRegular()
}

// EmptyResult is the result of statements that have no result. EmptyResult is
// typed so that it is not equal to struct{}.
var EmptyResult emptyResult

type emptyResult struct{}

func (emptyResult) String() string { return "()" }

func (interp *Interpreter) eval(src, name string, inc bool) (res reflect.Value, err error) {
if name != "" {
interp.name = name
Expand Down Expand Up @@ -607,6 +615,7 @@ func (interp *Interpreter) eval(src, name string, inc bool) (res reflect.Value,
for _, n := range initNodes {
interp.run(n, interp.frame)
}

v := genValue(root)
res = v(interp.frame)

Expand Down
112 changes: 62 additions & 50 deletions interp/interp_eval_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,9 @@ func TestEvalArithmetic(t *testing.T) {
func TestEvalShift(t *testing.T) {
i := interp.New(interp.Options{})
runTests(t, i, []testCase{
{src: "a, b, m := uint32(1), uint32(2), uint32(0); m = a + (1 << b)", res: "5"},
{src: "c := uint(1); d := uint(+(-(1 << c)))", res: "18446744073709551614"},
{src: "e, f := uint32(0), uint32(0); f = 1 << -(e * 2)", res: "1"},
{src: "a, b, m := uint32(1), uint32(2), uint32(0); m = a + (1 << b); m", res: "5"},
{src: "c := uint(1); d := uint(+(-(1 << c))); d", res: "18446744073709551614"},
{src: "e, f := uint32(0), uint32(0); f = 1 << -(e * 2); f", res: "1"},
{src: "p := uint(0xdead); byte((1 << (p & 7)) - 1)", res: "31"},
{pre: func() { eval(t, i, "const k uint = 1 << 17") }, src: "int(k)", res: "131072"},
})
Expand All @@ -97,7 +97,7 @@ func TestOpVarConst(t *testing.T) {
func TestEvalStar(t *testing.T) {
i := interp.New(interp.Options{})
runTests(t, i, []testCase{
{src: `a := &struct{A int}{1}; b := *a`, res: "{1}"},
{src: `a := &struct{A int}{1}; b := *a; b`, res: "{1}"},
{src: `a := struct{A int}{1}; b := *a`, err: "1:57: invalid operation: cannot indirect \"a\""},
})
}
Expand All @@ -118,16 +118,16 @@ func TestEvalAssign(t *testing.T) {
}

runTests(t, i, []testCase{
{src: `a := "Hello"; a += " world"`, res: "Hello world"},
{src: `a := "Hello"; a += " world"; a`, res: "Hello world"},
{src: `b := "Hello"; b += 1`, err: "1:42: invalid operation: mismatched types string and int"},
{src: `c := "Hello"; c -= " world"`, err: "1:42: invalid operation: operator -= not defined on string"},
{src: "e := 64.4; e %= 64", err: "1:39: invalid operation: operator %= not defined on float64"},
{src: "f := int64(3.2)", err: "1:39: cannot convert expression of type float64 to type int64"},
{src: "g := 1; g <<= 8", res: "256"},
{src: "h := 1; h >>= 8", res: "0"},
{src: "i := 1; j := &i; (*j) = 2", res: "2"},
{src: "g := 1; g <<= 8; g", res: "256"},
{src: "h := 1; h >>= 8; h", res: "0"},
{src: "i := 1; j := &i; (*j) = 2; *j", res: "2"},
{src: "i64 := testpkg.val; i64 == 11", res: "true"},
{pre: func() { eval(t, i, "k := 1") }, src: `k := "Hello world"`, res: "Hello world"}, // allow reassignment in subsequent evaluations
{pre: func() { eval(t, i, "k := 1") }, src: `k := "Hello world"; k`, res: "Hello world"}, // allow reassignment in subsequent evaluations
})
}

Expand All @@ -140,19 +140,19 @@ func TestEvalBuiltin(t *testing.T) {
{src: `string(append([]byte("hello "), "world"...))`, res: "hello world"},
{src: `e := "world"; string(append([]byte("hello "), e...))`, res: "hello world"},
{src: `b := []int{1}; b = append(1, 2, 3); b`, err: "1:54: first argument to append must be slice; have int"},
{src: `g := len(a)`, res: "1"},
{src: `g := cap(a)`, res: "1"},
{src: `g := len("test")`, res: "4"},
{src: `g := len(map[string]string{"a": "b"})`, res: "1"},
{src: `g := len(a); g`, res: "1"},
{src: `g := cap(a); g`, res: "1"},
{src: `g := len("test"); g`, res: "4"},
{src: `g := len(map[string]string{"a": "b"}); g`, res: "1"},
{src: `n := len()`, err: "not enough arguments in call to len"},
{src: `n := len([]int, 0)`, err: "too many arguments for len"},
{src: `g := cap("test")`, err: "1:37: invalid argument for cap"},
{src: `g := cap(map[string]string{"a": "b"})`, err: "1:37: invalid argument for cap"},
{src: `h := make(chan int, 1); close(h); len(h)`, res: "0"},
{src: `close(a)`, err: "1:34: invalid operation: non-chan type []int"},
{src: `h := make(chan int, 1); var i <-chan int = h; close(i)`, err: "1:80: invalid operation: cannot close receive-only channel"},
{src: `j := make([]int, 2)`, res: "[0 0]"},
{src: `j := make([]int, 2, 3)`, res: "[0 0]"},
{src: `j := make([]int, 2); j`, res: "[0 0]"},
{src: `j := make([]int, 2, 3); j`, res: "[0 0]"},
{src: `j := make(int)`, err: "1:38: cannot make int; type must be slice, map, or channel"},
{src: `j := make([]int)`, err: "1:33: not enough arguments in call to make"},
{src: `j := make([]int, 0, 1, 2)`, err: "1:33: too many arguments for make"},
Expand Down Expand Up @@ -194,7 +194,7 @@ func TestEvalDecl(t *testing.T) {
func TestEvalDeclWithExpr(t *testing.T) {
i := interp.New(interp.Options{})
runTests(t, i, []testCase{
{src: `a1 := ""; var a2 int; a2 = 2`, res: "2"},
{src: `a1 := ""; var a2 int; a2 = 2; a2`, res: "2"},
{src: `b1 := ""; const b2 = 2; b2`, res: "2"},
{src: `c1 := ""; var c2, c3 [8]byte; c3[3]`, res: "0"},
})
Expand Down Expand Up @@ -435,21 +435,21 @@ func TestEvalCompositeArray(t *testing.T) {
i := interp.New(interp.Options{})
eval(t, i, `const l = 10`)
runTests(t, i, []testCase{
{src: "a := []int{1, 2, 7: 20, 30}", res: "[1 2 0 0 0 0 0 20 30]"},
{src: "a := []int{1, 2, 7: 20, 30}; a", res: "[1 2 0 0 0 0 0 20 30]"},
{src: `a := []int{1, 1.2}`, err: "1:42: 6/5 truncated to int"},
{src: `a := []int{0:1, 0:1}`, err: "1:46: duplicate index 0 in array or slice literal"},
{src: `a := []int{1.1:1, 1.2:"test"}`, err: "1:39: index float64 must be integer constant"},
{src: `a := [2]int{1, 1.2}`, err: "1:43: 6/5 truncated to int"},
{src: `a := [1]int{1, 2}`, err: "1:43: index 1 is out of bounds (>= 1)"},
{src: `b := [l]int{1, 2}`, res: "[1 2 0 0 0 0 0 0 0 0]"},
{src: `b := [l]int{1, 2}; b`, res: "[1 2 0 0 0 0 0 0 0 0]"},
{src: `i := 10; a := [i]int{1, 2}`, err: "1:43: non-constant array bound \"i\""},
})
}

func TestEvalCompositeMap(t *testing.T) {
i := interp.New(interp.Options{})
runTests(t, i, []testCase{
{src: `a := map[string]int{"one":1, "two":2}`, res: "map[one:1 two:2]"},
{src: `a := map[string]int{"one":1, "two":2}; a`, res: "map[one:1 two:2]"},
{src: `a := map[string]int{1:1, 2:2}`, err: "1:48: cannot convert 1 to string"},
{src: `a := map[string]int{"one":1, "two":2.2}`, err: "1:63: 11/5 truncated to int"},
{src: `a := map[string]int{1, "two":2}`, err: "1:48: missing key in map literal"},
Expand All @@ -460,14 +460,14 @@ func TestEvalCompositeMap(t *testing.T) {
func TestEvalCompositeStruct(t *testing.T) {
i := interp.New(interp.Options{})
runTests(t, i, []testCase{
{src: `a := struct{A,B,C int}{}`, res: "{0 0 0}"},
{src: `a := struct{A,B,C int}{1,2,3}`, res: "{1 2 3}"},
{src: `a := struct{A,B,C int}{}; a`, res: "{0 0 0}"},
{src: `a := struct{A,B,C int}{1,2,3}; a`, res: "{1 2 3}"},
{src: `a := struct{A,B,C int}{1,2.2,3}`, err: "1:53: 11/5 truncated to int"},
{src: `a := struct{A,B,C int}{1,2}`, err: "1:53: too few values in struct literal"},
{src: `a := struct{A,B,C int}{1,2,3,4}`, err: "1:57: too many values in struct literal"},
{src: `a := struct{A,B,C int}{1,B:2,3}`, err: "1:53: mixture of field:value and value elements in struct literal"},
{src: `a := struct{A,B,C int}{A:1,B:2,C:3}`, res: "{1 2 3}"},
{src: `a := struct{A,B,C int}{B:2}`, res: "{0 2 0}"},
{src: `a := struct{A,B,C int}{A:1,B:2,C:3}; a`, res: "{1 2 3}"},
{src: `a := struct{A,B,C int}{B:2}; a`, res: "{0 2 0}"},
{src: `a := struct{A,B,C int}{A:1,D:2,C:3}`, err: "1:55: unknown field D in struct literal"},
{src: `a := struct{A,B,C int}{A:1,A:2,C:3}`, err: "1:55: duplicate field name A in struct literal"},
{src: `a := struct{A,B,C int}{A:1,B:2.2,C:3}`, err: "1:57: 11/5 truncated to int"},
Expand All @@ -478,16 +478,16 @@ func TestEvalCompositeStruct(t *testing.T) {
func TestEvalSliceExpression(t *testing.T) {
i := interp.New(interp.Options{})
runTests(t, i, []testCase{
{src: `a := []int{0,1,2}[1:3]`, res: "[1 2]"},
{src: `a := []int{0,1,2}[:3]`, res: "[0 1 2]"},
{src: `a := []int{0,1,2}[:]`, res: "[0 1 2]"},
{src: `a := []int{0,1,2,3}[1:3:4]`, res: "[1 2]"},
{src: `a := []int{0,1,2,3}[:3:4]`, res: "[0 1 2]"},
{src: `ar := [3]int{0,1,2}; a := ar[1:3]`, res: "[1 2]"},
{src: `a := (&[3]int{0,1,2})[1:3]`, res: "[1 2]"},
{src: `a := (&[3]int{0,1,2})[1:3]`, res: "[1 2]"},
{src: `s := "hello"[1:3]`, res: "el"},
{src: `str := "hello"; s := str[1:3]`, res: "el"},
{src: `a := []int{0,1,2}[1:3]; a`, res: "[1 2]"},
{src: `a := []int{0,1,2}[:3]; a`, res: "[0 1 2]"},
{src: `a := []int{0,1,2}[:]; a`, res: "[0 1 2]"},
{src: `a := []int{0,1,2,3}[1:3:4]; a`, res: "[1 2]"},
{src: `a := []int{0,1,2,3}[:3:4]; a`, res: "[0 1 2]"},
{src: `ar := [3]int{0,1,2}; a := ar[1:3]; a`, res: "[1 2]"},
{src: `a := (&[3]int{0,1,2})[1:3]; a`, res: "[1 2]"},
{src: `a := (&[3]int{0,1,2})[1:3]; a`, res: "[1 2]"},
{src: `s := "hello"[1:3]; s`, res: "el"},
{src: `str := "hello"; s := str[1:3]; s`, res: "el"},
{src: `a := int(1)[0:1]`, err: "1:33: cannot slice type int"},
{src: `a := (&[]int{0,1,2,3})[1:3]`, err: "1:33: cannot slice type *[]int"},
{src: `a := "hello"[1:3:4]`, err: "1:45: invalid operation: 3-index slice of string"},
Expand All @@ -502,19 +502,19 @@ func TestEvalSliceExpression(t *testing.T) {
func TestEvalConversion(t *testing.T) {
i := interp.New(interp.Options{})
runTests(t, i, []testCase{
{src: `a := uint64(1)`, res: "1"},
{src: `i := 1.1; a := uint64(i)`, res: "1"},
{src: `b := string(49)`, res: "1"},
{src: `a := uint64(1); a`, res: "1"},
{src: `i := 1.1; a := uint64(i); a`, res: "1"},
{src: `b := string(49); b`, res: "1"},
{src: `c := uint64(1.1)`, err: "1:40: cannot convert expression of type float64 to type uint64"},
})
}

func TestEvalUnary(t *testing.T) {
i := interp.New(interp.Options{})
runTests(t, i, []testCase{
{src: "a := -1", res: "-1"},
{src: "b := +1", res: "1", skip: "BUG"},
{src: "c := !false", res: "true"},
{src: "a := -1; a", res: "-1"},
{src: "b := +1; b", res: "1"},
{src: "c := !false; c", res: "true"},
})
}

Expand Down Expand Up @@ -612,19 +612,19 @@ func TestEvalCall(t *testing.T) {
i := interp.New(interp.Options{})
runTests(t, i, []testCase{
{src: ` test := func(a int, b float64) int { return a }
a := test(1, 2.3)`, res: "1"},
a := test(1, 2.3); a`, res: "1"},
{src: ` test := func(a int, b float64) int { return a }
a := test(1)`, err: "2:10: not enough arguments in call to test"},
{src: ` test := func(a int, b float64) int { return a }
s := "test"
a := test(1, s)`, err: "3:18: cannot use type string as type float64"},
{src: ` test := func(a ...int) int { return 1 }
a := test([]int{1}...)`, res: "1"},
a := test([]int{1}...); a`, res: "1"},
{src: ` test := func(a ...int) int { return 1 }
a := test()`, res: "1"},
a := test(); a`, res: "1"},
{src: ` test := func(a ...int) int { return 1 }
blah := func() []int { return []int{1,1} }
a := test(blah()...)`, res: "1"},
a := test(blah()...); a`, res: "1"},
{src: ` test := func(a ...int) int { return 1 }
a := test([]string{"1"}...)`, err: "2:15: cannot use []string as type []int"},
{src: ` test := func(a ...int) int { return 1 }
Expand All @@ -637,10 +637,10 @@ func TestEvalCall(t *testing.T) {
a := test(blah()...)`, err: "3:15: cannot use ... with 2-valued func()(int,int)"},
{src: ` test := func(a, b int) int { return a }
blah := func() (int, int) { return 1, 1 }
a := test(blah())`, res: "1"},
a := test(blah()); a`, res: "1"},
{src: ` test := func(a, b int) int { return a }
blah := func() int { return 1 }
a := test(blah(), blah())`, res: "1"},
a := test(blah(), blah()); a`, res: "1"},
{src: ` test := func(a, b, c, d int) int { return a }
blah := func() (int, int) { return 1, 1 }
a := test(blah(), blah())`, err: "3:15: cannot use func()(int,int) as type int"},
Expand All @@ -659,11 +659,11 @@ func TestEvalBinCall(t *testing.T) {
t.Fatal(err)
}
runTests(t, i, []testCase{
{src: `a := fmt.Sprint(1, 2.3)`, res: "1 2.3"},
{src: `a := fmt.Sprint(1, 2.3); a`, res: "1 2.3"},
{src: `a := fmt.Sprintf()`, err: "1:33: not enough arguments in call to fmt.Sprintf"},
{src: `i := 1
a := fmt.Sprintf(i)`, err: "2:24: cannot use type int as type string"},
{src: `a := fmt.Sprint()`, res: ""},
{src: `a := fmt.Sprint(); a`, res: ""},
})
}

Expand Down Expand Up @@ -1624,7 +1624,7 @@ func TestStdio(t *testing.T) {
func TestIssue1142(t *testing.T) {
i := interp.New(interp.Options{})
runTests(t, i, []testCase{
{src: "a := 1; // foo bar", res: "1"},
{src: "a := 1; // foo bar", res: "()"},
})
}

Expand Down Expand Up @@ -1706,7 +1706,19 @@ func TestIssue1151(t *testing.T) {
i.ImportUsed()

runTests(t, i, []testCase{
{src: "x := pkg.Struct{1}", res: "{1}"},
{src: "x := pkg.Array{1}", res: "[1]"},
{src: "x := pkg.Struct{1}", res: "()"},
{src: "x := pkg.Array{1}", res: "()"},
})
}

func TestEvalAssignmentEmptyResult(t *testing.T) {
i := interp.New(interp.Options{})
runTests(t, i, []testCase{
{desc: "define", src: "a := 1", res: "()"},
{desc: "define multiple", src: "a, b := 1, 2", res: "()"},
{desc: "assign", src: "a = 2", res: "()"},
{desc: "assign multiple", src: "a, b = 2, 3", res: "()"},
{desc: "declare", src: `var c = 1.2`, res: "()"},
{desc: "declare after code", src: `_ = ""; var d = 1.2`, res: "()"},
})
}
36 changes: 23 additions & 13 deletions interp/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,13 +191,7 @@ func genValue(n *node) func(*frame) reflect.Value {
}
return func(f *frame) reflect.Value { return v }
case funcDecl:
var v reflect.Value
if w, ok := n.val.(reflect.Value); ok {
v = w
} else {
v = reflect.ValueOf(n.val)
}
return func(f *frame) reflect.Value { return v }
return genNodeVal(n)
default:
if n.rval.IsValid() {
convertConstantValue(n)
Expand All @@ -215,18 +209,34 @@ func genValue(n *node) func(*frame) reflect.Value {
return valueGenerator(n, i)
}
if n.findex == notInFrame {
var v reflect.Value
if w, ok := n.val.(reflect.Value); ok {
v = w
} else {
v = reflect.ValueOf(n.val)
return genNodeVal(n)
}
if n.kind == blockStmt {
n := n.lastChild()
switch n.kind {
case defineStmt, assignStmt:
return genNodeVal(n)
case declStmt:
n := n.lastChild()
if n.kind == varDecl {
return genNodeVal(n)
}
}
return func(f *frame) reflect.Value { return v }
}
return valueGenerator(n, n.findex)
}
}

func genNodeVal(n *node) func(*frame) reflect.Value {
var v reflect.Value
if w, ok := n.val.(reflect.Value); ok {
v = w
} else {
v = reflect.ValueOf(n.val)
}
return func(f *frame) reflect.Value { return v }
}

func genDestValue(typ *itype, n *node) func(*frame) reflect.Value {
convertLiteralValue(n, typ.TypeOf())
switch {
Expand Down

0 comments on commit 3df4420

Please sign in to comment.