Compare commits

...

31 Commits

Author SHA1 Message Date
Marc Vertes
f76db27c77 interp: fix resolution of methods on aliased types
The type.val field was always pointing to the final underlying type
for aliased types, defeating a possible match if a method was
attached to a type in between. Now the complete chain of aliases
is always preserved.

We have added an underlying() itype method which returns the underlying
type of a defined type (aliasT), even in the presence of multiple
indirections.

We have added a definedType function which checks if type t1 is
defined from type t2 or t2 defined from t1, required when checking
assignability of aliasT types.

Fixes #1411.

PS: this is the 2nd attempt, as the first version #1412 wasn't passing
_test/issue-1408.go as well. This PR does pass and supersedes #1412.
2022-06-14 16:42:09 +02:00
mpl
996b1e33c8 interp: catch mismatched types for other comparisons
The check for mismatched types was already added recently for ==  and != comparisons.
This PR now adds it for other comparisons ( < , <=, > , >=).
2022-06-14 10:52:08 +02:00
Marc Vertes
236a0effaf interp: improve the behaviour of interface{} function parameters
We finally address a long standing limitation of the interpreter:
the capacity to generate the correct interface wrapper for an
anonymous interface{} function parameter of a binary function.

It allows for example fmt.Printf to invoke the String method
of an object defined within the interpreter, or json.Marshal
to invoke a textMarshaler method if it exists and if there is
no Marshaler method already defined for the passed interpreter
object.

To achieve that, we add a new mapType part of the "Used" symbols
to describe what not empty interfaces are expected and in which
priority order. This information can not be guessed and is found
in the related package documentation, then captured in stdlib/maptypes.go.

Then, at compile time and/or during execution, a lookup on mapTypes
is performed to allow the correct wrapper to be generated.

This change adds a new MapType type to the stdlib package.

Fixes #435.
2022-06-14 10:18:08 +02:00
Marc Vertes
eaeb445e17 interp: create interpreter interface value with new
In that case, the interface must be wrapped in an valueInterface
at creation.

With that fix, it is now possible to import
github.com/google/go-querystring/query. Not tested beyond that.

Fixes #1123.
2022-06-13 11:36:09 +02:00
Marc Vertes
6933ba2b4e interp: improve type checking for defined types
Fixes #1408.
2022-06-13 11:24:09 +02:00
Marc Vertes
a61a7d5bcd interp: avoid panic when defining a label in incremental parsing mode
In REPL mode, a panic (stack overflow) could be triggered by:

	$ yaegi
	> a:
	runtime: goroutine stack exceeds 1000000000-byte limit
	runtime: sp=0x14020760330 stack=[0x14020760000, 0x14040760000]
	fatal error: stack overflow
	[...]

This issue occurs in incremental parsing mode only, and not when the parser
is in file mode. We avoid it by being more defensive when generating
values.

Fixes #982.
2022-06-13 11:10:09 +02:00
Marc Vertes
259f64cfd4 interp: fix redeclaration of an interface variable
Fixes #1404.
2022-06-13 10:56:09 +02:00
Marc Vertes
6c74ab7bec interp: allow conversions of untyped complex
For untyped numerical types, conversions to different numerical
types can be allowed if there is no overflow (not checked here).

Fixes #1402.
2022-06-13 10:42:08 +02:00
Marc Vertes
d64563edee interp: improve handling values and comparisons in interfaces
Fixes #1347.
2022-05-23 10:30:08 +02:00
Marc Vertes
07039262a0 interp: implements detection of packages with no Go files
This avoids panic during import, and print a proper diagnostic
instead.

Fixes #1394.
2022-05-19 18:34:08 +02:00
Marc Vertes
4ed9ccb5c4 interp: fix retrieving the string value of an interface
Thanks to @bailsman for providing first insight, in addition to
raising the issue.

Fixes #1342.
2022-05-19 18:20:09 +02:00
Marc Vertes
25edcfee7a interp: fix handling of empty interfaces in map index expressions
There should be no need now to wrap empty interfaces in order to
retrieve its value.

Fixes #1344.
2022-05-19 18:08:09 +02:00
Marc Vertes
d183f4205e interp: improve handling of empty interface values (#1393)
At variable, function parameter, slice, map or field element assign,
if the destination type is an empty interface, the value was never
wrapped into a valueInterface (to preserve type mutability in case
of re-assign). Now we wrap it in a valueInterface if the source
type has a non empty set of methods, to allow a future use as a non
empty interface.

There are still corner cases, but it extends notably the support
of interfaces within the interpreter.

Fixes #1355.
2022-05-19 17:53:56 +02:00
Marc Vertes
821e9ee006 interp: recover interpreter internal panics in EvalWithContext 2022-05-19 17:30:09 +02:00
Marc Vertes
00e3f924c1 interp: fix the behaviour of goto, continue and break (#1392)
The logic of goto was false due to mixing break label and goto
label, despite being opposite. In case of break, jump to node (the
exit point) instead of node.start. Also always define label symbols
before their use is parsed.

* Fix continue label, detect invalid labels for break and continue

* fix multiple goto, break, continue to the same label

Fixes #1354.
2022-05-19 11:23:30 +02:00
Marc Vertes
2248851d77 interp: fix creation of binary composite types (#1391)
* interp: fix creation of binary composite types

Use direct assignment instead of reflect.Value Set method to
initialize a binary composite type, as for non binary types.
It ensures that a new reflect.Value is stored in the frame
instead of modifying a possibly existing one, which can defeat
the purpose of initializing variables in a body loop.

While there, remove the need to have and use a mutex on types.

Fixes #1381.

* review: rework a bit the test

Co-authored-by: mpl <mathieu.lonjaret@gmail.com>
2022-05-05 21:31:10 +02:00
Marc Vertes
f74d1ea6d8 interp: detect invalid uses of _ as value
We now detect the use of special identifier _ (blank) during parsing in order to abort compiling early. It allows to not panic later during execution. We must catch all the cases where blank is used as a value, but still preserve the cases where it is assigned, used as a struct field or for import side effects.

Fixes #1386.
2022-05-04 18:51:09 +02:00
Marc Vertes
606b4c3a37 interp: fix import of binary type symbols in current scope (#1380)
Fixes #1360.
2022-05-04 17:27:11 +02:00
Ethan Reesor
4e77fc9436 interp: delete incomplete type on pkg import
When a package is imported, it creates a symbol with a name like "errors/_.go". If a statement such as x := errors.New(...) is executed before import "errors", it creates an incomplete type symbol with a name like "errors". Importing the package after the incomplete type symbol has been created does not fix the compile issue because the incomplete type still exists.

To fix this, this PR deletes the incomplete type symbol, if one exists.

Closes #1388.
2022-04-26 11:04:13 +02:00
Ethan Reesor
ad9db379e7 interp: add a function to get globals (#1387)
Co-authored-by: Marc Vertes <mvertes@free.fr>
2022-04-25 16:11:18 +02:00
Ethan Reesor
7be17d393f interp: expose fset to fix CompileAST issue
The interpreter has its own internal fileset and expects all code to have been parsed using that fileset. If a user creates a fileset, calls `go/parser.Parse*`, then passes the result to `interp.CompileAST`, strange things can happen.

The solutions I can see are:

1. Expose the fileset so the user can use it when parsing source.
2. Add the fileset as an option (input to New) so that the user can tell the interpreter to use a specific fileset.
3. Pass the fileset into `CompileAST`

There are two ways to implement option 3. One is to add a field to nodes and update every reference to `interp.fset` to use `node.fset`. The other is to add a parameter to every function that references `interp.fset` or calls a function that does. Both of these are significant changes and involve an extra pointer for every node or most function calls.

Options 1 and 2 are easy. Option 2 involves adding an option so I went with option 1. I can imagine situations where option 2 could be necessary, but I can open another issue/PR if and when I need that.

Fixes #1383
2022-04-22 11:48:08 +02:00
cclerget
5665c9a410 interp: fix redeclaration scope issue
Fixes #1378
2022-04-09 15:28:07 +02:00
cclerget
1cf9d345aa Ignore private methods for binary types during type assertion
Fixes #1373
2022-04-07 18:16:08 +02:00
Marc Vertes
f07f25f1ba interp: handle struct with multiple recursive fields (#1372)
* interp: handle struct with multiple recursive fields

In case of a recursive struct with several recursive fields of
different type, only the first one was properly fixed when
constructing the corresponding reflect type. We now memorize and
process all fields at the same depth level.

Fixes #1371.

* Update interp/type.go

Co-authored-by: mpl <mathieu.lonjaret@gmail.com>

* fix lint

* fix comment

Co-authored-by: mpl <mathieu.lonjaret@gmail.com>
2022-04-07 14:53:23 +02:00
cclerget
c93b836c77 Prevent variadic arguments from being wrapped as function
Fixes #1375
2022-04-07 14:24:07 +02:00
Marc Vertes
371103f0d1 interp: fix switch expression (#1370)
The control flow graph was incorrect for the initial clause.

Fixes #1368.
2022-04-06 22:07:51 +02:00
Marc Vertes
8bd7afbe62 interp: fix handling of redeclaration in multi-assign expression (#1367)
* interp: fix handling of redeclaration in multi-assign expression

In a statement like `a, b := f()` if `a` was previously declared,
its symbol must be reused, a new symbol must not override its
previous value. This is now fixed.

* In case of redeclaration, reuse the existing only if the redeclared
variable has the same type. Add _test/var16.go to check this use
case.

Fixes #1365.
2022-04-06 20:01:25 +02:00
Marc Vertes
8ea3a493f4 interp: fix interface conversion from binary call (#1366)
Fixes #1364.
2022-04-06 19:51:12 +02:00
Marc Vertes
f2abd346c0 interp: fix passing binary function as parameter
Wrap binary function values in a node if passing it
as a parameter to an interperter defined function.

Fixes #1361.
2022-04-05 17:34:08 +02:00
Marc Vertes
c784713aca interp: make methods passed as value preserve receiver
Fixes #1332.
2022-04-05 16:58:09 +02:00
Marc Vertes
14acf618af interp: improve type switch on binary interfaces
Fixes #1337.
2022-01-04 10:50:08 +01:00
48 changed files with 1454 additions and 219 deletions

24
_test/break0.go Normal file
View 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
View 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
View 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
View 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
View 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
View 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

17
_test/issue-1332.go Normal file
View 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

26
_test/issue-1337.go Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

22
_test/issue-1404.go Normal file
View File

@@ -0,0 +1,22 @@
package main
type I interface {
inI()
}
type T struct {
name string
}
func (t *T) inI() {}
func main() {
var i I = &T{name: "foo"}
if i, ok := i.(*T); ok {
println(i.name)
}
}
// Output:
// foo

16
_test/issue-1408.go Normal file
View File

@@ -0,0 +1,16 @@
package main
type (
Number = int32
Number2 = Number
)
func f(n Number2) { println(n) }
func main() {
var n Number = 5
f(n)
}
// Output:
// 5

16
_test/issue-1411.go Normal file
View File

@@ -0,0 +1,16 @@
package main
type Number int32
func (n Number) IsValid() bool { return true }
type Number1 = Number
type Number2 = Number1
func main() {
a := Number2(5)
println(a.IsValid())
}
// Output: true

25
_test/issue-435.go Normal file
View File

@@ -0,0 +1,25 @@
package main
import (
"fmt"
"strconv"
)
type Foo int
func (f Foo) String() string {
return "foo-" + strconv.Itoa(int(f))
}
func print1(arg interface{}) {
fmt.Println(arg)
}
func main() {
var arg Foo = 3
var f = print1
f(arg)
}
// Output:
// foo-3

9
_test/p2/p2.go Normal file
View File

@@ -0,0 +1,9 @@
package p2
type I interface {
isI()
}
type T struct{}
func (t *T) isI() {}

0
_test/p3/empty Normal file
View File

19
_test/var16.go Normal file
View 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

View File

@@ -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:

View File

@@ -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: "*=",

View File

@@ -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):
@@ -1278,7 +1324,7 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
// retry with the filename, in case ident is a package name.
sym, level, found = sc.lookup(filepath.Join(n.ident, baseName))
if !found {
err = n.cfgErrorf("undefined: %s %d", n.ident, n.index)
err = n.cfgErrorf("undefined: %s", n.ident)
break
}
}
@@ -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.id() == t.id() {
// 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:
@@ -2623,11 +2700,7 @@ func compositeGenerator(n *node, typ *itype, rtyp reflect.Type) (gen bltnGenerat
}
case valueT:
if rtyp == nil {
rtyp = n.typ.rtype
}
// TODO(mpl): I do not understand where this side-effect is coming from, and why it happens. quickfix for now.
if rtyp == nil {
rtyp = n.typ.val.rtype
rtyp = n.typ.TypeOf()
}
switch k := rtyp.Kind(); k {
case reflect.Struct:
@@ -2728,3 +2801,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 == "_"
}

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -19,6 +19,8 @@ import (
"path"
"path/filepath"
"reflect"
"runtime"
"runtime/debug"
"strconv"
"strings"
"sync"
@@ -202,11 +204,12 @@ type Interpreter struct {
name string // name of the input source file (or main)
opt // user settable options
cancelChan bool // enables cancellable chan operations
fset *token.FileSet // fileset to locate node in source code
binPkg Exports // binary packages used in interpreter, indexed by path
rdir map[string]bool // for src import cycle detection
opt // user settable options
cancelChan bool // enables cancellable chan operations
fset *token.FileSet // fileset to locate node in source code
binPkg Exports // binary packages used in interpreter, indexed by path
rdir map[string]bool // for src import cycle detection
mapTypes map[reflect.Value][]reflect.Type // special interfaces mapping for wrappers
mutex sync.RWMutex
frame *frame // program data storage during execution
@@ -331,6 +334,7 @@ func New(options Options) *Interpreter {
universe: initUniverse(),
scopes: map[string]*scope{},
binPkg: Exports{"": map[string]reflect.Value{"_error": reflect.ValueOf((*_error)(nil))}},
mapTypes: map[reflect.Value][]reflect.Type{},
srcPkg: imports{},
pkgNames: map[string]string{},
rdir: map[string]bool{},
@@ -628,7 +632,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)
}()
@@ -666,6 +677,14 @@ func (interp *Interpreter) Use(values Exports) error {
importPath := path.Dir(k)
packageName := path.Base(k)
if k == "." && v["MapTypes"].IsValid() {
// Use mapping for special interface wrappers.
for kk, vv := range v["MapTypes"].Interface().(map[reflect.Value][]reflect.Type) {
interp.mapTypes[kk] = vv
}
continue
}
if importPath == "." {
return fmt.Errorf("export path %[1]q is missing a package name; did you mean '%[1]s/%[1]s'?", k)
}
@@ -717,6 +736,14 @@ func fixStdlib(interp *Interpreter) {
p["Scanf"] = reflect.ValueOf(func(f string, a ...interface{}) (n int, err error) { return fmt.Fscanf(stdin, f, a...) })
p["Scanln"] = reflect.ValueOf(func(a ...interface{}) (n int, err error) { return fmt.Fscanln(stdin, a...) })
// Update mapTypes to virtualized symbols as well.
interp.mapTypes[p["Print"]] = interp.mapTypes[reflect.ValueOf(fmt.Print)]
interp.mapTypes[p["Printf"]] = interp.mapTypes[reflect.ValueOf(fmt.Printf)]
interp.mapTypes[p["Println"]] = interp.mapTypes[reflect.ValueOf(fmt.Println)]
interp.mapTypes[p["Scan"]] = interp.mapTypes[reflect.ValueOf(fmt.Scan)]
interp.mapTypes[p["Scanf"]] = interp.mapTypes[reflect.ValueOf(fmt.Scanf)]
interp.mapTypes[p["Scanln"]] = interp.mapTypes[reflect.ValueOf(fmt.Scanln)]
if p = interp.binPkg["flag"]; p != nil {
c := flag.NewFlagSet(os.Args[0], flag.PanicOnError)
c.SetOutput(stderr)
@@ -743,6 +770,14 @@ func fixStdlib(interp *Interpreter) {
p["SetOutput"] = reflect.ValueOf(l.SetOutput)
p["SetPrefix"] = reflect.ValueOf(l.SetPrefix)
p["Writer"] = reflect.ValueOf(l.Writer)
// Update mapTypes to virtualized symbols as well.
interp.mapTypes[p["Print"]] = interp.mapTypes[reflect.ValueOf(log.Print)]
interp.mapTypes[p["Printf"]] = interp.mapTypes[reflect.ValueOf(log.Printf)]
interp.mapTypes[p["Println"]] = interp.mapTypes[reflect.ValueOf(log.Println)]
interp.mapTypes[p["Panic"]] = interp.mapTypes[reflect.ValueOf(log.Panic)]
interp.mapTypes[p["Panicf"]] = interp.mapTypes[reflect.ValueOf(log.Panicf)]
interp.mapTypes[p["Panicln"]] = interp.mapTypes[reflect.ValueOf(log.Panicln)]
}
if p = interp.binPkg["os"]; p != nil {

View File

@@ -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
@@ -198,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",

View File

@@ -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"},
})
}
@@ -170,13 +176,17 @@ func TestEvalBuiltin(t *testing.T) {
{src: `m := complex(3, 2); real(m)`, res: "3"},
{src: `m := complex(3, 2); imag(m)`, res: "2"},
{src: `m := complex("test", 2)`, err: "1:33: invalid types string and int"},
{src: `imag("test")`, err: "1:33: cannot convert \"test\" to complex128"},
{src: `imag("test")`, err: "1:33: cannot convert untyped string to untyped complex"},
{src: `imag(a)`, err: "1:33: invalid argument type []int for imag"},
{src: `real(a)`, err: "1:33: invalid argument type []int for real"},
{src: `t := map[int]int{}; t[123]++; t`, res: "map[123:1]"},
{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"},
{src: `f := func() complex64 { return complex(0, 0) }()`, res: "(0+0i)"},
{src: `f := func() float32 { return real(complex(2, 1)) }()`, res: "2"},
{src: `f := func() int8 { return imag(complex(2, 1)) }()`, res: "1"},
})
}
@@ -201,6 +211,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 +227,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"},
})
}
@@ -420,7 +440,7 @@ func TestEvalComparison(t *testing.T) {
{src: `a, b, c := 1, 1, false; if a == b { c = true }; c`, res: "true"},
{src: `a, b, c := 1, 2, false; if a != b { c = true }; c`, res: "true"},
{
desc: "mismatched types",
desc: "mismatched types equality",
src: `
type Foo string
type Bar string
@@ -431,6 +451,28 @@ func TestEvalComparison(t *testing.T) {
`,
err: "7:13: invalid operation: mismatched types main.Foo and main.Bar",
},
{
desc: "mismatched types less than",
src: `
type Foo string
type Bar string
var a = Foo("test")
var b = Bar("test")
var c = a < b
`,
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 +518,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 +544,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 +556,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 +566,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"},
})
}
@@ -671,6 +721,29 @@ func TestEvalBinCall(t *testing.T) {
})
}
func TestEvalReflect(t *testing.T) {
i := interp.New(interp.Options{})
if err := i.Use(stdlib.Symbols); err != nil {
t.Fatal(err)
}
if _, err := i.Eval(`
import (
"net/url"
"reflect"
)
type Encoder interface {
EncodeValues(key string, v *url.Values) error
}
`); err != nil {
t.Fatal(err)
}
runTests(t, i, []testCase{
{src: "reflect.TypeOf(new(Encoder)).Elem()", res: "interp.valueInterface"},
})
}
func TestEvalMissingSymbol(t *testing.T) {
defer func() {
r := recover()
@@ -1337,7 +1410,7 @@ func testConcurrentComposite(t *testing.T, filePath string) {
}
}
func TestEvalScanner(t *testing.T) {
func TestEvalREPL(t *testing.T) {
if testing.Short() {
return
}
@@ -1425,6 +1498,13 @@ func TestEvalScanner(t *testing.T) {
},
errorLine: -1,
},
{
desc: "define a label",
src: []string{
`a:`,
},
errorLine: -1,
},
}
runREPL := func(t *testing.T, test testCase) {
@@ -1625,6 +1705,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 +1839,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)
}
}

View File

@@ -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)
}
}

View File

@@ -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:

View File

@@ -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)

View File

@@ -9,7 +9,6 @@ import (
"reflect"
"regexp"
"strings"
"sync"
)
// bltn type defines functions which run at CFG execution.
@@ -1229,12 +1228,14 @@ func call(n *node) {
convertLiteralValue(c, argType)
}
switch {
case isEmptyInterface(arg):
case hasVariadicArgs:
values = append(values, genValue(c))
case isInterfaceSrc(arg) && !hasVariadicArgs:
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))
}
@@ -1404,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)
}
}
}
@@ -1468,6 +1479,20 @@ func callBin(n *node) {
}
}
// getMapType returns a reflect type suitable for interface wrapper for functions
// with some special processing in case of interface{} argument, i.e. fmt.Printf.
var getMapType func(*itype) reflect.Type
if lr, ok := n.interp.mapTypes[c0.rval]; ok {
getMapType = func(typ *itype) reflect.Type {
for _, rt := range lr {
if typ.implements(&itype{cat: valueT, rtype: rt}) {
return rt
}
}
return nil
}
}
// Determine if we should use `Call` or `CallSlice` on the function Value.
callFn := func(v reflect.Value, in []reflect.Value) []reflect.Value { return v.Call(in) }
if n.action == aCallSlice {
@@ -1475,13 +1500,6 @@ func callBin(n *node) {
}
for i, c := range child {
var defType reflect.Type
if variadic >= 0 && i+rcvrOffset >= variadic {
defType = funcType.In(variadic)
} else {
defType = funcType.In(rcvrOffset + i)
}
switch {
case isBinCall(c, c.scope):
// Handle nested function calls: pass returned values as arguments
@@ -1516,6 +1534,19 @@ func callBin(n *node) {
break
}
// defType is the target type for a potential interface wrapper.
var defType reflect.Type
if variadic >= 0 && i+rcvrOffset >= variadic {
defType = funcType.In(variadic)
} else {
defType = funcType.In(rcvrOffset + i)
}
if getMapType != nil {
if rt := getMapType(c.typ); rt != nil {
defType = rt
}
}
switch {
case isFuncSrc(c.typ):
values = append(values, genFunctionWrapper(c))
@@ -1554,7 +1585,7 @@ func callBin(n *node) {
val := make([]reflect.Value, l+1)
val[0] = value(f)
for i, v := range values {
val[i+1] = v(f)
val[i+1] = getBinValue(getMapType, v, f)
}
f.deferred = append([][]reflect.Value{val}, f.deferred...)
return tnext
@@ -1564,7 +1595,7 @@ func callBin(n *node) {
n.exec = func(f *frame) bltn {
in := make([]reflect.Value, l)
for i, v := range values {
in[i] = v(f)
in[i] = getBinValue(getMapType, v, f)
}
go callFn(value(f), in)
return tnext
@@ -1576,7 +1607,7 @@ func callBin(n *node) {
n.exec = func(f *frame) bltn {
in := make([]reflect.Value, l)
for i, v := range values {
in[i] = v(f)
in[i] = getBinValue(getMapType, v, f)
}
res := callFn(value(f), in)
b := res[0].Bool()
@@ -1608,7 +1639,7 @@ func callBin(n *node) {
n.exec = func(f *frame) bltn {
in := make([]reflect.Value, l)
for i, v := range values {
in[i] = v(f)
in[i] = getBinValue(getMapType, v, f)
}
out := callFn(value(f), in)
for i, v := range rvalues {
@@ -1625,7 +1656,7 @@ func callBin(n *node) {
n.exec = func(f *frame) bltn {
in := make([]reflect.Value, l)
for i, v := range values {
in[i] = v(f)
in[i] = getBinValue(getMapType, v, f)
}
out := callFn(value(f), in)
for i, v := range out {
@@ -1641,7 +1672,7 @@ func callBin(n *node) {
n.exec = func(f *frame) bltn {
in := make([]reflect.Value, l)
for i, v := range values {
in[i] = v(f)
in[i] = getBinValue(getMapType, v, f)
}
out := callFn(value(f), in)
for i := 0; i < len(out); i++ {
@@ -1755,9 +1786,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)
@@ -1822,7 +1850,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 != "_"
@@ -1839,21 +1866,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)
@@ -1875,21 +1887,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))
@@ -2614,6 +2611,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 {
@@ -2624,7 +2624,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
}
@@ -2649,8 +2649,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:]
@@ -2678,7 +2676,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)
@@ -2689,11 +2687,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))
}
@@ -2956,13 +2953,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
@@ -2980,12 +2985,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)
@@ -3087,9 +3117,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)
@@ -3111,9 +3139,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)
@@ -3297,11 +3323,20 @@ func _len(n *node) {
func _new(n *node) {
next := getExec(n.tnext)
typ := n.child[1].typ.TypeOf()
t1 := n.child[1].typ
typ := t1.TypeOf()
dest := genValueOutput(n, reflect.PtrTo(typ))
if isInterfaceSrc(t1) && (!isEmptyInterface(t1) || len(t1.method) > 0) {
typ = zeroInterfaceValue().Type()
}
n.exec = func(f *frame) bltn {
dest(f).Set(reflect.New(typ))
v := reflect.New(typ)
if vi, ok := v.Interface().(*valueInterface); ok {
vi.node = n
}
dest(f).Set(v)
return next
}
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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
@@ -240,9 +238,6 @@ func namedOf(val *itype, path, name string, opts ...itypeOption) *itype {
if path != "" {
str = path + "." + name
}
for val.cat == aliasT {
val = val.val
}
t := &itype{cat: aliasT, val: val, path: path, name: name, str: str}
for _, opt := range opts {
opt(t)
@@ -587,12 +582,12 @@ func nodeType2(interp *Interpreter, sc *scope, n *node, seen []*node) (t *itype,
}
if !t.incomplete {
switch k := t.TypeOf().Kind(); {
case t.untyped && isNumber(t.TypeOf()):
t = untypedFloat()
case k == reflect.Complex64:
t = sc.getType("float32")
case k == reflect.Complex128:
t = sc.getType("float64")
case t.untyped && isNumber(t.TypeOf()):
t = valueTOf(floatType, withUntyped(true), withScope(sc))
default:
err = n.cfgErrorf("invalid complex type %s", k)
}
@@ -1127,6 +1122,24 @@ func (t *itype) concrete() *itype {
return t
}
func (t *itype) underlying() *itype {
if t.cat == aliasT {
return t.val.underlying()
}
return t
}
// typeDefined returns true if type t1 is defined from type t2 or t2 from t1.
func typeDefined(t1, t2 *itype) bool {
if t1.cat == aliasT && t1.val == t2 {
return true
}
if t2.cat == aliasT && t2.val == t1 {
return true
}
return false
}
// isVariadic returns true if the function type is variadic.
// If the type is not a function or is not variadic, it will
// return false.
@@ -1200,8 +1213,7 @@ func (t *itype) assignableTo(o *itype) bool {
if t.equals(o) {
return true
}
if t.cat == aliasT && o.cat == aliasT {
// If alias types are not identical, it is not assignable.
if t.cat == aliasT && o.cat == aliasT && (t.underlying().id() != o.underlying().id() || !typeDefined(t, o)) {
return false
}
if t.isNil() && o.hasNil() || o.isNil() && t.hasNil() {
@@ -1221,6 +1233,11 @@ func (t *itype) assignableTo(o *itype) bool {
return true
}
if t.untyped && isNumber(t.TypeOf()) && isNumber(o.TypeOf()) {
// Assignability depends on constant numeric value (overflow check), to be tested elsewhere.
return true
}
n := t.node
if n == nil || !n.rval.IsValid() {
return false
@@ -1827,6 +1844,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 {
@@ -1834,6 +1852,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)
}
}
}
}
@@ -1842,13 +1863,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() {

View File

@@ -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 {
@@ -178,25 +184,31 @@ func (check typecheck) shift(n *node) error {
// comparison type checks a comparison binary expression.
func (check typecheck) comparison(n *node) error {
c0, c1 := n.child[0], n.child[1]
t0, t1 := n.child[0].typ, n.child[1].typ
if !c0.typ.assignableTo(c1.typ) && !c1.typ.assignableTo(c0.typ) {
return n.cfgErrorf("invalid operation: mismatched types %s and %s", c0.typ.id(), c1.typ.id())
if !t0.assignableTo(t1) && !t1.assignableTo(t0) {
return n.cfgErrorf("invalid operation: mismatched types %s and %s", t0.id(), t1.id())
}
ok := false
if !isInterface(t0) && !isInterface(t1) && !t0.isNil() && !t1.isNil() && t0.untyped == t1.untyped && t0.id() != t1.id() {
// Non interface types must be really equals.
return n.cfgErrorf("invalid operation: mismatched types %s and %s", t0.id(), t1.id())
}
switch n.action {
case aEqual, aNotEqual:
ok = c0.typ.comparable() && c1.typ.comparable() || c0.typ.isNil() && c1.typ.hasNil() || c1.typ.isNil() && c0.typ.hasNil()
ok = t0.comparable() && t1.comparable() || t0.isNil() && t1.hasNil() || t1.isNil() && t0.hasNil()
case aLower, aLowerEqual, aGreater, aGreaterEqual:
ok = c0.typ.ordered() && c1.typ.ordered()
ok = t0.ordered() && t1.ordered()
}
if !ok {
typ := c0.typ
typ := t0
if typ.isNil() {
typ = c1.typ
typ = t1
}
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 +233,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 +492,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 +613,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 {
@@ -793,7 +821,7 @@ func (check typecheck) builtin(name string, n *node, child []*node, ellipsis boo
case !typ0.untyped && typ1.untyped:
err = check.convertUntyped(p1.nod, typ0)
case typ0.untyped && typ1.untyped:
fltType := check.scope.getType("float64")
fltType := untypedFloat()
err = check.convertUntyped(p0.nod, fltType)
if err != nil {
break
@@ -816,7 +844,7 @@ func (check typecheck) builtin(name string, n *node, child []*node, ellipsis boo
p := params[0]
typ := p.Type()
if typ.untyped {
if err := check.convertUntyped(p.nod, check.scope.getType("complex128")); err != nil {
if err := check.convertUntyped(p.nod, untypedComplex()); err != nil {
return err
}
}

View File

@@ -34,10 +34,10 @@ func valueGenerator(n *node, i int) func(*frame) reflect.Value {
// because a cancellation prior to any evaluation result may leave
// the frame's data empty.
func valueOf(data []reflect.Value, i int) reflect.Value {
if i < len(data) {
return data[i]
if i < 0 || i >= len(data) {
return reflect.Value{}
}
return reflect.Value{}
return data[i]
}
func genValueBinMethodOnInterface(n *node, defaultGen func(*frame) reflect.Value) func(*frame) reflect.Value {
@@ -195,7 +195,7 @@ func genValue(n *node) func(*frame) reflect.Value {
}
if n.sym != nil {
i := n.sym.index
if i < 0 {
if i < 0 && n != n.sym.node {
return genValue(n.sym.node)
}
if n.sym.global {
@@ -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)
@@ -392,6 +392,21 @@ func genValueOutput(n *node, t reflect.Type) func(*frame) reflect.Value {
return value
}
func getBinValue(getMapType func(*itype) reflect.Type, value func(*frame) reflect.Value, f *frame) reflect.Value {
v := value(f)
if getMapType == nil {
return v
}
val, ok := v.Interface().(valueInterface)
if !ok || val.node == nil {
return v
}
if rt := getMapType(val.node.typ); rt != nil {
return genInterfaceWrapper(val.node, rt)(f)
}
return v
}
func valueInterfaceValue(v reflect.Value) reflect.Value {
for {
vv, ok := v.Interface().(valueInterface)
@@ -408,7 +423,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 +436,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 +610,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() }
}

58
stdlib/maptypes.go Normal file
View File

@@ -0,0 +1,58 @@
package stdlib
import (
"encoding"
"encoding/json"
"encoding/xml"
"fmt"
"log"
"reflect"
)
func init() {
mt := []reflect.Type{
reflect.TypeOf((*fmt.Formatter)(nil)).Elem(),
reflect.TypeOf((*fmt.Stringer)(nil)).Elem(),
}
MapTypes[reflect.ValueOf(fmt.Errorf)] = mt
MapTypes[reflect.ValueOf(fmt.Fprint)] = mt
MapTypes[reflect.ValueOf(fmt.Fprintf)] = mt
MapTypes[reflect.ValueOf(fmt.Fprintln)] = mt
MapTypes[reflect.ValueOf(fmt.Print)] = mt
MapTypes[reflect.ValueOf(fmt.Printf)] = mt
MapTypes[reflect.ValueOf(fmt.Println)] = mt
MapTypes[reflect.ValueOf(fmt.Sprint)] = mt
MapTypes[reflect.ValueOf(fmt.Sprintf)] = mt
MapTypes[reflect.ValueOf(fmt.Sprintln)] = mt
MapTypes[reflect.ValueOf(log.Fatal)] = mt
MapTypes[reflect.ValueOf(log.Fatalf)] = mt
MapTypes[reflect.ValueOf(log.Fatalln)] = mt
MapTypes[reflect.ValueOf(log.Panic)] = mt
MapTypes[reflect.ValueOf(log.Panicf)] = mt
MapTypes[reflect.ValueOf(log.Panicln)] = mt
mt = []reflect.Type{reflect.TypeOf((*fmt.Scanner)(nil)).Elem()}
MapTypes[reflect.ValueOf(fmt.Scan)] = mt
MapTypes[reflect.ValueOf(fmt.Scanf)] = mt
MapTypes[reflect.ValueOf(fmt.Scanln)] = mt
MapTypes[reflect.ValueOf(json.Marshal)] = []reflect.Type{
reflect.TypeOf((*json.Marshaler)(nil)).Elem(),
reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem(),
}
MapTypes[reflect.ValueOf(json.Unmarshal)] = []reflect.Type{
reflect.TypeOf((*json.Unmarshaler)(nil)).Elem(),
reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem(),
}
MapTypes[reflect.ValueOf(xml.Marshal)] = []reflect.Type{
reflect.TypeOf((*xml.Marshaler)(nil)).Elem(),
reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem(),
}
MapTypes[reflect.ValueOf(xml.Unmarshal)] = []reflect.Type{
reflect.TypeOf((*xml.Unmarshaler)(nil)).Elem(),
reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem(),
}
}

View File

@@ -9,10 +9,17 @@ import "reflect"
// Symbols variable stores the map of stdlib symbols per package.
var Symbols = map[string]map[string]reflect.Value{}
// MapTypes variable contains a map of functions which have an interface{} as parameter but
// do something special if the parameter implements a given interface.
var MapTypes = map[reflect.Value][]reflect.Type{}
func init() {
Symbols["github.com/traefik/yaegi/stdlib"] = map[string]reflect.Value{
Symbols["github.com/traefik/yaegi/stdlib/stdlib"] = map[string]reflect.Value{
"Symbols": reflect.ValueOf(Symbols),
}
Symbols["."] = map[string]reflect.Value{
"MapTypes": reflect.ValueOf(MapTypes),
}
}
// Provide access to go standard library (http://golang.org/pkg/)