Compare commits

...

37 Commits

Author SHA1 Message Date
Marc Vertes
b0cd93a936 fix: correct interrupt signal handling in REPL
Avoid goroutines leak, accumulation of defered functions and
spurious resets of signal handlers. Effectively catch interrupt
signal (Ctrl-C) to cancel current eval.

Fixes #713.
2020-08-12 22:22:03 +02:00
mpl
611a8c37fa interp: make REPL stricter about parsing errors
So far the REPL loop was treating any parsing error coming from
go/parser to generate the AST, as having occurred because the source
code was not yet complete (unfinished block). And it was therefore
ignoring all of them.

However, some of these errors are legitimate, and must be caught as soon
as they occur, otherwise the REPL cycle would stay in an errored state
forever (even when the block terminates), without the user getting any
feedback about it.

Therefore, this change adds an extra check when a parsing error occurs,
i.e. it verifies that it looks like an "EOF" error (unfinished block)
before it ignores it (as the user is supposed to terminate the block
eventually). Otherwise the error is treated just like a "non-parsing"
(cfg, gta, ...) error and printed out.

Fixes #637
2020-08-12 18:44:21 +02:00
Marc Vertes
e71ddc7edd chore: update golangci-lint config (#810)
Disable nlreturn. Fix one comment.
2020-08-12 17:46:28 +02:00
Ludovic Fernandez
1fe75f149d feat: update stdlib mapping for go1.15
- drop stdlib for go1.13
- generate stdlib for go1.15
-  update CI configuration.
2020-08-12 12:38:04 +02:00
Nicholas Wiersma
cdc352cee2 feat: add index and composite literal type checking
This adds type checking to both `IndexExpr` and `CompositeLitExpr` as well as handling any required constant type conversion.

This includes a change to the type propagation to the children of a composite literal. Previously in most cases the composite literal type was propagated to its children. This does not work with type checking as the actual child type is needed.
2020-08-11 15:58:04 +02:00
Nicholas Wiersma
88569f5df7 fix: interface call regression from #787
Fix #787 changes how interfaces are set on a struct (compositeSparce). This change however makes calling the interface panic. 

This PR reverts part of the change in #787 and adds a test to ensure it does not break again.
2020-08-10 16:32:05 +02:00
Marc Vertes
2ac0c6f70b feature: command line provide sub-commands
The Yaegi command line has been changed to provide subcommands.

The following sub-commands are provided:
- extract (formerly goexports)
- help
- run
- test

The previous behaviour is now implemented in run command which
is the default, so the change should be transparent.

In run command, prepare the ability to run a package or a directory
in addition to a file. Not implemented yet

The test command is not implemented yet.

The extract command is meant to generate wrappers to non stdlib
packages.

Fixes #639
2020-08-10 16:20:05 +02:00
Nicholas Wiersma
bd4ce37baa feat: refactor type checking
The previous type checking was off and did not do untyped type conversion. This endeavours to fix this with better type checking in its own type.
2020-07-31 14:00:03 +02:00
Nicholas Wiersma
9c4d3d1e5a chore: updated linter 2020-07-30 11:18:04 +02:00
Marc Vertes
25c681c1e6 fix: regression on range following #787 2020-07-30 10:52:05 +02:00
Marc Vertes
9c51f6bb69 fix: correct range on arrays of interface objects 2020-07-23 12:25:04 +02:00
Marc Vertes
589b2a0cd2 fix: correct conversion to int in slice index expressions. 2020-07-23 12:05:03 +02:00
Marc Vertes
68911f8b4e fix: type assertion expression was not forwarding type
The detection of special cases of interpreter functions and interfaces
is more precise. It allows at least to complete parsing of
code where type is derived from a type assertion expression.

Fixes #770.
2020-07-23 11:51:53 +02:00
mpl
e5a7b0de11 extract: new package to extract symbols from a dependency
cmd/goexports is now based on it.

Updates #639 

Co-authored-by: Marc Vertes <mvertes@free.fr>
2020-07-23 10:58:33 +02:00
mpl
563270ca02 interp: support yet another vendoring case
* interp: support another vendoring case

Namely, when the vendor dir is a sibling (or an uncle) relative to the
current pkg

Fixes #758

* make linter happier

* address review comments

* fix, cleanup, add unit tests

* add dummy files to force dirs into git
2020-07-15 15:35:04 +02:00
Marc Vertes
5eecbe515b fix: compositeSparse handles fields of interface kind
Fixes #776.
2020-07-13 17:55:04 +02:00
Marc Vertes
0a79069dfc fix: correct control flow graph for range init expression
The range init AST execution was skipped, and range could work
only over variables or direct function calls. By setting the
start node to the start of init and not init itself, we ensure
that the init AST is always taken into account.

Fixes #775.
2020-07-13 15:35:04 +02:00
Marc Vertes
0c8f538cd9 fix: apply method receiver offset when generating interface wrapper
Fixes #772.
2020-07-12 14:20:03 +02:00
Nicholas Wiersma
ca80ada849 fix: deal with untyped in type check 2020-07-10 11:55:04 +02:00
Nicholas Wiersma
3c6df504df fix: dont allow calling init 2020-07-09 14:35:04 +02:00
Nicholas Wiersma
98eacf3610 fix: execute global variables in the correct order
* fix: constant definition loop on out of order vars

* fix: do not wire global varDecl

* fix: wire and execute global vars

* chore: add tests

* fix: refactor and lint
2020-07-09 14:05:03 +02:00
Marc Vertes
16ff52a949 fix: avoid a panic in CFG in case of incomplete type
By returning early in case of incomplete type in CFG, we avoid
a panic, and let a chance to a new attempt after the missing
type has been parsed.

Fixes 763.
2020-07-09 13:05:04 +02:00
Marc Vertes
640d1429e5 fix: type assert when status is _
If the status is _, there is no storage allocated in frame, and
the status assign operation should be skipped.

Fixes #761.
2020-07-09 08:45:03 +02:00
Nicholas Wiersma
659913eebe fix: convert type properly to the correct type 2020-07-08 22:55:03 +02:00
Marc Vertes
b3766509cc feature: restrict symbols which can exit the interpreter process
* feature: restrict symbols which can exit the interpreter process

Some symbols such as os.Exit or log.Fatal, which make the current process
to exit, are now restricted. They are replaced by a version which panics
instead of exiting, as panics are recovered by Eval.

The restricted os.FindProcess version is identical to the original
except it errors when trying to return the self process, in order to
forbid killing or signaling the interpreter process from script.

The os/exec symbols are available only through unrestricted package.

The original symbols are stored in an unrestricted package, which
requires an explicit Use, as for unsafe and syscall packages.

The Use() interpreter method has been slightly modified to allow inplace
updating of package symbols, allowing to replace some symbols but not
the entire imported package.

A command line option -unrestricted has been added to yaegi CLI to use
the unrestricted symbols.

Fixes #486.

* fix: lint
2020-07-08 22:35:04 +02:00
Nicholas Wiersma
bc2b224bae fix: make a copy of defined before detecting recursivness 2020-07-07 12:05:03 +02:00
Nicholas Wiersma
9d4685deea fix: handle interfaces in composite sparce (#749)
Co-authored-by: Marc Vertes <mvertes@free.fr>
2020-07-06 15:41:27 +02:00
Marc Vertes
2a70a71dc2 fix: avoid infinite loop when parsing recursive types
Mark all visited types as such when walking down struct fields.

Fixes #750.
Updates #652.
2020-07-06 15:30:04 +02:00
Nicholas Wiersma
851444453c fix: assert switch type from valueT in struct case (#747)
* fix: switch type from valueT in struct case

In a struct case in type assertion, if the source is a valueT, we still need to take the struct type to allow method and field resolution.

* fix: handle all ptr structs as well
2020-07-06 15:09:48 +02:00
Nicholas Wiersma
a8b1c2a017 fix: a const, non-const equality check must convert 2020-07-06 11:25:03 +02:00
Nicholas Wiersma
d229c2a2c7 fix: handle Func Value in genFunctionWrapper params
* fix: handle Func value in func wrapper params

* fix: lint
2020-07-03 12:25:04 +02:00
Marc Vertes
2f2df7a0f8 fix: avoid memory errors by handling frame indirections (#739)
In all situations where the results are set directly
to the frame, and not using a value helper, the right level of
indirections must be applied, otherwise we may end-up writing
in the wrong frame (the local one, instead of a caller or global).

Fixes #735.
2020-07-03 11:02:46 +02:00
Nicholas Wiersma
4058fd8c44 fix: do type check on assignment (#738)
* fix: do type check on assignment

* fix: check for recursive type rather than field
2020-07-03 10:28:51 +02:00
Nicholas Wiersma
097a745e72 fix: variadic interface conversion in call/callBin 2020-07-02 23:55:03 +02:00
Nicholas Wiersma
1f514e63a8 fix: switch always compare like types 2020-07-02 23:35:03 +02:00
Nicholas Wiersma
a15ecb7176 feature: handle nested recursion
* fix: copy the type in recursion

In more advanced recursive cases, setting the rtype to interface may interfear with typeing. To stop this from happening, instead of setting t.val.rtype to interface in the hope it will be set correctly later, a copy if the type is made, and the rtype of the copy is set to interface{}.

* fix: detect intermediate recursive structs

In the case of a nested recussion, each symbol can have
a different perspective on the recursion. In this case,
it is impossible to move from one struct to the next.
To keep the perspectives the same, any intermediate struct
that contains a recursion should also be set to interface{}.
so that all perspectives are the same.

* fix: handle arb recursion

* chore: refactor dref to be consistent

* fix: invalid recursive struct issue

* fix: handle checkptr issue

* fix: move unsafe into function to stop ptr check

* fix: handle deref in assign
2020-07-02 23:20:03 +02:00
Marc Vertes
d4aa84f729 fix: set frame level in destination nodes to avoid memory corruption (#733)
When operations write their result to a non-local frame, the node
level field must be set accordingly, otherwise they attempt to write
in the wrong frame.

Fixes #730.
2020-07-02 10:03:32 +02:00
460 changed files with 9541 additions and 7384 deletions

View File

@@ -41,6 +41,8 @@
"testpackage",
"goerr113",
"nestif",
"exhaustive",
"nlreturn",
]
[issues]

View File

@@ -19,8 +19,8 @@ cache:
matrix:
fast_finish: true
include:
- go: 1.13.x
- go: 1.14.x
- go: 1.15.x
env: STABLE=true
env:

25
_test/addr0.go Normal file
View File

@@ -0,0 +1,25 @@
package main
import (
"fmt"
"net/http"
)
type extendedRequest struct {
http.Request
Data string
}
func main() {
r := extendedRequest{}
req := &r.Request
fmt.Println(r)
fmt.Println(req)
}
// Output:
// {{ <nil> 0 0 map[] <nil> <nil> 0 [] false map[] map[] <nil> map[] <nil> <nil> <nil> <nil>} }
// &{ <nil> 0 0 map[] <nil> <nil> 0 [] false map[] map[] <nil> map[] <nil> <nil> <nil> <nil>}

16
_test/assign14.go Normal file
View File

@@ -0,0 +1,16 @@
package main
var optionsG map[string]string = nil
var roundG = 30
func main() {
dummy := roundG
roundG = dummy + 1
println(roundG)
println(optionsG == nil)
}
// Output:
// 31
// true

11
_test/assign15.go Normal file
View File

@@ -0,0 +1,11 @@
package main
func main() {
var c chan<- struct{} = make(chan struct{})
var d <-chan struct{} = c
_ = d
}
// Error:
// _test/assign15.go:5:26: cannot use type chan<- struct{} as type <-chan struct{} in assignment

14
_test/comp2.go Normal file
View File

@@ -0,0 +1,14 @@
package main
type delta int32
func main() {
a := delta(-1)
println(a != -1)
println(a == -1)
}
// Output:
// false
// true

15
_test/composite11.go Normal file
View File

@@ -0,0 +1,15 @@
package main
import (
"fmt"
"image/color"
)
func main() {
c := color.NRGBA64{1, 1, 1, 1}
fmt.Println(c)
}
// Output:
// {1 1 1 1}

17
_test/const15.go Normal file
View File

@@ -0,0 +1,17 @@
package main
type T1 t1
type t1 int8
const (
P2 T1 = 2
P3 T1 = 3
)
func main() {
println(P3)
}
// Output:
// 3

12
_test/init1.go Normal file
View File

@@ -0,0 +1,12 @@
package main
func init() {
println("here")
}
func main() {
init()
}
// Error:
// _test/init1.go:8:2: undefined: init

19
_test/interface44.go Normal file
View File

@@ -0,0 +1,19 @@
package main
type S struct {
a int
}
func main() {
var i interface{} = S{a: 1}
s, ok := i.(S)
if !ok {
println("bad")
return
}
println(s.a)
}
// Output:
// 1

13
_test/interface45.go Normal file
View File

@@ -0,0 +1,13 @@
package main
import "fmt"
func main() {
var i interface{} = 1
var s struct{}
s, _ = i.(struct{})
fmt.Println(s)
}
// Output:
// {}

35
_test/interface46.go Normal file
View File

@@ -0,0 +1,35 @@
package main
import "fmt"
type I interface {
Foo() string
}
type Printer struct {
i I
}
func New(i I) *Printer {
return &Printer{
i: i,
}
}
func (p *Printer) Print() {
fmt.Println(p.i.Foo())
}
type T struct{}
func (t *T) Foo() string {
return "test"
}
func main() {
g := New(&T{})
g.Print()
}
// Output:
// test

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

@@ -0,0 +1,29 @@
package main
import (
"fmt"
"strconv"
)
var optionsG map[string]string
var roundG int = 30
func strToInt(s string, defaultValue int) int {
n, err := strconv.ParseInt(s, 10, 0)
if err != nil {
return defaultValue
}
return int(n)
}
func main() {
optionsG := map[string]string{"round": "12", "b": "one"}
roundG = strToInt(optionsG["round"], 50)
fmt.Println(roundG)
fmt.Println(optionsG)
}
// Output:
// 12
// map[b:one round:12]

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

@@ -0,0 +1,25 @@
package main
import "reflect"
type I interface {
Foo() int
}
type T struct {
Name string
}
func (t T) Foo() int { return 0 }
func f(v reflect.Value) int {
i := v.Interface().(I)
return i.Foo()
}
func main() {
println("hello")
}
// Output:
// hello

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

@@ -0,0 +1,31 @@
package main
import (
"log"
"os"
"text/template"
)
type Message struct {
Data string
}
func main() {
tmpl := template.New("name")
_, err := tmpl.Parse("{{.Data}}")
if err != nil {
log.Fatal(err)
}
err = tmpl.Execute(os.Stdout, Message{
Data: "Hello, World!!",
})
if err != nil {
log.Fatal(err)
}
}
// Output:
// Hello, World!!

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

@@ -0,0 +1,18 @@
package main
import (
"fmt"
"net/http/httptest"
)
func main() {
recorder := httptest.NewRecorder()
recorder.Header().Add("Foo", "Bar")
for key, value := range recorder.Header() {
fmt.Println(key, value)
}
}
// Output:
// Foo [Bar]

39
_test/issue-776.go Normal file
View File

@@ -0,0 +1,39 @@
package main
import "fmt"
// Filter is a filter
type Filter interface {
Foo()
}
// GIFT is a gift
type GIFT struct {
Filters []Filter
}
// New is a new filter list
func New(filters ...Filter) *GIFT {
return &GIFT{
Filters: filters,
}
}
// List lists filters
func (g *GIFT) List() {
fmt.Printf("Hello from List!\n")
}
// MyFilter is one of the filters
type MyFilter struct{}
// Foo is a foo
func (f *MyFilter) Foo() {}
func main() {
g := New(&MyFilter{})
g.List()
}
// Output:
// Hello from List!

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

@@ -0,0 +1,14 @@
package main
import "fmt"
func main() {
a := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
from := uint32(2)
to := uint32(4)
b := a[from:to]
fmt.Print(b)
}
// Output:
// [3 4]

39
_test/issue-784.go Normal file
View File

@@ -0,0 +1,39 @@
package main
import "fmt"
// Filter is a filter interface
type Filter interface {
Bounds(srcBounds string) (dstBounds string)
}
// GIFT type
type GIFT struct {
Filters []Filter
}
// New creates a new filter list and initializes it with the given slice of filters.
func New(filters ...Filter) *GIFT {
return &GIFT{
Filters: filters,
}
}
// Bounds calculates the appropriate bounds for the result image after applying all the added filters.
func (g *GIFT) Bounds(srcBounds string) (dstBounds string) {
dstBounds = srcBounds
for _, f := range g.Filters {
dstBounds = f.Bounds(dstBounds)
}
return dstBounds
}
func main() {
var filters []Filter
bounds := "foo"
g := New(filters...)
fmt.Println(g.Bounds(bounds))
}
// Output:
// foo

12
_test/math2.go Normal file
View File

@@ -0,0 +1,12 @@
package main
const c uint64 = 2
func main() {
if c&(1<<(uint64(1))) > 0 {
println("yes")
}
}
// Output:
// yes

32
_test/math3.go Normal file
View File

@@ -0,0 +1,32 @@
package main
import (
"crypto/md5"
"fmt"
)
func md5Crypt(password, salt, magic []byte) []byte {
d := md5.New()
d.Write(password)
d.Write(magic)
d.Write(salt)
d2 := md5.New()
d2.Write(password)
d2.Write(salt)
for i, mixin := 0, d2.Sum(nil); i < len(password); i++ {
d.Write([]byte{mixin[i%16]})
}
return d.Sum(nil)
}
func main() {
b := md5Crypt([]byte("1"), []byte("2"), []byte("3"))
fmt.Println(b)
}
// Output:
// [187 141 73 89 101 229 33 106 226 63 117 234 117 149 230 21]

View File

@@ -7,4 +7,4 @@ func main() {
}
// Error:
// 5:2: illegal operand types for '+=' operator
// 5:2: invalid operation: mismatched types int and float64

18
_test/restricted0.go Normal file
View File

@@ -0,0 +1,18 @@
package main
import (
"fmt"
"log"
)
func main() {
defer func() {
r := recover()
fmt.Println("recover:", r)
}()
log.Fatal("log.Fatal does not exit")
println("not printed")
}
// Output:
// recover: log.Fatal does not exit

18
_test/restricted1.go Normal file
View File

@@ -0,0 +1,18 @@
package main
import (
"fmt"
"os"
)
func main() {
defer func() {
r := recover()
fmt.Println("recover:", r)
}()
os.Exit(1)
println("not printed")
}
// Output:
// recover: os.Exit(1)

14
_test/restricted2.go Normal file
View File

@@ -0,0 +1,14 @@
package main
import (
"fmt"
"os"
)
func main() {
p, err := os.FindProcess(os.Getpid())
fmt.Println(p, err)
}
// Output:
// <nil> restricted

23
_test/restricted3.go Normal file
View File

@@ -0,0 +1,23 @@
package main
import (
"bytes"
"fmt"
"log"
)
var (
buf bytes.Buffer
logger = log.New(&buf, "logger: ", log.Lshortfile)
)
func main() {
defer func() {
r := recover()
fmt.Println("recover:", r, buf.String())
}()
logger.Fatal("test log")
}
// Output:
// recover: test log logger: restricted.go:39: test log

12
_test/rune1.go Normal file
View File

@@ -0,0 +1,12 @@
package main
import "fmt"
func main() {
t := make([]byte, 2)
t[0] = '$'
fmt.Println(t)
}
// Output:
// [36 0]

View File

@@ -1,15 +1,23 @@
package main
import (
"encoding/json"
"os"
)
type S struct {
Child []*S
Name string
Child []*S
}
func main() {
s := &S{Name: "root"}
s.Child = append(s.Child, &S{Name: "child"})
println(s.Child[0].Name)
a := S{Name: "hello"}
a.Child = append(a.Child, &S{Name: "world"})
json.NewEncoder(os.Stdout).Encode(a)
a.Child[0].Child = append([]*S{}, &S{Name: "sunshine"})
json.NewEncoder(os.Stdout).Encode(a)
}
// Output:
// child
// {"Name":"hello","Child":[{"Name":"world","Child":null}]}
// {"Name":"hello","Child":[{"Name":"world","Child":[{"Name":"sunshine","Child":null}]}]}

33
_test/struct49.go Normal file
View File

@@ -0,0 +1,33 @@
package main
type S struct {
ts map[string][]*T
}
type T struct {
s *S
}
func (c *S) getT(addr string) (t *T, ok bool) {
cns, ok := c.ts[addr]
if !ok || len(cns) == 0 {
return nil, false
}
t = cns[len(cns)-1]
c.ts[addr] = cns[:len(cns)-1]
return t, true
}
func main() {
s := &S{
ts: map[string][]*T{},
}
s.ts["test"] = append(s.ts["test"], &T{s: s})
t , ok:= s.getT("test")
println(t != nil, ok)
}
// Output:
// true true

20
_test/struct50.go Normal file
View File

@@ -0,0 +1,20 @@
package main
import "fmt"
type Node struct {
Name string
Child []Node
}
func main() {
a := Node{Name: "hello"}
a.Child = append([]Node{}, Node{Name: "world"})
fmt.Println(a)
a.Child[0].Child = append([]Node{}, Node{Name: "sunshine"})
fmt.Println(a)
}
// Output:
// {hello [{world []}]}
// {hello [{world [{sunshine []}]}]}

23
_test/struct51.go Normal file
View File

@@ -0,0 +1,23 @@
package main
import (
"encoding/json"
"os"
)
type Node struct {
Name string
Child [2]*Node
}
func main() {
a := Node{Name: "hello"}
a.Child[0] = &Node{Name: "world"}
json.NewEncoder(os.Stdout).Encode(a)
a.Child[0].Child[0] = &Node{Name: "sunshine"}
json.NewEncoder(os.Stdout).Encode(a)
}
// Output:
// {"Name":"hello","Child":[{"Name":"world","Child":[null,null]},null]}
// {"Name":"hello","Child":[{"Name":"world","Child":[{"Name":"sunshine","Child":[null,null]},null]},null]}

20
_test/struct52.go Normal file
View File

@@ -0,0 +1,20 @@
package main
import "fmt"
type Node struct {
Name string
Child map[string]Node
}
func main() {
a := Node{Name: "hello", Child: map[string]Node{}}
a.Child["1"] = Node{Name: "world", Child: map[string]Node{}}
fmt.Println(a)
a.Child["1"].Child["1"] = Node{Name: "sunshine", Child: map[string]Node{}}
fmt.Println(a)
}
// Output:
// {hello map[1:{world map[]}]}
// {hello map[1:{world map[1:{sunshine map[]}]}]}

23
_test/struct53.go Normal file
View File

@@ -0,0 +1,23 @@
package main
import "fmt"
type T1 struct {
P []*T
}
type T2 struct {
P2 *T
}
type T struct {
*T1
S1 *T
}
func main() {
fmt.Println(T2{})
}
// Output:
// {<nil>}

26
_test/struct54.go Normal file
View File

@@ -0,0 +1,26 @@
package main
type S struct {
t *T
}
func newS() *S {
return &S{
t: &T{u: map[string]*U{}},
}
}
type T struct {
u map[string]*U
}
type U struct {
a int
}
func main() {
s := newS()
_ = s
println("ok")
}

28
_test/switch38.go Normal file
View File

@@ -0,0 +1,28 @@
package main
func isSeparator(c byte) bool {
switch c {
case '(', ')', '<', '>', '@', ',', ';', ':', '\\', '"', '/', '[', ']', '?', '=', '{', '}', ' ', '\t':
return true
}
return false
}
func main() {
s := "max-age=20"
for _, c := range []byte(s) {
println(string(c), isSeparator(c))
}
}
// Output:
// m false
// a false
// x false
// - false
// a false
// g false
// e false
// = true
// 2 false
// 0 false

View File

@@ -1,5 +1,5 @@
// The following comment line has the same effect as 'go run -tags=dummy'
//yaegi:tags dummy
// yaegi:tags dummy
package main

18
_test/time13.go Normal file
View File

@@ -0,0 +1,18 @@
package main
import (
"fmt"
"time"
)
var dummy = 1
var t time.Time = time.Date(2007, time.November, 10, 23, 4, 5, 0, time.UTC)
func main() {
t = time.Date(2009, time.November, 10, 23, 4, 5, 0, time.UTC)
fmt.Println(t.Clock())
}
// Output:
// 23 4 5

44
_test/type25.go Normal file
View File

@@ -0,0 +1,44 @@
package main
import (
"errors"
"sync/atomic"
)
type wrappedError struct {
wrapped error
}
func (e wrappedError) Error() string {
return "some outer error"
}
func (e wrappedError) Unwrap() error {
return e.wrapped
}
var err atomic.Value
func getWrapped() *wrappedError {
if v := err.Load(); v != nil {
err := v.(wrappedError)
if err.wrapped != nil {
return &err
}
}
return nil
}
func main() {
err.Store(wrappedError{wrapped: errors.New("test")})
e := getWrapped()
if e != nil {
println(e.Error())
println(e.wrapped.Error())
}
}
// Output:
// some outer error
// test

44
_test/type26.go Normal file
View File

@@ -0,0 +1,44 @@
package main
import (
"errors"
"sync/atomic"
)
type wrappedError struct {
wrapped error
}
func (e *wrappedError) Error() string {
return "some outer error"
}
func (e *wrappedError) Unwrap() error {
return e.wrapped
}
var err atomic.Value
func getWrapped() *wrappedError {
if v := err.Load(); v != nil {
err := v.(*wrappedError)
if err.wrapped != nil {
return err
}
}
return nil
}
func main() {
err.Store(&wrappedError{wrapped: errors.New("test")})
e := getWrapped()
if e != nil {
println(e.Error())
println(e.wrapped.Error())
}
}
// Output:
// some outer error
// test

13
_test/var12.go Normal file
View File

@@ -0,0 +1,13 @@
package main
var (
a = b
b = "hello"
)
func main() {
println(a)
}
// Output:
// hello

23
_test/var13.go Normal file
View File

@@ -0,0 +1,23 @@
package main
var (
a = concat("hello", b)
b = concat(" ", c, "!")
c = d
d = "world"
)
func concat(a ...string) string {
var s string
for _, ss := range a {
s += ss
}
return s
}
func main() {
println(a)
}
// Output:
// hello world!

10
_test/var14.go Normal file
View File

@@ -0,0 +1,10 @@
package main
import "github.com/containous/yaegi/_test/vars"
func main() {
println(vars.A)
}
// Output:
// hello world!

14
_test/variadic9.go Normal file
View File

@@ -0,0 +1,14 @@
package main
import "fmt"
func Sprintf(format string, a ...interface{}) string {
return fmt.Sprintf(format, a...)
}
func main() {
fmt.Println(Sprintf("Hello %s", "World!"))
}
// Output:
// Hello World!

14
_test/vars/first.go Normal file
View File

@@ -0,0 +1,14 @@
package vars
var (
A = concat("hello", B)
C = D
)
func concat(a ...string) string {
var s string
for _, ss := range a {
s += ss
}
return s
}

6
_test/vars/second.go Normal file
View File

@@ -0,0 +1,6 @@
package vars
var (
B = concat(" ", C, "!")
D = "world"
)

View File

@@ -23,272 +23,16 @@ import (
"bytes"
"flag"
"fmt"
"go/constant"
"go/format"
"go/importer"
"go/token"
"go/types"
"io/ioutil"
"io"
"log"
"math/big"
"os"
"path"
"runtime"
"strconv"
"strings"
"text/template"
"github.com/containous/yaegi/extract"
)
const model = `// Code generated by 'goexports {{.PkgName}}'. DO NOT EDIT.
{{.License}}
{{if .BuildTags}}// +build {{.BuildTags}}{{end}}
package {{.Dest}}
import (
{{- range $key, $value := .Imports }}
{{- if $value}}
"{{$key}}"
{{- end}}
{{- end}}
"{{.PkgName}}"
"reflect"
)
func init() {
Symbols["{{.PkgName}}"] = map[string]reflect.Value{
{{- if .Val}}
// function, constant and variable definitions
{{range $key, $value := .Val -}}
{{- if $value.Addr -}}
"{{$key}}": reflect.ValueOf(&{{$value.Name}}).Elem(),
{{else -}}
"{{$key}}": reflect.ValueOf({{$value.Name}}),
{{end -}}
{{end}}
{{- end}}
{{- if .Typ}}
// type definitions
{{range $key, $value := .Typ -}}
"{{$key}}": reflect.ValueOf((*{{$value}})(nil)),
{{end}}
{{- end}}
{{- if .Wrap}}
// interface wrapper definitions
{{range $key, $value := .Wrap -}}
"_{{$key}}": reflect.ValueOf((*{{$value.Name}})(nil)),
{{end}}
{{- end}}
}
}
{{range $key, $value := .Wrap -}}
// {{$value.Name}} is an interface wrapper for {{$key}} type
type {{$value.Name}} struct {
{{range $m := $value.Method -}}
W{{$m.Name}} func{{$m.Param}} {{$m.Result}}
{{end}}
}
{{range $m := $value.Method -}}
func (W {{$value.Name}}) {{$m.Name}}{{$m.Param}} {{$m.Result}} { {{$m.Ret}} W.W{{$m.Name}}{{$m.Arg}} }
{{end}}
{{end}}
`
// Val store the value name and addressable status of symbols.
type Val struct {
Name string // "package.name"
Addr bool // true if symbol is a Var
}
// Method store information for generating interface wrapper method.
type Method struct {
Name, Param, Result, Arg, Ret string
}
// Wrap store information for generating interface wrapper.
type Wrap struct {
Name string
Method []Method
}
func genContent(dest, pkgName, license string, skip map[string]bool) ([]byte, error) {
p, err := importer.ForCompiler(token.NewFileSet(), "source", nil).Import(pkgName)
if err != nil {
return nil, err
}
prefix := "_" + pkgName + "_"
prefix = strings.NewReplacer("/", "_", "-", "_", ".", "_").Replace(prefix)
typ := map[string]string{}
val := map[string]Val{}
wrap := map[string]Wrap{}
imports := map[string]bool{}
sc := p.Scope()
for _, pkg := range p.Imports() {
imports[pkg.Path()] = false
}
qualify := func(pkg *types.Package) string {
if pkg.Path() != pkgName {
imports[pkg.Path()] = true
}
return pkg.Name()
}
for _, name := range sc.Names() {
o := sc.Lookup(name)
if !o.Exported() {
continue
}
pname := path.Base(pkgName) + "." + name
if skip[pname] {
continue
}
switch o := o.(type) {
case *types.Const:
if b, ok := o.Type().(*types.Basic); ok && (b.Info()&types.IsUntyped) != 0 {
// convert untyped constant to right type to avoid overflow
val[name] = Val{fixConst(pname, o.Val(), imports), false}
} else {
val[name] = Val{pname, false}
}
case *types.Func:
val[name] = Val{pname, false}
case *types.Var:
val[name] = Val{pname, true}
case *types.TypeName:
typ[name] = pname
if t, ok := o.Type().Underlying().(*types.Interface); ok {
var methods []Method
for i := 0; i < t.NumMethods(); i++ {
f := t.Method(i)
if !f.Exported() {
continue
}
sign := f.Type().(*types.Signature)
args := make([]string, sign.Params().Len())
params := make([]string, len(args))
for j := range args {
v := sign.Params().At(j)
if args[j] = v.Name(); args[j] == "" {
args[j] = fmt.Sprintf("a%d", j)
}
params[j] = args[j] + " " + types.TypeString(v.Type(), qualify)
}
arg := "(" + strings.Join(args, ", ") + ")"
param := "(" + strings.Join(params, ", ") + ")"
results := make([]string, sign.Results().Len())
for j := range results {
v := sign.Results().At(j)
results[j] = v.Name() + " " + types.TypeString(v.Type(), qualify)
}
result := "(" + strings.Join(results, ", ") + ")"
ret := ""
if sign.Results().Len() > 0 {
ret = "return"
}
methods = append(methods, Method{f.Name(), param, result, arg, ret})
}
wrap[name] = Wrap{prefix + name, methods}
}
}
}
var buildTags string
if runtime.Version() != "devel" {
parts := strings.Split(runtime.Version(), ".")
minorRaw := getMinor(parts[1])
currentGoVersion := parts[0] + "." + minorRaw
minor, errParse := strconv.Atoi(minorRaw)
if errParse != nil {
return nil, fmt.Errorf("failed to parse version: %v", errParse)
}
nextGoVersion := parts[0] + "." + strconv.Itoa(minor+1)
buildTags = currentGoVersion + ",!" + nextGoVersion
}
base := template.New("goexports")
parse, err := base.Parse(model)
if err != nil {
return nil, fmt.Errorf("template parsing error: %v", err)
}
if pkgName == "log/syslog" {
buildTags += ",!windows,!nacl,!plan9"
}
b := new(bytes.Buffer)
data := map[string]interface{}{
"Dest": dest,
"Imports": imports,
"PkgName": pkgName,
"Val": val,
"Typ": typ,
"Wrap": wrap,
"BuildTags": buildTags,
"License": license,
}
err = parse.Execute(b, data)
if err != nil {
return nil, fmt.Errorf("template error: %v", err)
}
// gofmt
source, err := format.Source(b.Bytes())
if err != nil {
return nil, fmt.Errorf("failed to format source: %v: %s", err, b.Bytes())
}
return source, nil
}
// fixConst checks untyped constant value, converting it if necessary to avoid overflow.
func fixConst(name string, val constant.Value, imports map[string]bool) string {
var (
tok string
str string
)
switch val.Kind() {
case constant.Int:
tok = "INT"
str = val.ExactString()
case constant.Float:
v := constant.Val(val) // v is *big.Rat or *big.Float
f, ok := v.(*big.Float)
if !ok {
f = new(big.Float).SetRat(v.(*big.Rat))
}
tok = "FLOAT"
str = f.Text('g', int(f.Prec()))
case constant.Complex:
// TODO: not sure how to parse this case
fallthrough
default:
return name
}
imports["go/constant"] = true
imports["go/token"] = true
return fmt.Sprintf("constant.MakeFromLiteral(\"%s\", token.%s, 0)", str, tok)
}
// genLicense generates the correct LICENSE header text from the provided
// path to a LICENSE file.
func genLicense(fname string) (string, error) {
@@ -318,9 +62,13 @@ func genLicense(fname string) (string, error) {
return license.String(), nil
}
func main() {
licenseFlag := flag.String("license", "", "path to a LICENSE file")
var (
licenseFlag = flag.String("license", "", "path to a LICENSE file")
// TODO: deal with a module that has several packages (so there's only one go.mod file at the root of the project).
importPathFlag = flag.String("import_path", "", "the namespace for the symbols extracted from the argument. Not needed if the argument is from the stdlib, or if the name can be found in a go.mod")
)
func main() {
flag.Parse()
if flag.NArg() == 0 {
@@ -333,55 +81,57 @@ func main() {
log.Fatal(err)
}
dir, err := os.Getwd()
wd, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
dest := path.Base(dir)
for _, pkg := range flag.Args() {
var oFile string
skip := map[string]bool{}
if pkg == "syscall" {
goos, arch := os.Getenv("GOOS"), os.Getenv("GOARCH")
oFile = strings.Replace(pkg, "/", "_", -1) + "_" + goos + "_" + arch + ".go"
if goos == "solaris" {
skip["syscall.RawSyscall6"] = true
skip["syscall.Syscall6"] = true
}
} else {
oFile = strings.Replace(pkg, "/", "_", -1) + ".go"
}
ext := extract.Extractor{
Dest: path.Base(wd),
License: license,
}
goos, goarch := os.Getenv("GOOS"), os.Getenv("GOARCH")
skip := map[string]bool{}
if goos == "solaris" {
skip["syscall.RawSyscall6"] = true
skip["syscall.Syscall6"] = true
}
ext.Skip = skip
content, err := genContent(dest, pkg, license, skip)
for _, pkgIdent := range flag.Args() {
var buf bytes.Buffer
importPath, err := ext.Extract(pkgIdent, *importPathFlag, &buf)
if err != nil {
log.Println(err)
continue
}
var oFile string
if pkgIdent == "syscall" {
oFile = strings.Replace(importPath, "/", "_", -1) + "_" + goos + "_" + goarch + ".go"
} else {
oFile = strings.Replace(importPath, "/", "_", -1) + ".go"
}
prefix := runtime.Version()
if runtime.Version() != "devel" {
parts := strings.Split(runtime.Version(), ".")
prefix = parts[0] + "_" + getMinor(parts[1])
prefix = parts[0] + "_" + extract.GetMinor(parts[1])
}
err = ioutil.WriteFile(prefix+"_"+oFile, content, 0666)
f, err := os.Create(prefix + "_" + oFile)
if err != nil {
log.Fatal(err)
}
if _, err := io.Copy(f, &buf); err != nil {
_ = f.Close()
log.Fatal(err)
}
if err := f.Close(); err != nil {
log.Fatal(err)
}
}
}
func getMinor(part string) string {
minor := part
index := strings.Index(minor, "beta")
if index < 0 {
index = strings.Index(minor, "rc")
}
if index > 0 {
minor = minor[:index]
}
return minor
}

107
cmd/yaegi/extract.go Normal file
View File

@@ -0,0 +1,107 @@
package main
import (
"bufio"
"bytes"
"flag"
"fmt"
"io"
"os"
"path"
"strings"
"github.com/containous/yaegi/extract"
)
func extractCmd(arg []string) error {
var licensePath string
var importPath string
eflag := flag.NewFlagSet("run", flag.ContinueOnError)
eflag.StringVar(&licensePath, "license", "", "path to a LICENSE file")
eflag.StringVar(&importPath, "import_path", "", "the namespace for the extracted symbols")
eflag.Usage = func() {
fmt.Println("Usage: yaegi extract [options] packages...")
fmt.Println("Options:")
eflag.PrintDefaults()
}
if err := eflag.Parse(arg); err != nil {
return err
}
args := eflag.Args()
if len(args) == 0 {
return fmt.Errorf("missing package")
}
license, err := genLicense(licensePath)
if err != nil {
return err
}
wd, err := os.Getwd()
if err != nil {
return err
}
ext := extract.Extractor{
Dest: path.Base(wd),
License: license,
}
for _, pkgIdent := range args {
var buf bytes.Buffer
importPath, err := ext.Extract(pkgIdent, importPath, &buf)
if err != nil {
fmt.Fprintln(os.Stderr, err)
continue
}
oFile := strings.Replace(importPath, "/", "_", -1) + ".go"
f, err := os.Create(oFile)
if err != nil {
return err
}
if _, err := io.Copy(f, &buf); err != nil {
_ = f.Close()
return err
}
if err := f.Close(); err != nil {
return err
}
}
return nil
}
// genLicense generates the correct LICENSE header text from the provided
// path to a LICENSE file.
func genLicense(fname string) (string, error) {
if fname == "" {
return "", nil
}
f, err := os.Open(fname)
if err != nil {
return "", fmt.Errorf("could not open LICENSE file: %v", err)
}
defer func() { _ = f.Close() }()
license := new(strings.Builder)
sc := bufio.NewScanner(f)
for sc.Scan() {
txt := sc.Text()
if txt != "" {
txt = " " + txt
}
license.WriteString("//" + txt + "\n")
}
if sc.Err() != nil {
return "", fmt.Errorf("could not scan LICENSE file: %v", err)
}
return license.String(), nil
}

43
cmd/yaegi/help.go Normal file
View File

@@ -0,0 +1,43 @@
package main
import "fmt"
const usage = `Yaegi is a Go interpreter.
Usage:
yaegi [command] [arguments]
The commands are:
extract generate a wrapper file from a source package
help print usage information
run execute a Go program from source
test execute test functions in a Go package
Use "yaegi help <command>" for more information about a command.
If no command is given or if the first argument is not a command, then
the run command is assumed.
`
func help(arg []string) error {
var cmd string
if len(arg) > 0 {
cmd = arg[0]
}
switch cmd {
case Extract:
return extractCmd([]string{"-h"})
case Help, "", "-h", "--help":
fmt.Print(usage)
return nil
case Run:
return run([]string{"-h"})
case Test:
return fmt.Errorf("help: test not implemented")
default:
return fmt.Errorf("help: invalid yaegi command: %v", cmd)
}
}

132
cmd/yaegi/run.go Normal file
View File

@@ -0,0 +1,132 @@
package main
import (
"flag"
"fmt"
"go/build"
"io/ioutil"
"os"
"strings"
"github.com/containous/yaegi/interp"
"github.com/containous/yaegi/stdlib"
"github.com/containous/yaegi/stdlib/syscall"
"github.com/containous/yaegi/stdlib/unrestricted"
"github.com/containous/yaegi/stdlib/unsafe"
)
func run(arg []string) error {
var interactive bool
var useSyscall bool
var useUnrestricted bool
var useUnsafe bool
var tags string
var cmd string
var err error
rflag := flag.NewFlagSet("run", flag.ContinueOnError)
rflag.BoolVar(&interactive, "i", false, "start an interactive REPL")
rflag.BoolVar(&useSyscall, "syscall", false, "include syscall symbols")
rflag.BoolVar(&useUnrestricted, "unrestricted", false, "include unrestricted symbols")
rflag.StringVar(&tags, "tags", "", "set a list of build tags")
rflag.BoolVar(&useUnsafe, "unsafe", false, "include usafe symbols")
rflag.StringVar(&cmd, "e", "", "set the command to be executed (instead of script or/and shell)")
rflag.Usage = func() {
fmt.Println("Usage: yaegi run [options] [path] [args]")
fmt.Println("Options:")
rflag.PrintDefaults()
}
if err = rflag.Parse(arg); err != nil {
return err
}
args := rflag.Args()
i := interp.New(interp.Options{GoPath: build.Default.GOPATH, BuildTags: strings.Split(tags, ",")})
i.Use(stdlib.Symbols)
i.Use(interp.Symbols)
if useSyscall {
i.Use(syscall.Symbols)
}
if useUnsafe {
i.Use(unsafe.Symbols)
}
if useUnrestricted {
// Use of unrestricted symbols should always follow use of stdlib symbols, to update them.
i.Use(unrestricted.Symbols)
}
if cmd != "" {
i.REPL(strings.NewReader(cmd), os.Stderr)
}
if len(args) == 0 {
if interactive || cmd == "" {
i.REPL(os.Stdin, os.Stdout)
}
return nil
}
// Skip first os arg to set command line as expected by interpreted main
path := args[0]
os.Args = arg[1:]
flag.CommandLine = flag.NewFlagSet(path, flag.ExitOnError)
if isPackageName(path) {
err = runPackage(i, path)
} else {
if isDir(path) {
err = runDir(i, path)
} else {
err = runFile(i, path)
}
}
if err != nil {
return err
}
if interactive {
i.REPL(os.Stdin, os.Stdout)
}
return nil
}
func isPackageName(path string) bool {
return !strings.HasPrefix(path, "/") && !strings.HasPrefix(path, "./") && !strings.HasPrefix(path, "../")
}
func isDir(path string) bool {
fi, err := os.Lstat(path)
return err == nil && fi.IsDir()
}
func runPackage(i *interp.Interpreter, path string) error {
return fmt.Errorf("runPackage not implemented")
}
func runDir(i *interp.Interpreter, path string) error {
return fmt.Errorf("runDir not implemented")
}
func runFile(i *interp.Interpreter, path string) error {
b, err := ioutil.ReadFile(path)
if err != nil {
return err
}
if s := string(b); strings.HasPrefix(s, "#!") {
// Allow executable go scripts, Have the same behavior as in interactive mode.
s = strings.Replace(s, "#!", "//", 1)
i.REPL(strings.NewReader(s), os.Stdout)
} else {
// Files not starting with "#!" are supposed to be pure Go, directly Evaled.
i.Name = path
_, err := i.Eval(s)
if err != nil {
fmt.Println(err)
if p, ok := err.(interp.Panic); ok {
fmt.Println(string(p.Stack))
}
}
}
return nil
}

View File

@@ -85,87 +85,53 @@ Debugging support (may be removed at any time):
package main
import (
"errors"
"flag"
"fmt"
"go/build"
"io/ioutil"
"log"
"os"
"strings"
)
"github.com/containous/yaegi/interp"
"github.com/containous/yaegi/stdlib"
"github.com/containous/yaegi/stdlib/syscall"
"github.com/containous/yaegi/stdlib/unsafe"
const (
Extract = "extract"
Help = "help"
Run = "run"
Test = "test"
)
func main() {
var interactive bool
var useUnsafe bool
var useSyscall bool
var tags string
var cmd string
flag.BoolVar(&interactive, "i", false, "start an interactive REPL")
flag.BoolVar(&useSyscall, "syscall", false, "include syscall symbols")
flag.StringVar(&tags, "tags", "", "set a list of build tags")
flag.BoolVar(&useUnsafe, "unsafe", false, "include usafe symbols")
flag.StringVar(&cmd, "e", "", "set the command to be executed (instead of script or/and shell)")
flag.Usage = func() {
fmt.Println("Usage:", os.Args[0], "[options] [script] [args]")
fmt.Println("Options:")
flag.PrintDefaults()
}
flag.Parse()
args := flag.Args()
log.SetFlags(log.Lshortfile)
var err error
var exitCode int
i := interp.New(interp.Options{GoPath: build.Default.GOPATH, BuildTags: strings.Split(tags, ",")})
i.Use(stdlib.Symbols)
i.Use(interp.Symbols)
if useSyscall {
i.Use(syscall.Symbols)
}
if useUnsafe {
i.Use(unsafe.Symbols)
log.SetFlags(log.Lshortfile) // Ease debugging.
if len(os.Args) > 1 {
cmd = os.Args[1]
}
if cmd != `` {
i.REPL(strings.NewReader(cmd), os.Stderr)
switch cmd {
case Extract:
err = extractCmd(os.Args[2:])
case Help, "-h", "--help":
err = help(os.Args[2:])
case Run:
err = run(os.Args[2:])
case Test:
err = fmt.Errorf("test not implemented")
default:
// If no command is given, fallback to default "run" command.
// This allows scripts starting with "#!/usr/bin/env yaegi",
// as passing more than 1 argument to #! executable may be not supported
// on all platforms.
cmd = Run
err = run(os.Args[1:])
}
if len(args) == 0 {
if interactive || cmd == `` {
i.REPL(os.Stdin, os.Stdout)
}
return
}
// Skip first os arg to set command line as expected by interpreted main
os.Args = os.Args[1:]
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
b, err := ioutil.ReadFile(args[0])
if err != nil {
log.Fatal("Could not read file: ", args[0])
}
if s := string(b); strings.HasPrefix(s, "#!") {
// Allow executable go scripts, Have the same behavior as in interactive mode.
s = strings.Replace(s, "#!", "//", 1)
i.REPL(strings.NewReader(s), os.Stdout)
} else {
// Files not starting with "#!" are supposed to be pure Go, directly Evaled.
i.Name = args[0]
_, err := i.Eval(s)
if err != nil {
fmt.Println(err)
if p, ok := err.(interp.Panic); ok {
fmt.Println(string(p.Stack))
}
}
}
if interactive {
i.REPL(os.Stdin, os.Stdout)
if err != nil && !errors.Is(err, flag.ErrHelp) {
err = fmt.Errorf("%s: %w", cmd, err)
fmt.Fprintln(os.Stderr, err)
exitCode = 1
}
os.Exit(exitCode)
}

View File

@@ -12,7 +12,7 @@ import (
)
const (
// CITimeoutMultiplier is the multiplier for all timeouts in the CI
// CITimeoutMultiplier is the multiplier for all timeouts in the CI.
CITimeoutMultiplier = 3
)

View File

@@ -0,0 +1,11 @@
package main
import (
"fmt"
"guthib.com/foo/pkg"
)
func main() {
fmt.Printf("%s", pkg.NewSample()())
}

View File

@@ -0,0 +1,17 @@
package pkg
import (
"fmt"
"guthib.com/bar"
)
func Here() string {
return "hello"
}
func NewSample() func() string {
return func() string {
return fmt.Sprintf("%s %s", bar.Bar(), Here())
}
}

View File

@@ -0,0 +1,6 @@
package bar
// Bar is bar
func Bar() string {
return "Yo"
}

View File

@@ -83,6 +83,18 @@ func TestPackages(t *testing.T) {
expected: "Fromage",
evalFile: "./_pkg11/src/foo/foo.go",
},
{
desc: "vendor dir is a sibling or an uncle",
goPath: "./_pkg12/",
expected: "Yo hello",
topImport: "guthib.com/foo/pkg",
},
{
desc: "eval main with vendor as a sibling",
goPath: "./_pkg12/",
expected: "Yo hello",
evalFile: "./_pkg12/src/guthib.com/foo/main.go",
},
}
for _, test := range testCases {
@@ -116,7 +128,7 @@ func TestPackages(t *testing.T) {
os.Stdout = pw
if _, err := i.Eval(string(data)); err != nil {
t.Fatal(err)
fatalStderrf(t, "%v", err)
}
var buf bytes.Buffer
@@ -127,10 +139,10 @@ func TestPackages(t *testing.T) {
}()
if err := pw.Close(); err != nil {
t.Fatal(err)
fatalStderrf(t, "%v", err)
}
if err := <-errC; err != nil {
t.Fatal(err)
fatalStderrf(t, "%v", err)
}
msg = buf.String()
} else {
@@ -153,12 +165,17 @@ func TestPackages(t *testing.T) {
}
if msg != test.expected {
t.Errorf("Got %q, want %q", msg, test.expected)
fatalStderrf(t, "Got %q, want %q", msg, test.expected)
}
})
}
}
func fatalStderrf(t *testing.T, format string, args ...interface{}) {
fmt.Fprintf(os.Stderr, format+"\n", args...)
t.FailNow()
}
func TestPackagesError(t *testing.T) {
testCases := []struct {
desc string

409
extract/extract.go Normal file
View File

@@ -0,0 +1,409 @@
/*
Package extract generates wrappers of package exported symbols.
*/
package extract
import (
"bufio"
"bytes"
"errors"
"fmt"
"go/constant"
"go/format"
"go/importer"
"go/token"
"go/types"
"io"
"math/big"
"os"
"path"
"path/filepath"
"runtime"
"strconv"
"strings"
"text/template"
)
const model = `// Code generated by 'github.com/containous/yaegi/extract {{.PkgName}}'. DO NOT EDIT.
{{.License}}
{{if .BuildTags}}// +build {{.BuildTags}}{{end}}
package {{.Dest}}
import (
{{- range $key, $value := .Imports }}
{{- if $value}}
"{{$key}}"
{{- end}}
{{- end}}
"{{.PkgName}}"
"reflect"
)
func init() {
Symbols["{{.PkgName}}"] = map[string]reflect.Value{
{{- if .Val}}
// function, constant and variable definitions
{{range $key, $value := .Val -}}
{{- if $value.Addr -}}
"{{$key}}": reflect.ValueOf(&{{$value.Name}}).Elem(),
{{else -}}
"{{$key}}": reflect.ValueOf({{$value.Name}}),
{{end -}}
{{end}}
{{- end}}
{{- if .Typ}}
// type definitions
{{range $key, $value := .Typ -}}
"{{$key}}": reflect.ValueOf((*{{$value}})(nil)),
{{end}}
{{- end}}
{{- if .Wrap}}
// interface wrapper definitions
{{range $key, $value := .Wrap -}}
"_{{$key}}": reflect.ValueOf((*{{$value.Name}})(nil)),
{{end}}
{{- end}}
}
}
{{range $key, $value := .Wrap -}}
// {{$value.Name}} is an interface wrapper for {{$key}} type
type {{$value.Name}} struct {
{{range $m := $value.Method -}}
W{{$m.Name}} func{{$m.Param}} {{$m.Result}}
{{end}}
}
{{range $m := $value.Method -}}
func (W {{$value.Name}}) {{$m.Name}}{{$m.Param}} {{$m.Result}} { {{$m.Ret}} W.W{{$m.Name}}{{$m.Arg}} }
{{end}}
{{end}}
`
// Val stores the value name and addressable status of symbols.
type Val struct {
Name string // "package.name"
Addr bool // true if symbol is a Var
}
// Method stores information for generating interface wrapper method.
type Method struct {
Name, Param, Result, Arg, Ret string
}
// Wrap stores information for generating interface wrapper.
type Wrap struct {
Name string
Method []Method
}
// restricted map defines symbols for which a special implementation is provided.
var restricted = map[string]bool{
"osExit": true,
"osFindProcess": true,
"logFatal": true,
"logFatalf": true,
"logFatalln": true,
"logLogger": true,
"logNew": true,
}
func genContent(dest, importPath, license string, p *types.Package, skip map[string]bool) ([]byte, error) {
prefix := "_" + importPath + "_"
prefix = strings.NewReplacer("/", "_", "-", "_", ".", "_").Replace(prefix)
typ := map[string]string{}
val := map[string]Val{}
wrap := map[string]Wrap{}
imports := map[string]bool{}
sc := p.Scope()
for _, pkg := range p.Imports() {
imports[pkg.Path()] = false
}
qualify := func(pkg *types.Package) string {
if pkg.Path() != importPath {
imports[pkg.Path()] = true
}
return pkg.Name()
}
for _, name := range sc.Names() {
o := sc.Lookup(name)
if !o.Exported() {
continue
}
pname := path.Base(importPath) + "." + name
if skip[pname] {
continue
}
if rname := path.Base(importPath) + name; restricted[rname] {
// Restricted symbol, locally provided by stdlib wrapper.
pname = rname
}
switch o := o.(type) {
case *types.Const:
if b, ok := o.Type().(*types.Basic); ok && (b.Info()&types.IsUntyped) != 0 {
// convert untyped constant to right type to avoid overflow
val[name] = Val{fixConst(pname, o.Val(), imports), false}
} else {
val[name] = Val{pname, false}
}
case *types.Func:
val[name] = Val{pname, false}
case *types.Var:
val[name] = Val{pname, true}
case *types.TypeName:
typ[name] = pname
if t, ok := o.Type().Underlying().(*types.Interface); ok {
var methods []Method
for i := 0; i < t.NumMethods(); i++ {
f := t.Method(i)
if !f.Exported() {
continue
}
sign := f.Type().(*types.Signature)
args := make([]string, sign.Params().Len())
params := make([]string, len(args))
for j := range args {
v := sign.Params().At(j)
if args[j] = v.Name(); args[j] == "" {
args[j] = fmt.Sprintf("a%d", j)
}
params[j] = args[j] + " " + types.TypeString(v.Type(), qualify)
}
arg := "(" + strings.Join(args, ", ") + ")"
param := "(" + strings.Join(params, ", ") + ")"
results := make([]string, sign.Results().Len())
for j := range results {
v := sign.Results().At(j)
results[j] = v.Name() + " " + types.TypeString(v.Type(), qualify)
}
result := "(" + strings.Join(results, ", ") + ")"
ret := ""
if sign.Results().Len() > 0 {
ret = "return"
}
methods = append(methods, Method{f.Name(), param, result, arg, ret})
}
wrap[name] = Wrap{prefix + name, methods}
}
}
}
buildTags, err := buildTags()
if err != nil {
return nil, err
}
base := template.New("goexports")
parse, err := base.Parse(model)
if err != nil {
return nil, fmt.Errorf("template parsing error: %v", err)
}
if importPath == "log/syslog" {
buildTags += ",!windows,!nacl,!plan9"
}
b := new(bytes.Buffer)
data := map[string]interface{}{
"Dest": dest,
"Imports": imports,
"PkgName": importPath,
"Val": val,
"Typ": typ,
"Wrap": wrap,
"BuildTags": buildTags,
"License": license,
}
err = parse.Execute(b, data)
if err != nil {
return nil, fmt.Errorf("template error: %v", err)
}
// gofmt
source, err := format.Source(b.Bytes())
if err != nil {
return nil, fmt.Errorf("failed to format source: %v: %s", err, b.Bytes())
}
return source, nil
}
// fixConst checks untyped constant value, converting it if necessary to avoid overflow.
func fixConst(name string, val constant.Value, imports map[string]bool) string {
var (
tok string
str string
)
switch val.Kind() {
case constant.Int:
tok = "INT"
str = val.ExactString()
case constant.Float:
v := constant.Val(val) // v is *big.Rat or *big.Float
f, ok := v.(*big.Float)
if !ok {
f = new(big.Float).SetRat(v.(*big.Rat))
}
tok = "FLOAT"
str = f.Text('g', int(f.Prec()))
case constant.Complex:
// TODO: not sure how to parse this case
fallthrough
default:
return name
}
imports["go/constant"] = true
imports["go/token"] = true
return fmt.Sprintf("constant.MakeFromLiteral(\"%s\", token.%s, 0)", str, tok)
}
// importPath checks whether pkgIdent is an existing directory relative to
// e.WorkingDir. If yes, it returns the actual import path of the Go package
// located in the directory. If it is definitely a relative path, but it does not
// exist, an error is returned. Otherwise, it is assumed to be an import path, and
// pkgIdent is returned.
func (e Extractor) importPath(pkgIdent, importPath string) (string, error) {
wd, err := os.Getwd()
if err != nil {
return "", err
}
dirPath := filepath.Join(wd, pkgIdent)
_, err = os.Stat(dirPath)
if err != nil && !os.IsNotExist(err) {
return "", err
}
if err != nil {
if len(pkgIdent) > 0 && pkgIdent[0] == '.' {
// pkgIdent is definitely a relative path, not a package name, and it does not exist
return "", err
}
// pkgIdent might be a valid stdlib package name. So we leave that responsibility to the caller now.
return pkgIdent, nil
}
// local import
if importPath != "" {
return importPath, nil
}
modPath := filepath.Join(dirPath, "go.mod")
_, err = os.Stat(modPath)
if os.IsNotExist(err) {
return "", errors.New("no go.mod found, and no import path specified")
}
if err != nil {
return "", err
}
f, err := os.Open(modPath)
if err != nil {
return "", err
}
defer func() {
_ = f.Close()
}()
sc := bufio.NewScanner(f)
var l string
for sc.Scan() {
l = sc.Text()
break
}
if sc.Err() != nil {
return "", err
}
parts := strings.Fields(l)
if len(parts) < 2 {
return "", errors.New(`invalid first line syntax in go.mod`)
}
if parts[0] != "module" {
return "", errors.New(`invalid first line in go.mod, no "module" found`)
}
return parts[1], nil
}
// Extractor creates a package with all the symbols from a dependency package.
type Extractor struct {
Dest string // the name of the created package.
License string // license text to be included in the created package, optional.
Skip map[string]bool
}
// Extract writes to rw a Go package with all the symbols found at pkgIdent.
// pkgIdent can be an import path, or a local path, relative to e.WorkingDir. In
// the latter case, Extract returns the actual import path of the package found at
// pkgIdent, otherwise it just returns pkgIdent.
// If pkgIdent is an import path, it is looked up in GOPATH. Vendoring is not
// supported yet, and the behavior is only defined for GO111MODULE=off.
func (e Extractor) Extract(pkgIdent, importPath string, rw io.Writer) (string, error) {
ipp, err := e.importPath(pkgIdent, importPath)
if err != nil {
return "", err
}
pkg, err := importer.ForCompiler(token.NewFileSet(), "source", nil).Import(pkgIdent)
if err != nil {
return "", err
}
content, err := genContent(e.Dest, ipp, e.License, pkg, e.Skip)
if err != nil {
return "", err
}
if _, err := rw.Write(content); err != nil {
return "", err
}
return ipp, nil
}
// GetMinor returns the minor part of the version number.
func GetMinor(part string) string {
minor := part
index := strings.Index(minor, "beta")
if index < 0 {
index = strings.Index(minor, "rc")
}
if index > 0 {
minor = minor[:index]
}
return minor
}
func buildTags() (string, error) {
version := runtime.Version()
if version == "devel" {
return "", nil
}
parts := strings.Split(version, ".")
minorRaw := GetMinor(parts[1])
currentGoVersion := parts[0] + "." + minorRaw
minor, err := strconv.Atoi(minorRaw)
if err != nil {
return "", fmt.Errorf("failed to parse version: %v", err)
}
nextGoVersion := parts[0] + "." + strconv.Itoa(minor+1)
return currentGoVersion + ",!" + nextGoVersion, nil
}

140
extract/extract_test.go Normal file
View File

@@ -0,0 +1,140 @@
package extract
import (
"bytes"
"os"
"path"
"strings"
"testing"
)
var expectedOutput = `// Code generated by 'github.com/containous/yaegi/extract guthib.com/baz'. DO NOT EDIT.
// +build BUILD_TAGS
package bar
import (
"guthib.com/baz"
"reflect"
)
func init() {
Symbols["guthib.com/baz"] = map[string]reflect.Value{
// function, constant and variable definitions
"Hello": reflect.ValueOf(baz.Hello),
}
}
`
func init() {
buildTags, err := buildTags()
if err != nil {
panic(err)
}
expectedOutput = strings.Replace(expectedOutput, "BUILD_TAGS", buildTags, 1)
}
func TestPackages(t *testing.T) {
testCases := []struct {
desc string
moduleOn string
wd string
arg string
importPath string
expected string
contains string
dest string
}{
{
desc: "stdlib math pkg, using go/importer",
dest: "math",
arg: "math",
// We check this one because it shows both defects when we break it: the value
// gets corrupted, and the type becomes token.INT
// TODO(mpl): if the ident between key and value becomes annoying, be smarter about it.
contains: `"MaxFloat64": reflect.ValueOf(constant.MakeFromLiteral("179769313486231570814527423731704356798100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", token.FLOAT, 0)),`,
},
{
desc: "using relative path, using go.mod",
wd: "./testdata/1/src/guthib.com/bar",
arg: "../baz",
expected: expectedOutput,
},
{
desc: "using relative path, manual import path",
wd: "./testdata/2/src/guthib.com/bar",
arg: "../baz",
importPath: "guthib.com/baz",
expected: expectedOutput,
},
{
desc: "using relative path, go.mod is ignored, because manual path",
wd: "./testdata/3/src/guthib.com/bar",
arg: "../baz",
importPath: "guthib.com/baz",
expected: expectedOutput,
},
{
desc: "using relative path, dep in vendor, using go.mod",
wd: "./testdata/4/src/guthib.com/bar",
arg: "./vendor/guthib.com/baz",
expected: expectedOutput,
},
{
desc: "using relative path, dep in vendor, manual import path",
wd: "./testdata/5/src/guthib.com/bar",
arg: "./vendor/guthib.com/baz",
importPath: "guthib.com/baz",
expected: expectedOutput,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
cwd, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
wd := test.wd
if wd == "" {
wd = cwd
} else {
if err := os.Chdir(wd); err != nil {
t.Fatal(err)
}
defer func() {
if err := os.Chdir(cwd); err != nil {
t.Fatal(err)
}
}()
}
dest := path.Base(wd)
if test.dest != "" {
dest = test.dest
}
ext := Extractor{
Dest: dest,
}
var out bytes.Buffer
if _, err := ext.Extract(test.arg, test.importPath, &out); err != nil {
t.Fatal(err)
}
if test.expected != "" {
if out.String() != test.expected {
t.Fatalf("\nGot:\n%q\nWant: \n%q", out.String(), test.expected)
}
}
if test.contains != "" {
if !strings.Contains(out.String(), test.contains) {
t.Fatalf("Missing expected part: %s in %s", test.contains, out.String())
}
}
})
}
}

View File

@@ -0,0 +1,9 @@
package main
import (
"guthib.com/baz"
)
func main() {
baz.Hello()
}

View File

@@ -0,0 +1,5 @@
package baz
func Hello() {
println("HELLO")
}

View File

@@ -0,0 +1,3 @@
module guthib.com/baz
go 1.14

View File

@@ -0,0 +1,9 @@
package main
import (
"guthib.com/baz"
)
func main() {
baz.Hello()
}

View File

@@ -0,0 +1,5 @@
package baz
func Hello() {
println("HELLO")
}

View File

@@ -0,0 +1,9 @@
package main
import (
"guthib.com/baz"
)
func main() {
baz.Hello()
}

View File

@@ -0,0 +1,5 @@
package baz
func Hello() {
println("HELLO")
}

View File

@@ -0,0 +1,3 @@
module should.not/be/used
go 1.14

View File

@@ -0,0 +1,9 @@
package main
import (
"guthib.com/baz"
)
func main() {
baz.Hello()
}

View File

@@ -0,0 +1,5 @@
package baz
func Hello() {
println("HELLO")
}

View File

@@ -0,0 +1,3 @@
module guthib.com/baz
go 1.14

View File

@@ -0,0 +1,9 @@
package main
import (
"guthib.com/baz"
)
func main() {
baz.Hello()
}

View File

@@ -0,0 +1,5 @@
package baz
func Hello() {
println("HELLO")
}

View File

@@ -181,7 +181,11 @@ func {{$name}}(n *node) {
func {{$name}}Const(n *node) {
v0, v1 := n.child[0].rval, n.child[1].rval
{{- if $op.Shift}}
isConst := (v0.IsValid() && isConstantValue(v0.Type()))
{{- else}}
isConst := (v0.IsValid() && isConstantValue(v0.Type())) && (v1.IsValid() && isConstantValue(v1.Type()))
{{- end}}
t := n.typ.rtype
if isConst {
t = constVal
@@ -190,8 +194,7 @@ func {{$name}}Const(n *node) {
switch {
case isConst:
{{- if $op.Shift}}
s, _ := constant.Uint64Val(vConstantValue(v1))
v := constant.Shift(vConstantValue(v0), token.{{tokenFromName $name}}, uint(s))
v := constant.Shift(vConstantValue(v0), token.{{tokenFromName $name}}, uint(vUint(v1)))
n.rval.Set(reflect.ValueOf(v))
{{- else}}
v := constant.BinaryOp(vConstantValue(v0), token.{{tokenFromName $name}}, vConstantValue(v1))
@@ -412,6 +415,8 @@ func {{$name}}(n *node) {
dest := genValueOutput(n, reflect.TypeOf(true))
c0, c1 := n.child[0], n.child[1]
{{- if or (eq $op.Name "==") (eq $op.Name "!=") }}
if c0.typ.cat == aliasT || c1.typ.cat == aliasT {
switch {
case c0.rval.IsValid():
@@ -432,7 +437,7 @@ func {{$name}}(n *node) {
dest := genValue(n)
n.exec = func(f *frame) bltn {
i1 := v1(f).Interface()
dest(f).SetBool(i0 != i1)
dest(f).SetBool(i0 {{$op.Name}} i1)
return tnext
}
}
@@ -454,7 +459,7 @@ func {{$name}}(n *node) {
dest := genValue(n)
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
dest(f).SetBool(i0 != i1)
dest(f).SetBool(i0 {{$op.Name}} i1)
return tnext
}
}
@@ -478,13 +483,14 @@ func {{$name}}(n *node) {
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
i1 := v1(f).Interface()
dest(f).SetBool(i0 != i1)
dest(f).SetBool(i0 {{$op.Name}} i1)
return tnext
}
}
}
return
}
{{- end}}
switch t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf(); {
case isString(t0) || isString(t1):

View File

@@ -190,6 +190,8 @@ type astError error
type action uint
// Node actions for the go language.
// It is important for type checking that *Assign directly
// follows it non-assign counterpart.
const (
aNop action = iota
aAddr
@@ -250,7 +252,6 @@ const (
aTypeAssert
aXor
aXorAssign
aMax
)
var actions = [...]string{
@@ -320,6 +321,15 @@ func (a action) String() string {
return "Action(" + strconv.Itoa(int(a)) + ")"
}
func isAssignAction(a action) bool {
switch a {
case aAddAssign, aAndAssign, aAndNotAssign, aMulAssign, aOrAssign,
aQuoAssign, aRemAssign, aShlAssign, aShrAssign, aSubAssign, aXorAssign:
return true
}
return false
}
func (interp *Interpreter) firstToken(src string) token.Token {
var s scanner.Scanner
file := interp.fset.AddFile("", interp.fset.Base(), len(src))
@@ -795,7 +805,7 @@ func (interp *Interpreter) ast(src, name string) (string, *node, error) {
}
case *ast.UnaryExpr:
var kind = unaryExpr
kind := unaryExpr
var act action
switch a.Op {
case token.ADD:

View File

@@ -8,7 +8,6 @@ import (
"path/filepath"
"reflect"
"regexp"
"strings"
"unicode"
)
@@ -46,12 +45,15 @@ var constBltn = map[string]func(*node){
var identifier = regexp.MustCompile(`([\pL_][\pL_\d]*)$`)
const nilIdent = "nil"
// cfg generates a control flow graph (CFG) from AST (wiring successors in AST)
// and pre-compute frame sizes and indexes for all un-named (temporary) and named
// variables. A list of nodes of init functions is returned.
// Following this pass, the CFG is ready to run.
func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
sc := interp.initScopePkg(pkgID)
check := typecheck{}
var initNodes []*node
var err error
@@ -193,7 +195,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
if len(n.child) == 2 {
// 1 type in clause: define the var with this type in the case clause scope
switch {
case n.child[0].ident == "nil":
case n.child[0].ident == nilIdent:
typ = sc.getType("interface{}")
case !n.child[0].isType(sc):
err = n.cfgErrorf("%s is not a type", n.child[0].ident)
@@ -243,6 +245,8 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
if n.typ, err = nodeType(interp, sc, n.child[0]); err != nil {
return false
}
// Indicate that the first child is the type
n.nleft = 1
} else {
// Get type from ancestor (implicit type)
if n.anc.kind == keyValueExpr && n == n.anc.child[0] {
@@ -254,20 +258,29 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
err = n.cfgErrorf("undefined type")
return false
}
n.typ.untyped = true
}
child := n.child
if n.nleft > 0 {
n.child[0].typ = n.typ
child = n.child[1:]
}
// Propagate type to children, to handle implicit types
for _, c := range n.child {
for _, c := range child {
switch c.kind {
case binaryExpr, unaryExpr:
case binaryExpr, unaryExpr, compositeLitExpr:
// Do not attempt to propagate composite type to operator expressions,
// it breaks constant folding.
case callExpr:
case keyValueExpr, typeAssertExpr, indexExpr:
c.typ = n.typ
default:
if c.ident == nilIdent {
c.typ = sc.getType(nilIdent)
continue
}
if c.typ, err = nodeType(interp, sc, c); err != nil {
return false
}
default:
c.typ = n.typ
}
}
@@ -430,6 +443,12 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
switch n.kind {
case addressExpr:
wireChild(n)
err = check.addressExpr(n)
if err != nil {
break
}
n.typ = &itype{cat: ptrT, val: n.child[0].typ}
n.findex = sc.add(n.typ)
@@ -476,6 +495,9 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
dest.typ = sc.fixType(src.typ)
}
}
if dest.typ.incomplete {
return
}
if dest.typ.sizedef {
dest.typ.size = arrayTypeLen(src)
dest.typ.rtype = nil
@@ -495,31 +517,14 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
} else {
sym, level, _ = sc.lookup(dest.ident)
}
switch t0, t1 := dest.typ.TypeOf(), src.typ.TypeOf(); n.action {
case aAddAssign:
if !(isNumber(t0) && isNumber(t1) || isString(t0) && isString(t1)) || isInt(t0) && isFloat(t1) {
err = n.cfgErrorf("illegal operand types for '%v' operator", n.action)
}
case aSubAssign, aMulAssign, aQuoAssign:
if !(isNumber(t0) && isNumber(t1)) || isInt(t0) && isFloat(t1) {
err = n.cfgErrorf("illegal operand types for '%v' operator", n.action)
}
case aRemAssign, aAndAssign, aOrAssign, aXorAssign, aAndNotAssign:
if !(isInt(t0) && isInt(t1)) {
err = n.cfgErrorf("illegal operand types for '%v' operator", n.action)
}
case aShlAssign, aShrAssign:
if !(dest.isInteger() && src.isNatural()) {
err = n.cfgErrorf("illegal operand types for '%v' operator", n.action)
}
default:
// Detect invalid float truncate.
if isInt(t0) && isFloat(t1) {
err = src.cfgErrorf("invalid float truncate")
return
}
err = check.assignExpr(n, dest, src)
if err != nil {
break
}
n.findex = dest.findex
n.level = dest.level
// Propagate type
// TODO: Check that existing destination type matches source type
@@ -548,23 +553,9 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
n.gen = nop
src.findex = dest.findex
src.level = level
case src.kind == basicLit:
// TODO: perform constant folding and propagation here.
switch {
case dest.typ.cat == interfaceT:
case isComplex(dest.typ.TypeOf()):
// Value set in genValue.
case !src.rval.IsValid():
// Assign to nil.
src.rval = reflect.New(dest.typ.TypeOf()).Elem()
case n.anc.kind == constDecl:
// Possible conversion from const to actual type will be handled later
default:
// Convert literal value to destination type.
convertConstantValue(src)
src.rval = src.rval.Convert(dest.typ.TypeOf())
src.typ = dest.typ
}
case src.kind == basicLit && !src.rval.IsValid():
// Assign to nil.
src.rval = reflect.New(dest.typ.TypeOf()).Elem()
}
n.typ = dest.typ
if sym != nil {
@@ -576,13 +567,11 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
dest.gen = nop // skip getIndexMap
}
if n.anc.kind == constDecl {
if !dest.typ.untyped {
// If the dest is untyped, any constant rval needs to be converted
convertConstantValue(src)
}
n.gen = nop
n.findex = -1
sc.sym[dest.ident].kind = constSym
if sym, _, ok := sc.lookup(dest.ident); ok {
sym.kind = constSym
}
if childPos(n) == len(n.anc.child)-1 {
sc.iota = 0
} else {
@@ -640,82 +629,20 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
case binaryExpr:
wireChild(n)
nilSym := interp.universe.sym["nil"]
nilSym := interp.universe.sym[nilIdent]
c0, c1 := n.child[0], n.child[1]
t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf()
isConstVal := func(n *node) bool {
return n.rval.IsValid() && isConstantValue(n.rval.Type())
}
// Type check the binary expression. Mimics Gos logic as closely and possible.
c := c0
if isConstVal(c) {
c = c1
}
if isShiftNode(n) {
if !c1.isNatural() {
err = n.cfgErrorf("invalid operation: shift count type %v, must be integer", strings.TrimLeft(c1.typ.id(), "."))
break
}
if !c0.isInteger() {
err = n.cfgErrorf("invalid operation: shift of type %v", strings.TrimLeft(c0.typ.id(), "."))
break
}
}
if !isShiftNode(n) && isComparisonNode(n) && !isConstVal(c) && !c0.typ.equals(c1.typ) {
if isInterface(c1.typ) && !isInterface(c0.typ) && !c0.typ.comparable() {
err = n.cfgErrorf("invalid operation: operator %v not defined on %s", n.action, strings.TrimLeft(c0.typ.id(), "."))
break
}
if isInterface(c0.typ) && !isInterface(c1.typ) && !c1.typ.comparable() {
err = n.cfgErrorf("invalid operation: operator %v not defined on %s", n.action, strings.TrimLeft(c1.typ.id(), "."))
break
}
}
if !isShiftNode(n) && !isConstVal(c) && !c0.typ.equals(c1.typ) && t0 != nil && t1 != nil {
switch {
case isConstVal(c0) && isNumber(t1) || isConstVal(c1) && isNumber(t0): // const <-> numberic case
case t0.Kind() == reflect.Uint8 && t1.Kind() == reflect.Int32 || t1.Kind() == reflect.Uint8 && t0.Kind() == reflect.Int32: // byte <-> rune case
case isInterface(c0.typ) && isInterface(c1.typ): // interface <-> interface case
default:
err = n.cfgErrorf("invalid operation: mismatched types %s and %s", strings.TrimLeft(c0.typ.id(), "."), strings.TrimLeft(c1.typ.id(), "."))
}
if err != nil {
break
}
}
cat := c.typ.cat
switch {
case isConstVal(c):
cat = catOfConst(c.rval)
case c.typ.cat == valueT:
cat = catOf(c.typ.rtype)
case c.typ.cat == aliasT:
cat = c.typ.val.cat
}
if !isShiftNode(n) && !okFor[n.action][cat] {
err = n.cfgErrorf("invalid operation: operator %v not defined on %s", n.action, strings.TrimLeft(c0.typ.id(), "."))
err = check.binaryExpr(n)
if err != nil {
break
}
if !isShiftNode(n) && isConstVal(c0) && isConstVal(c1) {
// If both are constants, check the left type as well.
if !okFor[n.action][catOfConst(c0.rval)] {
err = n.cfgErrorf("invalid operation: operator %v not defined on %s", n.action, strings.TrimLeft(c0.typ.id(), "."))
break
}
}
switch n.action {
case aRem, aShl, aShr:
n.typ = c0.typ
case aEqual, aNotEqual:
n.typ = sc.getType("bool")
if n.child[0].sym == nilSym || n.child[1].sym == nilSym {
if c0.sym == nilSym || c1.sym == nilSym {
if n.action == aEqual {
n.gen = isNil
} else {
@@ -749,6 +676,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
dest := n.anc.child[childPos(n)-n.anc.nright]
n.typ = dest.typ
n.findex = dest.findex
n.level = dest.level
case n.anc.kind == returnStmt:
// To avoid a copy in frame, if the result is to be returned, store it directly
// at the frame location reserved for output arguments.
@@ -784,13 +712,22 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
}
n.findex = sc.add(n.typ)
typ := t.TypeOf()
switch k := typ.Kind(); k {
case reflect.Map:
if typ.Kind() == reflect.Map {
err = check.assignment(n.child[1], t.key, "map index")
n.gen = getIndexMap
case reflect.Array, reflect.Slice, reflect.String:
break
}
l := -1
switch k := typ.Kind(); k {
case reflect.Array:
l = typ.Len()
fallthrough
case reflect.Slice, reflect.String:
n.gen = getIndexArray
case reflect.Ptr:
if typ2 := typ.Elem(); typ2.Kind() == reflect.Array {
l = typ2.Len()
n.gen = getIndexArray
} else {
err = n.cfgErrorf("type %v does not support indexing", typ)
@@ -799,11 +736,14 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
err = n.cfgErrorf("type is not an array, slice, string or map: %v", t.id())
}
err = check.index(n.child[1], l)
case blockStmt:
wireChild(n)
if len(n.child) > 0 {
l := n.lastChild()
n.findex = l.findex
n.level = l.level
n.val = l.val
n.sym = l.sym
n.typ = l.typ
@@ -811,13 +751,22 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
}
sc = sc.pop()
case constDecl, varDecl:
case constDecl:
wireChild(n)
case varDecl:
// Global varDecl do not need to be wired as this
// will be handled after cfg.
if n.anc.kind == fileStmt {
break
}
wireChild(n)
case declStmt, exprStmt, sendStmt:
wireChild(n)
l := n.lastChild()
n.findex = l.findex
n.level = l.level
n.val = l.val
n.sym = l.sym
n.typ = l.typ
@@ -872,9 +821,11 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
// Type conversion expression
if isInt(n.child[0].typ.TypeOf()) && n.child[1].kind == basicLit && isFloat(n.child[1].typ.TypeOf()) {
err = n.cfgErrorf("truncated to integer")
break
}
n.action = aConvert
if isInterface(n.child[0].typ) && !n.child[1].isNil() {
switch {
case isInterface(n.child[0].typ) && !n.child[1].isNil():
// Convert to interface: just check that all required methods are defined by concrete type.
c0, c1 := n.child[0], n.child[1]
if !c1.typ.implements(c0.typ) {
@@ -884,9 +835,16 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
n.gen = nop
n.typ = n.child[1].typ
n.findex = n.child[1].findex
n.level = n.child[1].level
n.val = n.child[1].val
n.rval = n.child[1].rval
} else {
case n.child[1].rval.IsValid() && isConstType(n.child[0].typ):
n.gen = nop
n.findex = -1
n.typ = n.child[0].typ
n.rval = n.child[1].rval
convertConstantValue(n)
default:
n.gen = convert
n.typ = n.child[0].typ
n.findex = sc.add(n.typ)
@@ -987,9 +945,49 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
case compositeLitExpr:
wireChild(n)
underlying := func(t *itype) *itype {
for {
switch t.cat {
case ptrT, aliasT:
t = t.val
continue
default:
return t
}
}
}
child := n.child
if n.nleft > 0 {
child = child[1:]
}
switch n.typ.cat {
case arrayT:
err = check.arrayLitExpr(child, underlying(n.typ.val), n.typ.size)
case mapT:
err = check.mapLitExpr(child, n.typ.key, underlying(n.typ.val))
case structT:
err = check.structLitExpr(child, n.typ)
case valueT:
rtype := n.typ.rtype
switch rtype.Kind() {
case reflect.Struct:
err = check.structBinLitExpr(child, rtype)
case reflect.Map:
ktyp := &itype{cat: valueT, rtype: rtype.Key()}
vtyp := &itype{cat: valueT, rtype: rtype.Elem()}
err = check.mapLitExpr(child, ktyp, vtyp)
}
}
if err != nil {
break
}
n.findex = sc.add(n.typ)
// TODO: Check that composite literal expr matches corresponding type
n.gen = compositeGenerator(n, sc)
n.gen = compositeGenerator(n)
case fallthroughtStmt:
if n.anc.kind != caseBody {
@@ -997,7 +995,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
}
case fileStmt:
wireChild(n)
wireChild(n, varDecl)
sc = sc.pop()
n.findex = -1
@@ -1109,11 +1107,11 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
n.types = sc.types
sc = sc.pop()
funcName := n.child[1].ident
if !isMethod(n) {
interp.scopes[pkgID].sym[funcName].index = -1 // to force value to n.val
interp.scopes[pkgID].sym[funcName].typ = n.typ
interp.scopes[pkgID].sym[funcName].kind = funcSym
interp.scopes[pkgID].sym[funcName].node = n
if sym := sc.sym[funcName]; !isMethod(n) && sym != nil {
sym.index = -1 // to force value to n.val
sym.typ = n.typ
sym.kind = funcSym
sym.node = n
}
case funcLit:
@@ -1128,6 +1126,10 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
if isKey(n) || isNewDefine(n, sc) {
break
}
if n.anc.kind == funcDecl && n.anc.child[1] == n {
// Dont process a function name identExpr.
break
}
sym, level, found := sc.lookup(n.ident)
if !found {
@@ -1150,9 +1152,9 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
n.rval = sym.rval
n.kind = basicLit
case n.ident == "iota":
n.rval = reflect.ValueOf(sc.iota)
n.rval = reflect.ValueOf(constant.MakeInt64(int64(sc.iota)))
n.kind = basicLit
case n.ident == "nil":
case n.ident == nilIdent:
n.kind = basicLit
case sym.kind == binSym:
n.typ = sym.typ
@@ -1278,12 +1280,13 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
wireChild(n)
c := n.lastChild()
n.findex = c.findex
n.level = c.level
n.typ = c.typ
n.rval = c.rval
case rangeStmt:
if sc.rangeChanType(n) != nil {
n.start = n.child[1] // Get chan
n.start = n.child[1].start // Get chan
n.child[1].tnext = n // then go to range function
n.tnext = n.child[2].start // then go to range body
n.child[2].tnext = n // then body go to range function (loop)
@@ -1295,7 +1298,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
} else {
k, o, body = n.child[0], n.child[1], n.child[2]
}
n.start = o // Get array or map object
n.start = o.start // Get array or map object
o.tnext = k.start // then go to iterator init
k.tnext = n // then go to range function
n.tnext = body.start // then go to range body
@@ -1674,17 +1677,18 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
case typeAssertExpr:
if len(n.child) > 1 {
wireChild(n)
if n.child[1].typ == nil {
if n.child[1].typ, err = nodeType(interp, sc, n.child[1]); err != nil {
c1 := n.child[1]
if c1.typ == nil {
if c1.typ, err = nodeType(interp, sc, c1); err != nil {
return
}
}
if n.anc.action != aAssignX {
if n.child[0].typ.cat == valueT {
if n.child[0].typ.cat == valueT && isFunc(c1.typ) {
// Avoid special wrapping of interfaces and func types.
n.typ = &itype{cat: valueT, rtype: n.child[1].typ.TypeOf()}
n.typ = &itype{cat: valueT, rtype: c1.typ.TypeOf()}
} else {
n.typ = n.child[1].typ
n.typ = c1.typ
}
n.findex = sc.add(n.typ)
}
@@ -1701,36 +1705,27 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
case unaryExpr:
wireChild(n)
err = check.unaryExpr(n)
if err != nil {
break
}
n.typ = n.child[0].typ
switch n.action {
case aRecv:
if n.action == aRecv {
// Channel receive operation: set type to the channel data type
if n.typ.cat == valueT {
n.typ = &itype{cat: valueT, rtype: n.typ.rtype.Elem()}
} else {
n.typ = n.typ.val
}
case aBitNot:
if !isInt(n.typ.TypeOf()) {
err = n.cfgErrorf("illegal operand type for '^' operator")
return
}
case aNot:
if !isBool(n.typ) {
err = n.cfgErrorf("illegal operand type for '!' operator")
return
}
case aNeg, aPos:
if !isNumber(n.typ.TypeOf()) {
err = n.cfgErrorf("illegal operand type for '%v' operator", n.action)
return
}
}
if n.typ == nil {
if n.typ, err = nodeType(interp, sc, n); err != nil {
return
}
}
// TODO: Optimisation: avoid allocation if boolean branch op (i.e. '!' in an 'if' expr)
if n.child[0].rval.IsValid() && !isInterface(n.typ) && constOp[n.action] != nil {
n.typ.TypeOf() // init reflect type
@@ -1744,6 +1739,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
dest := n.anc.child[childPos(n)-n.anc.nright]
n.typ = dest.typ
n.findex = dest.findex
n.level = dest.level
case n.anc.kind == returnStmt:
pos := childPos(n)
n.typ = sc.def.typ.ret[pos]
@@ -1902,6 +1898,90 @@ func genRun(nod *node) error {
return err
}
func genGlobalVars(roots []*node, sc *scope) (*node, error) {
var vars []*node
for _, n := range roots {
vars = append(vars, getVars(n)...)
}
if len(vars) == 0 {
return nil, nil
}
varNode, err := genGlobalVarDecl(vars, sc)
if err != nil {
return nil, err
}
setExec(varNode.start)
return varNode, nil
}
func getVars(n *node) (vars []*node) {
for _, child := range n.child {
if child.kind == varDecl {
vars = append(vars, child.child...)
}
}
return vars
}
func genGlobalVarDecl(nodes []*node, sc *scope) (*node, error) {
varNode := &node{kind: varDecl, action: aNop, gen: nop}
deps := map[*node][]*node{}
for _, n := range nodes {
deps[n] = getVarDependencies(n, sc)
}
inited := map[*node]bool{}
revisit := []*node{}
for {
for _, n := range nodes {
canInit := true
for _, d := range deps[n] {
if !inited[d] {
canInit = false
}
}
if !canInit {
revisit = append(revisit, n)
continue
}
varNode.child = append(varNode.child, n)
inited[n] = true
}
if len(revisit) == 0 || equalNodes(nodes, revisit) {
break
}
nodes = revisit
revisit = []*node{}
}
if len(revisit) > 0 {
return nil, revisit[0].cfgErrorf("variable definition loop")
}
wireChild(varNode)
return varNode, nil
}
func getVarDependencies(nod *node, sc *scope) (deps []*node) {
nod.Walk(func(n *node) bool {
if n.kind == identExpr {
if sym, _, ok := sc.lookup(n.ident); ok {
if sym.kind != varSym || !sym.global || sym.node == nod {
return false
}
deps = append(deps, sym.node)
}
}
return true
}, nil)
return deps
}
// setFnext sets the cond fnext field to next, propagates it for parenthesis blocks
// and sets the action to branch.
func setFNext(cond, next *node) {
@@ -1961,47 +2041,68 @@ func (n *node) isType(sc *scope) bool {
}
// wireChild wires AST nodes for CFG in subtree.
func wireChild(n *node) {
func wireChild(n *node, exclude ...nkind) {
child := excludeNodeKind(n.child, exclude)
// Set start node, in subtree (propagated to ancestors by post-order processing)
for _, child := range n.child {
switch child.kind {
for _, c := range child {
switch c.kind {
case arrayType, chanType, chanTypeRecv, chanTypeSend, funcDecl, importDecl, mapType, basicLit, identExpr, typeDecl:
continue
default:
n.start = child.start
n.start = c.start
}
break
}
// Chain sequential operations inside a block (next is right sibling)
for i := 1; i < len(n.child); i++ {
switch n.child[i].kind {
for i := 1; i < len(child); i++ {
switch child[i].kind {
case funcDecl:
n.child[i-1].tnext = n.child[i]
child[i-1].tnext = child[i]
default:
switch n.child[i-1].kind {
switch child[i-1].kind {
case breakStmt, continueStmt, gotoStmt, returnStmt:
// tnext is already computed, no change
default:
n.child[i-1].tnext = n.child[i].start
child[i-1].tnext = child[i].start
}
}
}
// Chain subtree next to self
for i := len(n.child) - 1; i >= 0; i-- {
switch n.child[i].kind {
for i := len(child) - 1; i >= 0; i-- {
switch child[i].kind {
case arrayType, chanType, chanTypeRecv, chanTypeSend, importDecl, mapType, funcDecl, basicLit, identExpr, typeDecl:
continue
case breakStmt, continueStmt, gotoStmt, returnStmt:
// tnext is already computed, no change
default:
n.child[i].tnext = n
child[i].tnext = n
}
break
}
}
func excludeNodeKind(child []*node, kinds []nkind) []*node {
if len(kinds) == 0 {
return child
}
var res []*node
for _, c := range child {
exclude := false
for _, k := range kinds {
if c.kind == k {
exclude = true
}
}
if !exclude {
res = append(res, c)
}
}
return res
}
func (n *node) name() (s string) {
switch {
case n.ident != "":
@@ -2012,43 +2113,6 @@ func (n *node) name() (s string) {
return s
}
// isInteger returns true if node type is integer, false otherwise.
func (n *node) isInteger() bool {
if isInt(n.typ.TypeOf()) {
return true
}
if n.rval.IsValid() {
t := n.rval.Type()
if isInt(t) {
return true
}
if isFloat(t) {
// untyped float constant with null decimal part is ok
f := n.rval.Float()
if f == math.Trunc(f) {
n.rval = reflect.ValueOf(int(f))
n.typ.rtype = n.rval.Type()
return true
}
}
if isConstantValue(t) {
c := n.rval.Interface().(constant.Value)
switch c.Kind() {
case constant.Int:
return true
case constant.Float:
f, _ := constant.Float64Val(c)
if f == math.Trunc(f) {
n.rval = reflect.ValueOf(constant.ToInt(c))
n.typ.rtype = n.rval.Type()
return true
}
}
}
}
return false
}
// isNatural returns true if node type is natural, false otherwise.
func (n *node) isNatural() bool {
if isUint(n.typ.TypeOf()) {
@@ -2135,7 +2199,17 @@ func isField(n *node) bool {
}
func isRecursiveField(n *node) bool {
return isField(n) && (n.typ.recursive || n.typ.cat == ptrT && n.typ.val.recursive)
if !isField(n) {
return false
}
t := n.typ
for t != nil {
if t.recursive {
return true
}
t = t.val
}
return false
}
// isNewDefine returns true if node refers to a new definition.
@@ -2268,12 +2342,11 @@ func gotoLabel(s *symbol) {
}
}
func compositeGenerator(n *node, sc *scope) (gen bltnGenerator) {
func compositeGenerator(n *node) (gen bltnGenerator) {
switch n.typ.cat {
case aliasT, ptrT:
n.typ.val.untyped = n.typ.untyped
n.typ = n.typ.val
gen = compositeGenerator(n, sc)
gen = compositeGenerator(n)
case arrayT:
gen = arrayLit
case mapT:
@@ -2283,13 +2356,13 @@ func compositeGenerator(n *node, sc *scope) (gen bltnGenerator) {
case len(n.child) == 0:
gen = compositeLitNotype
case n.lastChild().kind == keyValueExpr:
if n.child[0].isType(sc) {
if n.nleft == 1 {
gen = compositeSparse
} else {
gen = compositeSparseNotype
}
default:
if n.child[0].isType(sc) {
if n.nleft == 1 {
gen = compositeLit
} else {
gen = compositeLitNotype

View File

@@ -78,8 +78,8 @@ func (interp *Interpreter) gta(root *node, rpath, pkgID string) ([]*node, error)
if typ.isBinMethod {
typ = &itype{cat: valueT, rtype: typ.methodCallType(), isBinMethod: true, scope: sc}
}
if sc.sym[dest.ident] == nil {
sc.sym[dest.ident] = &symbol{kind: varSym, global: true, index: sc.add(typ), typ: typ, rval: val}
if sc.sym[dest.ident] == nil || sc.sym[dest.ident].typ.incomplete {
sc.sym[dest.ident] = &symbol{kind: varSym, global: true, index: sc.add(typ), typ: typ, rval: val, node: n}
}
if n.anc.kind == constDecl {
sc.sym[dest.ident].kind = constSym
@@ -112,7 +112,7 @@ func (interp *Interpreter) gta(root *node, rpath, pkgID string) ([]*node, error)
sym1, exists1 := sc.sym[asImportName]
sym2, exists2 := sc.sym[c.ident]
if !exists1 && !exists2 {
sc.sym[c.ident] = &symbol{index: sc.add(n.typ), kind: varSym, global: true, typ: n.typ}
sc.sym[c.ident] = &symbol{index: sc.add(n.typ), kind: varSym, global: true, typ: n.typ, node: n}
continue
}
@@ -138,11 +138,13 @@ func (interp *Interpreter) gta(root *node, rpath, pkgID string) ([]*node, error)
if n.typ, err = nodeType(interp, sc, n.child[2]); err != nil {
return false
}
if isMethod(n) {
ident := n.child[1].ident
switch {
case isMethod(n):
// TODO(mpl): redeclaration detection
// Add a method symbol in the receiver type name space
var rcvrtype *itype
n.ident = n.child[1].ident
n.ident = ident
rcvr := n.child[0].child[0]
rtn := rcvr.lastChild()
typeName := rtn.ident
@@ -167,33 +169,32 @@ func (interp *Interpreter) gta(root *node, rpath, pkgID string) ([]*node, error)
}
rcvrtype.method = append(rcvrtype.method, n)
n.child[0].child[0].lastChild().typ = rcvrtype
} else {
ident := n.child[1].ident
case ident == "init":
// TODO(mpl): use constant instead of hardcoded string?
if ident != "init" {
asImportName := filepath.Join(ident, baseName)
if _, exists := sc.sym[asImportName]; exists {
// redeclaration error
// TODO(mpl): improve error with position of previous declaration.
err = n.cfgErrorf("%s redeclared in this block", ident)
return false
}
sym, exists := sc.sym[ident]
if exists {
// Make sure the symbol we found seems to be about another node, before calling
// it a redeclaration.
if sym.typ.isComplete() {
// TODO(mpl): this check might be too permissive?
if sym.kind != funcSym || sym.typ.cat != n.typ.cat || sym.node != n || sym.index != -1 {
// redeclaration error
// TODO(mpl): improve error with position of previous declaration.
err = n.cfgErrorf("%s redeclared in this block", ident)
return false
}
// init functions do not get declared as per the Go spec.
default:
asImportName := filepath.Join(ident, baseName)
if _, exists := sc.sym[asImportName]; exists {
// redeclaration error
// TODO(mpl): improve error with position of previous declaration.
err = n.cfgErrorf("%s redeclared in this block", ident)
return false
}
sym, exists := sc.sym[ident]
if exists {
// Make sure the symbol we found seems to be about another node, before calling
// it a redeclaration.
if sym.typ.isComplete() {
// TODO(mpl): this check might be too permissive?
if sym.kind != funcSym || sym.typ.cat != n.typ.cat || sym.node != n || sym.index != -1 {
// redeclaration error
// TODO(mpl): improve error with position of previous declaration.
err = n.cfgErrorf("%s redeclared in this block", ident)
return false
}
}
}
// Add a function symbol in the package name space
// Add a function symbol in the package name space except for init
sc.sym[n.child[1].ident] = &symbol{kind: funcSym, typ: n.typ, node: n, index: -1}
}
if !n.typ.isComplete() {

View File

@@ -3,6 +3,7 @@ package interp
import (
"bufio"
"context"
"errors"
"fmt"
"go/build"
"go/scanner"
@@ -14,6 +15,7 @@ import (
"runtime"
"runtime/debug"
"strconv"
"strings"
"sync"
"sync/atomic"
)
@@ -30,7 +32,7 @@ type node struct {
index int64 // node index (dot display)
findex int // index of value in frame or frame size (func def, type def)
level int // number of frame indirections to access value
nleft int // number of children in left part (assign)
nleft int // number of children in left part (assign) or indicates preceding type (compositeLit)
nright int // number of children in right part (assign)
kind nkind // kind of node
pos token.Pos // position in source code, relative to fset
@@ -271,9 +273,9 @@ func initUniverse() *scope {
"uintptr": {kind: typeSym, typ: &itype{cat: uintptrT, name: "uintptr"}},
// predefined Go constants
"false": {kind: constSym, typ: &itype{cat: boolT, name: "bool"}, rval: reflect.ValueOf(false)},
"true": {kind: constSym, typ: &itype{cat: boolT, name: "bool"}, rval: reflect.ValueOf(true)},
"iota": {kind: constSym, typ: &itype{cat: intT}},
"false": {kind: constSym, typ: untypedBool, rval: reflect.ValueOf(false)},
"true": {kind: constSym, typ: untypedBool, rval: reflect.ValueOf(true)},
"iota": {kind: constSym, typ: untypedInt},
// predefined Go zero value
"nil": {typ: &itype{cat: nilT, untyped: true}},
@@ -405,6 +407,13 @@ func (interp *Interpreter) Eval(src string) (res reflect.Value, err error) {
// Execute node closures
interp.run(root, nil)
// Wire and execute global vars
n, err := genGlobalVars([]*node{root}, interp.scopes[interp.Name])
if err != nil {
return res, err
}
interp.run(n, nil)
for _, n := range initNodes {
interp.run(n, interp.frame)
}
@@ -474,10 +483,33 @@ func (interp *Interpreter) Use(values Exports) {
continue
}
interp.binPkg[k] = v
if interp.binPkg[k] == nil {
interp.binPkg[k] = v
continue
}
for s, sym := range v {
interp.binPkg[k][s] = sym
}
}
}
// ignoreScannerError returns true if the error from Go scanner can be safely ignored
// to let the caller grab one more line before retrying to parse its input.
func ignoreScannerError(e *scanner.Error, s string) bool {
msg := e.Msg
if strings.HasSuffix(msg, "found 'EOF'") {
return true
}
if msg == "raw string literal not terminated" {
return true
}
if strings.HasPrefix(msg, "expected operand, found '}'") && !strings.HasSuffix(s, "}") {
return true
}
return false
}
// REPL performs a Read-Eval-Print-Loop on input reader.
// Results are printed on output writer.
func (interp *Interpreter) REPL(in io.Reader, out io.Writer) {
@@ -494,29 +526,38 @@ func (interp *Interpreter) REPL(in io.Reader, out io.Writer) {
sc.sym[name] = &symbol{kind: pkgSym, typ: &itype{cat: binPkgT, path: k, scope: sc}}
}
// Set prompt.
var v reflect.Value
var err error
prompt := getPrompt(in, out)
ctx, cancel := context.WithCancel(context.Background())
end := make(chan struct{}) // channel to terminate signal handling goroutine
sig := make(chan os.Signal, 1) // channel to trap interrupt signal (Ctrl-C)
prompt := getPrompt(in, out) // prompt activated on tty like IO stream
s := bufio.NewScanner(in) // read input stream line by line
var v reflect.Value // result value from eval
var err error // error from eval
src := "" // source string to evaluate
signal.Notify(sig, os.Interrupt)
prompt(v)
// Read, Eval, Print in a Loop.
src := ""
s := bufio.NewScanner(in)
for s.Scan() {
src += s.Text() + "\n"
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
handleSignal(ctx, cancel)
// The following goroutine handles interrupt signal by canceling eval.
go func() {
select {
case <-sig:
cancel()
case <-end:
}
}()
v, err = interp.EvalWithContext(ctx, src)
signal.Reset()
if err != nil {
switch e := err.(type) {
case scanner.ErrorList:
// Early failure in the scanner: the source is incomplete
// and no AST could be produced, neither compiled / run.
// Get one more line, and retry
continue
if len(e) == 0 || ignoreScannerError(e[0], s.Text()) {
continue
}
fmt.Fprintln(out, e[0])
case Panic:
fmt.Fprintln(out, e.Value)
fmt.Fprintln(out, string(e.Stack))
@@ -524,9 +565,20 @@ func (interp *Interpreter) REPL(in io.Reader, out io.Writer) {
fmt.Fprintln(out, err)
}
}
if errors.Is(err, context.Canceled) {
// Eval has been interrupted by the above signal handling goroutine.
ctx, cancel = context.WithCancel(context.Background())
} else {
// No interrupt, release the above signal handling goroutine.
end <- struct{}{}
}
src = ""
prompt(v)
}
cancel() // Do not defer, as cancel func may change over time.
// TODO(mpl): log s.Err() if not nil?
}
// Repl performs a Read-Eval-Print-Loop on input file descriptor.
@@ -553,16 +605,3 @@ func getPrompt(in io.Reader, out io.Writer) func(reflect.Value) {
}
return func(reflect.Value) {}
}
// handleSignal wraps signal handling for eval cancellation.
func handleSignal(ctx context.Context, cancel context.CancelFunc) {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
select {
case <-c:
cancel()
case <-ctx.Done():
}
}()
}

View File

@@ -35,6 +35,7 @@ func TestInterpConsistencyBuild(t *testing.T) {
if filepath.Ext(file.Name()) != ".go" ||
file.Name() == "assign11.go" || // expect error
file.Name() == "assign12.go" || // expect error
file.Name() == "assign15.go" || // expect error
file.Name() == "bad0.go" || // expect error
file.Name() == "const9.go" || // expect error
file.Name() == "export1.go" || // non-main package
@@ -44,6 +45,7 @@ func TestInterpConsistencyBuild(t *testing.T) {
file.Name() == "fun22.go" || // expect error
file.Name() == "if2.go" || // expect error
file.Name() == "import6.go" || // expect error
file.Name() == "init1.go" || // expect error
file.Name() == "io0.go" || // use random number
file.Name() == "op1.go" || // expect error
file.Name() == "op7.go" || // expect error
@@ -74,6 +76,10 @@ func TestInterpConsistencyBuild(t *testing.T) {
file.Name() == "redeclaration-global4.go" || // expect error
file.Name() == "redeclaration-global5.go" || // expect error
file.Name() == "redeclaration-global6.go" || // expect error
file.Name() == "restricted0.go" || // expect error
file.Name() == "restricted1.go" || // expect error
file.Name() == "restricted2.go" || // expect error
file.Name() == "restricted3.go" || // expect error
file.Name() == "server6.go" || // syntax parsing
file.Name() == "server5.go" || // syntax parsing
file.Name() == "server4.go" || // syntax parsing
@@ -198,7 +204,7 @@ func TestInterpErrorConsistency(t *testing.T) {
},
{
fileName: "op1.go",
expectedInterp: "5:2: illegal operand types for '+=' operator",
expectedInterp: "5:2: invalid operation: mismatched types int and float64",
expectedExec: "5:4: constant 1.3 truncated to integer",
},
{

View File

@@ -1,12 +1,17 @@
package interp_test
import (
"bytes"
"context"
"fmt"
"io"
"log"
"net/http"
"os"
"reflect"
"strconv"
"strings"
"sync"
"testing"
"time"
@@ -42,7 +47,9 @@ func TestEvalArithmetic(t *testing.T) {
{desc: "mul_II", src: "2 * 3", res: "6"},
{desc: "mul_FI", src: "2.2 * 3", res: "6.6"},
{desc: "mul_IF", src: "3 * 2.2", res: "6.6"},
{desc: "quo_Z", src: "3 / 0", err: "1:28: invalid operation: division by zero"},
{desc: "rem_FI", src: "8.2 % 4", err: "1:28: invalid operation: operator % not defined on float64"},
{desc: "rem_Z", src: "8 % 0", err: "1:28: invalid operation: division by zero"},
{desc: "shl_II", src: "1 << 8", res: "256"},
{desc: "shl_IN", src: "1 << -1", err: "1:28: invalid operation: shift count type int, must be integer"},
{desc: "shl_IF", src: "1 << 1.0", res: "2"},
@@ -55,9 +62,9 @@ func TestEvalArithmetic(t *testing.T) {
{desc: "neg_I", src: "-2", res: "-2"},
{desc: "pos_I", src: "+2", res: "2"},
{desc: "bitnot_I", src: "^2", res: "-3"},
{desc: "bitnot_F", src: "^0.2", err: "1:28: illegal operand type for '^' operator"},
{desc: "bitnot_F", src: "^0.2", err: "1:28: invalid operation: operator ^ not defined on float64"},
{desc: "not_B", src: "!false", res: "true"},
{desc: "not_I", src: "!0", err: "1:28: illegal operand type for '!' operator"},
{desc: "not_I", src: "!0", err: "1:28: invalid operation: operator ! not defined on int"},
})
}
@@ -65,9 +72,9 @@ func TestEvalAssign(t *testing.T) {
i := interp.New(interp.Options{})
runTests(t, i, []testCase{
{src: `a := "Hello"; a += " world"`, res: "Hello world"},
{src: `b := "Hello"; b += 1`, err: "1:42: illegal operand types for '+=' operator"},
{src: `c := "Hello"; c -= " world"`, err: "1:42: illegal operand types for '-=' operator"},
{src: "e := 64.4; e %= 64", err: "1:39: illegal operand types for '%=' operator"},
{src: `b := "Hello"; b += 1`, err: "1:42: invalid operation: mismatched types string and int"},
{src: `c := "Hello"; c -= " world"`, err: "1:42: invalid operation: operator -= not defined on string"},
{src: "e := 64.4; e %= 64", err: "1:39: invalid operation: operator %= not defined on float64"},
{src: "f := int64(3.2)", err: "1:33: truncated to integer"},
{src: "g := 1; g <<= 8", res: "256"},
{src: "h := 1; h >>= 8", res: "0"},
@@ -309,6 +316,40 @@ func TestEvalCompositeArray(t *testing.T) {
i := interp.New(interp.Options{})
runTests(t, i, []testCase{
{src: "a := []int{1, 2, 7: 20, 30}", res: "[1 2 0 0 0 0 0 20 30]"},
{src: `a := []int{1, 1.2}`, err: "1:42: 6/5 truncated to int"},
{src: `a := []int{0:1, 0:1}`, err: "1:46: duplicate index 0 in array or slice literal"},
{src: `a := []int{1.1:1, 1.2:"test"}`, err: "1:39: index float64 must be integer constant"},
{src: `a := [2]int{1, 1.2}`, err: "1:43: 6/5 truncated to int"},
{src: `a := [1]int{1, 2}`, err: "1:43: index 1 is out of bounds (>= 1)"},
})
}
func TestEvalCompositeMap(t *testing.T) {
i := interp.New(interp.Options{})
runTests(t, i, []testCase{
{src: `a := map[string]int{"one":1, "two":2}`, res: "map[one:1 two:2]"},
{src: `a := map[string]int{1:1, 2:2}`, err: "1:48: cannot convert 1 to string"},
{src: `a := map[string]int{"one":1, "two":2.2}`, err: "1:63: 11/5 truncated to int"},
{src: `a := map[string]int{1, "two":2}`, err: "1:48: missing key in map literal"},
{src: `a := map[string]int{"one":1, "one":2}`, err: "1:57: duplicate key one in map literal"},
})
}
func TestEvalCompositeStruct(t *testing.T) {
i := interp.New(interp.Options{})
runTests(t, i, []testCase{
{src: `a := struct{A,B,C int}{}`, res: "{0 0 0}"},
{src: `a := struct{A,B,C int}{1,2,3}`, res: "{1 2 3}"},
{src: `a := struct{A,B,C int}{1,2.2,3}`, err: "1:53: 11/5 truncated to int"},
{src: `a := struct{A,B,C int}{1,2}`, err: "1:53: too few values in struct literal"},
{src: `a := struct{A,B,C int}{1,2,3,4}`, err: "1:57: too many values in struct literal"},
{src: `a := struct{A,B,C int}{1,B:2,3}`, err: "1:53: mixture of field:value and value elements in struct literal"},
{src: `a := struct{A,B,C int}{A:1,B:2,C:3}`, res: "{1 2 3}"},
{src: `a := struct{A,B,C int}{B:2}`, res: "{0 2 0}"},
{src: `a := struct{A,B,C int}{A:1,D:2,C:3}`, err: "1:55: unknown field D in struct literal"},
{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"},
})
}
@@ -385,6 +426,25 @@ func TestEvalChan(t *testing.T) {
})
}
func TestEvalFunctionCallWithFunctionParam(t *testing.T) {
i := interp.New(interp.Options{})
eval(t, i, `
func Bar(s string, fn func(string)string) string { return fn(s) }
`)
v := eval(t, i, "Bar")
bar := v.Interface().(func(string, func(string) string) string)
got := bar("hello ", func(s string) string {
return s + "world!"
})
want := "hello world!"
if got != want {
t.Errorf("unexpected result of function eval: got %q, want %q", got, want)
}
}
func TestEvalMissingSymbol(t *testing.T) {
defer func() {
r := recover()
@@ -556,3 +616,117 @@ func assertEval(t *testing.T, i *interp.Interpreter, src, expectedError, expecte
t.Fatalf("got %v, want %s", res, expectedRes)
}
}
func TestEvalEOF(t *testing.T) {
tests := []struct {
desc string
src []string
errorLine int
}{
{
desc: "no error",
src: []string{
`func main() {`,
`println("foo")`,
`}`,
},
errorLine: -1,
},
{
desc: "no parsing error, but block error",
src: []string{
`func main() {`,
`println(foo)`,
`}`,
},
errorLine: 2,
},
{
desc: "parsing error",
src: []string{
`func main() {`,
`println(/foo)`,
`}`,
},
errorLine: 1,
},
}
for it, test := range tests {
i := interp.New(interp.Options{})
var stderr bytes.Buffer
safeStderr := &safeBuffer{buf: &stderr}
pin, pout := io.Pipe()
defer func() {
// Closing the pipe also takes care of making i.REPL terminate,
// hence freeing its goroutine.
_ = pin.Close()
_ = pout.Close()
}()
go func() {
i.REPL(pin, safeStderr)
}()
for k, v := range test.src {
if _, err := pout.Write([]byte(v + "\n")); err != nil {
t.Error(err)
}
Sleep(100 * time.Millisecond)
errMsg := safeStderr.String()
if k == test.errorLine {
if errMsg == "" {
t.Fatalf("%d: statement %q should have produced an error", it, v)
}
break
}
if errMsg != "" {
t.Fatalf("%d: unexpected error: %v", it, errMsg)
}
}
}
}
type safeBuffer struct {
mu sync.RWMutex
buf *bytes.Buffer
}
func (sb *safeBuffer) Read(p []byte) (int, error) {
return sb.buf.Read(p)
}
func (sb *safeBuffer) String() string {
sb.mu.RLock()
defer sb.mu.RUnlock()
return sb.buf.String()
}
func (sb *safeBuffer) Write(p []byte) (int, error) {
sb.mu.Lock()
defer sb.mu.Unlock()
return sb.buf.Write(p)
}
const (
// CITimeoutMultiplier is the multiplier for all timeouts in the CI.
CITimeoutMultiplier = 3
)
// Sleep pauses the current goroutine for at least the duration d.
func Sleep(d time.Duration) {
d = applyCIMultiplier(d)
time.Sleep(d)
}
func applyCIMultiplier(timeout time.Duration) time.Duration {
ci := os.Getenv("CI")
if ci == "" {
return timeout
}
b, err := strconv.ParseBool(ci)
if err != nil || !b {
return timeout
}
return time.Duration(float64(timeout) * CITimeoutMultiplier)
}

View File

@@ -35,12 +35,12 @@ func TestIsNatural(t *testing.T) {
n: &node{
typ: &itype{
rtype: func() reflect.Type {
var x = 3
x := 3
return reflect.TypeOf(x)
}(),
},
rval: func() reflect.Value {
var x = 3
x := 3
return reflect.ValueOf(x)
}(),
},

View File

@@ -867,7 +867,7 @@ func shl(n *node) {
func shlConst(n *node) {
v0, v1 := n.child[0].rval, n.child[1].rval
isConst := (v0.IsValid() && isConstantValue(v0.Type())) && (v1.IsValid() && isConstantValue(v1.Type()))
isConst := (v0.IsValid() && isConstantValue(v0.Type()))
t := n.typ.rtype
if isConst {
t = constVal
@@ -875,8 +875,7 @@ func shlConst(n *node) {
n.rval = reflect.New(t).Elem()
switch {
case isConst:
s, _ := constant.Uint64Val(vConstantValue(v1))
v := constant.Shift(vConstantValue(v0), token.SHL, uint(s))
v := constant.Shift(vConstantValue(v0), token.SHL, uint(vUint(v1)))
n.rval.Set(reflect.ValueOf(v))
case isUint(t):
n.rval.SetUint(vUint(v0) << vUint(v1))
@@ -953,7 +952,7 @@ func shr(n *node) {
func shrConst(n *node) {
v0, v1 := n.child[0].rval, n.child[1].rval
isConst := (v0.IsValid() && isConstantValue(v0.Type())) && (v1.IsValid() && isConstantValue(v1.Type()))
isConst := (v0.IsValid() && isConstantValue(v0.Type()))
t := n.typ.rtype
if isConst {
t = constVal
@@ -961,8 +960,7 @@ func shrConst(n *node) {
n.rval = reflect.New(t).Elem()
switch {
case isConst:
s, _ := constant.Uint64Val(vConstantValue(v1))
v := constant.Shift(vConstantValue(v0), token.SHR, uint(s))
v := constant.Shift(vConstantValue(v0), token.SHR, uint(vUint(v1)))
n.rval.Set(reflect.ValueOf(v))
case isUint(t):
n.rval.SetUint(vUint(v0) >> vUint(v1))
@@ -2053,7 +2051,7 @@ func equal(n *node) {
dest := genValue(n)
n.exec = func(f *frame) bltn {
i1 := v1(f).Interface()
dest(f).SetBool(i0 != i1)
dest(f).SetBool(i0 == i1)
return tnext
}
}
@@ -2075,7 +2073,7 @@ func equal(n *node) {
dest := genValue(n)
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
dest(f).SetBool(i0 != i1)
dest(f).SetBool(i0 == i1)
return tnext
}
}
@@ -2099,7 +2097,7 @@ func equal(n *node) {
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
i1 := v1(f).Interface()
dest(f).SetBool(i0 != i1)
dest(f).SetBool(i0 == i1)
return tnext
}
}
@@ -2536,80 +2534,6 @@ func greater(n *node) {
dest := genValueOutput(n, reflect.TypeOf(true))
c0, c1 := n.child[0], n.child[1]
if c0.typ.cat == aliasT || c1.typ.cat == aliasT {
switch {
case c0.rval.IsValid():
i0 := c0.rval.Interface()
v1 := genValue(c1)
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
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 {
i1 := v1(f).Interface()
dest(f).SetBool(i0 != i1)
return tnext
}
}
case c1.rval.IsValid():
i1 := c1.rval.Interface()
v0 := genValue(c0)
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
i0 := v0(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()
dest(f).SetBool(i0 != i1)
return tnext
}
}
default:
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 t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf(); {
case isString(t0) || isString(t1):
switch {
@@ -2899,80 +2823,6 @@ func greaterEqual(n *node) {
dest := genValueOutput(n, reflect.TypeOf(true))
c0, c1 := n.child[0], n.child[1]
if c0.typ.cat == aliasT || c1.typ.cat == aliasT {
switch {
case c0.rval.IsValid():
i0 := c0.rval.Interface()
v1 := genValue(c1)
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
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 {
i1 := v1(f).Interface()
dest(f).SetBool(i0 != i1)
return tnext
}
}
case c1.rval.IsValid():
i1 := c1.rval.Interface()
v0 := genValue(c0)
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
i0 := v0(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()
dest(f).SetBool(i0 != i1)
return tnext
}
}
default:
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 t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf(); {
case isString(t0) || isString(t1):
switch {
@@ -3262,80 +3112,6 @@ func lower(n *node) {
dest := genValueOutput(n, reflect.TypeOf(true))
c0, c1 := n.child[0], n.child[1]
if c0.typ.cat == aliasT || c1.typ.cat == aliasT {
switch {
case c0.rval.IsValid():
i0 := c0.rval.Interface()
v1 := genValue(c1)
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
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 {
i1 := v1(f).Interface()
dest(f).SetBool(i0 != i1)
return tnext
}
}
case c1.rval.IsValid():
i1 := c1.rval.Interface()
v0 := genValue(c0)
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
i0 := v0(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()
dest(f).SetBool(i0 != i1)
return tnext
}
}
default:
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 t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf(); {
case isString(t0) || isString(t1):
switch {
@@ -3625,80 +3401,6 @@ func lowerEqual(n *node) {
dest := genValueOutput(n, reflect.TypeOf(true))
c0, c1 := n.child[0], n.child[1]
if c0.typ.cat == aliasT || c1.typ.cat == aliasT {
switch {
case c0.rval.IsValid():
i0 := c0.rval.Interface()
v1 := genValue(c1)
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
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 {
i1 := v1(f).Interface()
dest(f).SetBool(i0 != i1)
return tnext
}
}
case c1.rval.IsValid():
i1 := c1.rval.Interface()
v0 := genValue(c0)
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
i0 := v0(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()
dest(f).SetBool(i0 != i1)
return tnext
}
}
default:
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 t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf(); {
case isString(t0) || isString(t1):
switch {

View File

@@ -7,6 +7,7 @@ import (
"go/constant"
"log"
"reflect"
"unsafe"
)
// bltn type defines functions which run at CFG execution.
@@ -83,6 +84,9 @@ func init() {
}
func (interp *Interpreter) run(n *node, cf *frame) {
if n == nil {
return
}
var f *frame
if cf == nil {
f = interp.frame
@@ -229,10 +233,11 @@ func typeAssert(n *node) {
func typeAssert2(n *node) {
c0, c1 := n.child[0], n.child[1]
value := genValue(c0) // input value
value0 := genValue(n.anc.child[0]) // returned result
value1 := genValue(n.anc.child[1]) // returned status
typ := c1.typ // type to assert or convert to
value := genValue(c0) // input value
value0 := genValue(n.anc.child[0]) // returned result
value1 := genValue(n.anc.child[1]) // returned status
setStatus := n.anc.child[1].ident != "_" // do not assign status to "_"
typ := c1.typ // type to assert or convert to
typID := typ.id()
rtype := typ.rtype // type to assert
next := getExec(n.tnext)
@@ -246,7 +251,9 @@ func typeAssert2(n *node) {
} else {
ok = false
}
value1(f).SetBool(ok)
if setStatus {
value1(f).SetBool(ok)
}
return next
}
case isInterface(typ):
@@ -256,7 +263,9 @@ func typeAssert2(n *node) {
if ok {
value0(f).Set(v)
}
value1(f).SetBool(ok)
if setStatus {
value1(f).SetBool(ok)
}
return next
}
case n.child[0].typ.cat == valueT:
@@ -266,7 +275,9 @@ func typeAssert2(n *node) {
if ok {
value0(f).Set(v)
}
value1(f).SetBool(ok)
if setStatus {
value1(f).SetBool(ok)
}
return next
}
default:
@@ -276,13 +287,18 @@ func typeAssert2(n *node) {
if ok {
value0(f).Set(v.value)
}
value1(f).SetBool(ok)
if setStatus {
value1(f).SetBool(ok)
}
return next
}
}
}
func canAssertTypes(src, dest reflect.Type) bool {
if dest == nil {
return false
}
if src == dest {
return true
}
@@ -351,14 +367,16 @@ func convert(n *node) {
}
}
func isRecursiveStruct(t *itype, rtype reflect.Type) bool {
func isRecursiveType(t *itype, rtype reflect.Type) bool {
if t.cat == structT && rtype.Kind() == reflect.Interface {
return true
}
if t.cat == ptrT && t.rtype != nil {
return isRecursiveStruct(t.val, t.rtype.Elem())
switch t.cat {
case ptrT, arrayT, mapT:
return isRecursiveType(t.val, t.val.rtype)
default:
return false
}
return false
}
func assign(n *node) {
@@ -387,8 +405,10 @@ func assign(n *node) {
case src.kind == basicLit && src.val == nil:
t := dest.typ.TypeOf()
svalue[i] = func(*frame) reflect.Value { return reflect.New(t).Elem() }
case isRecursiveStruct(dest.typ, dest.typ.rtype):
svalue[i] = genValueInterfacePtr(src)
case isRecursiveType(dest.typ, dest.typ.rtype):
svalue[i] = genValueRecursiveInterface(src, dest.typ.rtype)
case isRecursiveType(src.typ, src.typ.rtype):
svalue[i] = genValueRecursiveInterfacePtrValue(src)
case src.typ.untyped && isComplex(dest.typ.TypeOf()):
svalue[i] = genValueComplex(src)
case src.typ.untyped && !dest.typ.untyped:
@@ -505,19 +525,21 @@ func deref(n *node) {
value := genValue(n.child[0])
tnext := getExec(n.tnext)
i := n.findex
l := n.level
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
f.data[i] = value(f).Elem()
if f.data[i].Bool() {
r := value(f).Elem()
if r.Bool() {
getFrame(f, l).data[i] = r
return tnext
}
return fnext
}
} else {
n.exec = func(f *frame) bltn {
f.data[i] = value(f).Elem()
getFrame(f, l).data[i] = value(f).Elem()
return tnext
}
}
@@ -672,9 +694,13 @@ func genFunctionWrapper(n *node) func(*frame) reflect.Value {
// Copy function input arguments in local frame
for i, arg := range in {
if def.typ.arg[i].cat == interfaceT {
typ := def.typ.arg[i]
switch {
case typ.cat == interfaceT:
d[i].Set(reflect.ValueOf(valueInterface{value: arg.Elem()}))
} else {
case typ.cat == funcT && arg.Kind() == reflect.Func:
d[i].Set(reflect.ValueOf(genFunctionNode(arg)))
default:
d[i].Set(arg)
}
}
@@ -698,6 +724,10 @@ func genFunctionWrapper(n *node) func(*frame) reflect.Value {
}
}
func genFunctionNode(v reflect.Value) *node {
return &node{kind: funcType, action: aNop, rval: v, typ: &itype{cat: valueT, rtype: v.Type()}}
}
func genInterfaceWrapper(n *node, typ reflect.Type) func(*frame) reflect.Value {
value := genValue(n)
if typ == nil || typ.Kind() != reflect.Interface || typ.NumMethod() == 0 || n.typ.cat == valueT {
@@ -762,7 +792,7 @@ func call(n *node) {
var values []func(*frame) reflect.Value
if n.child[0].recv != nil {
// Compute method receiver value.
if isRecursiveStruct(n.child[0].recv.node.typ, n.child[0].recv.node.typ.rtype) {
if isRecursiveType(n.child[0].recv.node.typ, n.child[0].recv.node.typ.rtype) {
values = append(values, genValueRecvInterfacePtr(n.child[0]))
} else {
values = append(values, genValueRecv(n.child[0]))
@@ -796,20 +826,21 @@ func call(n *node) {
values = append(values, func(f *frame) reflect.Value { return 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() {
var argType reflect.Type
if variadic >= 0 && i >= variadic {
argType = n.child[0].typ.arg[variadic].val.TypeOf()
} else {
argType = n.child[0].typ.arg[i].TypeOf()
}
argType := arg.TypeOf()
convertLiteralValue(c, argType)
}
switch {
case len(n.child[0].typ.arg) > i && n.child[0].typ.arg[i].cat == interfaceT:
case arg.cat == interfaceT:
values = append(values, genValueInterface(c))
case isRecursiveStruct(c.typ, c.typ.rtype):
values = append(values, genValueDerefInterfacePtr(c))
case isRecursiveType(c.typ, c.typ.rtype):
values = append(values, genValueRecursiveInterfacePtrValue(c))
default:
values = append(values, genValue(c))
}
@@ -852,9 +883,10 @@ func call(n *node) {
}
default:
// Multiple return values frame index are indexed from the node frame index.
l := n.level
for i := range rtypes {
j := n.findex + i
rvalues[i] = func(f *frame) reflect.Value { return f.data[j] }
rvalues[i] = func(f *frame) reflect.Value { return getFrame(f, l).data[j] }
}
}
@@ -1006,6 +1038,21 @@ func pindex(i, variadic int) int {
return variadic
}
func getFrame(f *frame, l int) *frame {
switch l {
case 0:
return f
case 1:
return f.anc
case 2:
return f.anc.anc
}
for ; l > 0; l-- {
f = f.anc
}
return f
}
// Callbin calls a function from a bin import, accessible through reflect.
func callBin(n *node) {
tnext := getExec(n.tnext)
@@ -1033,7 +1080,7 @@ func callBin(n *node) {
}
for i, c := range child {
defType := funcType.In(pindex(i, variadic))
defType := funcType.In(rcvrOffset + pindex(i, variadic))
switch {
case isBinCall(c):
// Handle nested function calls: pass returned values as arguments
@@ -1067,7 +1114,7 @@ func callBin(n *node) {
values = append(values, genFunctionWrapper(c))
case interfaceT:
values = append(values, genValueInterfaceValue(c))
case arrayT:
case arrayT, variadicT:
switch c.typ.val.cat {
case interfaceT:
values = append(values, genValueInterfaceArray(c))
@@ -1106,6 +1153,7 @@ func callBin(n *node) {
case fnext != nil:
// Handle branching according to boolean result.
index := n.findex
level := n.level
n.exec = func(f *frame) bltn {
in := make([]reflect.Value, l)
for i, v := range values {
@@ -1113,7 +1161,7 @@ func callBin(n *node) {
}
res := callFn(value(f), in)
b := res[0].Bool()
f.data[index].SetBool(b)
getFrame(f, level).data[index].SetBool(b)
if b {
return tnext
}
@@ -1169,7 +1217,7 @@ func callBin(n *node) {
}
out := callFn(value(f), in)
for i := 0; i < len(out); i++ {
f.data[n.findex+i].Set(out[i])
getFrame(f, n.level).data[n.findex+i].Set(out[i])
}
return tnext
}
@@ -1180,6 +1228,7 @@ func callBin(n *node) {
func getIndexBinMethod(n *node) {
// dest := genValue(n)
i := n.findex
l := n.level
m := n.val.(int)
value := genValue(n.child[0])
next := getExec(n.tnext)
@@ -1187,20 +1236,21 @@ func getIndexBinMethod(n *node) {
n.exec = func(f *frame) bltn {
// Can not use .Set() because dest type contains the receiver and source not
// dest(f).Set(value(f).Method(m))
f.data[i] = value(f).Method(m)
getFrame(f, l).data[i] = value(f).Method(m)
return next
}
}
func getIndexBinPtrMethod(n *node) {
i := n.findex
l := n.level
m := n.val.(int)
value := genValue(n.child[0])
next := getExec(n.tnext)
n.exec = func(f *frame) bltn {
// Can not use .Set() because dest type contains the receiver and source not
f.data[i] = value(f).Addr().Method(m)
getFrame(f, l).data[i] = value(f).Addr().Method(m)
return next
}
}
@@ -1210,21 +1260,23 @@ func getIndexArray(n *node) {
tnext := getExec(n.tnext)
value0 := genValueArray(n.child[0]) // array
i := n.findex
l := n.level
if n.child[1].rval.IsValid() { // constant array index
ai := int(vInt(n.child[1].rval))
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
f.data[i] = value0(f).Index(ai)
if f.data[i].Bool() {
r := value0(f).Index(ai)
getFrame(f, l).data[i] = r
if r.Bool() {
return tnext
}
return fnext
}
} else {
n.exec = func(f *frame) bltn {
f.data[i] = value0(f).Index(ai)
getFrame(f, l).data[i] = value0(f).Index(ai)
return tnext
}
}
@@ -1235,8 +1287,9 @@ func getIndexArray(n *node) {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
_, vi := value1(f)
f.data[i] = value0(f).Index(int(vi))
if f.data[i].Bool() {
r := value0(f).Index(int(vi))
getFrame(f, l).data[i] = r
if r.Bool() {
return tnext
}
return fnext
@@ -1244,7 +1297,7 @@ func getIndexArray(n *node) {
} else {
n.exec = func(f *frame) bltn {
_, vi := value1(f)
f.data[i] = value0(f).Index(int(vi))
getFrame(f, l).data[i] = value0(f).Index(int(vi))
return tnext
}
}
@@ -1262,7 +1315,6 @@ func getIndexMap(n *node) {
z := reflect.New(n.child[0].typ.frameType().Elem()).Elem()
if n.child[1].rval.IsValid() { // constant map index
convertConstantValue(n.child[1])
mi := n.child[1].rval
switch {
@@ -1356,7 +1408,6 @@ func getIndexMap2(n *node) {
return
}
if n.child[1].rval.IsValid() { // constant map index
convertConstantValue(n.child[1])
mi := n.child[1].rval
switch {
case !doValue:
@@ -1447,6 +1498,7 @@ func getFunc(n *node) {
func getMethod(n *node) {
i := n.findex
l := n.level
next := getExec(n.tnext)
n.exec = func(f *frame) bltn {
@@ -1455,7 +1507,7 @@ func getMethod(n *node) {
nod.val = &nod
nod.recv = n.recv
nod.frame = fr
f.data[i] = reflect.ValueOf(&nod)
getFrame(f, l).data[i] = reflect.ValueOf(&nod)
return next
}
}
@@ -1465,6 +1517,7 @@ func getMethodByName(n *node) {
value0 := genValue(n.child[0])
name := n.child[1].ident
i := n.findex
l := n.level
n.exec = func(f *frame) bltn {
val := value0(f).Interface().(valueInterface)
@@ -1474,7 +1527,7 @@ func getMethodByName(n *node) {
nod.val = &nod
nod.recv = &receiver{nil, val.value, li}
nod.frame = fr
f.data[i] = reflect.ValueOf(&nod)
getFrame(f, l).data[i] = reflect.ValueOf(&nod)
return next
}
}
@@ -1484,6 +1537,7 @@ func getIndexSeq(n *node) {
index := n.val.([]int)
tnext := getExec(n.tnext)
i := n.findex
l := n.level
// Note:
// Here we have to store the result using
@@ -1498,44 +1552,63 @@ func getIndexSeq(n *node) {
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
f.data[i] = value(f).FieldByIndex(index)
if f.data[i].Bool() {
v := value(f)
if v.Type().Kind() == reflect.Interface && n.child[0].typ.recursive {
v = writableDeref(v)
}
r := v.FieldByIndex(index)
getFrame(f, l).data[i] = r
if r.Bool() {
return tnext
}
return fnext
}
} else {
n.exec = func(f *frame) bltn {
f.data[i] = value(f).FieldByIndex(index)
v := value(f)
if v.Type().Kind() == reflect.Interface && n.child[0].typ.recursive {
v = writableDeref(v)
}
getFrame(f, l).data[i] = v.FieldByIndex(index)
return tnext
}
}
}
//go:nocheckptr
func writableDeref(v reflect.Value) reflect.Value {
// Here we have an interface to a struct. Any attempt to dereference it will
// make a copy of the struct. We need to get a Value to the actual struct.
// TODO: using unsafe is a temporary measure. Rethink this.
return reflect.NewAt(v.Elem().Type(), unsafe.Pointer(v.InterfaceData()[1])).Elem() //nolint:govet
}
func getPtrIndexSeq(n *node) {
index := n.val.([]int)
tnext := getExec(n.tnext)
var value func(*frame) reflect.Value
if isRecursiveStruct(n.child[0].typ, n.child[0].typ.rtype) {
if isRecursiveType(n.child[0].typ, n.child[0].typ.rtype) {
v := genValue(n.child[0])
value = func(f *frame) reflect.Value { return v(f).Elem().Elem() }
} else {
value = genValue(n.child[0])
}
i := n.findex
l := n.level
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
f.data[i] = value(f).Elem().FieldByIndex(index)
if f.data[i].Bool() {
r := value(f).Elem().FieldByIndex(index)
getFrame(f, l).data[i] = r
if r.Bool() {
return tnext
}
return fnext
}
} else {
n.exec = func(f *frame) bltn {
f.data[i] = value(f).Elem().FieldByIndex(index)
getFrame(f, l).data[i] = value(f).Elem().FieldByIndex(index)
return tnext
}
}
@@ -1545,22 +1618,25 @@ func getIndexSeqField(n *node) {
value := genValue(n.child[0])
index := n.val.([]int)
i := n.findex
l := n.level
tnext := getExec(n.tnext)
if n.fnext != nil {
fnext := getExec(n.fnext)
if n.child[0].typ.TypeOf().Kind() == reflect.Ptr {
n.exec = func(f *frame) bltn {
f.data[i] = value(f).Elem().FieldByIndex(index)
if f.data[i].Bool() {
r := value(f).Elem().FieldByIndex(index)
getFrame(f, l).data[i] = r
if r.Bool() {
return tnext
}
return fnext
}
} else {
n.exec = func(f *frame) bltn {
f.data[i] = value(f).FieldByIndex(index)
if f.data[i].Bool() {
r := value(f).FieldByIndex(index)
getFrame(f, l).data[i] = r
if r.Bool() {
return tnext
}
return fnext
@@ -1569,12 +1645,12 @@ func getIndexSeqField(n *node) {
} else {
if n.child[0].typ.TypeOf().Kind() == reflect.Ptr {
n.exec = func(f *frame) bltn {
f.data[i] = value(f).Elem().FieldByIndex(index)
getFrame(f, l).data[i] = value(f).Elem().FieldByIndex(index)
return tnext
}
} else {
n.exec = func(f *frame) bltn {
f.data[i] = value(f).FieldByIndex(index)
getFrame(f, l).data[i] = value(f).FieldByIndex(index)
return tnext
}
}
@@ -1587,16 +1663,17 @@ func getIndexSeqPtrMethod(n *node) {
fi := index[1:]
mi := index[0]
i := n.findex
l := n.level
next := getExec(n.tnext)
if n.child[0].typ.TypeOf().Kind() == reflect.Ptr {
n.exec = func(f *frame) bltn {
f.data[i] = value(f).Elem().FieldByIndex(fi).Addr().Method(mi)
getFrame(f, l).data[i] = value(f).Elem().FieldByIndex(fi).Addr().Method(mi)
return next
}
} else {
n.exec = func(f *frame) bltn {
f.data[i] = value(f).FieldByIndex(fi).Addr().Method(mi)
getFrame(f, l).data[i] = value(f).FieldByIndex(fi).Addr().Method(mi)
return next
}
}
@@ -1608,16 +1685,17 @@ func getIndexSeqMethod(n *node) {
fi := index[1:]
mi := index[0]
i := n.findex
l := n.level
next := getExec(n.tnext)
if n.child[0].typ.TypeOf().Kind() == reflect.Ptr {
n.exec = func(f *frame) bltn {
f.data[i] = value(f).Elem().FieldByIndex(fi).Method(mi)
getFrame(f, l).data[i] = value(f).Elem().FieldByIndex(fi).Method(mi)
return next
}
} else {
n.exec = func(f *frame) bltn {
f.data[i] = value(f).FieldByIndex(fi).Method(mi)
getFrame(f, l).data[i] = value(f).FieldByIndex(fi).Method(mi)
return next
}
}
@@ -1814,7 +1892,7 @@ func arrayLit(n *node) {
value := valueGenerator(n, n.findex)
next := getExec(n.tnext)
child := n.child
if !n.typ.untyped {
if n.nleft == 1 {
child = n.child[1:]
}
@@ -1867,7 +1945,7 @@ func mapLit(n *node) {
value := valueGenerator(n, n.findex)
next := getExec(n.tnext)
child := n.child
if !n.typ.untyped {
if n.nleft == 1 {
child = n.child[1:]
}
typ := n.typ.TypeOf()
@@ -1902,7 +1980,7 @@ func compositeBinMap(n *node) {
value := valueGenerator(n, n.findex)
next := getExec(n.tnext)
child := n.child
if !n.typ.untyped {
if n.nleft == 1 {
child = n.child[1:]
}
typ := n.typ.TypeOf()
@@ -1946,10 +2024,11 @@ func compositeBinStruct(n *node) {
}
} else {
fieldIndex[i] = []int{i}
convertLiteralValue(c.child[1], typ.Field(i).Type)
if c.typ.cat == funcT {
convertLiteralValue(c.child[1], typ.Field(i).Type)
values[i] = genFunctionWrapper(c.child[1])
} else {
convertLiteralValue(c, typ.Field(i).Type)
values[i] = genValue(c)
}
}
@@ -1995,6 +2074,7 @@ func doCompositeLit(n *node, hasType bool) {
}
i := n.findex
l := n.level
n.exec = func(f *frame) bltn {
a := reflect.New(n.typ.TypeOf()).Elem()
for i, v := range values {
@@ -2006,7 +2086,7 @@ func doCompositeLit(n *node, hasType bool) {
case destInterface:
d.Set(reflect.ValueOf(valueInterface{n, a}))
default:
f.data[i] = a
getFrame(f, l).data[i] = a
}
return next
}
@@ -2023,6 +2103,7 @@ func doCompositeSparse(n *node, hasType bool) {
if hasType {
child = n.child[1:]
}
destInterface := destType(n).cat == interfaceT
values := make(map[int]func(*frame) reflect.Value)
a, _ := n.typ.zero()
@@ -2033,8 +2114,10 @@ func doCompositeSparse(n *node, hasType bool) {
switch {
case c1.typ.cat == funcT:
values[field] = genFunctionWrapper(c1)
case isRecursiveStruct(n.typ.field[field].typ, n.typ.field[field].typ.rtype):
values[field] = genValueInterfacePtr(c1)
case isArray(c1.typ) && c1.typ.val != nil && c1.typ.val.cat == interfaceT:
values[field] = genValueInterfaceArray(c1)
case isRecursiveType(n.typ.field[field].typ, n.typ.field[field].typ.rtype):
values[field] = genValueRecursiveInterface(c1, n.typ.field[field].typ.rtype)
default:
values[field] = genValue(c1)
}
@@ -2044,9 +2127,13 @@ func doCompositeSparse(n *node, hasType bool) {
for i, v := range values {
a.Field(i).Set(v(f))
}
if d := value(f); d.Type().Kind() == reflect.Ptr {
d := value(f)
switch {
case d.Type().Kind() == reflect.Ptr:
d.Set(a.Addr())
} else {
case destInterface:
d.Set(reflect.ValueOf(valueInterface{n, a}))
default:
d.Set(a)
}
return next
@@ -2288,8 +2375,13 @@ func _case(n *node) {
values[i] = genValue(n.child[i])
}
n.exec = func(f *frame) bltn {
v0 := value(f)
for _, v := range values {
if value(f).Interface() == v(f).Interface() {
v1 := v(f)
if !v0.Type().AssignableTo(v1.Type()) {
v0 = v0.Convert(v1.Type())
}
if v0.Interface() == v1.Interface() {
return tnext
}
}
@@ -2336,8 +2428,8 @@ func _append(n *node) {
switch {
case n.typ.val.cat == interfaceT:
values[i] = genValueInterface(arg)
case isRecursiveStruct(n.typ.val, n.typ.val.rtype):
values[i] = genValueInterfacePtr(arg)
case isRecursiveType(n.typ.val, n.typ.val.rtype):
values[i] = genValueRecursiveInterface(arg, n.typ.val.rtype)
case arg.typ.untyped:
values[i] = genValueAs(arg, n.child[1].typ.TypeOf().Elem())
default:
@@ -2358,8 +2450,8 @@ func _append(n *node) {
switch {
case n.typ.val.cat == interfaceT:
value0 = genValueInterface(n.child[2])
case isRecursiveStruct(n.typ.val, n.typ.val.rtype):
value0 = genValueInterfacePtr(n.child[2])
case isRecursiveType(n.typ.val, n.typ.val.rtype):
value0 = genValueRecursiveInterface(n.child[2], n.typ.val.rtype)
case n.child[2].typ.untyped:
value0 = genValueAs(n.child[2], n.child[1].typ.TypeOf().Elem())
default:
@@ -2582,6 +2674,7 @@ func recv(n *node) {
value := genValue(n.child[0])
tnext := getExec(n.tnext)
i := n.findex
l := n.level
if n.interp.cancelChan {
// Cancellable channel read
@@ -2589,10 +2682,10 @@ func recv(n *node) {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
// Fast: channel read doesn't block
var ok bool
ch := value(f)
if f.data[i], ok = ch.TryRecv(); ok {
if f.data[i].Bool() {
if r, ok := ch.TryRecv(); ok {
getFrame(f, l).data[i] = r
if r.Bool() {
return tnext
}
return fnext
@@ -2610,14 +2703,14 @@ func recv(n *node) {
} else {
n.exec = func(f *frame) bltn {
// Fast: channel read doesn't block
var ok bool
ch := value(f)
if f.data[i], ok = ch.TryRecv(); ok {
if r, ok := ch.TryRecv(); ok {
getFrame(f, l).data[i] = r
return tnext
}
// Slow: channel is blocked, allow cancel
var chosen int
chosen, f.data[i], _ = reflect.Select([]reflect.SelectCase{f.done, {Dir: reflect.SelectRecv, Chan: ch}})
chosen, getFrame(f, l).data[i], _ = reflect.Select([]reflect.SelectCase{f.done, {Dir: reflect.SelectRecv, Chan: ch}})
if chosen == 0 {
return nil
}
@@ -2629,7 +2722,8 @@ func recv(n *node) {
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
if f.data[i], _ = value(f).Recv(); f.data[i].Bool() {
if r, _ := value(f).Recv(); r.Bool() {
getFrame(f, l).data[i] = r
return tnext
}
return fnext
@@ -2637,7 +2731,7 @@ func recv(n *node) {
} else {
i := n.findex
n.exec = func(f *frame) bltn {
f.data[i], _ = value(f).Recv()
getFrame(f, l).data[i], _ = value(f).Recv()
return tnext
}
}
@@ -2685,7 +2779,7 @@ func convertLiteralValue(n *node, t reflect.Type) {
case n.typ.cat == nilT:
// Create a zero value of target type.
n.rval = reflect.New(t).Elem()
case !(n.kind == basicLit || n.rval.IsValid()) || t == nil || t.Kind() == reflect.Interface:
case !(n.kind == basicLit || n.rval.IsValid()) || t == nil || t.Kind() == reflect.Interface || t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Interface:
// Skip non-constant values, undefined target type or interface target type.
case n.rval.IsValid():
// Convert constant value to target type.
@@ -2697,20 +2791,6 @@ func convertLiteralValue(n *node, t reflect.Type) {
}
}
var bitlen = [...]int{
reflect.Int: 64,
reflect.Int8: 8,
reflect.Int16: 16,
reflect.Int32: 32,
reflect.Int64: 64,
reflect.Uint: 64,
reflect.Uint8: 8,
reflect.Uint16: 16,
reflect.Uint32: 32,
reflect.Uint64: 64,
reflect.Uintptr: 64,
}
func convertConstantValue(n *node) {
if !n.rval.IsValid() {
return
@@ -2918,6 +2998,7 @@ func _select(n *node) {
// slice expression: array[low:high:max].
func slice(n *node) {
i := n.findex
l := n.level
next := getExec(n.tnext)
value0 := genValueArray(n.child[0]) // array
value1 := genValue(n.child[1]) // low (if 2 or 3 args) or high (if 1 arg)
@@ -2926,7 +3007,7 @@ func slice(n *node) {
case 2:
n.exec = func(f *frame) bltn {
a := value0(f)
f.data[i] = a.Slice(int(value1(f).Int()), a.Len())
getFrame(f, l).data[i] = a.Slice(int(vInt(value1(f))), a.Len())
return next
}
case 3:
@@ -2934,7 +3015,7 @@ func slice(n *node) {
n.exec = func(f *frame) bltn {
a := value0(f)
f.data[i] = a.Slice(int(value1(f).Int()), int(value2(f).Int()))
getFrame(f, l).data[i] = a.Slice(int(vInt(value1(f))), int(vInt(value2(f))))
return next
}
case 4:
@@ -2943,7 +3024,7 @@ func slice(n *node) {
n.exec = func(f *frame) bltn {
a := value0(f)
f.data[i] = a.Slice3(int(value1(f).Int()), int(value2(f).Int()), int(value3(f).Int()))
getFrame(f, l).data[i] = a.Slice3(int(vInt(value1(f))), int(vInt(value2(f))), int(vInt(value3(f))))
return next
}
}
@@ -2952,6 +3033,7 @@ func slice(n *node) {
// slice expression, no low value: array[:high:max].
func slice0(n *node) {
i := n.findex
l := n.level
next := getExec(n.tnext)
value0 := genValueArray(n.child[0])
@@ -2959,14 +3041,14 @@ func slice0(n *node) {
case 1:
n.exec = func(f *frame) bltn {
a := value0(f)
f.data[i] = a.Slice(0, a.Len())
getFrame(f, l).data[i] = a.Slice(0, a.Len())
return next
}
case 2:
value1 := genValue(n.child[1])
n.exec = func(f *frame) bltn {
a := value0(f)
f.data[i] = a.Slice(0, int(value1(f).Int()))
getFrame(f, l).data[i] = a.Slice(0, int(vInt(value1(f))))
return next
}
case 3:
@@ -2974,7 +3056,7 @@ func slice0(n *node) {
value2 := genValue(n.child[2])
n.exec = func(f *frame) bltn {
a := value0(f)
f.data[i] = a.Slice3(0, int(value1(f).Int()), int(value2(f).Int()))
getFrame(f, l).data[i] = a.Slice3(0, int(vInt(value1(f))), int(vInt(value2(f))))
return next
}
}

View File

@@ -53,8 +53,6 @@ type symbol struct {
rval reflect.Value // default value (used for constants)
builtin bltnGenerator // Builtin function or nil
global bool // true if symbol is defined in global space
// TODO: implement constant checking
//constant bool // true if symbol value is constant
}
// scope type stores symbols in maps, and frame layout as array of types

View File

@@ -22,7 +22,7 @@ func (interp *Interpreter) importSrc(rPath, path string) (string, error) {
// In all other cases, absolute import paths are resolved from the GOPATH
// and the nested "vendor" directories.
if isPathRelative(path) {
if rPath == "main" {
if rPath == mainID {
rPath = "."
}
dir = filepath.Join(filepath.Dir(interp.Name), rPath, path)
@@ -132,6 +132,13 @@ func (interp *Interpreter) importSrc(rPath, path string) (string, error) {
interp.run(n, nil)
}
// Wire and execute global vars
n, err := genGlobalVars(rootNodes, interp.scopes[path])
if err != nil {
return "", err
}
interp.run(n, nil)
// Add main to list of functions to run, after all inits
if m := interp.main(); m != nil {
initNodes = append(initNodes, m)
@@ -146,7 +153,7 @@ func (interp *Interpreter) importSrc(rPath, path string) (string, error) {
func (interp *Interpreter) rootFromSourceLocation(rPath string) (string, error) {
sourceFile := interp.Name
if rPath != "main" || !strings.HasSuffix(sourceFile, ".go") {
if rPath != mainID || !strings.HasSuffix(sourceFile, ".go") {
return rPath, nil
}
wd, err := os.Getwd()
@@ -181,13 +188,62 @@ func pkgDir(goPath string, root, path string) (string, string, error) {
return "", "", fmt.Errorf("unable to find source related to: %q", path)
}
return pkgDir(goPath, previousRoot(root), path)
rootPath := filepath.Join(goPath, "src", root)
prevRoot, err := previousRoot(rootPath, root)
if err != nil {
return "", "", err
}
return pkgDir(goPath, prevRoot, path)
}
// Find the previous source root (vendor > vendor > ... > GOPATH).
func previousRoot(root string) string {
splitRoot := strings.Split(root, string(filepath.Separator))
const vendor = "vendor"
// Find the previous source root (vendor > vendor > ... > GOPATH).
func previousRoot(rootPath, root string) (string, error) {
rootPath = filepath.Clean(rootPath)
parent, final := filepath.Split(rootPath)
parent = filepath.Clean(parent)
// TODO(mpl): maybe it works for the special case main, but can't be bothered for now.
if root != mainID && final != vendor {
root = strings.TrimSuffix(root, string(filepath.Separator))
prefix := strings.TrimSuffix(rootPath, root)
// look for the closest vendor in one of our direct ancestors, as it takes priority.
var vendored string
for {
fi, err := os.Lstat(filepath.Join(parent, vendor))
if err == nil && fi.IsDir() {
vendored = strings.TrimPrefix(strings.TrimPrefix(parent, prefix), string(filepath.Separator))
break
}
if !os.IsNotExist(err) {
return "", err
}
// stop when we reach GOPATH/src/blah
parent = filepath.Dir(parent)
if parent == prefix {
break
}
// just an additional failsafe, stop if we reach the filesystem root.
// TODO(mpl): It should probably be a critical error actually,
// as we shouldn't have gone that high up in the tree.
if parent == string(filepath.Separator) {
break
}
}
if vendored != "" {
return vendored, nil
}
}
// TODO(mpl): the algorithm below might be redundant with the one above,
// but keeping it for now. Investigate/simplify/remove later.
splitRoot := strings.Split(root, string(filepath.Separator))
var index int
for i := len(splitRoot) - 1; i >= 0; i-- {
if splitRoot[i] == "vendor" {
@@ -197,10 +253,10 @@ func previousRoot(root string) string {
}
if index == 0 {
return ""
return "", nil
}
return filepath.Join(splitRoot[:index]...)
return filepath.Join(splitRoot[:index]...), nil
}
func effectivePkg(root, path string) string {

View File

@@ -196,9 +196,10 @@ func Test_pkgDir(t *testing.T) {
func Test_previousRoot(t *testing.T) {
testCases := []struct {
desc string
root string
expected string
desc string
root string
rootPathSuffix string
expected string
}{
{
desc: "GOPATH",
@@ -215,6 +216,18 @@ func Test_previousRoot(t *testing.T) {
root: "github.com/foo/pkg/vendor/guthib.com/containous/fromage/vendor/guthib.com/containous/fuu",
expected: "github.com/foo/pkg/vendor/guthib.com/containous/fromage",
},
{
desc: "vendor is sibling",
root: "github.com/foo/bar",
rootPathSuffix: "testdata/src/github.com/foo/bar",
expected: "github.com/foo",
},
{
desc: "vendor is uncle",
root: "github.com/foo/bar/baz",
rootPathSuffix: "testdata/src/github.com/foo/bar/baz",
expected: "github.com/foo",
},
}
for _, test := range testCases {
@@ -222,7 +235,20 @@ func Test_previousRoot(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
p := previousRoot(test.root)
var rootPath string
if test.rootPathSuffix != "" {
wd, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
rootPath = filepath.Join(wd, test.rootPathSuffix)
} else {
rootPath = vendor
}
p, err := previousRoot(rootPath, test.root)
if err != nil {
t.Error(err)
}
if p != test.expected {
t.Errorf("got: %s, want: %s", p, test.expected)

View File

@@ -0,0 +1 @@
package baz

View File

@@ -0,0 +1 @@
package whatever

View File

@@ -124,6 +124,15 @@ type itype struct {
scope *scope // type declaration scope (in case of re-parse incomplete type)
}
var (
untypedBool = &itype{cat: boolT, name: "bool", untyped: true}
untypedString = &itype{cat: stringT, name: "string", untyped: true}
untypedRune = &itype{cat: int32T, name: "int32", untyped: true}
untypedInt = &itype{cat: intT, name: "int", untyped: true}
untypedFloat = &itype{cat: float64T, name: "float64", untyped: true}
untypedComplex = &itype{cat: complex128T, name: "complex128", untyped: true}
)
// nodeType returns a type definition for the corresponding AST subtree.
func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
if n.typ != nil && !n.typ.incomplete {
@@ -133,7 +142,7 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
return n.typ, nil
}
var t = &itype{node: n, scope: sc}
t := &itype{node: n, scope: sc}
if n.anc.kind == typeSpec {
name := n.anc.child[0].ident
@@ -207,57 +216,27 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
case basicLit:
switch v := n.rval.Interface().(type) {
case bool:
t.cat = boolT
t.name = "bool"
case byte:
t.cat = uint8T
t.name = "uint8"
t.untyped = true
case complex64:
t.cat = complex64T
t.name = "complex64"
case complex128:
t.cat = complex128T
t.name = "complex128"
t.untyped = true
case float32:
t.cat = float32T
t.name = "float32"
t.untyped = true
case float64:
t.cat = float64T
t.name = "float64"
t.untyped = true
case int:
t.cat = intT
t.name = "int"
t.untyped = true
case uint:
t.cat = uintT
t.name = "uint"
t.untyped = true
n.rval = reflect.ValueOf(constant.MakeBool(v))
t = untypedBool
case rune:
t.cat = int32T
t.name = "int32"
t.untyped = true
// It is impossible to work out rune const literals in AST
// with the correct type so we must make the const type here.
n.rval = reflect.ValueOf(constant.MakeInt64(int64(v)))
t = untypedRune
case string:
t.cat = stringT
t.name = "string"
t.untyped = true
t = untypedString
case constant.Value:
switch v.Kind() {
case constant.Bool:
t = untypedBool
case constant.String:
t = untypedString
case constant.Int:
t.cat = intT
t.name = "int"
t.untyped = true
t = untypedInt
case constant.Float:
t.cat = float64T
t.name = "float64"
t.untyped = true
t = untypedFloat
case constant.Complex:
t.cat = complex128T
t.name = "complex128"
t.untyped = true
t = untypedComplex
default:
err = n.cfgErrorf("missing support for type %v", n.rval)
}
@@ -691,7 +670,6 @@ func fieldName(n *node) string {
}
var zeroValues [maxT]reflect.Value
var okFor [aMax][maxT]bool
func init() {
zeroValues[boolT] = reflect.ValueOf(false)
@@ -712,77 +690,6 @@ func init() {
zeroValues[uint32T] = reflect.ValueOf(uint32(0))
zeroValues[uint64T] = reflect.ValueOf(uint64(0))
zeroValues[uintptrT] = reflect.ValueOf(uintptr(0))
// Calculate the action -> type allowances
var (
okForEq [maxT]bool
okForCmp [maxT]bool
okForAdd [maxT]bool
okForAnd [maxT]bool
okForBool [maxT]bool
okForArith [maxT]bool
)
for cat := tcat(0); cat < maxT; cat++ {
if (cat >= intT && cat <= int64T) || (cat >= uintT && cat <= uintptrT) {
okForEq[cat] = true
okForCmp[cat] = true
okForAdd[cat] = true
okForAnd[cat] = true
okForArith[cat] = true
}
if cat == float32T || cat == float64T {
okForEq[cat] = true
okForCmp[cat] = true
okForAdd[cat] = true
okForArith[cat] = true
}
if cat == complex64T || cat == complex128T {
okForEq[cat] = true
okForAdd[cat] = true
okForArith[cat] = true
}
}
okForAdd[stringT] = true
okForBool[boolT] = true
okForEq[nilT] = true
okForEq[ptrT] = true
okForEq[interfaceT] = true
okForEq[errorT] = true
okForEq[chanT] = true
okForEq[stringT] = true
okForEq[boolT] = true
okForEq[mapT] = true // nil only
okForEq[funcT] = true // nil only
okForEq[arrayT] = true // array: only if element type is comparable slice: nil only
okForEq[structT] = true // only if all struct fields are comparable
okForCmp[stringT] = true
okFor[aAdd] = okForAdd
okFor[aAnd] = okForAnd
okFor[aLand] = okForBool
okFor[aAndNot] = okForAnd
okFor[aQuo] = okForArith
okFor[aEqual] = okForEq
okFor[aGreaterEqual] = okForCmp
okFor[aGreater] = okForCmp
okFor[aLowerEqual] = okForCmp
okFor[aLower] = okForCmp
okFor[aRem] = okForAnd
okFor[aMul] = okForArith
okFor[aNotEqual] = okForEq
okFor[aOr] = okForAnd
okFor[aLor] = okForBool
okFor[aSub] = okForArith
okFor[aXor] = okForAnd
okFor[aShl] = okForAnd
okFor[aShr] = okForAnd
okFor[aNeg] = okForArith
okFor[aNot] = okForBool
okFor[aPos] = okForArith
}
// Finalize returns a type pointer and error. It reparses a type from the
@@ -933,6 +840,26 @@ func (t *itype) comparable() bool {
return t.cat == nilT || typ != nil && typ.Comparable()
}
func (t *itype) assignableTo(o *itype) bool {
if t.equals(o) {
return true
}
if t.cat == aliasT && o.cat == aliasT {
// if alias types are not identical, it is not assignable.
return false
}
if t.isNil() && o.hasNil() || o.isNil() && t.hasNil() {
return true
}
return t.TypeOf().AssignableTo(o.TypeOf())
}
// ordered returns true if the type is ordered.
func (t *itype) ordered() bool {
typ := t.TypeOf()
return isInt(typ) || isFloat(typ) || isString(typ)
}
// Equals returns true if the given type is identical to the receiver one.
func (t *itype) equals(o *itype) bool {
switch ti, oi := isInterface(t), isInterface(o); {
@@ -1007,13 +934,22 @@ func (t *itype) methods() methodSet {
// id returns a unique type identificator string.
func (t *itype) id() (res string) {
if t.name != "" {
return t.path + "." + t.name
if t.path != "" {
return t.path + "." + t.name
}
return t.name
}
switch t.cat {
case nilT:
res = "nil"
case arrayT:
res = "[" + strconv.Itoa(t.size) + "]" + t.val.id()
case chanT:
res = "<-" + t.val.id()
res = "chan " + t.val.id()
case chanSendT:
res = "chan<- " + t.val.id()
case chanRecvT:
res = "<-chan " + t.val.id()
case funcT:
res = "func("
for _, t := range t.arg {
@@ -1041,7 +977,11 @@ func (t *itype) id() (res string) {
}
res += "}"
case valueT:
res = t.rtype.PkgPath() + "." + t.rtype.Name()
res = ""
if t.rtype.PkgPath() != "" {
res += t.rtype.PkgPath() + "."
}
res += t.rtype.Name()
}
return res
}
@@ -1214,8 +1154,10 @@ func exportName(s string) string {
return "X" + s
}
var interf = reflect.TypeOf((*interface{})(nil)).Elem()
var constVal = reflect.TypeOf((*constant.Value)(nil)).Elem()
var (
interf = reflect.TypeOf((*interface{})(nil)).Elem()
constVal = reflect.TypeOf((*constant.Value)(nil)).Elem()
)
// RefType returns a reflect.Type representation from an interpreter type.
// In simple cases, reflect types are directly mapped from the interpreter
@@ -1241,6 +1183,10 @@ func (t *itype) refType(defined map[string]*itype, wrapRecursive bool) reflect.T
// a node can still point to a previous copy.
st.typ.recursive = st.typ.recursive || st.typ.isRecursive()
recursive = st.typ.isRecursive()
// It is possible that t.recursive is not inline with st.typ.recursive
// which will break recursion detection. Set it here to make sure it
// is correct.
t.recursive = recursive
}
}
if wrapRecursive && t.recursive {
@@ -1252,9 +1198,11 @@ func (t *itype) refType(defined map[string]*itype, wrapRecursive bool) reflect.T
if defined[name] != nil && defined[name].rtype != nil {
return defined[name].rtype
}
if t.val != nil && defined[t.val.path+"/"+t.val.name] != nil && t.val.rtype == nil {
if t.val != nil && t.val.cat == structT && t.val.rtype == nil && hasRecursiveStruct(t.val, copyDefined(defined)) {
// Replace reference to self (direct or indirect) by an interface{} to handle
// recursive types with reflect.
typ := *t.val
t.val = &typ
t.val.rtype = interf
recursive = true
}
@@ -1357,86 +1305,64 @@ func (t *itype) implements(it *itype) bool {
return t.methods().contains(it.methods())
}
var errType = reflect.TypeOf((*error)(nil)).Elem()
func catOf(t reflect.Type) tcat {
if t == nil {
return nilT
// defaultType returns the default type of an untyped type.
func (t *itype) defaultType() *itype {
if !t.untyped {
return t
}
if t == errType {
return errorT
}
switch t.Kind() {
case reflect.Bool:
return boolT
case reflect.Int:
return intT
case reflect.Int8:
return int8T
case reflect.Int16:
return int16T
case reflect.Int32:
return int32T
case reflect.Int64:
return int64T
case reflect.Uint:
return uintT
case reflect.Uint8:
return uint8T
case reflect.Uint16:
return uint16T
case reflect.Uint32:
return uint32T
case reflect.Uint64:
return uint64T
case reflect.Uintptr:
return uintptrT
case reflect.Float32:
return float32T
case reflect.Float64:
return float64T
case reflect.Complex64:
return complex64T
case reflect.Complex128:
return complex128T
case reflect.Array, reflect.Slice:
return arrayT
case reflect.Chan:
return chanT
case reflect.Func:
return funcT
case reflect.Interface:
return interfaceT
case reflect.Map:
return mapT
case reflect.Ptr:
return ptrT
case reflect.String:
return stringT
case reflect.Struct:
return structT
case reflect.UnsafePointer:
return uintptrT
}
return nilT
typ := *t
typ.untyped = false
return &typ
}
func catOfConst(v reflect.Value) tcat {
c, ok := v.Interface().(constant.Value)
if !ok {
return nilT
func (t *itype) isNil() bool { return t.cat == nilT }
func (t *itype) hasNil() bool {
switch t.TypeOf().Kind() {
case reflect.UnsafePointer:
return true
case reflect.Slice, reflect.Ptr, reflect.Func, reflect.Interface, reflect.Map, reflect.Chan:
return true
}
return false
}
func copyDefined(m map[string]*itype) map[string]*itype {
n := make(map[string]*itype, len(m))
for k, v := range m {
n[k] = v
}
return n
}
// hasRecursiveStruct determines if a struct is a recursion or a recursion
// intermediate. A recursion intermediate is a struct that contains a recursive
// struct.
func hasRecursiveStruct(t *itype, defined map[string]*itype) bool {
if len(defined) == 0 {
return false
}
switch c.Kind() {
case constant.Int:
return intT
case constant.Float:
return float64T
case constant.Complex:
return complex128T
default:
return nilT
typ := t
for typ != nil {
if typ.cat != structT {
typ = typ.val
continue
}
if defined[typ.path+"/"+typ.name] != nil {
return true
}
defined[typ.path+"/"+typ.name] = typ
for _, f := range typ.field {
if hasRecursiveStruct(f.typ, defined) {
return true
}
}
return false
}
return false
}
func constToInt(c constant.Value) int {
@@ -1465,14 +1391,6 @@ func isShiftNode(n *node) bool {
return false
}
func isComparisonNode(n *node) bool {
switch n.action {
case aEqual, aNotEqual, aGreater, aGreaterEqual, aLower, aLowerEqual:
return true
}
return false
}
// chanElement returns the channel element type.
func chanElement(t *itype) *itype {
switch t.cat {
@@ -1488,11 +1406,19 @@ func chanElement(t *itype) *itype {
func isBool(t *itype) bool { return t.TypeOf().Kind() == reflect.Bool }
func isChan(t *itype) bool { return t.TypeOf().Kind() == reflect.Chan }
func isFunc(t *itype) bool { return t.TypeOf().Kind() == reflect.Func }
func isMap(t *itype) bool { return t.TypeOf().Kind() == reflect.Map }
func isPtr(t *itype) bool { return t.TypeOf().Kind() == reflect.Ptr }
func isSendChan(t *itype) bool {
rt := t.TypeOf()
return rt.Kind() == reflect.Chan && rt.ChanDir() == reflect.SendDir
}
func isMap(t *itype) bool { return t.TypeOf().Kind() == reflect.Map }
func isArray(t *itype) bool {
k := t.TypeOf().Kind()
return k == reflect.Array || k == reflect.Slice
}
func isInterfaceSrc(t *itype) bool {
return t.cat == interfaceT || (t.cat == aliasT && isInterfaceSrc(t.val))
@@ -1518,6 +1444,11 @@ func isStruct(t *itype) bool {
}
}
func isConstType(t *itype) bool {
rt := t.TypeOf()
return isBoolean(rt) || isString(rt) || isNumber(rt)
}
func isInt(t reflect.Type) bool {
if t == nil {
return false
@@ -1575,5 +1506,6 @@ func isFloat64(t reflect.Type) bool { return t != nil && t.Kind() == reflect.Flo
func isNumber(t reflect.Type) bool {
return isInt(t) || isFloat(t) || isComplex(t) || isConstantValue(t)
}
func isBoolean(t reflect.Type) bool { return t != nil && t.Kind() == reflect.Bool }
func isString(t reflect.Type) bool { return t != nil && t.Kind() == reflect.String }
func isConstantValue(t reflect.Type) bool { return t != nil && t.Implements(constVal) }

670
interp/typecheck.go Normal file
View File

@@ -0,0 +1,670 @@
package interp
import (
"errors"
"go/constant"
"math"
"reflect"
)
type opPredicates map[action]func(reflect.Type) bool
// typecheck handles all type checking following "go/types" logic.
//
// Due to variant type systems (itype vs reflect.Type) a single
// type system should used, namely reflect.Type with exception
// of the untyped flag on itype.
type typecheck struct{}
// op type checks an expression against a set of expression predicates.
func (check typecheck) op(p opPredicates, a action, n, c *node, t reflect.Type) error {
if pred := p[a]; pred != nil {
if !pred(t) {
return n.cfgErrorf("invalid operation: operator %v not defined on %s", n.action, c.typ.id())
}
} else {
return n.cfgErrorf("invalid operation: unknown operator %v", n.action)
}
return nil
}
// assignment checks if n can be assigned to typ.
//
// Use typ == nil to indicate assignment to an untyped blank identifier.
func (check typecheck) assignment(n *node, typ *itype, context string) error {
if n.typ.untyped {
if typ == nil || isInterface(typ) {
if typ == nil && n.typ.cat == nilT {
return n.cfgErrorf("use of untyped nil in %s", context)
}
typ = n.typ.defaultType()
}
if err := check.convertUntyped(n, typ); err != nil {
return err
}
}
if typ == nil {
return nil
}
if !n.typ.assignableTo(typ) {
return n.cfgErrorf("cannot use type %s as type %s in %s", n.typ.id(), typ.id(), context)
}
return nil
}
// assignExpr type checks an assign expression.
//
// This is done per pair of assignments.
func (check typecheck) assignExpr(n, dest, src *node) error {
if n.action == aAssign {
isConst := n.anc.kind == constDecl
if !isConst {
// var operations must be typed
dest.typ = dest.typ.defaultType()
}
return check.assignment(src, dest.typ, "assignment")
}
// assignment operations.
if n.nleft > 1 || n.nright > 1 {
return n.cfgErrorf("assignment operation %s requires single-valued expressions", n.action)
}
return check.binaryExpr(n)
}
// addressExpr type checks a unary address expression.
func (check typecheck) addressExpr(n *node) error {
c0 := n.child[0]
found := false
for !found {
switch c0.kind {
case parenExpr:
c0 = c0.child[0]
continue
case selectorExpr:
c0 = c0.child[1]
continue
case indexExpr:
c := c0.child[0]
if isArray(c.typ) || isMap(c.typ) {
c0 = c
continue
}
case compositeLitExpr, identExpr:
found = true
continue
}
return n.cfgErrorf("invalid operation: cannot take address of %s", c0.typ.id())
}
return nil
}
var unaryOpPredicates = opPredicates{
aPos: isNumber,
aNeg: isNumber,
aBitNot: isInt,
aNot: isBoolean,
}
// unaryExpr type checks a unary expression.
func (check typecheck) unaryExpr(n *node) error {
c0 := n.child[0]
t0 := c0.typ.TypeOf()
if n.action == aRecv {
if !isChan(c0.typ) {
return n.cfgErrorf("invalid operation: cannot receive from non-channel %s", c0.typ.id())
}
if isSendChan(c0.typ) {
return n.cfgErrorf("invalid operation: cannot receive from send-only channel %s", c0.typ.id())
}
return nil
}
if err := check.op(unaryOpPredicates, n.action, n, c0, t0); err != nil {
return err
}
return nil
}
// shift type checks a shift binary expression.
func (check typecheck) shift(n *node) error {
c0, c1 := n.child[0], n.child[1]
t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf()
var v0 constant.Value
if c0.typ.untyped {
v0 = constant.ToInt(c0.rval.Interface().(constant.Value))
c0.rval = reflect.ValueOf(v0)
}
if !(c0.typ.untyped && v0 != nil && v0.Kind() == constant.Int || isInt(t0)) {
return n.cfgErrorf("invalid operation: shift of type %v", c0.typ.id())
}
switch {
case c1.typ.untyped:
if err := check.convertUntyped(c1, &itype{cat: uintT, name: "uint"}); err != nil {
return n.cfgErrorf("invalid operation: shift count type %v, must be integer", c1.typ.id())
}
case isInt(t1):
// nothing to do
default:
return n.cfgErrorf("invalid operation: shift count type %v, must be integer", c1.typ.id())
}
return nil
}
// comparison type checks a comparison binary expression.
func (check typecheck) comparison(n *node) error {
c0, c1 := n.child[0], n.child[1]
if !c0.typ.assignableTo(c1.typ) && !c1.typ.assignableTo(c0.typ) {
return n.cfgErrorf("invalid operation: mismatched types %s and %s", c0.typ.id(), c1.typ.id())
}
ok := false
switch n.action {
case aEqual, aNotEqual:
ok = c0.typ.comparable() && c1.typ.comparable() || c0.typ.isNil() && c1.typ.hasNil() || c1.typ.isNil() && c0.typ.hasNil()
case aLower, aLowerEqual, aGreater, aGreaterEqual:
ok = c0.typ.ordered() && c1.typ.ordered()
}
if !ok {
typ := c0.typ
if typ.isNil() {
typ = c1.typ
}
return n.cfgErrorf("invalid operation: operator %v not defined on %s", n.action, typ.id(), ".")
}
return nil
}
var binaryOpPredicates = opPredicates{
aAdd: func(typ reflect.Type) bool { return isNumber(typ) || isString(typ) },
aSub: isNumber,
aMul: isNumber,
aQuo: isNumber,
aRem: isInt,
aAnd: isInt,
aOr: isInt,
aXor: isInt,
aAndNot: isInt,
aLand: isBoolean,
aLor: isBoolean,
}
// binaryExpr type checks a binary expression.
func (check typecheck) binaryExpr(n *node) error {
c0, c1 := n.child[0], n.child[1]
a := n.action
if isAssignAction(a) {
a--
}
if isShiftAction(a) {
return check.shift(n)
}
_ = check.convertUntyped(c0, c1.typ)
_ = check.convertUntyped(c1, c0.typ)
if isComparisonAction(a) {
return check.comparison(n)
}
if !c0.typ.equals(c1.typ) {
return n.cfgErrorf("invalid operation: mismatched types %s and %s", c0.typ.id(), c1.typ.id())
}
t0 := c0.typ.TypeOf()
if err := check.op(binaryOpPredicates, a, n, c0, t0); err != nil {
return err
}
switch n.action {
case aQuo, aRem:
if (c0.typ.untyped || isInt(t0)) && c1.typ.untyped && constant.Sign(c1.rval.Interface().(constant.Value)) == 0 {
return n.cfgErrorf("invalid operation: division by zero")
}
}
return nil
}
func (check typecheck) index(n *node, max int) error {
if err := check.convertUntyped(n, &itype{cat: intT, name: "int"}); err != nil {
return err
}
if !isInt(n.typ.TypeOf()) {
return n.cfgErrorf("index %s must be integer", n.typ.id())
}
if !n.rval.IsValid() || max < 1 {
return nil
}
if int(vInt(n.rval)) >= max {
return n.cfgErrorf("index %s is out of bounds", n.typ.id())
}
return nil
}
// arrayLitExpr type checks an array composite literal expression.
func (check typecheck) arrayLitExpr(child []*node, typ *itype, length int) error {
visited := make(map[int]bool, len(child))
index := 0
for _, c := range child {
n := c
switch {
case c.kind == keyValueExpr:
if err := check.index(c.child[0], length); err != nil {
return c.cfgErrorf("index %s must be integer constant", c.child[0].typ.id())
}
n = c.child[1]
index = int(vInt(c.child[0].rval))
case length > 0 && index >= length:
return c.cfgErrorf("index %d is out of bounds (>= %d)", index, length)
}
if visited[index] {
return n.cfgErrorf("duplicate index %d in array or slice literal", index)
}
visited[index] = true
index++
if err := check.assignment(n, typ, "array or slice literal"); err != nil {
return err
}
}
return nil
}
// mapLitExpr type checks an map composite literal expression.
func (check typecheck) mapLitExpr(child []*node, ktyp, vtyp *itype) error {
visited := make(map[interface{}]bool, len(child))
for _, c := range child {
if c.kind != keyValueExpr {
return c.cfgErrorf("missing key in map literal")
}
key, val := c.child[0], c.child[1]
if err := check.assignment(key, ktyp, "map literal"); err != nil {
return err
}
if key.rval.IsValid() {
kval := key.rval.Interface()
if visited[kval] {
return c.cfgErrorf("duplicate key %s in map literal", kval)
}
visited[kval] = true
}
if err := check.assignment(val, vtyp, "map literal"); err != nil {
return err
}
}
return nil
}
// structLitExpr type checks an struct composite literal expression.
func (check typecheck) structLitExpr(child []*node, typ *itype) error {
if len(child) == 0 {
return nil
}
if child[0].kind == keyValueExpr {
// All children must be keyValueExpr
visited := make([]bool, len(typ.field))
for _, c := range child {
if c.kind != keyValueExpr {
return c.cfgErrorf("mixture of field:value and value elements in struct literal")
}
key, val := c.child[0], c.child[1]
name := key.ident
if name == "" {
return c.cfgErrorf("invalid field name %s in struct literal", key.typ.id())
}
i := typ.fieldIndex(name)
if i < 0 {
return c.cfgErrorf("unknown field %s in struct literal", name)
}
field := typ.field[i]
if err := check.assignment(val, field.typ, "struct literal"); err != nil {
return err
}
if visited[i] {
return c.cfgErrorf("duplicate field name %s in struct literal", name)
}
visited[i] = true
}
return nil
}
// No children can be keyValueExpr
for i, c := range child {
if c.kind == keyValueExpr {
return c.cfgErrorf("mixture of field:value and value elements in struct literal")
}
if i >= len(typ.field) {
return c.cfgErrorf("too many values in struct literal")
}
field := typ.field[i]
// TODO(nick): check if this field is not exported and in a different package.
if err := check.assignment(c, field.typ, "struct literal"); err != nil {
return err
}
}
if len(child) < len(typ.field) {
return child[len(child)-1].cfgErrorf("too few values in struct literal")
}
return nil
}
// structBinLitExpr type checks an struct composite literal expression on a binary type.
func (check typecheck) structBinLitExpr(child []*node, typ reflect.Type) error {
if len(child) == 0 {
return nil
}
if child[0].kind == keyValueExpr {
// All children must be keyValueExpr
visited := make(map[string]bool, typ.NumField())
for _, c := range child {
if c.kind != keyValueExpr {
return c.cfgErrorf("mixture of field:value and value elements in struct literal")
}
key, val := c.child[0], c.child[1]
name := key.ident
if name == "" {
return c.cfgErrorf("invalid field name %s in struct literal", key.typ.id())
}
field, ok := typ.FieldByName(name)
if !ok {
return c.cfgErrorf("unknown field %s in struct literal", name)
}
if err := check.assignment(val, &itype{cat: valueT, rtype: field.Type}, "struct literal"); err != nil {
return err
}
if visited[field.Name] {
return c.cfgErrorf("duplicate field name %s in struct literal", name)
}
visited[field.Name] = true
}
return nil
}
// No children can be keyValueExpr
for i, c := range child {
if c.kind == keyValueExpr {
return c.cfgErrorf("mixture of field:value and value elements in struct literal")
}
if i >= typ.NumField() {
return c.cfgErrorf("too many values in struct literal")
}
field := typ.Field(i)
if !canExport(field.Name) {
return c.cfgErrorf("implicit assignment to unexported field %s in %s literal", field.Name, typ)
}
if err := check.assignment(c, &itype{cat: valueT, rtype: field.Type}, "struct literal"); err != nil {
return err
}
}
if len(child) < typ.NumField() {
return child[len(child)-1].cfgErrorf("too few values in struct literal")
}
return nil
}
var errCantConvert = errors.New("cannot convert")
func (check typecheck) convertUntyped(n *node, typ *itype) error {
if n.typ == nil || !n.typ.untyped || typ == nil {
return nil
}
convErr := n.cfgErrorf("cannot convert %s to %s", n.typ.id(), typ.id())
ntyp, ttyp := n.typ.TypeOf(), typ.TypeOf()
if typ.untyped {
// Both n and target are untyped.
nkind, tkind := ntyp.Kind(), ttyp.Kind()
if isNumber(ntyp) && isNumber(ttyp) {
if nkind < tkind {
n.typ = typ
}
} else if nkind != tkind {
return convErr
}
return nil
}
var (
ityp *itype
rtyp reflect.Type
err error
)
switch {
case typ.isNil() && n.typ.isNil():
n.typ = typ
return nil
case isNumber(ttyp) || isString(ttyp) || isBoolean(ttyp):
ityp = typ
rtyp = ttyp
case isInterface(typ):
if n.typ.isNil() {
return nil
}
if len(n.typ.methods()) > 0 { // untyped cannot be set to iface
return convErr
}
ityp = n.typ.defaultType()
rtyp = ntyp
case isArray(typ) || isMap(typ) || isChan(typ) || isFunc(typ) || isPtr(typ):
// TODO(nick): above we are acting on itype, but really it is an rtype check. This is not clear which type
// plain we are in. Fix this later.
if !n.typ.isNil() {
return convErr
}
return nil
default:
return convErr
}
if err := check.representable(n, rtyp); err != nil {
return err
}
n.rval, err = check.convertConst(n.rval, rtyp)
if err != nil {
if errors.Is(err, errCantConvert) {
return convErr
}
return n.cfgErrorf(err.Error())
}
n.typ = ityp
return nil
}
func (check typecheck) representable(n *node, t reflect.Type) error {
if !n.rval.IsValid() {
// TODO(nick): This should be an error as the const is in the frame which is undesirable.
return nil
}
c, ok := n.rval.Interface().(constant.Value)
if !ok {
// TODO(nick): This should be an error as untyped strings and bools should be constant.Values.
return nil
}
if !representableConst(c, t) {
typ := n.typ.TypeOf()
if isNumber(typ) && isNumber(t) {
// numeric conversion : error msg
//
// integer -> integer : overflows
// integer -> float : overflows (actually not possible)
// float -> integer : truncated
// float -> float : overflows
//
if !isInt(typ) && isInt(t) {
return n.cfgErrorf("%s truncated to %s", c.ExactString(), t.Kind().String())
}
return n.cfgErrorf("%s overflows %s", c.ExactString(), t.Kind().String())
}
return n.cfgErrorf("cannot convert %s to %s", c.ExactString(), t.Kind().String())
}
return nil
}
func (check typecheck) convertConst(v reflect.Value, t reflect.Type) (reflect.Value, error) {
if !v.IsValid() {
// TODO(nick): This should be an error as the const is in the frame which is undesirable.
return v, nil
}
c, ok := v.Interface().(constant.Value)
if !ok {
// TODO(nick): This should be an error as untyped strings and bools should be constant.Values.
return v, nil
}
kind := t.Kind()
switch kind {
case reflect.Bool:
v = reflect.ValueOf(constant.BoolVal(c))
case reflect.String:
v = reflect.ValueOf(constant.StringVal(c))
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
i, _ := constant.Int64Val(constant.ToInt(c))
v = reflect.ValueOf(i).Convert(t)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
i, _ := constant.Uint64Val(constant.ToInt(c))
v = reflect.ValueOf(i).Convert(t)
case reflect.Float32:
f, _ := constant.Float32Val(constant.ToFloat(c))
v = reflect.ValueOf(f)
case reflect.Float64:
f, _ := constant.Float64Val(constant.ToFloat(c))
v = reflect.ValueOf(f)
case reflect.Complex64:
r, _ := constant.Float32Val(constant.Real(c))
i, _ := constant.Float32Val(constant.Imag(c))
v = reflect.ValueOf(complex(r, i)).Convert(t)
case reflect.Complex128:
r, _ := constant.Float64Val(constant.Real(c))
i, _ := constant.Float64Val(constant.Imag(c))
v = reflect.ValueOf(complex(r, i)).Convert(t)
default:
return v, errCantConvert
}
return v, nil
}
var bitlen = [...]int{
reflect.Int: 64,
reflect.Int8: 8,
reflect.Int16: 16,
reflect.Int32: 32,
reflect.Int64: 64,
reflect.Uint: 64,
reflect.Uint8: 8,
reflect.Uint16: 16,
reflect.Uint32: 32,
reflect.Uint64: 64,
reflect.Uintptr: 64,
}
func representableConst(c constant.Value, t reflect.Type) bool {
switch {
case isInt(t):
x := constant.ToInt(c)
if x.Kind() != constant.Int {
return false
}
switch t.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if _, ok := constant.Int64Val(x); !ok {
return false
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
if _, ok := constant.Uint64Val(x); !ok {
return false
}
default:
return false
}
return constant.BitLen(x) <= bitlen[t.Kind()]
case isFloat(t):
x := constant.ToFloat(c)
if x.Kind() != constant.Float {
return false
}
switch t.Kind() {
case reflect.Float32:
f, _ := constant.Float32Val(x)
return !math.IsInf(float64(f), 0)
case reflect.Float64:
f, _ := constant.Float64Val(x)
return !math.IsInf(f, 0)
default:
return false
}
case isComplex(t):
x := constant.ToComplex(c)
if x.Kind() != constant.Complex {
return false
}
switch t.Kind() {
case reflect.Complex64:
r, _ := constant.Float32Val(constant.Real(x))
i, _ := constant.Float32Val(constant.Imag(x))
return !math.IsInf(float64(r), 0) && !math.IsInf(float64(i), 0)
case reflect.Complex128:
r, _ := constant.Float64Val(constant.Real(x))
i, _ := constant.Float64Val(constant.Imag(x))
return !math.IsInf(r, 0) && !math.IsInf(i, 0)
default:
return false
}
case isString(t):
return c.Kind() == constant.String
case isBoolean(t):
return c.Kind() == constant.Bool
default:
return false
}
}
func isShiftAction(a action) bool {
switch a {
case aShl, aShr, aShlAssign, aShrAssign:
return true
}
return false
}
func isComparisonAction(a action) bool {
switch a {
case aEqual, aNotEqual, aGreater, aGreaterEqual, aLower, aLowerEqual:
return true
}
return false
}

View File

@@ -155,17 +155,36 @@ func genValueArray(n *node) func(*frame) reflect.Value {
func genValueRangeArray(n *node) func(*frame) reflect.Value {
value := genValue(n)
// dereference array pointer, to support array operations on array pointer
if n.typ.TypeOf().Kind() == reflect.Ptr {
switch {
case n.typ.TypeOf().Kind() == reflect.Ptr:
// dereference array pointer, to support array operations on array pointer
return func(f *frame) reflect.Value {
return value(f).Elem()
}
}
return func(f *frame) reflect.Value {
// This is necessary to prevent changes in the returned
// reflect.Value being reflected back to the value used
// for the range expression.
return reflect.ValueOf(value(f).Interface())
case n.typ.val != nil && n.typ.val.cat == interfaceT:
return func(f *frame) reflect.Value {
val := value(f)
v := []valueInterface{}
for i := 0; i < val.Len(); i++ {
switch av := val.Index(i).Interface().(type) {
case []valueInterface:
v = append(v, av...)
case valueInterface:
v = append(v, av)
default:
panic(n.cfgErrorf("invalid type %v", val.Index(i).Type()))
}
}
return reflect.ValueOf(v)
}
default:
return func(f *frame) reflect.Value {
// This is necessary to prevent changes in the returned
// reflect.Value being reflected back to the value used
// for the range expression.
return reflect.ValueOf(value(f).Interface())
}
}
}
@@ -182,16 +201,6 @@ func genValueInterfaceArray(n *node) func(*frame) reflect.Value {
}
}
func genValueInterfacePtr(n *node) func(*frame) reflect.Value {
value := genValue(n)
return func(f *frame) reflect.Value {
v := reflect.New(interf).Elem()
v.Set(value(f))
return v.Addr()
}
}
func genValueInterface(n *node) func(*frame) reflect.Value {
value := genValue(n)
@@ -211,18 +220,6 @@ func genValueInterface(n *node) func(*frame) reflect.Value {
}
}
func genValueDerefInterfacePtr(n *node) func(*frame) reflect.Value {
value := genValue(n)
return func(f *frame) reflect.Value {
v := value(f)
if v.IsZero() {
return v
}
return v.Elem().Elem()
}
}
func zeroInterfaceValue() reflect.Value {
n := &node{kind: basicLit, typ: &itype{cat: nilT, untyped: true}}
v := reflect.New(interf).Elem()
@@ -269,6 +266,63 @@ func genValueNode(n *node) func(*frame) reflect.Value {
}
}
func genValueRecursiveInterface(n *node, t reflect.Type) func(*frame) reflect.Value {
value := genValue(n)
return func(f *frame) reflect.Value {
vv := value(f)
v := reflect.New(t).Elem()
toRecursive(v, vv)
return v
}
}
func toRecursive(dest, src reflect.Value) {
if !src.IsValid() {
return
}
switch dest.Kind() {
case reflect.Map:
v := reflect.MakeMapWithSize(dest.Type(), src.Len())
for _, kv := range src.MapKeys() {
vv := reflect.New(dest.Type().Elem()).Elem()
toRecursive(vv, src.MapIndex(kv))
vv.SetMapIndex(kv, vv)
}
dest.Set(v)
case reflect.Slice:
l := src.Len()
v := reflect.MakeSlice(dest.Type(), l, l)
for i := 0; i < l; i++ {
toRecursive(v.Index(i), src.Index(i))
}
dest.Set(v)
case reflect.Ptr:
v := reflect.New(dest.Type().Elem()).Elem()
s := src
if s.Elem().Kind() != reflect.Struct { // In the case of *interface{}, we want *struct{}
s = s.Elem()
}
toRecursive(v, s)
dest.Set(v.Addr())
default:
dest.Set(src)
}
}
func genValueRecursiveInterfacePtrValue(n *node) func(*frame) reflect.Value {
value := genValue(n)
return func(f *frame) reflect.Value {
v := value(f)
if v.IsZero() {
return v
}
return v.Elem().Elem()
}
}
func vInt(v reflect.Value) (i int64) {
switch v.Type().Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
@@ -280,10 +334,6 @@ func vInt(v reflect.Value) (i int64) {
case reflect.Complex64, reflect.Complex128:
i = int64(real(v.Complex()))
}
if v.Type().Implements(constVal) {
c := v.Interface().(constant.Value)
i, _ = constant.Int64Val(constant.ToInt(c))
}
return
}
@@ -298,11 +348,6 @@ func vUint(v reflect.Value) (i uint64) {
case reflect.Complex64, reflect.Complex128:
i = uint64(real(v.Complex()))
}
if v.Type().Implements(constVal) {
c := v.Interface().(constant.Value)
iv, _ := constant.Int64Val(constant.ToInt(c))
i = uint64(iv)
}
return
}
@@ -317,13 +362,6 @@ func vComplex(v reflect.Value) (c complex128) {
case reflect.Complex64, reflect.Complex128:
c = v.Complex()
}
if v.Type().Implements(constVal) {
con := v.Interface().(constant.Value)
con = constant.ToComplex(con)
rel, _ := constant.Float64Val(constant.Real(con))
img, _ := constant.Float64Val(constant.Imag(con))
c = complex(rel, img)
}
return
}
@@ -338,10 +376,6 @@ func vFloat(v reflect.Value) (i float64) {
case reflect.Complex64, reflect.Complex128:
i = real(v.Complex())
}
if v.Type().Implements(constVal) {
c := v.Interface().(constant.Value)
i, _ = constant.Float64Val(constant.ToFloat(c))
}
return
}

View File

@@ -1,74 +0,0 @@
// Code generated by 'goexports debug/pe'. DO NOT EDIT.
// +build go1.13,!go1.14
package stdlib
import (
"debug/pe"
"go/constant"
"go/token"
"reflect"
)
func init() {
Symbols["debug/pe"] = map[string]reflect.Value{
// function, constant and variable definitions
"COFFSymbolSize": reflect.ValueOf(constant.MakeFromLiteral("18", token.INT, 0)),
"IMAGE_DIRECTORY_ENTRY_ARCHITECTURE": reflect.ValueOf(constant.MakeFromLiteral("7", token.INT, 0)),
"IMAGE_DIRECTORY_ENTRY_BASERELOC": reflect.ValueOf(constant.MakeFromLiteral("5", token.INT, 0)),
"IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT": reflect.ValueOf(constant.MakeFromLiteral("11", token.INT, 0)),
"IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR": reflect.ValueOf(constant.MakeFromLiteral("14", token.INT, 0)),
"IMAGE_DIRECTORY_ENTRY_DEBUG": reflect.ValueOf(constant.MakeFromLiteral("6", token.INT, 0)),
"IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT": reflect.ValueOf(constant.MakeFromLiteral("13", token.INT, 0)),
"IMAGE_DIRECTORY_ENTRY_EXCEPTION": reflect.ValueOf(constant.MakeFromLiteral("3", token.INT, 0)),
"IMAGE_DIRECTORY_ENTRY_EXPORT": reflect.ValueOf(constant.MakeFromLiteral("0", token.INT, 0)),
"IMAGE_DIRECTORY_ENTRY_GLOBALPTR": reflect.ValueOf(constant.MakeFromLiteral("8", token.INT, 0)),
"IMAGE_DIRECTORY_ENTRY_IAT": reflect.ValueOf(constant.MakeFromLiteral("12", token.INT, 0)),
"IMAGE_DIRECTORY_ENTRY_IMPORT": reflect.ValueOf(constant.MakeFromLiteral("1", token.INT, 0)),
"IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG": reflect.ValueOf(constant.MakeFromLiteral("10", token.INT, 0)),
"IMAGE_DIRECTORY_ENTRY_RESOURCE": reflect.ValueOf(constant.MakeFromLiteral("2", token.INT, 0)),
"IMAGE_DIRECTORY_ENTRY_SECURITY": reflect.ValueOf(constant.MakeFromLiteral("4", token.INT, 0)),
"IMAGE_DIRECTORY_ENTRY_TLS": reflect.ValueOf(constant.MakeFromLiteral("9", token.INT, 0)),
"IMAGE_FILE_MACHINE_AM33": reflect.ValueOf(constant.MakeFromLiteral("467", token.INT, 0)),
"IMAGE_FILE_MACHINE_AMD64": reflect.ValueOf(constant.MakeFromLiteral("34404", token.INT, 0)),
"IMAGE_FILE_MACHINE_ARM": reflect.ValueOf(constant.MakeFromLiteral("448", token.INT, 0)),
"IMAGE_FILE_MACHINE_ARM64": reflect.ValueOf(constant.MakeFromLiteral("43620", token.INT, 0)),
"IMAGE_FILE_MACHINE_ARMNT": reflect.ValueOf(constant.MakeFromLiteral("452", token.INT, 0)),
"IMAGE_FILE_MACHINE_EBC": reflect.ValueOf(constant.MakeFromLiteral("3772", token.INT, 0)),
"IMAGE_FILE_MACHINE_I386": reflect.ValueOf(constant.MakeFromLiteral("332", token.INT, 0)),
"IMAGE_FILE_MACHINE_IA64": reflect.ValueOf(constant.MakeFromLiteral("512", token.INT, 0)),
"IMAGE_FILE_MACHINE_M32R": reflect.ValueOf(constant.MakeFromLiteral("36929", token.INT, 0)),
"IMAGE_FILE_MACHINE_MIPS16": reflect.ValueOf(constant.MakeFromLiteral("614", token.INT, 0)),
"IMAGE_FILE_MACHINE_MIPSFPU": reflect.ValueOf(constant.MakeFromLiteral("870", token.INT, 0)),
"IMAGE_FILE_MACHINE_MIPSFPU16": reflect.ValueOf(constant.MakeFromLiteral("1126", token.INT, 0)),
"IMAGE_FILE_MACHINE_POWERPC": reflect.ValueOf(constant.MakeFromLiteral("496", token.INT, 0)),
"IMAGE_FILE_MACHINE_POWERPCFP": reflect.ValueOf(constant.MakeFromLiteral("497", token.INT, 0)),
"IMAGE_FILE_MACHINE_R4000": reflect.ValueOf(constant.MakeFromLiteral("358", token.INT, 0)),
"IMAGE_FILE_MACHINE_SH3": reflect.ValueOf(constant.MakeFromLiteral("418", token.INT, 0)),
"IMAGE_FILE_MACHINE_SH3DSP": reflect.ValueOf(constant.MakeFromLiteral("419", token.INT, 0)),
"IMAGE_FILE_MACHINE_SH4": reflect.ValueOf(constant.MakeFromLiteral("422", token.INT, 0)),
"IMAGE_FILE_MACHINE_SH5": reflect.ValueOf(constant.MakeFromLiteral("424", token.INT, 0)),
"IMAGE_FILE_MACHINE_THUMB": reflect.ValueOf(constant.MakeFromLiteral("450", token.INT, 0)),
"IMAGE_FILE_MACHINE_UNKNOWN": reflect.ValueOf(constant.MakeFromLiteral("0", token.INT, 0)),
"IMAGE_FILE_MACHINE_WCEMIPSV2": reflect.ValueOf(constant.MakeFromLiteral("361", token.INT, 0)),
"NewFile": reflect.ValueOf(pe.NewFile),
"Open": reflect.ValueOf(pe.Open),
// type definitions
"COFFSymbol": reflect.ValueOf((*pe.COFFSymbol)(nil)),
"DataDirectory": reflect.ValueOf((*pe.DataDirectory)(nil)),
"File": reflect.ValueOf((*pe.File)(nil)),
"FileHeader": reflect.ValueOf((*pe.FileHeader)(nil)),
"FormatError": reflect.ValueOf((*pe.FormatError)(nil)),
"ImportDirectory": reflect.ValueOf((*pe.ImportDirectory)(nil)),
"OptionalHeader32": reflect.ValueOf((*pe.OptionalHeader32)(nil)),
"OptionalHeader64": reflect.ValueOf((*pe.OptionalHeader64)(nil)),
"Reloc": reflect.ValueOf((*pe.Reloc)(nil)),
"Section": reflect.ValueOf((*pe.Section)(nil)),
"SectionHeader": reflect.ValueOf((*pe.SectionHeader)(nil)),
"SectionHeader32": reflect.ValueOf((*pe.SectionHeader32)(nil)),
"StringTable": reflect.ValueOf((*pe.StringTable)(nil)),
"Symbol": reflect.ValueOf((*pe.Symbol)(nil)),
}
}

View File

@@ -1,156 +0,0 @@
// Code generated by 'goexports os'. DO NOT EDIT.
// +build go1.13,!go1.14
package stdlib
import (
"go/constant"
"go/token"
"os"
"reflect"
"time"
)
func init() {
Symbols["os"] = map[string]reflect.Value{
// function, constant and variable definitions
"Args": reflect.ValueOf(&os.Args).Elem(),
"Chdir": reflect.ValueOf(os.Chdir),
"Chmod": reflect.ValueOf(os.Chmod),
"Chown": reflect.ValueOf(os.Chown),
"Chtimes": reflect.ValueOf(os.Chtimes),
"Clearenv": reflect.ValueOf(os.Clearenv),
"Create": reflect.ValueOf(os.Create),
"DevNull": reflect.ValueOf(os.DevNull),
"Environ": reflect.ValueOf(os.Environ),
"ErrClosed": reflect.ValueOf(&os.ErrClosed).Elem(),
"ErrExist": reflect.ValueOf(&os.ErrExist).Elem(),
"ErrInvalid": reflect.ValueOf(&os.ErrInvalid).Elem(),
"ErrNoDeadline": reflect.ValueOf(&os.ErrNoDeadline).Elem(),
"ErrNotExist": reflect.ValueOf(&os.ErrNotExist).Elem(),
"ErrPermission": reflect.ValueOf(&os.ErrPermission).Elem(),
"Executable": reflect.ValueOf(os.Executable),
"Exit": reflect.ValueOf(os.Exit),
"Expand": reflect.ValueOf(os.Expand),
"ExpandEnv": reflect.ValueOf(os.ExpandEnv),
"FindProcess": reflect.ValueOf(os.FindProcess),
"Getegid": reflect.ValueOf(os.Getegid),
"Getenv": reflect.ValueOf(os.Getenv),
"Geteuid": reflect.ValueOf(os.Geteuid),
"Getgid": reflect.ValueOf(os.Getgid),
"Getgroups": reflect.ValueOf(os.Getgroups),
"Getpagesize": reflect.ValueOf(os.Getpagesize),
"Getpid": reflect.ValueOf(os.Getpid),
"Getppid": reflect.ValueOf(os.Getppid),
"Getuid": reflect.ValueOf(os.Getuid),
"Getwd": reflect.ValueOf(os.Getwd),
"Hostname": reflect.ValueOf(os.Hostname),
"Interrupt": reflect.ValueOf(&os.Interrupt).Elem(),
"IsExist": reflect.ValueOf(os.IsExist),
"IsNotExist": reflect.ValueOf(os.IsNotExist),
"IsPathSeparator": reflect.ValueOf(os.IsPathSeparator),
"IsPermission": reflect.ValueOf(os.IsPermission),
"IsTimeout": reflect.ValueOf(os.IsTimeout),
"Kill": reflect.ValueOf(&os.Kill).Elem(),
"Lchown": reflect.ValueOf(os.Lchown),
"Link": reflect.ValueOf(os.Link),
"LookupEnv": reflect.ValueOf(os.LookupEnv),
"Lstat": reflect.ValueOf(os.Lstat),
"Mkdir": reflect.ValueOf(os.Mkdir),
"MkdirAll": reflect.ValueOf(os.MkdirAll),
"ModeAppend": reflect.ValueOf(os.ModeAppend),
"ModeCharDevice": reflect.ValueOf(os.ModeCharDevice),
"ModeDevice": reflect.ValueOf(os.ModeDevice),
"ModeDir": reflect.ValueOf(os.ModeDir),
"ModeExclusive": reflect.ValueOf(os.ModeExclusive),
"ModeIrregular": reflect.ValueOf(os.ModeIrregular),
"ModeNamedPipe": reflect.ValueOf(os.ModeNamedPipe),
"ModePerm": reflect.ValueOf(os.ModePerm),
"ModeSetgid": reflect.ValueOf(os.ModeSetgid),
"ModeSetuid": reflect.ValueOf(os.ModeSetuid),
"ModeSocket": reflect.ValueOf(os.ModeSocket),
"ModeSticky": reflect.ValueOf(os.ModeSticky),
"ModeSymlink": reflect.ValueOf(os.ModeSymlink),
"ModeTemporary": reflect.ValueOf(os.ModeTemporary),
"ModeType": reflect.ValueOf(os.ModeType),
"NewFile": reflect.ValueOf(os.NewFile),
"NewSyscallError": reflect.ValueOf(os.NewSyscallError),
"O_APPEND": reflect.ValueOf(os.O_APPEND),
"O_CREATE": reflect.ValueOf(os.O_CREATE),
"O_EXCL": reflect.ValueOf(os.O_EXCL),
"O_RDONLY": reflect.ValueOf(os.O_RDONLY),
"O_RDWR": reflect.ValueOf(os.O_RDWR),
"O_SYNC": reflect.ValueOf(os.O_SYNC),
"O_TRUNC": reflect.ValueOf(os.O_TRUNC),
"O_WRONLY": reflect.ValueOf(os.O_WRONLY),
"Open": reflect.ValueOf(os.Open),
"OpenFile": reflect.ValueOf(os.OpenFile),
"PathListSeparator": reflect.ValueOf(constant.MakeFromLiteral("58", token.INT, 0)),
"PathSeparator": reflect.ValueOf(constant.MakeFromLiteral("47", token.INT, 0)),
"Pipe": reflect.ValueOf(os.Pipe),
"Readlink": reflect.ValueOf(os.Readlink),
"Remove": reflect.ValueOf(os.Remove),
"RemoveAll": reflect.ValueOf(os.RemoveAll),
"Rename": reflect.ValueOf(os.Rename),
"SEEK_CUR": reflect.ValueOf(os.SEEK_CUR),
"SEEK_END": reflect.ValueOf(os.SEEK_END),
"SEEK_SET": reflect.ValueOf(os.SEEK_SET),
"SameFile": reflect.ValueOf(os.SameFile),
"Setenv": reflect.ValueOf(os.Setenv),
"StartProcess": reflect.ValueOf(os.StartProcess),
"Stat": reflect.ValueOf(os.Stat),
"Stderr": reflect.ValueOf(&os.Stderr).Elem(),
"Stdin": reflect.ValueOf(&os.Stdin).Elem(),
"Stdout": reflect.ValueOf(&os.Stdout).Elem(),
"Symlink": reflect.ValueOf(os.Symlink),
"TempDir": reflect.ValueOf(os.TempDir),
"Truncate": reflect.ValueOf(os.Truncate),
"Unsetenv": reflect.ValueOf(os.Unsetenv),
"UserCacheDir": reflect.ValueOf(os.UserCacheDir),
"UserConfigDir": reflect.ValueOf(os.UserConfigDir),
"UserHomeDir": reflect.ValueOf(os.UserHomeDir),
// type definitions
"File": reflect.ValueOf((*os.File)(nil)),
"FileInfo": reflect.ValueOf((*os.FileInfo)(nil)),
"FileMode": reflect.ValueOf((*os.FileMode)(nil)),
"LinkError": reflect.ValueOf((*os.LinkError)(nil)),
"PathError": reflect.ValueOf((*os.PathError)(nil)),
"ProcAttr": reflect.ValueOf((*os.ProcAttr)(nil)),
"Process": reflect.ValueOf((*os.Process)(nil)),
"ProcessState": reflect.ValueOf((*os.ProcessState)(nil)),
"Signal": reflect.ValueOf((*os.Signal)(nil)),
"SyscallError": reflect.ValueOf((*os.SyscallError)(nil)),
// interface wrapper definitions
"_FileInfo": reflect.ValueOf((*_os_FileInfo)(nil)),
"_Signal": reflect.ValueOf((*_os_Signal)(nil)),
}
}
// _os_FileInfo is an interface wrapper for FileInfo type
type _os_FileInfo struct {
WIsDir func() bool
WModTime func() time.Time
WMode func() os.FileMode
WName func() string
WSize func() int64
WSys func() interface{}
}
func (W _os_FileInfo) IsDir() bool { return W.WIsDir() }
func (W _os_FileInfo) ModTime() time.Time { return W.WModTime() }
func (W _os_FileInfo) Mode() os.FileMode { return W.WMode() }
func (W _os_FileInfo) Name() string { return W.WName() }
func (W _os_FileInfo) Size() int64 { return W.WSize() }
func (W _os_FileInfo) Sys() interface{} { return W.WSys() }
// _os_Signal is an interface wrapper for Signal type
type _os_Signal struct {
WSignal func()
WString func() string
}
func (W _os_Signal) Signal() { W.WSignal() }
func (W _os_Signal) String() string { return W.WString() }

View File

@@ -1,25 +0,0 @@
// Code generated by 'goexports os/exec'. DO NOT EDIT.
// +build go1.13,!go1.14
package stdlib
import (
"os/exec"
"reflect"
)
func init() {
Symbols["os/exec"] = map[string]reflect.Value{
// function, constant and variable definitions
"Command": reflect.ValueOf(exec.Command),
"CommandContext": reflect.ValueOf(exec.CommandContext),
"ErrNotFound": reflect.ValueOf(&exec.ErrNotFound).Elem(),
"LookPath": reflect.ValueOf(exec.LookPath),
// type definitions
"Cmd": reflect.ValueOf((*exec.Cmd)(nil)),
"Error": reflect.ValueOf((*exec.Error)(nil)),
"ExitError": reflect.ValueOf((*exec.ExitError)(nil)),
}
}

View File

@@ -1,4 +1,4 @@
// Code generated by 'goexports archive/tar'. DO NOT EDIT.
// Code generated by 'github.com/containous/yaegi/extract archive/tar'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'goexports archive/zip'. DO NOT EDIT.
// Code generated by 'github.com/containous/yaegi/extract archive/zip'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'goexports bufio'. DO NOT EDIT.
// Code generated by 'github.com/containous/yaegi/extract bufio'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'goexports bytes'. DO NOT EDIT.
// Code generated by 'github.com/containous/yaegi/extract bytes'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'goexports compress/bzip2'. DO NOT EDIT.
// Code generated by 'github.com/containous/yaegi/extract compress/bzip2'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'goexports compress/flate'. DO NOT EDIT.
// Code generated by 'github.com/containous/yaegi/extract compress/flate'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'goexports compress/gzip'. DO NOT EDIT.
// Code generated by 'github.com/containous/yaegi/extract compress/gzip'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'goexports compress/lzw'. DO NOT EDIT.
// Code generated by 'github.com/containous/yaegi/extract compress/lzw'. DO NOT EDIT.
// +build go1.14,!go1.15

View File

@@ -1,4 +1,4 @@
// Code generated by 'goexports compress/zlib'. DO NOT EDIT.
// Code generated by 'github.com/containous/yaegi/extract compress/zlib'. DO NOT EDIT.
// +build go1.14,!go1.15

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