Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d64563edee | ||
|
|
07039262a0 | ||
|
|
4ed9ccb5c4 | ||
|
|
25edcfee7a | ||
|
|
d183f4205e | ||
|
|
821e9ee006 | ||
|
|
00e3f924c1 | ||
|
|
2248851d77 | ||
|
|
f74d1ea6d8 | ||
|
|
606b4c3a37 | ||
|
|
4e77fc9436 | ||
|
|
ad9db379e7 | ||
|
|
7be17d393f | ||
|
|
5665c9a410 | ||
|
|
1cf9d345aa | ||
|
|
f07f25f1ba | ||
|
|
c93b836c77 | ||
|
|
371103f0d1 | ||
|
|
8bd7afbe62 | ||
|
|
8ea3a493f4 | ||
|
|
f2abd346c0 | ||
|
|
c784713aca | ||
|
|
14acf618af | ||
|
|
fbee2baf9d | ||
|
|
2819b4167b | ||
|
|
2af660cb1f | ||
|
|
8323068414 |
24
_test/break0.go
Normal file
24
_test/break0.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package main
|
||||
|
||||
func main() {
|
||||
n := 2
|
||||
m := 2
|
||||
foo := true
|
||||
OuterLoop:
|
||||
println("Boo")
|
||||
for i := 0; i < n; i++ {
|
||||
println("I: ", i)
|
||||
for j := 0; j < m; j++ {
|
||||
switch foo {
|
||||
case true:
|
||||
println(foo)
|
||||
break OuterLoop
|
||||
case false:
|
||||
println(foo)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Error:
|
||||
// 15:5: invalid break label OuterLoop
|
||||
24
_test/break1.go
Normal file
24
_test/break1.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package main
|
||||
|
||||
func main() {
|
||||
n := 2
|
||||
m := 2
|
||||
foo := true
|
||||
OuterLoop:
|
||||
for i := 0; i < n; i++ {
|
||||
println("I: ", i)
|
||||
for j := 0; j < m; j++ {
|
||||
switch foo {
|
||||
case true:
|
||||
println(foo)
|
||||
break OuterLoop
|
||||
case false:
|
||||
println(foo)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Output:
|
||||
// I: 0
|
||||
// true
|
||||
25
_test/break2.go
Normal file
25
_test/break2.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package main
|
||||
|
||||
func main() {
|
||||
n := 2
|
||||
m := 2
|
||||
foo := true
|
||||
OuterLoop:
|
||||
for i := 0; i < n; i++ {
|
||||
println("I: ", i)
|
||||
for j := 0; j < m; j++ {
|
||||
switch foo {
|
||||
case true:
|
||||
println(foo)
|
||||
break OuterLoop
|
||||
case false:
|
||||
println(foo)
|
||||
continue OuterLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Output:
|
||||
// I: 0
|
||||
// true
|
||||
27
_test/break3.go
Normal file
27
_test/break3.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package main
|
||||
|
||||
func main() {
|
||||
n := 2
|
||||
m := 2
|
||||
foo := true
|
||||
goto OuterLoop
|
||||
println("Boo")
|
||||
OuterLoop:
|
||||
for i := 0; i < n; i++ {
|
||||
println("I: ", i)
|
||||
for j := 0; j < m; j++ {
|
||||
switch foo {
|
||||
case true:
|
||||
println(foo)
|
||||
break OuterLoop
|
||||
case false:
|
||||
println(foo)
|
||||
goto OuterLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Output:
|
||||
// I: 0
|
||||
// true
|
||||
26
_test/cont2.go
Normal file
26
_test/cont2.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package main
|
||||
|
||||
func main() {
|
||||
n := 2
|
||||
m := 2
|
||||
foo := true
|
||||
OuterLoop:
|
||||
for i := 0; i < n; i++ {
|
||||
println("I: ", i)
|
||||
for j := 0; j < m; j++ {
|
||||
switch foo {
|
||||
case true:
|
||||
println(foo)
|
||||
continue OuterLoop
|
||||
case false:
|
||||
println(foo)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Output:
|
||||
// I: 0
|
||||
// true
|
||||
// I: 1
|
||||
// true
|
||||
24
_test/cont3.go
Normal file
24
_test/cont3.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package main
|
||||
|
||||
func main() {
|
||||
n := 2
|
||||
m := 2
|
||||
foo := true
|
||||
OuterLoop:
|
||||
println("boo")
|
||||
for i := 0; i < n; i++ {
|
||||
println("I: ", i)
|
||||
for j := 0; j < m; j++ {
|
||||
switch foo {
|
||||
case true:
|
||||
println(foo)
|
||||
continue OuterLoop
|
||||
case false:
|
||||
println(foo)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Error:
|
||||
// 15:5: invalid continue label OuterLoop
|
||||
42
_test/issue-1326.go
Normal file
42
_test/issue-1326.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package main
|
||||
|
||||
type Option interface {
|
||||
apply(*T)
|
||||
}
|
||||
|
||||
type T struct {
|
||||
s string
|
||||
}
|
||||
|
||||
type opt struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func (o *opt) apply(t *T) {
|
||||
println(o.name)
|
||||
}
|
||||
|
||||
func BuildOptions() []Option {
|
||||
return []Option{
|
||||
&opt{"opt1"},
|
||||
&opt{"opt2"},
|
||||
}
|
||||
}
|
||||
|
||||
func NewT(name string, options ...Option) *T {
|
||||
t := &T{name}
|
||||
for _, opt := range options {
|
||||
opt.apply(t)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func main() {
|
||||
t := NewT("hello", BuildOptions()...)
|
||||
println(t.s)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// opt1
|
||||
// opt2
|
||||
// hello
|
||||
16
_test/issue-1328.go
Normal file
16
_test/issue-1328.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
func main() {
|
||||
script := "hello"
|
||||
sumRaw := sha1.Sum([]byte(script))
|
||||
sum := hex.EncodeToString(sumRaw[:])
|
||||
println(sum)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d
|
||||
42
_test/issue-1330.go
Normal file
42
_test/issue-1330.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
)
|
||||
|
||||
type wrappedConn struct {
|
||||
net.Conn
|
||||
}
|
||||
|
||||
func main() {
|
||||
_, err := net.Listen("tcp", "127.0.0.1:49153")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
dialer := &net.Dialer{
|
||||
LocalAddr: &net.TCPAddr{
|
||||
IP: net.ParseIP("127.0.0.1"),
|
||||
Port: 0,
|
||||
},
|
||||
}
|
||||
|
||||
conn, err := dialer.Dial("tcp", "127.0.0.1:49153")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
t := &wrappedConn{conn}
|
||||
var w io.Writer = t
|
||||
if n, err := w.Write([]byte("hello")); err != nil {
|
||||
fmt.Println(err)
|
||||
} else {
|
||||
fmt.Println(n)
|
||||
}
|
||||
}
|
||||
|
||||
// Output:
|
||||
// 5
|
||||
17
_test/issue-1332.go
Normal file
17
_test/issue-1332.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package main
|
||||
|
||||
func run(fn func(name string)) { fn("test") }
|
||||
|
||||
type T2 struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func (t *T2) f(s string) { println(s, t.name) }
|
||||
|
||||
func main() {
|
||||
t2 := &T2{"foo"}
|
||||
run(t2.f)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// test foo
|
||||
37
_test/issue-1333.go
Normal file
37
_test/issue-1333.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
)
|
||||
|
||||
func mock(name string) http.HandlerFunc {
|
||||
return func(rw http.ResponseWriter, req *http.Request) {
|
||||
fmt.Fprint(rw, "Hello ", name)
|
||||
}
|
||||
}
|
||||
|
||||
func client(uri string) {
|
||||
resp, err := http.Get(uri)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(string(body))
|
||||
}
|
||||
|
||||
func main() {
|
||||
mux := http.NewServeMux()
|
||||
server := httptest.NewServer(mux)
|
||||
defer server.Close()
|
||||
mux.Handle("/", mock("foo"))
|
||||
client(server.URL)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Hello foo
|
||||
26
_test/issue-1337.go
Normal file
26
_test/issue-1337.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
func f(i interface{}) {
|
||||
switch at := i.(type) {
|
||||
case int, int8:
|
||||
println("integer", at)
|
||||
case io.Reader:
|
||||
println("reader")
|
||||
}
|
||||
println("bye")
|
||||
}
|
||||
|
||||
func main() {
|
||||
var fd *os.File
|
||||
var r io.Reader = fd
|
||||
f(r)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// reader
|
||||
// bye
|
||||
12
_test/issue-1342.go
Normal file
12
_test/issue-1342.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
var a interface{}
|
||||
a = "a"
|
||||
fmt.Println(a, a == "a")
|
||||
}
|
||||
|
||||
// Output:
|
||||
// a true
|
||||
13
_test/issue-1344.go
Normal file
13
_test/issue-1344.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
var m = map[string]interface{}{"a": "a"}
|
||||
a, _ := m["a"]
|
||||
b, ok := a.(string)
|
||||
fmt.Println("a:", a, ", b:", b, ", ok:", ok)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// a: a , b: a , ok: true
|
||||
21
_test/issue-1354.go
Normal file
21
_test/issue-1354.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package main
|
||||
|
||||
func main() {
|
||||
println(test()) // Go prints true, Yaegi false
|
||||
}
|
||||
|
||||
func test() bool {
|
||||
if true {
|
||||
goto label
|
||||
}
|
||||
goto label
|
||||
label:
|
||||
println("Go continues here")
|
||||
return true
|
||||
println("Yaegi goes straight to this return (this line is never printed)")
|
||||
return false
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Go continues here
|
||||
// true
|
||||
22
_test/issue-1355.go
Normal file
22
_test/issue-1355.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package main
|
||||
|
||||
import "github.com/traefik/yaegi/_test/p2"
|
||||
|
||||
func f(i interface{}) {
|
||||
_, ok := i.(p2.I)
|
||||
println("ok:", ok)
|
||||
}
|
||||
|
||||
func main() {
|
||||
var v *p2.T
|
||||
var i interface{}
|
||||
|
||||
i = v
|
||||
_, ok := i.(p2.I)
|
||||
println("ok:", ok)
|
||||
f(v)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// ok: true
|
||||
// ok: true
|
||||
14
_test/issue-1360.go
Normal file
14
_test/issue-1360.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
. "net"
|
||||
)
|
||||
|
||||
func main() {
|
||||
v := IP{}
|
||||
fmt.Println(v)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// <nil>
|
||||
27
_test/issue-1361.go
Normal file
27
_test/issue-1361.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
type obj struct {
|
||||
num float64
|
||||
}
|
||||
|
||||
type Fun func(o *obj) (r *obj, err error)
|
||||
|
||||
func numFun(fn func(f float64) float64) Fun {
|
||||
return func(o *obj) (*obj, error) {
|
||||
return &obj{fn(o.num)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
f := numFun(math.Cos)
|
||||
r, err := f(&obj{})
|
||||
fmt.Println(r, err)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// &{1} <nil>
|
||||
16
_test/issue-1364.go
Normal file
16
_test/issue-1364.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var value interface{}
|
||||
var err error
|
||||
value, err = strconv.ParseFloat("123", 64)
|
||||
fmt.Println(value, err)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// 123 <nil>
|
||||
18
_test/issue-1365.go
Normal file
18
_test/issue-1365.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package main
|
||||
|
||||
func genInt() (int, error) { return 3, nil }
|
||||
|
||||
func getInt() (value int) {
|
||||
value, err := genInt()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func main() {
|
||||
println(getInt())
|
||||
}
|
||||
|
||||
// Output:
|
||||
// 3
|
||||
16
_test/issue-1368.go
Normal file
16
_test/issue-1368.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package main
|
||||
|
||||
const dollar byte = 36
|
||||
|
||||
func main() {
|
||||
var c byte = 36
|
||||
switch true {
|
||||
case c == dollar:
|
||||
println("ok")
|
||||
default:
|
||||
println("not ok")
|
||||
}
|
||||
}
|
||||
|
||||
// Output:
|
||||
// ok
|
||||
18
_test/issue-1371.go
Normal file
18
_test/issue-1371.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type node struct {
|
||||
parent *node
|
||||
child []*node
|
||||
key string
|
||||
}
|
||||
|
||||
func main() {
|
||||
root := &node{key: "root"}
|
||||
root.child = nil
|
||||
fmt.Println("root:", root)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// root: &{<nil> [] root}
|
||||
17
_test/issue-1373.go
Normal file
17
_test/issue-1373.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
)
|
||||
|
||||
func NewBadExpr() ast.Expr {
|
||||
return &ast.BadExpr{}
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Printf("%T\n", NewBadExpr().(*ast.BadExpr))
|
||||
}
|
||||
|
||||
// Output:
|
||||
// *ast.BadExpr
|
||||
38
_test/issue-1375.go
Normal file
38
_test/issue-1375.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Option func(*Struct)
|
||||
|
||||
func WithOption(opt string) Option {
|
||||
return func(s *Struct) {
|
||||
s.opt = opt
|
||||
}
|
||||
}
|
||||
|
||||
type Struct struct {
|
||||
opt string
|
||||
}
|
||||
|
||||
func New(opts ...Option) *Struct {
|
||||
s := new(Struct)
|
||||
for _, opt := range opts {
|
||||
opt(s)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *Struct) ShowOption() {
|
||||
fmt.Println(s.opt)
|
||||
}
|
||||
|
||||
func main() {
|
||||
opts := []Option{
|
||||
WithOption("test"),
|
||||
}
|
||||
s := New(opts...)
|
||||
s.ShowOption()
|
||||
}
|
||||
|
||||
// Output:
|
||||
// test
|
||||
21
_test/issue-1378.go
Normal file
21
_test/issue-1378.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
t, err := time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fn := func() error {
|
||||
_, err := t.GobEncode()
|
||||
return err
|
||||
}
|
||||
fmt.Println(fn())
|
||||
}
|
||||
|
||||
// Output:
|
||||
// <nil>
|
||||
58
_test/issue-1381.go
Normal file
58
_test/issue-1381.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var bufPtrOne *bytes.Buffer
|
||||
var bufPtrTwo *bytes.Buffer
|
||||
var bufPtrThree *bytes.Buffer
|
||||
var bufPtrFour *bytes.Buffer
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
bufOne := bytes.Buffer{}
|
||||
bufTwo := &bytes.Buffer{}
|
||||
var bufThree bytes.Buffer
|
||||
bufFour := new(bytes.Buffer)
|
||||
|
||||
if bufPtrOne == nil {
|
||||
bufPtrOne = &bufOne
|
||||
} else if bufPtrOne == &bufOne {
|
||||
fmt.Println("bufOne was not properly redeclared")
|
||||
} else {
|
||||
fmt.Println("bufOne is properly redeclared")
|
||||
}
|
||||
|
||||
if bufPtrTwo == nil {
|
||||
bufPtrTwo = bufTwo
|
||||
} else if bufPtrTwo == bufTwo {
|
||||
fmt.Println("bufTwo was not properly redeclared")
|
||||
} else {
|
||||
fmt.Println("bufTwo is properly redeclared")
|
||||
}
|
||||
|
||||
if bufPtrThree == nil {
|
||||
bufPtrThree = &bufThree
|
||||
} else if bufPtrThree == &bufThree {
|
||||
fmt.Println("bufThree was not properly redeclared")
|
||||
} else {
|
||||
fmt.Println("bufThree is properly redeclared")
|
||||
}
|
||||
|
||||
if bufPtrFour == nil {
|
||||
bufPtrFour = bufFour
|
||||
} else if bufPtrFour == bufFour {
|
||||
fmt.Println("bufFour was not properly redeclared")
|
||||
} else {
|
||||
fmt.Println("bufFour is properly redeclared")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Output:
|
||||
// bufOne is properly redeclared
|
||||
// bufTwo is properly redeclared
|
||||
// bufThree is properly redeclared
|
||||
// bufFour is properly redeclared
|
||||
9
_test/p2/p2.go
Normal file
9
_test/p2/p2.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package p2
|
||||
|
||||
type I interface {
|
||||
isI()
|
||||
}
|
||||
|
||||
type T struct{}
|
||||
|
||||
func (t *T) isI() {}
|
||||
0
_test/p3/empty
Normal file
0
_test/p3/empty
Normal file
19
_test/var16.go
Normal file
19
_test/var16.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package main
|
||||
|
||||
func getArray() ([]int, error) { println("getArray"); return []int{1, 2}, nil }
|
||||
|
||||
func getNum() (int, error) { println("getNum"); return 3, nil }
|
||||
|
||||
func main() {
|
||||
if a, err := getNum(); err != nil {
|
||||
println("#1", a)
|
||||
} else if a, err := getArray(); err != nil {
|
||||
println("#2", a)
|
||||
}
|
||||
println("#3")
|
||||
}
|
||||
|
||||
// Output:
|
||||
// getNum
|
||||
// getArray
|
||||
// #3
|
||||
@@ -535,6 +535,7 @@ func {{$name}}(n *node) {
|
||||
typ := n.typ.concrete().TypeOf()
|
||||
isInterface := n.typ.TypeOf().Kind() == reflect.Interface
|
||||
c0, c1 := n.child[0], n.child[1]
|
||||
t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf()
|
||||
|
||||
{{- if or (eq $op.Name "==") (eq $op.Name "!=") }}
|
||||
|
||||
@@ -621,9 +622,39 @@ func {{$name}}(n *node) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Do not attempt to optimize '==' or '!=' if an operand is an interface.
|
||||
// This will preserve proper dynamic type checking at runtime. For static types,
|
||||
// type checks are already performed, so bypass them if possible.
|
||||
if t0.Kind() == reflect.Interface || t1.Kind() == reflect.Interface {
|
||||
v0 := genValue(c0)
|
||||
v1 := genValue(c1)
|
||||
if n.fnext != nil {
|
||||
fnext := getExec(n.fnext)
|
||||
n.exec = func(f *frame) bltn {
|
||||
i0 := v0(f).Interface()
|
||||
i1 := v1(f).Interface()
|
||||
if i0 {{$op.Name}} i1 {
|
||||
dest(f).SetBool(true)
|
||||
return tnext
|
||||
}
|
||||
dest(f).SetBool(false)
|
||||
return fnext
|
||||
}
|
||||
} else {
|
||||
dest := genValue(n)
|
||||
n.exec = func(f *frame) bltn {
|
||||
i0 := v0(f).Interface()
|
||||
i1 := v1(f).Interface()
|
||||
dest(f).SetBool(i0 {{$op.Name}} i1)
|
||||
return tnext
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
{{- end}}
|
||||
|
||||
switch t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf(); {
|
||||
switch {
|
||||
case isString(t0) || isString(t1):
|
||||
switch {
|
||||
case isInterface:
|
||||
|
||||
@@ -282,6 +282,7 @@ var actions = [...]string{
|
||||
aDec: "--",
|
||||
aEqual: "==",
|
||||
aGreater: ">",
|
||||
aGreaterEqual: ">=",
|
||||
aGetFunc: "getFunc",
|
||||
aGetIndex: "getIndex",
|
||||
aGetMethod: "getMethod",
|
||||
@@ -290,6 +291,7 @@ var actions = [...]string{
|
||||
aLand: "&&",
|
||||
aLor: "||",
|
||||
aLower: "<",
|
||||
aLowerEqual: "<=",
|
||||
aMethod: "Method",
|
||||
aMul: "*",
|
||||
aMulAssign: "*=",
|
||||
|
||||
172
interp/cfg.go
172
interp/cfg.go
@@ -202,38 +202,42 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
n.findex = -1
|
||||
n.val = nil
|
||||
sc = sc.pushBloc()
|
||||
// Pre-define symbols for labels defined in this block, so we are sure that
|
||||
// they are already defined when met.
|
||||
// TODO(marc): labels must be stored outside of symbols to avoid collisions.
|
||||
for _, c := range n.child {
|
||||
if c.kind != labeledStmt {
|
||||
continue
|
||||
}
|
||||
label := c.child[0].ident
|
||||
sym := &symbol{kind: labelSym, node: c, index: -1}
|
||||
sc.sym[label] = sym
|
||||
c.sym = sym
|
||||
}
|
||||
|
||||
case breakStmt, continueStmt, gotoStmt:
|
||||
if len(n.child) > 0 {
|
||||
// Handle labeled statements.
|
||||
label := n.child[0].ident
|
||||
if sym, _, ok := sc.lookup(label); ok {
|
||||
if sym.kind != labelSym {
|
||||
err = n.child[0].cfgErrorf("label %s not defined", label)
|
||||
break
|
||||
}
|
||||
sym.from = append(sym.from, n)
|
||||
n.sym = sym
|
||||
} else {
|
||||
n.sym = &symbol{kind: labelSym, from: []*node{n}, index: -1}
|
||||
sc.sym[label] = n.sym
|
||||
}
|
||||
if len(n.child) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
case labeledStmt:
|
||||
// Handle labeled statements.
|
||||
label := n.child[0].ident
|
||||
// TODO(marc): labels must be stored outside of symbols to avoid collisions
|
||||
// Used labels are searched in current and sub scopes, not upper ones.
|
||||
if sym, ok := sc.lookdown(label); ok {
|
||||
sym.node = n
|
||||
if sym, _, ok := sc.lookup(label); ok {
|
||||
if sym.kind != labelSym {
|
||||
err = n.child[0].cfgErrorf("label %s not defined", label)
|
||||
break
|
||||
}
|
||||
n.sym = sym
|
||||
} else {
|
||||
n.sym = &symbol{kind: labelSym, node: n, index: -1}
|
||||
n.sym = &symbol{kind: labelSym, index: -1}
|
||||
sc.sym[label] = n.sym
|
||||
}
|
||||
if n.kind == gotoStmt {
|
||||
n.sym.from = append(n.sym.from, n) // To allow forward goto statements.
|
||||
}
|
||||
sc.sym[label] = n.sym
|
||||
|
||||
case caseClause:
|
||||
sc = sc.pushBloc()
|
||||
@@ -319,6 +323,10 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
}
|
||||
// Propagate type to children, to handle implicit types
|
||||
for _, c := range child {
|
||||
if isBlank(c) {
|
||||
err = n.cfgErrorf("cannot use _ as value")
|
||||
return false
|
||||
}
|
||||
switch c.kind {
|
||||
case binaryExpr, unaryExpr, compositeLitExpr:
|
||||
// Do not attempt to propagate composite type to operator expressions,
|
||||
@@ -478,6 +486,10 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
|
||||
switch n.kind {
|
||||
case addressExpr:
|
||||
if isBlank(n.child[0]) {
|
||||
err = n.cfgErrorf("cannot use _ as value")
|
||||
break
|
||||
}
|
||||
wireChild(n)
|
||||
|
||||
err = check.addressExpr(n)
|
||||
@@ -513,6 +525,11 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
updateSym := false
|
||||
var sym *symbol
|
||||
var level int
|
||||
|
||||
if isBlank(src) {
|
||||
err = n.cfgErrorf("cannot use _ as value")
|
||||
break
|
||||
}
|
||||
if n.kind == defineStmt || (n.kind == assignStmt && dest.ident == "_") {
|
||||
if atyp != nil {
|
||||
dest.typ = atyp
|
||||
@@ -645,6 +662,10 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
}
|
||||
|
||||
case incDecStmt:
|
||||
err = check.unaryExpr(n)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
wireChild(n)
|
||||
n.findex = n.child[0].findex
|
||||
n.level = n.child[0].level
|
||||
@@ -763,6 +784,10 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
}
|
||||
|
||||
case indexExpr:
|
||||
if isBlank(n.child[0]) {
|
||||
err = n.cfgErrorf("cannot use _ as value")
|
||||
break
|
||||
}
|
||||
wireChild(n)
|
||||
t := n.child[0].typ
|
||||
switch t.cat {
|
||||
@@ -853,30 +878,51 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
n.rval = l.rval
|
||||
|
||||
case breakStmt:
|
||||
if len(n.child) > 0 {
|
||||
gotoLabel(n.sym)
|
||||
} else {
|
||||
if len(n.child) == 0 {
|
||||
n.tnext = sc.loop
|
||||
break
|
||||
}
|
||||
if !n.hasAnc(n.sym.node) {
|
||||
err = n.cfgErrorf("invalid break label %s", n.child[0].ident)
|
||||
break
|
||||
}
|
||||
n.tnext = n.sym.node
|
||||
|
||||
case continueStmt:
|
||||
if len(n.child) > 0 {
|
||||
gotoLabel(n.sym)
|
||||
} else {
|
||||
if len(n.child) == 0 {
|
||||
n.tnext = sc.loopRestart
|
||||
break
|
||||
}
|
||||
if !n.hasAnc(n.sym.node) {
|
||||
err = n.cfgErrorf("invalid continue label %s", n.child[0].ident)
|
||||
break
|
||||
}
|
||||
n.tnext = n.sym.node.child[1].lastChild().start
|
||||
|
||||
case gotoStmt:
|
||||
gotoLabel(n.sym)
|
||||
if n.sym.node == nil {
|
||||
// It can be only due to a forward goto, to be resolved at labeledStmt.
|
||||
// Invalid goto labels are catched at AST parsing.
|
||||
break
|
||||
}
|
||||
n.tnext = n.sym.node.start
|
||||
|
||||
case labeledStmt:
|
||||
wireChild(n)
|
||||
if len(n.child) > 1 {
|
||||
n.start = n.child[1].start
|
||||
}
|
||||
gotoLabel(n.sym)
|
||||
for _, c := range n.sym.from {
|
||||
c.tnext = n.start // Resolve forward goto.
|
||||
}
|
||||
|
||||
case callExpr:
|
||||
for _, c := range n.child {
|
||||
if isBlank(c) {
|
||||
err = n.cfgErrorf("cannot use _ as value")
|
||||
return
|
||||
}
|
||||
}
|
||||
wireChild(n)
|
||||
switch {
|
||||
case isBuiltinCall(n, sc):
|
||||
@@ -1392,9 +1438,17 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
sc = sc.pop()
|
||||
|
||||
case keyValueExpr:
|
||||
if isBlank(n.child[1]) {
|
||||
err = n.cfgErrorf("cannot use _ as value")
|
||||
break
|
||||
}
|
||||
wireChild(n)
|
||||
|
||||
case landExpr:
|
||||
if isBlank(n.child[0]) || isBlank(n.child[1]) {
|
||||
err = n.cfgErrorf("cannot use _ as value")
|
||||
break
|
||||
}
|
||||
n.start = n.child[0].start
|
||||
n.child[0].tnext = n.child[1].start
|
||||
setFNext(n.child[0], n)
|
||||
@@ -1406,6 +1460,10 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
}
|
||||
|
||||
case lorExpr:
|
||||
if isBlank(n.child[0]) || isBlank(n.child[1]) {
|
||||
err = n.cfgErrorf("cannot use _ as value")
|
||||
break
|
||||
}
|
||||
n.start = n.child[0].start
|
||||
n.child[0].tnext = n
|
||||
setFNext(n.child[0], n.child[1].start)
|
||||
@@ -1451,6 +1509,12 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
err = n.cfgErrorf("too many arguments to return")
|
||||
break
|
||||
}
|
||||
for _, c := range n.child {
|
||||
if isBlank(c) {
|
||||
err = n.cfgErrorf("cannot use _ as value")
|
||||
return
|
||||
}
|
||||
}
|
||||
returnSig := sc.def.child[2]
|
||||
if mustReturnValue(returnSig) {
|
||||
nret := len(n.child)
|
||||
@@ -1742,6 +1806,10 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
}
|
||||
|
||||
case starExpr:
|
||||
if isBlank(n.child[0]) {
|
||||
err = n.cfgErrorf("cannot use _ as value")
|
||||
break
|
||||
}
|
||||
switch {
|
||||
case n.anc.kind == defineStmt && len(n.anc.child) == 3 && n.anc.child[1] == n:
|
||||
// pointer type expression in a var definition
|
||||
@@ -1832,6 +1900,7 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
setFNext(c, clauses[i+1])
|
||||
}
|
||||
}
|
||||
sbn.start = clauses[0].start
|
||||
n.start = n.child[0].start
|
||||
n.child[0].tnext = sbn.start
|
||||
|
||||
@@ -1886,6 +1955,10 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
|
||||
wireChild(n)
|
||||
c0, c1 := n.child[0], n.child[1]
|
||||
if isBlank(c0) || isBlank(c1) {
|
||||
err = n.cfgErrorf("cannot use _ as value")
|
||||
break
|
||||
}
|
||||
if c1.typ == nil {
|
||||
if c1.typ, err = nodeType(interp, sc, c1); err != nil {
|
||||
return
|
||||
@@ -2060,12 +2133,18 @@ func compDefineX(sc *scope, n *node) error {
|
||||
}
|
||||
|
||||
for i, t := range types {
|
||||
index := sc.add(t)
|
||||
sc.sym[n.child[i].ident] = &symbol{index: index, kind: varSym, typ: t}
|
||||
var index int
|
||||
id := n.child[i].ident
|
||||
if sym, level, ok := sc.lookup(id); ok && level == n.child[i].level && sym.kind == varSym && sym.typ.equals(t) {
|
||||
// Reuse symbol in case of a variable redeclaration with the same type.
|
||||
index = sym.index
|
||||
} else {
|
||||
index = sc.add(t)
|
||||
sc.sym[id] = &symbol{index: index, kind: varSym, typ: t}
|
||||
}
|
||||
n.child[i].typ = t
|
||||
n.child[i].findex = index
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2419,6 +2498,15 @@ func (n *node) fieldType(m int) *node {
|
||||
// lastChild returns the last child of a node.
|
||||
func (n *node) lastChild() *node { return n.child[len(n.child)-1] }
|
||||
|
||||
func (n *node) hasAnc(nod *node) bool {
|
||||
for a := n.anc; a != nil; a = a.anc {
|
||||
if a == nod {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isKey(n *node) bool {
|
||||
return n.anc.kind == fileStmt ||
|
||||
(n.anc.kind == selectorExpr && n.anc.child[0] != n) ||
|
||||
@@ -2585,17 +2673,6 @@ func typeSwichAssign(n *node) bool {
|
||||
return ts.kind == typeSwitch && ts.child[1].action == aAssign
|
||||
}
|
||||
|
||||
func gotoLabel(s *symbol) {
|
||||
if s.node == nil {
|
||||
return
|
||||
}
|
||||
for _, c := range s.from {
|
||||
if c.tnext == nil {
|
||||
c.tnext = s.node.start
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func compositeGenerator(n *node, typ *itype, rtyp reflect.Type) (gen bltnGenerator) {
|
||||
switch typ.cat {
|
||||
case aliasT, ptrT:
|
||||
@@ -2728,3 +2805,10 @@ func isBoolAction(n *node) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isBlank(n *node) bool {
|
||||
if n.kind == parenExpr && len(n.child) > 0 {
|
||||
return isBlank(n.child[0])
|
||||
}
|
||||
return n.ident == "_"
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@ import (
|
||||
)
|
||||
|
||||
func TestCompileAST(t *testing.T) {
|
||||
file, err := parser.ParseFile(token.NewFileSet(), "_.go", `
|
||||
i := New(Options{})
|
||||
file, err := parser.ParseFile(i.FileSet(), "_.go", `
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
@@ -61,7 +62,6 @@ func TestCompileAST(t *testing.T) {
|
||||
{desc: "expr", node: dFunc.Body.List[0]},
|
||||
}
|
||||
|
||||
i := New(Options{})
|
||||
_ = i.Use(stdlib.Symbols)
|
||||
|
||||
for _, c := range cases {
|
||||
|
||||
@@ -59,6 +59,9 @@ func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([
|
||||
|
||||
for i := 0; i < n.nleft; i++ {
|
||||
dest, src := n.child[i], n.child[sbase+i]
|
||||
if isBlank(src) {
|
||||
err = n.cfgErrorf("cannot use _ as value")
|
||||
}
|
||||
val := src.rval
|
||||
if n.anc.kind == constDecl {
|
||||
if _, err2 := interp.cfg(n, sc, importPath, pkgName); err2 != nil {
|
||||
@@ -208,15 +211,23 @@ func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([
|
||||
case ".": // import symbols in current scope
|
||||
for n, v := range pkg {
|
||||
typ := v.Type()
|
||||
kind := binSym
|
||||
if isBinType(v) {
|
||||
typ = typ.Elem()
|
||||
kind = typeSym
|
||||
}
|
||||
sc.sym[n] = &symbol{kind: binSym, typ: valueTOf(typ, withScope(sc)), rval: v}
|
||||
sc.sym[n] = &symbol{kind: kind, typ: valueTOf(typ, withScope(sc)), rval: v}
|
||||
}
|
||||
default: // import symbols in package namespace
|
||||
if name == "" {
|
||||
name = interp.pkgNames[ipath]
|
||||
}
|
||||
|
||||
// If an incomplete type exists, delete it
|
||||
if sym, exists := sc.sym[name]; exists && sym.kind == typeSym && sym.typ.incomplete {
|
||||
delete(sc.sym, name)
|
||||
}
|
||||
|
||||
// Imports of a same package are all mapped in the same scope, so we cannot just
|
||||
// map them by their names, otherwise we could have collisions from same-name
|
||||
// imports in different source files of the same package. Therefore, we suffix
|
||||
@@ -266,6 +277,10 @@ func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([
|
||||
}
|
||||
|
||||
case typeSpec, typeSpecAssign:
|
||||
if isBlank(n.child[0]) {
|
||||
err = n.cfgErrorf("cannot use _ as value")
|
||||
return false
|
||||
}
|
||||
typeName := n.child[0].ident
|
||||
var typ *itype
|
||||
if typ, err = nodeType(interp, sc, n.child[1]); err != nil {
|
||||
|
||||
@@ -19,6 +19,8 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -628,7 +630,14 @@ func (interp *Interpreter) EvalWithContext(ctx context.Context, src string) (ref
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done)
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
var pc [64]uintptr
|
||||
n := runtime.Callers(1, pc[:])
|
||||
err = Panic{Value: r, Callers: pc[:n], Stack: debug.Stack()}
|
||||
}
|
||||
close(done)
|
||||
}()
|
||||
v, err = interp.Eval(src)
|
||||
}()
|
||||
|
||||
|
||||
@@ -37,6 +37,8 @@ func TestInterpConsistencyBuild(t *testing.T) {
|
||||
file.Name() == "assign12.go" || // expect error
|
||||
file.Name() == "assign15.go" || // expect error
|
||||
file.Name() == "bad0.go" || // expect error
|
||||
file.Name() == "break0.go" || // expect error
|
||||
file.Name() == "cont3.go" || // expect error
|
||||
file.Name() == "const9.go" || // expect error
|
||||
file.Name() == "export1.go" || // non-main package
|
||||
file.Name() == "export0.go" || // non-main package
|
||||
@@ -52,6 +54,7 @@ func TestInterpConsistencyBuild(t *testing.T) {
|
||||
file.Name() == "io0.go" || // use random number
|
||||
file.Name() == "issue-1093.go" || // expect error
|
||||
file.Name() == "issue-1276.go" || // expect error
|
||||
file.Name() == "issue-1330.go" || // expect error
|
||||
file.Name() == "op1.go" || // expect error
|
||||
file.Name() == "op7.go" || // expect error
|
||||
file.Name() == "op9.go" || // expect error
|
||||
@@ -197,6 +200,16 @@ func TestInterpErrorConsistency(t *testing.T) {
|
||||
expectedInterp: "1:1: expected 'package', found println",
|
||||
expectedExec: "1:1: expected 'package', found println",
|
||||
},
|
||||
{
|
||||
fileName: "break0.go",
|
||||
expectedInterp: "15:5: invalid break label OuterLoop",
|
||||
expectedExec: "15:11: invalid break label OuterLoop",
|
||||
},
|
||||
{
|
||||
fileName: "cont3.go",
|
||||
expectedInterp: "15:5: invalid continue label OuterLoop",
|
||||
expectedExec: "15:14: invalid continue label OuterLoop",
|
||||
},
|
||||
{
|
||||
fileName: "const9.go",
|
||||
expectedInterp: "5:2: constant definition loop",
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"go/parser"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
@@ -127,6 +129,10 @@ func TestEvalAssign(t *testing.T) {
|
||||
{src: "i := 1; j := &i; (*j) = 2", 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
|
||||
{src: "_ = _", err: "1:28: cannot use _ as value"},
|
||||
{src: "j := true || _", err: "1:33: cannot use _ as value"},
|
||||
{src: "j := true && _", err: "1:33: cannot use _ as value"},
|
||||
{src: "j := interface{}(int(1)); j.(_)", err: "1:54: cannot use _ as value"},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -177,6 +183,7 @@ func TestEvalBuiltin(t *testing.T) {
|
||||
{src: `t := map[int]int{}; t[123]--; t`, res: "map[123:-1]"},
|
||||
{src: `t := map[int]int{}; t[123] += 1; t`, res: "map[123:1]"},
|
||||
{src: `t := map[int]int{}; t[123] -= 1; t`, res: "map[123:-1]"},
|
||||
{src: `println("hello", _)`, err: "1:28: cannot use _ as value"},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -201,6 +208,14 @@ func TestEvalDeclWithExpr(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestEvalTypeSpec(t *testing.T) {
|
||||
i := interp.New(interp.Options{})
|
||||
runTests(t, i, []testCase{
|
||||
{src: `type _ struct{}`, err: "1:19: cannot use _ as value"},
|
||||
{src: `a := struct{a, _ int}{32, 0}`, res: "{32 0}"},
|
||||
})
|
||||
}
|
||||
|
||||
func TestEvalFunc(t *testing.T) {
|
||||
i := interp.New(interp.Options{})
|
||||
runTests(t, i, []testCase{
|
||||
@@ -209,6 +224,8 @@ func TestEvalFunc(t *testing.T) {
|
||||
{src: `(func () int {f := func() (a, b int) {a, b = 3, 4; return}; x, y := f(); return x+y})()`, res: "7"},
|
||||
{src: `(func () int {f := func() (a int, b, c int) {a, b, c = 3, 4, 5; return}; x, y, z := f(); return x+y+z})()`, res: "12"},
|
||||
{src: `(func () int {f := func() (a, b, c int) {a, b, c = 3, 4, 5; return}; x, y, z := f(); return x+y+z})()`, res: "12"},
|
||||
{src: `func f() int { return _ }`, err: "1:29: cannot use _ as value"},
|
||||
{src: `(func (x int) {})(_)`, err: "1:28: cannot use _ as value"},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -431,6 +448,16 @@ func TestEvalComparison(t *testing.T) {
|
||||
`,
|
||||
err: "7:13: invalid operation: mismatched types main.Foo and main.Bar",
|
||||
},
|
||||
{src: `1 > _`, err: "1:28: cannot use _ as value"},
|
||||
{src: `(_) > 1`, err: "1:28: cannot use _ as value"},
|
||||
{src: `v := interface{}(2); v == 2`, res: "true"},
|
||||
{src: `v := interface{}(2); v > 1`, err: "1:49: invalid operation: operator > not defined on interface{}"},
|
||||
{src: `v := interface{}(int64(2)); v == 2`, res: "false"},
|
||||
{src: `v := interface{}(int64(2)); v != 2`, res: "true"},
|
||||
{src: `v := interface{}(2.3); v == 2.3`, res: "true"},
|
||||
{src: `v := interface{}(float32(2.3)); v != 2.3`, res: "true"},
|
||||
{src: `v := interface{}("hello"); v == "hello"`, res: "true"},
|
||||
{src: `v := interface{}("hello"); v < "hellp"`, err: "1:55: invalid operation: operator < not defined on interface{}"},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -476,6 +503,8 @@ func TestEvalCompositeStruct(t *testing.T) {
|
||||
{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"},
|
||||
{src: `a := struct{A,B,C int}{A:1,2,C:3}`, err: "1:55: mixture of field:value and value elements in struct literal"},
|
||||
{src: `a := struct{A,B,C int}{1,2,_}`, err: "1:33: cannot use _ as value"},
|
||||
{src: `a := struct{A,B,C int}{B: _}`, err: "1:51: cannot use _ as value"},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -500,6 +529,8 @@ func TestEvalSliceExpression(t *testing.T) {
|
||||
{src: `a := []int{0,1,2,3}[1:3:]`, err: "1:51: 3rd index required in 3-index slice"},
|
||||
{src: `a := []int{0,1,2}[3:1]`, err: "invalid index values, must be low <= high <= max"},
|
||||
{pre: func() { eval(t, i, `type Str = string; var r Str = "truc"`) }, src: `r[1]`, res: "114"},
|
||||
{src: `_[12]`, err: "1:28: cannot use _ as value"},
|
||||
{src: `b := []int{0,1,2}[_:4]`, err: "1:33: cannot use _ as value"},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -510,6 +541,7 @@ func TestEvalConversion(t *testing.T) {
|
||||
{src: `i := 1.1; a := uint64(i)`, res: "1"},
|
||||
{src: `b := string(49)`, res: "1"},
|
||||
{src: `c := uint64(1.1)`, err: "1:40: cannot convert expression of type untyped float to type uint64"},
|
||||
{src: `int(_)`, err: "1:28: cannot use _ as value"},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -519,6 +551,9 @@ func TestEvalUnary(t *testing.T) {
|
||||
{src: "a := -1", res: "-1"},
|
||||
{src: "b := +1", res: "1", skip: "BUG"},
|
||||
{src: "c := !false", res: "true"},
|
||||
{src: "_ = 2; _++", err: "1:35: cannot use _ as value"},
|
||||
{src: "_ = false; !_ == true", err: "1:39: cannot use _ as value"},
|
||||
{src: "!((((_))))", err: "1:28: cannot use _ as value"},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1625,6 +1660,16 @@ func TestStdio(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoGoFiles(t *testing.T) {
|
||||
i := interp.New(interp.Options{GoPath: build.Default.GOPATH})
|
||||
_, err := i.Eval(`import "github.com/traefik/yaegi/_test/p3"`)
|
||||
if strings.Contains(err.Error(), "no Go files in") {
|
||||
return
|
||||
}
|
||||
|
||||
t.Fatalf("failed to detect no Go files: %v", err)
|
||||
}
|
||||
|
||||
func TestIssue1142(t *testing.T) {
|
||||
i := interp.New(interp.Options{})
|
||||
runTests(t, i, []testCase{
|
||||
@@ -1749,3 +1794,59 @@ func TestRestrictedEnv(t *testing.T) {
|
||||
t.Fatal("expected \"\", got " + s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssue1388(t *testing.T) {
|
||||
i := interp.New(interp.Options{Env: []string{"foo=bar"}})
|
||||
err := i.Use(stdlib.Symbols)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = i.Eval(`x := errors.New("")`)
|
||||
if err == nil {
|
||||
t.Fatal("Expected an error")
|
||||
}
|
||||
|
||||
_, err = i.Eval(`import "errors"`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = i.Eval(`x := errors.New("")`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssue1383(t *testing.T) {
|
||||
const src = `
|
||||
package main
|
||||
|
||||
func main() {
|
||||
fmt.Println("Hello")
|
||||
}
|
||||
`
|
||||
|
||||
interp := interp.New(interp.Options{})
|
||||
err := interp.Use(stdlib.Symbols)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = interp.Eval(`import "fmt"`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ast, err := parser.ParseFile(interp.FileSet(), "_.go", src, parser.DeclarationErrors)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
prog, err := interp.CompileAST(ast)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = interp.Execute(prog)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package interp
|
||||
|
||||
import (
|
||||
"go/constant"
|
||||
"log"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/traefik/yaegi/stdlib"
|
||||
)
|
||||
|
||||
func init() { log.SetFlags(log.Lshortfile) }
|
||||
@@ -19,13 +22,13 @@ func TestIsNatural(t *testing.T) {
|
||||
n: &node{
|
||||
typ: &itype{
|
||||
rtype: func() reflect.Type {
|
||||
var x uint = 3
|
||||
return reflect.TypeOf(x)
|
||||
var a uint = 3
|
||||
return reflect.TypeOf(a)
|
||||
}(),
|
||||
},
|
||||
rval: func() reflect.Value {
|
||||
var x uint = 3
|
||||
return reflect.ValueOf(x)
|
||||
var a uint = 3
|
||||
return reflect.ValueOf(a)
|
||||
}(),
|
||||
},
|
||||
expected: true,
|
||||
@@ -35,13 +38,13 @@ func TestIsNatural(t *testing.T) {
|
||||
n: &node{
|
||||
typ: &itype{
|
||||
rtype: func() reflect.Type {
|
||||
x := 3
|
||||
return reflect.TypeOf(x)
|
||||
a := 3
|
||||
return reflect.TypeOf(a)
|
||||
}(),
|
||||
},
|
||||
rval: func() reflect.Value {
|
||||
x := 3
|
||||
return reflect.ValueOf(x)
|
||||
a := 3
|
||||
return reflect.ValueOf(a)
|
||||
}(),
|
||||
},
|
||||
expected: true,
|
||||
@@ -51,13 +54,13 @@ func TestIsNatural(t *testing.T) {
|
||||
n: &node{
|
||||
typ: &itype{
|
||||
rtype: func() reflect.Type {
|
||||
var x int = 3
|
||||
return reflect.TypeOf(x)
|
||||
var a int = 3
|
||||
return reflect.TypeOf(a)
|
||||
}(),
|
||||
},
|
||||
rval: func() reflect.Value {
|
||||
var x int = 3
|
||||
return reflect.ValueOf(x)
|
||||
var a int = 3
|
||||
return reflect.ValueOf(a)
|
||||
}(),
|
||||
},
|
||||
expected: true,
|
||||
@@ -67,13 +70,13 @@ func TestIsNatural(t *testing.T) {
|
||||
n: &node{
|
||||
typ: &itype{
|
||||
rtype: func() reflect.Type {
|
||||
var x float64 = 3.0
|
||||
return reflect.TypeOf(x)
|
||||
var a float64 = 3.0
|
||||
return reflect.TypeOf(a)
|
||||
}(),
|
||||
},
|
||||
rval: func() reflect.Value {
|
||||
var x float64 = 3.0
|
||||
return reflect.ValueOf(x)
|
||||
var a float64 = 3.0
|
||||
return reflect.ValueOf(a)
|
||||
}(),
|
||||
},
|
||||
expected: true,
|
||||
@@ -83,13 +86,13 @@ func TestIsNatural(t *testing.T) {
|
||||
n: &node{
|
||||
typ: &itype{
|
||||
rtype: func() reflect.Type {
|
||||
var x float64 = 3.14
|
||||
return reflect.TypeOf(x)
|
||||
var a float64 = 3.14
|
||||
return reflect.TypeOf(a)
|
||||
}(),
|
||||
},
|
||||
rval: func() reflect.Value {
|
||||
var x float64 = 3.14
|
||||
return reflect.ValueOf(x)
|
||||
var a float64 = 3.14
|
||||
return reflect.ValueOf(a)
|
||||
}(),
|
||||
},
|
||||
expected: false,
|
||||
@@ -99,13 +102,13 @@ func TestIsNatural(t *testing.T) {
|
||||
n: &node{
|
||||
typ: &itype{
|
||||
rtype: func() reflect.Type {
|
||||
var x int = -3
|
||||
return reflect.TypeOf(x)
|
||||
var a int = -3
|
||||
return reflect.TypeOf(a)
|
||||
}(),
|
||||
},
|
||||
rval: func() reflect.Value {
|
||||
var x int = -3
|
||||
return reflect.ValueOf(x)
|
||||
var a int = -3
|
||||
return reflect.ValueOf(a)
|
||||
}(),
|
||||
},
|
||||
expected: false,
|
||||
@@ -188,3 +191,42 @@ func TestIsNatural(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGlobals(t *testing.T) {
|
||||
i := New(Options{})
|
||||
if err := i.Use(stdlib.Symbols); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := i.Eval("var a = 1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := i.Eval("b := 2"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := i.Eval("const c = 3"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
g := i.Globals()
|
||||
a := g["a"]
|
||||
if !a.IsValid() {
|
||||
t.Fatal("a not found")
|
||||
}
|
||||
if a := a.Interface(); a != 1 {
|
||||
t.Fatalf("wrong a: want (%[1]T) %[1]v, have (%[2]T) %[2]v", 1, a)
|
||||
}
|
||||
b := g["b"]
|
||||
if !b.IsValid() {
|
||||
t.Fatal("b not found")
|
||||
}
|
||||
if b := b.Interface(); b != 2 {
|
||||
t.Fatalf("wrong b: want (%[1]T) %[1]v, have (%[2]T) %[2]v", 2, b)
|
||||
}
|
||||
c := g["c"]
|
||||
if !c.IsValid() {
|
||||
t.Fatal("c not found")
|
||||
}
|
||||
if cc, ok := c.Interface().(constant.Value); ok && constant.MakeInt64(3) != cc {
|
||||
t.Fatalf("wrong c: want (%[1]T) %[1]v, have (%[2]T) %[2]v", constant.MakeInt64(3), cc)
|
||||
}
|
||||
}
|
||||
|
||||
78
interp/op.go
78
interp/op.go
@@ -2624,6 +2624,7 @@ func equal(n *node) {
|
||||
typ := n.typ.concrete().TypeOf()
|
||||
isInterface := n.typ.TypeOf().Kind() == reflect.Interface
|
||||
c0, c1 := n.child[0], n.child[1]
|
||||
t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf()
|
||||
|
||||
if c0.typ.cat == aliasT || c1.typ.cat == aliasT {
|
||||
switch {
|
||||
@@ -2709,7 +2710,37 @@ func equal(n *node) {
|
||||
return
|
||||
}
|
||||
|
||||
switch t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf(); {
|
||||
// Do not attempt to optimize '==' or '!=' if an operand is an interface.
|
||||
// This will preserve proper dynamic type checking at runtime. For static types,
|
||||
// type checks are already performed, so bypass them if possible.
|
||||
if t0.Kind() == reflect.Interface || t1.Kind() == reflect.Interface {
|
||||
v0 := genValue(c0)
|
||||
v1 := genValue(c1)
|
||||
if n.fnext != nil {
|
||||
fnext := getExec(n.fnext)
|
||||
n.exec = func(f *frame) bltn {
|
||||
i0 := v0(f).Interface()
|
||||
i1 := v1(f).Interface()
|
||||
if i0 == i1 {
|
||||
dest(f).SetBool(true)
|
||||
return tnext
|
||||
}
|
||||
dest(f).SetBool(false)
|
||||
return fnext
|
||||
}
|
||||
} else {
|
||||
dest := genValue(n)
|
||||
n.exec = func(f *frame) bltn {
|
||||
i0 := v0(f).Interface()
|
||||
i1 := v1(f).Interface()
|
||||
dest(f).SetBool(i0 == i1)
|
||||
return tnext
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
switch {
|
||||
case isString(t0) || isString(t1):
|
||||
switch {
|
||||
case isInterface:
|
||||
@@ -3193,8 +3224,9 @@ func greater(n *node) {
|
||||
typ := n.typ.concrete().TypeOf()
|
||||
isInterface := n.typ.TypeOf().Kind() == reflect.Interface
|
||||
c0, c1 := n.child[0], n.child[1]
|
||||
t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf()
|
||||
|
||||
switch t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf(); {
|
||||
switch {
|
||||
case isString(t0) || isString(t1):
|
||||
switch {
|
||||
case isInterface:
|
||||
@@ -3520,8 +3552,9 @@ func greaterEqual(n *node) {
|
||||
typ := n.typ.concrete().TypeOf()
|
||||
isInterface := n.typ.TypeOf().Kind() == reflect.Interface
|
||||
c0, c1 := n.child[0], n.child[1]
|
||||
t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf()
|
||||
|
||||
switch t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf(); {
|
||||
switch {
|
||||
case isString(t0) || isString(t1):
|
||||
switch {
|
||||
case isInterface:
|
||||
@@ -3847,8 +3880,9 @@ func lower(n *node) {
|
||||
typ := n.typ.concrete().TypeOf()
|
||||
isInterface := n.typ.TypeOf().Kind() == reflect.Interface
|
||||
c0, c1 := n.child[0], n.child[1]
|
||||
t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf()
|
||||
|
||||
switch t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf(); {
|
||||
switch {
|
||||
case isString(t0) || isString(t1):
|
||||
switch {
|
||||
case isInterface:
|
||||
@@ -4174,8 +4208,9 @@ func lowerEqual(n *node) {
|
||||
typ := n.typ.concrete().TypeOf()
|
||||
isInterface := n.typ.TypeOf().Kind() == reflect.Interface
|
||||
c0, c1 := n.child[0], n.child[1]
|
||||
t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf()
|
||||
|
||||
switch t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf(); {
|
||||
switch {
|
||||
case isString(t0) || isString(t1):
|
||||
switch {
|
||||
case isInterface:
|
||||
@@ -4501,6 +4536,7 @@ func notEqual(n *node) {
|
||||
typ := n.typ.concrete().TypeOf()
|
||||
isInterface := n.typ.TypeOf().Kind() == reflect.Interface
|
||||
c0, c1 := n.child[0], n.child[1]
|
||||
t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf()
|
||||
|
||||
if c0.typ.cat == aliasT || c1.typ.cat == aliasT {
|
||||
switch {
|
||||
@@ -4586,7 +4622,37 @@ func notEqual(n *node) {
|
||||
return
|
||||
}
|
||||
|
||||
switch t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf(); {
|
||||
// Do not attempt to optimize '==' or '!=' if an operand is an interface.
|
||||
// This will preserve proper dynamic type checking at runtime. For static types,
|
||||
// type checks are already performed, so bypass them if possible.
|
||||
if t0.Kind() == reflect.Interface || t1.Kind() == reflect.Interface {
|
||||
v0 := genValue(c0)
|
||||
v1 := genValue(c1)
|
||||
if n.fnext != nil {
|
||||
fnext := getExec(n.fnext)
|
||||
n.exec = func(f *frame) bltn {
|
||||
i0 := v0(f).Interface()
|
||||
i1 := v1(f).Interface()
|
||||
if i0 != i1 {
|
||||
dest(f).SetBool(true)
|
||||
return tnext
|
||||
}
|
||||
dest(f).SetBool(false)
|
||||
return fnext
|
||||
}
|
||||
} else {
|
||||
dest := genValue(n)
|
||||
n.exec = func(f *frame) bltn {
|
||||
i0 := v0(f).Interface()
|
||||
i1 := v1(f).Interface()
|
||||
dest(f).SetBool(i0 != i1)
|
||||
return tnext
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
switch {
|
||||
case isString(t0) || isString(t1):
|
||||
switch {
|
||||
case isInterface:
|
||||
|
||||
@@ -3,6 +3,7 @@ package interp
|
||||
import (
|
||||
"context"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
@@ -16,6 +17,12 @@ type Program struct {
|
||||
init []*node
|
||||
}
|
||||
|
||||
// FileSet is the fileset that must be used for parsing Go that will be passed
|
||||
// to interp.CompileAST().
|
||||
func (interp *Interpreter) FileSet() *token.FileSet {
|
||||
return interp.fset
|
||||
}
|
||||
|
||||
// Compile parses and compiles a Go code represented as a string.
|
||||
func (interp *Interpreter) Compile(src string) (*Program, error) {
|
||||
return interp.compileSrc(src, "", true)
|
||||
@@ -55,6 +62,9 @@ func (interp *Interpreter) compileSrc(src, name string, inc bool) (*Program, err
|
||||
// CompileAST builds a Program for the given Go code AST. Files and block
|
||||
// statements can be compiled, as can most expressions. Var declaration nodes
|
||||
// cannot be compiled.
|
||||
//
|
||||
// WARNING: The node must have been parsed using interp.FileSet(). Results are
|
||||
// unpredictable otherwise.
|
||||
func (interp *Interpreter) CompileAST(n ast.Node) (*Program, error) {
|
||||
// Convert AST.
|
||||
pkgName, root, err := interp.ast(n)
|
||||
|
||||
208
interp/run.go
208
interp/run.go
@@ -9,7 +9,6 @@ import (
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// bltn type defines functions which run at CFG execution.
|
||||
@@ -1086,7 +1085,7 @@ func genInterfaceWrapper(n *node, typ reflect.Type) func(*frame) reflect.Value {
|
||||
for i, m := range methods {
|
||||
if m == nil {
|
||||
// First direct method lookup on field.
|
||||
if r := methodByName(v, names[i]); r.IsValid() {
|
||||
if r := methodByName(v, names[i], indexes[i]); r.IsValid() {
|
||||
w.Field(i + 1).Set(r)
|
||||
continue
|
||||
}
|
||||
@@ -1111,15 +1110,50 @@ func genInterfaceWrapper(n *node, typ reflect.Type) func(*frame) reflect.Value {
|
||||
}
|
||||
}
|
||||
|
||||
// methodByName return the method corresponding to name on value, or nil if not found.
|
||||
// methodByName returns the method corresponding to name on value, or nil if not found.
|
||||
// The search is extended on valueInterface wrapper if present.
|
||||
func methodByName(value reflect.Value, name string) reflect.Value {
|
||||
// If valid, the returned value is a method function with the receiver already set
|
||||
// (no need to pass it at call).
|
||||
func methodByName(value reflect.Value, name string, index []int) (v reflect.Value) {
|
||||
if vi, ok := value.Interface().(valueInterface); ok {
|
||||
if v := getConcreteValue(vi.value).MethodByName(name); v.IsValid() {
|
||||
return v
|
||||
if v = getConcreteValue(vi.value).MethodByName(name); v.IsValid() {
|
||||
return
|
||||
}
|
||||
}
|
||||
return value.MethodByName(name)
|
||||
if v = value.MethodByName(name); v.IsValid() {
|
||||
return
|
||||
}
|
||||
for value.Kind() == reflect.Ptr {
|
||||
value = value.Elem()
|
||||
if checkFieldIndex(value.Type(), index) {
|
||||
value = value.FieldByIndex(index)
|
||||
}
|
||||
if v = value.MethodByName(name); v.IsValid() {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func checkFieldIndex(typ reflect.Type, index []int) bool {
|
||||
if len(index) == 0 {
|
||||
return false
|
||||
}
|
||||
t := typ
|
||||
for t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
if t.Kind() != reflect.Struct {
|
||||
return false
|
||||
}
|
||||
i := index[0]
|
||||
if i >= t.NumField() {
|
||||
return false
|
||||
}
|
||||
if len(index) > 1 {
|
||||
return checkFieldIndex(t.Field(i).Type, index[1:])
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func call(n *node) {
|
||||
@@ -1151,6 +1185,7 @@ func call(n *node) {
|
||||
child := n.child[1:]
|
||||
tnext := getExec(n.tnext)
|
||||
fnext := getExec(n.fnext)
|
||||
hasVariadicArgs := n.action == aCallSlice // callSlice implies variadic call with ellipsis.
|
||||
|
||||
// Compute input argument value functions.
|
||||
for i, c := range child {
|
||||
@@ -1166,7 +1201,7 @@ func call(n *node) {
|
||||
numOut := c.child[0].typ.rtype.NumOut()
|
||||
for j := 0; j < numOut; j++ {
|
||||
ind := c.findex + j
|
||||
if !isInterfaceSrc(arg) || isEmptyInterface(arg) {
|
||||
if hasVariadicArgs || !isInterfaceSrc(arg) || isEmptyInterface(arg) {
|
||||
values = append(values, func(f *frame) reflect.Value { return f.data[ind] })
|
||||
continue
|
||||
}
|
||||
@@ -1179,7 +1214,7 @@ func call(n *node) {
|
||||
cc0 := c.child[0]
|
||||
for j := range cc0.typ.ret {
|
||||
ind := c.findex + j
|
||||
if !isInterfaceSrc(arg) || isEmptyInterface(arg) {
|
||||
if hasVariadicArgs || !isInterfaceSrc(arg) || isEmptyInterface(arg) {
|
||||
values = append(values, func(f *frame) reflect.Value { return f.data[ind] })
|
||||
continue
|
||||
}
|
||||
@@ -1193,13 +1228,14 @@ func call(n *node) {
|
||||
convertLiteralValue(c, argType)
|
||||
}
|
||||
switch {
|
||||
case isEmptyInterface(arg):
|
||||
case hasVariadicArgs:
|
||||
values = append(values, genValue(c))
|
||||
case isInterfaceSrc(arg) && n.action != aCallSlice:
|
||||
// callSlice implies variadic call with ellipsis, do not wrap in valueInterface.
|
||||
case isInterfaceSrc(arg) && (!isEmptyInterface(arg) || len(c.typ.method) > 0):
|
||||
values = append(values, genValueInterface(c))
|
||||
case isInterfaceBin(arg):
|
||||
values = append(values, genInterfaceWrapper(c, arg.rtype))
|
||||
case isFuncSrc(arg):
|
||||
values = append(values, genValueNode(c))
|
||||
default:
|
||||
values = append(values, genValue(c))
|
||||
}
|
||||
@@ -1369,12 +1405,22 @@ func call(n *node) {
|
||||
}
|
||||
default:
|
||||
val := v(f)
|
||||
// The !val.IsZero is to work around a recursive struct zero interface
|
||||
// issue. Once there is a better way to handle this case, the dest
|
||||
// can just be set.
|
||||
if !val.IsZero() || dest[i].Kind() == reflect.Interface {
|
||||
dest[i].Set(val)
|
||||
if val.IsZero() && dest[i].Kind() != reflect.Interface {
|
||||
// Work around a recursive struct zero interface issue.
|
||||
// Once there is a better way to handle this case, the dest can just be set.
|
||||
continue
|
||||
}
|
||||
if nod, ok := val.Interface().(*node); ok && nod.recv != nil {
|
||||
// An interpreted method is passed as value in a function call.
|
||||
// It must be wrapped now, otherwise the receiver will be missing
|
||||
// at the method call (#1332).
|
||||
// TODO (marc): wrapping interpreted functions should be always done
|
||||
// everywhere at runtime to simplify the whole code,
|
||||
// but it requires deeper refactoring.
|
||||
dest[i] = genFunctionWrapper(nod)(f)
|
||||
continue
|
||||
}
|
||||
dest[i].Set(val)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1610,11 +1656,16 @@ func callBin(n *node) {
|
||||
}
|
||||
out := callFn(value(f), in)
|
||||
for i := 0; i < len(out); i++ {
|
||||
if out[i].Kind() == reflect.Func {
|
||||
getFrame(f, n.level).data[n.findex+i] = out[i]
|
||||
} else {
|
||||
getFrame(f, n.level).data[n.findex+i].Set(out[i])
|
||||
r := out[i]
|
||||
if r.Kind() == reflect.Func {
|
||||
getFrame(f, n.level).data[n.findex+i] = r
|
||||
continue
|
||||
}
|
||||
dest := getFrame(f, n.level).data[n.findex+i]
|
||||
if _, ok := dest.Interface().(valueInterface); ok {
|
||||
r = reflect.ValueOf(valueInterface{value: r})
|
||||
}
|
||||
dest.Set(r)
|
||||
}
|
||||
return tnext
|
||||
}
|
||||
@@ -1715,9 +1766,6 @@ func getIndexArray(n *node) {
|
||||
}
|
||||
}
|
||||
|
||||
// valueInterfaceType is the reflection type of valueInterface.
|
||||
var valueInterfaceType = reflect.TypeOf((*valueInterface)(nil)).Elem()
|
||||
|
||||
// getIndexMap retrieves map value from index.
|
||||
func getIndexMap(n *node) {
|
||||
dest := genValue(n)
|
||||
@@ -1782,7 +1830,6 @@ func getIndexMap2(n *node) {
|
||||
value0 := genValue(n.child[0]) // map
|
||||
value2 := genValue(n.anc.child[1]) // status
|
||||
next := getExec(n.tnext)
|
||||
typ := n.anc.child[0].typ
|
||||
doValue := n.anc.child[0].ident != "_"
|
||||
doStatus := n.anc.child[1].ident != "_"
|
||||
|
||||
@@ -1799,21 +1846,6 @@ func getIndexMap2(n *node) {
|
||||
value2(f).SetBool(v.IsValid())
|
||||
return next
|
||||
}
|
||||
case isInterfaceSrc(typ):
|
||||
n.exec = func(f *frame) bltn {
|
||||
v := value0(f).MapIndex(mi)
|
||||
if v.IsValid() {
|
||||
if e := v.Elem(); e.Type().AssignableTo(valueInterfaceType) {
|
||||
dest(f).Set(e)
|
||||
} else {
|
||||
dest(f).Set(reflect.ValueOf(valueInterface{n, e}))
|
||||
}
|
||||
}
|
||||
if doStatus {
|
||||
value2(f).SetBool(v.IsValid())
|
||||
}
|
||||
return next
|
||||
}
|
||||
default:
|
||||
n.exec = func(f *frame) bltn {
|
||||
v := value0(f).MapIndex(mi)
|
||||
@@ -1835,21 +1867,6 @@ func getIndexMap2(n *node) {
|
||||
value2(f).SetBool(v.IsValid())
|
||||
return next
|
||||
}
|
||||
case isInterfaceSrc(typ):
|
||||
n.exec = func(f *frame) bltn {
|
||||
v := value0(f).MapIndex(value1(f))
|
||||
if v.IsValid() {
|
||||
if e := v.Elem(); e.Type().AssignableTo(valueInterfaceType) {
|
||||
dest(f).Set(e)
|
||||
} else {
|
||||
dest(f).Set(reflect.ValueOf(valueInterface{n, e}))
|
||||
}
|
||||
}
|
||||
if doStatus {
|
||||
value2(f).SetBool(v.IsValid())
|
||||
}
|
||||
return next
|
||||
}
|
||||
default:
|
||||
n.exec = func(f *frame) bltn {
|
||||
v := value0(f).MapIndex(value1(f))
|
||||
@@ -1926,7 +1943,7 @@ func getMethodByName(n *node) {
|
||||
}
|
||||
return next
|
||||
}
|
||||
m, li := val.node.typ.lookupMethod(name)
|
||||
m, li := typ.lookupMethod(name)
|
||||
if m == nil {
|
||||
panic(n.cfgErrorf("method not found: %s", name))
|
||||
}
|
||||
@@ -2335,9 +2352,13 @@ func _return(n *node) {
|
||||
}
|
||||
values[i] = genValueInterface(c)
|
||||
case valueT:
|
||||
if t.rtype.Kind() == reflect.Interface {
|
||||
switch t.rtype.Kind() {
|
||||
case reflect.Interface:
|
||||
values[i] = genInterfaceWrapper(c, t.rtype)
|
||||
break
|
||||
continue
|
||||
case reflect.Func:
|
||||
values[i] = genFunctionWrapper(c)
|
||||
continue
|
||||
}
|
||||
fallthrough
|
||||
default:
|
||||
@@ -2570,6 +2591,9 @@ func doCompositeBinStruct(n *node, hasType bool) {
|
||||
}
|
||||
}
|
||||
|
||||
frameIndex := n.findex
|
||||
l := n.level
|
||||
|
||||
n.exec = func(f *frame) bltn {
|
||||
s := reflect.New(typ).Elem()
|
||||
for i, v := range values {
|
||||
@@ -2580,7 +2604,7 @@ func doCompositeBinStruct(n *node, hasType bool) {
|
||||
case d.Kind() == reflect.Ptr:
|
||||
d.Set(s.Addr())
|
||||
default:
|
||||
d.Set(s)
|
||||
getFrame(f, l).data[frameIndex] = s
|
||||
}
|
||||
return next
|
||||
}
|
||||
@@ -2605,8 +2629,6 @@ func doComposite(n *node, hasType bool, keyed bool) {
|
||||
if typ.cat == ptrT || typ.cat == aliasT {
|
||||
typ = typ.val
|
||||
}
|
||||
var mu sync.Mutex
|
||||
typ.mu = &mu
|
||||
child := n.child
|
||||
if hasType {
|
||||
child = n.child[1:]
|
||||
@@ -2634,7 +2656,7 @@ func doComposite(n *node, hasType bool, keyed bool) {
|
||||
values[fieldIndex] = genValueAsFunctionWrapper(val)
|
||||
case isArray(val.typ) && val.typ.val != nil && isInterfaceSrc(val.typ.val) && !isEmptyInterface(val.typ.val):
|
||||
values[fieldIndex] = genValueInterfaceArray(val)
|
||||
case isInterfaceSrc(ft) && !isEmptyInterface(ft):
|
||||
case isInterfaceSrc(ft) && (!isEmptyInterface(ft) || len(val.typ.method) > 0):
|
||||
values[fieldIndex] = genValueInterface(val)
|
||||
case isInterface(ft):
|
||||
values[fieldIndex] = genInterfaceWrapper(val, rft)
|
||||
@@ -2645,11 +2667,10 @@ func doComposite(n *node, hasType bool, keyed bool) {
|
||||
|
||||
frameIndex := n.findex
|
||||
l := n.level
|
||||
rt := typ.TypeOf()
|
||||
|
||||
n.exec = func(f *frame) bltn {
|
||||
typ.mu.Lock()
|
||||
// No need to call zero() as doComposite is only called for a structT.
|
||||
a := reflect.New(typ.TypeOf()).Elem()
|
||||
typ.mu.Unlock()
|
||||
a := reflect.New(rt).Elem()
|
||||
for i, v := range values {
|
||||
a.Field(i).Set(v(f))
|
||||
}
|
||||
@@ -2912,13 +2933,21 @@ func _case(n *node) {
|
||||
return tnext
|
||||
}
|
||||
rtyp := typ.TypeOf()
|
||||
if rtyp != nil && rtyp.String() == t.String() && implementsInterface(v, typ) {
|
||||
destValue(f).Set(v.Elem())
|
||||
if rtyp == nil {
|
||||
return fnext
|
||||
}
|
||||
elem := v.Elem()
|
||||
if rtyp.String() == t.String() && implementsInterface(v, typ) {
|
||||
destValue(f).Set(elem)
|
||||
return tnext
|
||||
}
|
||||
ival := v.Interface()
|
||||
if ival != nil && rtyp != nil && rtyp.String() == reflect.TypeOf(ival).String() {
|
||||
destValue(f).Set(v.Elem())
|
||||
if ival != nil && rtyp.String() == reflect.TypeOf(ival).String() {
|
||||
destValue(f).Set(elem)
|
||||
return tnext
|
||||
}
|
||||
if typ.cat == valueT && rtyp.Kind() == reflect.Interface && elem.IsValid() && elem.Type().Implements(rtyp) {
|
||||
destValue(f).Set(elem)
|
||||
return tnext
|
||||
}
|
||||
return fnext
|
||||
@@ -2936,12 +2965,37 @@ func _case(n *node) {
|
||||
}
|
||||
return fnext
|
||||
}
|
||||
|
||||
default:
|
||||
// TODO(mpl): probably needs to be fixed for empty interfaces, like above.
|
||||
// match against multiple types: assign var to interface value
|
||||
n.exec = func(f *frame) bltn {
|
||||
val := srcValue(f)
|
||||
if v := srcValue(f).Interface().(valueInterface).node; v != nil {
|
||||
if t := val.Type(); t.Kind() == reflect.Interface {
|
||||
for _, typ := range types {
|
||||
if typ.cat == nilT && val.IsNil() {
|
||||
return tnext
|
||||
}
|
||||
rtyp := typ.TypeOf()
|
||||
if rtyp == nil {
|
||||
continue
|
||||
}
|
||||
elem := val.Elem()
|
||||
if rtyp.String() == t.String() && implementsInterface(val, typ) {
|
||||
destValue(f).Set(elem)
|
||||
return tnext
|
||||
}
|
||||
ival := val.Interface()
|
||||
if ival != nil && rtyp.String() == reflect.TypeOf(ival).String() {
|
||||
destValue(f).Set(elem)
|
||||
return tnext
|
||||
}
|
||||
if typ.cat == valueT && rtyp.Kind() == reflect.Interface && elem.IsValid() && elem.Type().Implements(rtyp) {
|
||||
destValue(f).Set(elem)
|
||||
return tnext
|
||||
}
|
||||
}
|
||||
return fnext
|
||||
}
|
||||
if v := val.Interface().(valueInterface).node; v != nil {
|
||||
for _, typ := range types {
|
||||
if v.typ.id() == typ.id() {
|
||||
destValue(f).Set(val)
|
||||
@@ -3043,9 +3097,7 @@ func _append(n *node) {
|
||||
values := make([]func(*frame) reflect.Value, l)
|
||||
for i, arg := range args {
|
||||
switch elem := n.typ.elem(); {
|
||||
case isEmptyInterface(elem):
|
||||
values[i] = genValue(arg)
|
||||
case isInterfaceSrc(elem):
|
||||
case isInterfaceSrc(elem) && (!isEmptyInterface(elem) || len(arg.typ.method) > 0):
|
||||
values[i] = genValueInterface(arg)
|
||||
case isInterfaceBin(elem):
|
||||
values[i] = genInterfaceWrapper(arg, elem.rtype)
|
||||
@@ -3067,9 +3119,7 @@ func _append(n *node) {
|
||||
default:
|
||||
var value0 func(*frame) reflect.Value
|
||||
switch elem := n.typ.elem(); {
|
||||
case isEmptyInterface(elem):
|
||||
value0 = genValue(n.child[2])
|
||||
case isInterfaceSrc(elem):
|
||||
case isInterfaceSrc(elem) && (!isEmptyInterface(elem) || len(n.child[2].typ.method) > 0):
|
||||
value0 = genValueInterface(n.child[2])
|
||||
case isInterfaceBin(elem):
|
||||
value0 = genInterfaceWrapper(n.child[2], elem.rtype)
|
||||
|
||||
@@ -47,7 +47,7 @@ type symbol struct {
|
||||
kind sKind
|
||||
typ *itype // Type of value
|
||||
node *node // Node value if index is negative
|
||||
from []*node // list of nodes jumping to node if kind is label, or nil
|
||||
from []*node // list of goto nodes jumping to this label node, or nil
|
||||
recv *receiver // receiver node value, if sym refers to a method
|
||||
index int // index of value in frame or -1
|
||||
rval reflect.Value // default value (used for constants)
|
||||
@@ -144,20 +144,6 @@ func (s *scope) lookup(ident string) (*symbol, int, bool) {
|
||||
return nil, 0, false
|
||||
}
|
||||
|
||||
// lookdown searches for a symbol in the current scope and included ones, recursively.
|
||||
// It returns the first found symbol and true, or nil and false.
|
||||
func (s *scope) lookdown(ident string) (*symbol, bool) {
|
||||
if sym, ok := s.sym[ident]; ok {
|
||||
return sym, true
|
||||
}
|
||||
for _, c := range s.child {
|
||||
if sym, ok := c.lookdown(ident); ok {
|
||||
return sym, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (s *scope) rangeChanType(n *node) *itype {
|
||||
if sym, _, found := s.lookup(n.child[1].ident); found {
|
||||
if t := sym.typ; len(n.child) == 3 && t != nil && (t.cat == chanT || t.cat == chanRecvT) {
|
||||
@@ -241,3 +227,26 @@ func (interp *Interpreter) initScopePkg(pkgID, pkgName string) *scope {
|
||||
interp.mutex.Unlock()
|
||||
return sc
|
||||
}
|
||||
|
||||
// Globals returns a map of global variables and constants in the main package.
|
||||
func (interp *Interpreter) Globals() map[string]reflect.Value {
|
||||
syms := map[string]reflect.Value{}
|
||||
interp.mutex.RLock()
|
||||
defer interp.mutex.RUnlock()
|
||||
|
||||
v, ok := interp.srcPkg["main"]
|
||||
if !ok {
|
||||
return syms
|
||||
}
|
||||
|
||||
for n, s := range v {
|
||||
switch s.kind {
|
||||
case constSym:
|
||||
syms[n] = s.rval
|
||||
case varSym:
|
||||
syms[n] = interp.frame.data[s.index]
|
||||
}
|
||||
}
|
||||
|
||||
return syms
|
||||
}
|
||||
|
||||
@@ -132,6 +132,10 @@ func (interp *Interpreter) importSrc(rPath, importPath string, skipTest bool) (s
|
||||
// the global symbols in the package scope.
|
||||
interp.mutex.Lock()
|
||||
gs := interp.scopes[importPath]
|
||||
if gs == nil {
|
||||
// A nil scope means that no even an empty package is created from source.
|
||||
return "", fmt.Errorf("no Go files in %s", dir)
|
||||
}
|
||||
interp.srcPkg[importPath] = gs.sym
|
||||
interp.pkgNames[importPath] = pkgName
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/traefik/yaegi/internal/unsafe2"
|
||||
)
|
||||
@@ -110,7 +109,6 @@ type structField struct {
|
||||
|
||||
// itype defines the internal representation of types in the interpreter.
|
||||
type itype struct {
|
||||
mu *sync.Mutex
|
||||
cat tcat // Type category
|
||||
field []structField // Array of struct fields if structT or interfaceT
|
||||
key *itype // Type of key element if MapT or nil
|
||||
@@ -854,6 +852,14 @@ func nodeType2(interp *Interpreter, sc *scope, n *node, seen []*node) (t *itype,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if t.cat == valueT {
|
||||
switch t.rtype.Kind() {
|
||||
case reflect.Array, reflect.Ptr:
|
||||
t = valueTOf(reflect.SliceOf(t.rtype.Elem()), withScope(sc))
|
||||
}
|
||||
break
|
||||
}
|
||||
if t.cat == ptrT {
|
||||
t = t.val
|
||||
}
|
||||
@@ -1819,6 +1825,7 @@ func (t *itype) refType(ctx *refTypeContext) reflect.Type {
|
||||
}
|
||||
}
|
||||
}
|
||||
fieldFix := []int{} // Slice of field indices to fix for recursivity.
|
||||
t.rtype = reflect.StructOf(fields)
|
||||
if ctx.isComplete() {
|
||||
for _, s := range ctx.defined {
|
||||
@@ -1826,6 +1833,9 @@ func (t *itype) refType(ctx *refTypeContext) reflect.Type {
|
||||
f := s.rtype.Field(i)
|
||||
if strings.HasSuffix(f.Type.String(), "unsafe2.dummy") {
|
||||
unsafe2.SetFieldType(s.rtype, i, ctx.rect.fixDummy(s.rtype.Field(i).Type))
|
||||
if name == s.path+"/"+s.name {
|
||||
fieldFix = append(fieldFix, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1834,13 +1844,16 @@ func (t *itype) refType(ctx *refTypeContext) reflect.Type {
|
||||
// The rtype has now been built, we can go back and rebuild
|
||||
// all the recursive types that relied on this type.
|
||||
// However, as we are keyed by type name, if two or more (recursive) fields at
|
||||
// the same depth level are of the same type, they "mask" each other, and only one
|
||||
// of them is in ctx.refs, which means this pass below does not fully do the job.
|
||||
// Which is why we have the pass above that is done one last time, for all fields,
|
||||
// one the recursion has been fully resolved.
|
||||
// the same depth level are of the same type, or a "variation" of the same type
|
||||
// (slice of, map of, etc), they "mask" each other, and only one
|
||||
// of them is in ctx.refs. That is why the code around here is a bit convoluted,
|
||||
// and we need both the loop above, around all the struct fields, and the loop
|
||||
// below, around the ctx.refs.
|
||||
for _, f := range ctx.refs[name] {
|
||||
ftyp := f.typ.field[f.idx].typ.refType(&refTypeContext{defined: ctx.defined, rebuilding: true})
|
||||
unsafe2.SetFieldType(f.typ.rtype, f.idx, ftyp)
|
||||
for _, index := range fieldFix {
|
||||
ftyp := f.typ.field[index].typ.refType(&refTypeContext{defined: ctx.defined, rebuilding: true})
|
||||
unsafe2.SetFieldType(f.typ.rtype, index, ftyp)
|
||||
}
|
||||
}
|
||||
default:
|
||||
if z, _ := t.zero(); z.IsValid() {
|
||||
|
||||
@@ -3,6 +3,7 @@ package interp
|
||||
import (
|
||||
"errors"
|
||||
"go/constant"
|
||||
"go/token"
|
||||
"math"
|
||||
"reflect"
|
||||
)
|
||||
@@ -124,6 +125,8 @@ func (check typecheck) starExpr(n *node) error {
|
||||
}
|
||||
|
||||
var unaryOpPredicates = opPredicates{
|
||||
aInc: isNumber,
|
||||
aDec: isNumber,
|
||||
aPos: isNumber,
|
||||
aNeg: isNumber,
|
||||
aBitNot: isInt,
|
||||
@@ -133,6 +136,9 @@ var unaryOpPredicates = opPredicates{
|
||||
// unaryExpr type checks a unary expression.
|
||||
func (check typecheck) unaryExpr(n *node) error {
|
||||
c0 := n.child[0]
|
||||
if isBlank(c0) {
|
||||
return n.cfgErrorf("cannot use _ as value")
|
||||
}
|
||||
t0 := c0.typ.TypeOf()
|
||||
|
||||
if n.action == aRecv {
|
||||
@@ -196,7 +202,7 @@ func (check typecheck) comparison(n *node) error {
|
||||
if typ.isNil() {
|
||||
typ = c1.typ
|
||||
}
|
||||
return n.cfgErrorf("invalid operation: operator %v not defined on %s", n.action, typ.id(), ".")
|
||||
return n.cfgErrorf("invalid operation: operator %v not defined on %s", n.action, typ.id())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -221,6 +227,10 @@ var binaryOpPredicates = opPredicates{
|
||||
func (check typecheck) binaryExpr(n *node) error {
|
||||
c0, c1 := n.child[0], n.child[1]
|
||||
|
||||
if isBlank(c0) || isBlank(c1) {
|
||||
return n.cfgErrorf("cannot use _ as value")
|
||||
}
|
||||
|
||||
a := n.action
|
||||
if isAssignAction(a) {
|
||||
a--
|
||||
@@ -476,6 +486,12 @@ func (check typecheck) structBinLitExpr(child []*node, typ reflect.Type) error {
|
||||
|
||||
// sliceExpr type checks a slice expression.
|
||||
func (check typecheck) sliceExpr(n *node) error {
|
||||
for _, c := range n.child {
|
||||
if isBlank(c) {
|
||||
return n.cfgErrorf("cannot use _ as value")
|
||||
}
|
||||
}
|
||||
|
||||
c, child := n.child[0], n.child[1:]
|
||||
|
||||
t := c.typ.TypeOf()
|
||||
@@ -591,6 +607,12 @@ func (check typecheck) typeAssertionExpr(n *node, typ *itype) error {
|
||||
continue
|
||||
}
|
||||
if tm == nil {
|
||||
// Lookup for non-exported methods is impossible
|
||||
// for bin types, ignore them as they can't be used
|
||||
// directly by the interpreted programs.
|
||||
if !token.IsExported(name) && isBin(typ) {
|
||||
continue
|
||||
}
|
||||
return n.cfgErrorf("impossible type assertion: %s does not implement %s (missing %v method)", typ.id(), n.typ.id(), name)
|
||||
}
|
||||
if tm.recv != nil && tm.recv.TypeOf().Kind() == reflect.Ptr && typ.TypeOf().Kind() != reflect.Ptr {
|
||||
|
||||
@@ -219,7 +219,7 @@ func genValue(n *node) func(*frame) reflect.Value {
|
||||
func genDestValue(typ *itype, n *node) func(*frame) reflect.Value {
|
||||
convertLiteralValue(n, typ.TypeOf())
|
||||
switch {
|
||||
case isInterfaceSrc(typ) && !isEmptyInterface(typ):
|
||||
case isInterfaceSrc(typ) && (!isEmptyInterface(typ) || len(n.typ.method) > 0):
|
||||
return genValueInterface(n)
|
||||
case isFuncSrc(typ) && (n.typ.cat == valueT || n.typ.cat == nilT):
|
||||
return genValueNode(n)
|
||||
@@ -408,7 +408,7 @@ func genValueInterfaceValue(n *node) func(*frame) reflect.Value {
|
||||
|
||||
return func(f *frame) reflect.Value {
|
||||
v := value(f)
|
||||
if v.Interface().(valueInterface).node == nil {
|
||||
if vi, ok := v.Interface().(valueInterface); ok && vi.node == nil {
|
||||
// Uninitialized interface value, set it to a correct zero value.
|
||||
v.Set(zeroInterfaceValue())
|
||||
v = value(f)
|
||||
@@ -421,7 +421,11 @@ func genValueNode(n *node) func(*frame) reflect.Value {
|
||||
value := genValue(n)
|
||||
|
||||
return func(f *frame) reflect.Value {
|
||||
return reflect.ValueOf(&node{rval: value(f)})
|
||||
v := value(f)
|
||||
if _, ok := v.Interface().(*node); ok {
|
||||
return v
|
||||
}
|
||||
return reflect.ValueOf(&node{rval: v})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -591,5 +595,6 @@ func genComplex(n *node) func(*frame) complex128 {
|
||||
|
||||
func genValueString(n *node) func(*frame) (reflect.Value, string) {
|
||||
value := genValue(n)
|
||||
|
||||
return func(f *frame) (reflect.Value, string) { v := value(f); return v, v.String() }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user