Compare commits

...

74 Commits

Author SHA1 Message Date
Marc Vertes
f07f25f1ba interp: handle struct with multiple recursive fields (#1372)
* interp: handle struct with multiple recursive fields

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

Fixes #1371.

* Update interp/type.go

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

* fix lint

* fix comment

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

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

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

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

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

Fixes #1361.
2022-04-05 17:34:08 +02:00
Marc Vertes
c784713aca interp: make methods passed as value preserve receiver
Fixes #1332.
2022-04-05 16:58:09 +02:00
Marc Vertes
14acf618af interp: improve type switch on binary interfaces
Fixes #1337.
2022-01-04 10:50:08 +01:00
Marc Vertes
fbee2baf9d interp: fix wrapping of returned closure passed to runtime
Fixes #1333.
2021-12-21 17:44:06 +01:00
Marc Vertes
2819b4167b interp: fix derivation of type of slice expression of binary object
Fixes #1328.
2021-12-20 15:46:05 +01:00
Marc Vertes
2af660cb1f interp: improve method resolution on embedded fields
The capability to dereference pointers has been added to
methodByName(), improving method lookup on binary values.

Wrapping to valueInterface is performed in a missing use case at
return of function calls. It was done in the nested call, but not
at assign.

Fixes #1330.
2021-12-20 15:06:11 +01:00
Marc Vertes
8323068414 interp: fix nested calls with variadic interfaces
Fixes #1326
2021-12-08 17:16:05 +01:00
Marc Vertes
5b62f9fdb6 interp: fix setting of interface value from nested function calls
Fixes #1320 and #1294.
2021-11-30 15:44:05 +01:00
Marc Vertes
4f66e3fe6c interp: fix type switch when the case type is an interpreted interface
Fixes #1315.

With this PR, the package gopkg.in/yaml.v3 works in yaegi (fixes #1296).
2021-11-26 12:24:07 +01:00
Marc Vertes
1335b4c64f stdlib: add wrappers for go/build/constraint package 2021-11-26 12:10:08 +01:00
Marc Vertes
da03c922ca interp: get rid of deprecated stdlib package ioutil
No functional change.
2021-11-26 11:58:07 +01:00
Marc Vertes
9620116c30 interp: replace value.Type().Kind() by value.Kind()
No functional change. Just simpler and faster.
2021-11-26 11:42:07 +01:00
Marc Vertes
dd7197f2a2 interp: fix assign of multiple return function call
A runtime builtin assignFromCall is added to handle multiple values returned at once. It is necessary if some of the values require to be set to interface values in the caller space, which is performed by reflect.Set in assignFromCall.
 
Fixes #1311.
2021-11-19 15:30:05 +01:00
Marc Vertes
9214806342 interp: fix populating array of interfaces
Fixes #1308.
2021-11-09 11:26:11 +01:00
Marc Vertes
348e713a8e interp: avoid collision between type and variable names in assign
Fixes #1306
2021-11-09 11:14:09 +01:00
Marc Vertes
cb81fe41ab interp: fix type processing to support multiple recursive fields
Fixes #1304
2021-11-08 20:46:12 +01:00
Marc Vertes
a876bb3673 interp: virtualize environment in restricted mode
In restricted mode, replace environment related symbols in
stdlib os package by a version which operates on a private copy
per interpreter context.

It allows to have concurrent interpreters in the same process
operating each in their own environment without affecting each
other or the host.

If unrestricted opt is set, this behaviour is disabled, and the
default symbols from stdlib are used.

Note also that no modification is done for syscall package, as it
should be not used in restricted mode.
2021-11-08 09:58:10 +01:00
Marc Vertes
afa46daccd interp: fix assignment to a dereferenced struct pointer
disable the optimization of skipping assign operation in that case,
as this step is necessary in case of writing to a pointer.

Fixes #1300.
2021-11-02 15:56:13 +01:00
Marc Vertes
3c00da291e stdlib: add wrappers for standard lib runtime/metrics package
This package was forgotten. No reason to not include it.
2021-11-02 15:20:06 +01:00
Marc Vertes
c847481184 interp: fix comparison operators in if statement
At generation of operator closures, the comparison expression
was hard-coded instead of being derived from the operator name,
leading to a wrong result.

Fixes #1297.
2021-11-02 15:02:07 +01:00
Marc Vertes
f46ef67180 interp: fix computation of ellipsis array length
Fixes #1287.
2021-10-20 14:36:05 +02:00
Marc Vertes
229ddfdae1 interp: fix use of builtins in type definitions
Make len() and cap() work on pointers. Preserve scope in case of
nested calls of cfg.

Fixes #1285.
2021-10-18 10:50:14 +02:00
Marc Vertes
aa7f0849e3 interp: fix goto to a label with no statement
Fixes #1288.
2021-10-18 10:36:12 +02:00
Bai-Yingjie
7617b8a090 interp: add args option to let each interpreter have seperate args
In some cases, we use many yaegi instances in one process, each interpreter will expect its own os.Args. This is done in interp.go as an option in interp.Options besides Stdin Stdout and Stderr.
2021-10-11 16:58:07 +02:00
Marc Vertes
4e06abe002 interp: fix unsafe2 to work on 32 bits architectures
Fixes #1279.
2021-10-11 11:38:12 +02:00
Bai-Yingjie
b1a758dd5a extract: add an option to be able to set build tag to the created file
Add a tag option in the extract program to optionally give an opportunity to tailor some packages in for example the stdlib, for people who is interested in keeping the final binary size as smaller as possible.
Below go generate
```
//go:generate ../cmd/extract/extract -name stdlib -tag stdmime mime mime/multipart mime/quotedprintable
```
produces a header to stdlib/mime-multipart.go
```
// Code generated by 'yaegi extract mime/multipart'. DO NOT EDIT.

// +build go1.16,!go1.17,stdmime
```
2021-10-11 10:50:11 +02:00
Marc Vertes
5bf4daef2d interp: fix type check of methods with a receiver of interface kind
Fixes #1280.
2021-10-08 17:44:09 +02:00
Marc Vertes
e56db3b82e interp: improve interface wrappers when used by reflect
When an interpreter type implementing an interface is
used by the runtime, the runtime can extract its type
and create new values using reflect, and call methods
on it. The problem is that there will be no interpreted
method counterpart in this case, which makes wrapper panic.

Allow the String() method wrapper to always succeed and
return an empty string if no interpreted method is present.

This allows scripts to define custom flag.Value types on
which the runtime internally instantiates values using
reflect (see isZeroValue in Go src/flag/flag.go).

This workaround could be generalized to all wrappers if
necessary. At this moment, it is convenient to keep the
default behavior of expecting instantiated interpreter
methods, in order to catch interpreter bugs related to
interfaces.

Fixes #1276.
2021-10-08 15:56:06 +02:00
Marc Vertes
d3bbe01d5c interp: improve support of composed interfaces
Fixes #1260.
2021-10-07 16:28:05 +02:00
Nicholas Wiersma
286d6c6359 interp: handle recursive and incomplete seen types in nodeType
This comes from experiments looking into #1259 where incomplete twice seen types are marked as complete. To mitigate the problem instead of a map of seen types in `nodeType` a slice is used as a cheap way to keep track of our current path through the node tree.
2021-09-27 10:20:13 +02:00
Nicholas Wiersma
84424b52bc interp: handle alias of an alias
When dealing with an alias of an alias, the actual underlying type of the source alias should be used.
2021-09-27 10:08:11 +02:00
Marc Vertes
98c2dcd3e5 interp: fix interface wrapper for struct types
This change fixes a regression introduced by PR #1192 in a program using
https://github.com/NYTimes/gziphandler which defines several types
implementing stdlib interfaces. We do not implement a wrapper
if we see that a type already implements an interface, except that
it can be falsly reported by reflect in case of a struct with
embedded interface field. We need to force the wrapper generation
in this case.
The problem occurs only for wrappers on struct, not on pointers or
other indirection types.
2021-09-24 13:20:12 +02:00
Ethan Reesor
808f0bde9d interp: add a function to directly compile Go AST
Adds CompileAST, which can be used to compile Go AST directly. This
allows users to delegate parsing of source to their own code instead of
relying on the interpreter.

CLoses #1251
2021-09-23 12:34:12 +02:00
Nicholas Wiersma
c5c6012947 interp: support type spec assign
This supports type spec assign types which disallows the attachment of methods onto the alias type as per the Go spec.

Fixes #1154
2021-09-23 12:16:11 +02:00
Nicholas Wiersma
836060c8ad interp: allow GTA revisit for unresolved define statements
When a define statement relies on a selector or type that may exist in another file it should revisit once GTA is complete. This allows that revisit.

**Note:** In order to keep the original GTA error for the define statement, so the error received is correct and meaningful, I have added a node `meta` property. I decided to make it generic as it may be useful in future. There may be a better way to stash errors across the GTA runs and am open to suggestion here.

Fixes #1253
2021-09-20 12:04:06 +02:00
Nicholas Wiersma
7a54353c7b chore: update linter to v1.42.1
* chore: update linter to v1.42.1

* fix: linter issues
2021-09-20 10:18:14 +02:00
Marc Vertes
b591ba0e78 interp: do not export RealFS, used internally only 2021-09-15 16:22:07 +02:00
Nicholas Wiersma
5af51aefe6 interp: fix type selector precedence
The current `nodeType` selector precedence is heavy handed in favour of package type. It seems to often create `typeSym` symbols as variable types in the scope will never be found. To fix this if the ancestor node is a field expression, the package type is searched for. After this, if the type is still `nil` the normal scope is searched using `nodeType2`.

Fixes #1158
2021-09-15 10:42:08 +02:00
mpl
e7c0f68bab interp: fix Clone documentation 2021-09-14 12:30:13 +02:00
Marc Vertes
bd9a6a4f8a interp: improve processing of recursive types
Make sure to keep always a single copy of incomplete type structures.
Remove remnants of recursive types processing.

Now `import "go.uber.org/zap"` works again (see #1172), fixing regressions
introduced since #1236.
2021-09-13 18:24:10 +02:00
Nicholas Wiersma
3eb2c79fd8 interp: fix nil funcs in composite literals
When a nil are used in a func composite literal, the nil type is a `func` not a `*node`. This handles this case.

Fixes #1249
2021-09-13 16:32:12 +02:00
Nicholas Wiersma
4653d87298 interp: types should not recover data for aliases
When `nodeType` recovers names and methods, it can overwrite the data if the type is an aliasT. When aliasing a type, do not recover the methods, this will be done in the GTA typeSpec pass.

Related to #1158
2021-09-06 18:24:04 +02:00
Nicholas Wiersma
45d569c215 interp: fix parsing of late binding consts
When a const is late binding and specified with a type, the GTA defineStmt was creating the symbol with the current scopes `iota` which is incorrect. The symbol should be created with the source nodes `rval`.

Related to #1158
2021-09-06 17:30:12 +02:00
Nicholas Wiersma
c33caeb573 interp: improve handling of wrapped interface values
This test (assert2.go) display 2 separate issues:
1. assert2.go L28: Type assert tries to set an `interface{}` to a `valueInterface`. The typing here is complex, we have a valueT(strings.Builder) wrapped in a ptrT wrapped in a src iface wrapped in a valueT(interface{}). Type assert fails to realise that the `valueT` `interface{}` is wrapping the `valueInterface`.
2. assert2.go L29: `genValueBinMethodOnInterface` does not try and get the bin method, as the `typ.node` (`ptrT` or a `valueT`(`string.Builder`)) is set. In this case the src iface is called with a receiver argument. To fix this the method is looked for first if possible, and only if not found does it fall back to the `defaultGen`.

Fixes #1227
2021-09-06 17:16:11 +02:00
Ethan Reesor
91a55cc4c5 interp: add debug interface
Adds an interface to `interp` that can be used to build an interactive debugger, such as those used by IDEs.

Closes #1188

All basic debugger features work, with one exception: breakpoints in some locations don't work, due to `setExec` creating a temporary `bltn` (https://github.com/traefik/yaegi/issues/1188#issuecomment-886107905).

Example, using a Debug Adapter implementation with VSCode:

![image](https://user-images.githubusercontent.com/879055/128620736-94b6efde-2e5f-43ad-82c9-d919c3fe401f.png)
2021-09-06 15:32:10 +02:00
Nicholas Wiersma
05f08d776a interp: complete type constructors
This completes the types constructors, cleaning up `nodeType`.
2021-09-01 14:46:08 +02:00
Nicholas Wiersma
d2569a85a6 interp: fix default types for runes
When using an untyped rune in an interface, the default type was blindly untyping it. This fixes this issue.

Fixes #1238
2021-08-31 12:32:10 +02:00
Nicholas Wiersma
772cd68fea interp: make use of type constructors
It was initially assumed that `nodeType` needed to rebuild the type from scratch, but this is not the case anymore. The existing type constructors are now used in `nodeType` to make it more readable. In doing this some bugs in type strings were found and fixed, along with adding the real package name to the type.

As `ptrOf` is now the only place that pointer types are constructed, it is feasible to cache the pointer type on the value type. To ensures that we have a consistent pointer type for any value type.
2021-08-31 10:34:12 +02:00
Nicholas Wiersma
4af992bccb interp: create real recursive types with unsafe type swapping
As the unsafe and pointer methods in `reflect` are to be depreciated, and seeing no replacement functions, it is now forced that some unsafe is needed to replace this as when and interface is dereferenced it is made unsettable by reflect.

With this in mind, this adds real recursive types by hot swapping the struct field type on the fly. This removes a lot of compensation code, simplifying all previous cases.

**Note:** While the struct field type is swapped for the real type, the type string is not changed. Due to this, unsafe will recreate the same type.
2021-08-30 18:38:12 +02:00
Nicholas Wiersma
da922ce90b interp: build type strings on the fly
This adds `itype.str` which is a string representation of the type built when the type is built. The goal is to make type comparison simpler and centralise the creation of types just to constructors and `nodeType`. `nodeType` continues to build types in parts so to reuse underlying types better.
2021-08-27 19:44:05 +02:00
Aloïs Micard
7b77b0fa22 test: only enable race detector when supported
Closes: #1228
2021-08-27 15:50:13 +02:00
Marc Vertes
b7f9a39eff interp: fix support of 32 bits and big-endian arch
Force recompute of bits.UintSize, make related tests portable.

Fixes #1230.
2021-08-27 14:54:05 +02:00
Marc Vertes
d2b25a7426 interp: fix append with 1 argument
Fixes #1224.
2021-08-24 12:16:08 +02:00
Johnny
b5bf4ef31a interp: allow for reading source files from diverse filesystems
Make use of fs.FS (new to go 1.16) to allow for reading source files from diverse filesystems (local, embed, custom).

* `Options` has a new field `SourcecodeFilesystem fs.FS` so users can supply their own read-only filesystem containing source code.
* Defaults to the local filesystems (via `RealFS` - a thin `os.Open` wrapper complying with `fs.FS`) so regular users should see no change in behaviour.
* When no filesystem is available (e.g. WASM, or if you want to embed files to retain single binary distribution) an alternative filesystem is preferable to using `Eval(string)` as that requires the stringy code to be a single file monolith instead of multiple files. By using an `fs.FS` we can use `EvalPath()` and gain the ability to handle multiple files and packages.
* You can make use of embed filesystems (https://pkg.go.dev/embed) and custom filesystems obeying the `fs.FS` interface (I use one for http served zip files when targeting wasm as there is no local filesystem on wasm). Tests can make use of `fstest.Map`.
* NOTE: This does NOT affect what the running yaegi code considers its local filesystem, this is only for the interpreter finding the source code.

See `example/fs/fs_test.go` for an example.

Fixes #1200.
2021-08-19 11:28:13 +02:00
Nicholas Wiersma
a69b9bc2dc stdlib: support go1.17 unsafe functions
This adds support for go1.17 `unsafe.Add` and `unsafe.Slice`.
2021-08-19 10:38:05 +02:00
Marc Vertes
b84278dcc6 stdlib: remove wrapper of runtime/cgo
Temporarily removing this wrapper which causes a failure
on freebsd system at build.

Fixes #1221.
2021-08-17 16:38:12 +02:00
Nicholas Wiersma
32cbcfb412 feat: update stdlib mapping for go1.17
* Drop go1.15
* Generate go1.17
* Update minimum extract version
* Update the CI config
2021-08-17 11:42:07 +02:00
Nicholas Wiersma
5c73f30f36 interp: fix handling of forward function declaration
Set node address in `val` field at creation of `funcDecl` node so it can be used correctly at closure generation, even in the case of forward function declarations, where the value was zero.

Fixes #1214
2021-08-16 16:24:05 +02:00
Nicholas Wiersma
5cc6fa42e4 feat: fix zero instance issue
When calling a function, if the input param is a "zero" instance, it is not set on the input. This is an issue where the param is an `interface{}` as a `nil` value is set instead of the zero value.

The actual solution for this is to remove the `if !val.IsZero()`, this however runs into an issue on `_test/struct48.go` where we have a zero recursive struct instance (`*interface{}`) and we have no way to get its real type. Should a way be figured out to keep tabs on the original type, the `if` can go away, and in the zero case of `genValueRecursiveInterfacePtrValue`, the actual zero type can be returned. 

Fixes #1215
2021-08-16 10:34:09 +02:00
Marc Vertes
37fe3422d8 interp: improve handling of interface struct field
Wrap non empty interface value  in struct field, to allow method
lookup.

Fixes #1208.
2021-08-06 12:16:10 +02:00
Dan Kortschak
d4e25f0259 interp: fix package lookup for type analysis of local types
Fixes #1203.
2021-08-02 12:14:11 +02:00
Marc Vertes
32ff3fb9b0 interp: fix handling of interface values in variadic calls
Fixes #1205.
2021-07-29 14:36:10 +02:00
Marc Vertes
b41fa6eb9d stdlib: add missing wrappers of io/fs for go1.16
Fixes #1195
2021-07-27 10:58:06 +02:00
Marc Vertes
c80c605ab9 interp: support wrapping of nested interfaces
Add getConcreteType to retrieve the concrete type of a nested interface
value implementing a specific interface for which a wrapper exists.

If method resolution fails at runtime, a panic is now issued instead
of an error message and continue.

Fixes #1187.
2021-07-26 19:12:11 +02:00
Marc Vertes
bf843fc09e interp: fix handling returned func values
Fixes #1202.
2021-07-26 17:42:05 +02:00
Marc Vertes
a913a4ea8b interp: fix handling map of interfaces
Map handling builtins getIndexMap and rangeMap had some leftover
code of previous way of emulating interfaces, which was modified
following changes in #1017.

Specific code for interfaceT is removed, as not necessary anymore.
Map builtins are now simplified and more robust.

Fixes #1189.
2021-07-26 17:30:12 +02:00
Marc Vertes
2f8493c405 interp: fix map update by assign operators
When operating on map elements, result of assign operators were not
written to the map entry. Now check if the destination of an assign
operator is in a map, and if so, set the result to it.

Fixes #1194.
2021-07-21 11:44:04 +02:00
Marc Vertes
c7fcfa8534 interp: fix interface wrapper generation
Add early detection of cases where no wrapper is necessary because
the value type already implements the target interface.

It should both increase performances by avoiding the wrapper overhead,
and fix errors due to replacing valid values by incomplete wrappers,
caused by the presence of private methods in the interface definition,
as in #1191.

Fixes #1191.
2021-07-19 15:38:11 +02:00
Ludovic Fernandez
aa012b992e chore: update linter
- update golangci-lint to v1.41.1
- fix GitHub action cache configuration
2021-07-17 12:14:06 +02:00
619 changed files with 17797 additions and 3644 deletions

View File

@@ -17,7 +17,7 @@ jobs:
strategy:
matrix:
go-version: [ 1.15, 1.16 ]
go-version: [ 1.16, 1.17 ]
os: [ubuntu-latest, macos-latest, windows-latest]
include:
@@ -45,11 +45,16 @@ jobs:
- name: Cache Go modules
uses: actions/cache@v2
with:
# In order:
# * Module download cache
# * Build cache (Linux)
# * Build cache (Mac)
# * Build cache (Windows)
path: |
~/go/pkg/mod # Module download cache
~/.cache/go-build # Build cache (Linux)
~/Library/Caches/go-build # Build cache (Mac)
'%LocalAppData%\go-build' # Build cache (Windows)
~/go/pkg/mod
~/.cache/go-build
~/Library/Caches/go-build
%LocalAppData%\go-build
key: ${{ runner.os }}-${{ matrix.go-version }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-${{ matrix.go-version }}-go-

View File

@@ -7,8 +7,8 @@ on:
pull_request:
env:
GO_VERSION: 1.16
GOLANGCI_LINT_VERSION: v1.36.0
GO_VERSION: 1.17
GOLANGCI_LINT_VERSION: v1.42.1
jobs:
@@ -45,7 +45,7 @@ jobs:
needs: linting
strategy:
matrix:
go-version: [ 1.15, 1.16 ]
go-version: [ 1.16, 1.17 ]
steps:
- name: Set up Go ${{ matrix.go-version }}
uses: actions/setup-go@v2
@@ -75,7 +75,7 @@ jobs:
working-directory: ${{ github.workspace }}/go/src/github.com/traefik/yaegi
strategy:
matrix:
go-version: [ 1.15, 1.16 ]
go-version: [ 1.16, 1.17 ]
steps:
- name: Set up Go ${{ matrix.go-version }}

View File

@@ -6,7 +6,7 @@ on:
- v[0-9]+.[0-9]+*
env:
GO_VERSION: 1.16
GO_VERSION: 1.17
jobs:

View File

@@ -23,13 +23,16 @@
[linters]
enable-all = true
disable = [
"maligned",
"golint", # deprecated
"scopelint", # deprecated
"interfacer", # deprecated
"maligned", # deprecated
"lll",
"gas",
"dupl",
"prealloc",
"scopelint",
"gocyclo",
"cyclop",
"gochecknoinits",
"gochecknoglobals",
"wsl",
@@ -49,6 +52,7 @@
"exhaustivestruct",
"forbidigo",
"ifshort",
"forcetypeassert",
"errorlint", # TODO: must be reactivate before fixes
]
@@ -59,8 +63,11 @@
exclude = []
[[issues.exclude-rules]]
path = "interp/.+_test\\.go"
path = ".+_test\\.go"
linters = ["goconst"]
[[issues.exclude-rules]]
path = ".+_test\\.go"
text = "var-declaration:"
[[issues.exclude-rules]]
path = "interp/interp.go"
@@ -68,6 +75,9 @@
[[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"]

View File

@@ -18,7 +18,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.15 and Go 1.16 (the latest 2 major releases)
* Support Go 1.16 and Go 1.17 (the latest 2 major releases)
## Install

View File

@@ -6,16 +6,18 @@ const (
zero = iota
one
two
three
)
func main() {
a := [...]string{
zero: "zero",
one: "one",
two: "two",
zero: "zero",
one: "one",
three: "three",
three + 2: "five",
}
fmt.Printf("%v %T\n", a, a)
}
// Output:
// [zero one two] [3]string
// [zero one three five] [6]string

18
_test/alias2.go Normal file
View File

@@ -0,0 +1,18 @@
package main
import "fmt"
func (t MyT) Test() string {
return "hello"
}
type MyT int
func main() {
t := MyT(1)
fmt.Println(t.Test())
}
// Output:
// hello

22
_test/alias3.go Normal file
View File

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

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

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

27
_test/alias4.go Normal file
View File

@@ -0,0 +1,27 @@
package main
import (
"fmt"
"net/http"
)
type A http.Header
func (a A) Test1() {
fmt.Println("test1")
}
type B A
func (b B) Test2() {
fmt.Println("test2")
}
func main() {
b := B{}
b.Test2()
}
// Output:
// test2

37
_test/assert2.go Normal file
View File

@@ -0,0 +1,37 @@
package main
import (
"strings"
"sync"
)
// Defined an interface of stringBuilder that 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)
Reset()
Grow(n int)
String() string
}
var builderPool = sync.Pool{New: func() interface{} {
return newStringBuilder()
}}
func newStringBuilder() stringBuilder {
return &strings.Builder{}
}
func main() {
i := builderPool.Get()
sb := i.(stringBuilder)
_, _ = sb.WriteString("hello")
println(sb.String())
builderPool.Put(i)
}
// Output:
// hello

View File

@@ -8,4 +8,4 @@ func main() {
}
// Error:
// _test/assign15.go:5:26: cannot use type chan<- struct{} as type <-chan struct{} in assignment
// _test/assign15.go:5:26: cannot use type chan<- struct {} as type <-chan struct {} in assignment

View File

@@ -1,13 +0,0 @@
// A test program
// +build darwin,linux !arm
// +build go1.12 !go1.13
package main
func main() {
println("hello world")
}
// Output:
// hello world

View File

@@ -2,7 +2,7 @@ package main
import (
"fmt"
"io/ioutil"
"io"
"log"
"net"
"net/http"
@@ -13,7 +13,7 @@ func client(uri string) {
if err != nil {
log.Fatal(err)
}
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}

View File

@@ -2,7 +2,7 @@ package main
import (
"fmt"
"io/ioutil"
"io"
"log"
"net"
"net/http"
@@ -21,7 +21,7 @@ func client(uri string) {
if err != nil {
log.Fatal(err)
}
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}

View File

@@ -2,7 +2,7 @@ package main
import (
"fmt"
"io/ioutil"
"io"
"log"
"net/http"
"net/http/httptest"
@@ -13,7 +13,7 @@ func client(uri string) {
if err != nil {
log.Fatal(err)
}
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}

View File

@@ -2,7 +2,7 @@ package main
import (
"fmt"
"io/ioutil"
"io"
"log"
"net/http"
"net/http/httptest"
@@ -40,7 +40,7 @@ func client(uri string) {
if err != nil {
log.Fatal(err)
}
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}

View File

@@ -2,7 +2,7 @@ package main
import (
"fmt"
"io/ioutil"
"io"
"log"
"net/http"
"net/http/httptest"
@@ -40,7 +40,7 @@ func client(uri string) {
if err != nil {
log.Fatal(err)
}
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}

View File

@@ -2,7 +2,7 @@ package main
import (
"fmt"
"io/ioutil"
"io"
"log"
"net/http"
"net/http/httptest"
@@ -41,7 +41,7 @@ func client(uri string) {
if err != nil {
log.Fatal(err)
}
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}

32
_test/composite18.go Normal file
View File

@@ -0,0 +1,32 @@
package main
import "fmt"
type fn func(string, string) bool
var funcs = []fn{
cmpLessFn,
cmpGreaterFn,
nil,
}
func cmpLessFn(a string, b string) bool {
return a < b
}
func cmpGreaterFn(a string, b string) bool {
return a > b
}
func main() {
for _, f := range funcs {
if f == nil {
continue
}
fmt.Println(f("a", "b"))
}
}
// Output:
// true
// false

33
_test/composite19.go Normal file
View File

@@ -0,0 +1,33 @@
package main
import "fmt"
type fn func(string, string) bool
var funcs = map[string]fn{
"less": cmpLessFn,
"greater": cmpGreaterFn,
"none": nil,
}
func cmpLessFn(a string, b string) bool {
return a < b
}
func cmpGreaterFn(a string, b string) bool {
return a > b
}
func main() {
for _, n := range []string{"less", "greater", "none"} {
f := funcs[n]
if f == nil {
continue
}
fmt.Println(f("a", "b"))
}
}
// Output:
// true
// false

View File

@@ -9,7 +9,7 @@ func main() {
const huge = 1 << 100
const large = huge >> 38
fmt.Println(large)
fmt.Println(int64(large))
}
// Output:

View File

@@ -2,7 +2,7 @@ package main
import "fmt"
const maxLen = int64(int(^uint(0) >> 1))
const maxLen = int64(int64(^uint64(0) >> 1))
func main() {
fmt.Println(maxLen)

28
_test/const26.go Normal file
View File

@@ -0,0 +1,28 @@
package main
import (
"fmt"
)
func init() {
fmt.Println(constString)
fmt.Println(const2)
fmt.Println(varString)
}
const constString string = "hello"
const (
const1 = iota + 10
const2
const3
)
var varString string = "test"
func main() {}
// Output:
// hello
// 11
// test

View File

@@ -2,12 +2,11 @@ package main
import (
"fmt"
"io/ioutil"
"os"
)
func main() {
file, err := ioutil.TempFile("", "yeagibench")
file, err := os.CreateTemp("", "yeagibench")
if err != nil {
panic(err)
}
@@ -23,7 +22,7 @@ func main() {
panic(err)
}
b, err := ioutil.ReadFile(file.Name())
b, err := os.ReadFile(file.Name())
if err != nil {
panic(err)
}

25
_test/fun27.go Normal file
View File

@@ -0,0 +1,25 @@
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
print("test")
}()
wg.Wait()
}
func print(state string) {
fmt.Println(state)
}
// Output:
// test

17
_test/interface52.go Normal file
View File

@@ -0,0 +1,17 @@
package main
import "testing"
func main() {
t := testing.T{}
var tb testing.TB
tb = &t
if tb.TempDir() == "" {
println("FAIL")
return
}
println("PASS")
}
// Output:
// PASS

View File

@@ -2,7 +2,7 @@ package main
import (
"fmt"
"io/ioutil"
"io"
"log"
"strings"
)
@@ -10,7 +10,7 @@ import (
func main() {
r := strings.NewReader("Go is a general-purpose language designed with systems programming in mind.")
b, err := ioutil.ReadAll(r)
b, err := io.ReadAll(r)
if err != nil {
log.Fatal(err)
}

View File

@@ -10,4 +10,4 @@ func main() {
}
// Error:
// 9:6: cannot use type string as type int in assignment
// 9:6: cannot use type untyped string as type int in assignment

View File

@@ -7,4 +7,5 @@ func main() {
println(len(c))
}
// Output: 1
// Output:
// 1

46
_test/issue-1187.go Normal file
View File

@@ -0,0 +1,46 @@
package main
import (
"io"
"os"
)
type sink interface {
io.Writer
io.Closer
}
func newSink() sink {
// return os.Stdout // Stdout is special in yaegi tests
file, err := os.CreateTemp("", "yaegi-test.*")
if err != nil {
panic(err)
}
return file
}
func main() {
s := newSink()
n, err := s.Write([]byte("Hello\n"))
if err != nil {
panic(err)
}
var writer io.Writer = s
m, err := writer.Write([]byte("Hello\n"))
if err != nil {
panic(err)
}
var closer io.Closer = s
err = closer.Close()
if err != nil {
panic(err)
}
err = os.Remove(s.(*os.File).Name())
if err != nil {
panic(err)
}
println(m, n)
}
// Output:
// 6 6

31
_test/issue-1189.go Normal file
View File

@@ -0,0 +1,31 @@
package main
type I interface {
Foo() int
}
type S1 struct {
i int
}
func (s S1) Foo() int { return s.i }
type S2 struct{}
func (s *S2) Foo() int { return 42 }
func main() {
Is := map[string]I{
"foo": S1{21},
"bar": &S2{},
}
n := 0
for _, s := range Is {
n += s.Foo()
}
bar := "bar"
println(n, Is["foo"].Foo(), Is[bar].Foo())
}
// Output:
// 63 21 42

34
_test/issue-1202.go Normal file
View File

@@ -0,0 +1,34 @@
package main
import "fmt"
type foobar struct {
callback func(string) func()
}
func cb(text string) func() {
return func() {
fmt.Println(text)
}
}
func main() {
// These ways of invoking it all work...
cb("Hi from inline callback!")()
asVarTest1 := cb("Hi from asVarTest1 callback!")
asVarTest1()
asVarTest2 := cb
asVarTest2("Hi from asVarTest2 callback!")()
// But inside a struct panics in yaegi...
asStructField := &foobar{callback: cb}
asStructField.callback("Hi from struct field callback!")() // <--- panics here
}
// Output:
// Hi from inline callback!
// Hi from asVarTest1 callback!
// Hi from asVarTest2 callback!
// Hi from struct field callback!

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

@@ -0,0 +1,25 @@
package main
type Option interface {
apply()
}
func f(opts ...Option) {
for _, opt := range opts {
opt.apply()
}
}
type T struct{}
func (t *T) apply() { println("in apply") }
func main() {
opt := []Option{&T{}}
f(opt[0]) // works
f(opt...) // fails
}
// Output:
// in apply
// in apply

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

@@ -0,0 +1,23 @@
package main
type Enabler interface {
Enabled() bool
}
type Logger struct {
core Enabler
}
func (log *Logger) GetCore() Enabler { return log.core }
type T struct{}
func (t *T) Enabled() bool { return true }
func main() {
base := &Logger{&T{}}
println(base.GetCore().Enabled())
}
// Output:
// true

67
_test/issue-1260.go Normal file
View File

@@ -0,0 +1,67 @@
package main
import (
"fmt"
"io"
"os"
)
type WriteSyncer interface {
io.Writer
Sync() error
}
type Sink interface {
WriteSyncer
io.Closer
}
func newFileSink(path string) (Sink, error) {
return os.OpenFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
}
type Sink1 struct{ name string }
func (s Sink1) Write(b []byte) (int, error) { println("in Write"); return 0, nil }
func (s Sink1) Sync() error { println("in Sync"); return nil }
func (s Sink1) Close() error { println("in Close", s.name); return nil }
func newS1(name string) Sink { return Sink1{name} }
func newS1p(name string) Sink { return &Sink1{name} }
type Sink2 struct{ name string }
func (s *Sink2) Write(b []byte) (int, error) { println("in Write"); return 0, nil }
func (s *Sink2) Sync() error { println("in Sync"); return nil }
func (s *Sink2) Close() error { println("in Close", s.name); return nil }
func newS2(name string) Sink { return Sink1{name} }
func main() {
tmpfile, err := os.CreateTemp("", "xxx")
if err != nil {
panic(err)
}
defer os.Remove(tmpfile.Name())
closers := []io.Closer{}
sink, err := newFileSink(tmpfile.Name())
if err != nil {
panic(err)
}
closers = append(closers, sink)
s1p := newS1p("ptr")
s1 := newS1("struct")
s2 := newS2("ptr2")
closers = append(closers, s1p, s1, s2)
for _, closer := range closers {
fmt.Println(closer.Close())
}
}
// Output:
// <nil>
// in Close ptr
// <nil>
// in Close struct
// <nil>
// in Close ptr2
// <nil>

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

@@ -0,0 +1,24 @@
package main
import (
"flag"
)
type customFlag struct{}
func (cf customFlag) String() string {
return "custom flag"
}
func (cf customFlag) Set(string) error {
return nil
}
func main() {
flag.Var(customFlag{}, "cf", "custom flag")
flag.Parse()
println("Hello, playground")
}
// Output:
// Hello, playground

30
_test/issue-1280.go Normal file
View File

@@ -0,0 +1,30 @@
package main
import (
"io"
"log"
"os"
)
type DBReader interface {
io.ReadCloser
io.ReaderAt
}
type DB struct {
f DBReader
}
func main() {
f, err := os.Open("/dev/null")
if err != nil {
log.Fatal(err)
}
d := &DB{f}
data := make([]byte, 1)
_, _ = d.f.ReadAt(data, 0)
println("bye")
}
// Output:
// bye

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

@@ -0,0 +1,25 @@
package main
type (
T1 struct{ Path [12]int8 }
T2 struct{ Path *[12]int8 }
)
var (
t11 = &T1{}
t21 = &T2{}
)
func main() {
b := [12]byte{}
t12 := &T1{}
t22 := &T2{}
b11 := (*[len(t11.Path)]byte)(&b)
b12 := (*[len(t12.Path)]byte)(&b)
b21 := (*[len(t21.Path)]byte)(&b)
b22 := (*[len(t22.Path)]byte)(&b)
println(len(b11), len(b12), len(b21), len(b22))
}
// Output:
// 12 12 12 12

10
_test/issue-1288.go Normal file
View File

@@ -0,0 +1,10 @@
package main
func main() {
println("Hi")
goto done
done:
}
// Output:
// Hi

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

@@ -0,0 +1,20 @@
package main
const buflen = 512
type T struct {
buf []byte
}
func f(t *T) { *t = T{buf: make([]byte, 0, buflen)} }
func main() {
s := T{}
println(cap(s.buf))
f(&s)
println(cap(s.buf))
}
// Output:
// 0
// 512

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

@@ -0,0 +1,16 @@
package main
type Node struct {
Name string
Alias *Node
Child []*Node
}
func main() {
n := &Node{Name: "parent"}
n.Child = append(n.Child, &Node{Name: "child"})
println(n.Name, n.Child[0].Name)
}
// Output:
// parent child

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

@@ -0,0 +1,15 @@
package main
import "fmt"
func check() (result bool, err error) {
return true, nil
}
func main() {
result, error := check()
fmt.Println(result, error)
}
// Output:
// true <nil>

27
_test/issue-1308.go Normal file
View File

@@ -0,0 +1,27 @@
package main
import "fmt"
type test struct {
v interface{}
s string
}
type T struct {
name string
}
func main() {
t := []test{
{
v: []interface{}{
T{"hello"},
},
s: "world",
},
}
fmt.Println(t)
}
// Output:
// [{[{hello}] world}]

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

@@ -0,0 +1,19 @@
package main
type T struct {
v interface{}
}
func f() (ret int64, err error) {
ret += 2
return
}
func main() {
t := &T{}
t.v, _ = f()
println(t.v.(int64))
}
// Output:
// 2

32
_test/issue-1315.go Normal file
View File

@@ -0,0 +1,32 @@
package main
type Intf interface {
M()
}
type T struct {
s string
}
func (t *T) M() { println("in M") }
func f(i interface{}) {
switch j := i.(type) {
case Intf:
j.M()
default:
println("default")
}
}
func main() {
var i Intf
var k interface{} = 1
i = &T{"hello"}
f(i)
f(k)
}
// Output:
// in M
// default

29
_test/issue-1320.go Normal file
View File

@@ -0,0 +1,29 @@
package main
type Pooler interface {
Get() string
}
type baseClient struct {
connPool Pooler
}
type connPool struct {
name string
}
func (c *connPool) Get() string { return c.name }
func newBaseClient(i int, p Pooler) *baseClient {
return &baseClient{connPool: p}
}
func newConnPool() *connPool { return &connPool{name: "connPool"} }
func main() {
b := newBaseClient(0, newConnPool())
println(b.connPool.(*connPool).name)
}
// Output:
// connPool

42
_test/issue-1326.go Normal file
View File

@@ -0,0 +1,42 @@
package main
type Option interface {
apply(*T)
}
type T struct {
s string
}
type opt struct {
name string
}
func (o *opt) apply(t *T) {
println(o.name)
}
func BuildOptions() []Option {
return []Option{
&opt{"opt1"},
&opt{"opt2"},
}
}
func NewT(name string, options ...Option) *T {
t := &T{name}
for _, opt := range options {
opt.apply(t)
}
return t
}
func main() {
t := NewT("hello", BuildOptions()...)
println(t.s)
}
// Output:
// opt1
// opt2
// hello

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

@@ -0,0 +1,16 @@
package main
import (
"crypto/sha1"
"encoding/hex"
)
func main() {
script := "hello"
sumRaw := sha1.Sum([]byte(script))
sum := hex.EncodeToString(sumRaw[:])
println(sum)
}
// Output:
// aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d

42
_test/issue-1330.go Normal file
View File

@@ -0,0 +1,42 @@
package main
import (
"fmt"
"io"
"net"
)
type wrappedConn struct {
net.Conn
}
func main() {
_, err := net.Listen("tcp", "127.0.0.1:49153")
if err != nil {
panic(err)
}
dialer := &net.Dialer{
LocalAddr: &net.TCPAddr{
IP: net.ParseIP("127.0.0.1"),
Port: 0,
},
}
conn, err := dialer.Dial("tcp", "127.0.0.1:49153")
if err != nil {
panic(err)
}
defer conn.Close()
t := &wrappedConn{conn}
var w io.Writer = t
if n, err := w.Write([]byte("hello")); err != nil {
fmt.Println(err)
} else {
fmt.Println(n)
}
}
// Output:
// 5

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

@@ -0,0 +1,17 @@
package main
func run(fn func(name string)) { fn("test") }
type T2 struct {
name string
}
func (t *T2) f(s string) { println(s, t.name) }
func main() {
t2 := &T2{"foo"}
run(t2.f)
}
// Output:
// test foo

37
_test/issue-1333.go Normal file
View File

@@ -0,0 +1,37 @@
package main
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
)
func mock(name string) http.HandlerFunc {
return func(rw http.ResponseWriter, req *http.Request) {
fmt.Fprint(rw, "Hello ", name)
}
}
func client(uri string) {
resp, err := http.Get(uri)
if err != nil {
panic(err)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
panic(err)
}
fmt.Println(string(body))
}
func main() {
mux := http.NewServeMux()
server := httptest.NewServer(mux)
defer server.Close()
mux.Handle("/", mock("foo"))
client(server.URL)
}
// Output:
// Hello foo

26
_test/issue-1337.go Normal file
View File

@@ -0,0 +1,26 @@
package main
import (
"io"
"os"
)
func f(i interface{}) {
switch at := i.(type) {
case int, int8:
println("integer", at)
case io.Reader:
println("reader")
}
println("bye")
}
func main() {
var fd *os.File
var r io.Reader = fd
f(r)
}
// Output:
// reader
// bye

27
_test/issue-1361.go Normal file
View File

@@ -0,0 +1,27 @@
package main
import (
"fmt"
"math"
)
type obj struct {
num float64
}
type Fun func(o *obj) (r *obj, err error)
func numFun(fn func(f float64) float64) Fun {
return func(o *obj) (*obj, error) {
return &obj{fn(o.num)}, nil
}
}
func main() {
f := numFun(math.Cos)
r, err := f(&obj{})
fmt.Println(r, err)
}
// Output:
// &{1} <nil>

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

@@ -0,0 +1,16 @@
package main
import (
"fmt"
"strconv"
)
func main() {
var value interface{}
var err error
value, err = strconv.ParseFloat("123", 64)
fmt.Println(value, err)
}
// Output:
// 123 <nil>

18
_test/issue-1365.go Normal file
View File

@@ -0,0 +1,18 @@
package main
func genInt() (int, error) { return 3, nil }
func getInt() (value int) {
value, err := genInt()
if err != nil {
panic(err)
}
return
}
func main() {
println(getInt())
}
// Output:
// 3

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

@@ -0,0 +1,16 @@
package main
const dollar byte = 36
func main() {
var c byte = 36
switch true {
case c == dollar:
println("ok")
default:
println("not ok")
}
}
// Output:
// ok

18
_test/issue-1371.go Normal file
View File

@@ -0,0 +1,18 @@
package main
import "fmt"
type node struct {
parent *node
child []*node
key string
}
func main() {
root := &node{key: "root"}
root.child = nil
fmt.Println("root:", root)
}
// Output:
// root: &{<nil> [] root}

38
_test/issue-1375.go Normal file
View File

@@ -0,0 +1,38 @@
package main
import "fmt"
type Option func(*Struct)
func WithOption(opt string) Option {
return func(s *Struct) {
s.opt = opt
}
}
type Struct struct {
opt string
}
func New(opts ...Option) *Struct {
s := new(Struct)
for _, opt := range opts {
opt(s)
}
return s
}
func (s *Struct) ShowOption() {
fmt.Println(s.opt)
}
func main() {
opts := []Option{
WithOption("test"),
}
s := New(opts...)
s.ShowOption()
}
// Output:
// test

View File

@@ -3,7 +3,6 @@ package main
import (
"fmt"
"io"
"io/ioutil"
"log"
"strings"
)
@@ -36,7 +35,7 @@ type pipe struct {
func newReadAutoCloser(r io.Reader) readAutoCloser {
if _, ok := r.(io.Closer); !ok {
return readAutoCloser{ioutil.NopCloser(r)}
return readAutoCloser{io.NopCloser(r)}
}
return readAutoCloser{r.(io.ReadCloser)}
}
@@ -44,7 +43,7 @@ func newReadAutoCloser(r io.Reader) readAutoCloser {
func main() {
p := &pipe{}
p.Reader = newReadAutoCloser(strings.NewReader("test"))
b, err := ioutil.ReadAll(p.Reader)
b, err := io.ReadAll(p.Reader)
if err != nil {
log.Fatal(err)
}

14
_test/method38.go Normal file
View File

@@ -0,0 +1,14 @@
package main
import (
"fmt"
"github.com/traefik/yaegi/_test/method38"
)
func main() {
fmt.Println(method38.Get())
}
// Output:
// &{[] {<nil>}}

19
_test/method38/a.go Normal file
View File

@@ -0,0 +1,19 @@
package method38
import "sync"
func NewPool() Pool { return Pool{} }
type Buffer struct {
bs []byte
pool Pool
}
type Pool struct {
p *sync.Pool
}
var (
_pool = NewPool()
Get = _pool.Get
)

3
_test/method38/b.go Normal file
View File

@@ -0,0 +1,3 @@
package method38
func (p Pool) Get() *Buffer { return &Buffer{} }

32
_test/method39.go Normal file
View File

@@ -0,0 +1,32 @@
package main
import (
"fmt"
"sync"
"github.com/traefik/yaegi/_test/method38"
)
func NewPool() Pool { return Pool{} }
type Buffer struct {
bs []byte
pool Pool
}
type Pool struct {
p *sync.Pool
}
var (
_pool = NewPool()
Get = _pool.Get
)
func main() {
fmt.Println(Get())
}
// Error:
// 17:11: undefined selector Get

View File

@@ -7,4 +7,4 @@ func main() {
}
// Error:
// 5:2: invalid operation: mismatched types int and float64
// 5:2: invalid operation: mismatched types int and untyped float

View File

@@ -2,11 +2,11 @@ package main
import (
"fmt"
"io/ioutil"
"os"
)
func main() {
_, err := ioutil.ReadFile("__NotExisting__")
_, err := os.ReadFile("__NotExisting__")
if err != nil {
fmt.Println(err.Error())
}

22
_test/rune2.go Normal file
View File

@@ -0,0 +1,22 @@
package main
import "fmt"
const majorVersion = '2'
type hashed struct {
major byte
}
func main() {
fmt.Println(majorVersion)
p := new(hashed)
p.major = majorVersion
fmt.Println(p)
}
// Output:
// 50
// &{50}

View File

@@ -9,10 +9,20 @@ func test(time string, t time.Time) string {
return time
}
var zero = time.Time{}
func test2(time string) time.Time {
return zero
}
func main() {
str := test("test", time.Now())
fmt.Println(str)
str2 := test2("test2")
fmt.Println(str2)
}
// Output:
// test
// 0001-01-01 00:00:00 +0000 UTC

View File

@@ -8,6 +8,7 @@ type A struct {
}
type D struct {
F *A
E *A
}

20
_test/struct60.go Normal file
View File

@@ -0,0 +1,20 @@
package main
import (
"fmt"
)
type data struct {
S string
}
func render(v interface{}) {
fmt.Println(v)
}
func main() {
render(data{})
}
// Output:
// {}

22
_test/struct61.go Normal file
View File

@@ -0,0 +1,22 @@
package main
import "fmt"
type A struct {
B string
D
}
type D struct {
*A
E *A
}
func main() {
a := &A{B: "b"}
a.D = D{E: a}
fmt.Println(a.D.E.B)
}
// Output:
// b

11
_test/struct62.go Normal file
View File

@@ -0,0 +1,11 @@
package main
func main() {
type A struct{ *A }
v := &A{}
v.A = v
println("v.A.A = v", v.A.A == v)
}
// Output:
// v.A.A = v true

17
_test/type27.go Normal file
View File

@@ -0,0 +1,17 @@
package main
import "fmt"
type Foo = int
func (f Foo) Bar() int {
return f * f
}
func main() {
x := Foo(1)
fmt.Println(x.Bar())
}
// Error:
// 7:1: cannot define new methods on non-local type int

17
_test/type28.go Normal file
View File

@@ -0,0 +1,17 @@
package main
import "fmt"
type Foo = int
func (f *Foo) Bar() int {
return *f * *f
}
func main() {
x := Foo(1)
fmt.Println(x.Bar())
}
// Error:
// 7:1: cannot define new methods on non-local type int

12
_test/type29.go Normal file
View File

@@ -0,0 +1,12 @@
package main
var Foo int
func (f Foo) Bar() int {
return 1
}
func main() {}
// Error:
// 5:1: cannot define new methods on non-local type int

12
_test/type30.go Normal file
View File

@@ -0,0 +1,12 @@
package main
var Foo *int
func (f Foo) Bar() int {
return 1
}
func main() {}
// Error:
// 5:1: cannot define new methods on non-local type int

17
_test/type31.go Normal file
View File

@@ -0,0 +1,17 @@
package main
import "fmt"
func (f Foo) Bar() int {
return f * f
}
type Foo = int
func main() {
x := Foo(1)
fmt.Println(x.Bar())
}
// Error:
// 5:1: cannot define new methods on non-local type int

17
_test/type32.go Normal file
View File

@@ -0,0 +1,17 @@
package main
import "fmt"
func (f *Foo) Bar() int {
return *f * *f
}
type Foo = int
func main() {
x := Foo(1)
fmt.Println(x.Bar())
}
// Error:
// 5:1: cannot define new methods on non-local type int

11
_test/type33.go Normal file
View File

@@ -0,0 +1,11 @@
package main
func (f *Foo) Bar() int {
return *f * *f
}
func main() {
}
// Error:
// 3:1: undefined: Foo

View File

@@ -1,11 +1,14 @@
package main
import (
"fmt"
"math/bits"
"unsafe"
)
const SSize = 16
const (
SSize = 16
WSize = bits.UintSize / 8
)
type S struct {
X int
@@ -13,14 +16,19 @@ type S struct {
}
func main() {
bigEndian := (*(*[2]uint8)(unsafe.Pointer(&[]uint16{1}[0])))[0] == 0
var sBuf [SSize]byte
s := (*S)(unsafe.Pointer(&sBuf[0]))
s.X = 2
s.Y = 4
fmt.Println(sBuf)
if bigEndian {
println(sBuf[0+WSize-1], sBuf[WSize+WSize-1])
} else {
println(sBuf[0], sBuf[WSize])
}
}
// Output:
// [2 0 0 0 0 0 0 0 4 0 0 0 0 0 0 0]
// 2 4

View File

@@ -2,10 +2,11 @@ package main
import (
"fmt"
"math/bits"
"unsafe"
)
const SSize = 24
const WSize = bits.UintSize / 8
type S struct {
X int
@@ -20,7 +21,8 @@ func main() {
{X: 3},
}
addr := unsafe.Pointer(&arr[0])
s := *(*S)(unsafe.Pointer(uintptr(addr) + SSize*2))
// s := *(*S)(unsafe.Pointer(uintptr(addr) + SSize*2))
s := *(*S)(unsafe.Pointer(uintptr(addr) + WSize*6))
fmt.Println(s.X)
}

View File

@@ -1,10 +1,12 @@
package main
import (
"fmt"
"math/bits"
"unsafe"
)
const WSize = bits.UintSize / 8
type S struct {
X int
Y int
@@ -13,12 +15,12 @@ type S struct {
func main() {
x := S{}
size := unsafe.Sizeof(x)
align := unsafe.Alignof(x.Y)
offset := unsafe.Offsetof(x.Z)
size := unsafe.Sizeof(x) / WSize
align := unsafe.Alignof(x.Y) / WSize
offset := unsafe.Offsetof(x.Z) / WSize
fmt.Println(size, align, offset)
println(size, align, offset)
}
// Output:
// 24 8 16
// 3 1 2

23
_test/unsafe6.go Normal file
View File

@@ -0,0 +1,23 @@
package main
import (
"fmt"
"unsafe"
)
type S struct {
X int
Y int
Z int
}
func main() {
x := S{Z: 5}
ptr := unsafe.Pointer(&x)
offset := int(unsafe.Offsetof(x.Z))
p := unsafe.Add(ptr, offset)
i := *(*int)(p)
fmt.Println(i)
}

20
_test/unsafe7.go Normal file
View File

@@ -0,0 +1,20 @@
package main
import (
"fmt"
"unsafe"
)
type S struct {
X int
Y int
Z int
}
func main() {
x := [2]S{{Z: 5}, {Z: 10}}
s := unsafe.Slice(&x[0], 2)
fmt.Println(s)
}

19
_test/var16.go Normal file
View File

@@ -0,0 +1,19 @@
package main
func getArray() ([]int, error) { println("getArray"); return []int{1, 2}, nil }
func getNum() (int, error) { println("getNum"); return 3, nil }
func main() {
if a, err := getNum(); err != nil {
println("#1", a)
} else if a, err := getArray(); err != nil {
println("#2", a)
}
println("#3")
}
// Output:
// getNum
// getArray
// #3

View File

@@ -18,12 +18,14 @@ func extractCmd(arg []string) error {
var name string
var exclude string
var include string
var tag string
eflag := flag.NewFlagSet("run", flag.ContinueOnError)
eflag.StringVar(&licensePath, "license", "", "path to a LICENSE file")
eflag.StringVar(&name, "name", "", "the namespace for the extracted symbols")
eflag.StringVar(&exclude, "exclude", "", "comma separated list of regexp matching symbols to exclude")
eflag.StringVar(&include, "include", "", "comma separated list of regexp matching symbols to include")
eflag.StringVar(&tag, "tag", "", "comma separated list of build tags to be added to the created package")
eflag.Usage = func() {
fmt.Println("Usage: yaegi extract [options] packages...")
fmt.Println("Options:")
@@ -56,6 +58,9 @@ func extractCmd(arg []string) error {
Dest: name,
License: license,
}
if tag != "" {
ext.Tag = strings.Split(tag, ",")
}
if exclude != "" {
ext.Exclude = strings.Split(exclude, ",")

View File

@@ -4,7 +4,6 @@ import (
"flag"
"fmt"
"go/build"
"io/ioutil"
"os"
"reflect"
"strconv"
@@ -47,7 +46,12 @@ func run(arg []string) error {
}
args := rflag.Args()
i := interp.New(interp.Options{GoPath: build.Default.GOPATH, BuildTags: strings.Split(tags, ",")})
i := interp.New(interp.Options{
GoPath: build.Default.GOPATH,
BuildTags: strings.Split(tags, ","),
Env: os.Environ(),
Unrestricted: useUnrestricted,
})
if err := i.Use(stdlib.Symbols); err != nil {
return err
}
@@ -130,7 +134,7 @@ func isFile(path string) bool {
}
func runFile(i *interp.Interpreter, path string, noAutoImport bool) error {
b, err := ioutil.ReadFile(path)
b, err := os.ReadFile(path)
if err != nil {
return err
}

View File

@@ -116,7 +116,12 @@ func test(arg []string) (err error) {
return err
}
i := interp.New(interp.Options{GoPath: build.Default.GOPATH, BuildTags: strings.Split(tags, ",")})
i := interp.New(interp.Options{
GoPath: build.Default.GOPATH,
BuildTags: strings.Split(tags, ","),
Env: os.Environ(),
Unrestricted: useUnrestricted,
})
if err := i.Use(stdlib.Symbols); err != nil {
return err
}

View File

@@ -3,10 +3,10 @@ package main
import (
"bytes"
"context"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"testing"
@@ -37,7 +37,7 @@ func applyCIMultiplier(timeout time.Duration) time.Duration {
}
func TestYaegiCmdCancel(t *testing.T) {
tmp, err := ioutil.TempDir("", "yaegi-")
tmp, err := os.MkdirTemp("", "yaegi-")
if err != nil {
t.Fatalf("failed to create tmp directory: %v", err)
}
@@ -49,7 +49,15 @@ func TestYaegiCmdCancel(t *testing.T) {
}()
yaegi := filepath.Join(tmp, "yaegi")
build := exec.Command("go", "build", "-race", "-o", yaegi, ".")
args := []string{"build"}
if raceDetectorSupported(runtime.GOOS, runtime.GOARCH) {
args = append(args, "-race")
}
args = append(args, "-o", yaegi, ".")
build := exec.Command("go", args...)
out, err := build.CombinedOutput()
if err != nil {
t.Fatalf("failed to build yaegi command: %v: %s", err, out)
@@ -115,3 +123,16 @@ func TestYaegiCmdCancel(t *testing.T) {
}
}
}
func raceDetectorSupported(goos, goarch string) bool {
switch goos {
case "linux":
return goarch == "amd64" || goarch == "ppc64le" || goarch == "arm64"
case "darwin":
return goarch == "amd64" || goarch == "arm64"
case "freebsd", "netbsd", "openbsd", "windows":
return goarch == "amd64"
default:
return false
}
}

67
example/fs/fs_test.go Normal file
View File

@@ -0,0 +1,67 @@
package fs1
import (
"testing"
// only available from 1.16.
"testing/fstest"
"github.com/traefik/yaegi/interp"
"github.com/traefik/yaegi/stdlib"
)
var testFilesystem = fstest.MapFS{
"main.go": &fstest.MapFile{
Data: []byte(`package main
import (
"foo/bar"
"./localfoo"
)
func main() {
bar.PrintSomething()
localfoo.PrintSomethingElse()
}
`),
},
"_pkg/src/foo/bar/bar.go": &fstest.MapFile{
Data: []byte(`package bar
import (
"fmt"
)
func PrintSomething() {
fmt.Println("I am a virtual filesystem printing something from _pkg/src/foo/bar/bar.go!")
}
`),
},
"localfoo/foo.go": &fstest.MapFile{
Data: []byte(`package localfoo
import (
"fmt"
)
func PrintSomethingElse() {
fmt.Println("I am virtual filesystem printing else from localfoo/foo.go!")
}
`),
},
}
func TestFilesystemMapFS(t *testing.T) {
i := interp.New(interp.Options{
GoPath: "./_pkg",
SourcecodeFilesystem: testFilesystem,
})
if err := i.Use(stdlib.Symbols); err != nil {
t.Fatal(err)
}
_, err := i.EvalPath(`main.go`)
if err != nil {
t.Fatal(err)
}
}

View File

@@ -80,7 +80,14 @@ func init() {
{{end}}
}
{{range $m := $value.Method -}}
func (W {{$value.Name}}) {{$m.Name}}{{$m.Param}} {{$m.Result}} { {{$m.Ret}} W.W{{$m.Name}}{{$m.Arg}} }
func (W {{$value.Name}}) {{$m.Name}}{{$m.Param}} {{$m.Result}} {
{{- if eq $m.Name "String"}}
if W.WString == nil {
return ""
}
{{end -}}
{{$m.Ret}} W.W{{$m.Name}}{{$m.Arg}}
}
{{end}}
{{end}}
`
@@ -270,6 +277,15 @@ func (e *Extractor) genContent(importPath string, p *types.Package) ([]byte, err
}
}
for _, t := range e.Tag {
if len(t) != 0 {
buildTags += "," + t
}
}
if len(buildTags) != 0 && buildTags[0] == ',' {
buildTags = buildTags[1:]
}
b := new(bytes.Buffer)
data := map[string]interface{}{
"Dest": e.Dest,
@@ -336,6 +352,7 @@ type Extractor struct {
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
@@ -446,7 +463,7 @@ func GetMinor(part string) string {
return minor
}
const defaultMinorVersion = 16
const defaultMinorVersion = 17
func genBuildTags() (string, error) {
version := runtime.Version()

View File

@@ -43,7 +43,7 @@ func TestPackages(t *testing.T) {
// We check this one because it shows both defects when we break it: the value
// gets corrupted, and the type becomes token.INT
// TODO(mpl): if the ident between key and value becomes annoying, be smarter about it.
contains: `"MaxFloat64": reflect.ValueOf(constant.MakeFromLiteral("179769313486231570814527423731704356798100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", token.FLOAT, 0)),`,
contains: `"MaxFloat32": reflect.ValueOf(constant.MakeFromLiteral("340282346638528859811704183484516925440", token.FLOAT, 0)),`,
},
{
desc: "using relative path, using go.mod",

2
go.mod
View File

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

View File

@@ -3,8 +3,8 @@ package main
import (
"bytes"
"go/format"
"io/ioutil"
"log"
"os"
"strings"
"text/template"
)
@@ -289,6 +289,13 @@ func {{$name}}Assign(n *node) {
next := getExec(n.tnext)
typ := n.typ.TypeOf()
c0, c1 := n.child[0], n.child[1]
setMap := isMapEntry(c0)
var mapValue, indexValue func(*frame) reflect.Value
if setMap {
mapValue = genValue(c0.child[0])
indexValue = genValue(c0.child[1])
}
if c1.rval.IsValid() {
switch typ.Kind() {
@@ -299,6 +306,9 @@ func {{$name}}Assign(n *node) {
n.exec = func(f *frame) bltn {
v, s := v0(f)
v.SetString(s {{$op.Name}} v1)
if setMap {
mapValue(f).SetMapIndex(indexValue(f), v)
}
return next
}
{{- end}}
@@ -312,6 +322,9 @@ func {{$name}}Assign(n *node) {
n.exec = func(f *frame) bltn {
v, i := v0(f)
v.SetInt(i {{$op.Name}} j)
if setMap {
mapValue(f).SetMapIndex(indexValue(f), v)
}
return next
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
@@ -320,6 +333,9 @@ func {{$name}}Assign(n *node) {
n.exec = func(f *frame) bltn {
v, i := v0(f)
v.SetUint(i {{$op.Name}} j)
if setMap {
mapValue(f).SetMapIndex(indexValue(f), v)
}
return next
}
{{- if $op.Float}}
@@ -329,6 +345,9 @@ func {{$name}}Assign(n *node) {
n.exec = func(f *frame) bltn {
v, i := v0(f)
v.SetFloat(i {{$op.Name}} j)
if setMap {
mapValue(f).SetMapIndex(indexValue(f), v)
}
return next
}
case reflect.Complex64, reflect.Complex128:
@@ -337,6 +356,9 @@ func {{$name}}Assign(n *node) {
n.exec = func(f *frame) bltn {
v := v0(f)
v.SetComplex(v.Complex() {{$op.Name}} v1)
if setMap {
mapValue(f).SetMapIndex(indexValue(f), v)
}
return next
}
{{- end}}
@@ -350,6 +372,9 @@ func {{$name}}Assign(n *node) {
n.exec = func(f *frame) bltn {
v, s := v0(f)
v.SetString(s {{$op.Name}} v1(f).String())
if setMap {
mapValue(f).SetMapIndex(indexValue(f), v)
}
return next
}
{{- end}}
@@ -364,6 +389,9 @@ func {{$name}}Assign(n *node) {
v, i := v0(f)
_, j := v1(f)
v.SetInt(i {{$op.Name}} j)
if setMap {
mapValue(f).SetMapIndex(indexValue(f), v)
}
return next
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
@@ -373,6 +401,9 @@ func {{$name}}Assign(n *node) {
v, i := v0(f)
_, j := v1(f)
v.SetUint(i {{$op.Name}} j)
if setMap {
mapValue(f).SetMapIndex(indexValue(f), v)
}
return next
}
{{- if $op.Float}}
@@ -383,6 +414,9 @@ func {{$name}}Assign(n *node) {
v, i := v0(f)
_, j := v1(f)
v.SetFloat(i {{$op.Name}} j)
if setMap {
mapValue(f).SetMapIndex(indexValue(f), v)
}
return next
}
case reflect.Complex64, reflect.Complex128:
@@ -391,6 +425,9 @@ func {{$name}}Assign(n *node) {
n.exec = func(f *frame) bltn {
v := v0(f)
v.SetComplex(v.Complex() {{$op.Name}} v1(f).Complex())
if setMap {
mapValue(f).SetMapIndex(indexValue(f), v)
}
return next
}
{{- end}}
@@ -402,34 +439,54 @@ func {{$name}}Assign(n *node) {
func {{$name}}(n *node) {
next := getExec(n.tnext)
typ := n.typ.TypeOf()
c0 := n.child[0]
setMap := isMapEntry(c0)
var mapValue, indexValue func(*frame) reflect.Value
if setMap {
mapValue = genValue(c0.child[0])
indexValue = genValue(c0.child[1])
}
switch typ.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
v0 := genValueInt(n.child[0])
v0 := genValueInt(c0)
n.exec = func(f *frame) bltn {
v, i := v0(f)
v.SetInt(i {{$op.Name}} 1)
if setMap {
mapValue(f).SetMapIndex(indexValue(f), v)
}
return next
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
v0 := genValueUint(n.child[0])
v0 := genValueUint(c0)
n.exec = func(f *frame) bltn {
v, i := v0(f)
v.SetUint(i {{$op.Name}} 1)
if setMap {
mapValue(f).SetMapIndex(indexValue(f), v)
}
return next
}
case reflect.Float32, reflect.Float64:
v0 := genValueFloat(n.child[0])
v0 := genValueFloat(c0)
n.exec = func(f *frame) bltn {
v, i := v0(f)
v.SetFloat(i {{$op.Name}} 1)
if setMap {
mapValue(f).SetMapIndex(indexValue(f), v)
}
return next
}
case reflect.Complex64, reflect.Complex128:
v0 := genValue(n.child[0])
v0 := genValue(c0)
n.exec = func(f *frame) bltn {
v := v0(f)
v.SetComplex(v.Complex() {{$op.Name}} 1)
if setMap {
mapValue(f).SetMapIndex(indexValue(f), v)
}
return next
}
}
@@ -500,7 +557,7 @@ func {{$name}}(n *node) {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
i1 := v1(f).Interface()
if i0 != i1 {
if i0 {{$op.Name}} i1 {
dest(f).SetBool(true)
return tnext
}
@@ -522,7 +579,7 @@ func {{$name}}(n *node) {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
if i0 != i1 {
if i0 {{$op.Name}} i1 {
dest(f).SetBool(true)
return tnext
}
@@ -545,7 +602,7 @@ func {{$name}}(n *node) {
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
i1 := v1(f).Interface()
if i0 != i1 {
if i0 {{$op.Name}} i1 {
dest(f).SetBool(true)
return tnext
}
@@ -1126,7 +1183,7 @@ func main() {
log.Fatal(err)
}
if err = ioutil.WriteFile("op.go", source, 0666); err != nil {
if err = os.WriteFile("op.go", source, 0o666); err != nil {
log.Fatal(err)
}
}

View File

@@ -0,0 +1,61 @@
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/reflect/type.go.
type rtype struct {
_ uintptr
_ uintptr
_ uint32
_ uint32
_ uintptr
_ uintptr
_ uint32
_ uint32
}
type emptyInterface struct {
typ *rtype
_ unsafe.Pointer
}
type structField struct {
_ uintptr
typ *rtype
_ uintptr
}
type structType struct {
rtype
_ uintptr
fields []structField
}
// 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 := (*structType)(unsafe.Pointer(rtyp))
f := styp.fields[idx]
f.typ = unpackType(t)
styp.fields[idx] = f
}
func unpackType(t reflect.Type) *rtype {
v := reflect.New(t).Elem().Interface()
return (*emptyInterface)(unsafe.Pointer(&v)).typ
}

View File

@@ -0,0 +1,33 @@
package unsafe2_test
import (
"reflect"
"testing"
"github.com/traefik/yaegi/internal/unsafe2"
)
func TestSwapFieldType(t *testing.T) {
f := []reflect.StructField{
{
Name: "A",
Type: reflect.TypeOf(int(0)),
},
{
Name: "B",
Type: reflect.PtrTo(unsafe2.DummyType),
},
{
Name: "C",
Type: reflect.TypeOf(int64(0)),
},
}
typ := reflect.StructOf(f)
ntyp := reflect.PtrTo(typ)
unsafe2.SetFieldType(typ, 1, ntyp)
if typ.Field(1).Type != ntyp {
t.Fatalf("unexpected field type: want %s; got %s", ntyp, typ.Field(1).Type)
}
}

View File

@@ -1,7 +1,6 @@
package interp
import (
"errors"
"fmt"
"go/ast"
"go/constant"
@@ -93,7 +92,8 @@ const (
switchIfStmt
typeAssertExpr
typeDecl
typeSpec
typeSpec // type A int
typeSpecAssign // type A = int
typeSwitch
unaryExpr
valueSpec
@@ -176,6 +176,7 @@ var kinds = [...]string{
typeAssertExpr: "typeAssertExpr",
typeDecl: "typeDecl",
typeSpec: "typeSpec",
typeSpecAssign: "typeSpecAssign",
typeSwitch: "typeSwitch",
unaryExpr: "unaryExpr",
valueSpec: "valueSpec",
@@ -360,21 +361,14 @@ func wrapInMain(src string) string {
return fmt.Sprintf("package main; func main() {%s\n}", src)
}
// Note: no type analysis is performed at this stage, it is done in pre-order
// processing of CFG, in order to accommodate forward type declarations.
// ast parses src string containing Go code and generates the corresponding AST.
// The package name and the AST root node are returned.
// The given name is used to set the filename of the relevant source file in the
// interpreter's FileSet.
func (interp *Interpreter) ast(src, name string, inc bool) (string, *node, error) {
var inFunc bool
func (interp *Interpreter) parse(src, name string, inc bool) (node ast.Node, err error) {
mode := parser.DeclarationErrors
// Allow incremental parsing of declarations or statements, by inserting
// them in a pseudo file package or function. Those statements or
// declarations will be always evaluated in the global scope.
var tok token.Token
var inFunc bool
if inc {
tok = interp.firstToken(src)
switch tok {
@@ -391,18 +385,18 @@ func (interp *Interpreter) ast(src, name string, inc bool) (string, *node, error
}
if ok, err := interp.buildOk(&interp.context, name, src); !ok || err != nil {
return "", nil, err // skip source not matching build constraints
return nil, err // skip source not matching build constraints
}
f, err := parser.ParseFile(interp.fset, name, src, mode)
if err != nil {
// only retry if we're on an expression/statement about a func
if !inc || tok != token.FUNC {
return "", nil, err
return nil, err
}
// do not bother retrying if we know it's an error we're going to ignore later on.
if ignoreError(err, src) {
return "", nil, err
return nil, err
}
// do not lose initial error, in case retrying fails.
initialError := err
@@ -410,16 +404,32 @@ func (interp *Interpreter) ast(src, name string, inc bool) (string, *node, error
src := wrapInMain(strings.TrimPrefix(src, "package main;"))
f, err = parser.ParseFile(interp.fset, name, src, mode)
if err != nil {
return "", nil, initialError
return nil, initialError
}
}
setYaegiTags(&interp.context, f.Comments)
if inFunc {
// return the body of the wrapper main function
return f.Decls[0].(*ast.FuncDecl).Body, nil
}
setYaegiTags(&interp.context, f.Comments)
return f, nil
}
// Note: no type analysis is performed at this stage, it is done in pre-order
// processing of CFG, in order to accommodate forward type declarations.
// ast parses src string containing Go code and generates the corresponding AST.
// The package name and the AST root node are returned.
// The given name is used to set the filename of the relevant source file in the
// interpreter's FileSet.
func (interp *Interpreter) ast(f ast.Node) (string, *node, error) {
var err error
var root *node
var anc astNode
var st nodestack
var pkgName string
pkgName := "main"
addChild := func(root **node, anc astNode, pos token.Pos, kind nkind, act action) *node {
var i interface{}
@@ -627,7 +637,7 @@ func (interp *Interpreter) ast(src, name string, inc bool) (string, *node, error
}
st.push(addChild(&root, anc, pos, kind, aNop), nod)
case *ast.CommentGroup:
case *ast.CommentGroup, *ast.EmptyStmt:
return false
case *ast.CompositeLit:
@@ -680,6 +690,7 @@ func (interp *Interpreter) ast(src, name string, inc bool) (string, *node, error
case *ast.FuncDecl:
n := addChild(&root, anc, pos, funcDecl, aNop)
n.val = n
if a.Recv == nil {
// function is not a method, create an empty receiver list
addChild(&root, astNode{n, nod}, pos, fieldList, aNop)
@@ -825,6 +836,10 @@ func (interp *Interpreter) ast(src, name string, inc bool) (string, *node, error
st.push(addChild(&root, anc, pos, typeAssertExpr, aTypeAssert), nod)
case *ast.TypeSpec:
if a.Assign.IsValid() {
st.push(addChild(&root, anc, pos, typeSpecAssign, aNop), nod)
break
}
st.push(addChild(&root, anc, pos, typeSpec, aNop), nod)
case *ast.TypeSwitchStmt:
@@ -891,15 +906,8 @@ func (interp *Interpreter) ast(src, name string, inc bool) (string, *node, error
}
return true
})
if inFunc {
// Incremental parsing: statements were inserted in a pseudo function.
// Set root to function body so its statements are evaluated in global scope.
root = root.child[1].child[3]
root.anc = nil
}
if pkgName == "" {
return "", root, errors.New("no package name found")
}
interp.roots = append(interp.roots, root)
return pkgName, root, err
}

View File

@@ -178,6 +178,7 @@ var knownArch = map[string]bool{
"amd64": true,
"arm": true,
"arm64": true,
"loong64": true,
"mips": true,
"mips64": true,
"mips64le": true,

View File

@@ -49,9 +49,11 @@ const nilIdent = "nil"
// and pre-compute frame sizes and indexes for all un-named (temporary) and named
// variables. A list of nodes of init functions is returned.
// Following this pass, the CFG is ready to run.
func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
sc := interp.initScopePkg(importPath)
check := typecheck{}
func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string) ([]*node, error) {
if sc == nil {
sc = interp.initScopePkg(importPath, pkgName)
}
check := typecheck{scope: sc}
var initNodes []*node
var err error
@@ -62,6 +64,9 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
if err != nil {
return false
}
if n.scope == nil {
n.scope = sc
}
switch n.kind {
case binaryExpr, unaryExpr, parenExpr:
if isBoolAction(n) {
@@ -145,10 +150,10 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
switch typ.Kind() {
case reflect.Map:
n.anc.gen = rangeMap
ityp := &itype{cat: valueT, rtype: reflect.TypeOf((*reflect.MapIter)(nil))}
ityp := valueTOf(reflect.TypeOf((*reflect.MapIter)(nil)))
sc.add(ityp)
ktyp = &itype{cat: valueT, rtype: typ.Key()}
vtyp = &itype{cat: valueT, rtype: typ.Elem()}
ktyp = valueTOf(typ.Key())
vtyp = valueTOf(typ.Elem())
case reflect.String:
sc.add(sc.getType("int")) // Add a dummy type to store array shallow copy for range
sc.add(sc.getType("int")) // Add a dummy type to store index for range
@@ -157,11 +162,11 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
case reflect.Array, reflect.Slice:
sc.add(sc.getType("int")) // Add a dummy type to store array shallow copy for range
ktyp = sc.getType("int")
vtyp = &itype{cat: valueT, rtype: typ.Elem()}
vtyp = valueTOf(typ.Elem())
}
case mapT:
n.anc.gen = rangeMap
ityp := &itype{cat: valueT, rtype: reflect.TypeOf((*reflect.MapIter)(nil))}
ityp := valueTOf(reflect.TypeOf((*reflect.MapIter)(nil)))
sc.add(ityp)
ktyp = o.typ.key
vtyp = o.typ.val
@@ -169,7 +174,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
ktyp = sc.getType("int")
vtyp = o.typ.val
if vtyp.cat == valueT {
vtyp = &itype{cat: valueT, rtype: vtyp.rtype.Elem()}
vtyp = valueTOf(vtyp.rtype.Elem())
} else {
vtyp = vtyp.val
}
@@ -284,19 +289,19 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
case compositeLitExpr:
if len(n.child) > 0 && n.child[0].isType(sc) {
// Get type from 1st child
// Get type from 1st child.
if n.typ, err = nodeType(interp, sc, n.child[0]); err != nil {
return false
}
// Indicate that the first child is the type
// Indicate that the first child is the type.
n.nleft = 1
} else {
// 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 {
if atyp.cat == valueT {
n.typ = &itype{cat: valueT, rtype: atyp.rtype.Elem()}
if atyp.cat == valueT && hasElem(atyp.rtype) {
n.typ = valueTOf(atyp.rtype.Elem())
} else {
n.typ = atyp.val
}
@@ -410,11 +415,11 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
sc.loop = n
case importSpec:
// already all done in gta
// Already all done in GTA.
return false
case typeSpec:
// processing already done in GTA pass for global types, only parses inlined types
// Processing already done in GTA pass for global types, only parses inlined types.
if sc.def == nil {
return false
}
@@ -424,13 +429,16 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
return false
}
if typ.incomplete {
err = n.cfgErrorf("invalid type declaration")
return false
// Type may still be incomplete in case of a local recursive struct declaration.
if typ, err = typ.finalize(); err != nil {
err = n.cfgErrorf("invalid type declaration")
return false
}
}
switch n.child[1].kind {
case identExpr, selectorExpr:
n.typ = &itype{cat: aliasT, val: typ, name: typeName}
n.typ = namedOf(typ, pkgName, typeName)
default:
n.typ = typ
n.typ.name = typeName
@@ -443,7 +451,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
// values which may be used in further declarations.
if !sc.global {
for _, c := range n.child {
if _, err = interp.cfg(c, importPath); err != nil {
if _, err = interp.cfg(c, sc, importPath, pkgName); err != nil {
// No error processing here, to allow recovery in subtree nodes.
err = nil
}
@@ -477,7 +485,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
break
}
n.typ = &itype{cat: ptrT, val: n.child[0].typ}
n.typ = ptrOf(n.child[0].typ)
n.findex = sc.add(n.typ)
case assignStmt, defineStmt:
@@ -513,7 +521,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
return
}
if src.typ.isBinMethod {
dest.typ = &itype{cat: valueT, rtype: src.typ.methodCallType()}
dest.typ = valueTOf(src.typ.methodCallType())
} else {
// In a new definition, propagate the source type to the destination
// type. If the source is an untyped constant, make sure that the
@@ -572,7 +580,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
dest.gen = nop
case isFuncField(dest):
// Setting a struct field of function type requires an extra step. Do not optimize.
case isCall(src) && !isInterfaceSrc(dest.typ) && !isRecursiveField(dest) && n.kind != defineStmt:
case isCall(src) && !isInterfaceSrc(dest.typ) && n.kind != defineStmt:
// Call action may perform the assignment directly.
if dest.typ.id() != src.typ.id() {
// Skip optimitization if returned type doesn't match assigned one.
@@ -594,8 +602,8 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
// Skip optimisation for assigned interface.
break
}
if dest.action == aGetIndex {
// Skip optimization, as it does not work when assigning to a struct field.
if dest.action == aGetIndex || dest.action == aStar {
// Skip optimization, as it does not work when assigning to a struct field or a dereferenced pointer.
break
}
n.gen = nop
@@ -657,7 +665,12 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
if r := lc.child[0].typ.numOut(); r != l {
err = n.cfgErrorf("assignment mismatch: %d variables but %s returns %d values", l, lc.child[0].name(), r)
}
n.gen = nop
if isBinCall(lc, sc) {
n.gen = nop
} else {
// TODO (marc): skip if no conversion or wrapping is needed.
n.gen = assignFromCall
}
case indexExpr:
lc.gen = getIndexMap2
n.gen = nop
@@ -762,7 +775,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
case ptrT:
n.typ = t.val
if t.val.cat == valueT {
n.typ = &itype{cat: valueT, rtype: t.val.rtype.Elem()}
n.typ = valueTOf(t.val.rtype.Elem())
} else {
n.typ = t.val.val
}
@@ -772,7 +785,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
if t.rtype.Kind() == reflect.String {
n.typ = sc.getType("byte")
} else {
n.typ = &itype{cat: valueT, rtype: t.rtype.Elem()}
n.typ = valueTOf(t.rtype.Elem())
}
default:
n.typ = t.val
@@ -858,7 +871,9 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
case labeledStmt:
wireChild(n)
n.start = n.child[1].start
if len(n.child) > 1 {
n.start = n.child[1].start
}
gotoLabel(n.sym)
case callExpr:
@@ -885,7 +900,11 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
// Store result directly to frame output location, to avoid a frame copy.
n.findex = 0
case bname == "cap" && isInConstOrTypeDecl(n):
switch n.child[1].typ.TypeOf().Kind() {
t := n.child[1].typ.TypeOf()
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
switch t.Kind() {
case reflect.Array, reflect.Chan:
capConst(n)
default:
@@ -894,7 +913,11 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
n.findex = notInFrame
n.gen = nop
case bname == "len" && isInConstOrTypeDecl(n):
switch n.child[1].typ.TypeOf().Kind() {
t := n.child[1].typ.TypeOf()
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
switch t.Kind() {
case reflect.Array, reflect.Chan, reflect.String:
lenConst(n)
default:
@@ -955,7 +978,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
n.typ = c0.typ
n.findex = sc.add(n.typ)
}
case isBinCall(n):
case isBinCall(n, sc):
err = check.arguments(n, n.child[1:], n.child[0], n.action == aCallSlice)
if err != nil {
break
@@ -973,13 +996,13 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
sc.add(funcType.ret[i])
}
} else {
n.typ = &itype{cat: valueT, rtype: typ.Out(0)}
n.typ = valueTOf(typ.Out(0))
if n.anc.kind == returnStmt {
n.findex = childPos(n)
} else {
n.findex = sc.add(n.typ)
for i := 1; i < typ.NumOut(); i++ {
sc.add(&itype{cat: valueT, rtype: typ.Out(i)})
sc.add(valueTOf(typ.Out(i)))
}
}
}
@@ -995,7 +1018,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
err = n.cfgErrorf("struct does not contain field: %s", c1.child[1].ident)
break
}
n.typ = &itype{cat: valueT, rtype: reflect.TypeOf(field.Offset)}
n.typ = valueTOf(reflect.TypeOf(field.Offset))
n.rval = reflect.ValueOf(field.Offset)
n.gen = nop
default:
@@ -1081,8 +1104,8 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
case reflect.Struct:
err = check.structBinLitExpr(child, rtype)
case reflect.Map:
ktyp := &itype{cat: valueT, rtype: rtype.Key()}
vtyp := &itype{cat: valueT, rtype: rtype.Elem()}
ktyp := valueTOf(rtype.Key())
vtyp := valueTOf(rtype.Elem())
err = check.mapLitExpr(child, ktyp, vtyp)
}
}
@@ -1223,7 +1246,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
case funcDecl:
n.start = n.child[3].start
n.types = sc.types
n.types, n.scope = sc.types, sc
sc = sc.pop()
funcName := n.child[1].ident
if sym := sc.sym[funcName]; !isMethod(n) && sym != nil {
@@ -1234,7 +1257,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
}
case funcLit:
n.types = sc.types
n.types, n.scope = sc.types, sc
sc = sc.pop()
err = genRun(n)
@@ -1255,7 +1278,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
// retry with the filename, in case ident is a package name.
sym, level, found = sc.lookup(filepath.Join(n.ident, baseName))
if !found {
err = n.cfgErrorf("undefined: %s", n.ident)
err = n.cfgErrorf("undefined: %s %d", n.ident, n.index)
break
}
}
@@ -1480,9 +1503,9 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
pkg := n.child[0].sym.typ.path
if s, ok := interp.binPkg[pkg][name]; ok {
if isBinType(s) {
n.typ = &itype{cat: valueT, rtype: s.Type().Elem()}
n.typ = valueTOf(s.Type().Elem())
} else {
n.typ = &itype{cat: valueT, rtype: fixPossibleConstType(s.Type()), untyped: isValueUntyped(s)}
n.typ = valueTOf(fixPossibleConstType(s.Type()), withUntyped(isValueUntyped(s)))
n.rval = s
}
n.action = aGetSym
@@ -1534,16 +1557,14 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
n.gen = getPtrIndexSeq
if n.typ.cat == funcT {
// Function in a struct field is always wrapped in reflect.Value.
rtype := n.typ.TypeOf()
n.typ = &itype{cat: valueT, rtype: rtype, val: n.typ}
n.typ = wrapperValueTOf(n.typ.TypeOf(), n.typ)
}
default:
n.gen = getIndexSeq
n.typ = n.typ.fieldSeq(ti)
if n.typ.cat == funcT {
// Function in a struct field is always wrapped in reflect.Value.
rtype := n.typ.TypeOf()
n.typ = &itype{cat: valueT, rtype: rtype, val: n.typ}
n.typ = wrapperValueTOf(n.typ.TypeOf(), n.typ)
}
}
break
@@ -1565,7 +1586,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
}
}
n.val = lind
n.typ = &itype{cat: valueT, rtype: s.Type}
n.typ = valueTOf(s.Type)
break
}
// No field (embedded or not) matched. Try to match a method.
@@ -1582,13 +1603,13 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
n.gen = getIndexBinMethod
n.action = aGetMethod
n.recv = &receiver{node: n.child[0]}
n.typ = &itype{cat: valueT, rtype: method.Type, isBinMethod: true}
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 = &itype{cat: valueT, rtype: field.Type}
n.typ = valueTOf(field.Type)
n.val = field.Index
n.gen = getPtrIndexSeq
break
@@ -1596,7 +1617,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
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 = &itype{cat: valueT, rtype: field.Type}
n.typ = valueTOf(field.Type)
n.val = field.Index
n.gen = getIndexSeq
break
@@ -1608,7 +1629,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
if m2, ok2 := pt.MethodByName(n.child[1].ident); ok2 {
n.val = m2.Index
n.gen = getIndexBinPtrMethod
n.typ = &itype{cat: valueT, rtype: m2.Type, recv: &itype{cat: valueT, rtype: pt}, isBinMethod: true}
n.typ = valueTOf(m2.Type, isBinMethod(), withRecv(valueTOf(pt)))
n.recv = &receiver{node: n.child[0]}
n.action = aGetMethod
break
@@ -1619,18 +1640,18 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
// Handle pointer on object defined in runtime
if method, ok := n.typ.val.rtype.MethodByName(n.child[1].ident); ok {
n.val = method.Index
n.typ = &itype{cat: valueT, rtype: method.Type, recv: n.typ, isBinMethod: true}
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 = &itype{cat: valueT, rtype: method.Type, recv: &itype{cat: valueT, rtype: reflect.PtrTo(n.typ.val.rtype)}, isBinMethod: true}
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 = &itype{cat: valueT, rtype: field.Type}
n.typ = valueTOf(field.Type)
n.val = field.Index
n.gen = getPtrIndexSeq
} else {
@@ -1655,14 +1676,17 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
}
} else if m, lind, isPtr, ok := n.typ.lookupBinMethod(n.child[1].ident); ok {
n.action = aGetMethod
if isPtr && n.typ.fieldSeq(lind).cat != ptrT {
switch {
case isPtr && n.typ.fieldSeq(lind).cat != ptrT:
n.gen = getIndexSeqPtrMethod
} else {
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 = &itype{cat: valueT, rtype: m.Type, recv: n.child[0].typ, isBinMethod: true}
n.typ = valueTOf(m.Type, isBinMethod(), withRecv(n.child[0].typ))
} else {
err = n.cfgErrorf("undefined selector: %s", n.child[1].ident)
}
@@ -1731,7 +1755,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
case n.child[0].isType(sc):
// pointer type expression
n.gen = nop
n.typ = &itype{cat: ptrT, val: n.child[0].typ}
n.typ = ptrOf(n.child[0].typ)
default:
// dereference expression
wireChild(n)
@@ -1742,7 +1766,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
}
if c0 := n.child[0]; c0.typ.cat == valueT {
n.typ = &itype{cat: valueT, rtype: c0.typ.rtype.Elem()}
n.typ = valueTOf(c0.typ.rtype.Elem())
} else {
n.typ = c0.typ.val
}
@@ -1808,6 +1832,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
setFNext(c, clauses[i+1])
}
}
sbn.start = clauses[0].start
n.start = n.child[0].start
n.child[0].tnext = sbn.start
@@ -1876,7 +1901,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
if n.anc.action != aAssignX {
if c0.typ.cat == valueT && isFunc(c1.typ) {
// Avoid special wrapping of interfaces and func types.
n.typ = &itype{cat: valueT, rtype: c1.typ.TypeOf()}
n.typ = valueTOf(c1.typ.TypeOf())
} else {
n.typ = c1.typ
}
@@ -1908,7 +1933,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
if n.action == aRecv {
// Channel receive operation: set type to the channel data type
if n.typ.cat == valueT {
n.typ = &itype{cat: valueT, rtype: n.typ.rtype.Elem()}
n.typ = valueTOf(n.typ.rtype.Elem())
} else {
n.typ = n.typ.val
}
@@ -1992,18 +2017,23 @@ func compDefineX(sc *scope, n *node) error {
if funtype.cat == valueT {
// Handle functions imported from runtime.
for i := 0; i < funtype.rtype.NumOut(); i++ {
types = append(types, &itype{cat: valueT, rtype: funtype.rtype.Out(i)})
types = append(types, valueTOf(funtype.rtype.Out(i)))
}
} else {
types = funtype.ret
}
if n.child[l-1].isType(sc) {
if n.anc.kind == varDecl && n.child[l-1].isType(sc) {
l--
}
if len(types) != l {
return n.cfgErrorf("assignment mismatch: %d variables but %s returns %d values", l, src.child[0].name(), len(types))
}
n.gen = nop
if isBinCall(src, sc) {
n.gen = nop
} else {
// TODO (marc): skip if no conversion or wrapping is needed.
n.gen = assignFromCall
}
case indexExpr:
types = append(types, src.typ, sc.getType("bool"))
@@ -2031,12 +2061,18 @@ func compDefineX(sc *scope, n *node) error {
}
for i, t := range types {
index := sc.add(t)
sc.sym[n.child[i].ident] = &symbol{index: index, kind: varSym, typ: t}
var index int
id := n.child[i].ident
if sym, _, ok := sc.lookup(id); ok && sym.kind == varSym && sym.typ.equals(t) {
// Reuse symbol in case of a variable redeclaration with the same type.
index = sym.index
} else {
index = sc.add(t)
sc.sym[id] = &symbol{index: index, kind: varSym, typ: t}
}
n.child[i].typ = t
n.child[i].findex = index
}
return nil
}
@@ -2402,25 +2438,11 @@ func isField(n *node) bool {
return n.kind == selectorExpr && len(n.child) > 0 && n.child[0].typ != nil && isStruct(n.child[0].typ)
}
func isRecursiveField(n *node) bool {
if !isField(n) {
return false
}
t := n.typ
for t != nil {
if t.recursive {
return true
}
t = t.val
}
return false
}
func isInConstOrTypeDecl(n *node) bool {
anc := n.anc
for anc != nil {
switch anc.kind {
case constDecl, typeDecl:
case constDecl, typeDecl, arrayType, chanType:
return true
case varDecl, funcDecl:
return false
@@ -2466,8 +2488,19 @@ func isCall(n *node) bool {
return n.action == aCall || n.action == aCallSlice
}
func isBinCall(n *node) bool {
return isCall(n) && n.child[0].typ.cat == valueT && n.child[0].typ.rtype.Kind() == reflect.Func
func isBinCall(n *node, sc *scope) bool {
if !isCall(n) || len(n.child) == 0 {
return false
}
c0 := n.child[0]
if c0.typ == nil {
// If called early in parsing, child type may not be known yet.
c0.typ, _ = nodeType(n.interp, sc, c0)
if c0.typ == nil {
return false
}
}
return c0.typ.cat == valueT && c0.typ.rtype.Kind() == reflect.Func
}
func isOffsetof(n *node) bool {
@@ -2573,7 +2606,7 @@ func gotoLabel(s *symbol) {
func compositeGenerator(n *node, typ *itype, rtyp reflect.Type) (gen bltnGenerator) {
switch typ.cat {
case aliasT, ptrT:
gen = compositeGenerator(n, n.typ.val, rtyp)
gen = compositeGenerator(n, typ.val, rtyp)
case arrayT, sliceT:
gen = arrayLit
case mapT:
@@ -2627,23 +2660,54 @@ func compositeGenerator(n *node, typ *itype, rtyp reflect.Type) (gen bltnGenerat
// 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.
func arrayTypeLen(n *node) int {
func arrayTypeLen(n *node, sc *scope) (int, error) {
if n.typ != nil && n.typ.cat == arrayT {
return n.typ.length
return n.typ.length, nil
}
max := -1
for i, c := range n.child[1:] {
r := i
if c.kind == keyValueExpr {
if v := c.child[0].rval; v.IsValid() {
r = int(c.child[0].rval.Int())
for _, c := range n.child[1:] {
var r int
if c.kind != keyValueExpr {
r = max + 1
max = r
continue
}
c0 := c.child[0]
v := c0.rval
if v.IsValid() {
r = int(v.Int())
} else {
// Resolve array key value as a constant.
if c0.kind == identExpr {
// Key is defined by a symbol which must be a constant integer.
sym, _, ok := sc.lookup(c0.ident)
if !ok {
return 0, c0.cfgErrorf("undefined: %s", c0.ident)
}
if sym.kind != constSym {
return 0, c0.cfgErrorf("non-constant array bound %q", c0.ident)
}
r = int(vInt(sym.rval))
} else {
// Key is defined by a numeric constant expression.
if _, err := c0.interp.cfg(c0, sc, sc.pkgID, sc.pkgName); err != nil {
return 0, err
}
cv, ok := c0.rval.Interface().(constant.Value)
if !ok {
return 0, c0.cfgErrorf("non-constant expression")
}
r = constToInt(cv)
}
}
if r > max {
max = r
}
}
return max + 1
return max + 1, nil
}
// isValueUntyped returns true if value is untyped.

84
interp/compile_test.go Normal file
View File

@@ -0,0 +1,84 @@
package interp
import (
"go/ast"
"go/parser"
"go/token"
"testing"
"github.com/traefik/yaegi/stdlib"
)
func TestCompileAST(t *testing.T) {
file, err := parser.ParseFile(token.NewFileSet(), "_.go", `
package main
import "fmt"
type Foo struct{}
var foo Foo
const bar = "asdf"
func main() {
fmt.Println(1)
}
`, 0)
if err != nil {
panic(err)
}
if len(file.Imports) != 1 || len(file.Decls) != 5 {
panic("wrong number of imports or decls")
}
dType := file.Decls[1].(*ast.GenDecl)
dVar := file.Decls[2].(*ast.GenDecl)
dConst := file.Decls[3].(*ast.GenDecl)
dFunc := file.Decls[4].(*ast.FuncDecl)
if dType.Tok != token.TYPE {
panic("decl[1] is not a type")
}
if dVar.Tok != token.VAR {
panic("decl[2] is not a var")
}
if dConst.Tok != token.CONST {
panic("decl[3] is not a const")
}
cases := []struct {
desc string
node ast.Node
skip string
}{
{desc: "file", node: file},
{desc: "import", node: file.Imports[0]},
{desc: "type", node: dType},
{desc: "var", node: dVar, skip: "not supported"},
{desc: "const", node: dConst},
{desc: "func", node: dFunc},
{desc: "block", node: dFunc.Body},
{desc: "expr", node: dFunc.Body.List[0]},
}
i := New(Options{})
_ = i.Use(stdlib.Symbols)
for _, c := range cases {
t.Run(c.desc, func(t *testing.T) {
if c.skip != "" {
t.Skip(c.skip)
}
i := i
if _, ok := c.node.(*ast.File); ok {
i = New(Options{})
_ = i.Use(stdlib.Symbols)
}
_, err := i.CompileAST(c.node)
if err != nil {
t.Fatalf("Failed to compile %s: %v", c.desc, err)
}
})
}
}

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