Compare commits

...

25 Commits

Author SHA1 Message Date
Marc Vertes
6346d11286 chore: move to new organization 2020-09-16 10:58:04 +02:00
Marc Vertes
1edb6a1424 fix: native build of syscalls on android and illumos
As per https://golang.org/cmd/go/#hdr-Build_constraints,
using GOOS=android also matches tags and files for GOOS=linux,
so exclude it explicetly to avoid collisions.

Also using GOOS=illumos matches tags and files for GOOS=solaris.

Fixes #843.
2020-09-14 17:48:05 +02:00
mpl
a2f56431ea interp: fix data race for composite literal creation
This change fixes two data races related to composite literal creation.

The first one isn't controversial as it is just about initializing the
variable that contains the values in the right place, i.e. within the
n.exec, so that this variable is local to each potential goroutine,
instead of being racily shared by all goroutines.

The second one is more worrying, i.e. having to protect the node typ
with a mutex, because a call to func (t *itype) refType actually
modifies the itype itself, which means it is not concurrent safe.
The change seems to work, and does not seem to introduce regression, but
it is still a concern as it probably is a sign that more similar
guarding has to be done in several other places.
2020-09-14 16:22:04 +02:00
Marc Vertes
42abedb25d fix: keep atomic counter aligned on 64 bits boundary
Fixes #845.
2020-09-14 15:32:03 +02:00
Marc Vertes
151699ef9f feature: test subcommand to run test and benchmark functions
This change allows the interpreter to execute tests and benchmarks
functions provided by packages.

The test subcommand is similar to the "go test" command and
all the relevant flags have been kept.

The ability to evaluate a directory or a package has also been added.

A new method Symbol to access exported symbol values of an interpreted
package has been added. This method is used by the test subcommand.

An EvalTest method has been added to evaluate all Go files, including "*_test.go".

The testing packages from the standard library have been added to stdlib used
symbols.
2020-09-14 11:14:04 +02:00
Marc Vertes
f1f3ca7e06 fix: handle interface fields in literal composite structs
Struct fields of type interface must be converted in wrapper
values to be reachable by the runtime. Call genInterfaceWrapper
on such values.

Fixes #832.
2020-09-09 13:44:04 +02:00
Marc Vertes
9ddecfa121 fix: correct index for embedded binary method receiver
When searching for a binary method on structures, look up on embedded
fields first, otherwise the resulting index is incorrect, as
reflect.Type.MethodByName succeeds also on container struct.

Fixes #834.
2020-09-09 12:22:03 +02:00
mpl
04770a4b81 interp: fix data races (#839)
This change fixes two distinct data races:

1) some global vars of type *itype of the interp package are actually
mutated during the lifecycle of an Interpreter. Even worse: if more than
one Interpreter instance are created and used at a given time, they are
actually racing each other for these global vars.
Therefore, this change replaces these global vars with generator
functions that create the needed type on the fly.

2) the symbols given as argument of Interpreter.Use were directly copied
as reference (since they're maps) when mapped inside an Interpreter
instance. Since the usual case is to give the symbols from the stdlib
package, it means when the interpreter mutates its own symbols in
fixStdio, it would actually mutate the corresponding global vars of the
stdlib package. Again, this is at least racy as soon as several
instances of an Intepreter are concurrently running.
This change fixes the race by making sure Interpreter.Use actually
copies the symbol values instead of copying the references.
2020-09-09 11:59:07 +02:00
Marc Vertes
341c69d922 feat: configure stdin, stdout and stderr per interpreter
The goal is to provide greater control of input, output and error
streams of the interpreter. It is now possible to specify those
as options when creating a new interpreter. The provided values
are propagated to relevant stdlib symbols (i.e fmt.Print, etc).
Care is taken to not update the global variables os.Stdout, os.Stdin
and os.Stderr, as to not interfere with the host process.

The REPL function is now simplified. The deprecated version is removed.

The tests are updated to take advantage of the simplified access
to the interpreter output and errors.

Fixes #752.
2020-08-31 15:42:03 +02:00
mpl
f4cc059e3e TestEvalScanner: "fix" data race
When running TestEvalScanner with -race=true, one can observe a data race such as:

```
WARNING: DATA RACE
Read at 0x0000029f3d68 by goroutine 52:
  github.com/containous/yaegi/interp.(*itype).defaultType()
      /Users/mpl/src/github.com/containous/yaegi/interp/type.go:1466 +0x572
...
  github.com/containous/yaegi/interp.(*Interpreter).EvalWithContext.func1()
      /Users/mpl/src/github.com/containous/yaegi/interp/interp.go:501 +0xf0

Previous write at 0x0000029f3d68 by goroutine 43:
  github.com/containous/yaegi/interp.(*itype).refType()
      /Users/mpl/src/github.com/containous/yaegi/interp/type.go:1419 +0x854
  github.com/containous/yaegi/interp.(*itype).TypeOf()
      /Users/mpl/src/github.com/containous/yaegi/interp/type.go:1427 +0xa6
...
  github.com/containous/yaegi/interp.(*Interpreter).EvalWithContext.func1()
      /Users/mpl/src/github.com/containous/yaegi/interp/interp.go:501 +0xf0
```

Before this change, since closing the pipe to the REPL is done in a defer, it
means that all the i.REPL calls (and hence each goroutine for each of these
calls) are kept running and alive until the very end of the test. It should not
matter, since a new interpreter is created for each test case, and thus all the
i.REPL calls should be completely independent from each other.

And yet, by wrapping each test case in a function call, and thus making each
i.REPL call terminate as soon as the test case is over, the data race seems to
be fixed. This could suggest that the separate i.REPL calls from separate
interpreter instances are somehow sharing some memory, but I do not know how to
explain that.

The problem has yet to be fully understood, but at least this change restores
the test, without making the CI fail again.
2020-08-31 12:46:03 +02:00
mpl
535e7e1c42 interp: enable declaration errors detection at parsing time
The Go parser is able to detect some (but not all) (re-)decleration errors,
if the DeclarationErrors flag is enabled, which was not done so far.

This PR therefore enables that flag, which allows the removal of some of
the now unneeded code that was recently added to support redeclarations.

Fixes #811
2020-08-28 14:36:00 +02:00
mpl
cb0f3a77bb REPL: retry with full wrapping for anonymous func calls
In interactive mode, a line starting with the "func" keyword is usually
"wrapped", by prepending to it a "package main" statement, to make it
a valid piece of Go source code.

However, when the line is actually an anonymous function call, such as:

func() { println(3) }()

then this wrapping is not enough, as this is not valid Go in the global
context. Therefore, this kind of of expression must also be wrapped
inside a main func (as is the default case for most REPL inputs).

Since the detection and handling of such a case turned out to be quite
unelegant, this PR instead introduces a retrying phase when a parsing
error occurs for a particular class of cases. That is to say, when a
"func expression" wrapped in a main package fails to be parsed, it is
then wrapped in a main func before parsing is retried.

N.B. TestEvalScanner has been disabled for this change, because the additional test cases revealed a (most-likely already existing) data race.

Fixes #721
2020-08-28 10:28:15 +02:00
Marc Vertes
b1279d0a21 feature: improve handling of interrupt signal in REPL
The input scanning is now performed in a sub goroutine and
the interrupt is listened in another goroutine, either to cancel Eval
or to cancel the current line scan.
Entering a '\n' after a 'Ctrl-C` to get the prompt is
not necessary anymore.
2020-08-27 15:04:04 +02:00
Nicholas Wiersma
f3f9ffaea7 feat: add builtin type checking
This adds type checking for builtin functions. It also refactors builtin names into constants due to the number of times they are used.
2020-08-27 14:02:04 +02:00
Nicholas Wiersma
e332a6b3be fix: check array size symbol kind
When determining the size of an array and a symbol is found, the symbol must be a const for the type to be valid. 

While it makes sense for this check to be done in type checking, the type can be determined by GTA which would then fail. For now this check is done when getting the node type.

Fixes #825
2020-08-27 11:52:04 +02:00
Nicholas Wiersma
358a57b4b9 feat: add star expression type checking
This adds type checking to StarExpr. This also fixes a bug in assignment where the symbol type was updated but not the scope type associated with it.
2020-08-21 10:56:03 +02:00
Nicholas Wiersma
3640f2f820 feat: add type assertion expression type checking
This adds type checking to TypeAssertExpr. In order to allow for this, method types now have a receiver type in both reflect and native cases.
2020-08-20 17:06:05 +02:00
mpl
3faa47c61e interp: take into account embedded property of struct field
The trick is that in reflect, the field is called Anonymous, which is
actually a different notion in the Go spec/vocable.

n.b. only works for non-recursive types for now.

Fixes #781
2020-08-20 14:51:14 +02:00
mpl
896bfeb5a1 interp: new EvalPath API
The recent changes that added some redeclaration checks implicitly added more
strictness related to namespaces and scopes which, among other things, broke
some uses that "accidentally" used to work.

For example, given

const script1 = `
	import "fmt"

	// more code
`
const script2 = `
	import "fmt"

	// some other code
`
If one Evals script1, then script2, with the same interpreter, without
specifying any scope, as the two fragments would be considered part of the same
(.go file) scope by default, a redeclaration error would be triggered because
import "fmt" is seen twice.

A work-around would have been to specify (a different) i.Name before each Eval
call, so that each script is considered as coming from a different .go file, and
hence are respectively in different scopes with respect to imports.

That lead us to realize we had to make specifying things such as file-scope, and
"incremental mode" (aka REPL), more obvious in the context of an Eval call.

In addition, we want to lay down the foundations for Yaegi being able to behave
more like the go tool wrt to various inputs, i.e. it should be able to take a
package directory, or an import path, as input, instead of just a .go file.

Hence the introduction of a new kind of Eval method (whose signature is not fixed yet):

func (interp *Interpreter) EvalPath(path string) (res reflect.Value, err error)

It partially solves the problem described above because:

1. the path given to EvalPath can be used as the file-scope hint mentioned
above, for now (even though the related implementation details might change).
2. Eval always runs in incremental mode, whereas EvalPath always runs in
non-incremental mode, hence clarifying the situation in that respect.

And to avoid confusion, the Name field of Interpreter is now non-exported,
since it is somewhat redundant with the path argument of EvalPath.

Note that #731 is not fully fixed (and might never be), as a requirement of the
proposed solution is to move the input bits of code into respective files
(instead of leaving them as strings).

Finally, some related bugfixes, documention changes, and some refactoring have
been included. Notably, there is no "empty scope" anymore, i.e. name defaults
to "_.go" when it is not specified.

Updates #731
Fixes #778
Fixes #798
Fixes #789 

Co-authored-by: Marc Vertes <mvertes@free.fr>
2020-08-20 13:14:15 +02:00
Nicholas Wiersma
1029d102e5 feat: add slice expression type checking
This adds type checking to SliceExpr as well as handling any required constant type conversion.

It should be noted that the check that `high` and `max` are not nil in a 3-index slice has been left out as this check is handled before type checking. Tests have been included for these cases.
2020-08-19 16:04:05 +02:00
Marc Vertes
065d4fa4d7 fix: add mutual exclusion locks for cancelable select
In a couple of occurences, tests with enabled race detector exposed
some concurrent accesses to the cancelation callback used in select and
channel operations send and recv for EvalWithContext. This change ensure
that all accesses to this object are protected by mutex.

Fixes #815.
2020-08-19 13:52:04 +02:00
mpl
332becf95d interp: more tests for ignoreScannerError
They cover the extra cases that were "sneakily" added
after 611a8c37fa
2020-08-14 15:04:11 +02:00
Nicholas Wiersma
da9e6a0d6c fix: composite literal type check
In the case of a pointer or alias composite literal expression, `compositeGenerator` changes the type to remove the pointer or alias. This causes a nested composite literal to have the wrong type.

Instead of changing the node type, the removal of the pointer or alias is moved to the runtime, allowing the node type to remain unchanged. This fixes potential issues in the type checking.
2020-08-14 12:14:03 +02:00
Nicholas Wiersma
913680d1ed feat: add call expression (not builtin) type checking
This adds type checking to CallExpr (excluding builtin type checking, as that is a PR in its own right) as well as handling any required constant type conversion.

This also changes constant strings and runes to be represented as `constant.Value`. Runes change `rval` type at CFG typing time to avoid having to type at AST time. There are also changes to importSpecs and `stdlib` to account for the string change. With this all `untyped` types should now be `constant.Value`s, although errors are still not returned if this is not the case to be sure we do not break things.

This also fixed a bug in `itype.methods` that would panic if the type was recursive.
2020-08-14 12:02:04 +02:00
Ludovic Fernandez
a004809fc2 fix: main command for goreleaser. 2020-08-13 14:22:03 +02:00
489 changed files with 3661 additions and 1148 deletions

View File

@@ -3,7 +3,7 @@ project_name: yaegi
builds:
- id: yaegi
binary: yaegi
main: ./cmd/yaegi/yaegi.go
main: ./cmd/yaegi/
goos:
- darwin
@@ -28,7 +28,7 @@ builds:
- id: goexports
binary: goexports
main: ./cmd/goexports/goexports.go
main: ./cmd/goexports/
goos:
- darwin

View File

@@ -27,7 +27,7 @@ env:
global:
- GO111MODULE=on
go_import_path: github.com/containous/yaegi
go_import_path: github.com/traefik/yaegi
before_install:
# Install linters and misspell

View File

@@ -3,7 +3,7 @@
Yaegi is an open source project, and your feedback and contributions
are needed and always welcome.
[Issues] and [Pull Requests] are opened at https://github.com/containous/yaegi.
[Issues] and [Pull Requests] are opened at https://github.com/traefik/yaegi.
Non trivial changes should be discussed with the project maintainers by
opening a [Feature Request] clearly explaining rationale, background
@@ -13,12 +13,12 @@ discussions.
Once the proposal is approved, a Pull Request can be opened. If you want
to provide early visibility to reviewers, create a [Draft Pull Request].
We will also require you to sign the [Containous Contributor License Agreement]
We will also require you to sign the [Traefik Contributor License Agreement]
after you submit your first pull request to this project. The link to sign the
agreement will be presented to you in the web interface of the pull request.
[Issues]: https://github.com/containous/yaegi/issues
[Pull Requests]: https://github.com/containous/yaegi/issues
[Feature Request]: https://github.com/containous/yaegi/issues/new?template=feature_request.md
[Issues]: https://github.com/traefik/yaegi/issues
[Pull Requests]: https://github.com/traefik/yaegi/issues
[Feature Request]: https://github.com/traefik/yaegi/issues/new?template=feature_request.md
[Draft Pull Request]: https://github.blog/2019-02-14-introducing-draft-pull-requests/
[Containous Contributor License Agreement]: https://cla-assistant.io/containous/yaegi
[Traefik Labs Contributor License Agreement]: https://cla-assistant.io/traefik/yaegi

View File

@@ -187,6 +187,7 @@
identification within third-party archives.
Copyright 2019 Containous SAS
Copyright 2020 Traefik Labs SAS
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@@ -2,9 +2,9 @@
<img width="400" src="doc/images/yaegi.png" alt="Yaegi" title="Yaegi" />
</p>
[![release](https://img.shields.io/github/tag-date/containous/yaegi.svg?label=alpha)](https://github.com/containous/yaegi/releases)
[![Build Status](https://travis-ci.com/containous/yaegi.svg?branch=master)](https://travis-ci.com/containous/yaegi)
[![GoDoc](https://godoc.org/github.com/containous/yaegi?status.svg)](https://godoc.org/github.com/containous/yaegi)
[![release](https://img.shields.io/github/tag-date/traefik/yaegi.svg?label=alpha)](https://github.com/traefik/yaegi/releases)
[![Build Status](https://travis-ci.com/traefik/yaegi.svg?branch=master)](https://travis-ci.com/traefik/yaegi)
[![GoDoc](https://godoc.org/github.com/traefik/yaegi?status.svg)](https://godoc.org/github.com/traefik/yaegi)
[![Discourse status](https://img.shields.io/discourse/https/community.containo.us/status?label=Community&style=social)](https://community.containo.us/c/yaegi)
Yaegi is Another Elegant Go Interpreter.
@@ -25,13 +25,13 @@ It powers executable Go scripts and plugins, in embedded interpreters or interac
### Go package
```go
import "github.com/containous/yaegi/interp"
import "github.com/traefik/yaegi/interp"
```
### Command-line executable
```bash
go get -u github.com/containous/yaegi/cmd/yaegi
go get -u github.com/traefik/yaegi/cmd/yaegi
```
Note that you can use [rlwrap](https://github.com/hanslub42/rlwrap) (install with your favorite package manager),
@@ -47,8 +47,8 @@ Create an interpreter with `New()`, run Go code with `Eval()`:
package main
import (
"github.com/containous/yaegi/interp"
"github.com/containous/yaegi/stdlib"
"github.com/traefik/yaegi/interp"
"github.com/traefik/yaegi/stdlib"
)
func main() {
@@ -81,7 +81,7 @@ The following program is compiled ahead of time, except `bar()` which is interpr
```go
package main
import "github.com/containous/yaegi/interp"
import "github.com/traefik/yaegi/interp"
const src = `package foo
func Bar(s string) string { return s + "-Foo" }`
@@ -155,11 +155,11 @@ Documentation about Yaegi commands and libraries can be found at usual [godoc.or
Beside the known [bugs] which are supposed to be fixed in the short term, there are some limitations not planned to be addressed soon:
- assembly files (`.s`) are not supported
- calling C code is not supported (no virtual "C" package)
- interfaces to be used from the pre-compiled code can not be added dynamically, as it is required to pre-compile interface wrappers
- representation of types by `reflect` and printing values using %T may give different results between compiled mode and interpreted mode
- interpreting computation intensive code is likely to remain significantly slower than in compiled mode
- Assembly files (`.s`) are not supported.
- Calling C code is not supported (no virtual "C" package).
- Interfaces to be used from the pre-compiled code can not be added dynamically, as it is required to pre-compile interface wrappers.
- Representation of types by `reflect` and printing values using %T may give different results between compiled mode and interpreted mode.
- Interpreting computation intensive code is likely to remain significantly slower than in compiled mode.
## Contributing
@@ -170,7 +170,7 @@ Beside the known [bugs] which are supposed to be fixed in the short term, there
[Apache 2.0][License].
[specs]: https://golang.org/ref/spec
[docs]: https://godoc.org/github.com/containous/yaegi
[license]: https://github.com/containous/yaegi/blob/master/LICENSE
[github]: https://github.com/containous/yaegi
[bugs]: https://github.com/containous/yaegi/issues?q=is%3Aissue+is%3Aopen+label%3Abug
[docs]: https://godoc.org/github.com/traefik/yaegi
[license]: https://github.com/traefik/yaegi/blob/master/LICENSE
[github]: https://github.com/traefik/yaegi
[bugs]: https://github.com/traefik/yaegi/issues?q=is%3Aissue+is%3Aopen+label%3Abug

16
_test/a42.go Normal file
View File

@@ -0,0 +1,16 @@
package main
import (
"encoding/binary"
"fmt"
)
func main() {
var b [8]byte
binary.LittleEndian.PutUint64(b[:], uint64(1))
fmt.Println(b)
}
// Output:
// [1 0 0 0 0 0 0 0]

View File

@@ -1,6 +1,6 @@
package foo
import bar "github.com/containous/yaegi/_test/b2/foo"
import bar "github.com/traefik/yaegi/_test/b2/foo"
var Desc = "in b1/foo"

View File

@@ -1,5 +1,5 @@
package c1
import "github.com/containous/yaegi/_test/c2"
import "github.com/traefik/yaegi/_test/c2"
var C1 = c2.C2 + "x"

View File

@@ -1,5 +1,5 @@
package c2
import "github.com/containous/yaegi/_test/c1"
import "github.com/traefik/yaegi/_test/c1"
var C2 = c1.C1 + "Y"

51
_test/cli4.go Normal file
View File

@@ -0,0 +1,51 @@
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"net/http/httptest"
)
type mw1 struct {
next http.Handler
}
func (m *mw1) ServeHTTP(rw http.ResponseWriter, rq *http.Request) {
m.next.ServeHTTP(rw, rq)
}
type mw0 struct{}
func (m *mw0) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Welcome to my website!")
}
func main() {
m0 := &mw0{}
m1 := &mw1{m0}
mux := http.NewServeMux()
mux.HandleFunc("/", m1.ServeHTTP)
server := httptest.NewServer(mux)
defer server.Close()
client(server.URL)
}
func client(uri string) {
resp, err := http.Get(uri)
if err != nil {
log.Fatal(err)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(body))
}
// Output:
// Welcome to my website!

51
_test/cli5.go Normal file
View File

@@ -0,0 +1,51 @@
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"net/http/httptest"
)
type mw1 struct {
next http.Handler
}
func (m *mw1) ServeHTTP(rw http.ResponseWriter, rq *http.Request) {
m.next.ServeHTTP(rw, rq)
}
type mw0 struct{}
func (m *mw0) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Welcome to my website!")
}
func main() {
m0 := &mw0{}
m1 := &mw1{next: m0}
mux := http.NewServeMux()
mux.HandleFunc("/", m1.ServeHTTP)
server := httptest.NewServer(mux)
defer server.Close()
client(server.URL)
}
func client(uri string) {
resp, err := http.Get(uri)
if err != nil {
log.Fatal(err)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(body))
}
// Output:
// Welcome to my website!

52
_test/cli6.go Normal file
View File

@@ -0,0 +1,52 @@
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"net/http/httptest"
)
type T struct {
http.ResponseWriter
}
type mw1 struct {
next http.Handler
}
func (m *mw1) ServeHTTP(rw http.ResponseWriter, rq *http.Request) {
t := &T{
ResponseWriter: rw,
}
x := t.Header()
fmt.Fprint(rw, "Welcome to my website!", x)
}
func main() {
m1 := &mw1{}
mux := http.NewServeMux()
mux.HandleFunc("/", m1.ServeHTTP)
server := httptest.NewServer(mux)
defer server.Close()
client(server.URL)
}
func client(uri string) {
resp, err := http.Get(uri)
if err != nil {
log.Fatal(err)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(body))
}
// Output:
// Welcome to my website!map[]

View File

@@ -3,7 +3,7 @@ package main
import (
"fmt"
"github.com/containous/yaegi/_test/ct1"
"github.com/traefik/yaegi/_test/ct1"
)
type T struct {

View File

@@ -2,13 +2,14 @@ package main
import (
"log"
"os"
"github.com/containous/yaegi/interp"
"github.com/traefik/yaegi/interp"
)
func main() {
log.SetFlags(log.Lshortfile)
i := interp.New(interp.Options{})
i := interp.New(interp.Options{Stdout: os.Stdout})
if _, err := i.Eval(`func f() (int, int) { return 1, 2 }`); err != nil {
log.Fatal(err)
}

View File

@@ -1,6 +1,6 @@
package foo
import "github.com/containous/yaegi/_test/foo/boo"
import "github.com/traefik/yaegi/_test/foo/boo"
var Bar = "BARR"
var Boo = boo.Boo

View File

@@ -1,5 +1,5 @@
package foo
import "github.com/containous/yaegi/_test/foo/boo"
import "github.com/traefik/yaegi/_test/foo/boo"
var Bir = boo.Boo + "22"

View File

@@ -1,6 +1,6 @@
package main
import "github.com/containous/yaegi/_test/foo"
import "github.com/traefik/yaegi/_test/foo"
func main() { println(foo.Bar, foo.Boo) }

View File

@@ -1,6 +1,6 @@
package main
import "github.com/containous/yaegi/_test/p1"
import "github.com/traefik/yaegi/_test/p1"
func main() { println("num:", p1.Uint32()) }

View File

@@ -1,6 +1,6 @@
package main
import boo "github.com/containous/yaegi/_test/foo"
import boo "github.com/traefik/yaegi/_test/foo"
func main() { println(boo.Bar, boo.Boo, boo.Bir) }

View File

@@ -1,6 +1,6 @@
package main
import "github.com/containous/yaegi/_test/c1"
import "github.com/traefik/yaegi/_test/c1"
func main() {
println(c1.C1)
@@ -8,4 +8,4 @@ func main() {
// Error:
// import cycle not allowed
// imports github.com/containous/yaegi/_test/c1
// imports github.com/traefik/yaegi/_test/c1

View File

@@ -1,6 +1,6 @@
package main
import "github.com/containous/yaegi/_test/foo-bar"
import bar "github.com/traefik/yaegi/_test/foo-bar"
func main() {
println(bar.Name)

View File

@@ -1,6 +1,6 @@
package main
import "github.com/containous/yaegi/_test/b1/foo"
import "github.com/traefik/yaegi/_test/b1/foo"
func main() {
println(foo.Desc)

View File

@@ -1,6 +1,6 @@
package main
import "github.com/containous/yaegi/_test/baz-bat"
import "github.com/traefik/yaegi/_test/baz-bat"
func main() {
println(baz.Name)

View File

@@ -3,14 +3,14 @@ package main
import (
"log"
"github.com/containous/yaegi/interp"
"github.com/traefik/yaegi/interp"
)
func main() {
log.SetFlags(log.Lshortfile)
i := interp.New(interp.Options{})
i.Use(interp.Symbols)
if _, err := i.Eval(`import "github.com/containous/yaegi/interp"`); err != nil {
if _, err := i.Eval(`import "github.com/traefik/yaegi/interp"`); err != nil {
log.Fatal(err)
}
if _, err := i.Eval(`i := interp.New(interp.Options{})`); err != nil {
@@ -20,6 +20,3 @@ func main() {
log.Fatal(err)
}
}
// Output:
// 42

View File

@@ -1,7 +1,7 @@
package main
import (
"github.com/containous/yaegi/interp"
"github.com/traefik/yaegi/interp"
)
func main() {

View File

@@ -1,13 +1,13 @@
package main
import (
"github.com/containous/yaegi/interp"
"github.com/traefik/yaegi/interp"
)
func main() {
i := interp.New(interp.Opt{})
i.Use(interp.ExportValue, interp.ExportType)
i.Eval(`import "github.com/containous/yaegi/interp"`)
i.Eval(`import "github.com/traefik/yaegi/interp"`)
i.Eval(`i := interp.New(interp.Opt{})`)
i.Eval(`i.Eval("println(42)")`)
}

16
_test/ipp_as_key.go Normal file
View File

@@ -0,0 +1,16 @@
package main
import (
"guthib.com/toto" // pkg name is actually titi
"guthib.com/tata" // pkg name is actually tutu
)
func main() {
println("Hello", titi.Quux())
println("Hello", tutu.Quux())
}
// GOPATH:testdata/redeclaration-global7
// Output:
// Hello bar
// Hello baz

14
_test/m1/main.go Normal file
View File

@@ -0,0 +1,14 @@
package main
import (
"fmt"
"testing"
)
func main() {
fmt.Println("vim-go")
}
func TestWeird(t *testing.T) {
fmt.Println("in TestWeird")
}

17
_test/m1/main_test.go Normal file
View File

@@ -0,0 +1,17 @@
package main
import (
"fmt"
"math/rand"
"testing"
)
func TestMain(t *testing.T) {
fmt.Println("in test")
}
func BenchmarkMain(b *testing.B) {
for i := 0; i < b.N; i++ {
rand.Int()
}
}

14
_test/method35.go Normal file
View File

@@ -0,0 +1,14 @@
package main
import "strconv"
func main() {
var err error
_, err = strconv.Atoi("erwer")
if _, ok := err.(*strconv.NumError); ok {
println("here")
}
}
// Output:
// here

16
_test/pkgname0.go Normal file
View File

@@ -0,0 +1,16 @@
package main
import (
"guthib.com/bar" // pkg name is actually quux
baz "guthib.com/baz" // pkg name is also quux, force it to baz.
)
func main() {
println("Hello", quux.Quux())
println("Hello", baz.Quux())
}
// GOPATH:testdata/redeclaration-global7
// Output:
// Hello bar
// Hello baz

13
_test/pkgname1.go Normal file
View File

@@ -0,0 +1,13 @@
package main
import (
"guthib.com/bar" // pkg name is actually quux
)
func main() {
println("Hello", bar.Quux()) // bar should not be a known symbol.
}
// GOPATH:testdata/redeclaration-global7
// Error:
// ../_test/pkgname1.go:8:19: undefined: bar

13
_test/pkgname2.go Normal file
View File

@@ -0,0 +1,13 @@
package main
import (
"guthib.com/toto" // pkg name is actually titi
)
func main() {
println("Hello", titi.Quux())
}
// GOPATH:testdata/redeclaration-global7
// Output:
// Hello bar

View File

@@ -12,4 +12,5 @@ func main() {
}
// Error:
// ../_test/redeclaration-global5.go:5:1: time redeclared in this block
// ../_test/redeclaration-global5.go:5:6: time redeclared in this block
// previous declaration at ../_test/redeclaration-global5.go:3:5

View File

@@ -0,0 +1,14 @@
package main
import (
"guthib.com/bar" // pkg name is actually quux
"guthib.com/baz" // pkg name is also quux
)
func main() {
println("Hello", quux.Quux())
}
// GOPATH:testdata/redeclaration-global7
// Error:
// ../_test/redeclaration-global7.go:5:2: quux/redeclaration-global7.go redeclared as imported package name

22
_test/struct55.go Normal file
View File

@@ -0,0 +1,22 @@
package main
import (
"log"
"os"
)
type Logger struct {
m []*log.Logger
}
func (l *Logger) Infof(format string, args ...interface{}) {
l.m[0].Printf(format, args...)
}
func main() {
l := &Logger{m: []*log.Logger{log.New(os.Stdout, "", log.Lmsgprefix)}}
l.Infof("test %s", "test")
}
// Output:
// test test

23
_test/struct56.go Normal file
View File

@@ -0,0 +1,23 @@
package main
import (
"encoding/json"
"fmt"
)
type A struct {
IA InnerA
}
type InnerA struct {
Timestamp int64
}
func main() {
a := &A{}
b, _ := json.Marshal(a)
fmt.Println(string(b))
}
// Output:
// {"IA":{"Timestamp":0}}

23
_test/struct57.go Normal file
View File

@@ -0,0 +1,23 @@
package main
import (
"encoding/json"
"fmt"
)
type A struct {
InnerA
}
type InnerA struct {
Timestamp int64
}
func main() {
a := &A{}
b, _ := json.Marshal(a)
fmt.Println(string(b))
}
// Output:
// {"Timestamp":0}

View File

@@ -3,7 +3,7 @@
package main
import _ "github.com/containous/yaegi/_test/ct"
import _ "github.com/traefik/yaegi/_test/ct"
func main() {
println("bye")

View File

@@ -0,0 +1,5 @@
package quux
func Quux() string {
return "bar"
}

View File

@@ -0,0 +1,5 @@
package quux
func Quux() string {
return "baz"
}

View File

@@ -0,0 +1,7 @@
package tutu
import "guthib.com/baz"
func Quux() string {
return quux.Quux()
}

View File

@@ -0,0 +1,7 @@
package titi
import "guthib.com/bar"
func Quux() string {
return quux.Quux()
}

View File

@@ -1,6 +1,6 @@
package main
import "github.com/containous/yaegi/_test/vars"
import "github.com/traefik/yaegi/_test/vars"
func main() {
println(vars.A)

View File

@@ -11,7 +11,7 @@ Usage:
Example:
goexports github.com/containous/yaegi/interp
goexports github.com/traefik/yaegi/interp
The same goexport program is used for all target operating systems and architectures.
The GOOS and GOARCH environment variables set the desired target.
@@ -30,7 +30,7 @@ import (
"runtime"
"strings"
"github.com/containous/yaegi/extract"
"github.com/traefik/yaegi/extract"
)
// genLicense generates the correct LICENSE header text from the provided
@@ -108,9 +108,9 @@ func main() {
var oFile string
if pkgIdent == "syscall" {
oFile = strings.Replace(importPath, "/", "_", -1) + "_" + goos + "_" + goarch + ".go"
oFile = strings.ReplaceAll(importPath, "/", "_") + "_" + goos + "_" + goarch + ".go"
} else {
oFile = strings.Replace(importPath, "/", "_", -1) + ".go"
oFile = strings.ReplaceAll(importPath, "/", "_") + ".go"
}
prefix := runtime.Version()

View File

@@ -10,7 +10,7 @@ import (
"path"
"strings"
"github.com/containous/yaegi/extract"
"github.com/traefik/yaegi/extract"
)
func extractCmd(arg []string) error {
@@ -58,7 +58,7 @@ func extractCmd(arg []string) error {
continue
}
oFile := strings.Replace(importPath, "/", "_", -1) + ".go"
oFile := strings.ReplaceAll(importPath, "/", "_") + ".go"
f, err := os.Create(oFile)
if err != nil {
return err

View File

@@ -36,7 +36,7 @@ func help(arg []string) error {
case Run:
return run([]string{"-h"})
case Test:
return fmt.Errorf("help: test not implemented")
return test([]string{"-h"})
default:
return fmt.Errorf("help: invalid yaegi command: %v", cmd)
}

View File

@@ -8,11 +8,11 @@ import (
"os"
"strings"
"github.com/containous/yaegi/interp"
"github.com/containous/yaegi/stdlib"
"github.com/containous/yaegi/stdlib/syscall"
"github.com/containous/yaegi/stdlib/unrestricted"
"github.com/containous/yaegi/stdlib/unsafe"
"github.com/traefik/yaegi/interp"
"github.com/traefik/yaegi/stdlib"
"github.com/traefik/yaegi/stdlib/syscall"
"github.com/traefik/yaegi/stdlib/unrestricted"
"github.com/traefik/yaegi/stdlib/unsafe"
)
func run(arg []string) error {
@@ -56,14 +56,16 @@ func run(arg []string) error {
}
if cmd != "" {
i.REPL(strings.NewReader(cmd), os.Stderr)
_, err = i.Eval(cmd)
showError(err)
}
if len(args) == 0 {
if interactive || cmd == "" {
i.REPL(os.Stdin, os.Stdout)
_, err = i.REPL()
showError(err)
}
return nil
return err
}
// Skip first os arg to set command line as expected by interpreted main
@@ -71,40 +73,27 @@ func run(arg []string) error {
os.Args = arg[1:]
flag.CommandLine = flag.NewFlagSet(path, flag.ExitOnError)
if isPackageName(path) {
err = runPackage(i, path)
if isFile(path) {
err = runFile(i, path)
} else {
if isDir(path) {
err = runDir(i, path)
} else {
err = runFile(i, path)
}
_, err = i.EvalPath(path)
}
showError(err)
if err != nil {
return err
}
if interactive {
i.REPL(os.Stdin, os.Stdout)
_, err = i.REPL()
showError(err)
}
return nil
return err
}
func isPackageName(path string) bool {
return !strings.HasPrefix(path, "/") && !strings.HasPrefix(path, "./") && !strings.HasPrefix(path, "../")
}
func isDir(path string) bool {
fi, err := os.Lstat(path)
return err == nil && fi.IsDir()
}
func runPackage(i *interp.Interpreter, path string) error {
return fmt.Errorf("runPackage not implemented")
}
func runDir(i *interp.Interpreter, path string) error {
return fmt.Errorf("runDir not implemented")
func isFile(path string) bool {
fi, err := os.Stat(path)
return err == nil && fi.Mode().IsRegular()
}
func runFile(i *interp.Interpreter, path string) error {
@@ -116,17 +105,21 @@ func runFile(i *interp.Interpreter, path string) error {
if s := string(b); strings.HasPrefix(s, "#!") {
// Allow executable go scripts, Have the same behavior as in interactive mode.
s = strings.Replace(s, "#!", "//", 1)
i.REPL(strings.NewReader(s), os.Stdout)
} else {
// Files not starting with "#!" are supposed to be pure Go, directly Evaled.
i.Name = path
_, err := i.Eval(s)
if err != nil {
fmt.Println(err)
if p, ok := err.(interp.Panic); ok {
fmt.Println(string(p.Stack))
}
}
_, err = i.Eval(s)
return err
}
// Files not starting with "#!" are supposed to be pure Go, directly Evaled.
_, err = i.EvalPath(path)
return err
}
func showError(err error) {
if err == nil {
return
}
fmt.Fprintln(os.Stderr, err)
if p, ok := err.(interp.Panic); ok {
fmt.Fprintln(os.Stderr, string(p.Stack))
}
return nil
}

131
cmd/yaegi/test.go Normal file
View File

@@ -0,0 +1,131 @@
package main
import (
"flag"
"fmt"
"go/build"
"os"
"regexp"
"strings"
"testing"
"github.com/traefik/yaegi/interp"
"github.com/traefik/yaegi/stdlib"
"github.com/traefik/yaegi/stdlib/syscall"
"github.com/traefik/yaegi/stdlib/unrestricted"
"github.com/traefik/yaegi/stdlib/unsafe"
)
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
)
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.")
tflag.StringVar(&benchtime, "benchtime", "", "Run enough iterations of each benchmark to take t.")
tflag.StringVar(&count, "count", "", "Run each test and benchmark n times (default 1).")
tflag.StringVar(&cpu, "cpu", "", "Specify a list of GOMAXPROCS values for which the tests or benchmarks should be executed.")
tflag.BoolVar(&failfast, "failfast", false, "Do not start new tests after the first test failure.")
tflag.StringVar(&run, "run", "", "Run only those tests matching a regular expression.")
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(&verbose, "v", false, "Verbose output: log all tests as they are run.")
tflag.Usage = func() {
fmt.Println("Usage: yaegi test [options] [path]")
fmt.Println("Options:")
tflag.PrintDefaults()
}
if err = tflag.Parse(arg); err != nil {
return err
}
args := tflag.Args()
path := "."
if len(args) > 0 {
path = args[0]
}
// Overwrite os.Args with correct flags to setup testing.Init.
tf := []string{""}
if bench != "" {
tf = append(tf, "-test.bench", bench)
}
if benchmem {
tf = append(tf, "-test.benchmem")
}
if benchtime != "" {
tf = append(tf, "-test.benchtime", benchtime)
}
if count != "" {
tf = append(tf, "-test.count", count)
}
if cpu != "" {
tf = append(tf, "-test.cpu", cpu)
}
if failfast {
tf = append(tf, "-test.failfast")
}
if run != "" {
tf = append(tf, "-test.run", run)
}
if short {
tf = append(tf, "-test.short")
}
if timeout != "" {
tf = append(tf, "-test.timeout", timeout)
}
if verbose {
tf = append(tf, "-test.v")
}
testing.Init()
os.Args = tf
flag.Parse()
i := interp.New(interp.Options{GoPath: build.Default.GOPATH, BuildTags: strings.Split(tags, ",")})
i.Use(stdlib.Symbols)
i.Use(interp.Symbols)
if useSyscall {
i.Use(syscall.Symbols)
}
if useUnrestricted {
i.Use(unrestricted.Symbols)
}
if useUnsafe {
i.Use(unsafe.Symbols)
}
if err = i.EvalTest(path); err != nil {
return err
}
benchmarks := []testing.InternalBenchmark{}
tests := []testing.InternalTest{}
for name, sym := range i.Symbols(path) {
switch fun := sym.Interface().(type) {
case func(*testing.B):
benchmarks = append(benchmarks, testing.InternalBenchmark{name, fun})
case func(*testing.T):
tests = append(tests, testing.InternalTest{name, fun})
}
}
testing.Main(regexp.MatchString, tests, benchmarks, nil)
return nil
}

View File

@@ -118,7 +118,7 @@ func main() {
case Run:
err = run(os.Args[2:])
case Test:
err = fmt.Errorf("test not implemented")
err = test(os.Args[2:])
default:
// If no command is given, fallback to default "run" command.
// This allows scripts starting with "#!/usr/bin/env yaegi",

View File

@@ -2,11 +2,13 @@ package main
import (
"bytes"
"context"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"testing"
"time"
)
@@ -108,8 +110,8 @@ func TestYaegiCmdCancel(t *testing.T) {
continue
}
if outBuf.String() != "context canceled\n" {
t.Errorf("unexpected output: %q", &outBuf)
if strings.TrimSuffix(errBuf.String(), "\n") != context.Canceled.Error() {
t.Errorf("unexpected error: %q", &errBuf)
}
}
}

View File

@@ -3,8 +3,8 @@ package clos1
import (
"testing"
"github.com/containous/yaegi/interp"
"github.com/containous/yaegi/stdlib"
"github.com/traefik/yaegi/interp"
"github.com/traefik/yaegi/stdlib"
)
func TestFunctionCall(t *testing.T) {

View File

@@ -4,8 +4,8 @@ import (
"fmt"
"testing"
"github.com/containous/yaegi/interp"
"github.com/containous/yaegi/stdlib"
"github.com/traefik/yaegi/interp"
"github.com/traefik/yaegi/stdlib"
)
func TestGetFunc(t *testing.T) {

View File

@@ -3,7 +3,7 @@ package pkg
import (
"fmt"
"guthib.com/containous/fromage"
"guthib.com/traefik/fromage"
)
func Here() string {

View File

@@ -3,7 +3,7 @@ package pkg
import (
"fmt"
"guthib.com/containous/fromage"
"guthib.com/traefik/fromage"
)
func Here() string {

View File

@@ -3,7 +3,7 @@ package main
import (
"fmt"
"guthib.com/containous/fromage"
"guthib.com/traefik/fromage"
)
func main() {

View File

@@ -3,7 +3,7 @@ package pkg
import (
"fmt"
"guthib.com/containous/fromage"
"guthib.com/traefik/fromage"
)
func Here() string {

View File

@@ -3,7 +3,7 @@ package fromage
import (
"fmt"
"guthib.com/containous/cheese"
"guthib.com/traefik/cheese"
)
func Hello() string {

View File

@@ -3,7 +3,7 @@ package pkg
import (
"fmt"
"guthib.com/containous/fromage"
"guthib.com/traefik/fromage"
)
func Here() string {

View File

@@ -3,8 +3,8 @@ package fromage
import (
"fmt"
"guthib.com/containous/cheese"
"guthib.com/containous/fromage/couteau"
"guthib.com/traefik/cheese"
"guthib.com/traefik/fromage/couteau"
)
func Hello() string {

View File

@@ -3,7 +3,7 @@ package pkg
import (
"fmt"
"guthib.com/containous/fromage"
"guthib.com/traefik/fromage"
)
func Here() string {

View File

@@ -1,11 +0,0 @@
package cheese
import (
"fmt"
"guthib.com/containous/cheese/vin"
)
func Hello() string {
return fmt.Sprintf("Cheese %s", vin.Hello())
}

View File

@@ -3,8 +3,8 @@ package fromage
import (
"fmt"
"guthib.com/containous/cheese"
"guthib.com/containous/fromage/couteau"
"guthib.com/traefik/cheese"
"guthib.com/traefik/fromage/couteau"
)
func Hello() string {

View File

@@ -3,7 +3,7 @@ package cheese
import (
"fmt"
"guthib.com/containous/cheese/vin"
"guthib.com/traefik/cheese/vin"
)
func Hello() string {

View File

@@ -3,7 +3,7 @@ package pkg
import (
"fmt"
"guthib.com/containous/fromage"
"guthib.com/traefik/fromage"
)
func Here() string {

View File

@@ -3,7 +3,7 @@ package cheese
import (
"fmt"
"guthib.com/containous/cheese/vin"
"guthib.com/traefik/cheese/vin"
)
func Hello() string {

View File

@@ -3,8 +3,8 @@ package fromage
import (
"fmt"
"guthib.com/containous/cheese"
"guthib.com/containous/fromage/couteau"
"guthib.com/traefik/cheese"
"guthib.com/traefik/fromage/couteau"
)
func Hello() string {

View File

@@ -3,7 +3,7 @@ package pkg
import (
"fmt"
"guthib.com/containous/fromage"
"guthib.com/traefik/fromage"
)
func Here() string {

View File

@@ -3,8 +3,8 @@ package fromage
import (
"fmt"
"guthib.com/containous/cheese"
"guthib.com/containous/fromage/couteau"
"guthib.com/traefik/cheese"
"guthib.com/traefik/fromage/couteau"
)
func Hello() string {

View File

@@ -0,0 +1,11 @@
package cheese
import (
"fmt"
"guthib.com/traefik/cheese/vin"
)
func Hello() string {
return fmt.Sprintf("Cheese %s", vin.Hello())
}

View File

@@ -3,7 +3,7 @@ package pkg
import (
"fmt"
"guthib.com/containous/vin"
"guthib.com/traefik/vin"
)
func Here() string {

View File

@@ -3,7 +3,7 @@ package cheese
import (
"fmt"
"guthib.com/containous/fromage"
"guthib.com/traefik/fromage"
)
func Hello() string {

View File

@@ -3,7 +3,7 @@ package vin
import (
"fmt"
"guthib.com/containous/cheese"
"guthib.com/traefik/cheese"
)
func Hello() string {

View File

@@ -3,14 +3,12 @@ package pkg
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/containous/yaegi/interp"
"github.com/containous/yaegi/stdlib"
"github.com/traefik/yaegi/interp"
"github.com/traefik/yaegi/stdlib"
)
func TestPackages(t *testing.T) {
@@ -105,46 +103,16 @@ func TestPackages(t *testing.T) {
t.Fatal(err)
}
// Init go interpreter
i := interp.New(interp.Options{GoPath: goPath})
var stdout, stderr bytes.Buffer
i := interp.New(interp.Options{GoPath: goPath, Stdout: &stdout, Stderr: &stderr})
i.Use(stdlib.Symbols) // Use binary standard library
var msg string
if test.evalFile != "" {
// setting i.Name as this is how it's actually done in cmd/yaegi
i.Name = test.evalFile
data, err := ioutil.ReadFile(test.evalFile)
if err != nil {
t.Fatal(err)
}
// TODO(mpl): this is brittle if we do concurrent tests and stuff, do better later.
stdout := os.Stdout
defer func() { os.Stdout = stdout }()
pr, pw, err := os.Pipe()
if err != nil {
t.Fatal(err)
}
os.Stdout = pw
if _, err := i.Eval(string(data)); err != nil {
if _, err := i.EvalPath(test.evalFile); err != nil {
fatalStderrf(t, "%v", err)
}
var buf bytes.Buffer
errC := make(chan error)
go func() {
_, err := io.Copy(&buf, pr)
errC <- err
}()
if err := pw.Close(); err != nil {
fatalStderrf(t, "%v", err)
}
if err := <-errC; err != nil {
fatalStderrf(t, "%v", err)
}
msg = buf.String()
msg = stdout.String()
} else {
// Load pkg from sources
topImport := "github.com/foo/pkg"

View File

@@ -24,7 +24,7 @@ import (
"text/template"
)
const model = `// Code generated by 'github.com/containous/yaegi/extract {{.PkgName}}'. DO NOT EDIT.
const model = `// Code generated by 'github.com/traefik/yaegi/extract {{.PkgName}}'. DO NOT EDIT.
{{.License}}
@@ -216,6 +216,19 @@ func genContent(dest, importPath, license string, p *types.Package, skip map[str
buildTags += ",!windows,!nacl,!plan9"
}
if importPath == "syscall" {
// As per https://golang.org/cmd/go/#hdr-Build_constraints,
// using GOOS=android also matches tags and files for GOOS=linux,
// so exclude it explicitly to avoid collisions (issue #843).
// Also using GOOS=illumos matches tags and files for GOOS=solaris.
switch os.Getenv("GOOS") {
case "android":
buildTags += ",!linux"
case "illumos":
buildTags += ",!solaris"
}
}
b := new(bytes.Buffer)
data := map[string]interface{}{
"Dest": dest,
@@ -247,6 +260,9 @@ func fixConst(name string, val constant.Value, imports map[string]bool) string {
str string
)
switch val.Kind() {
case constant.String:
tok = "STRING"
str = val.ExactString()
case constant.Int:
tok = "INT"
str = val.ExactString()
@@ -269,7 +285,7 @@ func fixConst(name string, val constant.Value, imports map[string]bool) string {
imports["go/constant"] = true
imports["go/token"] = true
return fmt.Sprintf("constant.MakeFromLiteral(\"%s\", token.%s, 0)", str, tok)
return fmt.Sprintf("constant.MakeFromLiteral(%q, token.%s, 0)", str, tok)
}
// importPath checks whether pkgIdent is an existing directory relative to

View File

@@ -8,7 +8,7 @@ import (
"testing"
)
var expectedOutput = `// Code generated by 'github.com/containous/yaegi/extract guthib.com/baz'. DO NOT EDIT.
var expectedOutput = `// Code generated by 'github.com/traefik/yaegi/extract guthib.com/baz'. DO NOT EDIT.
// +build BUILD_TAGS

View File

@@ -1,7 +1,7 @@
package yaegi
//go:generate go generate github.com/containous/yaegi/interp
//go:generate go generate github.com/containous/yaegi/cmd/goexports
//go:generate go generate github.com/containous/yaegi/stdlib
//go:generate go generate github.com/containous/yaegi/stdlib/syscall
//go:generate go generate github.com/containous/yaegi/stdlib/unsafe
//go:generate go generate github.com/traefik/yaegi/interp
//go:generate go generate github.com/traefik/yaegi/cmd/goexports
//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/unsafe

2
go.mod
View File

@@ -1,3 +1,3 @@
module github.com/containous/yaegi
module github.com/traefik/yaegi
go 1.12

View File

@@ -32,7 +32,7 @@ func {{$name}}(n *node) {
case reflect.String:
switch {
case c0.rval.IsValid():
s0 := c0.rval.String()
s0 := vString(c0.rval)
v1 := genValue(c1)
n.exec = func(f *frame) bltn {
dest(f).SetString(s0 {{$op.Name}} v1(f).String())
@@ -40,7 +40,7 @@ func {{$name}}(n *node) {
}
case c1.rval.IsValid():
v0 := genValue(c0)
s1 := c1.rval.String()
s1 := vString(c1.rval)
n.exec = func(f *frame) bltn {
dest(f).SetString(v0(f).String() {{$op.Name}} s1)
return next
@@ -202,7 +202,7 @@ func {{$name}}Const(n *node) {
{{- end}}
{{- if $op.Str}}
case isString(t):
n.rval.SetString(v0.String() {{$op.Name}} v1.String())
n.rval.SetString(vString(v0) {{$op.Name}} vString(v1))
{{- end}}
{{- if $op.Float}}
case isComplex(t):
@@ -233,7 +233,7 @@ func {{$name}}Assign(n *node) {
{{- if $op.Str}}
case reflect.String:
v0 := genValueString(c0)
v1 := c1.rval.String()
v1 := vString(c1.rval)
n.exec = func(f *frame) bltn {
v, s := v0(f)
v.SetString(s {{$op.Name}} v1)
@@ -496,7 +496,7 @@ func {{$name}}(n *node) {
case isString(t0) || isString(t1):
switch {
case c0.rval.IsValid():
s0 := c0.rval.String()
s0 := vString(c0.rval)
v1 := genValueString(n.child[1])
if n.fnext != nil {
fnext := getExec(n.fnext)
@@ -517,7 +517,7 @@ func {{$name}}(n *node) {
}
}
case c1.rval.IsValid():
s1 := c1.rval.String()
s1 := vString(c1.rval)
v0 := genValueString(n.child[0])
if n.fnext != nil {
fnext := getExec(n.fnext)

View File

@@ -1,6 +1,7 @@
package interp
import (
"errors"
"fmt"
"go/ast"
"go/constant"
@@ -9,6 +10,7 @@ import (
"go/token"
"reflect"
"strconv"
"strings"
"sync/atomic"
)
@@ -339,30 +341,48 @@ func (interp *Interpreter) firstToken(src string) token.Token {
return tok
}
func ignoreError(err error, src string) bool {
se, ok := err.(scanner.ErrorList)
if !ok {
return false
}
if len(se) == 0 {
return false
}
return ignoreScannerError(se[0], src)
}
func wrapInMain(src string) string {
return fmt.Sprintf("package main; func main() {%s}", src)
}
// Note: no type analysis is performed at this stage, it is done in pre-order
// processing of CFG, in order to accommodate forward type declarations
// processing of CFG, in order to accommodate forward type declarations.
// ast parses src string containing Go code and generates the corresponding AST.
// The package name and the AST root node are returned.
func (interp *Interpreter) ast(src, name string) (string, *node, error) {
inRepl := name == ""
// The given name is used to set the filename of the relevant source file in the
// interpreter's FileSet.
func (interp *Interpreter) ast(src, name string, inc bool) (string, *node, error) {
var inFunc bool
var mode parser.Mode
mode := parser.DeclarationErrors
// Allow incremental parsing of declarations or statements, by inserting
// them in a pseudo file package or function. Those statements or
// declarations will be always evaluated in the global scope
if inRepl {
switch interp.firstToken(src) {
// declarations will be always evaluated in the global scope.
var tok token.Token
if inc {
tok = interp.firstToken(src)
switch tok {
case token.PACKAGE:
// nothing to do
// nothing to do.
case token.CONST, token.FUNC, token.IMPORT, token.TYPE, token.VAR:
src = "package main;" + src
default:
inFunc = true
src = "package main; func main() {" + src + "}"
src = wrapInMain(src)
}
// Parse comments in REPL mode, to allow tag setting
// Parse comments in REPL mode, to allow tag setting.
mode |= parser.ParseComments
}
@@ -372,7 +392,22 @@ func (interp *Interpreter) ast(src, name string) (string, *node, error) {
f, err := parser.ParseFile(interp.fset, name, src, mode)
if err != nil {
return "", nil, err
// only retry if we're on an expression/statement about a func
if !inc || tok != token.FUNC {
return "", nil, err
}
// do not bother retrying if we know it's an error we're going to ignore later on.
if ignoreError(err, src) {
return "", nil, err
}
// do not lose initial error, in case retrying fails.
initialError := err
// retry with default source code "wrapping", in the main function scope.
src := wrapInMain(strings.TrimPrefix(src, "package main;"))
f, err = parser.ParseFile(interp.fset, name, src, mode)
if err != nil {
return "", nil, initialError
}
}
setYaegiTags(&interp.context, f.Comments)
@@ -487,20 +522,12 @@ func (interp *Interpreter) ast(src, name string) (string, *node, error) {
n.ident = a.Value
switch a.Kind {
case token.CHAR:
// Char cannot be converted to a const here as we cannot tell the type.
v, _, _, _ := strconv.UnquoteChar(a.Value[1:len(a.Value)-1], '\'')
n.rval = reflect.ValueOf(v)
case token.FLOAT:
case token.FLOAT, token.IMAG, token.INT, token.STRING:
v := constant.MakeFromLiteral(a.Value, a.Kind, 0)
n.rval = reflect.ValueOf(v)
case token.IMAG:
v := constant.MakeFromLiteral(a.Value, a.Kind, 0)
n.rval = reflect.ValueOf(v)
case token.INT:
v := constant.MakeFromLiteral(a.Value, a.Kind, 0)
n.rval = reflect.ValueOf(v)
case token.STRING:
v, _ := strconv.Unquote(a.Value)
n.rval = reflect.ValueOf(v)
}
st.push(n, nod)
@@ -859,10 +886,13 @@ func (interp *Interpreter) ast(src, name string) (string, *node, error) {
})
if inFunc {
// Incremental parsing: statements were inserted in a pseudo function.
// Set root to function body so its statements are evaluated in global scope
// Set root to function body so its statements are evaluated in global scope.
root = root.child[1].child[3]
root.anc = nil
}
if pkgName == "" {
return "", root, errors.New("no package name found")
}
return pkgName, root, err
}

View File

@@ -5,6 +5,7 @@ import (
"go/build"
"go/parser"
"path"
"path/filepath"
"strconv"
"strings"
)
@@ -129,12 +130,15 @@ func goMinorVersion(ctx *build.Context) int {
}
// skipFile returns true if file should be skipped.
func skipFile(ctx *build.Context, p string) bool {
func skipFile(ctx *build.Context, p string, skipTest bool) bool {
if !strings.HasSuffix(p, ".go") {
return true
}
p = strings.TrimSuffix(path.Base(p), ".go")
if strings.HasSuffix(p, "_test") {
if pp := filepath.Base(p); strings.HasPrefix(pp, "_") || strings.HasPrefix(pp, ".") {
return true
}
if skipTest && strings.HasSuffix(p, "_test") {
return true
}
i := strings.Index(p, "_")
@@ -158,6 +162,7 @@ var knownOs = map[string]bool{
"darwin": true,
"dragonfly": true,
"freebsd": true,
"illumos": true,
"js": true,
"linux": true,
"nacl": true,

View File

@@ -74,7 +74,7 @@ func TestBuildFile(t *testing.T) {
for _, test := range tests {
test := test
t.Run(test.src, func(t *testing.T) {
if r := skipFile(&ctx, test.src); r != test.res {
if r := skipFile(&ctx, test.src, NoTest); r != test.res {
t.Errorf("got %v, want %v", r, test.res)
}
})

View File

@@ -8,6 +8,7 @@ import (
"path/filepath"
"reflect"
"regexp"
"strings"
"unicode"
)
@@ -38,9 +39,9 @@ var constOp = map[action]func(*node){
}
var constBltn = map[string]func(*node){
"complex": complexConst,
"imag": imagConst,
"real": realConst,
bltnComplex: complexConst,
bltnImag: imagConst,
bltnReal: realConst,
}
var identifier = regexp.MustCompile(`([\pL_][\pL_\d]*)$`)
@@ -51,8 +52,8 @@ const nilIdent = "nil"
// and pre-compute frame sizes and indexes for all un-named (temporary) and named
// variables. A list of nodes of init functions is returned.
// Following this pass, the CFG is ready to run.
func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
sc := interp.initScopePkg(pkgID)
func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
sc := interp.initScopePkg(importPath)
check := typecheck{}
var initNodes []*node
var err error
@@ -333,6 +334,8 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
return false
}
recvTypeNode.typ = typ
n.child[2].typ.recv = typ
n.typ.recv = typ
index := sc.add(typ)
if len(fr.child) > 1 {
sc.sym[fr.child[0].ident] = &symbol{index: index, kind: varSym, typ: typ}
@@ -365,19 +368,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
sc.loop = n
case importSpec:
var name, ipath string
if len(n.child) == 2 {
ipath = n.child[1].rval.String()
name = n.child[0].ident
} else {
ipath = n.child[0].rval.String()
name = identifier.FindString(ipath)
}
if interp.binPkg[ipath] != nil && name != "." {
sc.sym[name] = &symbol{kind: pkgSym, typ: &itype{cat: binPkgT, path: ipath}}
} else {
sc.sym[name] = &symbol{kind: pkgSym, typ: &itype{cat: srcPkgT, path: ipath}}
}
// already all done in gta
return false
case typeSpec:
@@ -395,12 +386,6 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
return false
}
if _, exists := sc.sym[typeName]; exists {
// TODO(mpl): find the exact location of the previous declaration
err = n.cfgErrorf("%s redeclared in this block", typeName)
return false
}
if n.child[1].kind == identExpr {
n.typ = &itype{cat: aliasT, val: typ, name: typeName}
} else {
@@ -415,7 +400,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
// values which may be used in further declarations.
if !sc.global {
for _, c := range n.child {
if _, err = interp.cfg(c, pkgID); err != nil {
if _, err = interp.cfg(c, importPath); err != nil {
// No error processing here, to allow recovery in subtree nodes.
err = nil
}
@@ -477,6 +462,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
wireChild(n)
for i := 0; i < n.nleft; i++ {
dest, src := n.child[i], n.child[sbase+i]
updateSym := false
var sym *symbol
var level int
if n.kind == defineStmt || (n.kind == assignStmt && dest.ident == "_") {
@@ -513,7 +499,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
dest.val = src.val
dest.recv = src.recv
dest.findex = sym.index
sym.rval = src.rval
updateSym = true
} else {
sym, level, _ = sc.lookup(dest.ident)
}
@@ -523,6 +509,15 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
break
}
if updateSym {
sym.typ = dest.typ
sym.rval = src.rval
// As we are updating the sym type, we need to update the sc.type
// when the sym has an index.
if sym.index >= 0 {
sc.types[sym.index] = sym.typ.frameType()
}
}
n.findex = dest.findex
n.level = dest.level
@@ -798,6 +793,11 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
wireChild(n)
switch {
case interp.isBuiltinCall(n):
err = check.builtin(n.child[0].ident, n, n.child[1:], n.action == aCallSlice)
if err != nil {
break
}
n.gen = n.child[0].sym.builtin
n.child[0].typ = &itype{cat: builtinT}
if n.typ, err = nodeType(interp, sc, n); err != nil {
@@ -816,57 +816,53 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
if op, ok := constBltn[n.child[0].ident]; ok && n.anc.action != aAssign {
op(n) // pre-compute non-assigned constant :
}
case n.child[0].isType(sc):
// Type conversion expression
if isInt(n.child[0].typ.TypeOf()) && n.child[1].kind == basicLit && isFloat(n.child[1].typ.TypeOf()) {
err = n.cfgErrorf("truncated to integer")
c0, c1 := n.child[0], n.child[1]
switch len(n.child) {
case 1:
err = n.cfgErrorf("missing argument in conversion to %s", c0.typ.id())
case 2:
err = check.conversion(c1, c0.typ)
default:
err = n.cfgErrorf("too many arguments in conversion to %s", c0.typ.id())
}
if err != nil {
break
}
n.action = aConvert
switch {
case isInterface(n.child[0].typ) && !n.child[1].isNil():
case isInterface(c0.typ) && !c1.isNil():
// Convert to interface: just check that all required methods are defined by concrete type.
c0, c1 := n.child[0], n.child[1]
if !c1.typ.implements(c0.typ) {
err = n.cfgErrorf("type %v does not implement interface %v", c1.typ.id(), c0.typ.id())
}
// Pass value as is
n.gen = nop
n.typ = n.child[1].typ
n.findex = n.child[1].findex
n.level = n.child[1].level
n.val = n.child[1].val
n.rval = n.child[1].rval
case n.child[1].rval.IsValid() && isConstType(n.child[0].typ):
n.typ = c1.typ
n.findex = c1.findex
n.level = c1.level
n.val = c1.val
n.rval = c1.rval
case c1.rval.IsValid() && isConstType(c0.typ):
n.gen = nop
n.findex = -1
n.typ = n.child[0].typ
n.rval = n.child[1].rval
convertConstantValue(n)
n.typ = c0.typ
n.rval = c1.rval
default:
n.gen = convert
n.typ = n.child[0].typ
n.typ = c0.typ
n.findex = sc.add(n.typ)
}
case isBinCall(n):
n.gen = callBin
typ := n.child[0].typ.rtype
numIn := len(n.child) - 1
tni := typ.NumIn()
if numIn == 1 && isCall(n.child[1]) {
numIn = n.child[1].typ.numOut()
}
if n.child[0].action == aGetMethod {
tni-- // The first argument is the method receiver.
}
if typ.IsVariadic() {
tni-- // The last argument could be empty.
}
if numIn < tni {
err = n.cfgErrorf("not enough arguments in call to %v", n.child[0].name())
err = check.arguments(n, n.child[1:], n.child[0], n.action == aCallSlice)
if err != nil {
break
}
n.gen = callBin
typ := n.child[0].typ.rtype
if typ.NumOut() > 0 {
if funcType := n.child[0].typ.val; funcType != nil {
// Use the original unwrapped function type, to allow future field and
@@ -889,6 +885,11 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
}
}
default:
err = check.arguments(n, n.child[1:], n.child[0], n.action == aCallSlice)
if err != nil {
break
}
if n.child[0].action == aGetFunc {
// Allocate a frame entry to store the anonymous function definition.
sc.add(n.child[0].typ)
@@ -946,18 +947,6 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
case compositeLitExpr:
wireChild(n)
underlying := func(t *itype) *itype {
for {
switch t.cat {
case ptrT, aliasT:
t = t.val
continue
default:
return t
}
}
}
child := n.child
if n.nleft > 0 {
child = child[1:]
@@ -965,9 +954,9 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
switch n.typ.cat {
case arrayT:
err = check.arrayLitExpr(child, underlying(n.typ.val), n.typ.size)
err = check.arrayLitExpr(child, n.typ.val, n.typ.size)
case mapT:
err = check.mapLitExpr(child, n.typ.key, underlying(n.typ.val))
err = check.mapLitExpr(child, n.typ.key, n.typ.val)
case structT:
err = check.structLitExpr(child, n.typ)
case valueT:
@@ -987,7 +976,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
n.findex = sc.add(n.typ)
// TODO: Check that composite literal expr matches corresponding type
n.gen = compositeGenerator(n)
n.gen = compositeGenerator(n, n.typ)
case fallthroughtStmt:
if n.anc.kind != caseBody {
@@ -1351,11 +1340,15 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
// Search for field must then be performed on type T only (not *T)
switch method, ok := n.typ.rtype.MethodByName(n.child[1].ident); {
case ok:
hasRecvType := n.typ.rtype.Kind() != reflect.Interface
n.val = method.Index
n.gen = getIndexBinMethod
n.action = aGetMethod
n.recv = &receiver{node: n.child[0]}
n.typ = &itype{cat: valueT, rtype: method.Type, isBinMethod: true}
if hasRecvType {
n.typ.recv = n.typ
}
case n.typ.rtype.Kind() == reflect.Ptr:
if field, ok := n.typ.rtype.Elem().FieldByName(n.child[1].ident); ok {
n.typ = &itype{cat: valueT, rtype: field.Type}
@@ -1375,7 +1368,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
if m2, ok2 := pt.MethodByName(n.child[1].ident); ok2 {
n.val = m2.Index
n.gen = getIndexBinPtrMethod
n.typ = &itype{cat: valueT, rtype: m2.Type}
n.typ = &itype{cat: valueT, rtype: m2.Type, recv: &itype{cat: valueT, rtype: pt}}
n.recv = &receiver{node: n.child[0]}
n.action = aGetMethod
} else {
@@ -1389,14 +1382,14 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
// Handle pointer on object defined in runtime
if method, ok := n.typ.val.rtype.MethodByName(n.child[1].ident); ok {
n.val = method.Index
n.typ = &itype{cat: valueT, rtype: method.Type}
n.typ = &itype{cat: valueT, rtype: method.Type, recv: n.typ}
n.recv = &receiver{node: n.child[0]}
n.gen = getIndexBinMethod
n.action = aGetMethod
} else if method, ok := reflect.PtrTo(n.typ.val.rtype).MethodByName(n.child[1].ident); ok {
n.val = method.Index
n.gen = getIndexBinMethod
n.typ = &itype{cat: valueT, rtype: method.Type}
n.typ = &itype{cat: valueT, rtype: method.Type, recv: &itype{cat: valueT, rtype: reflect.PtrTo(n.typ.val.rtype)}}
n.recv = &receiver{node: n.child[0]}
n.action = aGetMethod
} else if field, ok := n.typ.val.rtype.FieldByName(n.child[1].ident); ok {
@@ -1462,7 +1455,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
}
n.recv = &receiver{node: n.child[0], index: lind}
n.val = append([]int{m.Index}, lind...)
n.typ = &itype{cat: valueT, rtype: m.Type}
n.typ = &itype{cat: valueT, rtype: m.Type, recv: n.child[0].typ}
} else if ti := n.typ.lookupField(n.child[1].ident); len(ti) > 0 {
// Handle struct field
n.val = ti
@@ -1565,6 +1558,12 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
default:
// dereference expression
wireChild(n)
err = check.starExpr(n.child[0])
if err != nil {
break
}
if c0 := n.child[0]; c0.typ.cat == valueT {
n.typ = &itype{cat: valueT, rtype: c0.typ.rtype.Elem()}
} else {
@@ -1675,29 +1674,43 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
n.child[0].tnext = sbn.start
case typeAssertExpr:
if len(n.child) > 1 {
wireChild(n)
c1 := n.child[1]
if c1.typ == nil {
if c1.typ, err = nodeType(interp, sc, c1); err != nil {
return
}
}
if n.anc.action != aAssignX {
if n.child[0].typ.cat == valueT && isFunc(c1.typ) {
// Avoid special wrapping of interfaces and func types.
n.typ = &itype{cat: valueT, rtype: c1.typ.TypeOf()}
} else {
n.typ = c1.typ
}
n.findex = sc.add(n.typ)
}
} else {
if len(n.child) == 1 {
// The "o.(type)" is handled by typeSwitch.
n.gen = nop
break
}
wireChild(n)
c0, c1 := n.child[0], n.child[1]
if c1.typ == nil {
if c1.typ, err = nodeType(interp, sc, c1); err != nil {
return
}
}
err = check.typeAssertionExpr(c0, c1.typ)
if err != nil {
break
}
if n.anc.action != aAssignX {
if c0.typ.cat == valueT && isFunc(c1.typ) {
// Avoid special wrapping of interfaces and func types.
n.typ = &itype{cat: valueT, rtype: c1.typ.TypeOf()}
} else {
n.typ = c1.typ
}
n.findex = sc.add(n.typ)
}
case sliceExpr:
wireChild(n)
err = check.sliceExpr(n)
if err != nil {
break
}
if n.typ, err = nodeType(interp, sc, n); err != nil {
return
}
@@ -1762,19 +1775,6 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
// Global object allocation is already performed in GTA.
index = sc.sym[c.ident].index
} else {
if sym, exists := sc.sym[c.ident]; exists {
if sym.typ.node != nil &&
sym.typ.node.anc != nil {
// for non-predeclared identifiers (struct, map, etc)
prevDecl := n.interp.fset.Position(sym.typ.node.anc.pos)
err = n.cfgErrorf("%s redeclared in this block\n\tprevious declaration at %v", c.ident, prevDecl)
return
}
// for predeclared identifiers (int, string, etc)
// TODO(mpl): find the exact location of the previous declaration in all cases.
err = n.cfgErrorf("%s redeclared in this block", c.ident)
return
}
index = sc.add(n.typ)
sc.sym[c.ident] = &symbol{index: index, kind: varSym, typ: n.typ}
}
@@ -1870,7 +1870,12 @@ func childPos(n *node) int {
}
func (n *node) cfgErrorf(format string, a ...interface{}) *cfgError {
a = append([]interface{}{n.interp.fset.Position(n.pos)}, a...)
pos := n.interp.fset.Position(n.pos)
posString := n.interp.fset.Position(n.pos).String()
if pos.Filename == DefaultSourceName {
posString = strings.TrimPrefix(posString, DefaultSourceName+":")
}
a = append([]interface{}{posString}, a...)
return &cfgError{n, fmt.Errorf("%s: "+format, a...)}
}
@@ -2025,14 +2030,21 @@ func (n *node) isType(sc *scope) bool {
}
case selectorExpr:
pkg, name := n.child[0].ident, n.child[1].ident
if sym, _, ok := sc.lookup(pkg); ok && sym.kind == pkgSym {
path := sym.typ.path
if p, ok := n.interp.binPkg[path]; ok && isBinType(p[name]) {
return true // Imported binary type
}
if p, ok := n.interp.srcPkg[path]; ok && p[name] != nil && p[name].kind == typeSym {
return true // Imported source type
}
baseName := filepath.Base(n.interp.fset.Position(n.pos).Filename)
suffixedPkg := filepath.Join(pkg, baseName)
sym, _, ok := sc.lookup(suffixedPkg)
if !ok {
return false
}
if sym.kind != pkgSym {
return false
}
path := sym.typ.path
if p, ok := n.interp.binPkg[path]; ok && isBinType(p[name]) {
return true // Imported binary type
}
if p, ok := n.interp.srcPkg[path]; ok && p[name] != nil && p[name].kind == typeSym {
return true // Imported source type
}
case identExpr:
return sc.getType(n.ident) != nil
@@ -2342,11 +2354,10 @@ func gotoLabel(s *symbol) {
}
}
func compositeGenerator(n *node) (gen bltnGenerator) {
switch n.typ.cat {
func compositeGenerator(n *node, typ *itype) (gen bltnGenerator) {
switch typ.cat {
case aliasT, ptrT:
n.typ = n.typ.val
gen = compositeGenerator(n)
gen = compositeGenerator(n, n.typ.val)
case arrayT:
gen = arrayLit
case mapT:

View File

@@ -44,4 +44,5 @@ And include files containing
*/
package interp
// BUG(marc): Type checking is not implemented yet.
// BUG(marc): Support for recursive types is incomplete.
// BUG(marc): Support of types implementing multiple interfaces is incomplete.

View File

@@ -19,7 +19,7 @@ func (n *node) astDot(out io.Writer, name string) {
var label string
switch n.kind {
case basicLit, identExpr:
label = strings.Replace(n.ident, "\"", "\\\"", -1)
label = strings.ReplaceAll(n.ident, "\"", "\\\"")
default:
if n.action != aNop {
label = n.action.String()

View File

@@ -4,7 +4,7 @@ import (
"fmt"
"log"
"github.com/containous/yaegi/interp"
"github.com/traefik/yaegi/interp"
)
// Generic example.

17
interp/export_test.go Normal file
View File

@@ -0,0 +1,17 @@
package interp
func (interp *Interpreter) Scopes() map[string]map[string]struct{} {
scopes := make(map[string]map[string]struct{})
for k, v := range interp.scopes {
syms := make(map[string]struct{})
for kk := range v.sym {
syms[kk] = struct{}{}
}
scopes[k] = syms
}
return scopes
}
func (interp *Interpreter) Packages() map[string]string {
return interp.pkgNames
}

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