Compare commits

...

71 Commits

Author SHA1 Message Date
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
Marc Vertes
f76db27c77 interp: fix resolution of methods on aliased types
The type.val field was always pointing to the final underlying type
for aliased types, defeating a possible match if a method was
attached to a type in between. Now the complete chain of aliases
is always preserved.

We have added an underlying() itype method which returns the underlying
type of a defined type (aliasT), even in the presence of multiple
indirections.

We have added a definedType function which checks if type t1 is
defined from type t2 or t2 defined from t1, required when checking
assignability of aliasT types.

Fixes #1411.

PS: this is the 2nd attempt, as the first version #1412 wasn't passing
_test/issue-1408.go as well. This PR does pass and supersedes #1412.
2022-06-14 16:42:09 +02:00
mpl
996b1e33c8 interp: catch mismatched types for other comparisons
The check for mismatched types was already added recently for ==  and != comparisons.
This PR now adds it for other comparisons ( < , <=, > , >=).
2022-06-14 10:52:08 +02:00
Marc Vertes
236a0effaf interp: improve the behaviour of interface{} function parameters
We finally address a long standing limitation of the interpreter:
the capacity to generate the correct interface wrapper for an
anonymous interface{} function parameter of a binary function.

It allows for example fmt.Printf to invoke the String method
of an object defined within the interpreter, or json.Marshal
to invoke a textMarshaler method if it exists and if there is
no Marshaler method already defined for the passed interpreter
object.

To achieve that, we add a new mapType part of the "Used" symbols
to describe what not empty interfaces are expected and in which
priority order. This information can not be guessed and is found
in the related package documentation, then captured in stdlib/maptypes.go.

Then, at compile time and/or during execution, a lookup on mapTypes
is performed to allow the correct wrapper to be generated.

This change adds a new MapType type to the stdlib package.

Fixes #435.
2022-06-14 10:18:08 +02:00
Marc Vertes
eaeb445e17 interp: create interpreter interface value with new
In that case, the interface must be wrapped in an valueInterface
at creation.

With that fix, it is now possible to import
github.com/google/go-querystring/query. Not tested beyond that.

Fixes #1123.
2022-06-13 11:36:09 +02:00
Marc Vertes
6933ba2b4e interp: improve type checking for defined types
Fixes #1408.
2022-06-13 11:24:09 +02:00
Marc Vertes
a61a7d5bcd interp: avoid panic when defining a label in incremental parsing mode
In REPL mode, a panic (stack overflow) could be triggered by:

	$ yaegi
	> a:
	runtime: goroutine stack exceeds 1000000000-byte limit
	runtime: sp=0x14020760330 stack=[0x14020760000, 0x14040760000]
	fatal error: stack overflow
	[...]

This issue occurs in incremental parsing mode only, and not when the parser
is in file mode. We avoid it by being more defensive when generating
values.

Fixes #982.
2022-06-13 11:10:09 +02:00
Marc Vertes
259f64cfd4 interp: fix redeclaration of an interface variable
Fixes #1404.
2022-06-13 10:56:09 +02:00
Marc Vertes
6c74ab7bec interp: allow conversions of untyped complex
For untyped numerical types, conversions to different numerical
types can be allowed if there is no overflow (not checked here).

Fixes #1402.
2022-06-13 10:42:08 +02:00
Marc Vertes
d64563edee interp: improve handling values and comparisons in interfaces
Fixes #1347.
2022-05-23 10:30:08 +02:00
Marc Vertes
07039262a0 interp: implements detection of packages with no Go files
This avoids panic during import, and print a proper diagnostic
instead.

Fixes #1394.
2022-05-19 18:34:08 +02:00
Marc Vertes
4ed9ccb5c4 interp: fix retrieving the string value of an interface
Thanks to @bailsman for providing first insight, in addition to
raising the issue.

Fixes #1342.
2022-05-19 18:20:09 +02:00
Marc Vertes
25edcfee7a interp: fix handling of empty interfaces in map index expressions
There should be no need now to wrap empty interfaces in order to
retrieve its value.

Fixes #1344.
2022-05-19 18:08:09 +02:00
Marc Vertes
d183f4205e interp: improve handling of empty interface values (#1393)
At variable, function parameter, slice, map or field element assign,
if the destination type is an empty interface, the value was never
wrapped into a valueInterface (to preserve type mutability in case
of re-assign). Now we wrap it in a valueInterface if the source
type has a non empty set of methods, to allow a future use as a non
empty interface.

There are still corner cases, but it extends notably the support
of interfaces within the interpreter.

Fixes #1355.
2022-05-19 17:53:56 +02:00
Marc Vertes
821e9ee006 interp: recover interpreter internal panics in EvalWithContext 2022-05-19 17:30:09 +02:00
Marc Vertes
00e3f924c1 interp: fix the behaviour of goto, continue and break (#1392)
The logic of goto was false due to mixing break label and goto
label, despite being opposite. In case of break, jump to node (the
exit point) instead of node.start. Also always define label symbols
before their use is parsed.

* Fix continue label, detect invalid labels for break and continue

* fix multiple goto, break, continue to the same label

Fixes #1354.
2022-05-19 11:23:30 +02:00
Marc Vertes
2248851d77 interp: fix creation of binary composite types (#1391)
* interp: fix creation of binary composite types

Use direct assignment instead of reflect.Value Set method to
initialize a binary composite type, as for non binary types.
It ensures that a new reflect.Value is stored in the frame
instead of modifying a possibly existing one, which can defeat
the purpose of initializing variables in a body loop.

While there, remove the need to have and use a mutex on types.

Fixes #1381.

* review: rework a bit the test

Co-authored-by: mpl <mathieu.lonjaret@gmail.com>
2022-05-05 21:31:10 +02:00
Marc Vertes
f74d1ea6d8 interp: detect invalid uses of _ as value
We now detect the use of special identifier _ (blank) during parsing in order to abort compiling early. It allows to not panic later during execution. We must catch all the cases where blank is used as a value, but still preserve the cases where it is assigned, used as a struct field or for import side effects.

Fixes #1386.
2022-05-04 18:51:09 +02:00
Marc Vertes
606b4c3a37 interp: fix import of binary type symbols in current scope (#1380)
Fixes #1360.
2022-05-04 17:27:11 +02:00
Ethan Reesor
4e77fc9436 interp: delete incomplete type on pkg import
When a package is imported, it creates a symbol with a name like "errors/_.go". If a statement such as x := errors.New(...) is executed before import "errors", it creates an incomplete type symbol with a name like "errors". Importing the package after the incomplete type symbol has been created does not fix the compile issue because the incomplete type still exists.

To fix this, this PR deletes the incomplete type symbol, if one exists.

Closes #1388.
2022-04-26 11:04:13 +02:00
Ethan Reesor
ad9db379e7 interp: add a function to get globals (#1387)
Co-authored-by: Marc Vertes <mvertes@free.fr>
2022-04-25 16:11:18 +02:00
Ethan Reesor
7be17d393f interp: expose fset to fix CompileAST issue
The interpreter has its own internal fileset and expects all code to have been parsed using that fileset. If a user creates a fileset, calls `go/parser.Parse*`, then passes the result to `interp.CompileAST`, strange things can happen.

The solutions I can see are:

1. Expose the fileset so the user can use it when parsing source.
2. Add the fileset as an option (input to New) so that the user can tell the interpreter to use a specific fileset.
3. Pass the fileset into `CompileAST`

There are two ways to implement option 3. One is to add a field to nodes and update every reference to `interp.fset` to use `node.fset`. The other is to add a parameter to every function that references `interp.fset` or calls a function that does. Both of these are significant changes and involve an extra pointer for every node or most function calls.

Options 1 and 2 are easy. Option 2 involves adding an option so I went with option 1. I can imagine situations where option 2 could be necessary, but I can open another issue/PR if and when I need that.

Fixes #1383
2022-04-22 11:48:08 +02:00
cclerget
5665c9a410 interp: fix redeclaration scope issue
Fixes #1378
2022-04-09 15:28:07 +02:00
cclerget
1cf9d345aa Ignore private methods for binary types during type assertion
Fixes #1373
2022-04-07 18:16:08 +02:00
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
664 changed files with 18391 additions and 8192 deletions

View File

@@ -17,7 +17,7 @@ jobs:
strategy:
matrix:
go-version: [ 1.16, 1.17 ]
go-version: [ 1.18, 1.19 ]
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

View File

@@ -7,8 +7,8 @@ on:
pull_request:
env:
GO_VERSION: 1.17
GOLANGCI_LINT_VERSION: v1.41.1
GO_VERSION: 1.18
GOLANGCI_LINT_VERSION: v1.47.1
jobs:
@@ -45,12 +45,13 @@ jobs:
needs: linting
strategy:
matrix:
go-version: [ 1.16, 1.17 ]
go-version: [ 1.18, 1.19 ]
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.18, 1.19 ]
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

View File

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

View File

@@ -27,6 +27,7 @@
"scopelint", # deprecated
"interfacer", # deprecated
"maligned", # deprecated
"exhaustivestruct", # deprecated
"lll",
"gas",
"dupl",
@@ -49,10 +50,15 @@
"wrapcheck",
"nestif",
"exhaustive",
"exhaustivestruct",
"exhaustruct",
"forbidigo",
"ifshort",
"forcetypeassert",
"varnamelen",
"nosnakecase",
"nonamedreturns",
"nilnil",
"maintidx",
"errorlint", # TODO: must be reactivate before fixes
]
@@ -75,6 +81,12 @@
[[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"]
[[issues.exclude-rules]]
path = "interp/debugger.go"
linters = ["containedctx"]

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.16 and Go 1.17 (the latest 2 major releases)
* Support Go 1.18 and Go 1.19 (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

View File

@@ -1,5 +1,10 @@
package alias3
import (
"fmt"
"net/http"
)
type T struct {
A string
}
@@ -7,3 +12,20 @@ type T struct {
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
}

39
_test/alias4.go Normal file
View File

@@ -0,0 +1,39 @@
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 (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

24
_test/break0.go Normal file
View File

@@ -0,0 +1,24 @@
package main
func main() {
n := 2
m := 2
foo := true
OuterLoop:
println("Boo")
for i := 0; i < n; i++ {
println("I: ", i)
for j := 0; j < m; j++ {
switch foo {
case true:
println(foo)
break OuterLoop
case false:
println(foo)
}
}
}
}
// Error:
// 15:5: invalid break label OuterLoop

24
_test/break1.go Normal file
View File

@@ -0,0 +1,24 @@
package main
func main() {
n := 2
m := 2
foo := true
OuterLoop:
for i := 0; i < n; i++ {
println("I: ", i)
for j := 0; j < m; j++ {
switch foo {
case true:
println(foo)
break OuterLoop
case false:
println(foo)
}
}
}
}
// Output:
// I: 0
// true

25
_test/break2.go Normal file
View File

@@ -0,0 +1,25 @@
package main
func main() {
n := 2
m := 2
foo := true
OuterLoop:
for i := 0; i < n; i++ {
println("I: ", i)
for j := 0; j < m; j++ {
switch foo {
case true:
println(foo)
break OuterLoop
case false:
println(foo)
continue OuterLoop
}
}
}
}
// Output:
// I: 0
// true

27
_test/break3.go Normal file
View File

@@ -0,0 +1,27 @@
package main
func main() {
n := 2
m := 2
foo := true
goto OuterLoop
println("Boo")
OuterLoop:
for i := 0; i < n; i++ {
println("I: ", i)
for j := 0; j < m; j++ {
switch foo {
case true:
println(foo)
break OuterLoop
case false:
println(foo)
goto OuterLoop
}
}
}
}
// Output:
// I: 0
// true

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)
}

26
_test/cont2.go Normal file
View File

@@ -0,0 +1,26 @@
package main
func main() {
n := 2
m := 2
foo := true
OuterLoop:
for i := 0; i < n; i++ {
println("I: ", i)
for j := 0; j < m; j++ {
switch foo {
case true:
println(foo)
continue OuterLoop
case false:
println(foo)
}
}
}
}
// Output:
// I: 0
// true
// I: 1
// true

24
_test/cont3.go Normal file
View File

@@ -0,0 +1,24 @@
package main
func main() {
n := 2
m := 2
foo := true
OuterLoop:
println("boo")
for i := 0; i < n; i++ {
println("I: ", i)
for j := 0; j < m; j++ {
switch foo {
case true:
println(foo)
continue OuterLoop
case false:
println(foo)
}
}
}
}
// Error:
// 15:5: invalid continue label OuterLoop

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)
}

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))
}

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

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

@@ -2,7 +2,6 @@ package main
import (
"io"
"io/ioutil"
"os"
)
@@ -13,7 +12,7 @@ type sink interface {
func newSink() sink {
// return os.Stdout // Stdout is special in yaegi tests
file, err := ioutil.TempFile("", "yaegi-test.*")
file, err := os.CreateTemp("", "yaegi-test.*")
if err != nil {
panic(err)
}

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

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

@@ -0,0 +1,12 @@
package main
import "fmt"
func main() {
var a interface{}
a = "a"
fmt.Println(a, a == "a")
}
// Output:
// a true

13
_test/issue-1344.go Normal file
View File

@@ -0,0 +1,13 @@
package main
import "fmt"
func main() {
var m = map[string]interface{}{"a": "a"}
a, _ := m["a"]
b, ok := a.(string)
fmt.Println("a:", a, ", b:", b, ", ok:", ok)
}
// Output:
// a: a , b: a , ok: true

21
_test/issue-1354.go Normal file
View File

@@ -0,0 +1,21 @@
package main
func main() {
println(test()) // Go prints true, Yaegi false
}
func test() bool {
if true {
goto label
}
goto label
label:
println("Go continues here")
return true
println("Yaegi goes straight to this return (this line is never printed)")
return false
}
// Output:
// Go continues here
// true

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

@@ -0,0 +1,22 @@
package main
import "github.com/traefik/yaegi/_test/p2"
func f(i interface{}) {
_, ok := i.(p2.I)
println("ok:", ok)
}
func main() {
var v *p2.T
var i interface{}
i = v
_, ok := i.(p2.I)
println("ok:", ok)
f(v)
}
// Output:
// ok: true
// ok: true

14
_test/issue-1360.go Normal file
View File

@@ -0,0 +1,14 @@
package main
import (
"fmt"
. "net"
)
func main() {
v := IP{}
fmt.Println(v)
}
// Output:
// <nil>

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}

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

@@ -0,0 +1,17 @@
package main
import (
"fmt"
"go/ast"
)
func NewBadExpr() ast.Expr {
return &ast.BadExpr{}
}
func main() {
fmt.Printf("%T\n", NewBadExpr().(*ast.BadExpr))
}
// Output:
// *ast.BadExpr

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

21
_test/issue-1378.go Normal file
View File

@@ -0,0 +1,21 @@
package main
import (
"fmt"
"time"
)
func main() {
t, err := time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
if err != nil {
panic(err)
}
fn := func() error {
_, err := t.GobEncode()
return err
}
fmt.Println(fn())
}
// Output:
// <nil>

58
_test/issue-1381.go Normal file
View File

@@ -0,0 +1,58 @@
package main
import (
"bytes"
"fmt"
)
func main() {
var bufPtrOne *bytes.Buffer
var bufPtrTwo *bytes.Buffer
var bufPtrThree *bytes.Buffer
var bufPtrFour *bytes.Buffer
for i := 0; i < 2; i++ {
bufOne := bytes.Buffer{}
bufTwo := &bytes.Buffer{}
var bufThree bytes.Buffer
bufFour := new(bytes.Buffer)
if bufPtrOne == nil {
bufPtrOne = &bufOne
} else if bufPtrOne == &bufOne {
fmt.Println("bufOne was not properly redeclared")
} else {
fmt.Println("bufOne is properly redeclared")
}
if bufPtrTwo == nil {
bufPtrTwo = bufTwo
} else if bufPtrTwo == bufTwo {
fmt.Println("bufTwo was not properly redeclared")
} else {
fmt.Println("bufTwo is properly redeclared")
}
if bufPtrThree == nil {
bufPtrThree = &bufThree
} else if bufPtrThree == &bufThree {
fmt.Println("bufThree was not properly redeclared")
} else {
fmt.Println("bufThree is properly redeclared")
}
if bufPtrFour == nil {
bufPtrFour = bufFour
} else if bufPtrFour == bufFour {
fmt.Println("bufFour was not properly redeclared")
} else {
fmt.Println("bufFour is properly redeclared")
}
}
}
// Output:
// bufOne is properly redeclared
// bufTwo is properly redeclared
// bufThree is properly redeclared
// bufFour is properly redeclared

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

@@ -0,0 +1,22 @@
package main
type I interface {
inI()
}
type T struct {
name string
}
func (t *T) inI() {}
func main() {
var i I = &T{name: "foo"}
if i, ok := i.(*T); ok {
println(i.name)
}
}
// Output:
// foo

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

@@ -0,0 +1,16 @@
package main
type (
Number = int32
Number2 = Number
)
func f(n Number2) { println(n) }
func main() {
var n Number = 5
f(n)
}
// Output:
// 5

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

@@ -0,0 +1,16 @@
package main
type Number int32
func (n Number) IsValid() bool { return true }
type Number1 = Number
type Number2 = Number1
func main() {
a := Number2(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

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

@@ -0,0 +1,25 @@
package main
import (
"fmt"
"strconv"
)
type Foo int
func (f Foo) String() string {
return "foo-" + strconv.Itoa(int(f))
}
func print1(arg interface{}) {
fmt.Println(arg)
}
func main() {
var arg Foo = 3
var f = print1
f(arg)
}
// Output:
// foo-3

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

@@ -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())
}

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

@@ -0,0 +1,9 @@
package p2
type I interface {
isI()
}
type T struct{}
func (t *T) isI() {}

0
_test/p3/empty Normal file
View File

View File

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

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

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,7 +3,6 @@ package main
import (
"bytes"
"context"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
@@ -38,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)
}

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

@@ -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}}
`
@@ -187,6 +194,11 @@ func (e *Extractor) genContent(importPath string, p *types.Package) ([]byte, err
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 {
var methods []Method
@@ -270,6 +282,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 +357,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 +468,7 @@ func GetMinor(part string) string {
return minor
}
const defaultMinorVersion = 17
const defaultMinorVersion = 19
func genBuildTags() (string, error) {
version := runtime.Version()

2
go.mod
View File

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

View File

@@ -3,8 +3,8 @@ package main
import (
"bytes"
"go/format"
"io/ioutil"
"log"
"os"
"strings"
"text/template"
)
@@ -535,6 +535,7 @@ func {{$name}}(n *node) {
typ := n.typ.concrete().TypeOf()
isInterface := n.typ.TypeOf().Kind() == reflect.Interface
c0, c1 := n.child[0], n.child[1]
t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf()
{{- if or (eq $op.Name "==") (eq $op.Name "!=") }}
@@ -557,7 +558,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
}
@@ -579,7 +580,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
}
@@ -602,7 +603,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
}
@@ -621,9 +622,39 @@ func {{$name}}(n *node) {
}
return
}
// Do not attempt to optimize '==' or '!=' if an operand is an interface.
// This will preserve proper dynamic type checking at runtime. For static types,
// type checks are already performed, so bypass them if possible.
if t0.Kind() == reflect.Interface || t1.Kind() == reflect.Interface {
v0 := genValue(c0)
v1 := genValue(c1)
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
i1 := v1(f).Interface()
if i0 {{$op.Name}} i1 {
dest(f).SetBool(true)
return tnext
}
dest(f).SetBool(false)
return fnext
}
} else {
dest := genValue(n)
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
i1 := v1(f).Interface()
dest(f).SetBool(i0 {{$op.Name}} i1)
return tnext
}
}
return
}
{{- end}}
switch t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf(); {
switch {
case isString(t0) || isString(t1):
switch {
case isInterface:
@@ -1183,7 +1214,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

@@ -10,8 +10,17 @@ 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 {
_ [48]byte
_ uintptr
_ uintptr
_ uint32
_ uint32
_ uintptr
_ uintptr
_ uint32
_ uint32
}
type emptyInterface struct {
@@ -20,21 +29,21 @@ type emptyInterface struct {
}
type structField struct {
_ int64
_ uintptr
typ *rtype
_ uintptr
}
type structType struct {
rtype
_ int64
_ uintptr
fields []structField
}
// SwapFieldType swaps the type of the struct field with the given type.
// 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 SwapFieldType(s reflect.Type, idx int, t reflect.Type) {
func SetFieldType(s reflect.Type, idx int, t reflect.Type) {
if s.Kind() != reflect.Struct || idx >= s.NumField() {
return
}

View File

@@ -25,7 +25,7 @@ func TestSwapFieldType(t *testing.T) {
typ := reflect.StructOf(f)
ntyp := reflect.PtrTo(typ)
unsafe2.SwapFieldType(typ, 1, ntyp)
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"
@@ -73,6 +72,7 @@ const (
importSpec
incDecStmt
indexExpr
indexListExpr
interfaceType
keyValueExpr
labeledStmt
@@ -93,7 +93,8 @@ const (
switchIfStmt
typeAssertExpr
typeDecl
typeSpec
typeSpec // type A int
typeSpecAssign // type A = int
typeSwitch
unaryExpr
valueSpec
@@ -155,6 +156,7 @@ var kinds = [...]string{
importSpec: "importSpec",
incDecStmt: "incDecStmt",
indexExpr: "indexExpr",
indexListExpr: "indexListExpr",
interfaceType: "interfaceType",
keyValueExpr: "keyValueExpr",
labeledStmt: "labeledStmt",
@@ -176,6 +178,7 @@ var kinds = [...]string{
typeAssertExpr: "typeAssertExpr",
typeDecl: "typeDecl",
typeSpec: "typeSpec",
typeSpecAssign: "typeSpecAssign",
typeSwitch: "typeSwitch",
unaryExpr: "unaryExpr",
valueSpec: "valueSpec",
@@ -281,6 +284,7 @@ var actions = [...]string{
aDec: "--",
aEqual: "==",
aGreater: ">",
aGreaterEqual: ">=",
aGetFunc: "getFunc",
aGetIndex: "getIndex",
aGetMethod: "getMethod",
@@ -289,6 +293,7 @@ var actions = [...]string{
aLand: "&&",
aLor: "||",
aLower: "<",
aLowerEqual: "<=",
aMethod: "Method",
aMul: "*",
aMulAssign: "*=",
@@ -360,21 +365,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 +389,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 +408,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 +641,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:
@@ -682,7 +696,7 @@ func (interp *Interpreter) ast(src, name string, inc bool) (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)
@@ -694,7 +708,13 @@ func (interp *Interpreter) ast(src, name string, inc bool) (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
@@ -764,6 +784,9 @@ func (interp *Interpreter) ast(src, name string, inc bool) (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)
@@ -826,6 +849,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:
@@ -892,15 +919,7 @@ 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

@@ -49,8 +49,10 @@ 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, pkgName string) ([]*node, error) {
sc := interp.initScopePkg(importPath, pkgName)
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, pkgName string) ([]*node,
if err != nil {
return false
}
if n.scope == nil {
n.scope = sc
}
switch n.kind {
case binaryExpr, unaryExpr, parenExpr:
if isBoolAction(n) {
@@ -140,8 +145,11 @@ func (interp *Interpreter) cfg(root *node, importPath, pkgName string) ([]*node,
}
switch o.typ.cat {
case valueT:
case valueT, aliasT:
typ := o.typ.rtype
if o.typ.cat == aliasT {
typ = o.typ.val.TypeOf()
}
switch typ.Kind() {
case reflect.Map:
n.anc.gen = rangeMap
@@ -197,38 +205,42 @@ func (interp *Interpreter) cfg(root *node, importPath, pkgName string) ([]*node,
}
}
}
n.findex = -1
n.val = nil
sc = sc.pushBloc()
// Pre-define symbols for labels defined in this block, so we are sure that
// they are already defined when met.
// TODO(marc): labels must be stored outside of symbols to avoid collisions.
for _, c := range n.child {
if c.kind != labeledStmt {
continue
}
label := c.child[0].ident
sym := &symbol{kind: labelSym, node: c, index: -1}
sc.sym[label] = sym
c.sym = sym
}
case breakStmt, continueStmt, gotoStmt:
if len(n.child) > 0 {
// Handle labeled statements.
label := n.child[0].ident
if sym, _, ok := sc.lookup(label); ok {
if sym.kind != labelSym {
err = n.child[0].cfgErrorf("label %s not defined", label)
break
}
sym.from = append(sym.from, n)
n.sym = sym
} else {
n.sym = &symbol{kind: labelSym, from: []*node{n}, index: -1}
sc.sym[label] = n.sym
}
if len(n.child) == 0 {
break
}
case labeledStmt:
// Handle labeled statements.
label := n.child[0].ident
// TODO(marc): labels must be stored outside of symbols to avoid collisions
// Used labels are searched in current and sub scopes, not upper ones.
if sym, ok := sc.lookdown(label); ok {
sym.node = n
if sym, _, ok := sc.lookup(label); ok {
if sym.kind != labelSym {
err = n.child[0].cfgErrorf("label %s not defined", label)
break
}
n.sym = sym
} else {
n.sym = &symbol{kind: labelSym, node: n, index: -1}
n.sym = &symbol{kind: labelSym, index: -1}
sc.sym[label] = n.sym
}
if n.kind == gotoStmt {
n.sym.from = append(n.sym.from, n) // To allow forward goto statements.
}
sc.sym[label] = n.sym
case caseClause:
sc = sc.pushBloc()
@@ -284,14 +296,14 @@ func (interp *Interpreter) cfg(root *node, importPath, pkgName string) ([]*node,
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)
// 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 {
@@ -314,6 +326,10 @@ func (interp *Interpreter) cfg(root *node, importPath, pkgName string) ([]*node,
}
// Propagate type to children, to handle implicit types
for _, c := range child {
if isBlank(c) {
err = n.cfgErrorf("cannot use _ as value")
return false
}
switch c.kind {
case binaryExpr, unaryExpr, compositeLitExpr:
// Do not attempt to propagate composite type to operator expressions,
@@ -344,16 +360,55 @@ func (interp *Interpreter) cfg(root *node, importPath, pkgName string) ([]*node,
fallthrough
case funcDecl:
// Do not allow function declarations without body.
if len(n.child) < 4 {
err = n.cfgErrorf("function declaration without body is unsupported (linkname or assembly can not be interpreted).")
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
@@ -367,8 +422,9 @@ func (interp *Interpreter) cfg(root *node, importPath, pkgName string) ([]*node,
}
}
}
// 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()
@@ -383,8 +439,9 @@ func (interp *Interpreter) cfg(root *node, importPath, pkgName string) ([]*node,
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
@@ -393,6 +450,7 @@ func (interp *Interpreter) cfg(root *node, importPath, pkgName string) ([]*node,
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)
}
@@ -410,11 +468,11 @@ func (interp *Interpreter) cfg(root *node, importPath, pkgName string) ([]*node,
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,8 +482,11 @@ func (interp *Interpreter) cfg(root *node, importPath, pkgName string) ([]*node,
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 {
@@ -443,7 +504,7 @@ func (interp *Interpreter) cfg(root *node, importPath, pkgName string) ([]*node,
// values which may be used in further declarations.
if !sc.global {
for _, c := range n.child {
if _, err = interp.cfg(c, importPath, pkgName); err != nil {
if _, err = interp.cfg(c, sc, importPath, pkgName); err != nil {
// No error processing here, to allow recovery in subtree nodes.
err = nil
}
@@ -470,6 +531,10 @@ func (interp *Interpreter) cfg(root *node, importPath, pkgName string) ([]*node,
switch n.kind {
case addressExpr:
if isBlank(n.child[0]) {
err = n.cfgErrorf("cannot use _ as value")
break
}
wireChild(n)
err = check.addressExpr(n)
@@ -505,6 +570,11 @@ func (interp *Interpreter) cfg(root *node, importPath, pkgName string) ([]*node,
updateSym := false
var sym *symbol
var level int
if isBlank(src) {
err = n.cfgErrorf("cannot use _ as value")
break
}
if n.kind == defineStmt || (n.kind == assignStmt && dest.ident == "_") {
if atyp != nil {
dest.typ = atyp
@@ -594,8 +664,8 @@ func (interp *Interpreter) cfg(root *node, importPath, pkgName string) ([]*node,
// 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
@@ -637,6 +707,10 @@ func (interp *Interpreter) cfg(root *node, importPath, pkgName string) ([]*node,
}
case incDecStmt:
err = check.unaryExpr(n)
if err != nil {
break
}
wireChild(n)
n.findex = n.child[0].findex
n.level = n.child[0].level
@@ -657,7 +731,12 @@ func (interp *Interpreter) cfg(root *node, importPath, pkgName string) ([]*node,
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
@@ -750,15 +829,16 @@ func (interp *Interpreter) cfg(root *node, importPath, pkgName string) ([]*node,
}
case indexExpr:
if isBlank(n.child[0]) {
err = n.cfgErrorf("cannot use _ as value")
break
}
wireChild(n)
t := n.child[0].typ
for t.cat == aliasT {
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 {
@@ -774,6 +854,49 @@ func (interp *Interpreter) cfg(root *node, importPath, pkgName string) ([]*node,
} 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, err := genAST(sc, t.node.anc, []*node{c1})
if err != nil {
return
}
if _, err = interp.cfg(g, nil, 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
}
@@ -840,32 +963,88 @@ func (interp *Interpreter) cfg(root *node, importPath, pkgName string) ([]*node,
n.rval = l.rval
case breakStmt:
if len(n.child) > 0 {
gotoLabel(n.sym)
} else {
if len(n.child) == 0 {
n.tnext = sc.loop
break
}
if !n.hasAnc(n.sym.node) {
err = n.cfgErrorf("invalid break label %s", n.child[0].ident)
break
}
n.tnext = n.sym.node
case continueStmt:
if len(n.child) > 0 {
gotoLabel(n.sym)
} else {
if len(n.child) == 0 {
n.tnext = sc.loopRestart
break
}
if !n.hasAnc(n.sym.node) {
err = n.cfgErrorf("invalid continue label %s", n.child[0].ident)
break
}
n.tnext = n.sym.node.child[1].lastChild().start
case gotoStmt:
gotoLabel(n.sym)
if n.sym.node == nil {
// It can be only due to a forward goto, to be resolved at labeledStmt.
// Invalid goto labels are catched at AST parsing.
break
}
n.tnext = n.sym.node.start
case labeledStmt:
wireChild(n)
n.start = n.child[1].start
gotoLabel(n.sym)
if len(n.child) > 1 {
n.start = n.child[1].start
}
for _, c := range n.sym.from {
c.tnext = n.start // Resolve forward goto.
}
case callExpr:
for _, c := range n.child {
if isBlank(c) {
err = n.cfgErrorf("cannot use _ as value")
return
}
}
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
g, err := genAST(sc, fun, c0.child[1:])
if err != nil {
return
}
_, err = interp.cfg(g, nil, 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 {
@@ -885,7 +1064,11 @@ func (interp *Interpreter) cfg(root *node, importPath, pkgName string) ([]*node,
// 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 +1077,11 @@ func (interp *Interpreter) cfg(root *node, importPath, pkgName string) ([]*node,
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:
@@ -908,9 +1095,10 @@ func (interp *Interpreter) cfg(root *node, importPath, pkgName string) ([]*node,
if op, ok := constBltn[bname]; ok && n.anc.action != aAssign {
op(n) // pre-compute non-assigned constant :
}
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())
@@ -955,16 +1143,17 @@ func (interp *Interpreter) cfg(root *node, importPath, pkgName string) ([]*node,
n.typ = c0.typ
n.findex = sc.add(n.typ)
}
case isBinCall(n):
err = check.arguments(n, n.child[1:], n.child[0], n.action == aCallSlice)
case isBinCall(n, sc):
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]
@@ -984,7 +1173,8 @@ func (interp *Interpreter) cfg(root *node, importPath, pkgName string) ([]*node,
}
}
}
case isOffsetof(n):
case isOffsetof(c0):
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
@@ -998,17 +1188,45 @@ func (interp *Interpreter) cfg(root *node, importPath, pkgName string) ([]*node,
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 []*node
// 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, err = genAST(sc, fun, types); err != nil {
break
}
// Compile the generated function AST, so it becomes part of the scope.
if _, err = interp.cfg(g, nil, 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.
@@ -1226,7 +1444,7 @@ func (interp *Interpreter) cfg(root *node, importPath, pkgName string) ([]*node,
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
@@ -1369,9 +1587,17 @@ func (interp *Interpreter) cfg(root *node, importPath, pkgName string) ([]*node,
sc = sc.pop()
case keyValueExpr:
if isBlank(n.child[1]) {
err = n.cfgErrorf("cannot use _ as value")
break
}
wireChild(n)
case landExpr:
if isBlank(n.child[0]) || isBlank(n.child[1]) {
err = n.cfgErrorf("cannot use _ as value")
break
}
n.start = n.child[0].start
n.child[0].tnext = n.child[1].start
setFNext(n.child[0], n)
@@ -1383,6 +1609,10 @@ func (interp *Interpreter) cfg(root *node, importPath, pkgName string) ([]*node,
}
case lorExpr:
if isBlank(n.child[0]) || isBlank(n.child[1]) {
err = n.cfgErrorf("cannot use _ as value")
break
}
n.start = n.child[0].start
n.child[0].tnext = n
setFNext(n.child[0], n.child[1].start)
@@ -1428,6 +1658,12 @@ func (interp *Interpreter) cfg(root *node, importPath, pkgName string) ([]*node,
err = n.cfgErrorf("too many arguments to return")
break
}
for _, c := range n.child {
if isBlank(c) {
err = n.cfgErrorf("cannot use _ as value")
return
}
}
returnSig := sc.def.child[2]
if mustReturnValue(returnSig) {
nret := len(n.child)
@@ -1444,7 +1680,7 @@ func (interp *Interpreter) cfg(root *node, importPath, pkgName string) ([]*node,
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
}
@@ -1575,7 +1811,7 @@ func (interp *Interpreter) cfg(root *node, importPath, pkgName string) ([]*node,
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
hasRecvType := n.typ.TypeOf().Kind() != reflect.Interface
n.val = method.Index
n.gen = getIndexBinMethod
n.action = aGetMethod
@@ -1584,7 +1820,7 @@ func (interp *Interpreter) cfg(root *node, importPath, pkgName string) ([]*node,
if hasRecvType {
n.typ.recv = n.typ
}
case n.typ.rtype.Kind() == reflect.Ptr:
case n.typ.TypeOf().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
@@ -1592,7 +1828,7 @@ func (interp *Interpreter) cfg(root *node, importPath, pkgName string) ([]*node,
break
}
err = n.cfgErrorf("undefined field or method: %s", n.child[1].ident)
case n.typ.rtype.Kind() == reflect.Struct:
case n.typ.TypeOf().Kind() == reflect.Struct:
if field, ok := n.typ.rtype.FieldByName(n.child[1].ident); ok {
n.typ = valueTOf(field.Type)
n.val = field.Index
@@ -1637,7 +1873,7 @@ func (interp *Interpreter) cfg(root *node, importPath, pkgName string) ([]*node,
} 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
// Handle method as a function with receiver in 1st argument.
n.val = m
n.findex = notInFrame
n.gen = nop
@@ -1645,7 +1881,7 @@ func (interp *Interpreter) cfg(root *node, importPath, pkgName string) ([]*node,
*n.typ = *m.typ
n.typ.arg = append([]*itype{n.child[0].typ}, m.typ.arg...)
} else {
// Handle method with receiver
// Handle method with receiver.
n.gen = getMethod
n.val = m
n.typ = m.typ
@@ -1719,6 +1955,10 @@ func (interp *Interpreter) cfg(root *node, importPath, pkgName string) ([]*node,
}
case starExpr:
if isBlank(n.child[0]) {
err = n.cfgErrorf("cannot use _ as value")
break
}
switch {
case n.anc.kind == defineStmt && len(n.anc.child) == 3 && n.anc.child[1] == n:
// pointer type expression in a var definition
@@ -1809,6 +2049,7 @@ func (interp *Interpreter) cfg(root *node, importPath, pkgName string) ([]*node,
setFNext(c, clauses[i+1])
}
}
sbn.start = clauses[0].start
n.start = n.child[0].start
n.child[0].tnext = sbn.start
@@ -1863,6 +2104,10 @@ func (interp *Interpreter) cfg(root *node, importPath, pkgName string) ([]*node,
wireChild(n)
c0, c1 := n.child[0], n.child[1]
if isBlank(c0) || isBlank(c1) {
err = n.cfgErrorf("cannot use _ as value")
break
}
if c1.typ == nil {
if c1.typ, err = nodeType(interp, sc, c1); err != nil {
return
@@ -1998,13 +2243,18 @@ func compDefineX(sc *scope, n *node) error {
} 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"))
@@ -2032,12 +2282,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, 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.
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
}
@@ -2241,6 +2497,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
}
@@ -2391,6 +2651,15 @@ func (n *node) fieldType(m int) *node {
// lastChild returns the last child of a node.
func (n *node) lastChild() *node { return n.child[len(n.child)-1] }
func (n *node) hasAnc(nod *node) bool {
for a := n.anc; a != nil; a = a.anc {
if a == nod {
return true
}
}
return false
}
func isKey(n *node) bool {
return n.anc.kind == fileStmt ||
(n.anc.kind == selectorExpr && n.anc.child[0] != n) ||
@@ -2403,11 +2672,22 @@ 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 {
switch anc.kind {
case constDecl, typeDecl:
case constDecl, typeDecl, arrayType, chanType:
return true
case varDecl, funcDecl:
return false
@@ -2453,19 +2733,30 @@ 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 {
return isCall(n) && n.child[0].typ.cat == valueT && n.child[0].rval.String() == "Offsetof"
return n.typ != nil && n.typ.cat == valueT && n.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
}
@@ -2546,21 +2837,10 @@ func typeSwichAssign(n *node) bool {
return ts.kind == typeSwitch && ts.child[1].action == aAssign
}
func gotoLabel(s *symbol) {
if s.node == nil {
return
}
for _, c := range s.from {
if c.tnext == nil {
c.tnext = s.node.start
}
}
}
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:
@@ -2584,11 +2864,7 @@ func compositeGenerator(n *node, typ *itype, rtyp reflect.Type) (gen bltnGenerat
}
case valueT:
if rtyp == nil {
rtyp = n.typ.rtype
}
// TODO(mpl): I do not understand where this side-effect is coming from, and why it happens. quickfix for now.
if rtyp == nil {
rtyp = n.typ.val.rtype
rtyp = n.typ.TypeOf()
}
switch k := rtyp.Kind(); k {
case reflect.Struct:
@@ -2614,23 +2890,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.
@@ -2658,3 +2965,10 @@ func isBoolAction(n *node) bool {
}
return false
}
func isBlank(n *node) bool {
if n.kind == parenExpr && len(n.child) > 0 {
return isBlank(n.child[0])
}
return n.ident == "_"
}

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