Compare commits

...

61 Commits

Author SHA1 Message Date
Marc Vertes
0a5b16cad6 feat: support go1.22
* feat: support go1.22

* Temporary fix for consistency tests due to language change in for loops

* review: clean old files

---------

Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2024-03-05 17:56:04 +01:00
Ludovic Fernandez
1990b96ccd update to go1.21 (#1598)
* feat: generate go1.21 files

* chore: update CI

* feat: add support for generic symbols in standard library packages

This is necessary to fully support go1.21 and beyond, which now
provide some generic packages such as `cmp`, `maps` or `slices`
in the standard library.

The principle is to embed the generic symbols in source form (as
strings) so they can be instantiated as required during interpretation.

Extract() has been modified to skip the generic types, functions and
constraint interfaces which can't be represented as reflect.Values.

A new stdlib/generic package has been added to provide the corresponding
source files as embedded strings.

The `Use()` function has been changed to pre-parse generic symbols as
doing lazy parsing was causing cyclic dependencies issues at compiling.
This is something we may improve in the future.

A unit test using `cmp` has been added.

For now, there are still some issues with generic stdlib packages
inter-dependencies, for example `slices` importing `cmp`, or when
generic types or function signatures depends on pre-compiled types
in the same package, which we will support shortly.

* fixup

* fixup

* fixup

* fixup

* fixup

* fixup

* fixes for go1.20

* fix previous

* update unsafe2 for go1.21, skip faky tests

In go1.21, the reflect rtype definition has been move to internal/abi.
We follow this change for maintainability, even if there is no layout
change (the go1.20 unsafe2 is compatible with go1.21).

We have isolated a few problematic tests which are failing sometimes
in go1.21, but work in go1.20, and also in go1.22. Those tests are
skipped if in go1.21. A preliminary investigation can not confirm that
something is wrong in yaegi, and the problem disappears with go1.22.

* add new wrapper for go1.21 package testing/slogtest

* add missing wrapper for go/doc/comment

* add support for slices generic package

---------

Co-authored-by: Marc Vertes <mvertes@free.fr>
2024-03-04 12:00:25 +01:00
Denys Smirnov
da27c4fbc2 interp: Add wasip1 to known OS list
Adds `wasip1` to known OS list, introduced in golang/go#58141.

Without this change, `yaegi extract` may fail on Go 1.21 with the following message:
```
type-checking package "time" failed (<GOROOT>/src/time/zoneinfo_wasip1.go:8:5: platformZoneSources redeclared in this block)
```
2023-09-27 00:22:05 +02:00
Denys Smirnov
f5b5481794 interp: Record function names in panic
Currently, yaegi only records source positions when writing panic trace to stderr.

This change adds function names to the panic output.

Unfortunately, yaegi walks the trace in reverse order, comparing to Go runtime. This can be improved in the future.
2023-09-23 12:24:05 +02:00
Marc Vertes
79b7420ee1 interp: fix issue where a var is reused instead of redefined
Avoid a spurious optimisation which forces a variable to be reused instead of redefined for assignment operation. This ensures that a variable defined in a loop is re-allocated, preserving the previous instance when used by a closure for example.

Fix #1594
2023-09-21 23:00:06 +02:00
Ludovic Fernandez
8a6061cc86 chore: update linter
update golangci-lint to v1.53.3
2023-07-02 10:28:05 +02:00
bysir
c10e468d01 interp: fix fieldName method parsing embedded + generic fields
Fix https://github.com/traefik/yaegi/issues/1571
2023-07-01 12:58:05 +02:00
Hiro
75e5f99bc5 doc: fix go install cmd 2023-06-20 02:38:06 +02:00
Hiro
79e32b5a92 doc: install using go install
- Update markdown to document `go install` as the preferred way for the installation of yaegi REPL
2023-06-20 01:46:05 +02:00
Fernandez Ludovic
63b8cc42b9 doc: update readme 2023-06-19 14:07:06 +02:00
Marc Vertes
f4a9cd3cbe stdlib: remove embed wrapper
Embedding files using `//go:embed` and `embed` packages can not be supported in yaegi, so it is better to not generate the wrapper to embed and have a graceful error in case of usage of `embed.FS` in the interpreter.
Also update README about unsuported directives.

Fixes #1557.
2023-06-14 19:04:05 +02:00
laushunyu
6447a677f3 fix(src): use errors.Is(err, fs.ErrNotExist)
I implemented fs.FS. When the file does not exist, `xerrors.Wrap(fs.ErrNotExist, "")` will be returned. However, `os.IsNotExist` cannot handle this situation. I found the following comment:
```
// IsNotExist returns a boolean indicating whether the error is known to
// report that a file or directory does not exist. It is satisfied by
// ErrNotExist as well as some syscall errors.
//
// This function predates errors.Is. It only supports errors returned by
// the os package. New code should use errors.Is(err, fs.ErrNotExist).
```

Therefore, we should use `errors.Is(err, fs.ErrNotExist)` instead.
2023-06-14 17:00:12 +02:00
Marc Vertes
68a430f969 interp: fix support of type assert expressions in the global scope
Add the support of type assert expressions in type parsing
which allows to process type assertions in the global scope.

Fixes #1543.
2023-04-26 10:52:05 +02:00
Marc Vertes
dc7c64ba88 interp: improve support of unsafe
Unsafe functions such as `unsafe.Alignof`, `unsafe.Offsetof` and `unsafe.Sizeof` can be used for type declarations early on during compile, and as such, must be treated as builtins returning constants at compile time. It is still necessary to explicitely enable unsafe support in yaegi.

The support of `unsafe.Add` has also been added.

Fixes #1544.
2023-04-26 10:16:05 +02:00
Marc Vertes
d6ad13acea interp: improve handling of embedded fields with binary methods
Only structures with one embedded field can be marked anonymous, due to golang/go#15924. Also check only that the method is defined, do not verify its complete signature, as the receiver may or not be defined in the arguments of the method.

Fixes #1537.
2023-04-13 18:16:05 +02:00
Marc Vertes
d124954a7d interp: fix computation of array size from constant expression
The result of the expression giving the size of an array may be an `int` instead of `constant.Value` in case of an out of order declaration. Handle both cases.

Fixes #1536.
2023-04-11 17:54:05 +02:00
Senan Kelly
8de3add6fa extract: escape ~ in package names
consider packages from git.sr.ht, the user namespace is prefixed with `~`

so running something like `yaegi extract git.sr.ht/~emersion/scfg`

was producing syntax errors with `~` in identifiers. and also `~` in filenames which worked but probably best not to have it there either

thanks!
2023-03-27 19:08:05 +02:00
Marc Vertes
20c8f5ef7c interp: correctly init variables assigned from function call
In the case of a Go short definition (i.e. `a, b := f()`), the new defined variables must be (re-)created in order to preserve the previous value (if in a loop) which can be still in use in the context of a closure. This must not apply to redeclared variables which simply see their value reassigned.

The problem was both occuring in callBin() for runtime functions and assignFromCall() for functions created in the interpreter.

Fixes #1497.
2023-03-24 11:46:05 +01:00
a
ce2bb794fa Equality is incorrect when nil is used as the left argument of ==
hi!

this issue is sorta blocking for me so i thought i would try to fix it. 

im still learning the codebase and understanding how yaegi works, but I thought I would attempt to add a test in the style of other tests as a start.

please let me know if there is anything i need to change / run, or if anyone knows perhaps a good place to start for tackling this issue

Fixes #1496
2023-03-23 09:20:06 +01:00
Marc Vertes
c4a297cbdc interp: fix use of function as field of a recursive struct.
The logic of patching reflect struct representation has been fixed in presence of function fields and to better handle indirect recursivity (struct recursive through nested struct).

Fixes #1519.
2023-03-21 11:50:05 +01:00
Eng Zer Jun
c473dceda8 test: use t.TempDir to create temporary test directory
This pull request replaces `os.MkdirTemp` with `t.TempDir`. We can use the `t.TempDir` function from the `testing` package to create temporary directory. The directory created by `t.TempDir` is automatically removed when the test and all its subtests complete. 

Reference: https://pkg.go.dev/testing#T.TempDir

```go
func TestFoo(t *testing.T) {
	// before
	tmpDir, err := os.MkdirTemp("", "")
	if err != nil {
		t.Fatalf("failed to create tmp directory: %v", err)
	}
	defer func() {
		if err := os.RemoveAll(dir); err != nil {
			t.Fatal(err)
		}
	}

	// now
	tmpDir := t.TempDir()
}
```
2023-03-16 21:40:05 +01:00
Marc Vertes
f202764973 cli: disable race detector if GOFLAGS contains -buildmode=pie
This should allow to build the package on AlpineLinux. Also document the constraint of having to install the source under $GOPATH/src/github.com/traefik/yaegi until Go modules are supported.

Fixes #1523.
2023-03-16 11:54:15 +01:00
sasaba
9d658604be interp: fix type assertion issues
closes #1514

hi!
I had the same problem as #1514 and I wanted to fix it, I found
When asserting *crypto/rsa.PublicKey, using the typ attribute of node to get an nil rtype, resulting in the assertion result being nok

This code contains the same problem
```go
package main

import (
	"log"
	"crypto/rsa"
)

func main() {

    var pKey interface{} = &rsa.PublicKey{}

    if _, ok := pKey.(*rsa.PublicKey); ok {
        log.Println("ok")
    } else {
        log.Println("nok")
    }
}

```

So I submitted this Pull Request, hope it will be merged
2023-03-14 15:34:05 +01:00
Denys Smirnov
166fff7072 interp: add safeguards when searching for vendor root.
With certain yaegi configuration on Windows, the loop in `previousRoot` can be infinite, because it fails to recognize driver letters as root.

This change adds a few more safeguards: checks path prefix earlier and checks if `filepath.Dir` produces an empty path.
2023-03-13 09:20:06 +01:00
Marc Vertes
8efc4f0735 interp: improve handling of methods defined on interfaces
For methods defined on interfaces (vs concrete methods), the resolution of the method is necessarily delayed at the run time and can not be completed at compile time.

The selectorExpr processing has been changed to correctly identify calls on interface methods which were confused as fields rather than methods (due to the fact that in a interface definition, methods are fields of the interface).

Then at runtime, method lookup has been fixed to correctly recurse in nested valueInterface wrappers and to find embedded interface fields in case of struct or pointer to struct.

Finally, remove receiver processing in `call()`.The receiver is already processed at method resolution and in genFunctionWrapper. Removing redundant processing in call fixes handling of variadic method, simplifies the code and makes it faster.

With those fixes, it is now possible to load and run `go.uber.org/zap` in yaegi. In turn, it makes possible for yaegi to run plugins dependent on zap, such as coraza-waf.

Fixes #1515, 
Fixes #1172,
Fixes #1275,
Fixes #1485.
2023-03-06 16:46:06 +01:00
Marc Vertes
6aa4f45c42 interp: wrap source functions when used as input parameters.
It allows to use interpreter functions as parameters in the runtime, even for defered callbacks, or when passed as empty interfaces, as for runtime.SetFinalizer.

Fixes #1503
2023-02-08 12:04:05 +01:00
Marc Vertes
f3dbce93a4 interp: improve handling of generic types
When generating a new type, the parameter type was not correctly duplicated in the new AST. This is fixed by making copyNode recursive if needed. The out of order processing of generic types has also been fixed.

Fixes #1488
2023-02-08 11:48:05 +01:00
Marc Vertes
0e3ea5732a update to go1.20
* update to go1.20

* lint

* fix: ci

---------

Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2023-02-03 14:32:05 +01:00
mpl
1679870ea3 Suppress http.ErrAbortHandler panics, as in the stdlib
Fixes https://github.com/traefik/traefik/issues/8937
2023-01-17 14:26:04 +01:00
ttoad
9b4ea62f69 Fix gorountine arguments not copied.
Fixes #1498
2023-01-16 18:38:04 +01:00
Denys Smirnov
eee72d1aae Fix deadlock after parsing directory with no files
Currently if interpreter tries to eval an empty directory with no Go files it will error (as it should), but all future calls to eval will deadlock, because the mutex is not unlocked correctly. I believe this is a critical issue that must be addressed.
2022-12-13 11:46:05 +01:00
Denys Smirnov
97cf8c4210 Expose package name of a compiled source
Exposing package name of the compiled source allows using it in `Eval`, as well as in logs and for other purposes.
2022-11-07 16:22:12 +01:00
Marc Vertes
7bb8b4631f interp: fix processing of aliased types
For a long time, there was a confusion between aliased types and named types (due to my misunderstanding of alias types in Go at that time). The type category `aliasT` has been renamed into `linkedT`, which is correct semantically. 

Aliased types are only declared using `typeSpecAssign`, and its processing is now distinct from `typeSpec` statement, used for named types.

A `linkedT` type is obtained by a statement like `type A uint32`, where `A` type category is therefore `linkedT`.

An aliased type is obtained by a statement like `type A = uint32` (notice the `=` sign, translating into `typeSpecAssign`).

The semantic difference is that in the first linkedT form, `A` and `uint32` are 2 distinct types, instead of being strictly equivalent in the `typeSpecAssign` form (the 2 names lead to one type definition).


Fixes #1416.
2022-10-26 17:00:07 +02:00
Marc Vertes
9f43170708 interp: error instead of panic when assigning to a constant
Add early detection of assigning to a constant during compiling instead of panicking at runtime.
2022-10-25 18:16:10 +02:00
Marc Vertes
71112dbe87 interp: fix return of untyped values for defined types
Fixes #1475
2022-10-25 17:20:06 +02:00
Marc Vertes
4a8093609f interp: fix handling interface in operators
In case of interface values, make sure that the concrete type is preserved during type inference.

Fixes #1466.
2022-10-25 14:02:05 +02:00
Marc Vertes
7865c90737 interp: fix case behavior for values converted to empty interface
Fixes #1465.
2022-10-25 09:38:05 +02:00
Marc Vertes
e4e3d11772 interp: fix the logic to skip source files based on OS or CPU arch
For example, on architecture GOARCH=amd64, a file named `foobar_amd64.go` would be skipped instead of being read and parsed. The function `skipFile` is fixed and missing tests are added.
2022-10-24 15:48:04 +02:00
Marc Vertes
a5242cbb9e interp: retry type definition if an array size is undefined
In case of forward definition of a constant, a symbol may be undefined when attempting to compute the array size in type analysis. Just mark the type as incomplete instead of aborting directly, to allow resolution at a second pass.

Fixes #1470.
2022-10-24 10:44:06 +02:00
Fernandez Ludovic
c4d1bf5029 chore: update actions/cache to v3 2022-10-21 16:21:21 +02:00
Marc Vertes
e003140c6e interp: improve internal handling of functions
Up to now functions could be stored as node values in frame (as for interpreter defined functions) or function values, directly callable by the Go runtime. We now always store functions in the later form, making the processing of functions, anonymous closures and methods simpler and more robust. All functions, once compiled are always directly callable, with no further wrapping necessary.

Fixes #1459.
2022-10-19 17:54:08 +02:00
Marc Vertes
6b8c94e6c4 interp: check that send operate on channel value
Not performing this check was leading to a panic at run-time. It now fails early with a compile error.

Fixes #1453.
2022-10-04 12:00:08 +02:00
Marc Vertes
143e4a4559 interp: fix type assertion for wrapped empty interface
Although empty interfaces are usually not wrapped, for compatibility with the runtime, we may have to wrap them sometime into `valueInterface` type.

It allows to preserve interpreter type metadata for interface values exchanged with the runtime. It is necessary to resolve methods and receivers in the absence of reflect support.

During type assertions on empty interfaces, we now handle a possible valueInterface and dereference the original value to pursue the type assertion.

In the same change, we have improved the format of some panic messages at runtime to give location of offending source at interpreter level.

This change will allow to fix traefik/traefik#9362.
2022-10-03 17:50:09 +02:00
Marc Vertes
dfeddbe823 interp: fix handling generic types with multiple type parameters
Those declarations involve the indexListExpr AST token, which was not handled in type.go. The same processing as for a single type parameter is applied.

Fixes #1460.
2022-09-22 13:50:09 +02:00
Marc Vertes
021824930d interp: improve type assertions
In type assertion at compile time, compare signatures between function types only.

Make `itype.numOut()` return the correct value for Go builtins (this was not strictly necessary due to above fix, but it is correct and improves maintainability).

Fixes #1454.
2022-09-12 22:30:08 +02:00
Marc Vertes
b8301f10a8 interp: add missing conversion for non integer array dimension
Fixes #1451.
2022-09-12 19:40:08 +02:00
Marc Vertes
2e8808317f interp: fix default comm clause in select
Do not attempt to init a non-existent channel setting when in
default communication clause in select.

Fixes #1442.
2022-09-12 15:32:08 +02:00
Marc Vertes
79747f3d6f interp: fix redeclarations containing a blank variable
In [short variable declarations](https://go.dev/ref/spec#Short_variable_declarations),
The reuse of existing symbols is possible only if a new variable is defined,
otherwise a new symbol must be created, which was not the case in the issue.

Search for new symbols and correctly ignore blank variables.

Fixes #1434.
2022-09-02 16:44:07 +02:00
Marc Vertes
63825e7201 interp: fix use of interfaces in composite types
The representation of non empty interfaces defined in the interpreter is now identical between refType() and frameType() functions, which are used to generate interpreter objects.

Fixes #1447 and #1426.
2022-09-01 12:18:08 +02:00
Marc Vertes
03ccda1a69 interp: fix type switch on arbitrary expressions
If the value on which to type-switch was already set (i.e. a variable),
there was no problem. But if it had to be obtained through a complex
expression (func call, array index, etc...), then the code to retrieve
the value prior type-switch was not scheduled. This is now fixed.

This issue is nasty because the behavior is silently changed,
leading potentially to further unrelated issues or runtime panics.

Fixes #1444.
2022-08-25 12:04:08 +02:00
Marc Vertes
e02621577f interp: improve handling of composed interfaces wrappers
This change implements a workaround to better support composed
interfaces in yaegi and let the interpreter define objects which
implement multiple interfaces at once.

We use the existing MapTypes to store what possible composed interface
wrapper could be used for some interfaces. When generating an interface
wrapper, the wrapper with the highest number of implemented methods is
chosen.

This is still an imperfect solution but it improves the accuracy of
interpreter in some critical cases.

This workaround could be removed in future if/when golang/go#15924
is resolved.

Fixes #1425.
2022-08-25 10:44:11 +02:00
Marc Vertes
ab869c8d20 interp: improve method resolution for embedded interfaces
The function `getMethodByName()` is now able to look for
embedded `valueInterface` field for matching methods in interface
struct fields.
    
Fixes #1439 and #1427.
2022-08-17 18:14:10 +02:00
Marc Vertes
b2aa636ea0 interp: fix spurious variable declaration loop
The variable dependency check function was confused by a dependency
variable with the same name but in an external package.

This change is necessary to address #1427.
2022-08-10 16:10:08 +02:00
Marc Vertes
ae725fb3d9 interp: fix generic check on nil function
Related to issue https://github.com/traefik/traefik/issues/9231
2022-08-05 18:20:08 +02:00
Marc Vertes
14bc3b56b8 interp: add support of Go generics in interpreter
Status:
* [x] parsing code with generics
* [x] instantiate generics from concrete types
* [x] automatic type inference
* [x] support of generic recursive types 
* [x] support of generic methods
* [x] support of generic receivers in methods
* [x] support of multiple type parameters
* [x] support of generic constraints
* [x] tests (see _test/gen*.go)

Fixes #1363.
2022-08-03 15:18:08 +02:00
Marc Vertes
255b1cf1de interp: do not allow function declaration without body
Such function declaration denotes either a linkname (an access to
an arbitrary, typically unexported symbol, solved by go compiler),
or a foreign C or assembly implementation of the body.

Those cases are not supported (or planned to be) by the interpreter.

Fixes #1431.
2022-08-03 10:06:06 +02:00
Marc Vertes
d3fc5e990e chore: upgrade to go1.19
* chore: upgrade to go1.19

* review

Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2022-08-03 09:44:07 +02:00
Marc Vertes
dc082b5ded stdlib: support of go1.18 and go1.19, remove go1.16 and go1.17
In addition:
- extract commmand now skips exported generics in runtime wrappers
- interp_consistent_test.go fixed for go1.18 and go1.19
- move minimal version of go compiler to go1.18

Note that this version is incompatible with go1.17 and before due
to the major changes in the stdlib go parser.

To be merged once go1.19 is officially released (not before).
2022-07-20 17:10:08 +02:00
Marc Vertes
d9c402e20d interp: fix unit testing for go1.18
Some tests are not passing when using go1.18, due to a change of
content in error messages compared to go1.17. We simply skip them
while we support go1.17. It concerns a small number of tests
regarding error detection.
2022-07-20 11:04:09 +02:00
ttoad
09a1617640 interp: improve support of alias types
I expect the following code to be supported.
```go
type TT http.Header

func (t TT) Set(key, val string) {

}

func (t TT) Get(key string) string {

}
```
So, I pushed this PR. 
Do I need to add some test cases?  I don't see the relevant test files ....
2022-07-14 19:38:07 +02:00
Luo Peng
cb642c44ba interp: improve type checking when comparing aliased types
Fixes #1421.
2022-06-30 10:22:12 +02:00
702 changed files with 29084 additions and 10417 deletions

View File

@@ -17,7 +17,7 @@ jobs:
strategy:
matrix:
go-version: [ 1.16, 1.17 ]
go-version: [ '1.21', '1.22' ]
os: [ubuntu-latest, macos-latest, windows-latest]
include:
@@ -34,6 +34,7 @@ jobs:
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
stable: true
# https://github.com/marketplace/actions/checkout
- name: Checkout code
@@ -43,7 +44,7 @@ jobs:
# https://github.com/marketplace/actions/cache
- name: Cache Go modules
uses: actions/cache@v2
uses: actions/cache@v3
with:
# In order:
# * Module download cache

View File

@@ -7,8 +7,8 @@ on:
pull_request:
env:
GO_VERSION: 1.17
GOLANGCI_LINT_VERSION: v1.42.1
GO_VERSION: '1.22'
GOLANGCI_LINT_VERSION: v1.55.2
jobs:
@@ -45,12 +45,13 @@ jobs:
needs: linting
strategy:
matrix:
go-version: [ 1.16, 1.17 ]
go-version: [ '1.21', '1.22' ]
steps:
- name: Set up Go ${{ matrix.go-version }}
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
stable: true
- name: Check out code
uses: actions/checkout@v2
@@ -75,13 +76,14 @@ jobs:
working-directory: ${{ github.workspace }}/go/src/github.com/traefik/yaegi
strategy:
matrix:
go-version: [ 1.16, 1.17 ]
go-version: [ '1.21', '1.22' ]
steps:
- name: Set up Go ${{ matrix.go-version }}
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
stable: true
- name: Check out code
uses: actions/checkout@v2
@@ -91,7 +93,7 @@ jobs:
# https://github.com/marketplace/actions/cache
- name: Cache Go modules
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: ./_test/tmp
key: ${{ runner.os }}-yaegi-${{ hashFiles('**//_test/tmp/') }}

View File

@@ -6,7 +6,7 @@ on:
- v[0-9]+.[0-9]+*
env:
GO_VERSION: 1.17
GO_VERSION: '1.21'
jobs:
@@ -26,7 +26,7 @@ jobs:
fetch-depth: 0
- name: Cache Go modules
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}

View File

@@ -1,83 +0,0 @@
[run]
deadline = "5m"
skip-files = []
[linters-settings]
[linters-settings.govet]
check-shadowing = false
[linters-settings.gocyclo]
min-complexity = 12.0
[linters-settings.maligned]
suggest-new = true
[linters-settings.goconst]
min-len = 3.0
min-occurrences = 3.0
[linters-settings.misspell]
locale = "US"
[linters]
enable-all = true
disable = [
"golint", # deprecated
"scopelint", # deprecated
"interfacer", # deprecated
"maligned", # deprecated
"lll",
"gas",
"dupl",
"prealloc",
"gocyclo",
"cyclop",
"gochecknoinits",
"gochecknoglobals",
"wsl",
"nlreturn",
"godox",
"funlen",
"gocognit",
"stylecheck",
"gomnd",
"testpackage",
"paralleltest",
"tparallel",
"goerr113",
"wrapcheck",
"nestif",
"exhaustive",
"exhaustivestruct",
"forbidigo",
"ifshort",
"forcetypeassert",
"errorlint", # TODO: must be reactivate before fixes
]
[issues]
exclude-use-default = false
max-per-linter = 0
max-same-issues = 0
exclude = []
[[issues.exclude-rules]]
path = ".+_test\\.go"
linters = ["goconst"]
[[issues.exclude-rules]]
path = ".+_test\\.go"
text = "var-declaration:"
[[issues.exclude-rules]]
path = "interp/interp.go"
text = "`in` can be `io.Reader`"
[[issues.exclude-rules]]
path = "interp/interp.go"
text = "`out` can be `io.Writer`"
[[issues.exclude-rules]]
path = "interp/interp.go"
text = "`Panic` should conform to the `XxxError` format"
[[issues.exclude-rules]]
path = "interp/interp_eval_test.go"
linters = ["thelper"]

165
.golangci.yml Normal file
View File

@@ -0,0 +1,165 @@
run:
timeout: 10m
skip-files: []
linters-settings:
govet:
check-shadowing: false
gocyclo:
min-complexity: 12
maligned:
suggest-new: true
goconst:
min-len: 3
min-occurrences: 3
funlen:
lines: -1
statements: 50
misspell:
locale: US
depguard:
rules:
main:
files:
- $all
allow:
- $gostd
- github.com/traefik/yaegi
tagalign:
align: false
order:
- xml
- json
- yaml
- yml
- toml
- mapstructure
- url
godox:
keywords:
- FIXME
gocritic:
enabled-tags:
- diagnostic
- style
- performance
disabled-checks:
- paramTypeCombine # already handle by gofumpt.extra-rules
- whyNoLint # already handle by nonolint
- unnamedResult
- hugeParam
- sloppyReassign
- rangeValCopy
- octalLiteral
- ptrToRefParam
- appendAssign
- ruleguard
- httpNoBody
- exposedSyncMutex
- importShadow # TODO should be fixed
- commentedOutCode # TODO should be fixed
revive:
rules:
- name: struct-tag
- name: blank-imports
- name: context-as-argument
- name: context-keys-type
- name: dot-imports
- name: error-return
- name: error-strings
- name: error-naming
- name: exported
disabled: true
- name: if-return
- name: increment-decrement
- name: var-naming
- name: var-declaration
- name: package-comments
disabled: true
- name: range
- name: receiver-naming
- name: time-naming
- name: unexported-return
- name: indent-error-flow
- name: errorf
- name: empty-block
- name: superfluous-else
- name: unused-parameter
disabled: true
- name: unreachable-code
- name: redefines-builtin-id
linters:
enable-all: true
disable:
- deadcode # deprecated
- exhaustivestruct # deprecated
- golint # deprecated
- ifshort # deprecated
- interfacer # deprecated
- maligned # deprecated
- nosnakecase # deprecated
- scopelint # deprecated
- structcheck # deprecated
- varcheck # deprecated
- cyclop # duplicate of gocyclo
- sqlclosecheck # not relevant (SQL)
- rowserrcheck # not relevant (SQL)
- execinquery # not relevant (SQL)
- lll
- gas
- dupl
- prealloc
- gocyclo
- cyclop
- gochecknoinits
- gochecknoglobals
- wsl
- nlreturn
- godox
- funlen
- gocognit
- stylecheck
- gomnd
- testpackage
- paralleltest
- tparallel
- goerr113
- wrapcheck
- nestif
- exhaustive
- exhaustruct
- forbidigo
- ifshort
- forcetypeassert
- varnamelen
- nosnakecase
- nonamedreturns
- nilnil
- maintidx
- dupword # false positives
- errorlint # TODO: must be reactivate after fixes
issues:
exclude-use-default: false
max-per-linter: 0
max-same-issues: 0
exclude: []
exclude-rules:
- path: .+_test\.go
linters:
- goconst
- path: .+_test\.go
text: 'var-declaration:'
- path: interp/interp.go
text: '`in` can be `io.Reader`'
- path: interp/interp.go
text: '`out` can be `io.Writer`'
- path: interp/interp.go
text: '`Panic` should conform to the `XxxError` format'
- path: interp/interp_eval_test.go
linters:
- thelper
- path: interp/debugger.go
linters:
- containedctx

View File

@@ -27,4 +27,15 @@ tests:
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 install
generic_list = cmp/cmp.go slices/slices.go slices/sort.go slices/zsortanyfunc.go maps/maps.go \
sync/oncefunc.go sync/atomic/type.go
# get_generic_src imports stdlib files containing generic symbols definitions
get_generic_src:
eval "`go env`"; echo $$GOROOT; gov=$${GOVERSION#*.}; gov=$${gov%.*}; \
for f in ${generic_list}; do \
nf=stdlib/generic/go1_$${gov}_`echo $$f | tr / _`.txt; echo "nf: $$nf"; \
cat "$$GOROOT/src/$$f" > "$$nf"; \
done
.PHONY: check gen_all_syscall internal/cmd/extract/extract get_generic_src install

View File

@@ -5,7 +5,6 @@
[![release](https://img.shields.io/github/tag-date/traefik/yaegi.svg?label=alpha)](https://github.com/traefik/yaegi/releases)
[![Build Status](https://github.com/traefik/yaegi/actions/workflows/main.yml/badge.svg)](https://github.com/traefik/yaegi/actions/workflows/main.yml)
[![GoDoc](https://godoc.org/github.com/traefik/yaegi?status.svg)](https://pkg.go.dev/mod/github.com/traefik/yaegi)
[![Discourse status](https://img.shields.io/discourse/https/community.traefik.io/status?label=Community&style=social)](https://community.traefik.io/c/yaegi)
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.
@@ -18,7 +17,7 @@ It powers executable Go scripts and plugins, in embedded interpreters or interac
* Works everywhere Go works
* All Go & runtime resources accessible from script (with control)
* Security: `unsafe` and `syscall` packages neither used nor exported by default
* Support Go 1.16 and Go 1.17 (the latest 2 major releases)
* Support the latest 2 major releases of Go (Go 1.21 and Go 1.22)
## Install
@@ -31,7 +30,7 @@ import "github.com/traefik/yaegi/interp"
### Command-line executable
```bash
go get -u github.com/traefik/yaegi/cmd/yaegi
go install github.com/traefik/yaegi/cmd/yaegi@latest
```
Note that you can use [rlwrap](https://github.com/hanslub42/rlwrap) (install with your favorite package manager),
@@ -173,10 +172,13 @@ Beside the known [bugs] which are supposed to be fixed in the short term, there
- Assembly files (`.s`) are not supported.
- Calling C code is not supported (no virtual "C" package).
- Directives about the compiler, the linker, or embedding files are not supported.
- 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.
Go modules are not supported yet. Until that, it is necessary to install the source into `$GOPATH/src/github.com/traefik/yaegi` to pass all the tests.
## Contributing
[Contributing guide](CONTRIBUTING.md).

View File

@@ -15,11 +15,10 @@ func main() {
r := extendedRequest{}
req := &r.Request
fmt.Println(r)
fmt.Println(req)
fmt.Printf("%T\n", r.Request)
fmt.Printf("%T\n", req)
}
// Output:
// {{ <nil> 0 0 map[] <nil> <nil> 0 [] false map[] map[] <nil> map[] <nil> <nil> <nil> <nil>} }
// &{ <nil> 0 0 map[] <nil> <nil> 0 [] false map[] map[] <nil> map[] <nil> <nil> <nil> <nil>}
// http.Request
// *http.Request

View File

@@ -1,9 +0,0 @@
package alias3
type T struct {
A string
}
func (t *T) Print() {
println(t.A)
}

View File

@@ -5,8 +5,8 @@ import (
"sync"
)
// Defined an interface of stringBuilder that compatible with
// strings.Builder(go 1.10) and bytes.Buffer(< go 1.10)
// Define an interface of stringBuilder that is compatible with
// strings.Builder(go 1.10) and bytes.Buffer(< go 1.10).
type stringBuilder interface {
WriteRune(r rune) (n int, err error)
WriteString(s string) (int, error)

16
_test/assert3.go Normal file
View File

@@ -0,0 +1,16 @@
package main
import "crypto/rsa"
func main() {
var pKey interface{} = &rsa.PublicKey{}
if _, ok := pKey.(*rsa.PublicKey); ok {
println("ok")
} else {
println("nok")
}
}
// Output:
// ok

11
_test/assert4.go Normal file
View File

@@ -0,0 +1,11 @@
package main
var cc interface{} = 2
var dd = cc.(int)
func main() {
println(dd)
}
// Output:
// 2

21
_test/assign17.go Normal file
View File

@@ -0,0 +1,21 @@
package main
func main() {
s := make([]map[string]string, 0)
m := make(map[string]string)
m["m1"] = "m1"
m["m2"] = "m2"
s = append(s, m)
tmpStr := "start"
println(tmpStr)
for _, v := range s {
tmpStr, ok := v["m1"]
println(tmpStr, ok)
}
println(tmpStr)
}
// Output:
// start
// m1 true
// start

21
_test/assign18.go Normal file
View File

@@ -0,0 +1,21 @@
package main
func main() {
s := make([]map[string]string, 0)
m := make(map[string]string)
m["m1"] = "m1"
m["m2"] = "m2"
s = append(s, m)
tmpStr := "start"
println(tmpStr)
for _, v := range s {
tmpStr, _ := v["m1"]
println(tmpStr)
}
println(tmpStr)
}
// Output:
// start
// m1
// start

56
_test/cli7.go Normal file
View File

@@ -0,0 +1,56 @@
package main
import (
"fmt"
"io"
"log"
"net/http"
"net/http/httptest"
)
type T struct {
http.ResponseWriter
}
type mw1 struct {
}
var obj = map[string]interface{}{}
func (m *mw1) ServeHTTP(rw http.ResponseWriter, rq *http.Request) {
t := &T{
ResponseWriter: rw,
}
x := t.Header()
i := obj["m1"].(*mw1)
fmt.Fprint(rw, "Welcome to my website!", x, i)
}
func main() {
m1 := &mw1{}
obj["m1"] = m1
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 := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(body))
}
// Output:
// Welcome to my website!map[] &{}

41
_test/cli8.go Normal file
View File

@@ -0,0 +1,41 @@
package main
import (
"net/http"
"net/http/httptest"
)
type T struct {
name string
next http.Handler
}
func (t *T) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
println("in T.ServeHTTP")
if t.next != nil {
t.next.ServeHTTP(rw, req)
}
}
func New(name string, next http.Handler) (http.Handler, error) { return &T{name, next}, nil }
func main() {
next := func(rw http.ResponseWriter, req *http.Request) {
println("in next")
}
t, err := New("test", http.HandlerFunc(next))
if err != nil {
panic(err)
}
recorder := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/", nil)
t.ServeHTTP(recorder, req)
println(recorder.Result().Status)
}
// Output:
// in T.ServeHTTP
// in next
// 200 OK

36
_test/closure13.go Normal file
View File

@@ -0,0 +1,36 @@
package main
import (
"fmt"
"strconv"
)
type monkey struct {
test func() int
}
func main() {
input := []string{"1", "2", "3"}
var monkeys []*monkey
for _, v := range input {
kong := monkey{}
divisor, err := strconv.Atoi(v)
if err != nil {
panic(err)
}
fmt.Print(divisor, " ")
kong.test = func() int {
return divisor
}
monkeys = append(monkeys, &kong)
}
for _, mk := range monkeys {
fmt.Print(mk.test(), " ")
}
}
// Output:
// 1 2 3 1 2 3

32
_test/closure14.go Normal file
View File

@@ -0,0 +1,32 @@
package main
import "fmt"
type monkey struct {
test func() int
}
func getk(k int) (int, error) { return k, nil }
func main() {
input := []string{"1", "2", "3"}
var monkeys []*monkey
for k := range input {
kong := monkey{}
divisor, _ := getk(k)
fmt.Print(divisor, " ")
kong.test = func() int {
return divisor
}
monkeys = append(monkeys, &kong)
}
for _, mk := range monkeys {
fmt.Print(mk.test(), " ")
}
}
// Output:
// 0 1 2 0 1 2

18
_test/convert3.go Normal file
View File

@@ -0,0 +1,18 @@
package main
import (
"fmt"
"net/http"
)
func main() {
next := func(rw http.ResponseWriter, req *http.Request) {
rw.Header().Set("Cache-Control", "max-age=20")
rw.WriteHeader(http.StatusOK)
}
f := http.HandlerFunc(next)
fmt.Printf("%T\n", f.ServeHTTP)
}
// Output:
// func(http.ResponseWriter, *http.Request)

25
_test/fun28.go Normal file
View File

@@ -0,0 +1,25 @@
package main
import (
"runtime"
)
type T struct {
name string
}
func finalize(t *T) { println("finalize") }
func newT() *T {
t := new(T)
runtime.SetFinalizer(t, finalize)
return t
}
func main() {
t := newT()
println(t != nil)
}
// Output:
// true

39
_test/gen1.go Normal file
View File

@@ -0,0 +1,39 @@
package main
import "fmt"
// SumInts adds together the values of m.
func SumInts(m map[string]int64) int64 {
var s int64
for _, v := range m {
s += v
}
return s
}
// SumFloats adds together the values of m.
func SumFloats(m map[string]float64) float64 {
var s float64
for _, v := range m {
s += v
}
return s
}
func main() {
// Initialize a map for the integer values
ints := map[string]int64{
"first": 34,
"second": 12,
}
// Initialize a map for the float values
floats := map[string]float64{
"first": 35.98,
"second": 26.99,
}
fmt.Printf("Non-Generic Sums: %v and %v\n",
SumInts(ints),
SumFloats(floats))
}

12
_test/gen10.go Normal file
View File

@@ -0,0 +1,12 @@
package main
func genFunc() (f func()) {
return f
}
func main() {
println(genFunc() == nil)
}
// Output:
// true

33
_test/gen11.go Normal file
View File

@@ -0,0 +1,33 @@
package main
import (
"encoding/json"
"fmt"
"net/netip"
)
type Slice[T any] struct {
x []T
}
type IPPrefixSlice struct {
x Slice[netip.Prefix]
}
func (v Slice[T]) MarshalJSON() ([]byte, error) { return json.Marshal(v.x) }
// MarshalJSON implements json.Marshaler.
func (v IPPrefixSlice) MarshalJSON() ([]byte, error) {
return v.x.MarshalJSON()
}
func main() {
t := IPPrefixSlice{}
fmt.Println(t)
b, e := t.MarshalJSON()
fmt.Println(string(b), e)
}
// Output:
// {{[]}}
// null <nil>

31
_test/gen12.go Normal file
View File

@@ -0,0 +1,31 @@
package main
import (
"fmt"
)
func MapOf[K comparable, V any](m map[K]V) Map[K, V] {
return Map[K, V]{m}
}
type Map[K comparable, V any] struct {
ж map[K]V
}
func (v MapView) Int() Map[string, int] { return MapOf(v.ж.Int) }
type VMap struct {
Int map[string]int
}
type MapView struct {
ж *VMap
}
func main() {
mv := MapView{&VMap{}}
fmt.Println(mv.ж)
}
// Output:
// &{map[]}

18
_test/gen13.go Normal file
View File

@@ -0,0 +1,18 @@
package main
type Map[K comparable, V any] struct {
ж map[K]V
}
func (m Map[K, V]) Has(k K) bool {
_, ok := m.ж[k]
return ok
}
func main() {
m := Map[string, float64]{}
println(m.Has("test"))
}
// Output:
// false

34
_test/gen2.go Normal file
View File

@@ -0,0 +1,34 @@
package main
import "fmt"
// SumIntsOrFloats sums the values of map m. It supports both int64 and float64
// as types for map values.
func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
var s V
for _, v := range m {
s += v
}
return s
}
func main() {
// Initialize a map for the integer values
ints := map[string]int64{
"first": 34,
"second": 12,
}
// Initialize a map for the float values
floats := map[string]float64{
"first": 35.98,
"second": 26.99,
}
fmt.Printf("Generic Sums: %v and %v\n",
SumIntsOrFloats[string, int64](ints),
SumIntsOrFloats[string, float64](floats))
}
// Output:
// Generic Sums: 46 and 62.97

22
_test/gen3.go Normal file
View File

@@ -0,0 +1,22 @@
package main
type Number interface {
int | int64 | ~float64
}
func Sum[T Number](numbers []T) T {
var total T
for _, x := range numbers {
total += x
}
return total
}
func main() {
xs := []int{3, 5, 10}
total := Sum(xs)
println(total)
}
// Output:
// 18

42
_test/gen4.go Normal file
View File

@@ -0,0 +1,42 @@
package main
import "fmt"
type List[T any] struct {
head, tail *element[T]
}
// A recursive generic type.
type element[T any] struct {
next *element[T]
val T
}
func (lst *List[T]) Push(v T) {
if lst.tail == nil {
lst.head = &element[T]{val: v}
lst.tail = lst.head
} else {
lst.tail.next = &element[T]{val: v}
lst.tail = lst.tail.next
}
}
func (lst *List[T]) GetAll() []T {
var elems []T
for e := lst.head; e != nil; e = e.next {
elems = append(elems, e.val)
}
return elems
}
func main() {
lst := List[int]{}
lst.Push(10)
lst.Push(13)
lst.Push(23)
fmt.Println("list:", lst.GetAll())
}
// Output:
// list: [10 13 23]

24
_test/gen5.go Normal file
View File

@@ -0,0 +1,24 @@
package main
import "fmt"
type Set[Elem comparable] struct {
m map[Elem]struct{}
}
func Make[Elem comparable]() Set[Elem] {
return Set[Elem]{m: make(map[Elem]struct{})}
}
func (s Set[Elem]) Add(v Elem) {
s.m[v] = struct{}{}
}
func main() {
s := Make[int]()
s.Add(1)
fmt.Println(s)
}
// Output:
// {map[1:{}]}

19
_test/gen6.go Normal file
View File

@@ -0,0 +1,19 @@
package main
func MapKeys[K comparable, V any](m map[K]V) []K {
r := make([]K, 0, len(m))
for k := range m {
r = append(r, k)
}
return r
}
func main() {
var m = map[int]string{1: "2", 2: "4", 4: "8"}
// Test type inference
println(len(MapKeys(m)))
}
// Output:
// 3

19
_test/gen7.go Normal file
View File

@@ -0,0 +1,19 @@
package main
func MapKeys[K comparable, V any](m map[K]V) []K {
r := make([]K, 0, len(m))
for k := range m {
r = append(r, k)
}
return r
}
func main() {
var m = map[int]string{1: "2", 2: "4", 4: "8"}
// Test type inference
println(len(MapKeys))
}
// Error:
// invalid argument for len

15
_test/gen8.go Normal file
View File

@@ -0,0 +1,15 @@
package main
type Float interface {
~float32 | ~float64
}
func add[T Float](a, b T) float64 { return float64(a) + float64(b) }
func main() {
var x, y int = 1, 2
println(add(x, y))
}
// Error:
// int does not implement main.Float

14
_test/gen9.go Normal file
View File

@@ -0,0 +1,14 @@
package main
type Float interface {
~float32 | ~float64
}
func add[T Float](a, b T) float64 { return float64(a) + float64(b) }
func main() {
println(add(1, 2))
}
// Error:
// untyped int does not implement main.Float

17
_test/issue-1416.go Normal file
View File

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

9
_test/issue-1421.go Normal file
View File

@@ -0,0 +1,9 @@
package main
type Number = int
func main() {
println(Number(1) < int(2))
}
// Output: true

44
_test/issue-1425.go Normal file
View File

@@ -0,0 +1,44 @@
package main
import (
"io"
"log"
"os"
"strings"
)
type WrappedReader struct {
reader io.Reader
}
func (wr WrappedReader) Read(p []byte) (n int, err error) {
return wr.reader.Read(p)
}
// Of course, this implementation is completely stupid because it does not write
// to the intended writer, as any honest WriteTo implementation should. its
// implemtion is just to make obvious the divergence of behaviour with yaegi.
func (wr WrappedReader) WriteTo(w io.Writer) (n int64, err error) {
// Ignore w, send to Stdout to prove whether this WriteTo is used.
data, err := io.ReadAll(wr)
if err != nil {
return 0, err
}
nn, err := os.Stdout.Write(data)
return int64(nn), err
}
func main() {
f := strings.NewReader("hello world")
wr := WrappedReader{reader: f}
// behind the scenes, io.Copy is supposed to use wr.WriteTo if the implementation exists.
// With Go, it works as expected, i.e. the output is sent to os.Stdout.
// With Yaegi, it doesn't, i.e. the output is sent to io.Discard.
if _, err := io.Copy(io.Discard, wr); err != nil {
log.Fatal(err)
}
}
// Output:
// hello world

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

@@ -0,0 +1,25 @@
package main
type Transformer interface {
Reset()
}
type Encoder struct {
Transformer
}
type nop struct{}
func (nop) Reset() { println("Reset") }
func f(e Transformer) {
e.Reset()
}
func main() {
e := Encoder{Transformer: nop{}}
f(e)
}
// Output:
// Reset

41
_test/issue-1442.go Normal file
View File

@@ -0,0 +1,41 @@
package main
import (
"context"
)
func main() {
ctx, _ := context.WithCancel(context.Background())
ch := make(chan string, 20)
defer close(ch)
go func(ctx context.Context, ch <-chan string) {
for {
select {
case <-ctx.Done():
return
case tmp := <-ch:
_ = tmp
}
}
}(ctx, ch)
for _, i := range "abcdef" {
for _, j := range "0123456789" {
// i, j := "a", "0"
for _, k := range "ABCDEF" {
select {
case <-ctx.Done():
return
default:
tmp := string(i) + string(j) + string(k)
ch <- tmp
}
}
}
}
return
}
// Output:
//

20
_test/issue-1447.go Normal file
View File

@@ -0,0 +1,20 @@
package main
import "fmt"
type I interface {
Name() string
}
type S struct {
iMap map[string]I
}
func main() {
s := S{}
s.iMap = map[string]I{}
fmt.Println(s)
}
// Output:
// {map[]}

19
_test/issue-1451.go Normal file
View File

@@ -0,0 +1,19 @@
package main
type t1 uint8
const (
n1 t1 = iota
n2
)
type T struct {
elem [n2 + 1]int
}
func main() {
println(len(T{}.elem))
}
// Output:
// 2

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

@@ -0,0 +1,22 @@
package main
type I2 interface {
I2() string
}
type I interface {
I2
}
type S struct{}
func (*S) I2() string { return "foo" }
func main() {
var i I
_, ok := i.(*S)
println(ok)
}
// Output:
// false

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

@@ -0,0 +1,22 @@
package main
import "fmt"
type funclistItem func()
type funclist struct {
list []funclistItem
}
func main() {
funcs := funclist{}
funcs.list = append(funcs.list, func() { fmt.Println("first") })
for _, f := range funcs.list {
f()
}
}
// Output:
// first

84
_test/issue-1460.go Normal file
View File

@@ -0,0 +1,84 @@
package main
import (
"bytes"
"encoding/json"
"errors"
"net/netip"
"reflect"
)
func unmarshalJSON[T any](b []byte, x *[]T) error {
if *x != nil {
return errors.New("already initialized")
}
if len(b) == 0 {
return nil
}
return json.Unmarshal(b, x)
}
func SliceOfViews[T ViewCloner[T, V], V StructView[T]](x []T) SliceView[T, V] {
return SliceView[T, V]{x}
}
type StructView[T any] interface {
Valid() bool
AsStruct() T
}
type SliceView[T ViewCloner[T, V], V StructView[T]] struct {
ж []T
}
type ViewCloner[T any, V StructView[T]] interface {
View() V
Clone() T
}
func (v SliceView[T, V]) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
func (v *SliceView[T, V]) UnmarshalJSON(b []byte) error { return unmarshalJSON(b, &v.ж) }
type Slice[T any] struct {
ж []T
}
func (v Slice[T]) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
func (v *Slice[T]) UnmarshalJSON(b []byte) error { return unmarshalJSON(b, &v.ж) }
func SliceOf[T any](x []T) Slice[T] {
return Slice[T]{x}
}
type IPPrefixSlice struct {
ж Slice[netip.Prefix]
}
type viewStruct struct {
Int int
Strings Slice[string]
StringsPtr *Slice[string] `json:",omitempty"`
}
func main() {
ss := SliceOf([]string{"bar"})
in := viewStruct{
Int: 1234,
Strings: ss,
StringsPtr: &ss,
}
var buf bytes.Buffer
encoder := json.NewEncoder(&buf)
encoder.SetIndent("", "")
err1 := encoder.Encode(&in)
b := buf.Bytes()
var got viewStruct
err2 := json.Unmarshal(b, &got)
println(err1 == nil, err2 == nil, reflect.DeepEqual(got, in))
}
// Output:
// true true true

24
_test/issue-1465.go Normal file
View File

@@ -0,0 +1,24 @@
package main
import (
"fmt"
)
func SomeFunc[T int | string](defaultValue T) T {
switch v := any(&defaultValue).(type) {
case *string:
*v = *v + " abc"
case *int:
*v -= 234
}
return defaultValue
}
func main() {
fmt.Println(SomeFunc("test"))
fmt.Println(SomeFunc(1234))
}
// Output:
// test abc
// 1000

24
_test/issue-1466.go Normal file
View File

@@ -0,0 +1,24 @@
package main
import (
"fmt"
)
func SomeFunc(defaultValue interface{}) interface{} {
switch v := defaultValue.(type) {
case string:
return v + " abc"
case int:
return v - 234
}
panic("whoops")
}
func main() {
fmt.Println(SomeFunc(1234))
fmt.Println(SomeFunc("test"))
}
// Output:
// 1000
// test abc

15
_test/issue-1470.go Normal file
View File

@@ -0,0 +1,15 @@
package main
type T struct {
num [tnum + 2]int
}
const tnum = 23
func main() {
t := T{}
println(len(t.num))
}
// Output:
// 25

12
_test/issue-1475.go Normal file
View File

@@ -0,0 +1,12 @@
package main
type T uint16
func f() T { return 0 }
func main() {
println(f())
}
// Output:
// 0

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

@@ -0,0 +1,23 @@
package main
import "fmt"
type vector interface {
[]int | [3]int
}
func sum[V vector](v V) (out int) {
for i := 0; i < len(v); i++ {
out += v[i]
}
return
}
func main() {
va := [3]int{1, 2, 3}
vs := []int{1, 2, 3}
fmt.Println(sum[[3]int](va), sum[[]int](vs))
}
// Output:
// 6 6

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

@@ -0,0 +1,16 @@
package main
func main() {
a := []byte{} == nil
b := nil == []byte{}
c := nil == &struct{}{}
i := 100
d := nil == &i
var v interface{}
f := nil == v
g := v == nil
println(a, b, c, d, f, g)
}
// Output:
// false false false false true true

43
_test/issue-1515.go Normal file
View File

@@ -0,0 +1,43 @@
package main
type I1 interface {
I2
Wrap() *S3
}
type I2 interface {
F()
}
type S2 struct {
I2
}
func newS2(i2 I2) I1 {
return &S2{i2}
}
type S3 struct {
base *S2
}
func (s *S2) Wrap() *S3 {
i2 := s
return &S3{i2}
}
type T struct {
name string
}
func (t *T) F() { println("in F", t.name) }
func main() {
t := &T{"test"}
s2 := newS2(t)
s3 := s2.Wrap()
s3.base.F()
}
// Output:
// in F test

15
_test/issue-1536.go Normal file
View File

@@ -0,0 +1,15 @@
package main
var a [len(prefix+path) + 2]int
const (
prefix = "/usr/"
path = prefix + "local/bin"
)
func main() {
println(len(a))
}
// Output:
// 21

20
_test/issue-1571.go Normal file
View File

@@ -0,0 +1,20 @@
package main
type A struct {
*B[string]
}
type B[T any] struct {
data T
}
func main() {
_ = &A{
B: &B[string]{},
}
println("PASS")
}
// Output:
// PASS

17
_test/issue-1594.go Normal file
View File

@@ -0,0 +1,17 @@
package main
func main() {
var fns []func()
for _, v := range []int{1, 2, 3} {
x := v*100 + v
fns = append(fns, func() { println(x) })
}
for _, fn := range fns {
fn()
}
}
// Output:
// 101
// 202
// 303

32
_test/method40.go Normal file
View File

@@ -0,0 +1,32 @@
package main
import (
"bytes"
"io"
)
type TMemoryBuffer struct {
*bytes.Buffer
size int
}
func newTMemoryBuffer() *TMemoryBuffer {
return &TMemoryBuffer{}
}
var globalMemoryBuffer = newTMemoryBuffer()
type TTransport interface {
io.ReadWriter
}
func check(t TTransport) {
println("ok")
}
func main() {
check(globalMemoryBuffer)
}
// Output:
// ok

View File

@@ -1,6 +1,6 @@
package main
import "github.com/traefik/yaegi/_test/alias3"
import "github.com/traefik/yaegi/_test/named3"
var globalT *T
@@ -8,10 +8,10 @@ func init() {
globalT = &T{A: "test"}
}
type T alias3.T
type T named3.T
func (t *T) PrintT() {
(*alias3.T)(t).Print()
(*named3.T)(t).Print()
}
func main() {

31
_test/named3/named3.go Normal file
View File

@@ -0,0 +1,31 @@
package named3
import (
"fmt"
"net/http"
)
type T struct {
A string
}
func (t *T) Print() {
println(t.A)
}
type A http.Header
func (a A) ForeachKey() error {
for k, vals := range a {
for _, v := range vals {
fmt.Println(k, v)
}
}
return nil
}
func (a A) Set(k string, v []string) {
a[k] = v
}

View File

@@ -17,11 +17,23 @@ func (b B) Test2() {
fmt.Println("test2")
}
func (b B) Test3() {
for k, vals := range b {
for _, v := range vals {
fmt.Println(k, v)
}
}
}
func main() {
b := B{}
b.Test2()
b["test"] = []string{"a", "b"}
b.Test3()
}
// Output:
// test2
// test a
// test b

3
_test/p4/p4.go Normal file
View File

@@ -0,0 +1,3 @@
package p4
var Value1 = "value1"

10
_test/p5.go Normal file
View File

@@ -0,0 +1,10 @@
package main
import "github.com/traefik/yaegi/_test/p5"
func main() {
println(*p5.Value1)
}
// Output:
// value1

8
_test/p5/p5.go Normal file
View File

@@ -0,0 +1,8 @@
package p5
import "github.com/traefik/yaegi/_test/p4"
var (
Value1 = &val1
val1 = p4.Value1
)

14
_test/p6.go Normal file
View File

@@ -0,0 +1,14 @@
package main
import (
"fmt"
"github.com/traefik/yaegi/_test/p6"
)
func main() {
t := p6.IPPrefixSlice{}
fmt.Println(t)
b, e := t.MarshalJSON()
fmt.Println(string(b), e)
}

21
_test/p6/p6.go Normal file
View File

@@ -0,0 +1,21 @@
package p6
import (
"encoding/json"
"net/netip"
)
type Slice[T any] struct {
x []T
}
type IPPrefixSlice struct {
x Slice[netip.Prefix]
}
func (v Slice[T]) MarshalJSON() ([]byte, error) { return json.Marshal(v.x) }
// MarshalJSON implements json.Marshaler.
func (v IPPrefixSlice) MarshalJSON() ([]byte, error) {
return v.x.MarshalJSON()
}

20
_test/panic0.go Normal file
View File

@@ -0,0 +1,20 @@
package main
func main() {
foo()
}
func foo() {
bar()
}
func bar() {
baz()
}
func baz() {
panic("stop!")
}
// Error:
// stop!

16
_test/recurse1.go Normal file
View File

@@ -0,0 +1,16 @@
package main
type F func(a *A)
type A struct {
Name string
F
}
func main() {
a := &A{"Test", func(a *A) { println("in f", a.Name) }}
a.F(a)
}
// Output:
// in f Test

27
_test/recurse2.go Normal file
View File

@@ -0,0 +1,27 @@
package main
type F func(a *A)
type A struct {
B string
D
f F
}
type D struct {
*A
E *A
}
func f1(a *A) { println("in f1", a.B) }
func main() {
a := &A{B: "b", f: f1}
a.D = D{E: a}
println(a.D.E.B)
a.f(a)
}
// Output:
// b
// in f1 b

25
_test/recurse3.go Normal file
View File

@@ -0,0 +1,25 @@
package main
type F func(a *A)
type A struct {
B string
D
}
type D struct {
*A
E *A
f F
}
func f1(a *A) { println("in f1", a.B) }
func main() {
a := &A{B: "b"}
a.D = D{f: f1}
a.f(a)
}
// Output:
// in f1 b

View File

@@ -10,11 +10,11 @@ func main() {
c2 := make(chan string)
go func() {
time.Sleep(1e7)
time.Sleep(1e8)
c1 <- "one"
}()
go func() {
time.Sleep(2e7)
time.Sleep(2e8)
c2 <- "two"
}()

View File

@@ -6,8 +6,8 @@ import (
)
const (
period = 100 * time.Millisecond
precision = 7 * time.Millisecond
period = 300 * time.Millisecond
precision = 30 * time.Millisecond
)
func main() {

View File

@@ -25,7 +25,7 @@ func main() {
}
s.ts["test"] = append(s.ts["test"], &T{s: s})
t , ok:= s.getT("test")
t, ok := s.getT("test")
println(t != nil, ok)
}

17
_test/switch39.go Normal file
View File

@@ -0,0 +1,17 @@
package main
func f(params ...interface{}) {
switch p0 := params[0].(type) {
case string:
println("string:", p0)
default:
println("not a string")
}
}
func main() {
f("Hello")
}
// Output:
// string: Hello

17
_test/switch40.go Normal file
View File

@@ -0,0 +1,17 @@
package main
func f(params ...interface{}) {
switch params[0].(type) {
case string:
println("a string")
default:
println("not a string")
}
}
func main() {
f("Hello")
}
// Output:
// a string

View File

@@ -43,6 +43,6 @@ func assertValue() {
}
// Output:
// interface conversion: interface {} is int, not string
// interface conversion: interface {} is nil, not string
// interface conversion: *httptest.ResponseRecorder is not http.Pusher: missing method Push
// 22:10: interface conversion: interface {} is int, not string
// 32:10: interface conversion: interface {} is nil, not string
// 42:10: interface conversion: *httptest.ResponseRecorder is not http.Pusher: missing method Push

17
_test/unsafe10.go Normal file
View File

@@ -0,0 +1,17 @@
package main
import "unsafe"
type T struct {
X uint64
Y uint64
}
func f(off uintptr) { println(off) }
func main() {
f(unsafe.Offsetof(T{}.Y))
}
// Output:
// 8

View File

@@ -21,3 +21,6 @@ func main() {
fmt.Println(i)
}
// Output:
// 5

18
_test/unsafe8.go Normal file
View File

@@ -0,0 +1,18 @@
package main
import "unsafe"
type T struct {
i uint64
}
var d T
var b [unsafe.Sizeof(d)]byte
func main() {
println(len(b))
}
// Output:
// 8

View File

@@ -69,7 +69,7 @@ func extractCmd(arg []string) error {
ext.Include = strings.Split(include, ",")
}
r := strings.NewReplacer("/", "-", ".", "_")
r := strings.NewReplacer("/", "-", ".", "_", "~", "_")
for _, pkgIdent := range args {
var buf bytes.Buffer

View File

@@ -37,17 +37,7 @@ func applyCIMultiplier(timeout time.Duration) time.Duration {
}
func TestYaegiCmdCancel(t *testing.T) {
tmp, err := os.MkdirTemp("", "yaegi-")
if err != nil {
t.Fatalf("failed to create tmp directory: %v", err)
}
defer func() {
err = os.RemoveAll(tmp)
if err != nil {
t.Errorf("failed to clean up %v: %v", tmp, err)
}
}()
tmp := t.TempDir()
yaegi := filepath.Join(tmp, "yaegi")
args := []string{"build"}
@@ -125,6 +115,12 @@ func TestYaegiCmdCancel(t *testing.T) {
}
func raceDetectorSupported(goos, goarch string) bool {
if strings.Contains(os.Getenv("GOFLAGS"), "-buildmode=pie") {
// The Go race detector is not compatible with position independent code (pie).
// We read the conventional GOFLAGS env variable used for example on AlpineLinux
// to build packages, as there is no way to get this information from the runtime.
return false
}
switch goos {
case "linux":
return goarch == "amd64" || goarch == "ppc64le" || goarch == "arm64"

View File

@@ -2,9 +2,7 @@ package fs1
import (
"testing"
// only available from 1.16.
"testing/fstest"
"testing/fstest" // only available from 1.16.
"github.com/traefik/yaegi/interp"
"github.com/traefik/yaegi/stdlib"

View File

@@ -128,7 +128,7 @@ func TestPackages(t *testing.T) {
if test.topImport != "" {
topImport = test.topImport
}
if _, err = i.Eval(fmt.Sprintf(`import "%s"`, topImport)); err != nil {
if _, err = i.Eval(fmt.Sprintf(`import %q`, topImport)); err != nil {
t.Fatal(err)
}
value, err := i.Eval(`pkg.NewSample()`)

View File

@@ -39,7 +39,9 @@ import (
"{{$key}}"
{{- end}}
{{- end}}
{{- if or .Val .Typ }}
"{{.ImportPath}}"
{{- end}}
"reflect"
)
@@ -130,9 +132,18 @@ func matchList(name string, list []string) (match bool, err error) {
return
}
// Extractor creates a package with all the symbols from a dependency package.
type Extractor struct {
Dest string // The name of the created package.
License string // License text to be included in the created package, optional.
Exclude []string // Comma separated list of regexp matching symbols to exclude.
Include []string // Comma separated list of regexp matching symbols to include.
Tag []string // Comma separated of build tags to be added to the created package.
}
func (e *Extractor) genContent(importPath string, p *types.Package) ([]byte, error) {
prefix := "_" + importPath + "_"
prefix = strings.NewReplacer("/", "_", "-", "_", ".", "_").Replace(prefix)
prefix = strings.NewReplacer("/", "_", "-", "_", ".", "_", "~", "_").Replace(prefix)
typ := map[string]string{}
val := map[string]Val{}
@@ -190,12 +201,25 @@ func (e *Extractor) genContent(importPath string, p *types.Package) ([]byte, err
val[name] = Val{pname, false}
}
case *types.Func:
// Skip generic functions and methods.
if s := o.Type().(*types.Signature); s.TypeParams().Len() > 0 || s.RecvTypeParams().Len() > 0 {
continue
}
val[name] = Val{pname, false}
case *types.Var:
val[name] = Val{pname, true}
case *types.TypeName:
// Skip type if it is generic.
if t, ok := o.Type().(*types.Named); ok && t.TypeParams().Len() > 0 {
continue
}
typ[name] = pname
if t, ok := o.Type().Underlying().(*types.Interface); ok {
if t.NumMethods() == 0 && t.NumEmbeddeds() != 0 {
// Skip interfaces used to implement constraints for generics.
delete(typ, name)
continue
}
var methods []Method
for i := 0; i < t.NumMethods(); i++ {
f := t.Method(i)
@@ -278,11 +302,11 @@ func (e *Extractor) genContent(importPath string, p *types.Package) ([]byte, err
}
for _, t := range e.Tag {
if len(t) != 0 {
if t != "" {
buildTags += "," + t
}
}
if len(buildTags) != 0 && buildTags[0] == ',' {
if buildTags != "" && buildTags[0] == ',' {
buildTags = buildTags[1:]
}
@@ -335,7 +359,7 @@ func fixConst(name string, val constant.Value, imports map[string]bool) string {
str = f.Text('g', int(f.Prec()))
case constant.Complex:
// TODO: not sure how to parse this case
fallthrough
fallthrough //nolint:gocritic // Empty Fallthrough is expected.
default:
return name
}
@@ -346,15 +370,6 @@ func fixConst(name string, val constant.Value, imports map[string]bool) string {
return fmt.Sprintf("constant.MakeFromLiteral(%q, token.%s, 0)", str, tok)
}
// Extractor creates a package with all the symbols from a dependency package.
type Extractor struct {
Dest string // The name of the created package.
License string // License text to be included in the created package, optional.
Exclude []string // Comma separated list of regexp matching symbols to exclude.
Include []string // Comma separated list of regexp matching symbols to include.
Tag []string // Comma separated of build tags to be added to the created package.
}
// importPath checks whether pkgIdent is an existing directory relative to
// e.WorkingDir. If yes, it returns the actual import path of the Go package
// located in the directory. If it is definitely a relative path, but it does not
@@ -463,7 +478,7 @@ func GetMinor(part string) string {
return minor
}
const defaultMinorVersion = 17
const defaultMinorVersion = 22
func genBuildTags() (string, error) {
version := runtime.Version()

View File

@@ -1,3 +1,4 @@
// Package yaegi provides a Go interpreter.
package yaegi
//go:generate go generate github.com/traefik/yaegi/internal/cmd/extract

2
go.mod
View File

@@ -1,3 +1,3 @@
module github.com/traefik/yaegi
go 1.16
go 1.21

View File

@@ -11,7 +11,7 @@ Output files are written in the current directory, and prefixed with the go vers
Usage:
extract package...
extract package...
The same program is used for all target operating systems and architectures.
The GOOS and GOARCH environment variables set the desired target.

View File

@@ -539,7 +539,7 @@ func {{$name}}(n *node) {
{{- if or (eq $op.Name "==") (eq $op.Name "!=") }}
if c0.typ.cat == aliasT || c1.typ.cat == aliasT {
if c0.typ.cat == linkedT || c1.typ.cat == linkedT {
switch {
case isInterface:
v0 := genValue(c0)

View File

@@ -1,3 +1,7 @@
//go:build !go1.21
// +build !go1.21
// Package unsafe2 provides helpers to generate recursive struct types.
package unsafe2
import (

View File

@@ -0,0 +1,72 @@
//go:build go1.21
// +build go1.21
// Package unsafe2 provides helpers to generate recursive struct types.
package unsafe2
import (
"reflect"
"unsafe"
)
type dummy struct{}
// DummyType represents a stand-in for a recursive type.
var DummyType = reflect.TypeOf(dummy{})
// The following type sizes must match their original definition in Go src/internal/abi/type.go.
type abiType struct {
_ uintptr
_ uintptr
_ uint32
_ uint8
_ uint8
_ uint8
_ uint8
_ uintptr
_ uintptr
_ int32
_ int32
}
type abiName struct {
Bytes *byte
}
type abiStructField struct {
Name abiName
Typ *abiType
Offset uintptr
}
type abiStructType struct {
abiType
PkgPath abiName
Fields []abiStructField
}
type emptyInterface struct {
typ *abiType
_ unsafe.Pointer
}
// SetFieldType sets the type of the struct field at the given index, to the given type.
//
// The struct type must have been created at runtime. This is very unsafe.
func SetFieldType(s reflect.Type, idx int, t reflect.Type) {
if s.Kind() != reflect.Struct || idx >= s.NumField() {
return
}
rtyp := unpackType(s)
styp := (*abiStructType)(unsafe.Pointer(rtyp))
f := styp.Fields[idx]
f.Typ = unpackType(t)
styp.Fields[idx] = f
}
func unpackType(t reflect.Type) *abiType {
v := reflect.New(t).Elem().Interface()
eface := *(*emptyInterface)(unsafe.Pointer(&v))
return eface.typ
}

View File

@@ -72,6 +72,7 @@ const (
importSpec
incDecStmt
indexExpr
indexListExpr
interfaceType
keyValueExpr
labeledStmt
@@ -155,6 +156,7 @@ var kinds = [...]string{
importSpec: "importSpec",
incDecStmt: "incDecStmt",
indexExpr: "indexExpr",
indexListExpr: "indexListExpr",
interfaceType: "interfaceType",
keyValueExpr: "keyValueExpr",
labeledStmt: "labeledStmt",
@@ -694,7 +696,7 @@ func (interp *Interpreter) ast(f ast.Node) (string, *node, error) {
n := addChild(&root, anc, pos, funcDecl, aNop)
n.val = n
if a.Recv == nil {
// function is not a method, create an empty receiver list
// Function is not a method, create an empty receiver list.
addChild(&root, astNode{n, nod}, pos, fieldList, aNop)
}
st.push(n, nod)
@@ -706,7 +708,13 @@ func (interp *Interpreter) ast(f ast.Node) (string, *node, error) {
st.push(n, nod)
case *ast.FuncType:
st.push(addChild(&root, anc, pos, funcType, aNop), nod)
n := addChild(&root, anc, pos, funcType, aNop)
n.val = n
if a.TypeParams == nil {
// Function has no type parameters, create an empty fied list.
addChild(&root, astNode{n, nod}, pos, fieldList, aNop)
}
st.push(n, nod)
case *ast.GenDecl:
var kind nkind
@@ -776,6 +784,9 @@ func (interp *Interpreter) ast(f ast.Node) (string, *node, error) {
case *ast.IndexExpr:
st.push(addChild(&root, anc, pos, indexExpr, aGetIndex), nod)
case *ast.IndexListExpr:
st.push(addChild(&root, anc, pos, indexListExpr, aNop), nod)
case *ast.InterfaceType:
st.push(addChild(&root, anc, pos, interfaceType, aNop), nod)

View File

@@ -147,10 +147,22 @@ func skipFile(ctx *build.Context, p string, skipTest bool) bool {
}
a := strings.Split(p[i+1:], "_")
last := len(a) - 1
if last1 := last - 1; last1 >= 0 && a[last1] == ctx.GOOS && a[last] == ctx.GOARCH {
return false
if last-1 >= 0 {
switch x, y := a[last-1], a[last]; {
case x == ctx.GOOS:
if knownArch[y] {
return y != ctx.GOARCH
}
return false
case knownOs[x] && knownArch[y]:
return true
case knownArch[y] && y != ctx.GOARCH:
return true
default:
return false
}
}
if s := a[last]; s != ctx.GOOS && s != ctx.GOARCH && knownOs[s] || knownArch[s] {
if x := a[last]; knownOs[x] && x != ctx.GOOS || knownArch[x] && x != ctx.GOARCH {
return true
}
return false
@@ -170,6 +182,7 @@ var knownOs = map[string]bool{
"openbsd": true,
"plan9": true,
"solaris": true,
"wasip1": true,
"windows": true,
}

View File

@@ -51,7 +51,7 @@ func TestBuildTag(t *testing.T) {
}
}
func TestBuildFile(t *testing.T) {
func TestSkipFile(t *testing.T) {
// Assume a specific OS, arch and go pattern no matter the real underlying system
ctx := build.Context{
GOARCH: "amd64",
@@ -65,10 +65,18 @@ func TestBuildFile(t *testing.T) {
{"bar_linux.go", false},
{"bar_maix.go", false},
{"bar_mlinux.go", false},
{"bar_aix_foo.go", false},
{"bar_linux_foo.go", false},
{"bar_foo_amd64.go", false},
{"bar_foo_arm.go", true},
{"bar_aix_s390x.go", true},
{"bar_aix_amd64.go", true},
{"bar_linux_arm.go", true},
{"bar_amd64.go", false},
{"bar_arm.go", true},
}
for _, test := range tests {

View File

@@ -45,6 +45,13 @@ var constBltn = map[string]func(*node){
const nilIdent = "nil"
func init() {
// Use init() to avoid initialization cycles for the following constant builtins.
constBltn[bltnAlignof] = alignof
constBltn[bltnOffsetof] = offsetof
constBltn[bltnSizeof] = sizeof
}
// cfg generates a control flow graph (CFG) from AST (wiring successors in AST)
// and pre-compute frame sizes and indexes for all un-named (temporary) and named
// variables. A list of nodes of init functions is returned.
@@ -145,8 +152,11 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
}
switch o.typ.cat {
case valueT:
case valueT, linkedT:
typ := o.typ.rtype
if o.typ.cat == linkedT {
typ = o.typ.val.TypeOf()
}
switch typ.Kind() {
case reflect.Map:
n.anc.gen = rangeMap
@@ -218,6 +228,14 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
sc.sym[label] = sym
c.sym = sym
}
// If block is the body of a function, get declared variables in current scope.
// This is done in order to add the func signature symbols into sc.sym,
// as we will need them in post-processing.
if n.anc != nil && n.anc.kind == funcDecl {
for k, v := range sc.anc.sym {
sc.sym[k] = v
}
}
case breakStmt, continueStmt, gotoStmt:
if len(n.child) == 0 {
@@ -300,7 +318,7 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
// Indicate that the first child is the type.
n.nleft = 1
} else {
// Get type from ancestor (implicit type)
// Get type from ancestor (implicit type).
if n.anc.kind == keyValueExpr && n == n.anc.child[0] {
n.typ = n.anc.typ.key
} else if atyp := n.anc.typ; atyp != nil {
@@ -311,8 +329,60 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
}
}
if n.typ == nil {
err = n.cfgErrorf("undefined type")
return false
// A nil type indicates either an error or a generic type.
// A child indexExpr or indexListExpr is used for type parameters,
// it indicates an instanciated generic.
if n.child[0].kind != indexExpr && n.child[0].kind != indexListExpr {
err = n.cfgErrorf("undefined type")
return false
}
t0, err1 := nodeType(interp, sc, n.child[0].child[0])
if err1 != nil {
return false
}
if t0.cat != genericT {
err = n.cfgErrorf("undefined type")
return false
}
// We have a composite literal of generic type, instantiate it.
lt := []*itype{}
for _, n1 := range n.child[0].child[1:] {
t1, err1 := nodeType(interp, sc, n1)
if err1 != nil {
return false
}
lt = append(lt, t1)
}
var g *node
g, _, err = genAST(sc, t0.node.anc, lt)
if err != nil {
return false
}
n.child[0] = g.lastChild()
n.typ, err = nodeType(interp, sc, n.child[0])
if err != nil {
return false
}
// Generate methods if any.
for _, nod := range t0.method {
gm, _, err2 := genAST(nod.scope, nod, lt)
if err2 != nil {
err = err2
return false
}
gm.typ, err = nodeType(interp, nod.scope, gm.child[2])
if err != nil {
return false
}
if _, err = interp.cfg(gm, sc, sc.pkgID, sc.pkgName); err != nil {
return false
}
if err = genRun(gm); err != nil {
return false
}
n.typ.addMethod(gm)
}
n.nleft = 1 // Indictate the type of composite literal.
}
}
@@ -357,16 +427,55 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
fallthrough
case funcDecl:
// Do not allow function declarations without body.
if len(n.child) < 4 {
err = n.cfgErrorf("missing function body")
return false
}
n.val = n
// Skip substree in case of a generic function.
if len(n.child[2].child[0].child) > 0 {
return false
}
// Skip subtree if the function is a method with a generic receiver.
if len(n.child[0].child) > 0 {
recvTypeNode := n.child[0].child[0].lastChild()
typ, err := nodeType(interp, sc, recvTypeNode)
if err != nil {
return false
}
if typ.cat == genericT || (typ.val != nil && typ.val.cat == genericT) {
return false
}
if typ.cat == ptrT {
rc0 := recvTypeNode.child[0]
rt0, err := nodeType(interp, sc, rc0)
if err != nil {
return false
}
if rc0.kind == indexExpr && rt0.cat == structT {
return false
}
}
}
// Compute function type before entering local scope to avoid
// possible collisions with function argument names.
n.child[2].typ, err = nodeType(interp, sc, n.child[2])
// Add a frame indirection level as we enter in a func
if err != nil {
return false
}
n.typ = n.child[2].typ
// Add a frame indirection level as we enter in a func.
sc = sc.pushFunc()
sc.def = n
if len(n.child[2].child) == 2 {
// Allocate frame space for return values, define output symbols
for _, c := range n.child[2].child[1].child {
// Allocate frame space for return values, define output symbols.
if len(n.child[2].child) == 3 {
for _, c := range n.child[2].child[2].child {
var typ *itype
if typ, err = nodeType(interp, sc, c.lastChild()); err != nil {
return false
@@ -380,14 +489,28 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
}
}
}
// Define receiver symbol.
if len(n.child[0].child) > 0 {
// define receiver symbol
var typ *itype
fr := n.child[0].child[0]
recvTypeNode := fr.lastChild()
if typ, err = nodeType(interp, sc, recvTypeNode); err != nil {
return false
}
if typ.cat == nilT {
// This may happen when instantiating generic methods.
s2, _, ok := sc.lookup(typ.id())
if !ok {
err = n.cfgErrorf("type not found: %s", typ.id())
break
}
typ = s2.typ
if typ.cat == nilT {
err = n.cfgErrorf("nil type: %s", typ.id())
break
}
}
recvTypeNode.typ = typ
n.child[2].typ.recv = typ
n.typ.recv = typ
@@ -396,8 +519,9 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
sc.sym[fr.child[0].ident] = &symbol{index: index, kind: varSym, typ: typ}
}
}
for _, c := range n.child[2].child[0].child {
// define input parameter symbols
// Define input parameter symbols.
for _, c := range n.child[2].child[1].child {
var typ *itype
if typ, err = nodeType(interp, sc, c.lastChild()); err != nil {
return false
@@ -406,6 +530,7 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
sc.sym[cc.ident] = &symbol{index: sc.add(typ), kind: varSym, typ: typ}
}
}
if n.child[1].ident == "init" && len(n.child[0].child) == 0 {
initNodes = append(initNodes, n)
}
@@ -526,6 +651,10 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
var sym *symbol
var level int
if dest.rval.IsValid() && isConstType(dest.typ) {
err = n.cfgErrorf("cannot assign to %s (%s constant)", dest.rval, dest.typ.str)
break
}
if isBlank(src) {
err = n.cfgErrorf("cannot use _ as value")
break
@@ -550,7 +679,7 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
return
}
if sc.global {
// Do not overload existing symbols (defined in GTA) in global scope
// Do not overload existing symbols (defined in GTA) in global scope.
sym, _, _ = sc.lookup(dest.ident)
}
if sym == nil {
@@ -626,7 +755,7 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
n.gen = nop
src.findex = dest.findex
src.level = level
case len(n.child) < 4 && isArithmeticAction(src):
case len(n.child) < 4 && n.kind != defineStmt && isArithmeticAction(src) && !isInterface(dest.typ):
// Optimize single assignments from some arithmetic operations.
src.typ = dest.typ
src.findex = dest.findex
@@ -739,7 +868,11 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
n.typ = sc.getType("bool")
if c0.sym == nilSym || c1.sym == nilSym {
if n.action == aEqual {
n.gen = isNil
if c1.sym == nilSym {
n.gen = isNilChild(0)
} else {
n.gen = isNilChild(1)
}
} else {
n.gen = isNotNil
}
@@ -775,9 +908,7 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
case n.anc.kind == returnStmt:
// To avoid a copy in frame, if the result is to be returned, store it directly
// at the frame location reserved for output arguments.
pos := childPos(n)
n.typ = sc.def.typ.ret[pos]
n.findex = pos
n.findex = childPos(n)
default:
// Allocate a new location in frame, and store the result here.
n.findex = sc.add(n.typ)
@@ -790,13 +921,10 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
}
wireChild(n)
t := n.child[0].typ
for t.cat == linkedT {
t = t.val
}
switch t.cat {
case aliasT:
if isString(t.val.TypeOf()) {
n.typ = sc.getType("byte")
break
}
fallthrough
case ptrT:
n.typ = t.val
if t.val.cat == valueT {
@@ -812,6 +940,51 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
} else {
n.typ = valueTOf(t.rtype.Elem())
}
case funcT:
// A function indexed by a type means an instantiated generic function.
c1 := n.child[1]
if !c1.isType(sc) {
n.typ = t
return
}
g, found, err := genAST(sc, t.node.anc, []*itype{c1.typ})
if err != nil {
return
}
if !found {
if _, err = interp.cfg(g, t.node.anc.scope, importPath, pkgName); err != nil {
return
}
// Generate closures for function body.
if err = genRun(g.child[3]); err != nil {
return
}
}
// Replace generic func node by instantiated one.
n.anc.child[childPos(n)] = g
n.typ = g.typ
return
case genericT:
name := t.id() + "[" + n.child[1].typ.id() + "]"
sym, _, ok := sc.lookup(name)
if !ok {
err = n.cfgErrorf("type not found: %s", name)
return
}
n.gen = nop
n.typ = sym.typ
return
case structT:
// A struct indexed by a Type means an instantiated generic struct.
name := t.name + "[" + n.child[1].ident + "]"
sym, _, ok := sc.lookup(name)
if ok {
n.typ = sym.typ
n.findex = sc.add(n.typ)
n.gen = nop
return
}
default:
n.typ = t.val
}
@@ -867,7 +1040,14 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
}
wireChild(n)
case declStmt, exprStmt, sendStmt:
case sendStmt:
if !isChan(n.child[0].typ) {
err = n.cfgErrorf("invalid operation: cannot send to non-channel %s", n.child[0].typ.id())
break
}
fallthrough
case declStmt, exprStmt:
wireChild(n)
l := n.lastChild()
n.findex = l.findex
@@ -924,9 +1104,48 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
}
}
wireChild(n)
switch {
switch c0 := n.child[0]; {
case c0.kind == indexListExpr:
// Instantiate a generic function then call it.
fun := c0.child[0].sym.node
lt := []*itype{}
for _, c := range c0.child[1:] {
lt = append(lt, c.typ)
}
g, found, err := genAST(sc, fun, lt)
if err != nil {
return
}
if !found {
_, err = interp.cfg(g, fun.scope, importPath, pkgName)
if err != nil {
return
}
err = genRun(g.child[3]) // Generate closures for function body.
if err != nil {
return
}
}
n.child[0] = g
c0 = n.child[0]
wireChild(n)
if typ := c0.typ; len(typ.ret) > 0 {
n.typ = typ.ret[0]
if n.anc.kind == returnStmt && n.typ.id() == sc.def.typ.ret[0].id() {
// Store the result directly to the return value area of frame.
// It can be done only if no type conversion at return is involved.
n.findex = childPos(n)
} else {
n.findex = sc.add(n.typ)
for _, t := range typ.ret[1:] {
sc.add(t)
}
}
} else {
n.findex = notInFrame
}
case isBuiltinCall(n, sc):
c0 := n.child[0]
bname := c0.ident
err = check.builtin(bname, n, n.child[1:], n.action == aCallSlice)
if err != nil {
@@ -934,7 +1153,7 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
}
n.gen = c0.sym.builtin
c0.typ = &itype{cat: builtinT}
c0.typ = &itype{cat: builtinT, name: bname}
if n.typ, err = nodeType(interp, sc, n); err != nil {
return
}
@@ -942,6 +1161,10 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
case n.typ.cat == builtinT:
n.findex = notInFrame
n.val = nil
switch bname {
case "unsafe.alignOf", "unsafe.Offsetof", "unsafe.Sizeof":
n.gen = nop
}
case n.anc.kind == returnStmt:
// Store result directly to frame output location, to avoid a frame copy.
n.findex = 0
@@ -974,12 +1197,13 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
default:
n.findex = sc.add(n.typ)
}
if op, ok := constBltn[bname]; ok && n.anc.action != aAssign {
op(n) // pre-compute non-assigned constant :
if op, ok := constBltn[bname]; ok {
op(n)
}
case n.child[0].isType(sc):
case c0.isType(sc):
// Type conversion expression
c0, c1 := n.child[0], n.child[1]
c1 := n.child[1]
switch len(n.child) {
case 1:
err = n.cfgErrorf("missing argument in conversion to %s", c0.typ.id())
@@ -1024,16 +1248,17 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
n.typ = c0.typ
n.findex = sc.add(n.typ)
}
case isBinCall(n, sc):
err = check.arguments(n, n.child[1:], n.child[0], n.action == aCallSlice)
err = check.arguments(n, n.child[1:], c0, n.action == aCallSlice)
if err != nil {
break
}
n.gen = callBin
typ := n.child[0].typ.rtype
typ := c0.typ.rtype
if typ.NumOut() > 0 {
if funcType := n.child[0].typ.val; funcType != nil {
if funcType := c0.typ.val; funcType != nil {
// Use the original unwrapped function type, to allow future field and
// methods resolutions, otherwise impossible on the opaque bin type.
n.typ = funcType.ret[0]
@@ -1053,31 +1278,48 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
}
}
}
case isOffsetof(n):
if len(n.child) != 2 || n.child[1].kind != selectorExpr || !isStruct(n.child[1].child[0].typ) {
err = n.cfgErrorf("Offsetof argument: invalid expression")
break
}
c1 := n.child[1]
field, ok := c1.child[0].typ.rtype.FieldByName(c1.child[1].ident)
if !ok {
err = n.cfgErrorf("struct does not contain field: %s", c1.child[1].ident)
break
}
n.typ = valueTOf(reflect.TypeOf(field.Offset))
n.rval = reflect.ValueOf(field.Offset)
n.gen = nop
default:
err = check.arguments(n, n.child[1:], n.child[0], n.action == aCallSlice)
// The call may be on a generic function. In that case, replace the
// generic function AST by an instantiated one before going further.
if isGeneric(c0.typ) {
fun := c0.typ.node.anc
var g *node
var types []*itype
var found bool
// Infer type parameter from function call arguments.
if types, err = inferTypesFromCall(sc, fun, n.child[1:]); err != nil {
break
}
// Generate an instantiated AST from the generic function one.
if g, found, err = genAST(sc, fun, types); err != nil {
break
}
if !found {
// Compile the generated function AST, so it becomes part of the scope.
if _, err = interp.cfg(g, fun.scope, importPath, pkgName); err != nil {
break
}
// AST compilation part 2: Generate closures for function body.
if err = genRun(g.child[3]); err != nil {
break
}
}
n.child[0] = g
c0 = n.child[0]
}
err = check.arguments(n, n.child[1:], c0, n.action == aCallSlice)
if err != nil {
break
}
if n.child[0].action == aGetFunc {
if c0.action == aGetFunc {
// Allocate a frame entry to store the anonymous function definition.
sc.add(n.child[0].typ)
sc.add(c0.typ)
}
if typ := n.child[0].typ; len(typ.ret) > 0 {
if typ := c0.typ; len(typ.ret) > 0 {
n.typ = typ.ret[0]
if n.anc.kind == returnStmt && n.typ.id() == sc.def.typ.ret[0].id() {
// Store the result directly to the return value area of frame.
@@ -1295,7 +1537,7 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
n.types, n.scope = sc.types, sc
sc = sc.pop()
funcName := n.child[1].ident
if sym := sc.sym[funcName]; !isMethod(n) && sym != nil {
if sym := sc.sym[funcName]; !isMethod(n) && sym != nil && !isGeneric(sym.typ) {
sym.index = -1 // to force value to n.val
sym.typ = n.typ
sym.kind = funcSym
@@ -1321,6 +1563,10 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
sym, level, found := sc.lookup(n.ident)
if !found {
if n.typ != nil {
// Node is a generic instance with an already populated type.
break
}
// retry with the filename, in case ident is a package name.
sym, level, found = sc.lookup(filepath.Join(n.ident, baseName))
if !found {
@@ -1531,7 +1777,7 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
n.val = sc.def
for i, c := range n.child {
var typ *itype
typ, err = nodeType(interp, sc.upperLevel(), returnSig.child[1].fieldType(i))
typ, err = nodeType(interp, sc.upperLevel(), returnSig.child[2].fieldType(i))
if err != nil {
return
}
@@ -1543,12 +1789,7 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
}
if c.typ.cat == nilT {
// nil: Set node value to zero of return type
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()})
} else {
c.rval = reflect.New(typ.TypeOf()).Elem()
}
c.rval = reflect.New(typ.TypeOf()).Elem()
}
}
@@ -1571,6 +1812,10 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
} else {
n.typ = valueTOf(fixPossibleConstType(s.Type()), withUntyped(isValueUntyped(s)))
n.rval = s
if pkg == "unsafe" && (name == "AlignOf" || name == "Offsetof" || name == "Sizeof") {
n.sym = &symbol{kind: bltnSym, node: n, rval: s}
n.ident = pkg + "." + name
}
}
n.action = aGetSym
n.gen = nop
@@ -1657,105 +1902,9 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
tryMethods:
fallthrough
default:
// Find a matching method.
// TODO (marc): simplify the following if/elseif blocks.
if n.typ.cat == valueT || n.typ.cat == errorT {
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 = valueTOf(method.Type, isBinMethod())
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 = valueTOf(field.Type)
n.val = field.Index
n.gen = getPtrIndexSeq
break
}
err = n.cfgErrorf("undefined field or method: %s", n.child[1].ident)
case n.typ.rtype.Kind() == reflect.Struct:
if field, ok := n.typ.rtype.FieldByName(n.child[1].ident); ok {
n.typ = valueTOf(field.Type)
n.val = field.Index
n.gen = getIndexSeq
break
}
fallthrough
default:
// method lookup failed on type, now lookup on pointer to type
pt := reflect.PtrTo(n.typ.rtype)
if m2, ok2 := pt.MethodByName(n.child[1].ident); ok2 {
n.val = m2.Index
n.gen = getIndexBinPtrMethod
n.typ = valueTOf(m2.Type, isBinMethod(), withRecv(valueTOf(pt)))
n.recv = &receiver{node: n.child[0]}
n.action = aGetMethod
break
}
err = n.cfgErrorf("undefined field or method: %s", n.child[1].ident)
}
} else if n.typ.cat == ptrT && (n.typ.val.cat == valueT || n.typ.val.cat == errorT) {
// 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 = valueTOf(method.Type, isBinMethod(), withRecv(n.typ))
n.recv = &receiver{node: n.child[0]}
n.gen = getIndexBinElemMethod
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 = valueTOf(method.Type, withRecv(valueTOf(reflect.PtrTo(n.typ.val.rtype), isBinMethod())))
n.recv = &receiver{node: n.child[0]}
n.action = aGetMethod
} else if field, ok := n.typ.val.rtype.FieldByName(n.child[1].ident); ok {
n.typ = valueTOf(field.Type)
n.val = field.Index
n.gen = getPtrIndexSeq
} else {
err = n.cfgErrorf("undefined selector: %s", n.child[1].ident)
}
} else if m, lind := n.typ.lookupMethod(n.child[1].ident); m != nil {
n.action = aGetMethod
if n.child[0].isType(sc) {
// Handle method as a function with receiver in 1st argument
n.val = m
n.findex = notInFrame
n.gen = nop
n.typ = &itype{}
*n.typ = *m.typ
n.typ.arg = append([]*itype{n.child[0].typ}, m.typ.arg...)
} else {
// Handle method with receiver
n.gen = getMethod
n.val = m
n.typ = m.typ
n.recv = &receiver{node: n.child[0], index: lind}
}
} else if m, lind, isPtr, ok := n.typ.lookupBinMethod(n.child[1].ident); ok {
n.action = aGetMethod
switch {
case isPtr && n.typ.fieldSeq(lind).cat != ptrT:
n.gen = getIndexSeqPtrMethod
case isInterfaceSrc(n.typ):
n.gen = getMethodByName
default:
n.gen = getIndexSeqMethod
}
n.recv = &receiver{node: n.child[0], index: lind}
n.val = append([]int{m.Index}, lind...)
n.typ = valueTOf(m.Type, isBinMethod(), withRecv(n.child[0].typ))
} else {
err = n.cfgErrorf("undefined selector: %s", n.child[1].ident)
}
err = matchSelectorMethod(sc, n)
}
if err == nil && n.findex != -1 {
if err == nil && n.findex != -1 && n.typ.cat != genericT {
n.findex = sc.add(n.typ)
}
@@ -1763,9 +1912,13 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
wireChild(n)
// Move action to block statement, so select node can be an exit point.
n.child[0].gen = _select
// Chain channel init actions in commClauses prior to invoke select.
// Chain channel init actions in commClauses prior to invoking select.
var cur *node
for _, c := range n.child[0].child {
if c.kind == commClauseDefault {
// No channel init in this case.
continue
}
var an, pn *node // channel init action nodes
if len(c.child) > 0 {
switch c0 := c.child[0]; {
@@ -1902,7 +2055,14 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
}
sbn.start = clauses[0].start
n.start = n.child[0].start
n.child[0].tnext = sbn.start
if n.kind == typeSwitch {
// Handle the typeSwitch init (the type assert expression).
init := n.child[1].lastChild().child[0]
init.tnext = sbn.start
n.child[0].tnext = init.start
} else {
n.child[0].tnext = sbn.start
}
case switchIfStmt: // like an if-else chain
sc = sc.pop()
@@ -2132,12 +2292,38 @@ func compDefineX(sc *scope, n *node) error {
return n.cfgErrorf("unsupported assign expression")
}
// Handle redeclarations: find out new symbols vs existing ones.
symIsNew := map[string]bool{}
hasNewSymbol := false
for i := range types {
id := n.child[i].ident
if id == "_" || id == "" {
continue
}
if _, found := symIsNew[id]; found {
return n.cfgErrorf("%s repeated on left side of :=", id)
}
// A new symbol doesn't exist in current scope. Upper scopes are not
// taken into accout here, as a new symbol can shadow an existing one.
if _, found := sc.sym[id]; found {
symIsNew[id] = false
} else {
symIsNew[id] = true
hasNewSymbol = true
}
}
for i, t := range types {
var index int
id := n.child[i].ident
if sym, level, ok := sc.lookup(id); ok && level == n.child[i].level && sym.kind == varSym && sym.typ.id() == t.id() {
// Reuse symbol in case of a variable redeclaration with the same type.
// A variable can be redeclared if at least one other not blank variable is created.
// The redeclared variable must be of same type (it is reassigned, not created).
// Careful to not reuse a variable which has been shadowed (it must not be a newSym).
sym, level, ok := sc.lookup(id)
canRedeclare := hasNewSymbol && len(symIsNew) > 1 && !symIsNew[id] && ok
if canRedeclare && level == n.child[i].level && sym.kind == varSym && sym.typ.id() == t.id() {
index = sym.index
n.child[i].redeclared = true
} else {
index = sc.add(t)
sc.sym[id] = &symbol{index: index, kind: varSym, typ: t}
@@ -2178,11 +2364,13 @@ func (n *node) cfgErrorf(format string, a ...interface{}) *cfgError {
func genRun(nod *node) error {
var err error
seen := map[*node]bool{}
nod.Walk(func(n *node) bool {
if err != nil {
if err != nil || seen[n] {
return false
}
seen[n] = true
switch n.kind {
case funcType:
if len(n.anc.child) == 4 {
@@ -2271,15 +2459,22 @@ func genGlobalVarDecl(nodes []*node, sc *scope) (*node, error) {
func getVarDependencies(nod *node, sc *scope) (deps []*node) {
nod.Walk(func(n *node) bool {
if n.kind == identExpr {
if sym, _, ok := sc.lookup(n.ident); ok {
if sym.kind != varSym || !sym.global || sym.node == nod {
return false
}
deps = append(deps, sym.node)
}
if n.kind != identExpr {
return true
}
return true
// Process ident nodes, and avoid false dependencies.
if n.anc.kind == selectorExpr && childPos(n) == 1 {
return false
}
sym, _, ok := sc.lookup(n.ident)
if !ok {
return false
}
if sym.kind != varSym || !sym.global || sym.node == nod {
return false
}
deps = append(deps, sym.node)
return false
}, nil)
return deps
}
@@ -2348,6 +2543,10 @@ func (n *node) isType(sc *scope) bool {
}
case identExpr:
return sc.getType(n.ident) != nil
case indexExpr:
// Maybe a generic type.
sym, _, ok := sc.lookup(n.child[0].ident)
return ok && sym.kind == typeSym
}
return false
}
@@ -2519,6 +2718,17 @@ func isField(n *node) bool {
return n.kind == selectorExpr && len(n.child) > 0 && n.child[0].typ != nil && isStruct(n.child[0].typ)
}
func isInInterfaceType(n *node) bool {
anc := n.anc
for anc != nil {
if anc.kind == interfaceType {
return true
}
anc = anc.anc
}
return false
}
func isInConstOrTypeDecl(n *node) bool {
anc := n.anc
for anc != nil {
@@ -2584,15 +2794,11 @@ func isBinCall(n *node, sc *scope) bool {
return c0.typ.cat == valueT && c0.typ.rtype.Kind() == reflect.Func
}
func isOffsetof(n *node) bool {
return isCall(n) && n.child[0].typ.cat == valueT && n.child[0].rval.String() == "Offsetof"
}
func mustReturnValue(n *node) bool {
if len(n.child) < 2 {
if len(n.child) < 3 {
return false
}
for _, f := range n.child[1].child {
for _, f := range n.child[2].child {
if len(f.child) > 1 {
return false
}
@@ -2675,7 +2881,7 @@ func typeSwichAssign(n *node) bool {
func compositeGenerator(n *node, typ *itype, rtyp reflect.Type) (gen bltnGenerator) {
switch typ.cat {
case aliasT, ptrT:
case linkedT, ptrT:
gen = compositeGenerator(n, typ.val, rtyp)
case arrayT, sliceT:
gen = arrayLit
@@ -2723,6 +2929,124 @@ func compositeGenerator(n *node, typ *itype, rtyp reflect.Type) (gen bltnGenerat
return gen
}
// matchSelectorMethod, given that n represents a selector for a method, tries
// to find the corresponding method, and populates n accordingly.
func matchSelectorMethod(sc *scope, n *node) (err error) {
name := n.child[1].ident
if n.typ.cat == valueT || n.typ.cat == errorT {
switch method, ok := n.typ.rtype.MethodByName(name); {
case ok:
hasRecvType := n.typ.TypeOf().Kind() != reflect.Interface
n.val = method.Index
n.gen = getIndexBinMethod
n.action = aGetMethod
n.recv = &receiver{node: n.child[0]}
n.typ = valueTOf(method.Type, isBinMethod())
if hasRecvType {
n.typ.recv = n.typ
}
case n.typ.TypeOf().Kind() == reflect.Ptr:
if field, ok := n.typ.rtype.Elem().FieldByName(name); ok {
n.typ = valueTOf(field.Type)
n.val = field.Index
n.gen = getPtrIndexSeq
break
}
err = n.cfgErrorf("undefined method: %s", name)
case n.typ.TypeOf().Kind() == reflect.Struct:
if field, ok := n.typ.rtype.FieldByName(name); ok {
n.typ = valueTOf(field.Type)
n.val = field.Index
n.gen = getIndexSeq
break
}
fallthrough
default:
// method lookup failed on type, now lookup on pointer to type
pt := reflect.PtrTo(n.typ.rtype)
if m2, ok2 := pt.MethodByName(name); ok2 {
n.val = m2.Index
n.gen = getIndexBinPtrMethod
n.typ = valueTOf(m2.Type, isBinMethod(), withRecv(valueTOf(pt)))
n.recv = &receiver{node: n.child[0]}
n.action = aGetMethod
break
}
err = n.cfgErrorf("undefined method: %s", name)
}
return err
}
if n.typ.cat == ptrT && (n.typ.val.cat == valueT || n.typ.val.cat == errorT) {
// Handle pointer on object defined in runtime
if method, ok := n.typ.val.rtype.MethodByName(name); ok {
n.val = method.Index
n.typ = valueTOf(method.Type, isBinMethod(), withRecv(n.typ))
n.recv = &receiver{node: n.child[0]}
n.gen = getIndexBinElemMethod
n.action = aGetMethod
} else if method, ok := reflect.PtrTo(n.typ.val.rtype).MethodByName(name); ok {
n.val = method.Index
n.gen = getIndexBinMethod
n.typ = valueTOf(method.Type, withRecv(valueTOf(reflect.PtrTo(n.typ.val.rtype), isBinMethod())))
n.recv = &receiver{node: n.child[0]}
n.action = aGetMethod
} else if field, ok := n.typ.val.rtype.FieldByName(name); ok {
n.typ = valueTOf(field.Type)
n.val = field.Index
n.gen = getPtrIndexSeq
} else {
err = n.cfgErrorf("undefined selector: %s", name)
}
return err
}
if m, lind := n.typ.lookupMethod(name); m != nil {
n.action = aGetMethod
if n.child[0].isType(sc) {
// Handle method as a function with receiver in 1st argument.
n.val = m
n.findex = notInFrame
n.gen = nop
n.typ = &itype{}
*n.typ = *m.typ
n.typ.arg = append([]*itype{n.child[0].typ}, m.typ.arg...)
} else {
// Handle method with receiver.
n.gen = getMethod
n.val = m
n.typ = m.typ
n.recv = &receiver{node: n.child[0], index: lind}
}
return nil
}
if m, lind, isPtr, ok := n.typ.lookupBinMethod(name); ok {
n.action = aGetMethod
switch {
case isPtr && n.typ.fieldSeq(lind).cat != ptrT:
n.gen = getIndexSeqPtrMethod
case isInterfaceSrc(n.typ):
n.gen = getMethodByName
default:
n.gen = getIndexSeqMethod
}
n.recv = &receiver{node: n.child[0], index: lind}
n.val = append([]int{m.Index}, lind...)
n.typ = valueTOf(m.Type, isBinMethod(), withRecv(n.child[0].typ))
return nil
}
if typ := n.typ.interfaceMethod(name); typ != nil {
n.typ = typ
n.action = aGetMethod
n.gen = getMethodByName
return nil
}
return n.cfgErrorf("undefined selector: %s", name)
}
// arrayTypeLen returns the node's array length. If the expression is an
// array variable it is determined from the value's type, otherwise it is
// computed from the source definition.
@@ -2808,3 +3132,24 @@ func isBlank(n *node) bool {
}
return n.ident == "_"
}
func alignof(n *node) {
n.gen = nop
n.typ = n.scope.getType("uintptr")
n.rval = reflect.ValueOf(uintptr(n.child[1].typ.TypeOf().Align()))
}
func offsetof(n *node) {
n.gen = nop
n.typ = n.scope.getType("uintptr")
c1 := n.child[1]
if field, ok := c1.child[0].typ.rtype.FieldByName(c1.child[1].ident); ok {
n.rval = reflect.ValueOf(field.Offset)
}
}
func sizeof(n *node) {
n.gen = nop
n.typ = n.scope.getType("uintptr")
n.rval = reflect.ValueOf(n.child[1].typ.TypeOf().Size())
}

View File

@@ -52,7 +52,7 @@ func TestCompileAST(t *testing.T) {
node ast.Node
skip string
}{
{desc: "file", node: file},
{desc: "file", node: file, skip: "temporary ignore"},
{desc: "import", node: file.Imports[0]},
{desc: "type", node: dType},
{desc: "var", node: dVar, skip: "not supported"},

View File

@@ -4,7 +4,7 @@ Package interp provides a complete Go interpreter.
For the Go language itself, refer to the official Go specification
https://golang.org/ref/spec.
Importing packages
# Importing packages
Packages can be imported in source or binary form, using the standard
Go import statement. In source form, packages are searched first in the
@@ -16,7 +16,7 @@ Binary form packages are compiled and linked with the interpreter
executable, and exposed to scripts with the Use method. The extract
subcommand of yaegi can be used to generate package wrappers.
Custom build tags
# Custom build tags
Custom build tags allow to control which files in imported source
packages are interpreted, in the same way as the "-tags" option of the

319
interp/generic.go Normal file
View File

@@ -0,0 +1,319 @@
package interp
import (
"strings"
"sync/atomic"
)
// adot produces an AST dot(1) directed acyclic graph for the given node. For debugging only.
// func (n *node) adot() { n.astDot(dotWriter(n.interp.dotCmd), n.ident) }
// genAST returns a new AST where generic types are replaced by instantiated types.
func genAST(sc *scope, root *node, types []*itype) (*node, bool, error) {
typeParam := map[string]*node{}
pindex := 0
tname := ""
rtname := ""
recvrPtr := false
fixNodes := []*node{}
var gtree func(*node, *node) (*node, error)
sname := root.child[0].ident + "["
if root.kind == funcDecl {
sname = root.child[1].ident + "["
}
// Input type parameters must be resolved prior AST generation, as compilation
// of generated AST may occur in a different scope.
for _, t := range types {
sname += t.id() + ","
}
sname = strings.TrimSuffix(sname, ",") + "]"
gtree = func(n, anc *node) (*node, error) {
nod := copyNode(n, anc, false)
switch n.kind {
case funcDecl, funcType:
nod.val = nod
case identExpr:
// Replace generic type by instantiated one.
nt, ok := typeParam[n.ident]
if !ok {
break
}
nod = copyNode(nt, anc, true)
nod.typ = nt.typ
case indexExpr:
// Catch a possible recursive generic type definition
if root.kind != typeSpec {
break
}
if root.child[0].ident != n.child[0].ident {
break
}
nod := copyNode(n.child[0], anc, false)
fixNodes = append(fixNodes, nod)
return nod, nil
case fieldList:
// Node is the type parameters list of a generic function.
if root.kind == funcDecl && n.anc == root.child[2] && childPos(n) == 0 {
// Fill the types lookup table used for type substitution.
for _, c := range n.child {
l := len(c.child) - 1
for _, cc := range c.child[:l] {
if pindex >= len(types) {
return nil, cc.cfgErrorf("undefined type for %s", cc.ident)
}
t, err := nodeType(c.interp, sc, c.child[l])
if err != nil {
return nil, err
}
if err := checkConstraint(types[pindex], t); err != nil {
return nil, err
}
typeParam[cc.ident] = copyNode(cc, cc.anc, false)
typeParam[cc.ident].ident = types[pindex].id()
typeParam[cc.ident].typ = types[pindex]
pindex++
}
}
// Skip type parameters specification, so generated func doesn't look generic.
return nod, nil
}
// Node is the receiver of a generic method.
if root.kind == funcDecl && n.anc == root && childPos(n) == 0 && len(n.child) > 0 {
rtn := n.child[0].child[1]
// Method receiver is a generic type if it takes some type parameters.
if rtn.kind == indexExpr || rtn.kind == indexListExpr || (rtn.kind == starExpr && (rtn.child[0].kind == indexExpr || rtn.child[0].kind == indexListExpr)) {
if rtn.kind == starExpr {
// Method receiver is a pointer on a generic type.
rtn = rtn.child[0]
recvrPtr = true
}
rtname = rtn.child[0].ident + "["
for _, cc := range rtn.child[1:] {
if pindex >= len(types) {
return nil, cc.cfgErrorf("undefined type for %s", cc.ident)
}
it := types[pindex]
typeParam[cc.ident] = copyNode(cc, cc.anc, false)
typeParam[cc.ident].ident = it.id()
typeParam[cc.ident].typ = it
rtname += it.id() + ","
pindex++
}
rtname = strings.TrimSuffix(rtname, ",") + "]"
}
}
// Node is the type parameters list of a generic type.
if root.kind == typeSpec && n.anc == root && childPos(n) == 1 {
// Fill the types lookup table used for type substitution.
tname = n.anc.child[0].ident + "["
for _, c := range n.child {
l := len(c.child) - 1
for _, cc := range c.child[:l] {
if pindex >= len(types) {
return nil, cc.cfgErrorf("undefined type for %s", cc.ident)
}
it := types[pindex]
t, err := nodeType(c.interp, sc, c.child[l])
if err != nil {
return nil, err
}
if err := checkConstraint(types[pindex], t); err != nil {
return nil, err
}
typeParam[cc.ident] = copyNode(cc, cc.anc, false)
typeParam[cc.ident].ident = it.id()
typeParam[cc.ident].typ = it
tname += it.id() + ","
pindex++
}
}
tname = strings.TrimSuffix(tname, ",") + "]"
return nod, nil
}
}
for _, c := range n.child {
gn, err := gtree(c, nod)
if err != nil {
return nil, err
}
nod.child = append(nod.child, gn)
}
return nod, nil
}
if nod, found := root.interp.generic[sname]; found {
return nod, true, nil
}
r, err := gtree(root, root.anc)
if err != nil {
return nil, false, err
}
root.interp.generic[sname] = r
r.param = append(r.param, types...)
if tname != "" {
for _, nod := range fixNodes {
nod.ident = tname
}
r.child[0].ident = tname
}
if rtname != "" {
// Replace method receiver type by synthetized ident.
nod := r.child[0].child[0].child[1]
if recvrPtr {
nod = nod.child[0]
}
nod.kind = identExpr
nod.ident = rtname
nod.child = nil
}
// r.adot() // Used for debugging only.
return r, false, nil
}
func copyNode(n, anc *node, recursive bool) *node {
var i interface{}
nindex := atomic.AddInt64(&n.interp.nindex, 1)
nod := &node{
debug: n.debug,
anc: anc,
interp: n.interp,
index: nindex,
level: n.level,
nleft: n.nleft,
nright: n.nright,
kind: n.kind,
pos: n.pos,
action: n.action,
gen: n.gen,
val: &i,
rval: n.rval,
ident: n.ident,
meta: n.meta,
}
nod.start = nod
if recursive {
for _, c := range n.child {
nod.child = append(nod.child, copyNode(c, nod, true))
}
}
return nod
}
func inferTypesFromCall(sc *scope, fun *node, args []*node) ([]*itype, error) {
ftn := fun.typ.node
// Fill the map of parameter types, indexed by type param ident.
paramTypes := map[string]*itype{}
for _, c := range ftn.child[0].child {
typ, err := nodeType(fun.interp, sc, c.lastChild())
if err != nil {
return nil, err
}
for _, cc := range c.child[:len(c.child)-1] {
paramTypes[cc.ident] = typ
}
}
var inferTypes func(*itype, *itype) ([]*itype, error)
inferTypes = func(param, input *itype) ([]*itype, error) {
switch param.cat {
case chanT, ptrT, sliceT:
return inferTypes(param.val, input.val)
case mapT:
k, err := inferTypes(param.key, input.key)
if err != nil {
return nil, err
}
v, err := inferTypes(param.val, input.val)
if err != nil {
return nil, err
}
return append(k, v...), nil
case structT:
lt := []*itype{}
for i, f := range param.field {
nl, err := inferTypes(f.typ, input.field[i].typ)
if err != nil {
return nil, err
}
lt = append(lt, nl...)
}
return lt, nil
case funcT:
lt := []*itype{}
for i, t := range param.arg {
if i >= len(input.arg) {
break
}
nl, err := inferTypes(t, input.arg[i])
if err != nil {
return nil, err
}
lt = append(lt, nl...)
}
for i, t := range param.ret {
if i >= len(input.ret) {
break
}
nl, err := inferTypes(t, input.ret[i])
if err != nil {
return nil, err
}
lt = append(lt, nl...)
}
return lt, nil
case nilT:
if paramTypes[param.name] != nil {
return []*itype{input}, nil
}
case genericT:
return []*itype{input}, nil
}
return nil, nil
}
types := []*itype{}
for i, c := range ftn.child[1].child {
typ, err := nodeType(fun.interp, sc, c.lastChild())
if err != nil {
return nil, err
}
lt, err := inferTypes(typ, args[i].typ)
if err != nil {
return nil, err
}
types = append(types, lt...)
}
return types, nil
}
func checkConstraint(it, ct *itype) error {
if len(ct.constraint) == 0 && len(ct.ulconstraint) == 0 {
return nil
}
for _, c := range ct.constraint {
if it.equals(c) || it.matchDefault(c) {
return nil
}
}
for _, c := range ct.ulconstraint {
if it.underlying().equals(c) || it.matchDefault(c) {
return nil
}
}
return it.node.cfgErrorf("%s does not implement %s", it.id(), ct.id())
}

View File

@@ -21,6 +21,9 @@ func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([
if err != nil {
return false
}
if n.scope == nil {
n.scope = sc
}
switch n.kind {
case constDecl:
// Early parse of constDecl subtree, to compute all constant
@@ -144,6 +147,7 @@ func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([
if n.typ, err = nodeType(interp, sc, n.child[2]); err != nil {
return false
}
genericMethod := false
ident := n.child[1].ident
switch {
case isMethod(n):
@@ -153,8 +157,21 @@ func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([
rcvr := n.child[0].child[0]
rtn := rcvr.lastChild()
typName, typPtr := rtn.ident, false
// Identifies the receiver type name. It could be an ident, a
// generic type (indexExpr), or a pointer on either lasts.
if typName == "" {
typName, typPtr = rtn.child[0].ident, true
typName = rtn.child[0].ident
switch rtn.kind {
case starExpr:
typPtr = true
switch c := rtn.child[0]; c.kind {
case indexExpr, indexListExpr:
typName = c.child[0].ident
genericMethod = true
}
case indexExpr, indexListExpr:
genericMethod = true
}
}
sym, _, found := sc.lookup(typName)
if !found {
@@ -162,7 +179,7 @@ func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([
revisit = append(revisit, n)
return false
}
if sym.kind != typeSym || (sym.node != nil && sym.node.kind == typeSpecAssign) {
if sym.typ.path != pkgName {
err = n.cfgErrorf("cannot define new methods on non-local type %s", baseType(sym.typ).id())
return false
}
@@ -174,7 +191,15 @@ func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([
elementType.addMethod(n)
}
rcvrtype.addMethod(n)
n.child[0].child[0].lastChild().typ = rcvrtype
rtn.typ = rcvrtype
if rcvrtype.cat == genericT {
// generate methods for already instantiated receivers
for _, it := range rcvrtype.instance {
if err = genMethod(interp, sc, it, n, it.node.anc.param); err != nil {
return false
}
}
}
case ident == "init":
// init functions do not get declared as per the Go spec.
default:
@@ -185,9 +210,9 @@ func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([
return false
}
// Add a function symbol in the package name space except for init
sc.sym[n.child[1].ident] = &symbol{kind: funcSym, typ: n.typ, node: n, index: -1}
sc.sym[ident] = &symbol{kind: funcSym, typ: n.typ, node: n, index: -1}
}
if !n.typ.isComplete() {
if !n.typ.isComplete() && !genericMethod {
revisit = append(revisit, n)
}
return false
@@ -282,6 +307,15 @@ func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([
return false
}
typeName := n.child[0].ident
if len(n.child) > 2 {
// Handle a generic type: skip definition as parameter is not instantiated yet.
n.typ = genericOf(nil, typeName, pkgName, withNode(n.child[0]), withScope(sc))
if _, exists := sc.sym[typeName]; !exists {
sc.sym[typeName] = &symbol{kind: typeSym, node: n}
}
sc.sym[typeName].typ = n.typ
return false
}
var typ *itype
if typ, err = nodeType(interp, sc, n.child[1]); err != nil {
err = nil
@@ -289,6 +323,15 @@ func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([
return false
}
if n.kind == typeSpecAssign {
// Create an aliased type in the current scope
sc.sym[typeName] = &symbol{kind: typeSym, node: n, typ: typ}
n.typ = typ
break
}
// else we are not an alias (typeSpec)
switch n.child[1].kind {
case identExpr, selectorExpr:
n.typ = namedOf(typ, pkgName, typeName, withNode(n.child[0]), withScope(sc))
@@ -310,24 +353,15 @@ func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([
}
sym, exists := sc.sym[typeName]
if !exists {
sc.sym[typeName] = &symbol{kind: typeSym, node: n}
} else {
if sym.typ != nil && (len(sym.typ.method) > 0) {
if n.kind == typeSpecAssign {
err = n.cfgErrorf("cannot define new methods on non-local type %s", baseType(typ).id())
return false
}
// Type has already been seen as a receiver in a method function
for _, m := range sym.typ.method {
n.typ.addMethod(m)
}
} else {
// TODO(mpl): figure out how to detect redeclarations without breaking type aliases.
// Allow redeclarations for now.
sc.sym[typeName] = &symbol{kind: typeSym, node: n}
sym = &symbol{kind: typeSym, node: n}
sc.sym[typeName] = sym
} else if sym.typ != nil && (len(sym.typ.method) > 0) {
// Type has already been seen as a receiver in a method function
for _, m := range sym.typ.method {
n.typ.addMethod(m)
}
}
sc.sym[typeName].typ = n.typ
sym.typ = n.typ
if !n.typ.isComplete() {
revisit = append(revisit, n)
}
@@ -345,7 +379,7 @@ func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([
func baseType(t *itype) *itype {
for {
switch t.cat {
case ptrT, aliasT:
case ptrT, linkedT:
t = t.val
default:
return t
@@ -417,7 +451,7 @@ func definedType(typ *itype) error {
return err
}
fallthrough
case aliasT, arrayT, chanT, chanSendT, chanRecvT, ptrT, variadicT:
case linkedT, arrayT, chanT, chanSendT, chanRecvT, ptrT, variadicT:
if err := definedType(typ.val); err != nil {
return err
}

View File

@@ -4,16 +4,12 @@ import (
"bufio"
"context"
"errors"
"flag"
"fmt"
"go/build"
"go/constant"
"go/scanner"
"go/token"
"io"
"io/fs"
"log"
"math/bits"
"os"
"os/signal"
"path"
@@ -29,33 +25,35 @@ import (
// Interpreter node structure for AST and CFG.
type node struct {
debug *nodeDebugData // debug info
child []*node // child subtrees (AST)
anc *node // ancestor (AST)
start *node // entry point in subtree (CFG)
tnext *node // true branch successor (CFG)
fnext *node // false branch successor (CFG)
interp *Interpreter // interpreter context
frame *frame // frame pointer used for closures only (TODO: suppress this)
index int64 // node index (dot display)
findex int // index of value in frame or frame size (func def, type def)
level int // number of frame indirections to access value
nleft int // number of children in left part (assign) or indicates preceding type (compositeLit)
nright int // number of children in right part (assign)
kind nkind // kind of node
pos token.Pos // position in source code, relative to fset
sym *symbol // associated symbol
typ *itype // type of value in frame, or nil
recv *receiver // method receiver node for call, or nil
types []reflect.Type // frame types, used by function literals only
scope *scope // frame scope
action action // action
exec bltn // generated function to execute
gen bltnGenerator // generator function to produce above bltn
val interface{} // static generic value (CFG execution)
rval reflect.Value // reflection value to let runtime access interpreter (CFG)
ident string // set if node is a var or func
meta interface{} // meta stores meta information between gta runs, like errors
debug *nodeDebugData // debug info
child []*node // child subtrees (AST)
anc *node // ancestor (AST)
param []*itype // generic parameter nodes (AST)
start *node // entry point in subtree (CFG)
tnext *node // true branch successor (CFG)
fnext *node // false branch successor (CFG)
interp *Interpreter // interpreter context
frame *frame // frame pointer used for closures only (TODO: suppress this)
index int64 // node index (dot display)
findex int // index of value in frame or frame size (func def, type def)
level int // number of frame indirections to access value
nleft int // number of children in left part (assign) or indicates preceding type (compositeLit)
nright int // number of children in right part (assign)
kind nkind // kind of node
pos token.Pos // position in source code, relative to fset
sym *symbol // associated symbol
typ *itype // type of value in frame, or nil
recv *receiver // method receiver node for call, or nil
types []reflect.Type // frame types, used by function literals only
scope *scope // frame scope
action action // action
exec bltn // generated function to execute
gen bltnGenerator // generator function to produce above bltn
val interface{} // static generic value (CFG execution)
rval reflect.Value // reflection value to let runtime access interpreter (CFG)
ident string // set if node is a var or func
redeclared bool // set if node is a redeclared variable (CFG)
meta interface{} // meta stores meta information between gta runs, like errors
}
func (n *node) shouldBreak() bool {
@@ -108,7 +106,7 @@ type receiver struct {
type frame struct {
// id is an atomic counter used for cancellation, only accessed
// via newFrame/runid/setrunid/clone.
// Located at start of struct to ensure proper aligment.
// Located at start of struct to ensure proper alignment.
id uint64
debug *frameDebugData
@@ -186,14 +184,14 @@ type opt struct {
noRun bool // compile, but do not run
fastChan bool // disable cancellable chan operations
specialStdio bool // allows os.Stdin, os.Stdout, os.Stderr to not be file descriptors
unrestricted bool // allow use of non sandboxed symbols
unrestricted bool // allow use of non-sandboxed symbols
}
// Interpreter contains global resources and state.
type Interpreter struct {
// id is an atomic counter counter used for run cancellation,
// id is an atomic counter used for run cancellation,
// only accessed via runid/stop
// Located at start of struct to ensure proper alignment on 32 bit
// Located at start of struct to ensure proper alignment on 32-bit
// architectures.
id uint64
@@ -219,6 +217,7 @@ type Interpreter struct {
pkgNames map[string]string // package names, indexed by import path
done chan struct{} // for cancellation of channel operations
roots []*node
generic map[string]*node
hooks *hooks // symbol hooks
@@ -339,6 +338,7 @@ func New(options Options) *Interpreter {
pkgNames: map[string]string{},
rdir: map[string]bool{},
hooks: &hooks{},
generic: map[string]*node{},
}
if i.opt.stdin = options.Stdin; i.opt.stdin == nil {
@@ -405,28 +405,33 @@ func New(options Options) *Interpreter {
}
const (
bltnAppend = "append"
bltnCap = "cap"
bltnClose = "close"
bltnComplex = "complex"
bltnImag = "imag"
bltnCopy = "copy"
bltnDelete = "delete"
bltnLen = "len"
bltnMake = "make"
bltnNew = "new"
bltnPanic = "panic"
bltnPrint = "print"
bltnPrintln = "println"
bltnReal = "real"
bltnRecover = "recover"
bltnAlignof = "unsafe.Alignof"
bltnAppend = "append"
bltnCap = "cap"
bltnClose = "close"
bltnComplex = "complex"
bltnImag = "imag"
bltnCopy = "copy"
bltnDelete = "delete"
bltnLen = "len"
bltnMake = "make"
bltnNew = "new"
bltnOffsetof = "unsafe.Offsetof"
bltnPanic = "panic"
bltnPrint = "print"
bltnPrintln = "println"
bltnReal = "real"
bltnRecover = "recover"
bltnSizeof = "unsafe.Sizeof"
)
func initUniverse() *scope {
sc := &scope{global: true, sym: map[string]*symbol{
// predefined Go types
"any": {kind: typeSym, typ: &itype{cat: interfaceT, str: "any"}},
"bool": {kind: typeSym, typ: &itype{cat: boolT, name: "bool", str: "bool"}},
"byte": {kind: typeSym, typ: &itype{cat: uint8T, name: "uint8", str: "uint8"}},
"comparable": {kind: typeSym, typ: &itype{cat: comparableT, name: "comparable", str: "comparable"}},
"complex64": {kind: typeSym, typ: &itype{cat: complex64T, name: "complex64", str: "complex64"}},
"complex128": {kind: typeSym, typ: &itype{cat: complex128T, name: "complex128", str: "complex128"}},
"error": {kind: typeSym, typ: &itype{cat: errorT, name: "error", str: "error"}},
@@ -448,9 +453,9 @@ func initUniverse() *scope {
"uintptr": {kind: typeSym, typ: &itype{cat: uintptrT, name: "uintptr", str: "uintptr"}},
// predefined Go constants
"false": {kind: constSym, typ: untypedBool(), rval: reflect.ValueOf(false)},
"true": {kind: constSym, typ: untypedBool(), rval: reflect.ValueOf(true)},
"iota": {kind: constSym, typ: untypedInt()},
"false": {kind: constSym, typ: untypedBool(nil), rval: reflect.ValueOf(false)},
"true": {kind: constSym, typ: untypedBool(nil), rval: reflect.ValueOf(true)},
"iota": {kind: constSym, typ: untypedInt(nil)},
// predefined Go zero value
"nil": {typ: &itype{cat: nilT, untyped: true, str: "nil"}},
@@ -545,62 +550,6 @@ func (interp *Interpreter) EvalTest(path string) error {
return err
}
// 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()
defer interp.mutex.RUnlock()
for k, v := range interp.srcPkg {
if importPath != "" && k != importPath {
continue
}
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
}
func isFile(filesystem fs.FS, path string) bool {
fi, err := fs.Stat(filesystem, path)
return err == nil && fi.Mode().IsRegular()
@@ -662,167 +611,6 @@ func (interp *Interpreter) stop() {
func (interp *Interpreter) runid() uint64 { return atomic.LoadUint64(&interp.id) }
// getWrapper returns the wrapper type of the corresponding interface, or nil if not found.
func (interp *Interpreter) getWrapper(t reflect.Type) reflect.Type {
if p, ok := interp.binPkg[t.PkgPath()]; ok {
return p["_"+t.Name()].Type().Elem()
}
return nil
}
// Use loads binary runtime symbols in the interpreter context so
// they can be used in interpreted code.
func (interp *Interpreter) Use(values Exports) error {
for k, v := range values {
importPath := path.Dir(k)
packageName := path.Base(k)
if k == "." && v["MapTypes"].IsValid() {
// Use mapping for special interface wrappers.
for kk, vv := range v["MapTypes"].Interface().(map[reflect.Value][]reflect.Type) {
interp.mapTypes[kk] = vv
}
continue
}
if importPath == "." {
return fmt.Errorf("export path %[1]q is missing a package name; did you mean '%[1]s/%[1]s'?", k)
}
if importPath == selfPrefix {
interp.hooks.Parse(v)
continue
}
if interp.binPkg[importPath] == nil {
interp.binPkg[importPath] = make(map[string]reflect.Value)
interp.pkgNames[importPath] = packageName
}
for s, sym := range v {
interp.binPkg[importPath][s] = sym
}
if k == selfPath {
interp.binPkg[importPath]["Self"] = reflect.ValueOf(interp)
}
}
// Checks if input values correspond to stdlib packages by looking for one
// well known stdlib package path.
if _, ok := values["fmt/fmt"]; ok {
fixStdlib(interp)
}
return nil
}
// fixStdlib redefines interpreter stdlib symbols to use the standard input,
// output and errror assigned to the interpreter. The changes are limited to
// the interpreter only.
// Note that it is possible to escape the virtualized stdio by
// read/write directly to file descriptors 0, 1, 2.
func fixStdlib(interp *Interpreter) {
p := interp.binPkg["fmt"]
if p == nil {
return
}
stdin, stdout, stderr := interp.stdin, interp.stdout, interp.stderr
p["Print"] = reflect.ValueOf(func(a ...interface{}) (n int, err error) { return fmt.Fprint(stdout, a...) })
p["Printf"] = reflect.ValueOf(func(f string, a ...interface{}) (n int, err error) { return fmt.Fprintf(stdout, f, a...) })
p["Println"] = reflect.ValueOf(func(a ...interface{}) (n int, err error) { return fmt.Fprintln(stdout, a...) })
p["Scan"] = reflect.ValueOf(func(a ...interface{}) (n int, err error) { return fmt.Fscan(stdin, a...) })
p["Scanf"] = reflect.ValueOf(func(f string, a ...interface{}) (n int, err error) { return fmt.Fscanf(stdin, f, a...) })
p["Scanln"] = reflect.ValueOf(func(a ...interface{}) (n int, err error) { return fmt.Fscanln(stdin, a...) })
// Update mapTypes to virtualized symbols as well.
interp.mapTypes[p["Print"]] = interp.mapTypes[reflect.ValueOf(fmt.Print)]
interp.mapTypes[p["Printf"]] = interp.mapTypes[reflect.ValueOf(fmt.Printf)]
interp.mapTypes[p["Println"]] = interp.mapTypes[reflect.ValueOf(fmt.Println)]
interp.mapTypes[p["Scan"]] = interp.mapTypes[reflect.ValueOf(fmt.Scan)]
interp.mapTypes[p["Scanf"]] = interp.mapTypes[reflect.ValueOf(fmt.Scanf)]
interp.mapTypes[p["Scanln"]] = interp.mapTypes[reflect.ValueOf(fmt.Scanln)]
if p = interp.binPkg["flag"]; p != nil {
c := flag.NewFlagSet(os.Args[0], flag.PanicOnError)
c.SetOutput(stderr)
p["CommandLine"] = reflect.ValueOf(&c).Elem()
}
if p = interp.binPkg["log"]; p != nil {
l := log.New(stderr, "", log.LstdFlags)
// Restrict Fatal symbols to panic instead of exit.
p["Fatal"] = reflect.ValueOf(l.Panic)
p["Fatalf"] = reflect.ValueOf(l.Panicf)
p["Fatalln"] = reflect.ValueOf(l.Panicln)
p["Flags"] = reflect.ValueOf(l.Flags)
p["Output"] = reflect.ValueOf(l.Output)
p["Panic"] = reflect.ValueOf(l.Panic)
p["Panicf"] = reflect.ValueOf(l.Panicf)
p["Panicln"] = reflect.ValueOf(l.Panicln)
p["Prefix"] = reflect.ValueOf(l.Prefix)
p["Print"] = reflect.ValueOf(l.Print)
p["Printf"] = reflect.ValueOf(l.Printf)
p["Println"] = reflect.ValueOf(l.Println)
p["SetFlags"] = reflect.ValueOf(l.SetFlags)
p["SetOutput"] = reflect.ValueOf(l.SetOutput)
p["SetPrefix"] = reflect.ValueOf(l.SetPrefix)
p["Writer"] = reflect.ValueOf(l.Writer)
// Update mapTypes to virtualized symbols as well.
interp.mapTypes[p["Print"]] = interp.mapTypes[reflect.ValueOf(log.Print)]
interp.mapTypes[p["Printf"]] = interp.mapTypes[reflect.ValueOf(log.Printf)]
interp.mapTypes[p["Println"]] = interp.mapTypes[reflect.ValueOf(log.Println)]
interp.mapTypes[p["Panic"]] = interp.mapTypes[reflect.ValueOf(log.Panic)]
interp.mapTypes[p["Panicf"]] = interp.mapTypes[reflect.ValueOf(log.Panicf)]
interp.mapTypes[p["Panicln"]] = interp.mapTypes[reflect.ValueOf(log.Panicln)]
}
if p = interp.binPkg["os"]; p != nil {
p["Args"] = reflect.ValueOf(&interp.args).Elem()
if interp.specialStdio {
// Inherit streams from interpreter even if they do not have a file descriptor.
p["Stdin"] = reflect.ValueOf(&stdin).Elem()
p["Stdout"] = reflect.ValueOf(&stdout).Elem()
p["Stderr"] = reflect.ValueOf(&stderr).Elem()
} else {
// Inherits streams from interpreter only if they have a file descriptor and preserve original type.
if s, ok := stdin.(*os.File); ok {
p["Stdin"] = reflect.ValueOf(&s).Elem()
}
if s, ok := stdout.(*os.File); ok {
p["Stdout"] = reflect.ValueOf(&s).Elem()
}
if s, ok := stderr.(*os.File); ok {
p["Stderr"] = reflect.ValueOf(&s).Elem()
}
}
if !interp.unrestricted {
// In restricted mode, scripts can only access to a passed virtualized env, and can not write the real one.
getenv := func(key string) string { return interp.env[key] }
p["Clearenv"] = reflect.ValueOf(func() { interp.env = map[string]string{} })
p["ExpandEnv"] = reflect.ValueOf(func(s string) string { return os.Expand(s, getenv) })
p["Getenv"] = reflect.ValueOf(getenv)
p["LookupEnv"] = reflect.ValueOf(func(key string) (s string, ok bool) { s, ok = interp.env[key]; return })
p["Setenv"] = reflect.ValueOf(func(key, value string) error { interp.env[key] = value; return nil })
p["Unsetenv"] = reflect.ValueOf(func(key string) error { delete(interp.env, key); return nil })
p["Environ"] = reflect.ValueOf(func() (a []string) {
for k, v := range interp.env {
a = append(a, k+"="+v)
}
return
})
}
}
if p = interp.binPkg["math/bits"]; p != nil {
// Do not trust extracted value maybe from another arch.
p["UintSize"] = reflect.ValueOf(constant.MakeInt64(bits.UintSize))
}
}
// ignoreScannerError returns true if the error from Go scanner can be safely ignored
// to let the caller grab one more line before retrying to parse its input.
func ignoreScannerError(e *scanner.Error, s string) bool {

View File

@@ -1,11 +1,13 @@
package interp_test
import (
"bytes"
"go/build"
"io"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
@@ -14,6 +16,13 @@ import (
"github.com/traefik/yaegi/stdlib/unsafe"
)
// The following tests depend on an incompatible language change in go1.22, where `for` variables are now
// defined in body (thus reallocated at each loop). We skip them until both supported versions behave the same.
// We will remove this in Go1.23.
var testsToSkipGo122 = map[string]bool{"closure9.go": true, "closure10.go": true, "closure11.go": true, "closure12.go": true}
var go122 = strings.HasPrefix(runtime.Version(), "go1.22")
func TestInterpConsistencyBuild(t *testing.T) {
if testing.Short() {
t.Skip("short mode")
@@ -48,6 +57,9 @@ func TestInterpConsistencyBuild(t *testing.T) {
file.Name() == "fun23.go" || // expect error
file.Name() == "fun24.go" || // expect error
file.Name() == "fun25.go" || // expect error
file.Name() == "gen7.go" || // expect error
file.Name() == "gen8.go" || // expect error
file.Name() == "gen9.go" || // expect error
file.Name() == "if2.go" || // expect error
file.Name() == "import6.go" || // expect error
file.Name() == "init1.go" || // expect error
@@ -86,6 +98,7 @@ func TestInterpConsistencyBuild(t *testing.T) {
file.Name() == "redeclaration-global5.go" || // expect error
file.Name() == "redeclaration-global6.go" || // expect error
file.Name() == "redeclaration-global7.go" || // expect error
file.Name() == "panic0.go" || // expect error
file.Name() == "pkgname0.go" || // has deps
file.Name() == "pkgname1.go" || // expect error
file.Name() == "pkgname2.go" || // has deps
@@ -106,6 +119,7 @@ func TestInterpConsistencyBuild(t *testing.T) {
file.Name() == "range9.go" || // expect error
file.Name() == "unsafe6.go" || // needs go.mod to be 1.17
file.Name() == "unsafe7.go" || // needs go.mod to be 1.17
file.Name() == "type24.go" || // expect error
file.Name() == "type27.go" || // expect error
file.Name() == "type28.go" || // expect error
file.Name() == "type29.go" || // expect error
@@ -115,6 +129,13 @@ func TestInterpConsistencyBuild(t *testing.T) {
file.Name() == "type33.go" { // expect error
continue
}
// Skip some tests which are problematic in go1.21 only.
if go121 && testsToSkipGo121[file.Name()] {
continue
}
if go122 && testsToSkipGo122[file.Name()] {
continue
}
file := file
t.Run(file.Name(), func(t *testing.T) {
@@ -172,7 +193,7 @@ func TestInterpConsistencyBuild(t *testing.T) {
t.Fatal(err)
}
if string(outInterp) != string(outRun) {
if !bytes.Equal(outInterp, outRun) {
t.Errorf("\nGot: %q,\n want: %q", string(outInterp), string(outRun))
}
})
@@ -183,17 +204,18 @@ func TestInterpErrorConsistency(t *testing.T) {
testCases := []struct {
fileName string
expectedInterp string
expectedStderr string
expectedExec string
}{
{
fileName: "assign11.go",
expectedInterp: "6:2: assignment mismatch: 3 variables but fmt.Println returns 2 values",
expectedExec: "6:10: assignment mismatch: 3 variables but fmt.Println returns 2 values",
expectedExec: "6:12: assignment mismatch: 3 variables but fmt.Println returns 2 values",
},
{
fileName: "assign12.go",
expectedInterp: "6:2: assignment mismatch: 3 variables but fmt.Println returns 2 values",
expectedExec: "6:10: assignment mismatch: 3 variables but fmt.Println returns 2 values",
expectedExec: "6:13: assignment mismatch: 3 variables but fmt.Println returns 2 values",
},
{
fileName: "bad0.go",
@@ -213,46 +235,47 @@ func TestInterpErrorConsistency(t *testing.T) {
{
fileName: "const9.go",
expectedInterp: "5:2: constant definition loop",
expectedExec: "5:2: constant definition loop",
expectedExec: "5:2: initialization",
},
{
fileName: "if2.go",
expectedInterp: "7:5: non-bool used as if condition",
expectedExec: "7:2: non-bool i % 1000000 (type int) used as if condition",
expectedExec: "7:5: non-boolean condition in if statement",
},
{
fileName: "for7.go",
expectedInterp: "4:14: non-bool used as for condition",
expectedExec: "4:2: non-bool i (type int) used as for condition",
expectedExec: "4:14: non-boolean condition in for statement",
},
{
fileName: "fun21.go",
expectedInterp: "4:2: not enough arguments to return",
expectedExec: "4:2: not enough arguments to return",
expectedExec: "4:2: not enough return values",
},
{
fileName: "fun22.go",
expectedInterp: "6:2: not enough arguments in call to time.Date",
expectedExec: "6:11: not enough arguments in call to time.Date",
expectedExec: "6:2: 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",
expectedExec: "3:24: too many return values",
},
{
fileName: "issue-1093.go",
expectedInterp: "9:6: cannot use type untyped string as type int in assignment",
expectedExec: `9:4: cannot use "a" + b() (type string) as type int in assignment`,
expectedExec: `9:6: cannot use "a" + b() (value of type string)`,
},
{
fileName: "op1.go",
expectedInterp: "5:2: invalid operation: mismatched types int and untyped float",
expectedExec: "5:4: constant 1.3 truncated to integer",
expectedExec: "5:7: 1.3 (untyped float constant) truncated to int",
},
{
fileName: "bltn0.go",
expectedInterp: "4:7: use of builtin println not in function call",
expectedExec: "4:7: println (built-in) must be called",
},
{
fileName: "import6.go",
@@ -267,29 +290,40 @@ func TestInterpErrorConsistency(t *testing.T) {
{
fileName: "switch9.go",
expectedInterp: "9:3: cannot fallthrough in type switch",
expectedExec: "9:3: cannot fallthrough in type switch",
expectedExec: "fallthrough",
},
{
fileName: "switch13.go",
expectedInterp: "9:2: i is not a type",
expectedExec: "9:2: i (type interface {}) is not a type",
expectedExec: "9:7: i (variable of type interface{}) is not a type",
},
{
fileName: "switch19.go",
expectedInterp: "37:2: duplicate case Bir in type switch",
expectedExec: "37:2: duplicate case Bir in type switch",
expectedExec: "37:7: duplicate case Bir in type switch",
},
{
fileName: "panic0.go",
expectedInterp: "stop!",
expectedStderr: `
../_test/panic0.go:16:2: panic: main.baz(...)
../_test/panic0.go:12:2: panic: main.bar(...)
../_test/panic0.go:8:2: panic: main.foo(...)
../_test/panic0.go:4:2: panic: main.main(...)
`,
},
}
for _, test := range testCases {
t.Run(test.fileName, func(t *testing.T) {
if len(test.expectedInterp) == 0 && len(test.expectedExec) == 0 {
t.Fatal("at least expectedInterp must be define")
if test.expectedInterp == "" && test.expectedExec == "" {
t.Fatal("at least expectedInterp must be defined")
}
filePath := filepath.Join("..", "_test", test.fileName)
i := interp.New(interp.Options{GoPath: build.Default.GOPATH})
var stderr bytes.Buffer
i := interp.New(interp.Options{GoPath: build.Default.GOPATH, Stderr: &stderr})
if err := i.Use(stdlib.Symbols); err != nil {
t.Fatal(err)
}
@@ -302,6 +336,12 @@ func TestInterpErrorConsistency(t *testing.T) {
if !strings.Contains(errEval.Error(), test.expectedInterp) {
t.Errorf("got %q, want: %q", errEval.Error(), test.expectedInterp)
}
if test.expectedStderr != "" {
exp, got := strings.TrimSpace(test.expectedStderr), strings.TrimSpace(stderr.String())
if exp != got {
t.Errorf("got %q, want: %q", got, exp)
}
}
cmd := exec.Command("go", "run", filePath)
outRun, errExec := cmd.CombinedOutput()
@@ -310,7 +350,7 @@ func TestInterpErrorConsistency(t *testing.T) {
t.Fatal("An error is expected but got none.")
}
if len(test.expectedExec) == 0 && !strings.Contains(string(outRun), test.expectedInterp) {
if test.expectedExec == "" && !strings.Contains(string(outRun), test.expectedInterp) {
t.Errorf("got %q, want: %q", string(outRun), test.expectedInterp)
} else if !strings.Contains(string(outRun), test.expectedExec) {
t.Errorf("got %q, want: %q", string(outRun), test.expectedExec)

View File

@@ -133,6 +133,9 @@ func TestEvalAssign(t *testing.T) {
{src: "j := true || _", err: "1:33: cannot use _ as value"},
{src: "j := true && _", err: "1:33: cannot use _ as value"},
{src: "j := interface{}(int(1)); j.(_)", err: "1:54: cannot use _ as value"},
{src: "ff := func() (a, b, c int) {return 1, 2, 3}; x, y, x := ff()", err: "1:73: x repeated on left side of :="},
{src: "xx := 1; xx, _ := 2, 3", err: "1:37: no new variables on left side of :="},
{src: "1 = 2", err: "1:28: cannot assign to 1 (untyped int constant)"},
})
}
@@ -216,6 +219,8 @@ func TestEvalTypeSpec(t *testing.T) {
runTests(t, i, []testCase{
{src: `type _ struct{}`, err: "1:19: cannot use _ as value"},
{src: `a := struct{a, _ int}{32, 0}`, res: "{32 0}"},
{src: "type A int; type A = string", err: "1:31: A redeclared in this block"},
{src: "type B int; type B string", err: "1:31: B redeclared in this block"},
})
}
@@ -540,8 +545,8 @@ func TestEvalSliceExpression(t *testing.T) {
{src: `a := (&[]int{0,1,2,3})[1:3]`, err: "1:33: cannot slice type *[]int"},
{src: `a := "hello"[1:3:4]`, err: "1:45: invalid operation: 3-index slice of string"},
{src: `ar := [3]int{0,1,2}; a := ar[:4]`, err: "1:58: index int is out of bounds"},
{src: `a := []int{0,1,2,3}[1::4]`, err: "1:49: 2nd index required in 3-index slice"},
{src: `a := []int{0,1,2,3}[1:3:]`, err: "1:51: 3rd index required in 3-index slice"},
{src: `a := []int{0,1,2,3}[1::4]`, err: "index required in 3-index slice"},
{src: `a := []int{0,1,2,3}[1:3:]`, err: "index required in 3-index slice"},
{src: `a := []int{0,1,2}[3:1]`, err: "invalid index values, must be low <= high <= max"},
{pre: func() { eval(t, i, `type Str = string; var r Str = "truc"`) }, src: `r[1]`, res: "114"},
{src: `_[12]`, err: "1:28: cannot use _ as value"},
@@ -640,6 +645,8 @@ func TestEvalChan(t *testing.T) {
return ok && msg == "ping"
})()`, res: "true",
},
{src: `a :=5; a <- 4`, err: "cannot send to non-channel int"},
{src: `a :=5; b := <-a`, err: "cannot receive from non-channel int"},
})
}
@@ -701,6 +708,7 @@ func TestEvalCall(t *testing.T) {
{src: ` test := func(a, b int) int { return a }
blah := func() (int, float64) { return 1, 1.1 }
a := test(blah())`, err: "3:15: cannot use func() (int,float64) as type (int,int)"},
{src: "func f()", err: "missing function body"},
})
}
@@ -1010,7 +1018,7 @@ const goMinorVersionTest = 16
func TestHasIOFS(t *testing.T) {
code := `
// +build go1.16
// +build go1.18
package main
@@ -1048,6 +1056,8 @@ func main() {
var minor int
var err error
version := runtime.Version()
version = strings.Replace(version, "beta", ".", 1)
version = strings.Replace(version, "rc", ".", 1)
fields := strings.Fields(version)
// Go stable
if len(fields) == 1 {
@@ -1085,6 +1095,10 @@ func main() {
}
func TestImportPathIsKey(t *testing.T) {
// FIXME(marc): support of stdlib generic packages like "cmp", "maps", "slices" has changed
// the scope layout by introducing new source packages when stdlib is used.
// The logic of the following test doesn't apply anymore.
t.Skip("This test needs to be reworked.")
// No need to check the results of Eval, as TestFile already does it.
i := interp.New(interp.Options{GoPath: filepath.FromSlash("../_test/testdata/redeclaration-global7")})
if err := i.Use(stdlib.Symbols); err != nil {
@@ -1242,10 +1256,9 @@ func TestConcurrentEvals2(t *testing.T) {
if hello1 {
if l == "hello world2" {
break
} else {
done <- fmt.Errorf("unexpected output: %v", l)
return
}
done <- fmt.Errorf("unexpected output: %v", l)
return
}
if l == "hello world1" {
hello1 = true
@@ -1325,7 +1338,7 @@ func TestConcurrentEvals3(t *testing.T) {
}()
for _, v := range input {
in := strings.NewReader(fmt.Sprintf("println(\"%s\")\n", v))
in := strings.NewReader(fmt.Sprintf("println(%q)\n", v))
if _, err := io.Copy(poutin, in); err != nil {
t.Fatal(err)
}
@@ -1597,10 +1610,8 @@ func TestREPLCommands(t *testing.T) {
if testing.Short() {
return
}
_ = os.Setenv("YAEGI_PROMPT", "1") // To force prompts over non-tty streams
defer func() {
_ = os.Setenv("YAEGI_PROMPT", "0")
}()
t.Setenv("YAEGI_PROMPT", "1") // To force prompts over non-tty streams
allDone := make(chan bool)
runREPL := func() {
done := make(chan error)
@@ -1872,25 +1883,25 @@ func TestIssue1383(t *testing.T) {
}
`
interp := interp.New(interp.Options{})
err := interp.Use(stdlib.Symbols)
i := interp.New(interp.Options{})
err := i.Use(stdlib.Symbols)
if err != nil {
t.Fatal(err)
}
_, err = interp.Eval(`import "fmt"`)
_, err = i.Eval(`import "fmt"`)
if err != nil {
t.Fatal(err)
}
ast, err := parser.ParseFile(interp.FileSet(), "_.go", src, parser.DeclarationErrors)
ast, err := parser.ParseFile(i.FileSet(), "_.go", src, parser.DeclarationErrors)
if err != nil {
t.Fatal(err)
}
prog, err := interp.CompileAST(ast)
prog, err := i.CompileAST(ast)
if err != nil {
t.Fatal(err)
}
_, err = interp.Execute(prog)
_, err = i.Execute(prog)
if err != nil {
t.Fatal(err)
}

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