Compare commits

...

25 Commits

Author SHA1 Message Date
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
59 changed files with 906 additions and 289 deletions

View File

@@ -45,3 +45,19 @@ archives:
format: zip
files:
- 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
go generate
install:
GOFLAGS=-ldflags=-X=main.version=$$(git describe --tags) go install ./...
tests:
GO111MODULE=off go test -v ./...
GO111MODULE=off go test -race ./interp
go test -v ./...
go test -race ./interp
# https://github.com/goreleaser/godownloader
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
$ 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)]

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

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

View File

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

View File

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

View File

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

View File

@@ -6,6 +6,7 @@ import (
"go/build"
"io/ioutil"
"os"
"strconv"
"strings"
"github.com/traefik/yaegi/interp"
@@ -17,19 +18,21 @@ import (
func run(arg []string) error {
var interactive bool
var useSyscall bool
var useUnrestricted bool
var useUnsafe bool
var tags string
var cmd string
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.BoolVar(&interactive, "i", false, "start an interactive REPL")
rflag.BoolVar(&useSyscall, "syscall", false, "include syscall symbols")
rflag.BoolVar(&useUnrestricted, "unrestricted", false, "include unrestricted symbols")
rflag.BoolVar(&useSyscall, "syscall", useSyscall, "include syscall symbols")
rflag.BoolVar(&useUnrestricted, "unrestricted", useUnrestricted, "include unrestricted symbols")
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.Usage = func() {
fmt.Println("Usage: yaegi run [options] [path] [args]")
@@ -46,24 +49,33 @@ func run(arg []string) error {
i.Use(interp.Symbols)
if useSyscall {
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 {
i.Use(unsafe.Symbols)
if err := os.Setenv("YAEGI_UNSAFE", "1"); err != nil {
return err
}
}
if useUnrestricted {
// Use of unrestricted symbols should always follow stdlib and syscall symbols, to update them.
i.Use(unrestricted.Symbols)
if err := os.Setenv("YAEGI_UNRESTRICTED", "1"); err != nil {
return err
}
}
if cmd != "" {
_, err = i.Eval(cmd)
showError(err)
}
if len(args) == 0 {
if interactive || cmd == "" {
_, err = i.REPL()
showError(err)
_, err = i.REPL()
}
return err
}
@@ -78,7 +90,6 @@ func run(arg []string) error {
} else {
_, err = i.EvalPath(path)
}
showError(err)
if err != nil {
return err
@@ -86,7 +97,6 @@ func run(arg []string) error {
if interactive {
_, err = i.REPL()
showError(err)
}
return err
}

View File

@@ -1,11 +1,13 @@
package main
import (
"errors"
"flag"
"fmt"
"go/build"
"os"
"regexp"
"strconv"
"strings"
"testing"
@@ -18,22 +20,24 @@ import (
func test(arg []string) (err error) {
var (
bench string
benchmem bool
benchtime string
count string
cpu string
failfast bool
run string
short bool
tags string
useUnrestricted bool
useUnsafe bool
useSyscall bool
timeout string
verbose bool
bench string
benchmem bool
benchtime string
count string
cpu string
failfast bool
run string
short bool
tags string
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.StringVar(&bench, "bench", "", "Run only those benchmarks matching a regular expression.")
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.StringVar(&tags, "tags", "", "Set a list of build tags.")
tflag.StringVar(&timeout, "timeout", "", "If a test binary runs longer than duration d, panic.")
tflag.BoolVar(&useUnrestricted, "unrestricted", false, "Include unrestricted symbols.")
tflag.BoolVar(&useUnsafe, "unsafe", false, "Include usafe symbols.")
tflag.BoolVar(&useSyscall, "syscall", false, "Include syscall symbols.")
tflag.BoolVar(&useUnrestricted, "unrestricted", useUnrestricted, "Include unrestricted symbols.")
tflag.BoolVar(&useUnsafe, "unsafe", useUnsafe, "Include usafe symbols.")
tflag.BoolVar(&useSyscall, "syscall", useSyscall, "Include syscall symbols.")
tflag.BoolVar(&verbose, "v", false, "Verbose output: log all tests as they are run.")
tflag.Usage = func() {
fmt.Println("Usage: yaegi test [options] [path]")
@@ -104,12 +108,22 @@ func test(arg []string) (err error) {
i.Use(interp.Symbols)
if useSyscall {
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 {
i.Use(unrestricted.Symbols)
if err := os.Setenv("YAEGI_UNRESTRICTED", "1"); err != nil {
return err
}
}
if useUnsafe {
i.Use(unsafe.Symbols)
if err := os.Setenv("YAEGI_UNSAFE", "1"); err != nil {
return err
}
}
if err = i.EvalTest(path); err != nil {
return err
@@ -117,7 +131,11 @@ func test(arg []string) (err error) {
benchmarks := []testing.InternalBenchmark{}
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) {
case func(*testing.B):
benchmarks = append(benchmarks, testing.InternalBenchmark{name, fun})

View File

@@ -71,7 +71,16 @@ Options:
-unsafe
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
Generate and display graphviz dot of AST with dotty(1)
YAEGI_CFG_DOT=1
@@ -90,6 +99,8 @@ import (
"fmt"
"log"
"os"
"github.com/traefik/yaegi/interp"
)
const (
@@ -97,8 +108,11 @@ const (
Help = "help"
Run = "run"
Test = "test"
Version = "version"
)
var version = "devel" // This may be overwritten at build time.
func main() {
var cmd string
var err error
@@ -119,6 +133,8 @@ func main() {
err = run(os.Args[2:])
case Test:
err = test(os.Args[2:])
case Version:
fmt.Println(version)
default:
// If no command is given, fallback to default "run" command.
// This allows scripts starting with "#!/usr/bin/env yaegi",
@@ -129,8 +145,10 @@ func main() {
}
if err != nil && !errors.Is(err, flag.ErrHelp) {
err = fmt.Errorf("%s: %w", cmd, err)
fmt.Fprintln(os.Stderr, err)
fmt.Fprintln(os.Stderr, fmt.Errorf("%s: %w", cmd, err))
if p, ok := err.(interp.Panic); ok {
fmt.Fprintln(os.Stderr, string(p.Stack))
}
exitCode = 1
}
os.Exit(exitCode)

View File

@@ -1,6 +1,6 @@
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/stdlib
//go:generate go generate github.com/traefik/yaegi/stdlib/syscall

View File

@@ -28,7 +28,7 @@ import (
"runtime"
"strings"
"github.com/traefik/yaegi/internal/extract"
"github.com/traefik/yaegi/extract"
)
var (

View File

@@ -196,6 +196,18 @@ func {{$name}}Const(n *node) {
{{- if $op.Shift}}
v := constant.Shift(vConstantValue(v0), token.{{tokenFromName $name}}, uint(vUint(v1)))
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}}
v := constant.BinaryOp(vConstantValue(v0), token.{{tokenFromName $name}}, vConstantValue(v1))
n.rval.Set(reflect.ValueOf(v))
@@ -395,10 +407,10 @@ func {{$name}}Const(n *node) {
case isConst:
v := constant.UnaryOp(token.{{tokenFromName $name}}, vConstantValue(v0), 0)
n.rval.Set(reflect.ValueOf(v))
case isInt(t):
n.rval.SetInt({{$op.Name}} v0.Int())
case isUint(t):
n.rval.SetUint({{$op.Name}} v0.Uint())
case isInt(t):
n.rval.SetInt({{$op.Name}} v0.Int())
{{- if $op.Float}}
case isFloat(t):
n.rval.SetFloat({{$op.Name}} v0.Float())

View File

@@ -443,10 +443,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
n.gen = nop
break
}
if n.anc.kind == commClause {
n.gen = nop
break
}
var atyp *itype
if n.nleft+n.nright < len(n.child) {
if atyp, err = nodeType(interp, sc, n.child[n.nleft]); err != nil {
@@ -554,6 +551,14 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
n.gen = nop
src.findex = dest.findex
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():
// Assign to nil.
src.rval = reflect.New(dest.typ.TypeOf()).Elem()
@@ -855,7 +860,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
n.gen = nop
n.findex = -1
n.typ = c0.typ
n.rval = c1.rval
n.rval = c1.rval.Convert(c0.typ.rtype)
default:
n.gen = convert
n.typ = c0.typ
@@ -982,7 +987,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
n.findex = sc.add(n.typ)
// TODO: Check that composite literal expr matches corresponding type
n.gen = compositeGenerator(n, n.typ)
n.gen = compositeGenerator(n, n.typ, nil)
case fallthroughtStmt:
if n.anc.kind != caseBody {
@@ -1129,7 +1134,6 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
sym, level, found := sc.lookup(n.ident)
if !found {
// 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))
if !found {
err = n.cfgErrorf("undefined: %s", n.ident)
@@ -1302,7 +1306,12 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
}
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)
if nret == 1 && isCall(n.child[0]) {
nret = n.child[0].child[0].typ.numOut()
@@ -1316,13 +1325,19 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
n.tnext = nil
n.val = sc.def
for i, c := range n.child {
var typ *itype
typ, err = nodeType(interp, sc, 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 {
// 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 {
// 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()})
@@ -2360,10 +2375,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 {
case aliasT, ptrT:
gen = compositeGenerator(n, n.typ.val)
gen = compositeGenerator(n, n.typ.val, rtyp)
case arrayT:
gen = arrayLit
case mapT:
@@ -2386,11 +2401,21 @@ func compositeGenerator(n *node, typ *itype) (gen bltnGenerator) {
}
}
case valueT:
switch k := n.typ.rtype.Kind(); k {
if rtyp == nil {
rtyp = n.typ.rtype
}
switch k := rtyp.Kind(); k {
case reflect.Struct:
gen = compositeBinStruct
if n.nleft == 1 {
gen = compositeBinStruct
} else {
gen = compositeBinStructNotype
}
case reflect.Map:
// TODO(mpl): maybe needs a NoType version too
gen = compositeBinMap
case reflect.Ptr:
gen = compositeGenerator(n, typ, n.typ.val.rtype)
default:
log.Panic(n.cfgErrorf("compositeGenerator not implemented for type kind: %s", k))
}
@@ -2432,3 +2457,13 @@ func isValueUntyped(v reflect.Value) bool {
}
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
}
}

View File

@@ -248,6 +248,8 @@ func (interp *Interpreter) gta(root *node, rpath, importPath string) ([]*node, e
typeName := n.child[0].ident
var typ *itype
if typ, err = nodeType(interp, sc, n.child[1]); err != nil {
err = nil
revisit = append(revisit, n)
return false
}

View File

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

View File

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

View File

@@ -43,6 +43,9 @@ func TestInterpConsistencyBuild(t *testing.T) {
file.Name() == "for7.go" || // expect error
file.Name() == "fun21.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() == "import6.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",
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",
expectedInterp: "5:2: invalid operation: mismatched types int and float64",

View File

@@ -986,6 +986,9 @@ func TestConcurrentEvals(t *testing.T) {
// 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.
func TestConcurrentEvals2(t *testing.T) {
if testing.Short() {
return
}
pin, pout := io.Pipe()
defer func() {
_ = pin.Close()
@@ -1045,6 +1048,9 @@ func TestConcurrentEvals2(t *testing.T) {
// - when calling Interpreter.Use, the symbols given as argument should be
// copied when being inserted into interp.binPkg, and not directly used as-is.
func TestConcurrentEvals3(t *testing.T) {
if testing.Short() {
return
}
allDone := make(chan bool)
runREPL := func() {
done := make(chan error)
@@ -1123,6 +1129,9 @@ func TestConcurrentComposite2(t *testing.T) {
}
func testConcurrentComposite(t *testing.T, filePath string) {
if testing.Short() {
return
}
pin, pout := io.Pipe()
i := interp.New(interp.Options{Stdout: pout})
i.Use(stdlib.Symbols)
@@ -1160,6 +1169,9 @@ func testConcurrentComposite(t *testing.T, filePath string) {
}
func TestEvalScanner(t *testing.T) {
if testing.Short() {
return
}
type testCase struct {
desc string
src []string
@@ -1331,3 +1343,94 @@ func applyCIMultiplier(timeout time.Duration) time.Duration {
}
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()
switch {
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))
case isComplex(t):
n.rval.SetComplex(vComplex(v0) / vComplex(v1))
@@ -1957,10 +1966,10 @@ func bitNotConst(n *node) {
case isConst:
v := constant.UnaryOp(token.XOR, vConstantValue(v0), 0)
n.rval.Set(reflect.ValueOf(v))
case isInt(t):
n.rval.SetInt(^v0.Int())
case isUint(t):
n.rval.SetUint(^v0.Uint())
case isInt(t):
n.rval.SetInt(^v0.Int())
}
}
@@ -1976,10 +1985,10 @@ func negConst(n *node) {
case isConst:
v := constant.UnaryOp(token.SUB, vConstantValue(v0), 0)
n.rval.Set(reflect.ValueOf(v))
case isInt(t):
n.rval.SetInt(-v0.Int())
case isUint(t):
n.rval.SetUint(-v0.Uint())
case isInt(t):
n.rval.SetInt(-v0.Int())
case isFloat(t):
n.rval.SetFloat(-v0.Float())
case isComplex(t):
@@ -2015,10 +2024,10 @@ func posConst(n *node) {
case isConst:
v := constant.UnaryOp(token.ADD, vConstantValue(v0), 0)
n.rval.Set(reflect.ValueOf(v))
case isInt(t):
n.rval.SetInt(+v0.Int())
case isUint(t):
n.rval.SetUint(+v0.Uint())
case isInt(t):
n.rval.SetInt(+v0.Int())
case isFloat(t):
n.rval.SetFloat(+v0.Float())
case isComplex(t):

View File

@@ -1869,7 +1869,10 @@ func _return(n *node) {
case 0:
n.exec = nil
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
} else {
v := values[0]
@@ -2010,12 +2013,18 @@ func compositeBinMap(n *node) {
}
}
// compositeBinStruct creates and populates a struct object from a binary type.
func compositeBinStruct(n *node) {
// doCompositeBinStruct creates and populates a struct object from a binary type.
func doCompositeBinStruct(n *node, hasType bool) {
next := getExec(n.tnext)
value := valueGenerator(n, n.findex)
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))
fieldIndex := make([][]int, len(child))
for i, c := range child {
@@ -2031,7 +2040,7 @@ func compositeBinStruct(n *node) {
}
} else {
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)
values[i] = genFunctionWrapper(c.child[1])
} else {
@@ -2046,11 +2055,20 @@ func compositeBinStruct(n *node) {
for i, v := range values {
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
}
}
func compositeBinStruct(n *node) { doCompositeBinStruct(n, true) }
func compositeBinStructNotype(n *node) { doCompositeBinStruct(n, false) }
func destType(n *node) *itype {
switch n.anc.kind {
case assignStmt, defineStmt:
@@ -2412,11 +2430,16 @@ func appendSlice(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() ||
isByteArray(c1.typ.TypeOf()) && isString(c2.typ.TypeOf()) {
appendSlice(n)
return
if len(n.child) == 3 {
c1, c2 := n.child[1], n.child[2]
if (c1.typ.cat == valueT || c2.typ.cat == valueT) && c1.typ.rtype == c2.typ.rtype ||
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)
value := genValue(n.child[1])
next := getExec(n.tnext)
@@ -2813,65 +2836,29 @@ func convertConstantValue(n *node) {
return
}
v := n.rval
typ := n.typ.TypeOf()
kind := typ.Kind()
switch kind {
case reflect.Bool:
v = reflect.ValueOf(constant.BoolVal(c)).Convert(typ)
case reflect.String:
v = reflect.ValueOf(constant.StringVal(c)).Convert(typ)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
i, _ := constant.Int64Val(c)
l := constant.BitLen(c)
if l > bitlen[kind] {
panic(fmt.Sprintf("constant %s overflows int%d", c.ExactString(), bitlen[kind]))
var v reflect.Value
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(i).Convert(typ)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
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:
v = reflect.ValueOf(int(i))
case constant.Float:
f, _ := constant.Float64Val(c)
v = reflect.ValueOf(f).Convert(typ)
case reflect.Complex64:
r, _ := constant.Float32Val(constant.Real(c))
i, _ := constant.Float32Val(constant.Imag(c))
v = reflect.ValueOf(complex(r, i)).Convert(typ)
case reflect.Complex128:
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)).Convert(typ)
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))
}
v = reflect.ValueOf(complex(r, i))
}
n.rval = v
n.rval = v.Convert(n.typ.TypeOf())
}
// Write to a channel.
@@ -2951,37 +2938,40 @@ func _select(n *node) {
next := getExec(n.tnext)
for i := 0; i < nbClause; i++ {
if len(n.child[i].child) == 0 {
// The comm clause is an empty default, exit select.
cl := n.child[i]
if cl.kind == commClauseDefault {
cases[i].Dir = reflect.SelectDefault
clause[i] = func(*frame) bltn { return next }
} else {
switch c0 := n.child[i].child[0]; {
case len(n.child[i].child) > 1:
// 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
if len(cl.child) == 0 {
clause[i] = func(*frame) bltn { return next }
} else {
clause[i] = getExec(cl.child[0].start)
}
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 }
}
}

View File

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

View File

@@ -166,56 +166,68 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
case arrayType:
t.cat = arrayT
if len(n.child) > 1 {
v := n.child[0].rval
switch {
case v.IsValid():
// 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 {
c0 := n.child[0]
if len(n.child) == 1 {
// Array size is not defined.
if t.val, err = nodeType(interp, sc, c0); err != nil {
return nil, err
}
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:
switch v := n.rval.Interface().(type) {
@@ -550,7 +562,6 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
}
} else {
err = n.cfgErrorf("undefined selector %s.%s", lt.path, name)
panic(err)
}
case srcPkgT:
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() {
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.
@@ -924,7 +951,7 @@ func (t *itype) convertibleTo(o *itype) bool {
return true
}
// unsafe checkes
// unsafe checks
tt, ot := t.TypeOf(), o.TypeOf()
if (tt.Kind() == reflect.Ptr || tt.Kind() == reflect.Uintptr) && ot.Kind() == reflect.UnsafePointer {
return true
@@ -1180,7 +1207,11 @@ func (t *itype) lookupBinField(name string) (s reflect.StructField, index []int,
if !isStruct(t) {
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 {
for i, f := range t.field {
if f.embed {

View File

@@ -91,7 +91,10 @@ func (check typecheck) addressExpr(n *node) error {
case selectorExpr:
c0 = c0.child[1]
continue
case indexExpr:
case starExpr:
c0 = c0.child[0]
continue
case indexExpr, sliceExpr:
c := c0.child[0]
if isArray(c.typ) || isMap(c.typ) {
c0 = c
@@ -101,7 +104,7 @@ func (check typecheck) addressExpr(n *node) error {
found = true
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
}
@@ -148,7 +151,7 @@ func (check typecheck) shift(n *node) error {
t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf()
var v0 constant.Value
if c0.typ.untyped {
if c0.typ.untyped && c0.rval.IsValid() {
v0 = constant.ToInt(c0.rval.Interface().(constant.Value))
c0.rval = reflect.ValueOf(v0)
}
@@ -483,7 +486,7 @@ func (check typecheck) sliceExpr(n *node) error {
case reflect.Array:
valid = true
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())
}
case reflect.Slice:
@@ -1040,24 +1043,10 @@ func (check typecheck) convertUntyped(n *node, typ *itype) error {
return convErr
}
isFloatToIntDivision := false
if err := check.representable(n, rtyp); err != nil {
if !isInt(rtyp) || n.action != aQuo {
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)
return err
}
n.rval, err = check.convertConst(n.rval, rtyp)
if err != nil {
if errors.Is(err, errCantConvert) {
return convErr
@@ -1099,22 +1088,6 @@ func (check typecheck) representable(n *node, t reflect.Type) error {
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) {
if !v.IsValid() {
// TODO(nick): This should be an error as the const is in the frame which is undesirable.