Compare commits

..

56 Commits

Author SHA1 Message Date
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
282 changed files with 6089 additions and 1490 deletions

View File

@@ -8,7 +8,7 @@ on:
env:
GO_VERSION: 1.17
GOLANGCI_LINT_VERSION: v1.41.1
GOLANGCI_LINT_VERSION: v1.42.1
jobs:

View File

@@ -75,6 +75,9 @@
[[issues.exclude-rules]]
path = "interp/interp.go"
text = "`out` can be `io.Writer`"
[[issues.exclude-rules]]
path = "interp/interp.go"
text = "`Panic` should conform to the `XxxError` format"
[[issues.exclude-rules]]
path = "interp/interp_eval_test.go"
linters = ["thelper"]

View File

@@ -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

27
_test/alias4.go Normal file
View File

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

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

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

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

@@ -80,7 +80,14 @@ func init() {
{{end}}
}
{{range $m := $value.Method -}}
func (W {{$value.Name}}) {{$m.Name}}{{$m.Param}} {{$m.Result}} { {{$m.Ret}} W.W{{$m.Name}}{{$m.Arg}} }
func (W {{$value.Name}}) {{$m.Name}}{{$m.Param}} {{$m.Result}} {
{{- if eq $m.Name "String"}}
if W.WString == nil {
return ""
}
{{end -}}
{{$m.Ret}} W.W{{$m.Name}}{{$m.Arg}}
}
{{end}}
{{end}}
`
@@ -270,6 +277,15 @@ func (e *Extractor) genContent(importPath string, p *types.Package) ([]byte, err
}
}
for _, t := range e.Tag {
if len(t) != 0 {
buildTags += "," + t
}
}
if len(buildTags) != 0 && buildTags[0] == ',' {
buildTags = buildTags[1:]
}
b := new(bytes.Buffer)
data := map[string]interface{}{
"Dest": e.Dest,
@@ -336,6 +352,7 @@ type Extractor struct {
License string // License text to be included in the created package, optional.
Exclude []string // Comma separated list of regexp matching symbols to exclude.
Include []string // Comma separated list of regexp matching symbols to include.
Tag []string // Comma separated of build tags to be added to the created package.
}
// importPath checks whether pkgIdent is an existing directory relative to

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"
@@ -93,7 +92,8 @@ const (
switchIfStmt
typeAssertExpr
typeDecl
typeSpec
typeSpec // type A int
typeSpecAssign // type A = int
typeSwitch
unaryExpr
valueSpec
@@ -176,6 +176,7 @@ var kinds = [...]string{
typeAssertExpr: "typeAssertExpr",
typeDecl: "typeDecl",
typeSpec: "typeSpec",
typeSpecAssign: "typeSpecAssign",
typeSwitch: "typeSwitch",
unaryExpr: "unaryExpr",
valueSpec: "valueSpec",
@@ -281,6 +282,7 @@ var actions = [...]string{
aDec: "--",
aEqual: "==",
aGreater: ">",
aGreaterEqual: ">=",
aGetFunc: "getFunc",
aGetIndex: "getIndex",
aGetMethod: "getMethod",
@@ -289,6 +291,7 @@ var actions = [...]string{
aLand: "&&",
aLor: "||",
aLower: "<",
aLowerEqual: "<=",
aMethod: "Method",
aMul: "*",
aMulAssign: "*=",
@@ -360,21 +363,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 +387,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 +406,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 +639,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:
@@ -826,6 +838,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 +908,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) {
@@ -197,38 +202,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,11 +293,11 @@ 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)
@@ -314,6 +323,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,
@@ -410,11 +423,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 +437,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 +459,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 +486,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 +525,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 +619,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 +662,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 +686,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,6 +784,10 @@ 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
switch t.cat {
@@ -840,28 +878,51 @@ 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 {
case isBuiltinCall(n, sc):
@@ -885,7 +946,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 +959,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:
@@ -955,7 +1024,7 @@ func (interp *Interpreter) cfg(root *node, importPath, pkgName string) ([]*node,
n.typ = c0.typ
n.findex = sc.add(n.typ)
}
case isBinCall(n):
case isBinCall(n, sc):
err = check.arguments(n, n.child[1:], n.child[0], n.action == aCallSlice)
if err != nil {
break
@@ -1255,7 +1324,7 @@ func (interp *Interpreter) cfg(root *node, importPath, pkgName string) ([]*node,
// retry with the filename, in case ident is a package name.
sym, level, found = sc.lookup(filepath.Join(n.ident, baseName))
if !found {
err = n.cfgErrorf("undefined: %s", n.ident)
err = n.cfgErrorf("undefined: %s %d", n.ident, n.index)
break
}
}
@@ -1369,9 +1438,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 +1460,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 +1509,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)
@@ -1719,6 +1806,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 +1900,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 +1955,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 +2094,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 +2133,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.equals(t) {
// Reuse symbol in case of a variable redeclaration with the same type.
index = sym.index
} else {
index = sc.add(t)
sc.sym[id] = &symbol{index: index, kind: varSym, typ: t}
}
n.child[i].typ = t
n.child[i].findex = index
}
return nil
}
@@ -2391,6 +2498,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) ||
@@ -2407,7 +2523,7 @@ 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,8 +2569,19 @@ func isCall(n *node) bool {
return n.action == aCall || n.action == aCallSlice
}
func isBinCall(n *node) bool {
return isCall(n) && n.child[0].typ.cat == valueT && n.child[0].typ.rtype.Kind() == reflect.Func
func isBinCall(n *node, sc *scope) bool {
if !isCall(n) || len(n.child) == 0 {
return false
}
c0 := n.child[0]
if c0.typ == nil {
// If called early in parsing, child type may not be known yet.
c0.typ, _ = nodeType(n.interp, sc, c0)
if c0.typ == nil {
return false
}
}
return c0.typ.cat == valueT && c0.typ.rtype.Kind() == reflect.Func
}
func isOffsetof(n *node) bool {
@@ -2546,21 +2673,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:
@@ -2614,23 +2730,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 +2805,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 == "_"
}

84
interp/compile_test.go Normal file
View File

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

View File

@@ -3,7 +3,6 @@ package interp
import (
"fmt"
"io"
"io/ioutil"
"log"
"os/exec"
"path/filepath"
@@ -69,7 +68,7 @@ func (nopCloser) Close() error { return nil }
// dotWriter returns an output stream to a dot(1) co-process where to write data in .dot format.
func dotWriter(dotCmd string) io.WriteCloser {
if dotCmd == "" {
return nopCloser{ioutil.Discard}
return nopCloser{io.Discard}
}
fields := strings.Fields(dotCmd)
cmd := exec.Command(fields[0], fields[1:]...)

View File

@@ -25,7 +25,7 @@ func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([
case constDecl:
// Early parse of constDecl subtree, to compute all constant
// values which may be used in further declarations.
if _, err = interp.cfg(n, importPath, pkgName); err != nil {
if _, err = interp.cfg(n, sc, importPath, pkgName); err != nil {
// No error processing here, to allow recovery in subtree nodes.
// TODO(marc): check for a non recoverable error and return it for better diagnostic.
err = nil
@@ -37,10 +37,17 @@ func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([
}
case defineStmt:
var atyp *itype
var (
atyp *itype
err2 error
)
if n.nleft+n.nright < len(n.child) {
// Type is declared explicitly in the assign expression.
if atyp, err = nodeType(interp, sc, n.child[n.nleft]); err != nil {
if atyp, err2 = nodeType(interp, sc, n.child[n.nleft]); err2 != nil {
// The type does not exist yet, stash the error and come back
// when the type is known.
n.meta = err2
revisit = append(revisit, n)
return false
}
}
@@ -52,9 +59,12 @@ func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([
for i := 0; i < n.nleft; i++ {
dest, src := n.child[i], n.child[sbase+i]
if isBlank(src) {
err = n.cfgErrorf("cannot use _ as value")
}
val := src.rval
if n.anc.kind == constDecl {
if _, err2 := interp.cfg(n, importPath, pkgName); err2 != nil {
if _, err2 := interp.cfg(n, sc, importPath, pkgName); err2 != nil {
// Constant value can not be computed yet.
// Come back when child dependencies are known.
revisit = append(revisit, n)
@@ -63,7 +73,11 @@ func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([
}
typ := atyp
if typ == nil {
if typ, err = nodeType(interp, sc, src); err != nil || typ == nil {
if typ, err2 = nodeType(interp, sc, src); err2 != nil || typ == nil {
// The type does is not known yet, stash the error and come back
// when the type is known.
n.meta = err2
revisit = append(revisit, n)
return false
}
val = src.rval
@@ -138,26 +152,26 @@ func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([
n.ident = ident
rcvr := n.child[0].child[0]
rtn := rcvr.lastChild()
typeName := rtn.ident
if typeName == "" {
// The receiver is a pointer, retrieve typeName from indirection
typeName = rtn.child[0].ident
elementType := sc.getType(typeName)
if elementType == nil {
// Add type if necessary, so method can be registered
sc.sym[typeName] = &symbol{kind: typeSym, typ: &itype{name: typeName, path: pkgName, incomplete: true, node: rtn.child[0], scope: sc}}
elementType = sc.sym[typeName].typ
}
typName, typPtr := rtn.ident, false
if typName == "" {
typName, typPtr = rtn.child[0].ident, true
}
sym, _, found := sc.lookup(typName)
if !found {
n.meta = n.cfgErrorf("undefined: %s", typName)
revisit = append(revisit, n)
return false
}
if sym.kind != typeSym || (sym.node != nil && sym.node.kind == typeSpecAssign) {
err = n.cfgErrorf("cannot define new methods on non-local type %s", baseType(sym.typ).id())
return false
}
rcvrtype = sym.typ
if typPtr {
elementType := sym.typ
rcvrtype = ptrOf(elementType, withNode(rtn), withScope(sc))
rcvrtype.incomplete = elementType.incomplete
elementType.addMethod(n)
} else {
rcvrtype = sc.getType(typeName)
if rcvrtype == nil {
// Add type if necessary, so method can be registered
sc.sym[typeName] = &symbol{kind: typeSym, typ: &itype{name: typeName, path: pkgName, incomplete: true, node: rtn, scope: sc}}
rcvrtype = sc.sym[typeName].typ
}
}
rcvrtype.addMethod(n)
n.child[0].child[0].lastChild().typ = rcvrtype
@@ -197,15 +211,23 @@ func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([
case ".": // import symbols in current scope
for n, v := range pkg {
typ := v.Type()
kind := binSym
if isBinType(v) {
typ = typ.Elem()
kind = typeSym
}
sc.sym[n] = &symbol{kind: binSym, typ: valueTOf(typ, withScope(sc)), rval: v}
sc.sym[n] = &symbol{kind: kind, typ: valueTOf(typ, withScope(sc)), rval: v}
}
default: // import symbols in package namespace
if name == "" {
name = interp.pkgNames[ipath]
}
// If an incomplete type exists, delete it
if sym, exists := sc.sym[name]; exists && sym.kind == typeSym && sym.typ.incomplete {
delete(sc.sym, name)
}
// Imports of a same package are all mapped in the same scope, so we cannot just
// map them by their names, otherwise we could have collisions from same-name
// imports in different source files of the same package. Therefore, we suffix
@@ -254,7 +276,11 @@ func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([
err = n.cfgErrorf("import %q error: %v", ipath, err)
}
case typeSpec:
case typeSpec, typeSpecAssign:
if isBlank(n.child[0]) {
err = n.cfgErrorf("cannot use _ as value")
return false
}
typeName := n.child[0].ident
var typ *itype
if typ, err = nodeType(interp, sc, n.child[1]); err != nil {
@@ -284,9 +310,13 @@ func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([
}
sym, exists := sc.sym[typeName]
if !exists {
sc.sym[typeName] = &symbol{kind: typeSym}
sc.sym[typeName] = &symbol{kind: typeSym, node: n}
} else {
if sym.typ != nil && (len(sym.typ.method) > 0) {
if n.kind == typeSpecAssign {
err = n.cfgErrorf("cannot define new methods on non-local type %s", baseType(typ).id())
return false
}
// Type has already been seen as a receiver in a method function
for _, m := range sym.typ.method {
n.typ.addMethod(m)
@@ -294,7 +324,7 @@ func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([
} else {
// TODO(mpl): figure out how to detect redeclarations without breaking type aliases.
// Allow redeclarations for now.
sc.sym[typeName] = &symbol{kind: typeSym}
sc.sym[typeName] = &symbol{kind: typeSym, node: n}
}
}
sc.sym[typeName].typ = n.typ
@@ -312,6 +342,17 @@ func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([
return revisit, err
}
func baseType(t *itype) *itype {
for {
switch t.cat {
case ptrT, aliasT:
t = t.val
default:
return t
}
}
}
// gtaRetry (re)applies gta until all global constants and types are defined.
func (interp *Interpreter) gtaRetry(nodes []*node, importPath, pkgName string) error {
revisit := []*node{}
@@ -334,10 +375,15 @@ func (interp *Interpreter) gtaRetry(nodes []*node, importPath, pkgName string) e
if len(revisit) > 0 {
n := revisit[0]
if n.kind == typeSpec {
switch n.kind {
case typeSpec, typeSpecAssign:
if err := definedType(n.typ); err != nil {
return err
}
case defineStmt, funcDecl:
if err, ok := n.meta.(error); ok {
return err
}
}
return n.cfgErrorf("constant definition loop")
}

View File

@@ -19,6 +19,8 @@ import (
"path"
"path/filepath"
"reflect"
"runtime"
"runtime/debug"
"strconv"
"strings"
"sync"
@@ -53,6 +55,7 @@ type node struct {
val interface{} // static generic value (CFG execution)
rval reflect.Value // reflection value to let runtime access interpreter (CFG)
ident string // set if node is a var or func
meta interface{} // meta stores meta information between gta runs, like errors
}
func (n *node) shouldBreak() bool {
@@ -168,19 +171,22 @@ type imports map[string]map[string]*symbol
// opt stores interpreter options.
type opt struct {
astDot bool // display AST graph (debug)
cfgDot bool // display CFG graph (debug)
// dotCmd is the command to process the dot graph produced when astDot and/or
// cfgDot is enabled. It defaults to 'dot -Tdot -o <filename>.dot'.
dotCmd string
noRun bool // compile, but do not run
fastChan bool // disable cancellable chan operations
context build.Context // build context: GOPATH, build constraints
specialStdio bool // Allows os.Stdin, os.Stdout, os.Stderr to not be file descriptors
stdin io.Reader // standard input
stdout io.Writer // standard output
stderr io.Writer // standard error
filesystem fs.FS
context build.Context // build context: GOPATH, build constraints
stdin io.Reader // standard input
stdout io.Writer // standard output
stderr io.Writer // standard error
args []string // cmdline args
env map[string]string // environment of interpreter, entries in form of "key=value"
filesystem fs.FS // filesystem containing sources
astDot bool // display AST graph (debug)
cfgDot bool // display CFG graph (debug)
noRun bool // compile, but do not run
fastChan bool // disable cancellable chan operations
specialStdio bool // allows os.Stdin, os.Stdout, os.Stderr to not be file descriptors
unrestricted bool // allow use of non sandboxed symbols
}
// Interpreter contains global resources and state.
@@ -302,17 +308,26 @@ type Options struct {
Stdin io.Reader
Stdout, Stderr io.Writer
// Cmdline args, defaults to os.Args.
Args []string
// Environment of interpreter. Entries are in the form "key=values".
Env []string
// SourcecodeFilesystem is where the _sourcecode_ is loaded from and does
// NOT affect the filesystem of scripts when they run.
// It can be any fs.FS compliant filesystem (e.g. embed.FS, or fstest.MapFS for testing)
// See example/fs/fs_test.go for an example.
SourcecodeFilesystem fs.FS
// Unrestricted allows to run non sandboxed stdlib symbols such as os/exec and environment
Unrestricted bool
}
// New returns a new interpreter.
func New(options Options) *Interpreter {
i := Interpreter{
opt: opt{context: build.Default, filesystem: &realFS{}},
opt: opt{context: build.Default, filesystem: &realFS{}, env: map[string]string{}},
frame: newFrame(nil, 0, 0),
fset: token.NewFileSet(),
universe: initUniverse(),
@@ -336,6 +351,24 @@ func New(options Options) *Interpreter {
i.opt.stderr = os.Stderr
}
if i.opt.args = options.Args; i.opt.args == nil {
i.opt.args = os.Args
}
// unrestricted allows to use non sandboxed stdlib symbols and env.
if options.Unrestricted {
i.opt.unrestricted = true
} else {
for _, e := range options.Env {
a := strings.SplitN(e, "=", 2)
if len(a) == 2 {
i.opt.env[a[0]] = a[1]
} else {
i.opt.env[a[0]] = ""
}
}
}
if options.SourcecodeFilesystem != nil {
i.opt.filesystem = options.SourcecodeFilesystem
}
@@ -572,7 +605,7 @@ func isFile(filesystem fs.FS, path string) bool {
}
func (interp *Interpreter) eval(src, name string, inc bool) (res reflect.Value, err error) {
prog, err := interp.compile(src, name, inc)
prog, err := interp.compileSrc(src, name, inc)
if err != nil {
return res, err
}
@@ -597,7 +630,14 @@ func (interp *Interpreter) EvalWithContext(ctx context.Context, src string) (ref
done := make(chan struct{})
go func() {
defer close(done)
defer func() {
if r := recover(); r != nil {
var pc [64]uintptr
n := runtime.Callers(1, pc[:])
err = Panic{Value: r, Callers: pc[:n], Stack: debug.Stack()}
}
close(done)
}()
v, err = interp.Eval(src)
}()
@@ -715,6 +755,7 @@ func fixStdlib(interp *Interpreter) {
}
if p = interp.binPkg["os"]; p != nil {
p["Args"] = reflect.ValueOf(&interp.args).Elem()
if interp.specialStdio {
// Inherit streams from interpreter even if they do not have a file descriptor.
p["Stdin"] = reflect.ValueOf(&stdin).Elem()
@@ -732,6 +773,22 @@ func fixStdlib(interp *Interpreter) {
p["Stderr"] = reflect.ValueOf(&s).Elem()
}
}
if !interp.unrestricted {
// In restricted mode, scripts can only access to a passed virtualized env, and can not write the real one.
getenv := func(key string) string { return interp.env[key] }
p["Clearenv"] = reflect.ValueOf(func() { interp.env = map[string]string{} })
p["ExpandEnv"] = reflect.ValueOf(func(s string) string { return os.Expand(s, getenv) })
p["Getenv"] = reflect.ValueOf(getenv)
p["LookupEnv"] = reflect.ValueOf(func(key string) (s string, ok bool) { s, ok = interp.env[key]; return })
p["Setenv"] = reflect.ValueOf(func(key, value string) error { interp.env[key] = value; return nil })
p["Unsetenv"] = reflect.ValueOf(func(key string) error { delete(interp.env, key); return nil })
p["Environ"] = reflect.ValueOf(func() (a []string) {
for k, v := range interp.env {
a = append(a, k+"="+v)
}
return
})
}
}
if p = interp.binPkg["math/bits"]; p != nil {

View File

@@ -2,7 +2,7 @@ package interp_test
import (
"go/build"
"io/ioutil"
"io"
"os"
"os/exec"
"path/filepath"
@@ -20,13 +20,13 @@ func TestInterpConsistencyBuild(t *testing.T) {
}
dir := filepath.Join("..", "_test", "tmp")
if _, err := os.Stat(dir); os.IsNotExist(err) {
if err := os.Mkdir(dir, 0700); err != nil {
if err := os.Mkdir(dir, 0o700); err != nil {
t.Fatal(err)
}
}
baseDir := filepath.Join("..", "_test")
files, err := ioutil.ReadDir(baseDir)
files, err := os.ReadDir(baseDir)
if err != nil {
t.Fatal(err)
}
@@ -37,6 +37,8 @@ func TestInterpConsistencyBuild(t *testing.T) {
file.Name() == "assign12.go" || // expect error
file.Name() == "assign15.go" || // expect error
file.Name() == "bad0.go" || // expect error
file.Name() == "break0.go" || // expect error
file.Name() == "cont3.go" || // expect error
file.Name() == "const9.go" || // expect error
file.Name() == "export1.go" || // non-main package
file.Name() == "export0.go" || // non-main package
@@ -51,11 +53,14 @@ func TestInterpConsistencyBuild(t *testing.T) {
file.Name() == "init1.go" || // expect error
file.Name() == "io0.go" || // use random number
file.Name() == "issue-1093.go" || // expect error
file.Name() == "issue-1276.go" || // expect error
file.Name() == "issue-1330.go" || // expect error
file.Name() == "op1.go" || // expect error
file.Name() == "op7.go" || // expect error
file.Name() == "op9.go" || // expect error
file.Name() == "bltn0.go" || // expect error
file.Name() == "method16.go" || // private struct field
file.Name() == "method39.go" || // expect error
file.Name() == "switch8.go" || // expect error
file.Name() == "switch9.go" || // expect error
file.Name() == "switch13.go" || // expect error
@@ -100,7 +105,14 @@ func TestInterpConsistencyBuild(t *testing.T) {
file.Name() == "server.go" || // syntax parsing
file.Name() == "range9.go" || // expect error
file.Name() == "unsafe6.go" || // needs go.mod to be 1.17
file.Name() == "unsafe7.go" { // needs go.mod to be 1.17
file.Name() == "unsafe7.go" || // needs go.mod to be 1.17
file.Name() == "type27.go" || // expect error
file.Name() == "type28.go" || // expect error
file.Name() == "type29.go" || // expect error
file.Name() == "type30.go" || // expect error
file.Name() == "type31.go" || // expect error
file.Name() == "type32.go" || // expect error
file.Name() == "type33.go" { // expect error
continue
}
@@ -136,7 +148,7 @@ func TestInterpConsistencyBuild(t *testing.T) {
if err = w.Close(); err != nil {
t.Fatal(err)
}
outInterp, err := ioutil.ReadAll(r)
outInterp, err := io.ReadAll(r)
if err != nil {
t.Fatal(err)
}
@@ -188,6 +200,16 @@ func TestInterpErrorConsistency(t *testing.T) {
expectedInterp: "1:1: expected 'package', found println",
expectedExec: "1:1: expected 'package', found println",
},
{
fileName: "break0.go",
expectedInterp: "15:5: invalid break label OuterLoop",
expectedExec: "15:11: invalid break label OuterLoop",
},
{
fileName: "cont3.go",
expectedInterp: "15:5: invalid continue label OuterLoop",
expectedExec: "15:14: invalid continue label OuterLoop",
},
{
fileName: "const9.go",
expectedInterp: "5:2: constant definition loop",

View File

@@ -5,8 +5,9 @@ import (
"bytes"
"context"
"fmt"
"go/build"
"go/parser"
"io"
"io/ioutil"
"log"
"net/http"
"os"
@@ -128,6 +129,10 @@ func TestEvalAssign(t *testing.T) {
{src: "i := 1; j := &i; (*j) = 2", res: "2"},
{src: "i64 := testpkg.val; i64 == 11", res: "true"},
{pre: func() { eval(t, i, "k := 1") }, src: `k := "Hello world"`, res: "Hello world"}, // allow reassignment in subsequent evaluations
{src: "_ = _", err: "1:28: cannot use _ as value"},
{src: "j := true || _", err: "1:33: cannot use _ as value"},
{src: "j := true && _", err: "1:33: cannot use _ as value"},
{src: "j := interface{}(int(1)); j.(_)", err: "1:54: cannot use _ as value"},
})
}
@@ -178,6 +183,7 @@ func TestEvalBuiltin(t *testing.T) {
{src: `t := map[int]int{}; t[123]--; t`, res: "map[123:-1]"},
{src: `t := map[int]int{}; t[123] += 1; t`, res: "map[123:1]"},
{src: `t := map[int]int{}; t[123] -= 1; t`, res: "map[123:-1]"},
{src: `println("hello", _)`, err: "1:28: cannot use _ as value"},
})
}
@@ -202,6 +208,14 @@ func TestEvalDeclWithExpr(t *testing.T) {
})
}
func TestEvalTypeSpec(t *testing.T) {
i := interp.New(interp.Options{})
runTests(t, i, []testCase{
{src: `type _ struct{}`, err: "1:19: cannot use _ as value"},
{src: `a := struct{a, _ int}{32, 0}`, res: "{32 0}"},
})
}
func TestEvalFunc(t *testing.T) {
i := interp.New(interp.Options{})
runTests(t, i, []testCase{
@@ -210,6 +224,8 @@ func TestEvalFunc(t *testing.T) {
{src: `(func () int {f := func() (a, b int) {a, b = 3, 4; return}; x, y := f(); return x+y})()`, res: "7"},
{src: `(func () int {f := func() (a int, b, c int) {a, b, c = 3, 4, 5; return}; x, y, z := f(); return x+y+z})()`, res: "12"},
{src: `(func () int {f := func() (a, b, c int) {a, b, c = 3, 4, 5; return}; x, y, z := f(); return x+y+z})()`, res: "12"},
{src: `func f() int { return _ }`, err: "1:29: cannot use _ as value"},
{src: `(func (x int) {})(_)`, err: "1:28: cannot use _ as value"},
})
}
@@ -418,6 +434,8 @@ func TestEvalComparison(t *testing.T) {
{src: `2 > 1`, res: "true"},
{src: `1.2 > 1.1`, res: "true"},
{src: `"hhh" > "ggg"`, res: "true"},
{src: `a, b, c := 1, 1, false; if a == b { c = true }; c`, res: "true"},
{src: `a, b, c := 1, 2, false; if a != b { c = true }; c`, res: "true"},
{
desc: "mismatched types",
src: `
@@ -430,6 +448,16 @@ func TestEvalComparison(t *testing.T) {
`,
err: "7:13: invalid operation: mismatched types main.Foo and main.Bar",
},
{src: `1 > _`, err: "1:28: cannot use _ as value"},
{src: `(_) > 1`, err: "1:28: cannot use _ as value"},
{src: `v := interface{}(2); v == 2`, res: "true"},
{src: `v := interface{}(2); v > 1`, err: "1:49: invalid operation: operator > not defined on interface{}"},
{src: `v := interface{}(int64(2)); v == 2`, res: "false"},
{src: `v := interface{}(int64(2)); v != 2`, res: "true"},
{src: `v := interface{}(2.3); v == 2.3`, res: "true"},
{src: `v := interface{}(float32(2.3)); v != 2.3`, res: "true"},
{src: `v := interface{}("hello"); v == "hello"`, res: "true"},
{src: `v := interface{}("hello"); v < "hellp"`, err: "1:55: invalid operation: operator < not defined on interface{}"},
})
}
@@ -445,6 +473,7 @@ func TestEvalCompositeArray(t *testing.T) {
{src: `a := [1]int{1, 2}`, err: "1:43: index 1 is out of bounds (>= 1)"},
{src: `b := [l]int{1, 2}`, res: "[1 2 0 0 0 0 0 0 0 0]"},
{src: `i := 10; a := [i]int{1, 2}`, err: "1:43: non-constant array bound \"i\""},
{src: `c := [...]float64{1, 3: 3.4, 5}`, res: "[1 0 0 3.4 5]"},
})
}
@@ -474,6 +503,8 @@ func TestEvalCompositeStruct(t *testing.T) {
{src: `a := struct{A,B,C int}{A:1,A:2,C:3}`, err: "1:55: duplicate field name A in struct literal"},
{src: `a := struct{A,B,C int}{A:1,B:2.2,C:3}`, err: "1:57: 11/5 truncated to int"},
{src: `a := struct{A,B,C int}{A:1,2,C:3}`, err: "1:55: mixture of field:value and value elements in struct literal"},
{src: `a := struct{A,B,C int}{1,2,_}`, err: "1:33: cannot use _ as value"},
{src: `a := struct{A,B,C int}{B: _}`, err: "1:51: cannot use _ as value"},
})
}
@@ -498,6 +529,8 @@ func TestEvalSliceExpression(t *testing.T) {
{src: `a := []int{0,1,2,3}[1:3:]`, err: "1:51: 3rd index required in 3-index slice"},
{src: `a := []int{0,1,2}[3:1]`, err: "invalid index values, must be low <= high <= max"},
{pre: func() { eval(t, i, `type Str = string; var r Str = "truc"`) }, src: `r[1]`, res: "114"},
{src: `_[12]`, err: "1:28: cannot use _ as value"},
{src: `b := []int{0,1,2}[_:4]`, err: "1:33: cannot use _ as value"},
})
}
@@ -508,6 +541,7 @@ func TestEvalConversion(t *testing.T) {
{src: `i := 1.1; a := uint64(i)`, res: "1"},
{src: `b := string(49)`, res: "1"},
{src: `c := uint64(1.1)`, err: "1:40: cannot convert expression of type untyped float to type uint64"},
{src: `int(_)`, err: "1:28: cannot use _ as value"},
})
}
@@ -517,6 +551,9 @@ func TestEvalUnary(t *testing.T) {
{src: "a := -1", res: "-1"},
{src: "b := +1", res: "1", skip: "BUG"},
{src: "c := !false", res: "true"},
{src: "_ = 2; _++", err: "1:35: cannot use _ as value"},
{src: "_ = false; !_ == true", err: "1:39: cannot use _ as value"},
{src: "!((((_))))", err: "1:28: cannot use _ as value"},
})
}
@@ -882,7 +919,7 @@ func TestMultiEval(t *testing.T) {
if err = w.Close(); err != nil {
t.Fatal(err)
}
outInterp, err := ioutil.ReadAll(r)
outInterp, err := io.ReadAll(r)
if err != nil {
t.Fatal(err)
}
@@ -913,7 +950,7 @@ func TestMultiEvalNoName(t *testing.T) {
t.Fatal(err)
}
for k, v := range names {
data, err := ioutil.ReadFile(filepath.Join(f.Name(), v))
data, err := os.ReadFile(filepath.Join(f.Name(), v))
if err != nil {
t.Fatal(err)
}
@@ -1623,6 +1660,16 @@ func TestStdio(t *testing.T) {
}
}
func TestNoGoFiles(t *testing.T) {
i := interp.New(interp.Options{GoPath: build.Default.GOPATH})
_, err := i.Eval(`import "github.com/traefik/yaegi/_test/p3"`)
if strings.Contains(err.Error(), "no Go files in") {
return
}
t.Fatalf("failed to detect no Go files: %v", err)
}
func TestIssue1142(t *testing.T) {
i := interp.New(interp.Options{})
runTests(t, i, []testCase{
@@ -1712,3 +1759,94 @@ func TestIssue1151(t *testing.T) {
{src: "x := pkg.Array{1}", res: "[1]"},
})
}
func TestPassArgs(t *testing.T) {
i := interp.New(interp.Options{Args: []string{"arg0", "arg1"}})
if err := i.Use(stdlib.Symbols); err != nil {
t.Fatal(err)
}
i.ImportUsed()
runTests(t, i, []testCase{
{src: "os.Args", res: "[arg0 arg1]"},
})
}
func TestRestrictedEnv(t *testing.T) {
i := interp.New(interp.Options{Env: []string{"foo=bar"}})
if err := i.Use(stdlib.Symbols); err != nil {
t.Fatal(err)
}
i.ImportUsed()
runTests(t, i, []testCase{
{src: `os.Getenv("foo")`, res: "bar"},
{src: `s, ok := os.LookupEnv("foo"); s`, res: "bar"},
{src: `s, ok := os.LookupEnv("foo"); ok`, res: "true"},
{src: `s, ok := os.LookupEnv("PATH"); s`, res: ""},
{src: `s, ok := os.LookupEnv("PATH"); ok`, res: "false"},
{src: `os.Setenv("foo", "baz"); os.Environ()`, res: "[foo=baz]"},
{src: `os.ExpandEnv("foo is ${foo}")`, res: "foo is baz"},
{src: `os.Unsetenv("foo"); os.Environ()`, res: "[]"},
{src: `os.Setenv("foo", "baz"); os.Environ()`, res: "[foo=baz]"},
{src: `os.Clearenv(); os.Environ()`, res: "[]"},
{src: `os.Setenv("foo", "baz"); os.Environ()`, res: "[foo=baz]"},
})
if s, ok := os.LookupEnv("foo"); ok {
t.Fatal("expected \"\", got " + s)
}
}
func TestIssue1388(t *testing.T) {
i := interp.New(interp.Options{Env: []string{"foo=bar"}})
err := i.Use(stdlib.Symbols)
if err != nil {
t.Fatal(err)
}
_, err = i.Eval(`x := errors.New("")`)
if err == nil {
t.Fatal("Expected an error")
}
_, err = i.Eval(`import "errors"`)
if err != nil {
t.Fatal(err)
}
_, err = i.Eval(`x := errors.New("")`)
if err != nil {
t.Fatal(err)
}
}
func TestIssue1383(t *testing.T) {
const src = `
package main
func main() {
fmt.Println("Hello")
}
`
interp := interp.New(interp.Options{})
err := interp.Use(stdlib.Symbols)
if err != nil {
t.Fatal(err)
}
_, err = interp.Eval(`import "fmt"`)
if err != nil {
t.Fatal(err)
}
ast, err := parser.ParseFile(interp.FileSet(), "_.go", src, parser.DeclarationErrors)
if err != nil {
t.Fatal(err)
}
prog, err := interp.CompileAST(ast)
if err != nil {
t.Fatal(err)
}
_, err = interp.Execute(prog)
if err != nil {
t.Fatal(err)
}
}

View File

@@ -5,7 +5,6 @@ import (
"go/build"
"go/parser"
"go/token"
"io/ioutil"
"os"
"path/filepath"
"strings"
@@ -26,7 +25,7 @@ func TestFile(t *testing.T) {
_ = os.Setenv("YAEGI_SPECIAL_STDIO", "1")
baseDir := filepath.Join("..", "_test")
files, err := ioutil.ReadDir(baseDir)
files, err := os.ReadDir(baseDir)
if err != nil {
t.Fatal(err)
}

View File

@@ -1,9 +1,12 @@
package interp
import (
"go/constant"
"log"
"reflect"
"testing"
"github.com/traefik/yaegi/stdlib"
)
func init() { log.SetFlags(log.Lshortfile) }
@@ -19,13 +22,13 @@ func TestIsNatural(t *testing.T) {
n: &node{
typ: &itype{
rtype: func() reflect.Type {
var x uint = 3
return reflect.TypeOf(x)
var a uint = 3
return reflect.TypeOf(a)
}(),
},
rval: func() reflect.Value {
var x uint = 3
return reflect.ValueOf(x)
var a uint = 3
return reflect.ValueOf(a)
}(),
},
expected: true,
@@ -35,13 +38,13 @@ func TestIsNatural(t *testing.T) {
n: &node{
typ: &itype{
rtype: func() reflect.Type {
x := 3
return reflect.TypeOf(x)
a := 3
return reflect.TypeOf(a)
}(),
},
rval: func() reflect.Value {
x := 3
return reflect.ValueOf(x)
a := 3
return reflect.ValueOf(a)
}(),
},
expected: true,
@@ -51,13 +54,13 @@ func TestIsNatural(t *testing.T) {
n: &node{
typ: &itype{
rtype: func() reflect.Type {
var x int = 3
return reflect.TypeOf(x)
var a int = 3
return reflect.TypeOf(a)
}(),
},
rval: func() reflect.Value {
var x int = 3
return reflect.ValueOf(x)
var a int = 3
return reflect.ValueOf(a)
}(),
},
expected: true,
@@ -67,13 +70,13 @@ func TestIsNatural(t *testing.T) {
n: &node{
typ: &itype{
rtype: func() reflect.Type {
var x float64 = 3.0
return reflect.TypeOf(x)
var a float64 = 3.0
return reflect.TypeOf(a)
}(),
},
rval: func() reflect.Value {
var x float64 = 3.0
return reflect.ValueOf(x)
var a float64 = 3.0
return reflect.ValueOf(a)
}(),
},
expected: true,
@@ -83,13 +86,13 @@ func TestIsNatural(t *testing.T) {
n: &node{
typ: &itype{
rtype: func() reflect.Type {
var x float64 = 3.14
return reflect.TypeOf(x)
var a float64 = 3.14
return reflect.TypeOf(a)
}(),
},
rval: func() reflect.Value {
var x float64 = 3.14
return reflect.ValueOf(x)
var a float64 = 3.14
return reflect.ValueOf(a)
}(),
},
expected: false,
@@ -99,13 +102,13 @@ func TestIsNatural(t *testing.T) {
n: &node{
typ: &itype{
rtype: func() reflect.Type {
var x int = -3
return reflect.TypeOf(x)
var a int = -3
return reflect.TypeOf(a)
}(),
},
rval: func() reflect.Value {
var x int = -3
return reflect.ValueOf(x)
var a int = -3
return reflect.ValueOf(a)
}(),
},
expected: false,
@@ -188,3 +191,42 @@ func TestIsNatural(t *testing.T) {
}
}
}
func TestGlobals(t *testing.T) {
i := New(Options{})
if err := i.Use(stdlib.Symbols); err != nil {
t.Fatal(err)
}
if _, err := i.Eval("var a = 1"); err != nil {
t.Fatal(err)
}
if _, err := i.Eval("b := 2"); err != nil {
t.Fatal(err)
}
if _, err := i.Eval("const c = 3"); err != nil {
t.Fatal(err)
}
g := i.Globals()
a := g["a"]
if !a.IsValid() {
t.Fatal("a not found")
}
if a := a.Interface(); a != 1 {
t.Fatalf("wrong a: want (%[1]T) %[1]v, have (%[2]T) %[2]v", 1, a)
}
b := g["b"]
if !b.IsValid() {
t.Fatal("b not found")
}
if b := b.Interface(); b != 2 {
t.Fatalf("wrong b: want (%[1]T) %[1]v, have (%[2]T) %[2]v", 2, b)
}
c := g["c"]
if !c.IsValid() {
t.Fatal("c not found")
}
if cc, ok := c.Interface().(constant.Value); ok && constant.MakeInt64(3) != cc {
t.Fatalf("wrong c: want (%[1]T) %[1]v, have (%[2]T) %[2]v", constant.MakeInt64(3), cc)
}
}

View File

@@ -2624,6 +2624,7 @@ func equal(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 c0.typ.cat == aliasT || c1.typ.cat == aliasT {
switch {
@@ -2644,7 +2645,7 @@ func equal(n *node) {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
i1 := v1(f).Interface()
if i0 != i1 {
if i0 == i1 {
dest(f).SetBool(true)
return tnext
}
@@ -2666,7 +2667,7 @@ func equal(n *node) {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
if i0 != i1 {
if i0 == i1 {
dest(f).SetBool(true)
return tnext
}
@@ -2689,7 +2690,7 @@ func equal(n *node) {
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
i1 := v1(f).Interface()
if i0 != i1 {
if i0 == i1 {
dest(f).SetBool(true)
return tnext
}
@@ -2709,7 +2710,37 @@ func equal(n *node) {
return
}
switch t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf(); {
// 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 == 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 == i1)
return tnext
}
}
return
}
switch {
case isString(t0) || isString(t1):
switch {
case isInterface:
@@ -3193,8 +3224,9 @@ func greater(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()
switch t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf(); {
switch {
case isString(t0) || isString(t1):
switch {
case isInterface:
@@ -3520,8 +3552,9 @@ func greaterEqual(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()
switch t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf(); {
switch {
case isString(t0) || isString(t1):
switch {
case isInterface:
@@ -3847,8 +3880,9 @@ func lower(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()
switch t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf(); {
switch {
case isString(t0) || isString(t1):
switch {
case isInterface:
@@ -4174,8 +4208,9 @@ func lowerEqual(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()
switch t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf(); {
switch {
case isString(t0) || isString(t1):
switch {
case isInterface:
@@ -4501,6 +4536,7 @@ func notEqual(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 c0.typ.cat == aliasT || c1.typ.cat == aliasT {
switch {
@@ -4586,7 +4622,37 @@ func notEqual(n *node) {
return
}
switch t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf(); {
// 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 != 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 != i1)
return tnext
}
}
return
}
switch {
case isString(t0) || isString(t1):
switch {
case isInterface:

View File

@@ -2,7 +2,9 @@ package interp
import (
"context"
"io/ioutil"
"go/ast"
"go/token"
"os"
"reflect"
"runtime"
"runtime/debug"
@@ -15,9 +17,15 @@ type Program struct {
init []*node
}
// FileSet is the fileset that must be used for parsing Go that will be passed
// to interp.CompileAST().
func (interp *Interpreter) FileSet() *token.FileSet {
return interp.fset
}
// Compile parses and compiles a Go code represented as a string.
func (interp *Interpreter) Compile(src string) (*Program, error) {
return interp.compile(src, "", true)
return interp.compileSrc(src, "", true)
}
// CompilePath parses and compiles a Go code located at the given path.
@@ -27,14 +35,14 @@ func (interp *Interpreter) CompilePath(path string) (*Program, error) {
return nil, err
}
b, err := ioutil.ReadFile(path)
b, err := os.ReadFile(path)
if err != nil {
return nil, err
}
return interp.compile(string(b), path, false)
return interp.compileSrc(string(b), path, false)
}
func (interp *Interpreter) compile(src, name string, inc bool) (*Program, error) {
func (interp *Interpreter) compileSrc(src, name string, inc bool) (*Program, error) {
if name != "" {
interp.name = name
}
@@ -43,7 +51,23 @@ func (interp *Interpreter) compile(src, name string, inc bool) (*Program, error)
}
// Parse source to AST.
pkgName, root, err := interp.ast(src, interp.name, inc)
n, err := interp.parse(src, interp.name, inc)
if err != nil {
return nil, err
}
return interp.CompileAST(n)
}
// CompileAST builds a Program for the given Go code AST. Files and block
// statements can be compiled, as can most expressions. Var declaration nodes
// cannot be compiled.
//
// WARNING: The node must have been parsed using interp.FileSet(). Results are
// unpredictable otherwise.
func (interp *Interpreter) CompileAST(n ast.Node) (*Program, error) {
// Convert AST.
pkgName, root, err := interp.ast(n)
if err != nil || root == nil {
return nil, err
}
@@ -65,7 +89,7 @@ func (interp *Interpreter) compile(src, name string, inc bool) (*Program, error)
}
// Annotate AST with CFG informations.
initNodes, err := interp.cfg(root, pkgName, pkgName)
initNodes, err := interp.cfg(root, nil, pkgName, pkgName)
if err != nil {
if interp.cfgDot {
dotCmd := interp.dotCmd

View File

@@ -9,7 +9,6 @@ import (
"reflect"
"regexp"
"strings"
"sync"
)
// bltn type defines functions which run at CFG execution.
@@ -616,6 +615,34 @@ func convert(n *node) {
}
}
// assignFromCall assigns values from a function call.
func assignFromCall(n *node) {
ncall := n.lastChild()
l := len(n.child) - 1
if n.anc.kind == varDecl && n.child[l-1].isType(n.scope) {
// Ignore the type in the assignment if it is part of a variable declaration.
l--
}
dvalue := make([]func(*frame) reflect.Value, l)
for i := range dvalue {
if n.child[i].ident == "_" {
continue
}
dvalue[i] = genValue(n.child[i])
}
next := getExec(n.tnext)
n.exec = func(f *frame) bltn {
for i, v := range dvalue {
if v == nil {
continue
}
s := f.data[ncall.findex+i]
v(f).Set(s)
}
return next
}
}
func assign(n *node) {
next := getExec(n.tnext)
dvalue := make([]func(*frame) reflect.Value, n.nleft)
@@ -952,24 +979,32 @@ func genFunctionWrapper(n *node) func(*frame) reflect.Value {
d[i] = reflect.New(t).Elem()
}
// Copy method receiver as first argument, if defined.
if rcvr != nil {
if rcvr == nil {
d = d[numRet:]
} else {
// Copy method receiver as first argument.
src, dest := rcvr(f), d[numRet]
if src.Type().Kind() != dest.Type().Kind() {
sk, dk := src.Kind(), dest.Kind()
switch {
case sk == reflect.Ptr && dk != reflect.Ptr:
dest.Set(src.Elem())
case sk != reflect.Ptr && dk == reflect.Ptr:
dest.Set(src.Addr())
} else {
default:
if wrappedSrc, ok := src.Interface().(valueInterface); ok {
src = wrappedSrc.value
}
dest.Set(src)
}
d = d[numRet+1:]
} else {
d = d[numRet:]
}
// Copy function input arguments in local frame.
for i, arg := range in {
if i >= len(d) {
// In case of unused arg, there may be not even a frame entry allocated, just skip.
break
}
typ := def.typ.arg[i]
switch {
case isEmptyInterface(typ):
@@ -1006,9 +1041,14 @@ func genInterfaceWrapper(n *node, typ reflect.Type) func(*frame) reflect.Value {
if typ == nil || typ.Kind() != reflect.Interface || typ.NumMethod() == 0 || n.typ.cat == valueT {
return value
}
nt := n.typ.frameType()
if nt != nil && nt.Implements(typ) {
return value
tc := n.typ.cat
if tc != structT {
// Always force wrapper generation for struct types, as they may contain
// embedded interface fields which require wrapping, even if reported as
// implementing typ by reflect.
if nt := n.typ.frameType(); nt != nil && nt.Implements(typ) {
return value
}
}
mn := typ.NumMethod()
names := make([]string, mn)
@@ -1026,35 +1066,41 @@ func genInterfaceWrapper(n *node, typ reflect.Type) func(*frame) reflect.Value {
return func(f *frame) reflect.Value {
v := value(f)
if v.Type().Implements(typ) {
if tc != structT && v.Type().Implements(typ) {
return v
}
vv := v
switch v.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
if v.IsNil() {
return reflect.New(typ).Elem()
}
if v.Kind() == reflect.Ptr {
vv = v.Elem()
}
}
var n2 *node
if vi, ok := v.Interface().(valueInterface); ok {
n2 = vi.node
}
v = getConcreteValue(v)
w := reflect.New(wrap).Elem()
w.Field(0).Set(v)
for i, m := range methods {
if m == nil {
if r := v.MethodByName(names[i]); r.IsValid() {
// First direct method lookup on field.
if r := methodByName(v, names[i], indexes[i]); r.IsValid() {
w.Field(i + 1).Set(r)
continue
}
o := vv.FieldByIndex(indexes[i])
if r := o.MethodByName(names[i]); r.IsValid() {
w.Field(i + 1).Set(r)
} else {
if n2 == nil {
panic(n.cfgErrorf("method not found: %s", names[i]))
}
continue
// Method lookup in embedded valueInterface.
m2, i2 := n2.typ.lookupMethod(names[i])
if m2 != nil {
nod := *m2
nod.recv = &receiver{n, v, i2}
w.Field(i + 1).Set(genFunctionWrapper(&nod)(f))
continue
}
panic(n.cfgErrorf("method not found: %s", names[i]))
}
nod := *m
nod.recv = &receiver{n, v, indexes[i]}
@@ -1064,70 +1110,132 @@ func genInterfaceWrapper(n *node, typ reflect.Type) func(*frame) reflect.Value {
}
}
// methodByName returns the method corresponding to name on value, or nil if not found.
// The search is extended on valueInterface wrapper if present.
// If valid, the returned value is a method function with the receiver already set
// (no need to pass it at call).
func methodByName(value reflect.Value, name string, index []int) (v reflect.Value) {
if vi, ok := value.Interface().(valueInterface); ok {
if v = getConcreteValue(vi.value).MethodByName(name); v.IsValid() {
return
}
}
if v = value.MethodByName(name); v.IsValid() {
return
}
for value.Kind() == reflect.Ptr {
value = value.Elem()
if checkFieldIndex(value.Type(), index) {
value = value.FieldByIndex(index)
}
if v = value.MethodByName(name); v.IsValid() {
return
}
}
return
}
func checkFieldIndex(typ reflect.Type, index []int) bool {
if len(index) == 0 {
return false
}
t := typ
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
if t.Kind() != reflect.Struct {
return false
}
i := index[0]
if i >= t.NumField() {
return false
}
if len(index) > 1 {
return checkFieldIndex(t.Field(i).Type, index[1:])
}
return true
}
func call(n *node) {
goroutine := n.anc.kind == goStmt
var method bool
value := genValue(n.child[0])
c0 := n.child[0]
value := genValue(c0)
var values []func(*frame) reflect.Value
recvIndexLater := false
switch {
case n.child[0].recv != nil:
case c0.recv != nil:
// Compute method receiver value.
values = append(values, genValueRecv(n.child[0]))
values = append(values, genValueRecv(c0))
method = true
case len(n.child[0].child) > 0 && n.child[0].child[0].typ != nil && isInterfaceSrc(n.child[0].child[0].typ):
case len(c0.child) > 0 && c0.child[0].typ != nil && isInterfaceSrc(c0.child[0].typ):
recvIndexLater = true
values = append(values, genValueBinRecv(n.child[0], &receiver{node: n.child[0].child[0]}))
values = append(values, genValueBinRecv(c0, &receiver{node: c0.child[0]}))
value = genValueBinMethodOnInterface(n, value)
method = true
case n.child[0].action == aMethod:
case c0.action == aMethod:
// Add a place holder for interface method receiver.
values = append(values, nil)
method = true
}
numRet := len(n.child[0].typ.ret)
numRet := len(c0.typ.ret)
variadic := variadicPos(n)
child := n.child[1:]
tnext := getExec(n.tnext)
fnext := getExec(n.fnext)
hasVariadicArgs := n.action == aCallSlice // callSlice implies variadic call with ellipsis.
// Compute input argument value functions.
for i, c := range child {
var arg *itype
if variadic >= 0 && i >= variadic {
arg = c0.typ.arg[variadic].val
} else {
arg = c0.typ.arg[i]
}
switch {
case isBinCall(c):
case isBinCall(c, c.scope):
// Handle nested function calls: pass returned values as arguments.
numOut := c.child[0].typ.rtype.NumOut()
for j := 0; j < numOut; j++ {
ind := c.findex + j
values = append(values, func(f *frame) reflect.Value { return f.data[ind] })
if hasVariadicArgs || !isInterfaceSrc(arg) || isEmptyInterface(arg) {
values = append(values, func(f *frame) reflect.Value { return f.data[ind] })
continue
}
values = append(values, func(f *frame) reflect.Value {
return reflect.ValueOf(valueInterface{value: f.data[ind]})
})
}
case isRegularCall(c):
// Arguments are return values of a nested function call.
for j := range c.child[0].typ.ret {
cc0 := c.child[0]
for j := range cc0.typ.ret {
ind := c.findex + j
values = append(values, func(f *frame) reflect.Value { return f.data[ind] })
if hasVariadicArgs || !isInterfaceSrc(arg) || isEmptyInterface(arg) {
values = append(values, func(f *frame) reflect.Value { return f.data[ind] })
continue
}
values = append(values, func(f *frame) reflect.Value {
return reflect.ValueOf(valueInterface{node: cc0.typ.ret[j].node, value: f.data[ind]})
})
}
default:
var arg *itype
if variadic >= 0 && i >= variadic {
arg = n.child[0].typ.arg[variadic].val
} else {
arg = n.child[0].typ.arg[i]
}
if c.kind == basicLit || c.rval.IsValid() {
argType := arg.TypeOf()
convertLiteralValue(c, argType)
}
switch {
case isEmptyInterface(arg):
case hasVariadicArgs:
values = append(values, genValue(c))
case isInterfaceSrc(arg) && n.action != aCallSlice:
// callSlice implies variadic call with ellipsis, do not wrap in valueInterface.
case isInterfaceSrc(arg) && (!isEmptyInterface(arg) || len(c.typ.method) > 0):
values = append(values, genValueInterface(c))
case isInterfaceBin(arg):
values = append(values, genInterfaceWrapper(c, arg.rtype))
case isFuncSrc(arg):
values = append(values, genValueNode(c))
default:
values = append(values, genValue(c))
}
@@ -1135,10 +1243,11 @@ func call(n *node) {
}
// Compute output argument value functions.
rtypes := n.child[0].typ.ret
rtypes := c0.typ.ret
rvalues := make([]func(*frame) reflect.Value, len(rtypes))
switch n.anc.kind {
case defineXStmt, assignXStmt:
l := n.level
for i := range rvalues {
c := n.anc.child[i]
switch {
@@ -1147,7 +1256,8 @@ func call(n *node) {
case isInterfaceSrc(c.typ) && !isEmptyInterface(c.typ) && !isInterfaceSrc(rtypes[i]):
rvalues[i] = genValueInterfaceValue(c)
default:
rvalues[i] = genValue(c)
j := n.findex + i
rvalues[i] = func(f *frame) reflect.Value { return getFrame(f, l).data[j] }
}
}
case returnStmt:
@@ -1168,7 +1278,7 @@ func call(n *node) {
if n.anc.kind == deferStmt {
// Store function call in frame for deferred execution.
value = genFunctionWrapper(n.child[0])
value = genFunctionWrapper(c0)
if method {
// The receiver is already passed in the function wrapper, skip it.
values = values[1:]
@@ -1295,12 +1405,22 @@ func call(n *node) {
}
default:
val := v(f)
// The !val.IsZero is to work around a recursive struct zero interface
// issue. Once there is a better way to handle this case, the dest
// can just be set.
if !val.IsZero() || dest[i].Type().Kind() == reflect.Interface {
dest[i].Set(val)
if val.IsZero() && dest[i].Kind() != reflect.Interface {
// Work around a recursive struct zero interface issue.
// Once there is a better way to handle this case, the dest can just be set.
continue
}
if nod, ok := val.Interface().(*node); ok && nod.recv != nil {
// An interpreted method is passed as value in a function call.
// It must be wrapped now, otherwise the receiver will be missing
// at the method call (#1332).
// TODO (marc): wrapping interpreted functions should be always done
// everywhere at runtime to simplify the whole code,
// but it requires deeper refactoring.
dest[i] = genFunctionWrapper(nod)(f)
continue
}
dest[i].Set(val)
}
}
}
@@ -1374,7 +1494,7 @@ func callBin(n *node) {
}
switch {
case isBinCall(c):
case isBinCall(c, c.scope):
// Handle nested function calls: pass returned values as arguments
numOut := c.child[0].typ.rtype.NumOut()
for j := 0; j < numOut; j++ {
@@ -1487,12 +1607,13 @@ func callBin(n *node) {
rvalues := make([]func(*frame) reflect.Value, funcType.NumOut())
for i := range rvalues {
c := n.anc.child[i]
if c.ident != "_" {
if isInterfaceSrc(c.typ) {
rvalues[i] = genValueInterfaceValue(c)
} else {
rvalues[i] = genValue(c)
}
if c.ident == "_" {
continue
}
if isInterfaceSrc(c.typ) {
rvalues[i] = genValueInterfaceValue(c)
} else {
rvalues[i] = genValue(c)
}
}
n.exec = func(f *frame) bltn {
@@ -1519,7 +1640,11 @@ func callBin(n *node) {
}
out := callFn(value(f), in)
for i, v := range out {
f.data[b+i].Set(v)
dest := f.data[b+i]
if _, ok := dest.Interface().(valueInterface); ok {
v = reflect.ValueOf(valueInterface{value: v})
}
dest.Set(v)
}
return tnext
}
@@ -1531,11 +1656,16 @@ func callBin(n *node) {
}
out := callFn(value(f), in)
for i := 0; i < len(out); i++ {
if out[i].Type().Kind() == reflect.Func {
getFrame(f, n.level).data[n.findex+i] = out[i]
} else {
getFrame(f, n.level).data[n.findex+i].Set(out[i])
r := out[i]
if r.Kind() == reflect.Func {
getFrame(f, n.level).data[n.findex+i] = r
continue
}
dest := getFrame(f, n.level).data[n.findex+i]
if _, ok := dest.Interface().(valueInterface); ok {
r = reflect.ValueOf(valueInterface{value: r})
}
dest.Set(r)
}
return tnext
}
@@ -1636,9 +1766,6 @@ func getIndexArray(n *node) {
}
}
// valueInterfaceType is the reflection type of valueInterface.
var valueInterfaceType = reflect.TypeOf((*valueInterface)(nil)).Elem()
// getIndexMap retrieves map value from index.
func getIndexMap(n *node) {
dest := genValue(n)
@@ -1703,7 +1830,6 @@ func getIndexMap2(n *node) {
value0 := genValue(n.child[0]) // map
value2 := genValue(n.anc.child[1]) // status
next := getExec(n.tnext)
typ := n.anc.child[0].typ
doValue := n.anc.child[0].ident != "_"
doStatus := n.anc.child[1].ident != "_"
@@ -1720,21 +1846,6 @@ func getIndexMap2(n *node) {
value2(f).SetBool(v.IsValid())
return next
}
case isInterfaceSrc(typ):
n.exec = func(f *frame) bltn {
v := value0(f).MapIndex(mi)
if v.IsValid() {
if e := v.Elem(); e.Type().AssignableTo(valueInterfaceType) {
dest(f).Set(e)
} else {
dest(f).Set(reflect.ValueOf(valueInterface{n, e}))
}
}
if doStatus {
value2(f).SetBool(v.IsValid())
}
return next
}
default:
n.exec = func(f *frame) bltn {
v := value0(f).MapIndex(mi)
@@ -1756,21 +1867,6 @@ func getIndexMap2(n *node) {
value2(f).SetBool(v.IsValid())
return next
}
case isInterfaceSrc(typ):
n.exec = func(f *frame) bltn {
v := value0(f).MapIndex(value1(f))
if v.IsValid() {
if e := v.Elem(); e.Type().AssignableTo(valueInterfaceType) {
dest(f).Set(e)
} else {
dest(f).Set(reflect.ValueOf(valueInterface{n, e}))
}
}
if doStatus {
value2(f).SetBool(v.IsValid())
}
return next
}
default:
n.exec = func(f *frame) bltn {
v := value0(f).MapIndex(value1(f))
@@ -1847,7 +1943,7 @@ func getMethodByName(n *node) {
}
return next
}
m, li := val.node.typ.lookupMethod(name)
m, li := typ.lookupMethod(name)
if m == nil {
panic(n.cfgErrorf("method not found: %s", name))
}
@@ -2256,9 +2352,13 @@ func _return(n *node) {
}
values[i] = genValueInterface(c)
case valueT:
if t.rtype.Kind() == reflect.Interface {
switch t.rtype.Kind() {
case reflect.Interface:
values[i] = genInterfaceWrapper(c, t.rtype)
break
continue
case reflect.Func:
values[i] = genFunctionWrapper(c)
continue
}
fallthrough
default:
@@ -2491,6 +2591,9 @@ func doCompositeBinStruct(n *node, hasType bool) {
}
}
frameIndex := n.findex
l := n.level
n.exec = func(f *frame) bltn {
s := reflect.New(typ).Elem()
for i, v := range values {
@@ -2498,10 +2601,10 @@ func doCompositeBinStruct(n *node, hasType bool) {
}
d := value(f)
switch {
case d.Type().Kind() == reflect.Ptr:
case d.Kind() == reflect.Ptr:
d.Set(s.Addr())
default:
d.Set(s)
getFrame(f, l).data[frameIndex] = s
}
return next
}
@@ -2526,8 +2629,6 @@ func doComposite(n *node, hasType bool, keyed bool) {
if typ.cat == ptrT || typ.cat == aliasT {
typ = typ.val
}
var mu sync.Mutex
typ.mu = &mu
child := n.child
if hasType {
child = n.child[1:]
@@ -2553,9 +2654,9 @@ func doComposite(n *node, hasType bool, keyed bool) {
values[fieldIndex] = func(*frame) reflect.Value { return reflect.New(rft).Elem() }
case isFuncSrc(val.typ):
values[fieldIndex] = genValueAsFunctionWrapper(val)
case isArray(val.typ) && val.typ.val != nil && isInterfaceSrc(val.typ.val):
case isArray(val.typ) && val.typ.val != nil && isInterfaceSrc(val.typ.val) && !isEmptyInterface(val.typ.val):
values[fieldIndex] = genValueInterfaceArray(val)
case isInterfaceSrc(ft) && !isEmptyInterface(ft):
case isInterfaceSrc(ft) && (!isEmptyInterface(ft) || len(val.typ.method) > 0):
values[fieldIndex] = genValueInterface(val)
case isInterface(ft):
values[fieldIndex] = genInterfaceWrapper(val, rft)
@@ -2566,17 +2667,16 @@ func doComposite(n *node, hasType bool, keyed bool) {
frameIndex := n.findex
l := n.level
rt := typ.TypeOf()
n.exec = func(f *frame) bltn {
typ.mu.Lock()
// No need to call zero() as doComposite is only called for a structT.
a := reflect.New(typ.TypeOf()).Elem()
typ.mu.Unlock()
a := reflect.New(rt).Elem()
for i, v := range values {
a.Field(i).Set(v(f))
}
d := value(f)
switch {
case d.Type().Kind() == reflect.Ptr:
case d.Kind() == reflect.Ptr:
d.Set(a.Addr())
case destInterface:
if len(destType(n).field) > 0 {
@@ -2832,13 +2932,22 @@ func _case(n *node) {
if typ.cat == nilT && v.IsNil() {
return tnext
}
if typ.TypeOf().String() == t.String() {
destValue(f).Set(v.Elem())
rtyp := typ.TypeOf()
if rtyp == nil {
return fnext
}
elem := v.Elem()
if rtyp.String() == t.String() && implementsInterface(v, typ) {
destValue(f).Set(elem)
return tnext
}
ival := v.Interface()
if ival != nil && typ.TypeOf().String() == reflect.TypeOf(ival).String() {
destValue(f).Set(v.Elem())
if ival != nil && rtyp.String() == reflect.TypeOf(ival).String() {
destValue(f).Set(elem)
return tnext
}
if typ.cat == valueT && rtyp.Kind() == reflect.Interface && elem.IsValid() && elem.Type().Implements(rtyp) {
destValue(f).Set(elem)
return tnext
}
return fnext
@@ -2856,12 +2965,37 @@ func _case(n *node) {
}
return fnext
}
default:
// TODO(mpl): probably needs to be fixed for empty interfaces, like above.
// match against multiple types: assign var to interface value
n.exec = func(f *frame) bltn {
val := srcValue(f)
if v := srcValue(f).Interface().(valueInterface).node; v != nil {
if t := val.Type(); t.Kind() == reflect.Interface {
for _, typ := range types {
if typ.cat == nilT && val.IsNil() {
return tnext
}
rtyp := typ.TypeOf()
if rtyp == nil {
continue
}
elem := val.Elem()
if rtyp.String() == t.String() && implementsInterface(val, typ) {
destValue(f).Set(elem)
return tnext
}
ival := val.Interface()
if ival != nil && rtyp.String() == reflect.TypeOf(ival).String() {
destValue(f).Set(elem)
return tnext
}
if typ.cat == valueT && rtyp.Kind() == reflect.Interface && elem.IsValid() && elem.Type().Implements(rtyp) {
destValue(f).Set(elem)
return tnext
}
}
return fnext
}
if v := val.Interface().(valueInterface).node; v != nil {
for _, typ := range types {
if v.typ.id() == typ.id() {
destValue(f).Set(val)
@@ -2900,6 +3034,22 @@ func _case(n *node) {
}
}
func implementsInterface(v reflect.Value, t *itype) bool {
rt := v.Type()
if t.cat == valueT {
return rt.Implements(t.rtype)
}
vt := &itype{cat: valueT, rtype: rt}
if vt.methods().contains(t.methods()) {
return true
}
vi, ok := v.Interface().(valueInterface)
if !ok {
return false
}
return vi.node != nil && vi.node.typ.methods().contains(t.methods())
}
func appendSlice(n *node) {
dest := genValueOutput(n, n.typ.rtype)
next := getExec(n.tnext)
@@ -2946,13 +3096,11 @@ func _append(n *node) {
l := len(args)
values := make([]func(*frame) reflect.Value, l)
for i, arg := range args {
switch {
case isEmptyInterface(n.typ.val):
values[i] = genValue(arg)
case isInterfaceSrc(n.typ.val):
switch elem := n.typ.elem(); {
case isInterfaceSrc(elem) && (!isEmptyInterface(elem) || len(arg.typ.method) > 0):
values[i] = genValueInterface(arg)
case isInterfaceBin(n.typ.val):
values[i] = genInterfaceWrapper(arg, n.typ.val.rtype)
case isInterfaceBin(elem):
values[i] = genInterfaceWrapper(arg, elem.rtype)
case arg.typ.untyped:
values[i] = genValueAs(arg, n.child[1].typ.TypeOf().Elem())
default:
@@ -2971,9 +3119,7 @@ func _append(n *node) {
default:
var value0 func(*frame) reflect.Value
switch elem := n.typ.elem(); {
case isEmptyInterface(elem):
value0 = genValue(n.child[2])
case isInterfaceSrc(elem):
case isInterfaceSrc(elem) && (!isEmptyInterface(elem) || len(n.child[2].typ.method) > 0):
value0 = genValueInterface(n.child[2])
case isInterfaceBin(elem):
value0 = genInterfaceWrapper(n.child[2], elem.rtype)
@@ -3109,23 +3255,37 @@ func _delete(n *node) {
}
func capConst(n *node) {
n.rval = reflect.New(reflect.TypeOf(int(0))).Elem()
// There is no Cap() method for reflect.Type, just return Len() instead.
n.rval.SetInt(int64(n.child[1].typ.TypeOf().Len()))
lenConst(n)
}
func lenConst(n *node) {
n.rval = reflect.New(reflect.TypeOf(int(0))).Elem()
if c1 := n.child[1]; c1.rval.IsValid() {
c1 := n.child[1]
if c1.rval.IsValid() {
n.rval.SetInt(int64(len(vString(c1.rval))))
} else {
n.rval.SetInt(int64(c1.typ.TypeOf().Len()))
return
}
t := c1.typ.TypeOf()
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
n.rval.SetInt(int64(t.Len()))
}
func _len(n *node) {
dest := genValueOutput(n, reflect.TypeOf(int(0)))
value := genValue(n.child[1])
if isPtr(n.child[1].typ) {
val := value
value = func(f *frame) reflect.Value {
v := val(f).Elem()
for v.Kind() == reflect.Ptr {
v = v.Elem()
}
return v
}
}
next := getExec(n.tnext)
if wantEmptyInterface(n) {

View File

@@ -47,7 +47,7 @@ type symbol struct {
kind sKind
typ *itype // Type of value
node *node // Node value if index is negative
from []*node // list of nodes jumping to node if kind is label, or nil
from []*node // list of goto nodes jumping to this label node, or nil
recv *receiver // receiver node value, if sym refers to a method
index int // index of value in frame or -1
rval reflect.Value // default value (used for constants)
@@ -144,20 +144,6 @@ func (s *scope) lookup(ident string) (*symbol, int, bool) {
return nil, 0, false
}
// lookdown searches for a symbol in the current scope and included ones, recursively.
// It returns the first found symbol and true, or nil and false.
func (s *scope) lookdown(ident string) (*symbol, bool) {
if sym, ok := s.sym[ident]; ok {
return sym, true
}
for _, c := range s.child {
if sym, ok := c.lookdown(ident); ok {
return sym, true
}
}
return nil, false
}
func (s *scope) rangeChanType(n *node) *itype {
if sym, _, found := s.lookup(n.child[1].ident); found {
if t := sym.typ; len(n.child) == 3 && t != nil && (t.cat == chanT || t.cat == chanRecvT) {
@@ -241,3 +227,26 @@ func (interp *Interpreter) initScopePkg(pkgID, pkgName string) *scope {
interp.mutex.Unlock()
return sc
}
// Globals returns a map of global variables and constants in the main package.
func (interp *Interpreter) Globals() map[string]reflect.Value {
syms := map[string]reflect.Value{}
interp.mutex.RLock()
defer interp.mutex.RUnlock()
v, ok := interp.srcPkg["main"]
if !ok {
return syms
}
for n, s := range v {
switch s.kind {
case constSym:
syms[n] = s.rval
case varSym:
syms[n] = interp.frame.data[s.index]
}
}
return syms
}

View File

@@ -73,8 +73,16 @@ func (interp *Interpreter) importSrc(rPath, importPath string, skipTest bool) (s
return "", err
}
n, err := interp.parse(string(buf), name, false)
if err != nil {
return "", err
}
if n == nil {
continue
}
var pname string
if pname, root, err = interp.ast(string(buf), name, false); err != nil {
if pname, root, err = interp.ast(n); err != nil {
return "", err
}
if root == nil {
@@ -114,7 +122,7 @@ func (interp *Interpreter) importSrc(rPath, importPath string, skipTest bool) (s
// Generate control flow graphs.
for _, root := range rootNodes {
var nodes []*node
if nodes, err = interp.cfg(root, importPath, pkgName); err != nil {
if nodes, err = interp.cfg(root, nil, importPath, pkgName); err != nil {
return "", err
}
initNodes = append(initNodes, nodes...)
@@ -124,6 +132,10 @@ func (interp *Interpreter) importSrc(rPath, importPath string, skipTest bool) (s
// the global symbols in the package scope.
interp.mutex.Lock()
gs := interp.scopes[importPath]
if gs == nil {
// A nil scope means that no even an empty package is created from source.
return "", fmt.Errorf("no Go files in %s", dir)
}
interp.srcPkg[importPath] = gs.sym
interp.pkgNames[importPath] = pkgName

View File

@@ -1,7 +1,6 @@
package interp
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
@@ -50,7 +49,7 @@ func Test_effectivePkg(t *testing.T) {
func Test_pkgDir(t *testing.T) {
// create GOPATH
goPath, err := ioutil.TempDir("", "pkdir")
goPath, err := os.MkdirTemp("", "pkdir")
if err != nil {
t.Fatal(err)
}
@@ -60,7 +59,7 @@ func Test_pkgDir(t *testing.T) {
// Create project
project := filepath.Join(goPath, "src", "guthib.com", "foo", "root")
if err := os.MkdirAll(project, 0700); err != nil {
if err := os.MkdirAll(project, 0o700); err != nil {
t.Fatal(err)
}
@@ -81,7 +80,7 @@ func Test_pkgDir(t *testing.T) {
path: "guthib.com/foo/bar",
root: "",
setup: func() error {
return os.MkdirAll(filepath.Join(goPath, "src", "guthib.com", "foo", "bar"), 0700)
return os.MkdirAll(filepath.Join(goPath, "src", "guthib.com", "foo", "bar"), 0o700)
},
expected: expected{
dir: filepath.Join(goPath, "src", "guthib.com", "foo", "bar"),
@@ -93,7 +92,7 @@ func Test_pkgDir(t *testing.T) {
path: "guthib.com/foo/bar",
root: filepath.Join("guthib.com", "foo", "root"),
setup: func() error {
return os.MkdirAll(filepath.Join(project, "vendor", "guthib.com", "foo", "bar"), 0700)
return os.MkdirAll(filepath.Join(project, "vendor", "guthib.com", "foo", "bar"), 0o700)
},
expected: expected{
dir: filepath.Join(goPath, "src", "guthib.com", "foo", "root", "vendor", "guthib.com", "foo", "bar"),
@@ -105,7 +104,7 @@ func Test_pkgDir(t *testing.T) {
path: "guthib.com/foo/bar",
root: filepath.Join("guthib.com", "foo", "root"),
setup: func() error {
return os.MkdirAll(filepath.Join(goPath, "src", "guthib.com", "foo", "bar"), 0700)
return os.MkdirAll(filepath.Join(goPath, "src", "guthib.com", "foo", "bar"), 0o700)
},
expected: expected{
dir: filepath.Join(goPath, "src", "guthib.com", "foo", "bar"),
@@ -117,10 +116,10 @@ func Test_pkgDir(t *testing.T) {
path: "guthib.com/foo/bar",
root: filepath.Join("guthib.com", "foo", "root", "vendor", "guthib.com", "foo", "bir"),
setup: func() error {
if err := os.MkdirAll(filepath.Join(project, "vendor", "guthib.com", "foo", "bar"), 0700); err != nil {
if err := os.MkdirAll(filepath.Join(project, "vendor", "guthib.com", "foo", "bar"), 0o700); err != nil {
return err
}
return os.MkdirAll(filepath.Join(project, "vendor", "guthib.com", "foo", "bir"), 0700)
return os.MkdirAll(filepath.Join(project, "vendor", "guthib.com", "foo", "bir"), 0o700)
},
expected: expected{
dir: filepath.Join(goPath, "src", "guthib.com", "foo", "root", "vendor", "guthib.com", "foo", "bar"),
@@ -132,10 +131,10 @@ func Test_pkgDir(t *testing.T) {
path: "guthib.com/foo/bar",
root: filepath.Join("guthib.com", "foo", "root", "vendor", "guthib.com", "foo", "bir"),
setup: func() error {
if err := os.MkdirAll(filepath.Join(goPath, "src", "guthib.com", "foo", "bar"), 0700); err != nil {
if err := os.MkdirAll(filepath.Join(goPath, "src", "guthib.com", "foo", "bar"), 0o700); err != nil {
return err
}
return os.MkdirAll(filepath.Join(project, "vendor", "guthib.com", "foo", "bir"), 0700)
return os.MkdirAll(filepath.Join(project, "vendor", "guthib.com", "foo", "bir"), 0o700)
},
expected: expected{
dir: filepath.Join(goPath, "src", "guthib.com", "foo", "bar"),
@@ -149,10 +148,10 @@ func Test_pkgDir(t *testing.T) {
setup: func() error {
if err := os.MkdirAll(
filepath.Join(goPath, "src", "guthib.com", "foo", "root", "vendor", "guthib.com", "foo", "bir", "vendor", "guthib.com", "foo", "bur"),
0700); err != nil {
0o700); err != nil {
return err
}
return os.MkdirAll(filepath.Join(project, "vendor", "guthib.com", "foo", "bar"), 0700)
return os.MkdirAll(filepath.Join(project, "vendor", "guthib.com", "foo", "bar"), 0o700)
},
expected: expected{
dir: filepath.Join(project, "vendor", "guthib.com", "foo", "bar"),
@@ -173,7 +172,7 @@ func Test_pkgDir(t *testing.T) {
if err := os.RemoveAll(goPath); err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(goPath, 0700); err != nil {
if err := os.MkdirAll(goPath, 0o700); err != nil {
t.Fatal(err)
}

View File

@@ -6,7 +6,7 @@ import (
"path/filepath"
"reflect"
"strconv"
"sync"
"strings"
"github.com/traefik/yaegi/internal/unsafe2"
)
@@ -109,7 +109,6 @@ type structField struct {
// itype defines the internal representation of types in the interpreter.
type itype struct {
mu *sync.Mutex
cat tcat // Type category
field []structField // Array of struct fields if structT or interfaceT
key *itype // Type of key element if MapT or nil
@@ -239,6 +238,9 @@ func namedOf(val *itype, path, name string, opts ...itypeOption) *itype {
if path != "" {
str = path + "." + name
}
for val.cat == aliasT {
val = val.val
}
t := &itype{cat: aliasT, val: val, path: path, name: name, str: str}
for _, opt := range opts {
opt(t)
@@ -361,12 +363,25 @@ func structOf(t *itype, fields []structField, opts ...itypeOption) *itype {
return t
}
// nodeType returns a type definition for the corresponding AST subtree.
func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
return nodeType2(interp, sc, n, map[*node]bool{})
// seenNode determines if a node has been seen.
//
// seenNode treats the slice of nodes as the path traveled down a node
// tree.
func seenNode(ns []*node, n *node) bool {
for _, nn := range ns {
if nn == n {
return true
}
}
return false
}
func nodeType2(interp *Interpreter, sc *scope, n *node, seen map[*node]bool) (t *itype, err error) {
// nodeType returns a type definition for the corresponding AST subtree.
func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
return nodeType2(interp, sc, n, nil)
}
func nodeType2(interp *Interpreter, sc *scope, n *node, seen []*node) (t *itype, err error) {
if n.typ != nil && !n.typ.incomplete {
return n.typ, nil
}
@@ -376,14 +391,15 @@ func nodeType2(interp *Interpreter, sc *scope, n *node, seen map[*node]bool) (t
if sym.typ.isComplete() {
return sym.typ, nil
}
if seen[n] {
// TODO (marc): find a better way to distinguish recursive vs incomplete types.
if seenNode(seen, n) {
// We have seen this node in our tree, so it must be recursive.
sym.typ.incomplete = false
return sym.typ, nil
}
}
}
seen[n] = true
seen = append(seen, n)
defer func() { seen = seen[:len(seen)-1] }()
switch n.kind {
case addressExpr, starExpr:
@@ -421,7 +437,10 @@ func nodeType2(interp *Interpreter, sc *scope, n *node, seen map[*node]bool) (t
}
case c0.kind == ellipsisExpr:
// [...]T expression, get size from the length of composite array.
length = arrayTypeLen(n.anc)
length, err = arrayTypeLen(n.anc, sc)
if err != nil {
incomplete = true
}
case c0.kind == identExpr:
sym, _, ok := sc.lookup(c0.ident)
if !ok {
@@ -439,7 +458,7 @@ func nodeType2(interp *Interpreter, sc *scope, n *node, seen map[*node]bool) (t
length = int(vInt(sym.rval))
default:
// Size is defined by a numeric constant expression.
if _, err = interp.cfg(c0, sc.pkgID, sc.pkgName); err != nil {
if _, err = interp.cfg(c0, sc, sc.pkgID, sc.pkgName); err != nil {
return nil, err
}
v, ok := c0.rval.Interface().(constant.Value)
@@ -833,6 +852,14 @@ func nodeType2(interp *Interpreter, sc *scope, n *node, seen map[*node]bool) (t
if err != nil {
return nil, err
}
if t.cat == valueT {
switch t.rtype.Kind() {
case reflect.Array, reflect.Ptr:
t = valueTOf(reflect.SliceOf(t.rtype.Elem()), withScope(sc))
}
break
}
if t.cat == ptrT {
t = t.val
}
@@ -1054,7 +1081,7 @@ func (t *itype) in(i int) *itype {
return t.arg[i]
case valueT:
if t.rtype.Kind() == reflect.Func {
if t.recv != nil {
if t.recv != nil && !isInterface(t.recv) {
i++
}
if t.rtype.IsVariadic() && i == t.rtype.NumIn()-1 {
@@ -1508,22 +1535,33 @@ func (t *itype) getMethod(name string) *node {
// LookupMethod returns a pointer to method definition associated to type t
// and the list of indices to access the right struct field, in case of an embedded method.
func (t *itype) lookupMethod(name string) (*node, []int) {
return t.lookupMethod2(name, nil)
}
func (t *itype) lookupMethod2(name string, seen map[*itype]bool) (*node, []int) {
if seen == nil {
seen = map[*itype]bool{}
}
if seen[t] {
return nil, nil
}
seen[t] = true
if t.cat == ptrT {
return t.val.lookupMethod(name)
return t.val.lookupMethod2(name, seen)
}
var index []int
m := t.getMethod(name)
if m == nil {
for i, f := range t.field {
if f.embed {
if n, index2 := f.typ.lookupMethod(name); n != nil {
if n, index2 := f.typ.lookupMethod2(name, seen); n != nil {
index = append([]int{i}, index2...)
return n, index
}
}
}
if t.cat == aliasT || isInterfaceSrc(t) && t.val != nil {
return t.val.lookupMethod(name)
return t.val.lookupMethod2(name, seen)
}
}
return m, index
@@ -1542,12 +1580,23 @@ func (t *itype) methodDepth(name string) int {
// LookupBinMethod returns a method and a path to access a field in a struct object (the receiver).
func (t *itype) lookupBinMethod(name string) (m reflect.Method, index []int, isPtr, ok bool) {
return t.lookupBinMethod2(name, nil)
}
func (t *itype) lookupBinMethod2(name string, seen map[*itype]bool) (m reflect.Method, index []int, isPtr, ok bool) {
if seen == nil {
seen = map[*itype]bool{}
}
if seen[t] {
return
}
seen[t] = true
if t.cat == ptrT {
return t.val.lookupBinMethod(name)
return t.val.lookupBinMethod2(name, seen)
}
for i, f := range t.field {
if f.embed {
if m2, index2, isPtr2, ok2 := f.typ.lookupBinMethod(name); ok2 {
if m2, index2, isPtr2, ok2 := f.typ.lookupBinMethod2(name, seen); ok2 {
index = append([]int{i}, index2...)
return m2, index, isPtr2, ok2
}
@@ -1610,8 +1659,19 @@ type fieldRebuild struct {
}
type refTypeContext struct {
defined map[string]*itype
refs map[string][]fieldRebuild
defined map[string]*itype
// refs keeps track of all the places (in the same type recursion) where the
// type name (as key) is used as a field of another (or possibly the same) struct
// type. Each of these fields will then live as an unsafe2.dummy type until the
// whole recursion is fully resolved, and the type is fixed.
refs map[string][]fieldRebuild
// When we detect for the first time that we are in a recursive type (thanks to
// defined), we keep track of the first occurrence of the type where the recursion
// started, so we can restart the last step that fixes all the types from the same
// "top-level" point.
rect *itype
rebuilding bool
}
@@ -1620,12 +1680,57 @@ func (c *refTypeContext) Clone() *refTypeContext {
return &refTypeContext{defined: c.defined, refs: c.refs, rebuilding: c.rebuilding}
}
func (c *refTypeContext) isComplete() bool {
for _, t := range c.defined {
if t.rtype == nil {
return false
}
}
return true
}
func (t *itype) fixDummy(typ reflect.Type) reflect.Type {
if typ == unsafe2.DummyType {
return t.rtype
}
switch typ.Kind() {
case reflect.Array:
return reflect.ArrayOf(typ.Len(), t.fixDummy(typ.Elem()))
case reflect.Chan:
return reflect.ChanOf(typ.ChanDir(), t.fixDummy(typ.Elem()))
case reflect.Func:
in := make([]reflect.Type, typ.NumIn())
for i := range in {
in[i] = t.fixDummy(typ.In(i))
}
out := make([]reflect.Type, typ.NumOut())
for i := range out {
out[i] = t.fixDummy(typ.Out(i))
}
return reflect.FuncOf(in, out, typ.IsVariadic())
case reflect.Map:
return reflect.MapOf(t.fixDummy(typ.Key()), t.fixDummy(typ.Elem()))
case reflect.Ptr:
return reflect.PtrTo(t.fixDummy(typ.Elem()))
case reflect.Slice:
return reflect.SliceOf(t.fixDummy(typ.Elem()))
case reflect.Struct:
fields := make([]reflect.StructField, typ.NumField())
for i := range fields {
fields[i] = typ.Field(i)
fields[i].Type = t.fixDummy(fields[i].Type)
}
return reflect.StructOf(fields)
}
return typ
}
// RefType returns a reflect.Type representation from an interpreter type.
// In simple cases, reflect types are directly mapped from the interpreter
// counterpart.
// For recursive named struct or interfaces, as reflect does not permit to
// create a recursive named struct, a nil type is set temporarily for each recursive
// field. When done, the nil type fields are updated with the original reflect type
// create a recursive named struct, a dummy type is set temporarily for each recursive
// field. When done, the dummy type fields are updated with the original reflect type
// pointer using unsafe. We thus obtain a usable recursive type definition, except
// for string representation, as created reflect types are still unnamed.
func (t *itype) refType(ctx *refTypeContext) reflect.Type {
@@ -1647,14 +1752,23 @@ func (t *itype) refType(ctx *refTypeContext) reflect.Type {
return t.rtype
}
if dt := ctx.defined[name]; dt != nil {
// We get here when we are a struct field, and our type name has already been
// seen at least once in one of our englobing structs. i.e. there's at least one
// level of type recursion.
if dt.rtype != nil {
t.rtype = dt.rtype
return dt.rtype
}
// To indicate that a rebuild is needed on the nearest struct
// field, create an entry with a nil type.
// The recursion has not been fully resolved yet.
// To indicate that a rebuild is needed on the englobing struct,
// return a dummy field type and create an entry with an empty fieldRebuild.
flds := ctx.refs[name]
ctx.rect = dt
// We know we are used as a field by someone, but we don't know by who
// at this point in the code, so we just mark it as an empty fieldRebuild for now.
// We'll complete the fieldRebuild in the caller.
ctx.refs[name] = append(flds, fieldRebuild{})
return unsafe2.DummyType
}
@@ -1697,9 +1811,8 @@ func (t *itype) refType(ctx *refTypeContext) reflect.Type {
}
var fields []reflect.StructField
for i, f := range t.field {
fctx := ctx.Clone()
field := reflect.StructField{
Name: exportName(f.name), Type: f.typ.refType(fctx),
Name: exportName(f.name), Type: f.typ.refType(ctx),
Tag: reflect.StructTag(f.tag), Anonymous: f.embed,
}
fields = append(fields, field)
@@ -1712,13 +1825,35 @@ func (t *itype) refType(ctx *refTypeContext) reflect.Type {
}
}
}
fieldFix := []int{} // Slice of field indices to fix for recursivity.
t.rtype = reflect.StructOf(fields)
if ctx.isComplete() {
for _, s := range ctx.defined {
for i := 0; i < s.rtype.NumField(); i++ {
f := s.rtype.Field(i)
if strings.HasSuffix(f.Type.String(), "unsafe2.dummy") {
unsafe2.SetFieldType(s.rtype, i, ctx.rect.fixDummy(s.rtype.Field(i).Type))
if name == s.path+"/"+s.name {
fieldFix = append(fieldFix, i)
}
}
}
}
}
// The rtype has now been built, we can go back and rebuild
// all the recursive types that relied on this type.
// However, as we are keyed by type name, if two or more (recursive) fields at
// the same depth level are of the same type, or a "variation" of the same type
// (slice of, map of, etc), they "mask" each other, and only one
// of them is in ctx.refs. That is why the code around here is a bit convoluted,
// and we need both the loop above, around all the struct fields, and the loop
// below, around the ctx.refs.
for _, f := range ctx.refs[name] {
ftyp := f.typ.field[f.idx].typ.refType(&refTypeContext{defined: ctx.defined, rebuilding: true})
unsafe2.SwapFieldType(f.typ.rtype, f.idx, ftyp)
for _, index := range fieldFix {
ftyp := f.typ.field[index].typ.refType(&refTypeContext{defined: ctx.defined, rebuilding: true})
unsafe2.SetFieldType(f.typ.rtype, index, ftyp)
}
}
default:
if z, _ := t.zero(); z.IsValid() {

View File

@@ -3,6 +3,7 @@ package interp
import (
"errors"
"go/constant"
"go/token"
"math"
"reflect"
)
@@ -53,7 +54,7 @@ func (check typecheck) assignment(n *node, typ *itype, context string) error {
return nil
}
if !n.typ.assignableTo(typ) {
if !n.typ.assignableTo(typ) && typ.str != "*unsafe2.dummy" {
if context == "" {
return n.cfgErrorf("cannot use type %s as type %s", n.typ.id(), typ.id())
}
@@ -124,6 +125,8 @@ func (check typecheck) starExpr(n *node) error {
}
var unaryOpPredicates = opPredicates{
aInc: isNumber,
aDec: isNumber,
aPos: isNumber,
aNeg: isNumber,
aBitNot: isInt,
@@ -133,6 +136,9 @@ var unaryOpPredicates = opPredicates{
// unaryExpr type checks a unary expression.
func (check typecheck) unaryExpr(n *node) error {
c0 := n.child[0]
if isBlank(c0) {
return n.cfgErrorf("cannot use _ as value")
}
t0 := c0.typ.TypeOf()
if n.action == aRecv {
@@ -196,7 +202,7 @@ func (check typecheck) comparison(n *node) error {
if typ.isNil() {
typ = c1.typ
}
return n.cfgErrorf("invalid operation: operator %v not defined on %s", n.action, typ.id(), ".")
return n.cfgErrorf("invalid operation: operator %v not defined on %s", n.action, typ.id())
}
return nil
}
@@ -221,6 +227,10 @@ var binaryOpPredicates = opPredicates{
func (check typecheck) binaryExpr(n *node) error {
c0, c1 := n.child[0], n.child[1]
if isBlank(c0) || isBlank(c1) {
return n.cfgErrorf("cannot use _ as value")
}
a := n.action
if isAssignAction(a) {
a--
@@ -476,6 +486,12 @@ func (check typecheck) structBinLitExpr(child []*node, typ reflect.Type) error {
// sliceExpr type checks a slice expression.
func (check typecheck) sliceExpr(n *node) error {
for _, c := range n.child {
if isBlank(c) {
return n.cfgErrorf("cannot use _ as value")
}
}
c, child := n.child[0], n.child[1:]
t := c.typ.TypeOf()
@@ -591,6 +607,12 @@ func (check typecheck) typeAssertionExpr(n *node, typ *itype) error {
continue
}
if tm == nil {
// Lookup for non-exported methods is impossible
// for bin types, ignore them as they can't be used
// directly by the interpreted programs.
if !token.IsExported(name) && isBin(typ) {
continue
}
return n.cfgErrorf("impossible type assertion: %s does not implement %s (missing %v method)", typ.id(), n.typ.id(), name)
}
if tm.recv != nil && tm.recv.TypeOf().Kind() == reflect.Ptr && typ.TypeOf().Kind() != reflect.Ptr {
@@ -850,7 +872,7 @@ func (check typecheck) builtin(name string, n *node, child []*node, ellipsis boo
return params[0].nod.cfgErrorf("first argument to delete must be map; have %s", typ.id())
}
ktyp := params[1].Type()
if !ktyp.assignableTo(typ.key) {
if typ.key != nil && !ktyp.assignableTo(typ.key) {
return params[1].nod.cfgErrorf("cannot use %s as type %s in delete", ktyp.id(), typ.key.id())
}
case bltnMake:

View File

@@ -81,8 +81,14 @@ func genValueBinMethodOnInterface(n *node, defaultGen func(*frame) reflect.Value
}
func genValueRecvIndirect(n *node) func(*frame) reflect.Value {
v := genValueRecv(n)
return func(f *frame) reflect.Value { return v(f).Elem() }
vr := genValueRecv(n)
return func(f *frame) reflect.Value {
v := vr(f)
if vi, ok := v.Interface().(valueInterface); ok {
return vi.value
}
return v.Elem()
}
}
func genValueRecv(n *node) func(*frame) reflect.Value {
@@ -141,7 +147,7 @@ func genValueAsFunctionWrapper(n *node) func(*frame) reflect.Value {
return reflect.New(typ).Elem()
}
vn, ok := v.Interface().(*node)
if ok && vn.rval.IsValid() && vn.rval.Type().Kind() == reflect.Func {
if ok && vn.rval.Kind() == reflect.Func {
// The node value is already a callable func, no need to wrap it.
return vn.rval
}
@@ -154,7 +160,7 @@ func genValueAs(n *node, t reflect.Type) func(*frame) reflect.Value {
return func(f *frame) reflect.Value {
v := value(f)
switch v.Type().Kind() {
switch v.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice, reflect.UnsafePointer:
if v.IsNil() {
return reflect.New(t).Elem()
@@ -213,7 +219,7 @@ func genValue(n *node) func(*frame) reflect.Value {
func genDestValue(typ *itype, n *node) func(*frame) reflect.Value {
convertLiteralValue(n, typ.TypeOf())
switch {
case isInterfaceSrc(typ) && !isEmptyInterface(typ):
case isInterfaceSrc(typ) && (!isEmptyInterface(typ) || len(n.typ.method) > 0):
return genValueInterface(n)
case isFuncSrc(typ) && (n.typ.cat == valueT || n.typ.cat == nilT):
return genValueNode(n)
@@ -312,7 +318,7 @@ func genValueInterface(n *node) func(*frame) reflect.Value {
}
// empty interface, do not wrap.
if nod.typ.cat == interfaceT && len(nod.typ.field) == 0 {
if nod != nil && isEmptyInterface(nod.typ) {
return v
}
@@ -332,13 +338,13 @@ func getConcreteValue(val reflect.Value) reflect.Value {
if v.NumMethod() > 0 {
return v
}
if v.Type().Kind() != reflect.Struct {
if v.Kind() != reflect.Struct {
return v
}
// Search a concrete value in fields of an emulated interface.
for i := v.NumField() - 1; i >= 0; i-- {
vv := v.Field(i)
if vv.Type().Kind() == reflect.Interface {
if vv.Kind() == reflect.Interface {
vv = vv.Elem()
}
if vv.IsValid() {
@@ -402,7 +408,7 @@ func genValueInterfaceValue(n *node) func(*frame) reflect.Value {
return func(f *frame) reflect.Value {
v := value(f)
if v.Interface().(valueInterface).node == nil {
if vi, ok := v.Interface().(valueInterface); ok && vi.node == nil {
// Uninitialized interface value, set it to a correct zero value.
v.Set(zeroInterfaceValue())
v = value(f)
@@ -415,7 +421,11 @@ func genValueNode(n *node) func(*frame) reflect.Value {
value := genValue(n)
return func(f *frame) reflect.Value {
return reflect.ValueOf(&node{rval: value(f)})
v := value(f)
if _, ok := v.Interface().(*node); ok {
return v
}
return reflect.ValueOf(&node{rval: v})
}
}
@@ -424,7 +434,7 @@ func vInt(v reflect.Value) (i int64) {
i, _ = constant.Int64Val(constant.ToInt(c))
return i
}
switch v.Type().Kind() {
switch v.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
i = v.Int()
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
@@ -442,7 +452,7 @@ func vUint(v reflect.Value) (i uint64) {
i, _ = constant.Uint64Val(constant.ToInt(c))
return i
}
switch v.Type().Kind() {
switch v.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
i = uint64(v.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
@@ -462,7 +472,7 @@ func vComplex(v reflect.Value) (c complex128) {
img, _ := constant.Float64Val(constant.Imag(c))
return complex(rel, img)
}
switch v.Type().Kind() {
switch v.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
c = complex(float64(v.Int()), 0)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
@@ -480,7 +490,7 @@ func vFloat(v reflect.Value) (i float64) {
i, _ = constant.Float64Val(constant.ToFloat(c))
return i
}
switch v.Type().Kind() {
switch v.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
i = float64(v.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
@@ -585,5 +595,6 @@ func genComplex(n *node) func(*frame) complex128 {
func genValueString(n *node) func(*frame) (reflect.Value, string) {
value := genValue(n)
return func(f *frame) (reflect.Value, string) { v := value(f); return v, v.String() }
}

View File

@@ -47,8 +47,12 @@ type _compress_flate_Reader struct {
WReadByte func() (byte, error)
}
func (W _compress_flate_Reader) Read(p []byte) (n int, err error) { return W.WRead(p) }
func (W _compress_flate_Reader) ReadByte() (byte, error) { return W.WReadByte() }
func (W _compress_flate_Reader) Read(p []byte) (n int, err error) {
return W.WRead(p)
}
func (W _compress_flate_Reader) ReadByte() (byte, error) {
return W.WReadByte()
}
// _compress_flate_Resetter is an interface wrapper for Resetter type
type _compress_flate_Resetter struct {
@@ -56,4 +60,6 @@ type _compress_flate_Resetter struct {
WReset func(r io.Reader, dict []byte) error
}
func (W _compress_flate_Resetter) Reset(r io.Reader, dict []byte) error { return W.WReset(r, dict) }
func (W _compress_flate_Resetter) Reset(r io.Reader, dict []byte) error {
return W.WReset(r, dict)
}

View File

@@ -44,4 +44,6 @@ type _compress_zlib_Resetter struct {
WReset func(r io.Reader, dict []byte) error
}
func (W _compress_zlib_Resetter) Reset(r io.Reader, dict []byte) error { return W.WReset(r, dict) }
func (W _compress_zlib_Resetter) Reset(r io.Reader, dict []byte) error {
return W.WReset(r, dict)
}

View File

@@ -36,8 +36,18 @@ type _container_heap_Interface struct {
WSwap func(i int, j int)
}
func (W _container_heap_Interface) Len() int { return W.WLen() }
func (W _container_heap_Interface) Less(i int, j int) bool { return W.WLess(i, j) }
func (W _container_heap_Interface) Pop() interface{} { return W.WPop() }
func (W _container_heap_Interface) Push(x interface{}) { W.WPush(x) }
func (W _container_heap_Interface) Swap(i int, j int) { W.WSwap(i, j) }
func (W _container_heap_Interface) Len() int {
return W.WLen()
}
func (W _container_heap_Interface) Less(i int, j int) bool {
return W.WLess(i, j)
}
func (W _container_heap_Interface) Pop() interface{} {
return W.WPop()
}
func (W _container_heap_Interface) Push(x interface{}) {
W.WPush(x)
}
func (W _container_heap_Interface) Swap(i int, j int) {
W.WSwap(i, j)
}

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