Compare commits

...

39 Commits

Author SHA1 Message Date
Marc Vertes
38a7331bf9 interp: fix type check on function signature
Perform function declaration type check from the upper level scope (the scope where the
function is declared), to avoid possible collisions of local variables with package names.

Fixes #957.
2020-11-13 18:02:04 +01:00
Marc Vertes
13783889cb interp: avoid useless interface wrapping
in `callBin`, call arguments are converted to the corresponding
parameter type. In a case of an interface, the original concrete type
should be preserved instead, and only wrapped to an interface type for
internal interpreter types, as runtime values should already implement the
interface.

This change removes the interface wrapping when parameter is a runtime
value (valueT or ptrT to valueT).

This removes some overhead when handling runtime values, and keep a
similar behavior between interpreted and pre-compiled code. For
example, `io.Copy` preserves its internal optimisations when passed a
`bytes.Buffer`.
2020-11-12 10:48:04 +01:00
mpl
ed626f3fb9 interp: support conversion of runtime func into interp func
Conversion of interp func into runtime func already worked, but added a
test anyway to watch out for regressions.

Fixes #941
2020-11-10 00:40:04 +01:00
Marc Vertes
d0a34d467b interp: fix getting unsigned constant value
The function vUint, used to get the unsigned integer value of a value,
variable (frame) or constant, was broken for constant.Value expression.

Fixes #948.
2020-11-09 17:40:04 +01:00
Nicholas Wiersma
83676577ac fix: use the import path for types
When running GTA, the type `path` was set to `rpath`. This equates to the package path (`github.com/traefik/yaegi`) in most cases. In the vendored case the `rpath` is the sub package path `something/vendor/github.com/traefik/yaegi` causing issues in typecheck and likely further down the line. By using the `importPath` it makes this consistent.

**Note:** I have no clue how to test this decently. I am open to options here.

Potentially Fixes #916
2020-11-05 13:42:03 +01:00
mpl
f0fc907269 extract: support building/running yaegi with Go devel
Fixes #928
2020-11-05 11:40:04 +01:00
Marc Vertes
61f4704925 interp: fix CFG in case of for loop with empty init and cond
Refactor `for` variants for clarity. Ensure all possible 8 combinations
are covered.

Fixes #942.
2020-11-05 11:00:04 +01:00
Marc Vertes
b1ccfbf47f interp: apply type conversion on untyped variable at run
Fixes #938.
2020-11-04 18:16:04 +01:00
Marc Vertes
0ed4b362dc interp: implement conversion for interpreter function types
Interpreter function types are represented internally by the AST node
of their definition. The conversion operation creates a new node with
the type field pointing to the target type.

Fixes #936.
2020-11-03 17:48:03 +01:00
Marc Vertes
98807387a4 interp: resolve type for untyped shift expressions
A non-constant shift expression can be untyped, requiring to apply a
type from inherited context. This change insures that such context is
propagated during CFG pre-order walk, to be used if necessary.
    
Fixes #927.
2020-11-02 18:08:04 +01:00
Marc Vertes
c817823ba1 interp: fix incorrect infinite loop on for statement
Add a for statement variant for the case of a "for" with an init, no
condition and no post-increment.

Fixes #933.
2020-11-02 17:52:03 +01:00
mpl
3cb8bca81a interp: on panic, look for node where offending exec originated from
The execution flow is such that a node can end up running several chained exec
funcs, some of which actually originate from other nodes. For example, in:

var m []int // L0
println("hello world") // L1
m[0] = 1 // L2

the offending code is on a node on line 2 (out of range error). However, since
the assignment to m is part of the execution flow of the variable m, we'll get
the panic when running all the chained exec funcs attached to the node for m on
line 0.

Which is why, when that happens, we need to look for the actual node (the one on
L2) where the offending instruction originates from, in order to
properly report the origin of the panic.

Fixes #546
2020-11-02 10:26:04 +01:00
Marc Vertes
a38d19288f interp: fix testing for nil interface values
Fixes #924.
2020-10-30 16:20:04 +01:00
Marc Vertes
7f8ffa6719 interp: handle explicit nil values in literal composite values
Fixes #922.
2020-10-27 11:24:04 +01:00
Marc Vertes
513f5e37aa interp: fix type conversion for constant expressions
The case of a constant arithmetic expression being of float kind because
of quotient was not handled correctly. Simplify constant extraction to
reflect.Value first, then conversion to target type, and let reflect Convert
method panic if conversion is not valid.

Fixes #920.
2020-10-23 10:44:04 +02:00
Marc Vertes
9520a92241 interp: fix type in assign of shift operations
Type checking on shift operands was failing for untyped variable values.

Fix propagation of type in assignment. Optimize assignment of arithmetic
operations on variables by skipping the assign and writing directly to
destination frame value in the operator function.

Skip some slow tests when given -short test option.

Fixes #917.
2020-10-23 10:32:03 +02:00
rsteube
d47821bfaa interp: add indexExpr check for reflect.Array
In typecheck.go, type check was failing for expressions like `[2]int{0, 1}[0:]`.
```
cannot slice type [2]int
```

Fixes #914 .
2020-10-21 23:00:04 +02:00
rsteube
d7ede8ed5c interp: added sliceExpr and starExpr
Type check was failing for expression such as: `&(*tree)[node:][0]`, as in

```go
tree, _ := huffmanTreePool.Get().(*[]huffmanTree)
...

initHuffmanTree(&(*tree)[node:][0], histogram[l], -1, int16(l))
```

see c3da72aa01/brotli_bit_stream.go (L469)
2020-10-21 18:14:03 +02:00
Marc Vertes
22c63b225c interp: fix array size definition in case of forward declared constant
In type.go, the symbol lookup on constant id was not performed. Handle
the ident kind explicitely to allow that.

Fixes #911.
2020-10-21 13:08:03 +02:00
mpl
c0eaab0891 interp: fix assignable check
The assignable check used to be too strict as it lacked the property that
if an untyped const can be represented as a T, then it is assignable to T.

And we can now use that fixed check to add a missing check: in a return
statement, we now make sure that any of the returned elements are
assignable to what the signature tells us they should be.
2020-10-21 10:06:03 +02:00
rsteube
c74d050c5a interp: added kind to log for typecheck (#910) 2020-10-21 09:46:05 +02:00
Marc Vertes
804664c631 interp: fix type check in constant unary operations
In unary constant operations, the test for unsigned was defeated by
testing for int first, which is true also for unsigned. Make sure that
testing for unsigned precedes testing for int.

Fixes #907.
2020-10-19 10:32:03 +02:00
Kamil Samigullin
f6d8261a8a chore: add homebrew-tap recipe
Publish homebrew-tap recipe to https://github.com/traefik/homebrew-tap
2020-10-17 05:54:04 +02:00
Marc Vertes
68c02ce054 feature: expose extract package, previously internal 2020-10-15 18:58:03 +02:00
Marc Vertes
4b3e9ee231 feature: use environment in addition to flags to control test options
This applies to -syscall, -unsafe and -unrestricted flags with the
corresponding env variables YAEGI_SYSCALL, YAEGI_UNSAFE and
YAEGI_UNRESTRICTED, already used in the same way for the run
sub-command.
2020-10-15 18:42:04 +02:00
mpl
8916618a81 interp: API change for Symbols method
Often enough when debugging, one does not know exactly what argument
should be given to Symbols, as it's not always clear in which
scope/namespace the symbol one is looking for is stored in.

Therefore, this change enables the Symbols method to now take the empty
string as an argument, which results in all the known symbols to be
returned (keyed by import path).

As a consequence, when an non-empty argument is given, the returned
result should be similar to what we had before, except it is now
returned as the sole entry of an encompassing map.

In addition, the "binary" symbols (i.e. the ones ingested through a
Use call), are now also taken into account.
2020-10-15 10:02:04 +02:00
mpl
57b49f40d6 interp: make Symbols method take into account type objects too 2020-10-14 17:44:09 +02:00
Marc Vertes
190dade469 fix: detect a wrong number of arguments at return
Also fix error ouptut in the run command to avoid displaying twice
the same error.

Fixes #819.
2020-10-14 17:30:04 +02:00
Marc Vertes
6b652ea485 feature: provide a version sub-command
A version sub-command is implemented.
A local install target is added to Makefile, to set version from git at build.

Fixes #840.
2020-10-14 16:24:05 +02:00
Marc Vertes
473bc63588 fix: behavior of select and time ticker
There was several issues:
- access to field on pointer to struct from runtime: fix in
  lookupBinField
- assign operation was skipped when performed in a comm clause
- the direction of comm clause was wrong if a channel send operation was
  performed in a body of a receive comm clause

Fixes #884.
2020-10-14 15:50:04 +02:00
Marc Vertes
e32da38ad0 fix: append from / to runtime defined array
Check first for runtime defined array (typ.cat of valueT)
to avoid checking inexisting properties (an panic), when
deciding to use appendSlice or not.

Fixes #880.
2020-10-13 17:14:04 +02:00
Marc Vertes
b2b519c2fd fix: correctly return constant expressions in functions
In the case of a function returning a single value, a constant
result could be ignored, and the function would return a zero value.

Fixes #889.
2020-10-13 17:02:04 +02:00
Marc Vertes
9491e58920 fix: correct handling of select comm clause with empty body
Fixes #887.
2020-10-13 11:24:04 +02:00
Marc Vertes
f362237ac5 fix: perform constant type conversion when required
Fixes #893.
2020-10-12 19:06:03 +02:00
Marc Vertes
a83ec1f925 fix: allow yaegi command to interpret itself
Since the introduction of restricted stdlib and syscall symbols, the
capability of yaegi to interpret itself was broken.
The use of unrestricted symbols is now also controlled by environment
variables, to allow propagation accross nested interpreters.
The interpreter Panic symbol was not wrapped, this is fixed now.
the import path resolution was failing if the working directory was
outside of GOPATH.
The documentation and readme have been ajusted.

Fixes #890.
2020-10-09 11:48:04 +02:00
mpl
155ca4e6ad interp: support implicit-type slice of pointers of struct composite literal
Also fix same case for implicit-type maps of etc.

Fixes #883
2020-10-09 10:32:03 +02:00
mpl
ca196a5768 interp: fix implicit type for bin composite lit case
When the type is implicit, the first element in the list of children is
not the type of the composite literal, but it is one of the actual
children, so it should not be discarded.

Fixes #862
2020-10-05 16:42:03 +02:00
Marc Vertes
b78d55c66b fix: compute array type size from constant expression
Fixes #875.
2020-10-05 16:12:04 +02:00
mpl
16f5586a11 interp: apply integer division when appropriate
When working with an untyped const expression involving a division, if
the default type of the result should be an int (for example because the
default types of all the operands are ints as well), then we should make
sure that the operation that is applied is indeed an integer division,
and that the type of the result is not a float.

This is achieved by using the QUO_ASSIGN operator, instead of the QUO
operator.

This should fix several problems lurking around, and it notably fixes
one of the visible consequences, which is a systematic panic when using
the REPL as a "calculator".

This incidentally also allows us to revert what was done in
5dfc3b86dc since it now turns out it was
just a hack to fix one of the symptoms.

Fixes #864
2020-10-05 10:50:03 +02:00
298 changed files with 1622 additions and 594 deletions

View File

@@ -45,3 +45,19 @@ archives:
format: zip format: zip
files: files:
- LICENSE - LICENSE
brews:
- github:
owner: traefik
name: homebrew-tap
commit_author:
name: traefiker
email: 30906710+traefiker@users.noreply.github.com
folder: Formula
homepage: https://github.com/traefik/yaegi
description: |
Yaegi is Another Elegant Go Interpreter.
It powers executable Go scripts and plugins, in embedded interpreters
or interactive shells, on top of the Go runtime.
test: |
system "#{bin}/yaegi version"

View File

@@ -16,12 +16,15 @@ internal/cmd/extract/extract:
generate: gen_all_syscall generate: gen_all_syscall
go generate go generate
install:
GOFLAGS=-ldflags=-X=main.version=$$(git describe --tags) go install ./...
tests: tests:
GO111MODULE=off go test -v ./... go test -v ./...
GO111MODULE=off go test -race ./interp go test -race ./interp
# https://github.com/goreleaser/godownloader # https://github.com/goreleaser/godownloader
install.sh: .goreleaser.yml install.sh: .goreleaser.yml
godownloader --repo=traefik/yaegi -o install.sh .goreleaser.yml godownloader --repo=traefik/yaegi -o install.sh .goreleaser.yml
.PHONY: check gen_all_syscall gen_tests generate_downloader internal/cmd/extract/extract .PHONY: check gen_all_syscall gen_tests generate_downloader internal/cmd/extract/extract install

View File

@@ -128,10 +128,20 @@ Hello World
> >
``` ```
Or interpret Go files: Note that in interactive mode, all stdlib package are pre-imported,
you can use them directly:
```console ```console
$ yaegi cmd/yaegi/yaegi.go $ yaegi
> reflect.TypeOf(time.Date)
: func(int, time.Month, int, int, int, int, int, *time.Location) time.Time
>
```
Or interpret Go packages, directories or files, including itself:
```console
$ yaegi -syscall -unsafe -unrestricted github.com/traefik/yaegi/cmd/yaegi
> >
``` ```

17
_test/a43.go Normal file
View File

@@ -0,0 +1,17 @@
package main
import "fmt"
type T [l1 + l2]int
const (
l1 = 2
l2 = 3
)
func main() {
fmt.Println(T{})
}
// Output:
// [0 0 0 0 0]

12
_test/a44.go Normal file
View File

@@ -0,0 +1,12 @@
package main
var a = [max]int{}
const max = 32
func main() {
println(len(a))
}
// Output:
// 32

22
_test/addr1.go Normal file
View File

@@ -0,0 +1,22 @@
package main
import "fmt"
type T struct {
A int
B int
}
func main() {
a := &[]T{
{1, 2},
{3, 4},
}
fmt.Println("a:", a)
x := &(*a)[1:][0]
fmt.Println("x:", x)
}
// Output:
// a: &[{1 2} {3 4}]
// x: &{3 4}

15
_test/append1.go Normal file
View File

@@ -0,0 +1,15 @@
package main
import (
"bufio"
"bytes"
)
func main() {
s := bufio.NewScanner(bytes.NewReader([]byte("Hello\nTest\nLine3")))
s.Scan()
println(string(append(s.Bytes(), []byte(" World")...)))
}
// Output:
// Hello World

15
_test/append2.go Normal file
View File

@@ -0,0 +1,15 @@
package main
import (
"bufio"
"bytes"
)
func main() {
s := bufio.NewScanner(bytes.NewReader([]byte("Hello\nTest\nLine3")))
s.Scan()
println(string(append(s.Bytes(), " World"...)))
}
// Output:
// Hello World

15
_test/assign16.go Normal file
View File

@@ -0,0 +1,15 @@
package main
type H struct {
bits uint
}
func main() {
h := &H{8}
var x uint = (1 << h.bits) >> 6
println(x)
}
// Output:
// 4

View File

@@ -0,0 +1,17 @@
package main
import (
"fmt"
"image"
)
func main() {
v := map[string]*image.Point{
"foo": {X: 3, Y: 2},
"bar": {X: 4, Y: 5},
}
fmt.Println(v["foo"], v["bar"])
}
// Output:
// (3,2) (4,5)

View File

@@ -0,0 +1,17 @@
package main
import (
"fmt"
"image"
)
func main() {
v := []*image.Point{
{X: 3, Y: 2},
{X: 4, Y: 5},
}
fmt.Println(v)
}
// Output:
// [(3,2) (4,5)]

16
_test/binstruct_slice0.go Normal file
View File

@@ -0,0 +1,16 @@
package main
import (
"fmt"
"image"
)
func main() {
v := []image.Point{
{X: 3, Y: 2},
}
fmt.Println(v)
}
// Output:
// [(3,2)]

15
_test/composite14.go Normal file
View File

@@ -0,0 +1,15 @@
package main
import "fmt"
type T struct {
b []byte
}
func main() {
t := T{nil}
fmt.Println(t)
}
// Output:
// {[]}

14
_test/const17.go Normal file
View File

@@ -0,0 +1,14 @@
package main
import "fmt"
var t [7/3]int
func main() {
t[0] = 3/2
t[1] = 5/2
fmt.Println(t)
}
// Output:
// [1 2]

11
_test/const18.go Normal file
View File

@@ -0,0 +1,11 @@
package main
import "time"
func main() {
a := int64(time.Second)
println(a)
}
// Output:
// 1000000000

17
_test/const19.go Normal file
View File

@@ -0,0 +1,17 @@
package main
import (
"fmt"
"time"
)
func get10Hours() time.Duration {
return 10 * time.Hour
}
func main() {
fmt.Println(get10Hours().String())
}
// Output:
// 10h0m0s

12
_test/const20.go Normal file
View File

@@ -0,0 +1,12 @@
package main
import "fmt"
const maxLen = int64(int(^uint(0) >> 1))
func main() {
fmt.Println(maxLen)
}
// Output:
// 9223372036854775807

12
_test/const21.go Normal file
View File

@@ -0,0 +1,12 @@
package main
const a = 64
var b uint = a * a / 2
func main() {
println(b)
}
// Output:
// 2048

21
_test/convert0.go Normal file
View File

@@ -0,0 +1,21 @@
package main
type T struct {
v int
}
type comparator func(T, T) bool
func sort(items []T, comp comparator) {
println("in sort")
}
func compT(t0, t1 T) bool { return t0.v < t1.v }
func main() {
a := []T{}
sort(a, comparator(compT))
}
// Output:
// in sort

17
_test/convert1.go Normal file
View File

@@ -0,0 +1,17 @@
package main
import "strconv"
type atoidef func(s string) (int, error)
func main() {
stdatoi := atoidef(strconv.Atoi)
n, err := stdatoi("7")
if err != nil {
panic(err)
}
println(n)
}
// Output:
// 7

19
_test/convert2.go Normal file
View File

@@ -0,0 +1,19 @@
package main
import "bufio"
func fakeSplitFunc(data []byte, atEOF bool) (advance int, token []byte, err error) {
return 7, nil, nil
}
func main() {
splitfunc := bufio.SplitFunc(fakeSplitFunc)
n, _, err := splitfunc(nil, true)
if err != nil {
panic(err)
}
println(n)
}
// Output:
// 7

16
_test/for15.go Normal file
View File

@@ -0,0 +1,16 @@
package main
func f() int { println("in f"); return 1 }
func main() {
for i := f(); ; {
println("in loop")
if i > 0 {
break
}
}
}
// Output:
// in f
// in loop

16
_test/for16.go Normal file
View File

@@ -0,0 +1,16 @@
package main
func main() {
max := 1
for ; ; max-- {
if max == 0 {
break
}
println("in for")
}
println("bye")
}
// Output:
// in for
// bye

10
_test/fun23.go Normal file
View File

@@ -0,0 +1,10 @@
package main
func f(x int) { return x }
func main() {
print("hello")
}
// Error:
// 3:17: too many arguments to return

10
_test/fun24.go Normal file
View File

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

10
_test/fun25.go Normal file
View File

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

23
_test/issue-880.go Normal file
View File

@@ -0,0 +1,23 @@
package main
import (
"bufio"
"bytes"
)
func main() {
var buf1 = make([]byte, 1024)
var buf2 []byte
buf1 = []byte("Hallo\nTest\nLine3")
s := bufio.NewScanner(bytes.NewReader(buf1))
for s.Scan() {
buf2 = append(buf2, append(s.Bytes(), []byte("\n")...)...)
}
print(string(buf2))
}
// Output:
// Hallo
// Test
// Line3

28
_test/nil3.go Normal file
View File

@@ -0,0 +1,28 @@
package main
type I interface {
Hello()
}
type T struct {
h I
}
func (t *T) Hello() { println("Hello") }
func main() {
t := &T{}
println(t.h != nil)
println(t.h == nil)
t.h = t
println(t.h != nil)
println(t.h == nil)
t.h.Hello()
}
// Output:
// false
// true
// true
// false
// Hello

View File

@@ -1,18 +1,20 @@
package main package main
import "time" import (
import "fmt" "fmt"
"time"
)
func main() { func main() {
c1 := make(chan string) c1 := make(chan string)
c2 := make(chan string) c2 := make(chan string)
go func() { go func() {
time.Sleep(1e9) time.Sleep(1e7)
c1 <- "one" c1 <- "one"
}() }()
go func() { go func() {
time.Sleep(2e9) time.Sleep(2e7)
c2 <- "two" c2 <- "two"
}() }()

44
_test/select14.go Normal file
View File

@@ -0,0 +1,44 @@
package main
import (
"fmt"
"time"
)
const (
period = 100 * time.Millisecond
precision = 5 * time.Millisecond
)
func main() {
counter := 0
p := time.Now()
ticker := time.NewTicker(period)
ch := make(chan int)
go func() {
for i := 0; i < 3; i++ {
select {
case t := <-ticker.C:
counter = counter + 1
ch <- counter
if d := t.Sub(p) - period; d < -precision || d > precision {
fmt.Println("wrong delay", d)
}
p = t
}
}
ch <- 0
}()
for c := range ch {
if c == 0 {
break
}
println(c)
}
}
// Output:
// 1
// 2
// 3

23
_test/select15.go Normal file
View File

@@ -0,0 +1,23 @@
package main
type T struct {
c1 chan string
c2 chan string
}
func main() {
t := &T{}
t.c2 = make(chan string)
go func(c chan string) { c <- "done" }(t.c2)
select {
case msg := <-t.c1:
println("received from c1:", msg)
case <-t.c2:
}
println("Bye")
}
// Output:
// Bye

11
_test/slice.go Normal file
View File

@@ -0,0 +1,11 @@
package main
import "fmt"
func main() {
a := [2][2]int{{0, 1}, {2, 3}}
fmt.Println(a[0][0:])
}
// Output:
// [0 1]

20
_test/time14.go Normal file
View File

@@ -0,0 +1,20 @@
package main
import (
"fmt"
"time"
)
var t time.Time
func f() time.Time {
time := t
return time
}
func main() {
fmt.Println(f())
}
// Output:
// 0001-01-01 00:00:00 +0000 UTC

View File

@@ -10,7 +10,7 @@ import (
"path" "path"
"strings" "strings"
"github.com/traefik/yaegi/internal/extract" "github.com/traefik/yaegi/extract"
) )
func extractCmd(arg []string) error { func extractCmd(arg []string) error {

View File

@@ -14,6 +14,7 @@ The commands are:
help print usage information help print usage information
run execute a Go program from source run execute a Go program from source
test execute test functions in a Go package test execute test functions in a Go package
version print version
Use "yaegi help <command>" for more information about a command. Use "yaegi help <command>" for more information about a command.
@@ -37,6 +38,9 @@ func help(arg []string) error {
return run([]string{"-h"}) return run([]string{"-h"})
case Test: case Test:
return test([]string{"-h"}) return test([]string{"-h"})
case Version:
fmt.Println("Usage: yaegi version")
return nil
default: default:
return fmt.Errorf("help: invalid yaegi command: %v", cmd) return fmt.Errorf("help: invalid yaegi command: %v", cmd)
} }

View File

@@ -6,6 +6,7 @@ import (
"go/build" "go/build"
"io/ioutil" "io/ioutil"
"os" "os"
"strconv"
"strings" "strings"
"github.com/traefik/yaegi/interp" "github.com/traefik/yaegi/interp"
@@ -17,19 +18,21 @@ import (
func run(arg []string) error { func run(arg []string) error {
var interactive bool var interactive bool
var useSyscall bool
var useUnrestricted bool
var useUnsafe bool
var tags string var tags string
var cmd string var cmd string
var err error var err error
// The following flags are initialized from environment.
useSyscall, _ := strconv.ParseBool(os.Getenv("YAEGI_SYSCALL"))
useUnrestricted, _ := strconv.ParseBool(os.Getenv("YAEGI_UNRESTRICTED"))
useUnsafe, _ := strconv.ParseBool(os.Getenv("YAEGI_UNSAFE"))
rflag := flag.NewFlagSet("run", flag.ContinueOnError) rflag := flag.NewFlagSet("run", flag.ContinueOnError)
rflag.BoolVar(&interactive, "i", false, "start an interactive REPL") rflag.BoolVar(&interactive, "i", false, "start an interactive REPL")
rflag.BoolVar(&useSyscall, "syscall", false, "include syscall symbols") rflag.BoolVar(&useSyscall, "syscall", useSyscall, "include syscall symbols")
rflag.BoolVar(&useUnrestricted, "unrestricted", false, "include unrestricted symbols") rflag.BoolVar(&useUnrestricted, "unrestricted", useUnrestricted, "include unrestricted symbols")
rflag.StringVar(&tags, "tags", "", "set a list of build tags") rflag.StringVar(&tags, "tags", "", "set a list of build tags")
rflag.BoolVar(&useUnsafe, "unsafe", false, "include usafe symbols") rflag.BoolVar(&useUnsafe, "unsafe", useUnsafe, "include unsafe symbols")
rflag.StringVar(&cmd, "e", "", "set the command to be executed (instead of script or/and shell)") rflag.StringVar(&cmd, "e", "", "set the command to be executed (instead of script or/and shell)")
rflag.Usage = func() { rflag.Usage = func() {
fmt.Println("Usage: yaegi run [options] [path] [args]") fmt.Println("Usage: yaegi run [options] [path] [args]")
@@ -46,24 +49,33 @@ func run(arg []string) error {
i.Use(interp.Symbols) i.Use(interp.Symbols)
if useSyscall { if useSyscall {
i.Use(syscall.Symbols) i.Use(syscall.Symbols)
// Using a environment var allows a nested interpreter to import the syscall package.
if err := os.Setenv("YAEGI_SYSCALL", "1"); err != nil {
return err
}
} }
if useUnsafe { if useUnsafe {
i.Use(unsafe.Symbols) i.Use(unsafe.Symbols)
if err := os.Setenv("YAEGI_UNSAFE", "1"); err != nil {
return err
}
} }
if useUnrestricted { if useUnrestricted {
// Use of unrestricted symbols should always follow stdlib and syscall symbols, to update them. // Use of unrestricted symbols should always follow stdlib and syscall symbols, to update them.
i.Use(unrestricted.Symbols) i.Use(unrestricted.Symbols)
if err := os.Setenv("YAEGI_UNRESTRICTED", "1"); err != nil {
return err
}
} }
if cmd != "" { if cmd != "" {
_, err = i.Eval(cmd) _, err = i.Eval(cmd)
showError(err)
} }
if len(args) == 0 { if len(args) == 0 {
if interactive || cmd == "" { if interactive || cmd == "" {
_, err = i.REPL()
showError(err) showError(err)
_, err = i.REPL()
} }
return err return err
} }
@@ -78,7 +90,6 @@ func run(arg []string) error {
} else { } else {
_, err = i.EvalPath(path) _, err = i.EvalPath(path)
} }
showError(err)
if err != nil { if err != nil {
return err return err
@@ -86,7 +97,6 @@ func run(arg []string) error {
if interactive { if interactive {
_, err = i.REPL() _, err = i.REPL()
showError(err)
} }
return err return err
} }

View File

@@ -1,11 +1,13 @@
package main package main
import ( import (
"errors"
"flag" "flag"
"fmt" "fmt"
"go/build" "go/build"
"os" "os"
"regexp" "regexp"
"strconv"
"strings" "strings"
"testing" "testing"
@@ -18,22 +20,24 @@ import (
func test(arg []string) (err error) { func test(arg []string) (err error) {
var ( var (
bench string bench string
benchmem bool benchmem bool
benchtime string benchtime string
count string count string
cpu string cpu string
failfast bool failfast bool
run string run string
short bool short bool
tags string tags string
useUnrestricted bool timeout string
useUnsafe bool verbose bool
useSyscall bool
timeout string
verbose bool
) )
// The following flags are initialized from environment.
useSyscall, _ := strconv.ParseBool(os.Getenv("YAEGI_SYSCALL"))
useUnrestricted, _ := strconv.ParseBool(os.Getenv("YAEGI_UNRESTRICTED"))
useUnsafe, _ := strconv.ParseBool(os.Getenv("YAEGI_UNSAFE"))
tflag := flag.NewFlagSet("test", flag.ContinueOnError) tflag := flag.NewFlagSet("test", flag.ContinueOnError)
tflag.StringVar(&bench, "bench", "", "Run only those benchmarks matching a regular expression.") tflag.StringVar(&bench, "bench", "", "Run only those benchmarks matching a regular expression.")
tflag.BoolVar(&benchmem, "benchmem", false, "Print memory allocation statistics for benchmarks.") tflag.BoolVar(&benchmem, "benchmem", false, "Print memory allocation statistics for benchmarks.")
@@ -45,9 +49,9 @@ func test(arg []string) (err error) {
tflag.BoolVar(&short, "short", false, "Tell long-running tests to shorten their run time.") tflag.BoolVar(&short, "short", false, "Tell long-running tests to shorten their run time.")
tflag.StringVar(&tags, "tags", "", "Set a list of build tags.") tflag.StringVar(&tags, "tags", "", "Set a list of build tags.")
tflag.StringVar(&timeout, "timeout", "", "If a test binary runs longer than duration d, panic.") tflag.StringVar(&timeout, "timeout", "", "If a test binary runs longer than duration d, panic.")
tflag.BoolVar(&useUnrestricted, "unrestricted", false, "Include unrestricted symbols.") tflag.BoolVar(&useUnrestricted, "unrestricted", useUnrestricted, "Include unrestricted symbols.")
tflag.BoolVar(&useUnsafe, "unsafe", false, "Include usafe symbols.") tflag.BoolVar(&useUnsafe, "unsafe", useUnsafe, "Include usafe symbols.")
tflag.BoolVar(&useSyscall, "syscall", false, "Include syscall symbols.") tflag.BoolVar(&useSyscall, "syscall", useSyscall, "Include syscall symbols.")
tflag.BoolVar(&verbose, "v", false, "Verbose output: log all tests as they are run.") tflag.BoolVar(&verbose, "v", false, "Verbose output: log all tests as they are run.")
tflag.Usage = func() { tflag.Usage = func() {
fmt.Println("Usage: yaegi test [options] [path]") fmt.Println("Usage: yaegi test [options] [path]")
@@ -104,12 +108,22 @@ func test(arg []string) (err error) {
i.Use(interp.Symbols) i.Use(interp.Symbols)
if useSyscall { if useSyscall {
i.Use(syscall.Symbols) i.Use(syscall.Symbols)
// Using a environment var allows a nested interpreter to import the syscall package.
if err := os.Setenv("YAEGI_SYSCALL", "1"); err != nil {
return err
}
} }
if useUnrestricted { if useUnrestricted {
i.Use(unrestricted.Symbols) i.Use(unrestricted.Symbols)
if err := os.Setenv("YAEGI_UNRESTRICTED", "1"); err != nil {
return err
}
} }
if useUnsafe { if useUnsafe {
i.Use(unsafe.Symbols) i.Use(unsafe.Symbols)
if err := os.Setenv("YAEGI_UNSAFE", "1"); err != nil {
return err
}
} }
if err = i.EvalTest(path); err != nil { if err = i.EvalTest(path); err != nil {
return err return err
@@ -117,7 +131,11 @@ func test(arg []string) (err error) {
benchmarks := []testing.InternalBenchmark{} benchmarks := []testing.InternalBenchmark{}
tests := []testing.InternalTest{} tests := []testing.InternalTest{}
for name, sym := range i.Symbols(path) { syms, ok := i.Symbols(path)[path]
if !ok {
return errors.New("No tests found")
}
for name, sym := range syms {
switch fun := sym.Interface().(type) { switch fun := sym.Interface().(type) {
case func(*testing.B): case func(*testing.B):
benchmarks = append(benchmarks, testing.InternalBenchmark{name, fun}) benchmarks = append(benchmarks, testing.InternalBenchmark{name, fun})

View File

@@ -71,7 +71,16 @@ Options:
-unsafe -unsafe
include unsafe symbols. include unsafe symbols.
Debugging support (may be removed at any time): Environment variables:
YAEGI_SYSCALL=1
Include syscall symbols (same as -syscall flag).
YAEGI_UNRESTRICTED=1
Include unrestricted symbols (same as -unrestricted flag).
YAEGI_UNSAFE=1
Include unsafe symbols (same as -unsafe flag).
YAEGI_PROMPT=1
Force enable the printing of the REPL prompt and the result of last instruction,
even if stdin is not a terminal.
YAEGI_AST_DOT=1 YAEGI_AST_DOT=1
Generate and display graphviz dot of AST with dotty(1) Generate and display graphviz dot of AST with dotty(1)
YAEGI_CFG_DOT=1 YAEGI_CFG_DOT=1
@@ -90,6 +99,8 @@ import (
"fmt" "fmt"
"log" "log"
"os" "os"
"github.com/traefik/yaegi/interp"
) )
const ( const (
@@ -97,8 +108,11 @@ const (
Help = "help" Help = "help"
Run = "run" Run = "run"
Test = "test" Test = "test"
Version = "version"
) )
var version = "devel" // This may be overwritten at build time.
func main() { func main() {
var cmd string var cmd string
var err error var err error
@@ -119,6 +133,8 @@ func main() {
err = run(os.Args[2:]) err = run(os.Args[2:])
case Test: case Test:
err = test(os.Args[2:]) err = test(os.Args[2:])
case Version:
fmt.Println(version)
default: default:
// If no command is given, fallback to default "run" command. // If no command is given, fallback to default "run" command.
// This allows scripts starting with "#!/usr/bin/env yaegi", // This allows scripts starting with "#!/usr/bin/env yaegi",
@@ -129,8 +145,10 @@ func main() {
} }
if err != nil && !errors.Is(err, flag.ErrHelp) { if err != nil && !errors.Is(err, flag.ErrHelp) {
err = fmt.Errorf("%s: %w", cmd, err) fmt.Fprintln(os.Stderr, fmt.Errorf("%s: %w", cmd, err))
fmt.Fprintln(os.Stderr, err) if p, ok := err.(interp.Panic); ok {
fmt.Fprintln(os.Stderr, string(p.Stack))
}
exitCode = 1 exitCode = 1
} }
os.Exit(exitCode) os.Exit(exitCode)

View File

@@ -0,0 +1,13 @@
package main
import (
"fmt"
"guthib.com/bat/baz"
)
func main() {
t := baz.NewT()
fmt.Printf("%s", t.A3)
}

View File

@@ -0,0 +1,22 @@
package baz
func NewT() *T {
return &T{
A1: make([]U, 0),
A3: "foobar",
}
}
type T struct {
A1 []U
A3 string
}
type U struct {
B1 V
B2 V
}
type V struct {
C1 string
}

View File

@@ -93,6 +93,12 @@ func TestPackages(t *testing.T) {
expected: "Yo hello", expected: "Yo hello",
evalFile: "./_pkg12/src/guthib.com/foo/main.go", evalFile: "./_pkg12/src/guthib.com/foo/main.go",
}, },
{
desc: "eval main with vendor",
goPath: "./_pkg13/",
expected: "foobar",
evalFile: "./_pkg13/src/guthib.com/foo/bar/main.go",
},
} }
for _, test := range testCases { for _, test := range testCases {

View File

@@ -436,10 +436,12 @@ func GetMinor(part string) string {
return minor return minor
} }
const defaultMinorVersion = 15
func genBuildTags() (string, error) { func genBuildTags() (string, error) {
version := runtime.Version() version := runtime.Version()
if version == "devel" { if strings.HasPrefix(version, "devel") {
return "", nil return "", fmt.Errorf("extracting only supported with stable releases of Go, not %v", version)
} }
parts := strings.Split(version, ".") parts := strings.Split(version, ".")
@@ -452,6 +454,11 @@ func genBuildTags() (string, error) {
return "", fmt.Errorf("failed to parse version: %v", err) return "", fmt.Errorf("failed to parse version: %v", err)
} }
// Only append an upper bound if we are not on the latest go
if minor >= defaultMinorVersion {
return currentGoVersion, nil
}
nextGoVersion := parts[0] + "." + strconv.Itoa(minor+1) nextGoVersion := parts[0] + "." + strconv.Itoa(minor+1)
return currentGoVersion + ",!" + nextGoVersion, nil return currentGoVersion + ",!" + nextGoVersion, nil

View File

@@ -1,6 +1,6 @@
package yaegi package yaegi
//go:generate go generate github.com/traefik/yaegi/internal/extract //go:generate go generate github.com/traefik/yaegi/internal/cmd/extract
//go:generate go generate github.com/traefik/yaegi/interp //go:generate go generate github.com/traefik/yaegi/interp
//go:generate go generate github.com/traefik/yaegi/stdlib //go:generate go generate github.com/traefik/yaegi/stdlib
//go:generate go generate github.com/traefik/yaegi/stdlib/syscall //go:generate go generate github.com/traefik/yaegi/stdlib/syscall

View File

@@ -28,7 +28,7 @@ import (
"runtime" "runtime"
"strings" "strings"
"github.com/traefik/yaegi/internal/extract" "github.com/traefik/yaegi/extract"
) )
var ( var (
@@ -84,11 +84,12 @@ func main() {
oFile = strings.ReplaceAll(importPath, "/", "_") + ".go" oFile = strings.ReplaceAll(importPath, "/", "_") + ".go"
} }
prefix := runtime.Version() version := runtime.Version()
if runtime.Version() != "devel" { if strings.HasPrefix(version, "devel") {
parts := strings.Split(runtime.Version(), ".") log.Fatalf("extracting only supported with stable releases of Go, not %v", version)
prefix = parts[0] + "_" + extract.GetMinor(parts[1])
} }
parts := strings.Split(version, ".")
prefix := parts[0] + "_" + extract.GetMinor(parts[1])
f, err := os.Create(prefix + "_" + oFile) f, err := os.Create(prefix + "_" + oFile)
if err != nil { if err != nil {

View File

@@ -196,6 +196,18 @@ func {{$name}}Const(n *node) {
{{- if $op.Shift}} {{- if $op.Shift}}
v := constant.Shift(vConstantValue(v0), token.{{tokenFromName $name}}, uint(vUint(v1))) v := constant.Shift(vConstantValue(v0), token.{{tokenFromName $name}}, uint(vUint(v1)))
n.rval.Set(reflect.ValueOf(v)) n.rval.Set(reflect.ValueOf(v))
{{- else if (eq $op.Name "/")}}
var operator token.Token
// When the result of the operation is expected to be an int (because both
// operands are ints), we want to force the type of the whole expression to be an
// int (and not a float), which is achieved by using the QUO_ASSIGN operator.
if n.typ.untyped && isInt(n.typ.rtype) {
operator = token.QUO_ASSIGN
} else {
operator = token.QUO
}
v := constant.BinaryOp(vConstantValue(v0), operator, vConstantValue(v1))
n.rval.Set(reflect.ValueOf(v))
{{- else}} {{- else}}
v := constant.BinaryOp(vConstantValue(v0), token.{{tokenFromName $name}}, vConstantValue(v1)) v := constant.BinaryOp(vConstantValue(v0), token.{{tokenFromName $name}}, vConstantValue(v1))
n.rval.Set(reflect.ValueOf(v)) n.rval.Set(reflect.ValueOf(v))
@@ -395,10 +407,10 @@ func {{$name}}Const(n *node) {
case isConst: case isConst:
v := constant.UnaryOp(token.{{tokenFromName $name}}, vConstantValue(v0), 0) v := constant.UnaryOp(token.{{tokenFromName $name}}, vConstantValue(v0), 0)
n.rval.Set(reflect.ValueOf(v)) n.rval.Set(reflect.ValueOf(v))
case isInt(t):
n.rval.SetInt({{$op.Name}} v0.Int())
case isUint(t): case isUint(t):
n.rval.SetUint({{$op.Name}} v0.Uint()) n.rval.SetUint({{$op.Name}} v0.Uint())
case isInt(t):
n.rval.SetInt({{$op.Name}} v0.Int())
{{- if $op.Float}} {{- if $op.Float}}
case isFloat(t): case isFloat(t):
n.rval.SetFloat({{$op.Name}} v0.Float()) n.rval.SetFloat({{$op.Name}} v0.Float())

View File

@@ -51,12 +51,14 @@ const (
fieldList fieldList
fileStmt fileStmt
forStmt0 // for {} forStmt0 // for {}
forStmt1 // for cond {} forStmt1 // for init; ; {}
forStmt2 // for init; cond; {} forStmt2 // for cond {}
forStmt3 // for ; cond; post {} forStmt3 // for init; cond; {}
forStmt3a // for init; ; post {} forStmt4 // for ; ; post {}
forStmt4 // for init; cond; post {} forStmt5 // for ; cond; post {}
forRangeStmt // for range forStmt6 // for init; ; post {}
forStmt7 // for init; cond; post {}
forRangeStmt // for range {}
funcDecl funcDecl
funcLit funcLit
funcType funcType
@@ -134,8 +136,10 @@ var kinds = [...]string{
forStmt1: "forStmt1", forStmt1: "forStmt1",
forStmt2: "forStmt2", forStmt2: "forStmt2",
forStmt3: "forStmt3", forStmt3: "forStmt3",
forStmt3a: "forStmt3a",
forStmt4: "forStmt4", forStmt4: "forStmt4",
forStmt5: "forStmt5",
forStmt6: "forStmt6",
forStmt7: "forStmt7",
forRangeStmt: "forRangeStmt", forRangeStmt: "forRangeStmt",
funcDecl: "funcDecl", funcDecl: "funcDecl",
funcType: "funcType", funcType: "funcType",
@@ -654,23 +658,23 @@ func (interp *Interpreter) ast(src, name string, inc bool) (string, *node, error
case *ast.ForStmt: case *ast.ForStmt:
// Disambiguate variants of FOR statements with a node kind per variant // Disambiguate variants of FOR statements with a node kind per variant
var kind nkind var kind nkind
if a.Cond == nil { switch {
if a.Init != nil && a.Post != nil { case a.Cond == nil && a.Init == nil && a.Post == nil:
kind = forStmt3a kind = forStmt0
} else { case a.Cond == nil && a.Init != nil && a.Post == nil:
kind = forStmt0 kind = forStmt1
} case a.Cond != nil && a.Init == nil && a.Post == nil:
} else { kind = forStmt2
switch { case a.Cond != nil && a.Init != nil && a.Post == nil:
case a.Init == nil && a.Post == nil: kind = forStmt3
kind = forStmt1 case a.Cond == nil && a.Init == nil && a.Post != nil:
case a.Init != nil && a.Post == nil: kind = forStmt4
kind = forStmt2 case a.Cond != nil && a.Init == nil && a.Post != nil:
case a.Init == nil && a.Post != nil: kind = forStmt5
kind = forStmt3 case a.Cond == nil && a.Init != nil && a.Post != nil:
default: kind = forStmt6
kind = forStmt4 case a.Cond != nil && a.Init != nil && a.Post != nil:
} kind = forStmt7
} }
st.push(addChild(&root, anc, pos, kind, aNop), nod) st.push(addChild(&root, anc, pos, kind, aNop), nod)

View File

@@ -66,6 +66,41 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
return false return false
} }
switch n.kind { switch n.kind {
case binaryExpr, unaryExpr, parenExpr:
if isBoolAction(n) {
break
}
// Gather assigned type if set, to give context for type propagation at post-order.
switch n.anc.kind {
case assignStmt, defineStmt:
a := n.anc
i := childPos(n) - a.nright
if len(a.child) > a.nright+a.nleft {
i--
}
dest := a.child[i]
if dest.typ != nil && !isInterface(dest.typ) {
// Interface type are not propagated, and will be resolved at post-order.
n.typ = dest.typ
}
case binaryExpr, unaryExpr, parenExpr:
n.typ = n.anc.typ
}
case defineStmt:
// Determine type of variables initialized at declaration, so it can be propagated.
if n.nleft+n.nright == len(n.child) {
// No type was specified on the left hand side, it will resolved at post-order.
break
}
n.typ, err = nodeType(interp, sc, n.child[n.nleft])
if err != nil {
break
}
for i := 0; i < n.nleft; i++ {
n.child[i].typ = n.typ
}
case blockStmt: case blockStmt:
if n.anc != nil && n.anc.kind == rangeStmt { if n.anc != nil && n.anc.kind == rangeStmt {
// For range block: ensure that array or map type is propagated to iterators // For range block: ensure that array or map type is propagated to iterators
@@ -285,11 +320,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
} }
} }
case forStmt0, forRangeStmt: case forStmt0, forStmt1, forStmt2, forStmt3, forStmt4, forStmt5, forStmt6, forStmt7, forRangeStmt:
sc = sc.pushBloc()
sc.loop, sc.loopRestart = n, n.child[0]
case forStmt1, forStmt2, forStmt3, forStmt3a, forStmt4:
sc = sc.pushBloc() sc = sc.pushBloc()
sc.loop, sc.loopRestart = n, n.lastChild() sc.loop, sc.loopRestart = n, n.lastChild()
@@ -443,14 +474,11 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
n.gen = nop n.gen = nop
break break
} }
if n.anc.kind == commClause {
n.gen = nop
break
}
var atyp *itype var atyp *itype
if n.nleft+n.nright < len(n.child) { if n.nleft+n.nright < len(n.child) {
if atyp, err = nodeType(interp, sc, n.child[n.nleft]); err != nil { if atyp, err = nodeType(interp, sc, n.child[n.nleft]); err != nil {
return break
} }
} }
@@ -554,6 +582,14 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
n.gen = nop n.gen = nop
src.findex = dest.findex src.findex = dest.findex
src.level = level src.level = level
case n.action == aAssign && len(n.child) < 4 && !src.rval.IsValid() && isArithmeticAction(src):
// Optimize single assignments from some arithmetic operations.
// Skip the assign operation entirely, the source frame index is set
// to destination index, avoiding extra memory alloc and duplication.
src.typ = dest.typ
src.findex = dest.findex
src.level = level
n.gen = nop
case src.kind == basicLit && !src.rval.IsValid(): case src.kind == basicLit && !src.rval.IsValid():
// Assign to nil. // Assign to nil.
src.rval = reflect.New(dest.typ.TypeOf()).Elem() src.rval = reflect.New(dest.typ.TypeOf()).Elem()
@@ -639,7 +675,12 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
} }
switch n.action { switch n.action {
case aRem, aShl, aShr: case aRem:
n.typ = c0.typ
case aShl, aShr:
if c0.typ.untyped {
break
}
n.typ = c0.typ n.typ = c0.typ
case aEqual, aNotEqual: case aEqual, aNotEqual:
n.typ = sc.getType("bool") n.typ = sc.getType("bool")
@@ -855,7 +896,12 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
n.gen = nop n.gen = nop
n.findex = -1 n.findex = -1
n.typ = c0.typ n.typ = c0.typ
n.rval = c1.rval if c, ok := c1.rval.Interface().(constant.Value); ok {
i, _ := constant.Int64Val(constant.ToInt(c))
n.rval = reflect.ValueOf(i).Convert(c0.typ.rtype)
} else {
n.rval = c1.rval.Convert(c0.typ.rtype)
}
default: default:
n.gen = convert n.gen = convert
n.typ = c0.typ n.typ = c0.typ
@@ -982,7 +1028,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
n.findex = sc.add(n.typ) n.findex = sc.add(n.typ)
// TODO: Check that composite literal expr matches corresponding type // TODO: Check that composite literal expr matches corresponding type
n.gen = compositeGenerator(n, n.typ) n.gen = compositeGenerator(n, n.typ, nil)
case fallthroughtStmt: case fallthroughtStmt:
if n.anc.kind != caseBody { if n.anc.kind != caseBody {
@@ -1000,7 +1046,14 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
body.tnext = n.start body.tnext = n.start
sc = sc.pop() sc = sc.pop()
case forStmt1: // for cond {} case forStmt1: // for init; ; {}
init, body := n.child[0], n.child[1]
n.start = init.start
init.tnext = body.start
body.tnext = n.start
sc = sc.pop()
case forStmt2: // for cond {}
cond, body := n.child[0], n.child[1] cond, body := n.child[0], n.child[1]
if !isBool(cond.typ) { if !isBool(cond.typ) {
err = cond.cfgErrorf("non-bool used as for condition") err = cond.cfgErrorf("non-bool used as for condition")
@@ -1019,7 +1072,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
setFNext(cond, n) setFNext(cond, n)
sc = sc.pop() sc = sc.pop()
case forStmt2: // for init; cond; {} case forStmt3: // for init; cond; {}
init, cond, body := n.child[0], n.child[1], n.child[2] init, cond, body := n.child[0], n.child[1], n.child[2]
if !isBool(cond.typ) { if !isBool(cond.typ) {
err = cond.cfgErrorf("non-bool used as for condition") err = cond.cfgErrorf("non-bool used as for condition")
@@ -1041,7 +1094,14 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
setFNext(cond, n) setFNext(cond, n)
sc = sc.pop() sc = sc.pop()
case forStmt3: // for ; cond; post {} case forStmt4: // for ; ; post {}
post, body := n.child[0], n.child[1]
n.start = body.start
post.tnext = body.start
body.tnext = post.start
sc = sc.pop()
case forStmt5: // for ; cond; post {}
cond, post, body := n.child[0], n.child[1], n.child[2] cond, post, body := n.child[0], n.child[1], n.child[2]
if !isBool(cond.typ) { if !isBool(cond.typ) {
err = cond.cfgErrorf("non-bool used as for condition") err = cond.cfgErrorf("non-bool used as for condition")
@@ -1061,7 +1121,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
body.tnext = post.start body.tnext = post.start
sc = sc.pop() sc = sc.pop()
case forStmt3a: // for init; ; post {} case forStmt6: // for init; ; post {}
init, post, body := n.child[0], n.child[1], n.child[2] init, post, body := n.child[0], n.child[1], n.child[2]
n.start = init.start n.start = init.start
init.tnext = body.start init.tnext = body.start
@@ -1069,7 +1129,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
post.tnext = body.start post.tnext = body.start
sc = sc.pop() sc = sc.pop()
case forStmt4: // for init; cond; post {} case forStmt7: // for init; cond; post {}
init, cond, post, body := n.child[0], n.child[1], n.child[2], n.child[3] init, cond, post, body := n.child[0], n.child[1], n.child[2], n.child[3]
if !isBool(cond.typ) { if !isBool(cond.typ) {
err = cond.cfgErrorf("non-bool used as for condition") err = cond.cfgErrorf("non-bool used as for condition")
@@ -1129,7 +1189,6 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
sym, level, found := sc.lookup(n.ident) sym, level, found := sc.lookup(n.ident)
if !found { if !found {
// retry with the filename, in case ident is a package name. // retry with the filename, in case ident is a package name.
// TODO(mpl): maybe we improve lookup itself so it can deal with that.
sym, level, found = sc.lookup(filepath.Join(n.ident, baseName)) sym, level, found = sc.lookup(filepath.Join(n.ident, baseName))
if !found { if !found {
err = n.cfgErrorf("undefined: %s", n.ident) err = n.cfgErrorf("undefined: %s", n.ident)
@@ -1302,7 +1361,12 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
} }
case returnStmt: case returnStmt:
if mustReturnValue(sc.def.child[2]) { if len(n.child) > sc.def.typ.numOut() {
err = n.cfgErrorf("too many arguments to return")
break
}
returnSig := sc.def.child[2]
if mustReturnValue(returnSig) {
nret := len(n.child) nret := len(n.child)
if nret == 1 && isCall(n.child[0]) { if nret == 1 && isCall(n.child[0]) {
nret = n.child[0].child[0].typ.numOut() nret = n.child[0].child[0].typ.numOut()
@@ -1316,13 +1380,19 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
n.tnext = nil n.tnext = nil
n.val = sc.def n.val = sc.def
for i, c := range n.child { for i, c := range n.child {
var typ *itype
typ, err = nodeType(interp, sc.upperLevel(), returnSig.child[1].fieldType(i))
if err != nil {
return
}
// TODO(mpl): move any of that code to typecheck?
c.typ.node = c
if !c.typ.assignableTo(typ) {
err = fmt.Errorf("cannot use %v (type %v) as type %v in return argument", c.ident, c.typ.cat, typ.cat)
return
}
if c.typ.cat == nilT { if c.typ.cat == nilT {
// nil: Set node value to zero of return type // nil: Set node value to zero of return type
f := sc.def
var typ *itype
if typ, err = nodeType(interp, sc, f.child[2].child[1].fieldType(i)); err != nil {
return
}
if typ.cat == funcT { if typ.cat == funcT {
// Wrap the typed nil value in a node, as per other interpreter functions // Wrap the typed nil value in a node, as per other interpreter functions
c.rval = reflect.ValueOf(&node{kind: basicLit, rval: reflect.New(typ.TypeOf()).Elem()}) c.rval = reflect.ValueOf(&node{kind: basicLit, rval: reflect.New(typ.TypeOf()).Elem()})
@@ -2360,10 +2430,10 @@ func gotoLabel(s *symbol) {
} }
} }
func compositeGenerator(n *node, typ *itype) (gen bltnGenerator) { func compositeGenerator(n *node, typ *itype, rtyp reflect.Type) (gen bltnGenerator) {
switch typ.cat { switch typ.cat {
case aliasT, ptrT: case aliasT, ptrT:
gen = compositeGenerator(n, n.typ.val) gen = compositeGenerator(n, n.typ.val, rtyp)
case arrayT: case arrayT:
gen = arrayLit gen = arrayLit
case mapT: case mapT:
@@ -2386,11 +2456,21 @@ func compositeGenerator(n *node, typ *itype) (gen bltnGenerator) {
} }
} }
case valueT: case valueT:
switch k := n.typ.rtype.Kind(); k { if rtyp == nil {
rtyp = n.typ.rtype
}
switch k := rtyp.Kind(); k {
case reflect.Struct: case reflect.Struct:
gen = compositeBinStruct if n.nleft == 1 {
gen = compositeBinStruct
} else {
gen = compositeBinStructNotype
}
case reflect.Map: case reflect.Map:
// TODO(mpl): maybe needs a NoType version too
gen = compositeBinMap gen = compositeBinMap
case reflect.Ptr:
gen = compositeGenerator(n, typ, n.typ.val.rtype)
default: default:
log.Panic(n.cfgErrorf("compositeGenerator not implemented for type kind: %s", k)) log.Panic(n.cfgErrorf("compositeGenerator not implemented for type kind: %s", k))
} }
@@ -2432,3 +2512,22 @@ func isValueUntyped(v reflect.Value) bool {
} }
return t.String() == t.Kind().String() return t.String() == t.Kind().String()
} }
// isArithmeticAction returns true if the node action is an arithmetic operator.
func isArithmeticAction(n *node) bool {
switch n.action {
case aAdd, aAnd, aAndNot, aBitNot, aMul, aQuo, aRem, aShl, aShr, aSub, aXor:
return true
default:
return false
}
}
func isBoolAction(n *node) bool {
switch n.action {
case aEqual, aGreater, aGreaterEqual, aLand, aLor, aLower, aLowerEqual, aNot, aNotEqual:
return true
default:
return false
}
}

View File

@@ -145,7 +145,7 @@ func (interp *Interpreter) gta(root *node, rpath, importPath string) ([]*node, e
elementType := sc.getType(typeName) elementType := sc.getType(typeName)
if elementType == nil { if elementType == nil {
// Add type if necessary, so method can be registered // Add type if necessary, so method can be registered
sc.sym[typeName] = &symbol{kind: typeSym, typ: &itype{name: typeName, path: rpath, incomplete: true, node: rtn.child[0], scope: sc}} sc.sym[typeName] = &symbol{kind: typeSym, typ: &itype{name: typeName, path: importPath, incomplete: true, node: rtn.child[0], scope: sc}}
elementType = sc.sym[typeName].typ elementType = sc.sym[typeName].typ
} }
rcvrtype = &itype{cat: ptrT, val: elementType, incomplete: elementType.incomplete, node: rtn, scope: sc} rcvrtype = &itype{cat: ptrT, val: elementType, incomplete: elementType.incomplete, node: rtn, scope: sc}
@@ -154,7 +154,7 @@ func (interp *Interpreter) gta(root *node, rpath, importPath string) ([]*node, e
rcvrtype = sc.getType(typeName) rcvrtype = sc.getType(typeName)
if rcvrtype == nil { if rcvrtype == nil {
// Add type if necessary, so method can be registered // Add type if necessary, so method can be registered
sc.sym[typeName] = &symbol{kind: typeSym, typ: &itype{name: typeName, path: rpath, incomplete: true, node: rtn, scope: sc}} sc.sym[typeName] = &symbol{kind: typeSym, typ: &itype{name: typeName, path: importPath, incomplete: true, node: rtn, scope: sc}}
rcvrtype = sc.sym[typeName].typ rcvrtype = sc.sym[typeName].typ
} }
} }
@@ -248,16 +248,18 @@ func (interp *Interpreter) gta(root *node, rpath, importPath string) ([]*node, e
typeName := n.child[0].ident typeName := n.child[0].ident
var typ *itype var typ *itype
if typ, err = nodeType(interp, sc, n.child[1]); err != nil { if typ, err = nodeType(interp, sc, n.child[1]); err != nil {
err = nil
revisit = append(revisit, n)
return false return false
} }
if n.child[1].kind == identExpr { if n.child[1].kind == identExpr {
n.typ = &itype{cat: aliasT, val: typ, name: typeName, path: rpath, field: typ.field, incomplete: typ.incomplete, scope: sc, node: n.child[0]} n.typ = &itype{cat: aliasT, val: typ, name: typeName, path: importPath, field: typ.field, incomplete: typ.incomplete, scope: sc, node: n.child[0]}
copy(n.typ.method, typ.method) copy(n.typ.method, typ.method)
} else { } else {
n.typ = typ n.typ = typ
n.typ.name = typeName n.typ.name = typeName
n.typ.path = rpath n.typ.path = importPath
} }
asImportName := filepath.Join(typeName, baseName) asImportName := filepath.Join(typeName, baseName)

View File

@@ -2,8 +2,6 @@ package interp
import "reflect" import "reflect"
const hooksPath = "github.com/traefik/yaegi"
// convertFn is the signature of a symbol converter. // convertFn is the signature of a symbol converter.
type convertFn func(from, to reflect.Type) func(src, dest reflect.Value) type convertFn func(from, to reflect.Type) func(src, dest reflect.Value)

View File

@@ -155,8 +155,9 @@ type Interpreter struct {
} }
const ( const (
mainID = "main" mainID = "main"
selfPath = "github.com/traefik/yaegi/interp" selfPrefix = "github.com/traefik/yaegi"
selfPath = selfPrefix + "/interp"
// DefaultSourceName is the name used by default when the name of the input // DefaultSourceName is the name used by default when the name of the input
// source file has not been specified for an Eval. // source file has not been specified for an Eval.
// TODO(mpl): something even more special as a name? // TODO(mpl): something even more special as a name?
@@ -175,6 +176,7 @@ var Symbols = Exports{
"Interpreter": reflect.ValueOf((*Interpreter)(nil)), "Interpreter": reflect.ValueOf((*Interpreter)(nil)),
"Options": reflect.ValueOf((*Options)(nil)), "Options": reflect.ValueOf((*Options)(nil)),
"Panic": reflect.ValueOf((*Panic)(nil)),
}, },
} }
@@ -401,32 +403,59 @@ func (interp *Interpreter) EvalTest(path string) error {
return err return err
} }
// Symbols returns a map of interpreter exported symbol values for the given path. // Symbols returns a map of interpreter exported symbol values for the given
func (interp *Interpreter) Symbols(path string) map[string]reflect.Value { // import path. If the argument is the empty string, all known symbols are
m := map[string]reflect.Value{} // returned.
func (interp *Interpreter) Symbols(importPath string) Exports {
m := map[string]map[string]reflect.Value{}
interp.mutex.RLock() interp.mutex.RLock()
if interp.scopes[path] == nil { defer interp.mutex.RUnlock()
interp.mutex.RUnlock()
return m
}
sym := interp.scopes[path].sym
interp.mutex.RUnlock()
for n, s := range sym { for k, v := range interp.srcPkg {
if !canExport(n) { if importPath != "" && k != importPath {
// Skip private non-exported symbols.
continue continue
} }
switch s.kind { syms := map[string]reflect.Value{}
case constSym: for n, s := range v {
m[n] = s.rval if !canExport(n) {
case funcSym: // Skip private non-exported symbols.
m[n] = genFunctionWrapper(s.node)(interp.frame) continue
case varSym: }
m[n] = interp.frame.data[s.index] switch s.kind {
case constSym:
syms[n] = s.rval
case funcSym:
syms[n] = genFunctionWrapper(s.node)(interp.frame)
case varSym:
syms[n] = interp.frame.data[s.index]
case typeSym:
syms[n] = reflect.New(s.typ.TypeOf())
}
}
if len(syms) > 0 {
m[k] = syms
}
if importPath != "" {
return m
} }
} }
if importPath != "" && len(m) > 0 {
return m
}
for k, v := range interp.binPkg {
if importPath != "" && k != importPath {
continue
}
m[k] = v
if importPath != "" {
return m
}
}
return m return m
} }
@@ -603,7 +632,7 @@ func (interp *Interpreter) getWrapper(t reflect.Type) reflect.Type {
// they can be used in interpreted code. // they can be used in interpreted code.
func (interp *Interpreter) Use(values Exports) { func (interp *Interpreter) Use(values Exports) {
for k, v := range values { for k, v := range values {
if k == hooksPath { if k == selfPrefix {
interp.hooks.Parse(v) interp.hooks.Parse(v)
continue continue
} }
@@ -785,20 +814,28 @@ func (interp *Interpreter) REPL() (reflect.Value, error) {
} }
} }
func doPrompt(out io.Writer) func(v reflect.Value) {
return func(v reflect.Value) {
if v.IsValid() {
fmt.Fprintln(out, ":", v)
}
fmt.Fprint(out, "> ")
}
}
// getPrompt returns a function which prints a prompt only if input is a terminal. // getPrompt returns a function which prints a prompt only if input is a terminal.
func getPrompt(in io.Reader, out io.Writer) func(reflect.Value) { func getPrompt(in io.Reader, out io.Writer) func(reflect.Value) {
forcePrompt, _ := strconv.ParseBool(os.Getenv("YAEGI_PROMPT"))
if forcePrompt {
return doPrompt(out)
}
s, ok := in.(interface{ Stat() (os.FileInfo, error) }) s, ok := in.(interface{ Stat() (os.FileInfo, error) })
if !ok { if !ok {
return func(reflect.Value) {} return func(reflect.Value) {}
} }
stat, err := s.Stat() stat, err := s.Stat()
if err == nil && stat.Mode()&os.ModeCharDevice != 0 { if err == nil && stat.Mode()&os.ModeCharDevice != 0 {
return func(v reflect.Value) { return doPrompt(out)
if v.IsValid() {
fmt.Fprintln(out, ":", v)
}
fmt.Fprint(out, "> ")
}
} }
return func(reflect.Value) {} return func(reflect.Value) {}
} }

View File

@@ -43,6 +43,9 @@ func TestInterpConsistencyBuild(t *testing.T) {
file.Name() == "for7.go" || // expect error file.Name() == "for7.go" || // expect error
file.Name() == "fun21.go" || // expect error file.Name() == "fun21.go" || // expect error
file.Name() == "fun22.go" || // expect error file.Name() == "fun22.go" || // expect error
file.Name() == "fun23.go" || // expect error
file.Name() == "fun24.go" || // expect error
file.Name() == "fun25.go" || // expect error
file.Name() == "if2.go" || // expect error file.Name() == "if2.go" || // expect error
file.Name() == "import6.go" || // expect error file.Name() == "import6.go" || // expect error
file.Name() == "init1.go" || // expect error file.Name() == "init1.go" || // expect error
@@ -201,6 +204,11 @@ func TestInterpErrorConsistency(t *testing.T) {
expectedInterp: "6:2: not enough arguments in call to time.Date", expectedInterp: "6:2: not enough arguments in call to time.Date",
expectedExec: "6:11: not enough arguments in call to time.Date", expectedExec: "6:11: not enough arguments in call to time.Date",
}, },
{
fileName: "fun23.go",
expectedInterp: "3:17: too many arguments to return",
expectedExec: "3:17: too many arguments to return",
},
{ {
fileName: "op1.go", fileName: "op1.go",
expectedInterp: "5:2: invalid operation: mismatched types int and float64", expectedInterp: "5:2: invalid operation: mismatched types int and float64",

View File

@@ -71,6 +71,27 @@ func TestEvalArithmetic(t *testing.T) {
}) })
} }
func TestEvalShift(t *testing.T) {
i := interp.New(interp.Options{})
runTests(t, i, []testCase{
{src: "a, b, m := uint32(1), uint32(2), uint32(0); m = a + (1 << b)", res: "5"},
{src: "c := uint(1); d := uint(+(-(1 << c)))", res: "18446744073709551614"},
{src: "e, f := uint32(0), uint32(0); f = 1 << -(e * 2)", res: "1"},
{src: "p := uint(0xdead); byte((1 << (p & 7)) - 1)", res: "31"},
{pre: func() { eval(t, i, "const k uint = 1 << 17") }, src: "int(k)", res: "131072"},
})
}
func TestOpVarConst(t *testing.T) {
i := interp.New(interp.Options{})
runTests(t, i, []testCase{
{pre: func() { eval(t, i, "const a uint = 8 + 2") }, src: "a", res: "10"},
{src: "b := uint(5); a+b", res: "15"},
{src: "b := uint(5); b+a", res: "15"},
{src: "b := uint(5); b>a", res: "false"},
})
}
func TestEvalStar(t *testing.T) { func TestEvalStar(t *testing.T) {
i := interp.New(interp.Options{}) i := interp.New(interp.Options{})
runTests(t, i, []testCase{ runTests(t, i, []testCase{
@@ -986,6 +1007,9 @@ func TestConcurrentEvals(t *testing.T) {
// called by EvalWithContext is sequential. And that there is no data race for the // called by EvalWithContext is sequential. And that there is no data race for the
// interp package global vars or the interpreter fields in this case. // interp package global vars or the interpreter fields in this case.
func TestConcurrentEvals2(t *testing.T) { func TestConcurrentEvals2(t *testing.T) {
if testing.Short() {
return
}
pin, pout := io.Pipe() pin, pout := io.Pipe()
defer func() { defer func() {
_ = pin.Close() _ = pin.Close()
@@ -1045,6 +1069,9 @@ func TestConcurrentEvals2(t *testing.T) {
// - when calling Interpreter.Use, the symbols given as argument should be // - when calling Interpreter.Use, the symbols given as argument should be
// copied when being inserted into interp.binPkg, and not directly used as-is. // copied when being inserted into interp.binPkg, and not directly used as-is.
func TestConcurrentEvals3(t *testing.T) { func TestConcurrentEvals3(t *testing.T) {
if testing.Short() {
return
}
allDone := make(chan bool) allDone := make(chan bool)
runREPL := func() { runREPL := func() {
done := make(chan error) done := make(chan error)
@@ -1123,6 +1150,9 @@ func TestConcurrentComposite2(t *testing.T) {
} }
func testConcurrentComposite(t *testing.T, filePath string) { func testConcurrentComposite(t *testing.T, filePath string) {
if testing.Short() {
return
}
pin, pout := io.Pipe() pin, pout := io.Pipe()
i := interp.New(interp.Options{Stdout: pout}) i := interp.New(interp.Options{Stdout: pout})
i.Use(stdlib.Symbols) i.Use(stdlib.Symbols)
@@ -1160,6 +1190,9 @@ func testConcurrentComposite(t *testing.T, filePath string) {
} }
func TestEvalScanner(t *testing.T) { func TestEvalScanner(t *testing.T) {
if testing.Short() {
return
}
type testCase struct { type testCase struct {
desc string desc string
src []string src []string
@@ -1331,3 +1364,94 @@ func applyCIMultiplier(timeout time.Duration) time.Duration {
} }
return time.Duration(float64(timeout) * CITimeoutMultiplier) return time.Duration(float64(timeout) * CITimeoutMultiplier)
} }
func TestREPLDivision(t *testing.T) {
if testing.Short() {
return
}
_ = os.Setenv("YAEGI_PROMPT", "1")
defer func() {
_ = os.Setenv("YAEGI_PROMPT", "0")
}()
allDone := make(chan bool)
runREPL := func() {
done := make(chan error)
pinin, poutin := io.Pipe()
pinout, poutout := io.Pipe()
i := interp.New(interp.Options{Stdin: pinin, Stdout: poutout})
i.Use(stdlib.Symbols)
go func() {
_, _ = i.REPL()
}()
defer func() {
_ = pinin.Close()
_ = poutin.Close()
_ = pinout.Close()
_ = poutout.Close()
allDone <- true
}()
input := []string{
`1/1`,
`7/3`,
`16/5`,
`3./2`, // float
}
output := []string{
`1`,
`2`,
`3`,
`1.5`,
}
go func() {
sc := bufio.NewScanner(pinout)
k := 0
for sc.Scan() {
l := sc.Text()
if l != "> : "+output[k] {
done <- fmt.Errorf("unexpected output, want %q, got %q", output[k], l)
return
}
k++
if k > 3 {
break
}
}
done <- nil
}()
for _, v := range input {
in := strings.NewReader(v + "\n")
if _, err := io.Copy(poutin, in); err != nil {
t.Fatal(err)
}
select {
case err := <-done:
if err != nil {
t.Fatal(err)
}
return
default:
time.Sleep(time.Second)
}
}
if err := <-done; err != nil {
t.Fatal(err)
}
}
go func() {
runREPL()
}()
timeout := time.NewTimer(10 * time.Second)
select {
case <-allDone:
case <-timeout.C:
t.Fatal("timeout")
}
}

View File

@@ -701,7 +701,16 @@ func quoConst(n *node) {
n.rval = reflect.New(t).Elem() n.rval = reflect.New(t).Elem()
switch { switch {
case isConst: case isConst:
v := constant.BinaryOp(vConstantValue(v0), token.QUO, vConstantValue(v1)) var operator token.Token
// When the result of the operation is expected to be an int (because both
// operands are ints), we want to force the type of the whole expression to be an
// int (and not a float), which is achieved by using the QUO_ASSIGN operator.
if n.typ.untyped && isInt(n.typ.rtype) {
operator = token.QUO_ASSIGN
} else {
operator = token.QUO
}
v := constant.BinaryOp(vConstantValue(v0), operator, vConstantValue(v1))
n.rval.Set(reflect.ValueOf(v)) n.rval.Set(reflect.ValueOf(v))
case isComplex(t): case isComplex(t):
n.rval.SetComplex(vComplex(v0) / vComplex(v1)) n.rval.SetComplex(vComplex(v0) / vComplex(v1))
@@ -1957,10 +1966,10 @@ func bitNotConst(n *node) {
case isConst: case isConst:
v := constant.UnaryOp(token.XOR, vConstantValue(v0), 0) v := constant.UnaryOp(token.XOR, vConstantValue(v0), 0)
n.rval.Set(reflect.ValueOf(v)) n.rval.Set(reflect.ValueOf(v))
case isInt(t):
n.rval.SetInt(^v0.Int())
case isUint(t): case isUint(t):
n.rval.SetUint(^v0.Uint()) n.rval.SetUint(^v0.Uint())
case isInt(t):
n.rval.SetInt(^v0.Int())
} }
} }
@@ -1976,10 +1985,10 @@ func negConst(n *node) {
case isConst: case isConst:
v := constant.UnaryOp(token.SUB, vConstantValue(v0), 0) v := constant.UnaryOp(token.SUB, vConstantValue(v0), 0)
n.rval.Set(reflect.ValueOf(v)) n.rval.Set(reflect.ValueOf(v))
case isInt(t):
n.rval.SetInt(-v0.Int())
case isUint(t): case isUint(t):
n.rval.SetUint(-v0.Uint()) n.rval.SetUint(-v0.Uint())
case isInt(t):
n.rval.SetInt(-v0.Int())
case isFloat(t): case isFloat(t):
n.rval.SetFloat(-v0.Float()) n.rval.SetFloat(-v0.Float())
case isComplex(t): case isComplex(t):
@@ -2015,10 +2024,10 @@ func posConst(n *node) {
case isConst: case isConst:
v := constant.UnaryOp(token.ADD, vConstantValue(v0), 0) v := constant.UnaryOp(token.ADD, vConstantValue(v0), 0)
n.rval.Set(reflect.ValueOf(v)) n.rval.Set(reflect.ValueOf(v))
case isInt(t):
n.rval.SetInt(+v0.Int())
case isUint(t): case isUint(t):
n.rval.SetUint(+v0.Uint()) n.rval.SetUint(+v0.Uint())
case isInt(t):
n.rval.SetInt(+v0.Int())
case isFloat(t): case isFloat(t):
n.rval.SetFloat(+v0.Float()) n.rval.SetFloat(+v0.Float())
case isComplex(t): case isComplex(t):

View File

@@ -108,10 +108,53 @@ func (interp *Interpreter) run(n *node, cf *frame) {
runCfg(n.start, f) runCfg(n.start, f)
} }
// originalExecNode looks in the tree of nodes for the node which has exec,
// aside from n, in order to know where n "inherited" that exec from.
func originalExecNode(n *node, exec bltn) *node {
execAddr := reflect.ValueOf(exec).Pointer()
var originalNode *node
seen := make(map[int64]struct{})
root := n
for {
root = root.anc
if root == nil {
break
}
if _, ok := seen[root.index]; ok {
continue
}
root.Walk(func(wn *node) bool {
if _, ok := seen[wn.index]; ok {
return true
}
seen[wn.index] = struct{}{}
if wn.index == n.index {
return true
}
if wn.exec == nil {
return true
}
if reflect.ValueOf(wn.exec).Pointer() == execAddr {
originalNode = wn
return false
}
return true
}, nil)
if originalNode != nil {
break
}
}
return originalNode
}
// Functions set to run during execution of CFG. // Functions set to run during execution of CFG.
// runCfg executes a node AST by walking its CFG and running node builtin at each step. // runCfg executes a node AST by walking its CFG and running node builtin at each step.
func runCfg(n *node, f *frame) { func runCfg(n *node, f *frame) {
var exec bltn
defer func() { defer func() {
f.mutex.Lock() f.mutex.Lock()
f.recovered = recover() f.recovered = recover()
@@ -119,14 +162,18 @@ func runCfg(n *node, f *frame) {
val[0].Call(val[1:]) val[0].Call(val[1:])
} }
if f.recovered != nil { if f.recovered != nil {
fmt.Println(n.cfgErrorf("panic")) oNode := originalExecNode(n, exec)
if oNode == nil {
oNode = n
}
fmt.Println(oNode.cfgErrorf("panic"))
f.mutex.Unlock() f.mutex.Unlock()
panic(f.recovered) panic(f.recovered)
} }
f.mutex.Unlock() f.mutex.Unlock()
}() }()
for exec := n.exec; exec != nil && f.runid() == n.interp.runid(); { for exec = n.exec; exec != nil && f.runid() == n.interp.runid(); {
exec = exec(f) exec = exec(f)
} }
} }
@@ -343,10 +390,30 @@ func convert(n *node) {
return return
} }
if n.child[0].typ.cat == funcT && c.typ.cat == funcT {
value := genValue(c)
n.exec = func(f *frame) bltn {
n, ok := value(f).Interface().(*node)
if !ok || !n.typ.convertibleTo(c.typ) {
panic("cannot convert")
}
n1 := *n
n1.typ = c.typ
dest(f).Set(reflect.ValueOf(&n1))
return next
}
return
}
doConvert := true
var value func(*frame) reflect.Value var value func(*frame) reflect.Value
if c.typ.cat == funcT { switch {
case c.typ.cat == funcT:
value = genFunctionWrapper(c) value = genFunctionWrapper(c)
} else { case n.child[0].typ.cat == funcT && c.typ.cat == valueT:
doConvert = false
value = genValueNode(c)
default:
value = genValue(c) value = genValue(c)
} }
@@ -367,7 +434,11 @@ func convert(n *node) {
} }
n.exec = func(f *frame) bltn { n.exec = func(f *frame) bltn {
dest(f).Set(value(f).Convert(typ)) if doConvert {
dest(f).Set(value(f).Convert(typ))
} else {
dest(f).Set(value(f))
}
return next return next
} }
} }
@@ -1116,6 +1187,7 @@ func callBin(n *node) {
c.val = reflect.Zero(argType) c.val = reflect.Zero(argType)
} }
} }
switch c.typ.cat { switch c.typ.cat {
case funcT: case funcT:
values = append(values, genFunctionWrapper(c)) values = append(values, genFunctionWrapper(c))
@@ -1128,6 +1200,14 @@ func callBin(n *node) {
default: default:
values = append(values, genInterfaceWrapper(c, defType)) values = append(values, genInterfaceWrapper(c, defType))
} }
case ptrT:
if c.typ.val.cat == valueT {
values = append(values, genValue(c))
} else {
values = append(values, genInterfaceWrapper(c, defType))
}
case valueT:
values = append(values, genValue(c))
default: default:
values = append(values, genInterfaceWrapper(c, defType)) values = append(values, genInterfaceWrapper(c, defType))
} }
@@ -1719,6 +1799,11 @@ func neg(n *node) {
dest(f).SetInt(-value(f).Int()) dest(f).SetInt(-value(f).Int())
return next return next
} }
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
n.exec = func(f *frame) bltn {
dest(f).SetUint(-value(f).Uint())
return next
}
case reflect.Float32, reflect.Float64: case reflect.Float32, reflect.Float64:
n.exec = func(f *frame) bltn { n.exec = func(f *frame) bltn {
dest(f).SetFloat(-value(f).Float()) dest(f).SetFloat(-value(f).Float())
@@ -1869,7 +1954,10 @@ func _return(n *node) {
case 0: case 0:
n.exec = nil n.exec = nil
case 1: case 1:
if child[0].kind == binaryExpr || isCall(child[0]) { // This is an optimisation that is applied for binary expressions or function
// calls, but not for (binary) expressions involving const, as the values are not
// stored in the frame in that case.
if !child[0].rval.IsValid() && child[0].kind == binaryExpr || isCall(child[0]) {
n.exec = nil n.exec = nil
} else { } else {
v := values[0] v := values[0]
@@ -2010,12 +2098,18 @@ func compositeBinMap(n *node) {
} }
} }
// compositeBinStruct creates and populates a struct object from a binary type. // doCompositeBinStruct creates and populates a struct object from a binary type.
func compositeBinStruct(n *node) { func doCompositeBinStruct(n *node, hasType bool) {
next := getExec(n.tnext) next := getExec(n.tnext)
value := valueGenerator(n, n.findex) value := valueGenerator(n, n.findex)
typ := n.typ.rtype typ := n.typ.rtype
child := n.child[1:] if n.typ.cat == ptrT || n.typ.cat == aliasT {
typ = n.typ.val.rtype
}
child := n.child
if hasType {
child = n.child[1:]
}
values := make([]func(*frame) reflect.Value, len(child)) values := make([]func(*frame) reflect.Value, len(child))
fieldIndex := make([][]int, len(child)) fieldIndex := make([][]int, len(child))
for i, c := range child { for i, c := range child {
@@ -2031,7 +2125,7 @@ func compositeBinStruct(n *node) {
} }
} else { } else {
fieldIndex[i] = []int{i} fieldIndex[i] = []int{i}
if c.typ.cat == funcT { if c.typ.cat == funcT && len(c.child) > 1 {
convertLiteralValue(c.child[1], typ.Field(i).Type) convertLiteralValue(c.child[1], typ.Field(i).Type)
values[i] = genFunctionWrapper(c.child[1]) values[i] = genFunctionWrapper(c.child[1])
} else { } else {
@@ -2046,11 +2140,20 @@ func compositeBinStruct(n *node) {
for i, v := range values { for i, v := range values {
s.FieldByIndex(fieldIndex[i]).Set(v(f)) s.FieldByIndex(fieldIndex[i]).Set(v(f))
} }
value(f).Set(s) d := value(f)
switch {
case d.Type().Kind() == reflect.Ptr:
d.Set(s.Addr())
default:
d.Set(s)
}
return next return next
} }
} }
func compositeBinStruct(n *node) { doCompositeBinStruct(n, true) }
func compositeBinStructNotype(n *node) { doCompositeBinStruct(n, false) }
func destType(n *node) *itype { func destType(n *node) *itype {
switch n.anc.kind { switch n.anc.kind {
case assignStmt, defineStmt: case assignStmt, defineStmt:
@@ -2086,16 +2189,20 @@ func doComposite(n *node, hasType bool, keyed bool) {
val = c val = c
fieldIndex = i fieldIndex = i
} }
convertLiteralValue(val, typ.field[fieldIndex].typ.TypeOf()) ft := typ.field[fieldIndex].typ
rft := ft.TypeOf()
convertLiteralValue(val, rft)
switch { switch {
case val.typ.cat == nilT:
values[fieldIndex] = func(*frame) reflect.Value { return reflect.New(rft).Elem() }
case val.typ.cat == funcT: case val.typ.cat == funcT:
values[fieldIndex] = genFunctionWrapper(val) values[fieldIndex] = genFunctionWrapper(val)
case isArray(val.typ) && val.typ.val != nil && val.typ.val.cat == interfaceT: case isArray(val.typ) && val.typ.val != nil && val.typ.val.cat == interfaceT:
values[fieldIndex] = genValueInterfaceArray(val) values[fieldIndex] = genValueInterfaceArray(val)
case isRecursiveType(typ.field[fieldIndex].typ, typ.field[fieldIndex].typ.rtype): case isRecursiveType(ft, rft):
values[fieldIndex] = genValueRecursiveInterface(val, typ.field[fieldIndex].typ.rtype) values[fieldIndex] = genValueRecursiveInterface(val, rft)
case isInterface(typ.field[fieldIndex].typ): case isInterface(ft):
values[fieldIndex] = genInterfaceWrapper(val, typ.field[fieldIndex].typ.rtype) values[fieldIndex] = genInterfaceWrapper(val, rft)
default: default:
values[fieldIndex] = genValue(val) values[fieldIndex] = genValue(val)
} }
@@ -2412,11 +2519,16 @@ func appendSlice(n *node) {
} }
func _append(n *node) { func _append(n *node) {
if c1, c2 := n.child[1], n.child[2]; len(n.child) == 3 && c2.typ.cat == arrayT && c2.typ.val.id() == n.typ.val.id() || if len(n.child) == 3 {
isByteArray(c1.typ.TypeOf()) && isString(c2.typ.TypeOf()) { c1, c2 := n.child[1], n.child[2]
appendSlice(n) if (c1.typ.cat == valueT || c2.typ.cat == valueT) && c1.typ.rtype == c2.typ.rtype ||
return c2.typ.cat == arrayT && c2.typ.val.id() == n.typ.val.id() ||
isByteArray(c1.typ.TypeOf()) && isString(c2.typ.TypeOf()) {
appendSlice(n)
return
}
} }
dest := genValueOutput(n, n.typ.rtype) dest := genValueOutput(n, n.typ.rtype)
value := genValue(n.child[1]) value := genValue(n.child[1])
next := getExec(n.tnext) next := getExec(n.tnext)
@@ -2813,65 +2925,29 @@ func convertConstantValue(n *node) {
return return
} }
v := n.rval var v reflect.Value
typ := n.typ.TypeOf()
kind := typ.Kind() switch c.Kind() {
switch kind { case constant.Bool:
case reflect.Bool: v = reflect.ValueOf(constant.BoolVal(c))
v = reflect.ValueOf(constant.BoolVal(c)).Convert(typ) case constant.String:
case reflect.String: v = reflect.ValueOf(constant.StringVal(c))
v = reflect.ValueOf(constant.StringVal(c)).Convert(typ) case constant.Int:
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: i, x := constant.Int64Val(c)
i, _ := constant.Int64Val(c) if !x {
l := constant.BitLen(c) panic(fmt.Sprintf("constant %s overflows int64", c.ExactString()))
if l > bitlen[kind] {
panic(fmt.Sprintf("constant %s overflows int%d", c.ExactString(), bitlen[kind]))
} }
v = reflect.ValueOf(i).Convert(typ) v = reflect.ValueOf(int(i))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: case constant.Float:
i, _ := constant.Uint64Val(c)
l := constant.BitLen(c)
if l > bitlen[kind] {
panic(fmt.Sprintf("constant %s overflows uint%d", c.ExactString(), bitlen[kind]))
}
v = reflect.ValueOf(i).Convert(typ)
case reflect.Float32:
f, _ := constant.Float32Val(c)
v = reflect.ValueOf(f).Convert(typ)
case reflect.Float64:
f, _ := constant.Float64Val(c) f, _ := constant.Float64Val(c)
v = reflect.ValueOf(f).Convert(typ) v = reflect.ValueOf(f)
case reflect.Complex64: case constant.Complex:
r, _ := constant.Float32Val(constant.Real(c))
i, _ := constant.Float32Val(constant.Imag(c))
v = reflect.ValueOf(complex(r, i)).Convert(typ)
case reflect.Complex128:
r, _ := constant.Float64Val(constant.Real(c)) r, _ := constant.Float64Val(constant.Real(c))
i, _ := constant.Float64Val(constant.Imag(c)) i, _ := constant.Float64Val(constant.Imag(c))
v = reflect.ValueOf(complex(r, i)).Convert(typ) v = reflect.ValueOf(complex(r, i))
default:
// Type kind is from internal constant representation. Only use default types here.
switch c.Kind() {
case constant.Bool:
v = reflect.ValueOf(constant.BoolVal(c))
case constant.String:
v = reflect.ValueOf(constant.StringVal(c))
case constant.Int:
i, x := constant.Int64Val(c)
if !x {
panic(fmt.Sprintf("constant %s overflows int64", c.ExactString()))
}
v = reflect.ValueOf(int(i))
case constant.Float:
f, _ := constant.Float64Val(c)
v = reflect.ValueOf(f)
case constant.Complex:
r, _ := constant.Float64Val(constant.Real(c))
i, _ := constant.Float64Val(constant.Imag(c))
v = reflect.ValueOf(complex(r, i))
}
} }
n.rval = v
n.rval = v.Convert(n.typ.TypeOf())
} }
// Write to a channel. // Write to a channel.
@@ -2951,37 +3027,40 @@ func _select(n *node) {
next := getExec(n.tnext) next := getExec(n.tnext)
for i := 0; i < nbClause; i++ { for i := 0; i < nbClause; i++ {
if len(n.child[i].child) == 0 { cl := n.child[i]
// The comm clause is an empty default, exit select. if cl.kind == commClauseDefault {
cases[i].Dir = reflect.SelectDefault cases[i].Dir = reflect.SelectDefault
clause[i] = func(*frame) bltn { return next } if len(cl.child) == 0 {
} else { clause[i] = func(*frame) bltn { return next }
switch c0 := n.child[i].child[0]; { } else {
case len(n.child[i].child) > 1: clause[i] = getExec(cl.child[0].start)
// The comm clause contains a channel operation and a clause body.
clause[i] = getExec(n.child[i].child[1].start)
chans[i], assigned[i], ok[i], cases[i].Dir = clauseChanDir(n.child[i])
chanValues[i] = genValue(chans[i])
if assigned[i] != nil {
assignedValues[i] = genValue(assigned[i])
}
if ok[i] != nil {
okValues[i] = genValue(ok[i])
}
case c0.kind == exprStmt && len(c0.child) == 1 && c0.child[0].action == aRecv:
// The comm clause has an empty body clause after channel receive.
chanValues[i] = genValue(c0.child[0].child[0])
cases[i].Dir = reflect.SelectRecv
case c0.kind == sendStmt:
// The comm clause as an empty body clause after channel send.
chanValues[i] = genValue(c0.child[0])
cases[i].Dir = reflect.SelectSend
assignedValues[i] = genValue(c0.child[1])
default:
// The comm clause has a default clause.
clause[i] = getExec(c0.start)
cases[i].Dir = reflect.SelectDefault
} }
continue
}
// The comm clause is in send or recv direction.
switch c0 := cl.child[0]; {
case len(cl.child) > 1:
// The comm clause contains a channel operation and a clause body.
clause[i] = getExec(cl.child[1].start)
chans[i], assigned[i], ok[i], cases[i].Dir = clauseChanDir(c0)
chanValues[i] = genValue(chans[i])
if assigned[i] != nil {
assignedValues[i] = genValue(assigned[i])
}
if ok[i] != nil {
okValues[i] = genValue(ok[i])
}
case c0.kind == exprStmt && len(c0.child) == 1 && c0.child[0].action == aRecv:
// The comm clause has an empty body clause after channel receive.
chanValues[i] = genValue(c0.child[0].child[0])
cases[i].Dir = reflect.SelectRecv
clause[i] = func(*frame) bltn { return next }
case c0.kind == sendStmt:
// The comm clause as an empty body clause after channel send.
chanValues[i] = genValue(c0.child[0])
cases[i].Dir = reflect.SelectSend
assignedValues[i] = genValue(c0.child[1])
clause[i] = func(*frame) bltn { return next }
} }
} }
@@ -3097,9 +3176,9 @@ func isNil(n *node) {
fnext := getExec(n.fnext) fnext := getExec(n.fnext)
if c0.typ.cat == interfaceT { if c0.typ.cat == interfaceT {
n.exec = func(f *frame) bltn { n.exec = func(f *frame) bltn {
vi := value(f).Interface().(valueInterface) v := value(f)
if (vi == valueInterface{} || vi, ok := v.Interface().(valueInterface)
vi.node.kind == basicLit && vi.node.typ.cat == nilT) { if ok && (vi == valueInterface{} || vi.node.kind == basicLit && vi.node.typ.cat == nilT) || v.IsNil() {
dest(f).SetBool(true) dest(f).SetBool(true)
return tnext return tnext
} }
@@ -3119,7 +3198,12 @@ func isNil(n *node) {
} else { } else {
if c0.typ.cat == interfaceT { if c0.typ.cat == interfaceT {
n.exec = func(f *frame) bltn { n.exec = func(f *frame) bltn {
dest(f).SetBool(value(f).Interface().(valueInterface) == valueInterface{}) v := value(f)
if vi, ok := v.Interface().(valueInterface); ok {
dest(f).SetBool(vi == valueInterface{} || vi.node.kind == basicLit && vi.node.typ.cat == nilT)
} else {
dest(f).SetBool(v.IsNil())
}
return tnext return tnext
} }
} else { } else {
@@ -3146,9 +3230,9 @@ func isNotNil(n *node) {
fnext := getExec(n.fnext) fnext := getExec(n.fnext)
if c0.typ.cat == interfaceT { if c0.typ.cat == interfaceT {
n.exec = func(f *frame) bltn { n.exec = func(f *frame) bltn {
vi := value(f).Interface().(valueInterface) v := value(f)
if (vi == valueInterface{} || vi, ok := v.Interface().(valueInterface)
vi.node.kind == basicLit && vi.node.typ.cat == nilT) { if ok && (vi == valueInterface{} || vi.node.kind == basicLit && vi.node.typ.cat == nilT) || v.IsNil() {
dest(f).SetBool(false) dest(f).SetBool(false)
return fnext return fnext
} }
@@ -3168,7 +3252,12 @@ func isNotNil(n *node) {
} else { } else {
if c0.typ.cat == interfaceT { if c0.typ.cat == interfaceT {
n.exec = func(f *frame) bltn { n.exec = func(f *frame) bltn {
dest(f).SetBool(!(value(f).Interface().(valueInterface) == valueInterface{})) v := value(f)
if vi, ok := v.Interface().(valueInterface); ok {
dest(f).SetBool(!(vi == valueInterface{} || vi.node.kind == basicLit && vi.node.typ.cat == nilT))
} else {
dest(f).SetBool(!v.IsNil())
}
return tnext return tnext
} }
} else { } else {

View File

@@ -113,6 +113,14 @@ func (s *scope) pop() *scope {
return s.anc return s.anc
} }
func (s *scope) upperLevel() *scope {
level := s.level
for s != nil && s.level == level {
s = s.anc
}
return s
}
// lookup searches for a symbol in the current scope, and upper ones if not found // lookup searches for a symbol in the current scope, and upper ones if not found
// it returns the symbol, the number of indirections level from the current scope // it returns the symbol, the number of indirections level from the current scope
// and status (false if no result). // and status (false if no result).

View File

@@ -33,17 +33,12 @@ func (interp *Interpreter) importSrc(rPath, importPath string, skipTest bool) (s
rPath = "." rPath = "."
} }
dir = filepath.Join(filepath.Dir(interp.name), rPath, importPath) dir = filepath.Join(filepath.Dir(interp.name), rPath, importPath)
} else { } else if dir, rPath, err = pkgDir(interp.context.GOPATH, rPath, importPath); err != nil {
var root string // Try again, assuming a root dir at the source location.
if rPath == mainID { if rPath, err = interp.rootFromSourceLocation(); err != nil {
root, err = interp.rootFromSourceLocation() return "", err
if err != nil {
return "", err
}
} else {
root = rPath
} }
if dir, rPath, err = pkgDir(interp.context.GOPATH, root, importPath); err != nil { if dir, rPath, err = pkgDir(interp.context.GOPATH, rPath, importPath); err != nil {
return "", err return "", err
} }
} }

View File

@@ -166,56 +166,68 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
case arrayType: case arrayType:
t.cat = arrayT t.cat = arrayT
if len(n.child) > 1 { c0 := n.child[0]
v := n.child[0].rval if len(n.child) == 1 {
switch { // Array size is not defined.
case v.IsValid(): if t.val, err = nodeType(interp, sc, c0); err != nil {
// constant size
if isConstantValue(v.Type()) {
c := v.Interface().(constant.Value)
t.size = constToInt(c)
} else {
t.size = int(v.Int())
}
case n.child[0].kind == ellipsisExpr:
// [...]T expression
t.size = arrayTypeLen(n.anc)
default:
if sym, _, ok := sc.lookup(n.child[0].ident); ok {
if sym.kind != constSym {
return nil, n.child[0].cfgErrorf("non-constant array bound %q", n.child[0].ident)
}
// Resolve symbol to get size value
if sym.typ != nil && sym.typ.cat == intT {
if v, ok := sym.rval.Interface().(int); ok {
t.size = v
} else if c, ok := sym.rval.Interface().(constant.Value); ok {
t.size = constToInt(c)
} else {
t.incomplete = true
}
} else {
t.incomplete = true
}
} else {
// Evaluate constant array size expression
if _, err = interp.cfg(n.child[0], sc.pkgID); err != nil {
return nil, err
}
t.incomplete = true
}
}
if t.val, err = nodeType(interp, sc, n.child[1]); err != nil {
return nil, err
}
t.sizedef = true
t.incomplete = t.incomplete || t.val.incomplete
} else {
if t.val, err = nodeType(interp, sc, n.child[0]); err != nil {
return nil, err return nil, err
} }
t.incomplete = t.val.incomplete t.incomplete = t.val.incomplete
break
} }
// Array size is defined.
switch v := c0.rval; {
case v.IsValid():
// Size if defined by a constant litteral value.
if isConstantValue(v.Type()) {
c := v.Interface().(constant.Value)
t.size = constToInt(c)
} else {
t.size = int(v.Int())
}
case c0.kind == ellipsisExpr:
// [...]T expression, get size from the length of composite array.
t.size = arrayTypeLen(n.anc)
case c0.kind == identExpr:
sym, _, ok := sc.lookup(c0.ident)
if !ok {
t.incomplete = true
break
}
// Size is defined by a symbol which must be a constant integer.
if sym.kind != constSym {
return nil, c0.cfgErrorf("non-constant array bound %q", c0.ident)
}
if sym.typ == nil || sym.typ.cat != intT {
t.incomplete = true
break
}
if v, ok := sym.rval.Interface().(int); ok {
t.size = v
break
}
if c, ok := sym.rval.Interface().(constant.Value); ok {
t.size = constToInt(c)
break
}
t.incomplete = true
default:
// Size is defined by a numeric constant expression.
if _, err = interp.cfg(c0, sc.pkgID); err != nil {
return nil, err
}
v, ok := c0.rval.Interface().(constant.Value)
if !ok {
t.incomplete = true
break
}
t.size = constToInt(v)
}
if t.val, err = nodeType(interp, sc, n.child[1]); err != nil {
return nil, err
}
t.sizedef = true
t.incomplete = t.incomplete || t.val.incomplete
case basicLit: case basicLit:
switch v := n.rval.Interface().(type) { switch v := n.rval.Interface().(type) {
@@ -550,7 +562,6 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
} }
} else { } else {
err = n.cfgErrorf("undefined selector %s.%s", lt.path, name) err = n.cfgErrorf("undefined selector %s.%s", lt.path, name)
panic(err)
} }
case srcPkgT: case srcPkgT:
pkg := interp.srcPkg[lt.path] pkg := interp.srcPkg[lt.path]
@@ -915,7 +926,23 @@ func (t *itype) assignableTo(o *itype) bool {
if t.isNil() && o.hasNil() || o.isNil() && t.hasNil() { if t.isNil() && o.hasNil() || o.isNil() && t.hasNil() {
return true return true
} }
return t.TypeOf().AssignableTo(o.TypeOf())
if t.TypeOf().AssignableTo(o.TypeOf()) {
return true
}
n := t.node
if n == nil || !n.rval.IsValid() {
return false
}
con, ok := n.rval.Interface().(constant.Value)
if !ok {
return false
}
if con == nil || !isConstType(o) {
return false
}
return representableConst(con, o.TypeOf())
} }
// convertibleTo returns true if t is convertible to o. // convertibleTo returns true if t is convertible to o.
@@ -924,7 +951,7 @@ func (t *itype) convertibleTo(o *itype) bool {
return true return true
} }
// unsafe checkes // unsafe checks
tt, ot := t.TypeOf(), o.TypeOf() tt, ot := t.TypeOf(), o.TypeOf()
if (tt.Kind() == reflect.Ptr || tt.Kind() == reflect.Uintptr) && ot.Kind() == reflect.UnsafePointer { if (tt.Kind() == reflect.Ptr || tt.Kind() == reflect.Uintptr) && ot.Kind() == reflect.UnsafePointer {
return true return true
@@ -1180,7 +1207,11 @@ func (t *itype) lookupBinField(name string) (s reflect.StructField, index []int,
if !isStruct(t) { if !isStruct(t) {
return return
} }
s, ok = t.TypeOf().FieldByName(name) rt := t.rtype
if t.cat == valueT && rt.Kind() == reflect.Ptr {
rt = rt.Elem()
}
s, ok = rt.FieldByName(name)
if !ok { if !ok {
for i, f := range t.field { for i, f := range t.field {
if f.embed { if f.embed {

View File

@@ -91,7 +91,10 @@ func (check typecheck) addressExpr(n *node) error {
case selectorExpr: case selectorExpr:
c0 = c0.child[1] c0 = c0.child[1]
continue continue
case indexExpr: case starExpr:
c0 = c0.child[0]
continue
case indexExpr, sliceExpr:
c := c0.child[0] c := c0.child[0]
if isArray(c.typ) || isMap(c.typ) { if isArray(c.typ) || isMap(c.typ) {
c0 = c c0 = c
@@ -101,7 +104,7 @@ func (check typecheck) addressExpr(n *node) error {
found = true found = true
continue continue
} }
return n.cfgErrorf("invalid operation: cannot take address of %s", c0.typ.id()) return n.cfgErrorf("invalid operation: cannot take address of %s [kind: %s]", c0.typ.id(), kinds[c0.kind])
} }
return nil return nil
} }
@@ -148,7 +151,7 @@ func (check typecheck) shift(n *node) error {
t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf() t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf()
var v0 constant.Value var v0 constant.Value
if c0.typ.untyped { if c0.typ.untyped && c0.rval.IsValid() {
v0 = constant.ToInt(c0.rval.Interface().(constant.Value)) v0 = constant.ToInt(c0.rval.Interface().(constant.Value))
c0.rval = reflect.ValueOf(v0) c0.rval = reflect.ValueOf(v0)
} }
@@ -214,6 +217,7 @@ var binaryOpPredicates = opPredicates{
// binaryExpr type checks a binary expression. // binaryExpr type checks a binary expression.
func (check typecheck) binaryExpr(n *node) error { func (check typecheck) binaryExpr(n *node) error {
c0, c1 := n.child[0], n.child[1] c0, c1 := n.child[0], n.child[1]
a := n.action a := n.action
if isAssignAction(a) { if isAssignAction(a) {
a-- a--
@@ -223,6 +227,21 @@ func (check typecheck) binaryExpr(n *node) error {
return check.shift(n) return check.shift(n)
} }
switch n.action {
case aRem:
if zeroConst(c1) {
return n.cfgErrorf("invalid operation: division by zero")
}
case aQuo:
if zeroConst(c1) {
return n.cfgErrorf("invalid operation: division by zero")
}
if c0.rval.IsValid() && c1.rval.IsValid() {
// Avoid constant conversions below to ensure correct constant integer quotient.
return nil
}
}
_ = check.convertUntyped(c0, c1.typ) _ = check.convertUntyped(c0, c1.typ)
_ = check.convertUntyped(c1, c0.typ) _ = check.convertUntyped(c1, c0.typ)
@@ -238,16 +257,13 @@ func (check typecheck) binaryExpr(n *node) error {
if err := check.op(binaryOpPredicates, a, n, c0, t0); err != nil { if err := check.op(binaryOpPredicates, a, n, c0, t0); err != nil {
return err return err
} }
switch n.action {
case aQuo, aRem:
if (c0.typ.untyped || isInt(t0)) && c1.typ.untyped && constant.Sign(c1.rval.Interface().(constant.Value)) == 0 {
return n.cfgErrorf("invalid operation: division by zero")
}
}
return nil return nil
} }
func zeroConst(n *node) bool {
return n.typ.untyped && constant.Sign(n.rval.Interface().(constant.Value)) == 0
}
func (check typecheck) index(n *node, max int) error { func (check typecheck) index(n *node, max int) error {
if err := check.convertUntyped(n, &itype{cat: intT, name: "int"}); err != nil { if err := check.convertUntyped(n, &itype{cat: intT, name: "int"}); err != nil {
return err return err
@@ -483,7 +499,7 @@ func (check typecheck) sliceExpr(n *node) error {
case reflect.Array: case reflect.Array:
valid = true valid = true
l = t.Len() l = t.Len()
if c.kind != selectorExpr && (c.sym == nil || c.sym.kind != varSym) { if c.kind != indexExpr && c.kind != selectorExpr && (c.sym == nil || c.sym.kind != varSym) {
return c.cfgErrorf("cannot slice type %s", c.typ.id()) return c.cfgErrorf("cannot slice type %s", c.typ.id())
} }
case reflect.Slice: case reflect.Slice:
@@ -618,16 +634,13 @@ func (check typecheck) conversion(n *node, typ *itype) error {
if !ok { if !ok {
return n.cfgErrorf("cannot convert expression of type %s to type %s", n.typ.id(), typ.id()) return n.cfgErrorf("cannot convert expression of type %s to type %s", n.typ.id(), typ.id())
} }
if !n.typ.untyped || c == nil {
if n.typ.untyped { return nil
if isInterface(typ) || c != nil && !isConstType(typ) {
typ = n.typ.defaultType()
}
if err := check.convertUntyped(n, typ); err != nil {
return err
}
} }
return nil if isInterface(typ) || !isConstType(typ) {
typ = n.typ.defaultType()
}
return check.convertUntyped(n, typ)
} }
type param struct { type param struct {
@@ -1040,24 +1053,10 @@ func (check typecheck) convertUntyped(n *node, typ *itype) error {
return convErr return convErr
} }
isFloatToIntDivision := false
if err := check.representable(n, rtyp); err != nil { if err := check.representable(n, rtyp); err != nil {
if !isInt(rtyp) || n.action != aQuo { return err
return err
}
// retry in the case of a division, and pretend we want a float. Because if we
// can represent a float, then it follows that we can represent the integer
// part of that float as an int.
if err := check.representable(n, reflect.TypeOf(1.0)); err != nil {
return err
}
isFloatToIntDivision = true
}
if isFloatToIntDivision {
n.rval, err = check.convertConstFloatToInt(n.rval)
} else {
n.rval, err = check.convertConst(n.rval, rtyp)
} }
n.rval, err = check.convertConst(n.rval, rtyp)
if err != nil { if err != nil {
if errors.Is(err, errCantConvert) { if errors.Is(err, errCantConvert) {
return convErr return convErr
@@ -1099,22 +1098,6 @@ func (check typecheck) representable(n *node, t reflect.Type) error {
return nil return nil
} }
func (check typecheck) convertConstFloatToInt(v reflect.Value) (reflect.Value, error) {
if !v.IsValid() {
return v, errors.New("invalid float reflect value")
}
c, ok := v.Interface().(constant.Value)
if !ok {
return v, errors.New("unexpected non-constant value")
}
if constant.ToFloat(c).Kind() != constant.Float {
return v, errors.New("const value cannot be converted to float")
}
fl, _ := constant.Float64Val(c)
return reflect.ValueOf(int(fl)).Convert(reflect.TypeOf(1.0)), nil
}
func (check typecheck) convertConst(v reflect.Value, t reflect.Type) (reflect.Value, error) { func (check typecheck) convertConst(v reflect.Value, t reflect.Type) (reflect.Value, error) {
if !v.IsValid() { if !v.IsValid() {
// TODO(nick): This should be an error as the const is in the frame which is undesirable. // TODO(nick): This should be an error as the const is in the frame which is undesirable.

View File

@@ -342,6 +342,10 @@ func vInt(v reflect.Value) (i int64) {
} }
func vUint(v reflect.Value) (i uint64) { func vUint(v reflect.Value) (i uint64) {
if c := vConstantValue(v); c != nil {
i, _ = constant.Uint64Val(constant.ToInt(c))
return i
}
switch v.Type().Kind() { switch v.Type().Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
i = uint64(v.Int()) i = uint64(v.Int())

View File

@@ -1,6 +1,6 @@
// Code generated by 'yaegi extract archive/tar'. DO NOT EDIT. // Code generated by 'yaegi extract archive/tar'. DO NOT EDIT.
// +build go1.15,!go1.16 // +build go1.15
package stdlib package stdlib

View File

@@ -1,6 +1,6 @@
// Code generated by 'yaegi extract archive/zip'. DO NOT EDIT. // Code generated by 'yaegi extract archive/zip'. DO NOT EDIT.
// +build go1.15,!go1.16 // +build go1.15
package stdlib package stdlib

View File

@@ -1,6 +1,6 @@
// Code generated by 'yaegi extract bufio'. DO NOT EDIT. // Code generated by 'yaegi extract bufio'. DO NOT EDIT.
// +build go1.15,!go1.16 // +build go1.15
package stdlib package stdlib

View File

@@ -1,6 +1,6 @@
// Code generated by 'yaegi extract bytes'. DO NOT EDIT. // Code generated by 'yaegi extract bytes'. DO NOT EDIT.
// +build go1.15,!go1.16 // +build go1.15
package stdlib package stdlib

View File

@@ -1,6 +1,6 @@
// Code generated by 'yaegi extract compress/bzip2'. DO NOT EDIT. // Code generated by 'yaegi extract compress/bzip2'. DO NOT EDIT.
// +build go1.15,!go1.16 // +build go1.15
package stdlib package stdlib

View File

@@ -1,6 +1,6 @@
// Code generated by 'yaegi extract compress/flate'. DO NOT EDIT. // Code generated by 'yaegi extract compress/flate'. DO NOT EDIT.
// +build go1.15,!go1.16 // +build go1.15
package stdlib package stdlib

View File

@@ -1,6 +1,6 @@
// Code generated by 'yaegi extract compress/gzip'. DO NOT EDIT. // Code generated by 'yaegi extract compress/gzip'. DO NOT EDIT.
// +build go1.15,!go1.16 // +build go1.15
package stdlib package stdlib

View File

@@ -1,6 +1,6 @@
// Code generated by 'yaegi extract compress/lzw'. DO NOT EDIT. // Code generated by 'yaegi extract compress/lzw'. DO NOT EDIT.
// +build go1.15,!go1.16 // +build go1.15
package stdlib package stdlib

View File

@@ -1,6 +1,6 @@
// Code generated by 'yaegi extract compress/zlib'. DO NOT EDIT. // Code generated by 'yaegi extract compress/zlib'. DO NOT EDIT.
// +build go1.15,!go1.16 // +build go1.15
package stdlib package stdlib

View File

@@ -1,6 +1,6 @@
// Code generated by 'yaegi extract container/heap'. DO NOT EDIT. // Code generated by 'yaegi extract container/heap'. DO NOT EDIT.
// +build go1.15,!go1.16 // +build go1.15
package stdlib package stdlib

View File

@@ -1,6 +1,6 @@
// Code generated by 'yaegi extract container/list'. DO NOT EDIT. // Code generated by 'yaegi extract container/list'. DO NOT EDIT.
// +build go1.15,!go1.16 // +build go1.15
package stdlib package stdlib

View File

@@ -1,6 +1,6 @@
// Code generated by 'yaegi extract container/ring'. DO NOT EDIT. // Code generated by 'yaegi extract container/ring'. DO NOT EDIT.
// +build go1.15,!go1.16 // +build go1.15
package stdlib package stdlib

View File

@@ -1,6 +1,6 @@
// Code generated by 'yaegi extract context'. DO NOT EDIT. // Code generated by 'yaegi extract context'. DO NOT EDIT.
// +build go1.15,!go1.16 // +build go1.15
package stdlib package stdlib

View File

@@ -1,6 +1,6 @@
// Code generated by 'yaegi extract crypto'. DO NOT EDIT. // Code generated by 'yaegi extract crypto'. DO NOT EDIT.
// +build go1.15,!go1.16 // +build go1.15
package stdlib package stdlib

View File

@@ -1,6 +1,6 @@
// Code generated by 'yaegi extract crypto/aes'. DO NOT EDIT. // Code generated by 'yaegi extract crypto/aes'. DO NOT EDIT.
// +build go1.15,!go1.16 // +build go1.15
package stdlib package stdlib

View File

@@ -1,6 +1,6 @@
// Code generated by 'yaegi extract crypto/cipher'. DO NOT EDIT. // Code generated by 'yaegi extract crypto/cipher'. DO NOT EDIT.
// +build go1.15,!go1.16 // +build go1.15
package stdlib package stdlib

View File

@@ -1,6 +1,6 @@
// Code generated by 'yaegi extract crypto/des'. DO NOT EDIT. // Code generated by 'yaegi extract crypto/des'. DO NOT EDIT.
// +build go1.15,!go1.16 // +build go1.15
package stdlib package stdlib

View File

@@ -1,6 +1,6 @@
// Code generated by 'yaegi extract crypto/dsa'. DO NOT EDIT. // Code generated by 'yaegi extract crypto/dsa'. DO NOT EDIT.
// +build go1.15,!go1.16 // +build go1.15
package stdlib package stdlib

View File

@@ -1,6 +1,6 @@
// Code generated by 'yaegi extract crypto/ecdsa'. DO NOT EDIT. // Code generated by 'yaegi extract crypto/ecdsa'. DO NOT EDIT.
// +build go1.15,!go1.16 // +build go1.15
package stdlib package stdlib

View File

@@ -1,6 +1,6 @@
// Code generated by 'yaegi extract crypto/ed25519'. DO NOT EDIT. // Code generated by 'yaegi extract crypto/ed25519'. DO NOT EDIT.
// +build go1.15,!go1.16 // +build go1.15
package stdlib package stdlib

View File

@@ -1,6 +1,6 @@
// Code generated by 'yaegi extract crypto/elliptic'. DO NOT EDIT. // Code generated by 'yaegi extract crypto/elliptic'. DO NOT EDIT.
// +build go1.15,!go1.16 // +build go1.15
package stdlib package stdlib

View File

@@ -1,6 +1,6 @@
// Code generated by 'yaegi extract crypto/hmac'. DO NOT EDIT. // Code generated by 'yaegi extract crypto/hmac'. DO NOT EDIT.
// +build go1.15,!go1.16 // +build go1.15
package stdlib package stdlib

View File

@@ -1,6 +1,6 @@
// Code generated by 'yaegi extract crypto/md5'. DO NOT EDIT. // Code generated by 'yaegi extract crypto/md5'. DO NOT EDIT.
// +build go1.15,!go1.16 // +build go1.15
package stdlib package stdlib

View File

@@ -1,6 +1,6 @@
// Code generated by 'yaegi extract crypto/rand'. DO NOT EDIT. // Code generated by 'yaegi extract crypto/rand'. DO NOT EDIT.
// +build go1.15,!go1.16 // +build go1.15
package stdlib package stdlib

View File

@@ -1,6 +1,6 @@
// Code generated by 'yaegi extract crypto/rc4'. DO NOT EDIT. // Code generated by 'yaegi extract crypto/rc4'. DO NOT EDIT.
// +build go1.15,!go1.16 // +build go1.15
package stdlib package stdlib

View File

@@ -1,6 +1,6 @@
// Code generated by 'yaegi extract crypto/rsa'. DO NOT EDIT. // Code generated by 'yaegi extract crypto/rsa'. DO NOT EDIT.
// +build go1.15,!go1.16 // +build go1.15
package stdlib package stdlib

View File

@@ -1,6 +1,6 @@
// Code generated by 'yaegi extract crypto/sha1'. DO NOT EDIT. // Code generated by 'yaegi extract crypto/sha1'. DO NOT EDIT.
// +build go1.15,!go1.16 // +build go1.15
package stdlib package stdlib

Some files were not shown because too many files have changed in this diff Show More