Compare commits

..

41 Commits

Author SHA1 Message Date
Marc Vertes
0f46cd5efb fix: handle type declaration inside function 2019-09-26 00:50:04 +02:00
Marc Vertes
35e645c690 fix: correct handling of types alias of interfaces 2019-09-25 15:24:04 +02:00
Marc Vertes
03596dac45 fix: correct automatic type conversion for untyped constants 2019-09-25 15:02:04 +02:00
Dan Kortschak
8db7a815e3 interp: don't panic for undefined types 2019-09-25 12:24:05 +02:00
Dan Kortschak
424e7ac90d test: ensure hour-aligned timezone 2019-09-25 02:40:03 +02:00
Marc Vertes
effd64c980 fix: handle forward declaration of alias type 2019-09-24 16:10:04 +02:00
Marc Vertes
8a88a1ab8a fix: range over string iterates on runes instead of bytes 2019-09-23 17:02:04 +02:00
Marc Vertes
030dd3cbc2 fix: support variadic parameters on methods 2019-09-23 15:20:04 +02:00
Marc Vertes
bee21968c7 doc: README, supported go versions: 1.12 and 1.13 2019-09-19 15:00:05 +02:00
Marc Vertes
9abaeeb729 fix: binary method lookup works for struct field pointer receiver 2019-09-19 14:02:06 +02:00
Ludovic Fernandez
c8ae73ae8c chore: allow to build tags 2019-09-19 08:58:03 +02:00
Marc Vertes
5e49f83519 fix: shift operator inherits its type from first arg only 2019-09-18 23:44:03 +02:00
Marc Vertes
ec1ee5f5b6 fix: support array operations on array pointers 2019-09-18 23:32:04 +02:00
Marc Vertes
e03016b6d7 feature: detect import cycle 2019-09-17 01:32:03 +02:00
Marc Vertes
400b625153 fix: support defining recursive interface types 2019-09-17 01:18:03 +02:00
Marc Vertes
f7810d9761 fix: init global var from builtin 2019-09-16 16:50:06 +02:00
Ludovic Fernandez
7d4e5fb224 chore: build only master branch. 2019-09-16 15:38:04 +02:00
Marc Vertes
b08a51cb16 fix: conversion of type to reflect.Type avoids recursive loop 2019-09-16 15:04:03 +02:00
Marc Vertes
9e664ee8dd fix: global variable init from selector expression 2019-09-16 14:30:05 +02:00
Marc Vertes
cda23836f0 feature: additional binary packages in stdlib (#366)
Generate wrappers for the following packages:

- crypto/ed25519 (go1.13 only)
- debug/dwarf
- debug/elf
- debug/gosym
- debug/pe
- debug/plan9obj
- net/http/pprof
- runtime/pprof
- runtime/trace
2019-09-16 14:06:11 +02:00
Marc Vertes
058c121273 fix: add support for out of order declarations of empty global variables 2019-09-11 12:52:04 +02:00
Marc Vertes
3645904a15 fix: correct resolve of type and symbols from imported source package 2019-09-11 12:30:05 +02:00
Marc Vertes
82dd3f2953 fix: implement variadic using a type category to avoid corruption 2019-09-10 13:12:03 +02:00
Marc Vertes
2f0279f0f5 fix: reuse rather than re-import an already imported source package 2019-09-09 15:54:05 +02:00
Ludovic Fernandez
bfa9a267be Update stdlib for go1.13 (#351)
* chore: generate stdlib for go1.13

* chore: update CI configuration.

* fix: struct13 test is too dependent of the Go implementation.

* fix: disable test import4.go
2019-09-09 09:59:26 +02:00
Marc Vertes
978bbe0301 fix: avoid collision between package names and function args 2019-09-06 18:44:03 +02:00
Marc Vertes
63a537c8d9 fix: correct computation of type when dereference a binary object (#349) 2019-09-05 15:27:35 +02:00
Marc Vertes
b0937618b0 fix: improve handling of out of order declarations (#344) 2019-09-05 11:37:34 +02:00
Marc Vertes
d23a7e1d8b fix: assign a function value to a pre-declared variable 2019-08-29 05:16:04 -07:00
Marc Vertes
71fd938040 fix: improve handling of global declarations, possibly out of order (#336) 2019-08-28 16:59:46 +02:00
Marc Vertes
869b6d2850 fix: iterate on global type analyis when necessary (#335) 2019-08-22 17:36:23 +02:00
Marc Vertes
a4e15d7788 fix: do not skip AST errors when not in REPL mode (#329) 2019-08-20 18:43:01 +02:00
Marc Vertes
c88297459d doc: add a contributing guide (#333) 2019-08-19 19:14:58 +02:00
Marc Vertes
8aa22b9ebb feature: locate origin of CFG panic in input source (#330) 2019-08-19 17:40:03 +02:00
Sebastien Binet
75ad29c6b4 cmd/goexports: add -license flag to goexports 2019-08-05 03:10:05 -07:00
Ludovic Fernandez
a89a8b9ce4 chore: add goexports to the archive. 2019-08-01 01:46:04 -07:00
Ludovic Fernandez
458e8e911a interp/build: support custom build constraints. 2019-07-31 09:00:05 -07:00
Ludovic Fernandez
ee81ee7fea interp/build: improve Go version handling. 2019-07-30 14:34:04 -07:00
Marc Vertes
aaddc39981 fix: support 'range' with no index and value assigned 2019-07-30 11:00:06 -07:00
Ludovic Fernandez
5bf6618a53 goexports: allow to generate mapping for devel, beta, and rc. 2019-07-30 08:12:04 -07:00
Ludovic Fernandez
aad4c5a99b doc: add community badge. 2019-07-27 16:52:03 -07:00
623 changed files with 94970 additions and 1709 deletions

View File

@@ -0,0 +1,19 @@
---
name: Feature request
about: Propose a change to Yaegi!
---
<!-- ⚠️ If you do not respect this template your issue will be closed. -->
<!-- ⚠️ Make sure to browse the opened and closed issues before submit your issue. -->
#### Proposal
<!-- Write your feature request in the form of a proposal to be considered for implementation -->
#### Background
<!-- Describe the background problem or need that led to this feature request -->
#### Workarounds
<!-- Are there any current workarounds that you're using that others in similar positions should know about? -->

1
.gitignore vendored
View File

@@ -6,3 +6,4 @@
cmd/goexports/goexports
example/inception/inception
_test/tmp/
/dist

View File

@@ -32,6 +32,7 @@
"gocyclo",
"gochecknoinits",
"gochecknoglobals",
"typecheck", # v1.17.1 and Go1.13 => bug
]
[issues]
@@ -44,7 +45,6 @@
path = "cmd/goexports/goexports.go"
text = "SA1019: importer.For is deprecated: use ForCompiler, which populates a FileSet with the positions of objects created by the importer."
# structcheck false-positive
[[issues.exclude-rules]]
path = "interp/interp.go"
text = "`(astDot|cfgDot|noRun)` is unused"
path = "interp/.+_test\\.go"
linters = ["goconst"]

View File

@@ -1,7 +1,8 @@
project_name: yaegi
builds:
- binary: yaegi
- id: yaegi
binary: yaegi
main: ./cmd/yaegi/yaegi.go
goos:
@@ -25,6 +26,31 @@ builds:
- goos: darwin
goarch: 386
- id: goexports
binary: goexports
main: ./cmd/goexports/goexports.go
goos:
- darwin
- linux
# - windows
- freebsd
- openbsd
- solaris
goarch:
- amd64
- 386
- arm
- arm64
goarm:
- 7
- 6
- 5
ignore:
- goos: darwin
goarch: 386
changelog:
sort: asc
filters:
@@ -35,11 +61,12 @@ changelog:
- '^test:'
- '^tests:'
archive:
name_template: '{{ .ProjectName }}_v{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
format: tar.gz
format_overrides:
- goos: windows
format: zip
files:
- LICENSE
archives:
- id: archive
name_template: '{{ .ProjectName }}_v{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
format: tar.gz
format_overrides:
- goos: windows
format: zip
files:
- LICENSE

View File

@@ -2,23 +2,37 @@ language: go
dist: xenial
env:
- GO111MODULE=on
branches:
only:
- master
- /^v\d+\.\d+(\.\d+)?(-\S*)?$/
notifications:
email:
on_success: never
on_failure: change
go:
- 1.11.x
- 1.12.x
cache:
directories:
- $GOPATH/pkg/mod
matrix:
fast_finish: true
include:
- go: 1.11.x
- go: 1.12.x
- go: 1.13.x
env: STABLE=true
env:
global:
- GO111MODULE=on
go_import_path: github.com/containous/yaegi
before_install:
# Install linters and misspell
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b $GOPATH/bin v1.16.0
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b $GOPATH/bin ${GOLANGCI_LINT_VERSION}
- golangci-lint --version
install:
@@ -44,4 +58,4 @@ deploy:
script: curl -sL https://git.io/goreleaser | bash
on:
tags: true
condition: $TRAVIS_GO_VERSION =~ ^1\.12\.x$
condition: $STABLE = true

19
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,19 @@
# Contributing
Yaegi is an open source project, and your feedback and contributions
are needed and always welcome.
[Issues] and [Pull Requests] are opened at https://github.com/containous/yaegi.
Non trivial changes should be discussed with the project maintainers by
opening a [Feature Request] clearly explaining rationale, background
and possible implementation ideas. Feel free to provide code in such
discussions.
Once the proposal is approved, a Pull Request can be opened. If you want
to provide early visibility to reviewers, create a [Draft Pull Request].
[Issues]: https://github.com/containous/yaegi/issues
[Pull Requests]: https://github.com/containous/yaegi/issues
[Feature Request]: https://github.com/containous/yaegi/issues/new?template=feature_request.md
[Draft Pull Request]: https://github.blog/2019-02-14-introducing-draft-pull-requests/

View File

@@ -5,6 +5,7 @@
[![release](https://img.shields.io/github/tag-date/containous/yaegi.svg?label=alpha)](https://github.com/containous/yaegi/releases)
[![Build Status](https://travis-ci.com/containous/yaegi.svg?branch=master)](https://travis-ci.com/containous/yaegi)
[![GoDoc](https://godoc.org/github.com/containous/yaegi?status.svg)](https://godoc.org/github.com/containous/yaegi)
[![Discourse status](https://img.shields.io/discourse/https/community.containo.us/status?label=Community&style=social)](https://community.containo.us/c/yaegi)
Yaegi is Another Elegant Go Interpreter.
It powers executable Go scripts and plugins, in embedded interpreters or interactive shells, on top of the Go runtime.
@@ -17,7 +18,7 @@ It powers executable Go scripts and plugins, in embedded interpreters or interac
* Works everywhere Go works
* All Go & runtime resources accessible from script (with control)
* Security: `unsafe` and `syscall` packages neither used nor exported by default
* Support Go 1.11 and Go 1.12 (the latest 2 major releases)
* Support Go 1.12 and Go 1.13 (the latest 2 major releases)
## Install
@@ -144,9 +145,7 @@ Beside the known [bugs] which are supposed to be fixed in the short term, there
## Contributing
Yaegi is an open source project, and your feedback and contributions are needed and always welcome.
[Issues] and [pull requests] are opened at https://github.com/containous/yaegi
[Contributing guide](CONTRIBUTING.md).
## License
@@ -156,6 +155,4 @@ Yaegi is an open source project, and your feedback and contributions are needed
[docs]: https://godoc.org/github.com/containous/yaegi
[license]: https://github.com/containous/yaegi/blob/master/LICENSE
[github]: https://github.com/containous/yaegi
[Issues]: https://github.com/containous/yaegi/issues
[pull requests]: https://github.com/containous/yaegi/issues
[bugs]: https://github.com/containous/yaegi/issues?q=is%3Aissue+is%3Aopen+label%3Abug

10
_test/a30.go Normal file
View File

@@ -0,0 +1,10 @@
package main
func main() {
for range []struct{}{} {
}
println("ok")
}
// Output:
// ok

11
_test/a31.go Normal file
View File

@@ -0,0 +1,11 @@
package main
func main() {
for range []int{0, 1, 2} {
print("hello ")
}
println("")
}
// Output:
// hello hello hello

14
_test/a32.go Normal file
View File

@@ -0,0 +1,14 @@
package main
import "fmt"
type T struct{}
var a = []T{{}}
func main() {
fmt.Println(a)
}
// Output:
// [{}]

16
_test/alias1.go Normal file
View File

@@ -0,0 +1,16 @@
package main
import "fmt"
type MyT T
type T struct {
Name string
}
func main() {
fmt.Println(MyT{})
}
// Output:
// {}

15
_test/assign9.go Normal file
View File

@@ -0,0 +1,15 @@
package main
type foo func(b int)
func boo(b int) { println("boo", b) }
func main() {
var f foo
f = boo
f(4)
}
// Output:
// boo 4

4
_test/bad0.go Normal file
View File

@@ -0,0 +1,4 @@
println("Hello")
// Error:
// _test/bad0.go:1:1: expected 'package', found println

5
_test/c1/c1.go Normal file
View File

@@ -0,0 +1,5 @@
package c1
import "github.com/containous/yaegi/_test/c2"
var C1 = c2.C2 + "x"

5
_test/c2/c2.go Normal file
View File

@@ -0,0 +1,5 @@
package c2
import "github.com/containous/yaegi/_test/c1"
var C2 = c1.C1 + "Y"

14
_test/composite0.go Normal file
View File

@@ -0,0 +1,14 @@
package main
import "fmt"
var a = &[]*T{}
type T struct{ name string }
func main() {
fmt.Println(a)
}
// Output:
// &[]

14
_test/composite1.go Normal file
View File

@@ -0,0 +1,14 @@
package main
import "fmt"
var a = &[]*T{{}}
type T struct{ name string }
func main() {
fmt.Println((*a)[0])
}
// Output:
// &{}

14
_test/composite2.go Normal file
View File

@@ -0,0 +1,14 @@
package main
import "fmt"
var a = &[]*T{{"hello"}}
type T struct{ name string }
func main() {
fmt.Println((*a)[0])
}
// Output:
// &{hello}

14
_test/copy1.go Normal file
View File

@@ -0,0 +1,14 @@
package main
import "fmt"
func main() {
a := []int{10, 20, 30}
b := &[4]int{}
c := b[:]
copy(c, a)
fmt.Println(c)
}
// Output:
// [10 20 30 0]

View File

@@ -1,6 +1,8 @@
package foo
import "./boo"
import "github.com/containous/yaegi/_test/foo/boo"
var Bar = "BARR"
var Boo = boo.Boo
func init() { println("init foo") }

5
_test/foo/bir.go Normal file
View File

@@ -0,0 +1,5 @@
package foo
import "github.com/containous/yaegi/_test/foo/boo"
var Bir = boo.Boo + "22"

View File

@@ -1,3 +1,5 @@
package boo
var Boo = "Boo"
func init() { println("init boo") }

19
_test/for6.go Normal file
View File

@@ -0,0 +1,19 @@
package main
import "fmt"
func main() {
s := "三"
for i := 0; i < len(s); i++ {
fmt.Printf("byte %d: %d\n", i, s[i])
}
for i, r := range s {
fmt.Printf("rune %d: %d\n", i, r)
}
}
// Output:
// byte 0: 228
// byte 1: 184
// byte 2: 137
// rune 0: 19977

21
_test/fun6.go Normal file
View File

@@ -0,0 +1,21 @@
package main
import (
"fmt"
"sync"
)
func NewPool() Pool { return Pool{} }
type Pool struct {
p *sync.Pool
}
var _pool = NewPool()
func main() {
fmt.Println(_pool)
}
// Output:
// {<nil>}

18
_test/fun7.go Normal file
View File

@@ -0,0 +1,18 @@
package main
import (
goflag "flag"
"fmt"
)
func Foo(goflag *goflag.Flag) {
fmt.Println(goflag)
}
func main() {
g := &goflag.Flag{}
Foo(g)
}
// Output:
// &{ <nil> }

8
_test/fun8.go Normal file
View File

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

12
_test/fun9.go Normal file
View File

@@ -0,0 +1,12 @@
package main
type T uint
func main() {
type myint int
var i = myint(1)
println(i)
}
// Output:
// 1

View File

@@ -1,8 +1,10 @@
package main
import "./foo"
import "github.com/containous/yaegi/_test/foo"
func main() { println(foo.Bar, foo.Boo) }
// Output:
// init boo
// init foo
// BARR Boo

View File

@@ -1,6 +1,6 @@
package main
import "./p1"
import "github.com/containous/yaegi/_test/p1"
func main() { println("num:", p1.Uint32()) }

10
_test/import5.go Normal file
View File

@@ -0,0 +1,10 @@
package main
import boo "github.com/containous/yaegi/_test/foo"
func main() { println(boo.Bar, boo.Boo, boo.Bir) }
// Output:
// init boo
// init foo
// BARR Boo Boo22

11
_test/import6.go Normal file
View File

@@ -0,0 +1,11 @@
package main
import "github.com/containous/yaegi/_test/c1"
func main() {
println(c1.C1)
}
// Error:
// import cycle not allowed
// imports github.com/containous/yaegi/_test/c1

12
_test/interface10.go Normal file
View File

@@ -0,0 +1,12 @@
package main
type Edge interface {
ReverseEdge() Edge
}
func main() {
println("hello")
}
// Output:
// hello

27
_test/interface11.go Normal file
View File

@@ -0,0 +1,27 @@
package main
import "fmt"
type Error interface {
error
Code() string
}
type MyError Error
type T struct {
Name string
}
func (t *T) Error() string { return "err: " + t.Name }
func (t *T) Code() string { return "code: " + t.Name }
func newT(s string) MyError { return &T{s} }
func main() {
t := newT("foo")
fmt.Println(t.Code())
}
// Output:
// code: foo

14
_test/map18.go Normal file
View File

@@ -0,0 +1,14 @@
package main
import "fmt"
var foo = make([]int, 1)
func main() {
for _, v := range foo {
fmt.Println(v)
}
}
// Output:
// 0

View File

@@ -13,3 +13,6 @@ func (*Hello) Hi() string {
func main() {
fmt.Println(&Hello{})
}
// Output:
// &{}

19
_test/method22.go Normal file
View File

@@ -0,0 +1,19 @@
package main
func Bar() {
s := Obj.Foo()
println(s)
}
var Obj = &T{}
type T struct{}
func (t *T) Foo() bool { return t != nil }
func main() {
Bar()
}
// Output:
// true

21
_test/method23.go Normal file
View File

@@ -0,0 +1,21 @@
package main
func Bar() {
s := Obj.Foo()
println(s)
}
var Obj = NewT()
func NewT() *T { return &T{} }
type T struct{}
func (t *T) Foo() bool { return t != nil }
func main() {
Bar()
}
// Output:
// true

33
_test/method24.go Normal file
View File

@@ -0,0 +1,33 @@
package main
import (
"fmt"
"sync"
)
type Pool struct {
p *sync.Pool
}
func (p Pool) Get() *Buffer { return &Buffer{} }
func NewPool() Pool { return Pool{} }
type Buffer struct {
bs []byte
pool Pool
}
var (
_pool = NewPool()
Get = _pool.Get
)
func main() {
fmt.Println(_pool)
fmt.Println(Get())
}
// Output:
// {<nil>}
// &{[] {<nil>}}

33
_test/method25.go Normal file
View File

@@ -0,0 +1,33 @@
package main
import (
"fmt"
"sync"
)
func (p Pool) Get() *Buffer { return &Buffer{} }
func NewPool() Pool { return Pool{} }
type Buffer struct {
bs []byte
pool Pool
}
type Pool struct {
p *sync.Pool
}
var (
_pool = NewPool()
Get = _pool.Get
)
func main() {
fmt.Println(_pool)
fmt.Println(Get())
}
// Output:
// {<nil>}
// &{[] {<nil>}}

18
_test/method26.go Normal file
View File

@@ -0,0 +1,18 @@
package main
func NewT(name string) *T { return &T{name} }
var C = NewT("test")
func (t *T) f() { println(t == C) }
type T struct {
Name string
}
func main() {
C.f()
}
// Output:
// true

20
_test/method27.go Normal file
View File

@@ -0,0 +1,20 @@
package main
import (
"fmt"
"net/http"
)
type AuthenticatedRequest struct {
http.Request
Username string
}
func main() {
a := &AuthenticatedRequest{}
fmt.Println("ua:", a.UserAgent())
}
// Output:
// ua:

26
_test/ptr7.go Normal file
View File

@@ -0,0 +1,26 @@
package main
import (
"fmt"
"net"
"strings"
)
type ipNetValue net.IPNet
func (ipnet *ipNetValue) Set(value string) error {
_, n, err := net.ParseCIDR(strings.TrimSpace(value))
if err != nil {
return err
}
*ipnet = ipNetValue(*n)
return nil
}
func main() {
v := ipNetValue{}
fmt.Println(v)
}
// Output:
// {<nil> <nil>}

13
_test/ptr_array0.go Normal file
View File

@@ -0,0 +1,13 @@
package main
type T [2]int
func F0(t *T) int { return t[0] }
func main() {
t := &T{1, 2}
println(F0(t))
}
// Output:
// 1

19
_test/ptr_array1.go Normal file
View File

@@ -0,0 +1,19 @@
package main
type T [3]int
func F0(t *T) {
for i, v := range t {
println(i, v)
}
}
func main() {
t := &T{1, 2, 3}
F0(t)
}
// Output:
// 0 1
// 1 2
// 2 3

16
_test/ptr_array2.go Normal file
View File

@@ -0,0 +1,16 @@
package main
import "fmt"
type T [2]int
func F1(t *T) { t[0] = 1 }
func main() {
t := &T{}
F1(t)
fmt.Println(t)
}
// Output:
// &[1 0]

11
_test/ptr_array3.go Normal file
View File

@@ -0,0 +1,11 @@
package main
import "fmt"
func main() {
a := &[...]int{1, 2, 3}
fmt.Println(a[:])
}
// Output:
// [1 2 3]

39
_test/recurse0.go Normal file
View File

@@ -0,0 +1,39 @@
package main
import "fmt"
type T struct {
a []T
b []*T
c map[string]T
d map[string]*T
e chan T
f chan *T
h *T
i func(T) T
j func(*T) *T
U
}
type U struct {
k []T
l []*T
m map[string]T
n map[string]*T
o chan T
p chan *T
q *T
r func(T) T
s func(*T) *T
}
func main() {
t := T{}
u := U{}
fmt.Println(t)
fmt.Println(u)
}
// Output:
// {[] [] map[] map[] <nil> <nil> <nil> <nil> <nil> {[] [] map[] map[] <nil> <nil> <nil> <nil> <nil>}}
// {[] [] map[] map[] <nil> <nil> <nil> <nil> <nil>}

View File

@@ -9,6 +9,9 @@ func forever() {
func main() {
go forever()
time.Sleep(1e9)
time.Sleep(1e4)
println("bye")
}
// Output:
// bye

12
_test/shift1.go Normal file
View File

@@ -0,0 +1,12 @@
package main
import "fmt"
const a1 = 0x7f8 >> 3
func main() {
fmt.Printf("%T %v\n", a1, a1)
}
// Output:
// int 255

10
_test/shift2.go Normal file
View File

@@ -0,0 +1,10 @@
package main
func main() {
var u uint64
var v uint32
println(u << v)
}
// Output:
// 0

10
_test/shift3.go Normal file
View File

@@ -0,0 +1,10 @@
package main
const a = 1.0
const b = a + 3
func main() { println(b << (1)) }
// Output:
// 8

View File

@@ -13,3 +13,6 @@ type S2 struct {
func main() {
fmt.Println(S2{})
}
// Output:
// {<nil>}

View File

@@ -11,8 +11,8 @@ type Fromage struct {
func main() {
a := Fromage{}
fmt.Println(a.Server)
fmt.Println(a.Server.WriteTimeout)
}
// Output:
// { <nil> <nil> 0s 0s 0s 0s 0 map[] <nil> <nil> 0 0 {{0 0} 0} <nil> {0 0} map[] map[] <nil> []}
// 0s

View File

@@ -21,3 +21,6 @@ func (w GzipResponseWriterWithCloseNotify) CloseNotify() <-chan bool {
func main() {
fmt.Println("hello")
}
// Output:
// hello

View File

@@ -21,3 +21,6 @@ func main() {
c := CreateConfig()
fmt.Println(c)
}
// Output:
// &{[] false }

12
_test/struct24.go Normal file
View File

@@ -0,0 +1,12 @@
package main
var a = &T{}
type T struct{}
func main() {
println(a != nil)
}
// Output:
// true

14
_test/struct25.go Normal file
View File

@@ -0,0 +1,14 @@
package main
import "fmt"
var a = T{}
type T struct{}
func main() {
fmt.Println(a)
}
// Output:
// {}

20
_test/struct26.go Normal file
View File

@@ -0,0 +1,20 @@
package main
import "fmt"
func newT2() *T2 { return &T2{} }
type T2 struct {
T1
}
type T1 struct {
bs []byte
}
func main() {
fmt.Println(newT2())
}
// Output:
// &{{[]}}

18
_test/struct27.go Normal file
View File

@@ -0,0 +1,18 @@
package main
import "fmt"
func (f *Foo) Boo() { fmt.Println(f.name, "Boo") }
type Foo struct {
name string
fun func(f *Foo)
}
func main() {
t := &Foo{name: "foo"}
t.Boo()
}
// Output:
// foo Boo

19
_test/struct28.go Normal file
View File

@@ -0,0 +1,19 @@
package main
import "fmt"
type T1 struct {
T2
}
type T2 struct {
*T1
}
func main() {
t := T1{}
fmt.Println(t)
}
// Output:
// {{<nil>}}

View File

@@ -6,7 +6,7 @@ import (
)
func main() {
t := time.Unix(1e9, 0)
t := time.Unix(1e9, 0).In(time.UTC)
fmt.Println(t.Minute())
}

17
_test/time8.go Normal file
View File

@@ -0,0 +1,17 @@
package main
import (
"time"
)
type durationValue time.Duration
func (d *durationValue) String() string { return (*time.Duration)(d).String() }
func main() {
var d durationValue
println(d.String())
}
// Output:
// 0s

View File

@@ -25,3 +25,6 @@ func main() {
t := &T1{}
println(t.Get())
}
// Output:
// no name

12
_test/type13.go Normal file
View File

@@ -0,0 +1,12 @@
package main
var a = &T{}
type T struct{}
func main() {
println(a != nil)
}
// Output:
// true

14
_test/type14.go Normal file
View File

@@ -0,0 +1,14 @@
package main
import "fmt"
var a = T{}
type T struct{}
func main() {
fmt.Println(a)
}
// Output:
// {}

18
_test/var7.go Normal file
View File

@@ -0,0 +1,18 @@
package main
import (
"fmt"
)
type T struct {
Name string
}
var m = make(map[string]*T)
func main() {
fmt.Println(m)
}
// Output:
// map[]

19
_test/var8.go Normal file
View File

@@ -0,0 +1,19 @@
package main
import (
"fmt"
"reflect"
)
type Message struct {
Name string
}
var protoMessageType = reflect.TypeOf((*Message)(nil)).Elem()
func main() {
fmt.Println(protoMessageType.Kind())
}
// Output:
// struct

10
_test/variadic4.go Normal file
View File

@@ -0,0 +1,10 @@
package main
func variadic(s ...string) {}
func f(s string) { println(s + "bar") }
func main() { f("foo") }
// Output:
// foobar

22
_test/variadic5.go Normal file
View File

@@ -0,0 +1,22 @@
package main
import (
"fmt"
)
type A struct {
}
func (a A) f(vals ...bool) {
for _, v := range vals {
fmt.Println(v)
}
}
func main() {
a := A{}
a.f(true)
}
// Output:
// true

View File

@@ -19,7 +19,9 @@ The GOOS and GOARCH environment variables set the desired target.
package main
import (
"bufio"
"bytes"
"flag"
"fmt"
"go/constant"
"go/format"
@@ -35,12 +37,14 @@ import (
"text/template"
)
const model = `// +build {{.BuildTags}}
const model = `// Code generated by 'goexports {{.PkgName}}'. DO NOT EDIT.
{{.License}}
{{if .BuildTags}}// +build {{.BuildTags}}{{end}}
package {{.Dest}}
// Code generated by 'goexports {{.PkgName}}'. DO NOT EDIT.
import (
{{- range $key, $value := .Imports }}
{{- if $value}}
@@ -53,6 +57,7 @@ import (
func init() {
Symbols["{{.PkgName}}"] = map[string]reflect.Value{
{{- if .Val}}
// function, constant and variable definitions
{{range $key, $value := .Val -}}
{{- if $value.Addr -}}
@@ -62,15 +67,20 @@ func init() {
{{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 -}}
@@ -103,7 +113,7 @@ type Wrap struct {
Method []Method
}
func genContent(dest, pkgName string) ([]byte, error) {
func genContent(dest, pkgName, license string) ([]byte, error) {
p, err := importer.For("source", nil).Import(pkgName)
if err != nil {
return nil, err
@@ -184,14 +194,23 @@ func genContent(dest, pkgName string) ([]byte, error) {
}
}
parts := strings.Split(runtime.Version(), ".")
currentGoVersion := parts[0] + "." + parts[1]
var buildTags string
if runtime.Version() != "devel" {
parts := strings.Split(runtime.Version(), ".")
minor, err := strconv.Atoi(parts[1])
if err != nil {
return nil, fmt.Errorf("failed to parse version: %v", err)
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
}
nextGoVersion := parts[0] + "." + strconv.Itoa(minor+1)
base := template.New("goexports")
parse, err := base.Parse(model)
@@ -199,12 +218,11 @@ func genContent(dest, pkgName string) ([]byte, error) {
return nil, fmt.Errorf("template parsing error: %v", err)
}
buildTags := currentGoVersion + ",!" + nextGoVersion
if pkgName == "log/syslog" {
buildTags += ",!windows,!nacl,!plan9"
}
b := &bytes.Buffer{}
b := new(bytes.Buffer)
data := map[string]interface{}{
"Dest": dest,
"Imports": imports,
@@ -213,6 +231,7 @@ func genContent(dest, pkgName string) ([]byte, error) {
"Typ": typ,
"Wrap": wrap,
"BuildTags": buildTags,
"License": license,
}
err = parse.Execute(b, data)
if err != nil {
@@ -250,17 +269,61 @@ func fixConst(name string, val constant.Value) string {
return name
}
// 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
}
func main() {
licenseFlag := flag.String("license", "", "path to a LICENSE file")
flag.Parse()
if flag.NArg() == 0 {
flag.Usage()
log.Fatalf("missing package path")
}
license, err := genLicense(*licenseFlag)
if err != nil {
log.Fatal(err)
}
dir, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
dest := path.Base(dir)
for _, pkg := range os.Args[1:] {
content, err := genContent(dest, pkg)
for _, pkg := range flag.Args() {
content, err := genContent(dest, pkg, license)
if err != nil {
log.Fatal(err)
log.Println(err)
continue
}
var oFile string
@@ -271,11 +334,29 @@ func main() {
oFile = strings.Replace(pkg, "/", "_", -1) + ".go"
}
parts := strings.Split(runtime.Version(), ".")
prefix := runtime.Version()
if runtime.Version() != "devel" {
parts := strings.Split(runtime.Version(), ".")
err = ioutil.WriteFile(parts[0]+"_"+parts[1]+"_"+oFile, content, 0666)
prefix = parts[0] + "_" + getMinor(parts[1])
}
err = ioutil.WriteFile(prefix+"_"+oFile, content, 0666)
if 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
}

View File

@@ -109,7 +109,7 @@ func TestPackagesError(t *testing.T) {
{
desc: "different packages in the same directory",
goPath: "./_pkg9/",
expected: "found packages pkg and pkgfalse in _pkg9/src/github.com/foo/pkg",
expected: "1:21: import \"github.com/foo/pkg\" error: found packages pkg and pkgfalse in _pkg9/src/github.com/foo/pkg",
},
}

View File

@@ -318,23 +318,26 @@ func (interp *Interpreter) firstToken(src string) token.Token {
// ast parses src string containing Go code and generates the corresponding AST.
// The package name and the AST root node are returned.
func (interp *Interpreter) ast(src, name string) (string, *node, error) {
inRepl := name == ""
var inFunc bool
// Allow incremental parsing of declarations or statements, by inserting
// them in a pseudo file package or function. Those statements or
// declarations will be always evaluated in the global scope
switch interp.firstToken(src) {
case token.PACKAGE:
// nothing to do
case token.CONST, token.FUNC, token.IMPORT, token.TYPE, token.VAR:
src = "package main;" + src
default:
inFunc = true
src = "package main; func main() {" + src + "}"
if inRepl {
switch interp.firstToken(src) {
case token.PACKAGE:
// nothing to do
case token.CONST, token.FUNC, token.IMPORT, token.TYPE, token.VAR:
src = "package main;" + src
default:
inFunc = true
src = "package main; func main() {" + src + "}"
}
}
if !interp.buildOk(name, src) {
return "", nil, nil // skip source not matching build constraints
if ok, err := interp.buildOk(interp.context, name, src); !ok || err != nil {
return "", nil, err // skip source not matching build constraints
}
f, err := parser.ParseFile(interp.fset, name, src, 0)
@@ -696,7 +699,13 @@ func (interp *Interpreter) ast(src, name string) (string, *node, error) {
case *ast.RangeStmt:
// Insert a missing ForRangeStmt for AST correctness
n := addChild(&root, anc, pos, forRangeStmt, aNop)
st.push(addChild(&root, astNode{n, nod}, pos, rangeStmt, aRange), nod)
r := addChild(&root, astNode{n, nod}, pos, rangeStmt, aRange)
st.push(r, nod)
if a.Key == nil {
// range not in an assign expression: insert a "_" key variable to store iteration index
k := addChild(&root, astNode{r, nod}, pos, identExpr, aNop)
k.ident = "_"
}
case *ast.ReturnStmt:
st.push(addChild(&root, anc, pos, returnStmt, aReturn), nod)

View File

@@ -1,42 +1,43 @@
package interp
import (
"go/build"
"go/parser"
"path"
"runtime"
"strconv"
"strings"
)
// buildOk returns true if a file or script matches build constraints
// as specified in https://golang.org/pkg/go/build/#hdr-Build_Constraints
func (interp *Interpreter) buildOk(name, src string) bool {
// as specified in https://golang.org/pkg/go/build/#hdr-Build_Constraints.
// An error from parser is returned as well.
func (interp *Interpreter) buildOk(ctx build.Context, name, src string) (bool, error) {
// Extract comments before the first clause
f, err := parser.ParseFile(interp.fset, name, src, parser.PackageClauseOnly|parser.ParseComments)
if err != nil {
return false
return false, err
}
for _, g := range f.Comments {
// in file, evaluate the AND of multiple line build constraints
for _, line := range strings.Split(strings.TrimSpace(g.Text()), "\n") {
if !buildLineOk(line) {
return false
if !buildLineOk(ctx, line) {
return false, nil
}
}
}
return true
return true, nil
}
// buildLineOk returns true if line is not a build constraint or
// if build constraint is satisfied
func buildLineOk(line string) (ok bool) {
func buildLineOk(ctx build.Context, line string) (ok bool) {
if len(line) < 7 || line[:7] != "+build " {
return true
}
// In line, evaluate the OR of space-separated options
options := strings.Split(strings.TrimSpace(line[6:]), " ")
for _, o := range options {
if ok = buildOptionOk(o); ok {
if ok = buildOptionOk(ctx, o); ok {
break
}
}
@@ -44,39 +45,35 @@ func buildLineOk(line string) (ok bool) {
}
// buildOptionOk return true if all comma separated tags match, false otherwise
func buildOptionOk(tag string) bool {
func buildOptionOk(ctx build.Context, tag string) bool {
// in option, evaluate the AND of individual tags
for _, t := range strings.Split(tag, ",") {
if !buildTagOk(t) {
if !buildTagOk(ctx, t) {
return false
}
}
return true
}
var (
goos = runtime.GOOS
goarch = runtime.GOARCH
goversion = goNumVersion()
)
// buildTagOk returns true if a build tag matches, false otherwise
// if first character is !, result is negated
func buildTagOk(s string) (r bool) {
func buildTagOk(ctx build.Context, s string) (r bool) {
not := s[0] == '!'
if not {
s = s[1:]
}
switch {
case s == goos:
case contains(ctx.BuildTags, s):
r = true
case s == goarch:
case s == ctx.GOOS:
r = true
case s == ctx.GOARCH:
r = true
case len(s) > 4 && s[:4] == "go1.":
if n, err := strconv.Atoi(s[4:]); err != nil {
r = false
} else {
r = goversion >= n
r = goMinorVersion(ctx) >= n
}
}
if not {
@@ -85,15 +82,33 @@ func buildTagOk(s string) (r bool) {
return
}
// goNumVersion returns the go minor version number
func goNumVersion() int {
v := strings.Split(runtime.Version(), ".")
n, _ := strconv.Atoi(v[1])
return n
func contains(tags []string, tag string) bool {
for _, t := range tags {
if t == tag {
return true
}
}
return false
}
// goMinorVersion returns the go minor version number
func goMinorVersion(ctx build.Context) int {
current := ctx.ReleaseTags[len(ctx.ReleaseTags)-1]
v := strings.Split(current, ".")
if len(v) < 2 {
panic("unsupported Go version: " + current)
}
m, err := strconv.Atoi(v[1])
if err != nil {
panic("unsupported Go version: " + current)
}
return m
}
// skipFile returns true if file should be skipped
func skipFile(p string) bool {
func skipFile(ctx build.Context, p string) bool {
if !strings.HasSuffix(p, ".go") {
return true
}
@@ -107,10 +122,10 @@ func skipFile(p string) bool {
}
a := strings.Split(p[i+1:], "_")
last := len(a) - 1
if last1 := last - 1; last1 >= 0 && a[last1] == goos && a[last] == goarch {
if last1 := last - 1; last1 >= 0 && a[last1] == ctx.GOOS && a[last] == ctx.GOARCH {
return false
}
if s := a[last]; s != goos && s != goarch && knownOs[s] || knownArch[s] {
if s := a[last]; s != ctx.GOOS && s != ctx.GOARCH && knownOs[s] || knownArch[s] {
return true
}
return false

View File

@@ -1,6 +1,7 @@
package interp
import (
"go/build"
"testing"
)
@@ -11,18 +12,21 @@ type testBuild struct {
func TestBuildTag(t *testing.T) {
// Assume a specific OS, arch and go version no matter the real underlying system
oo, oa, ov := goos, goarch, goversion
goos, goarch, goversion = "linux", "amd64", 11
defer func() { goos, goarch, goversion = oo, oa, ov }()
ctx := build.Context{
GOARCH: "amd64",
GOOS: "linux",
BuildTags: []string{"foo"},
ReleaseTags: []string{"go1.11"},
}
tests := []testBuild{
{"// +build linux", true},
{"// +build windows", false},
{"// +build go1.9", true},
{"// +build go1.11", true},
{"// +build !go1.12", true},
{"// +build go1.12", false},
{"// +build !go1.10", false},
{"// +build go1.9", true},
{"// +build !go1.12", true},
{"// +build ignore", false},
{"// +build linux,amd64", true},
{"// +build linux,i386", false},
@@ -30,14 +34,17 @@ func TestBuildTag(t *testing.T) {
{"// +build linux\n// +build amd64", true},
{"// +build linux\n\n\n// +build amd64", true},
{"// +build linux\n// +build i386", false},
{"// +build foo", true},
{"// +build !foo", false},
{"// +build bar", false},
}
i := New(Options{})
for _, test := range tests {
test := test
src := test.src + "\npackage x"
t.Run("", func(t *testing.T) {
if r := i.buildOk("", src); r != test.res {
t.Run(test.src, func(t *testing.T) {
if r, _ := i.buildOk(ctx, "", src); r != test.res {
t.Errorf("got %v, want %v", r, test.res)
}
})
@@ -46,9 +53,10 @@ func TestBuildTag(t *testing.T) {
func TestBuildFile(t *testing.T) {
// Assume a specific OS, arch and go pattern no matter the real underlying system
oo, oa := goos, goarch
goos, goarch = "linux", "amd64"
defer func() { goos, goarch = oo, oa }()
ctx := build.Context{
GOARCH: "amd64",
GOOS: "linux",
}
tests := []testBuild{
{"foo/bar_linux_amd64.go", false},
@@ -66,9 +74,43 @@ func TestBuildFile(t *testing.T) {
for _, test := range tests {
test := test
t.Run(test.src, func(t *testing.T) {
if r := skipFile(test.src); r != test.res {
if r := skipFile(ctx, test.src); r != test.res {
t.Errorf("got %v, want %v", r, test.res)
}
})
}
}
func Test_goMinorVersion(t *testing.T) {
tests := []struct {
desc string
context build.Context
expected int
}{
{
desc: "stable",
context: build.Context{ReleaseTags: []string{
"go1.1", "go1.2", "go1.3", "go1.4", "go1.5", "go1.6", "go1.7", "go1.8", "go1.9", "go1.10", "go1.11", "go1.12",
}},
expected: 12,
},
{
desc: "devel/beta/rc",
context: build.Context{ReleaseTags: []string{
"go1.1", "go1.2", "go1.3", "go1.4", "go1.5", "go1.6", "go1.7", "go1.8", "go1.9", "go1.10", "go1.11", "go1.12", "go1.13",
}},
expected: 13,
},
}
for _, test := range tests {
test := test
t.Run(test.desc, func(t *testing.T) {
minor := goMinorVersion(test.context)
if minor != test.expected {
t.Errorf("got %v, want %v", minor, test.expected)
}
})
}
}

View File

@@ -3,6 +3,7 @@ package interp
import (
"fmt"
"log"
"math"
"path"
"reflect"
"unicode"
@@ -77,7 +78,7 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
vtyp = &itype{cat: valueT, rtype: typ.Elem()}
case reflect.String:
ktyp = sc.getType("int")
vtyp = sc.getType("byte")
vtyp = sc.getType("rune")
case reflect.Array, reflect.Slice:
ktyp = sc.getType("int")
vtyp = &itype{cat: valueT, rtype: typ.Elem()}
@@ -86,10 +87,18 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
n.anc.gen = rangeMap
ktyp = o.typ.key
vtyp = o.typ.val
case ptrT:
ktyp = sc.getType("int")
vtyp = o.typ.val
if vtyp.cat == valueT {
vtyp = &itype{cat: valueT, rtype: vtyp.rtype.Elem()}
} else {
vtyp = vtyp.val
}
case stringT:
ktyp = sc.getType("int")
vtyp = sc.getType("byte")
case arrayT:
vtyp = sc.getType("rune")
case arrayT, variadicT:
ktyp = sc.getType("int")
vtyp = o.typ.val
}
@@ -183,7 +192,7 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
}
case compositeLitExpr:
if n.child[0].isType(sc) {
if len(n.child) > 0 && n.child[0].isType(sc) {
// Get type from 1st child
if n.typ, err = nodeType(interp, sc, n.child[0]); err != nil {
return false
@@ -195,7 +204,10 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
} else if n.anc.typ != nil {
n.typ = n.anc.typ.val
}
// FIXME n.typ can be nil.
if n.typ == nil {
err = n.cfgErrorf("undefined type")
return false
}
n.typ.untyped = true
}
// Propagate type to children, to handle implicit types
@@ -221,6 +233,9 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
case funcDecl:
n.val = n
// Compute function type before entering local scope to avoid
// possible collisions with function argument names.
n.child[2].typ, err = nodeType(interp, sc, n.child[2])
// Add a frame indirection level as we enter in a func
sc = sc.pushFunc()
sc.def = n
@@ -257,9 +272,6 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
if typ, err = nodeType(interp, sc, c.lastChild()); err != nil {
return false
}
if typ.variadic {
typ = &itype{cat: arrayT, val: typ}
}
for _, cc := range c.child[:len(c.child)-1] {
sc.sym[cc.ident] = &symbol{index: sc.add(typ), kind: varSym, typ: typ}
}
@@ -290,14 +302,32 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
name = path.Base(ipath)
}
if interp.binPkg[ipath] != nil && name != "." {
sc.sym[name] = &symbol{kind: pkgSym, typ: &itype{cat: binPkgT}, path: ipath}
sc.sym[name] = &symbol{kind: pkgSym, typ: &itype{cat: binPkgT, path: ipath}}
} else {
sc.sym[name] = &symbol{kind: pkgSym, typ: &itype{cat: srcPkgT}, path: ipath}
sc.sym[name] = &symbol{kind: pkgSym, typ: &itype{cat: srcPkgT, path: ipath}}
}
return false
case typeSpec:
// processing already done in GTA pass
// processing already done in GTA pass for global types, only parses inlined types
if sc.def != nil {
typeName := n.child[0].ident
var typ *itype
if typ, err = nodeType(interp, sc, n.child[1]); err != nil {
return false
}
if typ.incomplete {
err = n.cfgErrorf("invalid type declaration")
return false
}
if n.child[1].kind == identExpr {
n.typ = &itype{cat: aliasT, val: typ, name: typeName}
} else {
n.typ = typ
n.typ.name = typeName
}
sc.sym[typeName] = &symbol{kind: typeSym, typ: n.typ}
}
return false
case arrayType, basicLit, chanType, funcType, mapType, structType:
@@ -310,6 +340,14 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
if err != nil {
return
}
defer func() {
if r := recover(); r != nil {
// Display the exact location in input source which triggered the panic
panic(n.cfgErrorf("CFG post-order panic: %v", r))
}
}()
switch n.kind {
case addressExpr:
wireChild(n)
@@ -344,13 +382,12 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
var sym *symbol
var level int
if n.kind == defineStmt || (n.kind == assignStmt && dest.ident == "_") {
if src.typ != nil && src.typ.cat == nilT {
err = src.cfgErrorf("use of untyped nil")
break
}
if atyp != nil {
dest.typ = atyp
} else {
if src.typ, err = nodeType(interp, sc, src); err != nil {
return
}
dest.typ = src.typ
}
if dest.typ.sizedef {
@@ -385,7 +422,7 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
err = n.cfgErrorf("illegal operand types for '%v' operator", n.action)
}
case aShlAssign, aShrAssign:
if !(isInt(t0) && isUint(t1)) {
if !(dest.isInteger() && src.isNatural()) {
err = n.cfgErrorf("illegal operand types for '%v' operator", n.action)
}
default:
@@ -483,7 +520,9 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
nilSym := interp.universe.sym["nil"]
c0, c1 := n.child[0], n.child[1]
t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf()
if !c0.typ.untyped && !c1.typ.untyped && c0.typ.id() != c1.typ.id() {
// Shift operator type is inherited from first parameter only
// All other binary operators require both parameter types to be the same
if !isShiftNode(n) && !c0.typ.untyped && !c1.typ.untyped && c0.typ.id() != c1.typ.id() {
err = n.cfgErrorf("mismatched types %s and %s", c0.typ.id(), c1.typ.id())
break
}
@@ -501,7 +540,7 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
err = n.cfgErrorf("illegal operand types for '%v' operator", n.action)
}
case aShl, aShr:
if !(isInt(t0) && isUint(t1)) {
if !(c0.isInteger() && c1.isNatural()) {
err = n.cfgErrorf("illegal operand types for '%v' operator", n.action)
}
n.typ = c0.typ
@@ -562,20 +601,34 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
wireChild(n)
t := n.child[0].typ
switch t.cat {
case valueT:
n.typ = &itype{cat: valueT, rtype: t.rtype.Elem()}
case ptrT:
n.typ = t.val
if t.val.cat == valueT {
n.typ = &itype{cat: valueT, rtype: t.val.rtype.Elem()}
} else {
n.typ = t.val.val
}
case stringT:
n.typ = sc.getType("byte")
case valueT:
n.typ = &itype{cat: valueT, rtype: t.rtype.Elem()}
default:
n.typ = t.val
}
n.findex = sc.add(n.typ)
n.recv = &receiver{node: n}
switch k := t.TypeOf().Kind(); k {
typ := t.TypeOf()
switch k := typ.Kind(); k {
case reflect.Map:
n.gen = getIndexMap
case reflect.Array, reflect.Slice, reflect.String:
n.gen = getIndexArray
case reflect.Ptr:
if typ2 := typ.Elem(); typ2.Kind() == reflect.Array {
n.gen = getIndexArray
} else {
err = n.cfgErrorf("type %v does not support indexing", typ)
}
default:
err = n.cfgErrorf("type is not an array, slice, string or map: %v", t.id())
}
@@ -633,73 +686,17 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
case callExpr:
wireChild(n)
switch {
case isBuiltinCall(n):
case interp.isBuiltinCall(n):
n.gen = n.child[0].sym.builtin
n.child[0].typ = &itype{cat: builtinT}
switch n.child[0].ident {
case "append":
c1, c2 := n.child[1], n.child[2]
if n.typ = sc.getType(c1.ident); n.typ == nil {
if n.typ, err = nodeType(interp, sc, c1); err != nil {
return
}
}
if len(n.child) == 3 {
if c2.typ.cat == arrayT && c2.typ.val.id() == n.typ.val.id() ||
isByteArray(c1.typ.TypeOf()) && isString(c2.typ.TypeOf()) {
n.gen = appendSlice
}
}
case "cap", "copy", "len":
n.typ = sc.getType("int")
case "complex":
c0, c1 := n.child[1], n.child[2]
switch t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf(); {
case isFloat32(t0) && isFloat32(t1):
n.typ = sc.getType("complex64")
case isFloat64(t0) && isFloat64(t1):
n.typ = sc.getType("complex128")
case c0.typ.untyped && isNumber(t0) && c1.typ.untyped && isNumber(t1):
n.typ = &itype{cat: valueT, rtype: complexType}
case c0.typ.untyped && isFloat32(t1) || c1.typ.untyped && isFloat32(t0):
n.typ = sc.getType("complex64")
case c0.typ.untyped && isFloat64(t1) || c1.typ.untyped && isFloat64(t0):
n.typ = sc.getType("complex128")
default:
err = n.cfgErrorf("invalid types %s and %s", t0.Kind(), t1.Kind())
}
case "real", "imag":
switch k := n.child[1].typ.TypeOf().Kind(); {
case k == reflect.Complex64:
n.typ = sc.getType("float32")
case k == reflect.Complex128:
n.typ = sc.getType("float64")
case n.child[1].typ.untyped && isNumber(n.child[1].typ.TypeOf()):
n.typ = &itype{cat: valueT, rtype: floatType}
default:
err = n.cfgErrorf("invalid complex type %s", k)
}
case "make":
if n.typ = sc.getType(n.child[1].ident); n.typ == nil {
if n.typ, err = nodeType(interp, sc, n.child[1]); err != nil {
return
}
}
n.child[1].val = n.typ
n.child[1].kind = basicLit
case "new":
if n.typ, err = nodeType(interp, sc, n.child[1]); err != nil {
return
}
n.typ = &itype{cat: ptrT, val: n.typ}
case "recover":
n.typ = sc.getType("interface{}")
if n.typ, err = nodeType(interp, sc, n); err != nil {
return
}
if n.typ != nil {
n.findex = sc.add(n.typ)
} else {
if n.typ.cat == builtinT {
n.findex = -1
n.val = nil
} else {
n.findex = sc.add(n.typ)
}
case n.child[0].isType(sc):
// Type conversion expression
@@ -1066,11 +1063,7 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
}
} else if n.typ.cat == ptrT && (n.typ.val.cat == valueT || n.typ.val.cat == errorT) {
// Handle pointer on object defined in runtime
if field, ok := n.typ.val.rtype.FieldByName(n.child[1].ident); ok {
n.typ = &itype{cat: valueT, rtype: field.Type}
n.val = field.Index
n.gen = getPtrIndexSeq
} else if method, ok := n.typ.val.rtype.MethodByName(n.child[1].ident); ok {
if method, ok := n.typ.val.rtype.MethodByName(n.child[1].ident); ok {
n.val = method.Index
n.typ = &itype{cat: valueT, rtype: method.Type}
n.recv = &receiver{node: n.child[0]}
@@ -1080,13 +1073,18 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
n.gen = getIndexBinMethod
n.typ = &itype{cat: valueT, rtype: method.Type}
n.recv = &receiver{node: n.child[0]}
} else if field, ok := n.typ.val.rtype.FieldByName(n.child[1].ident); ok {
n.typ = &itype{cat: valueT, rtype: field.Type}
n.val = field.Index
n.gen = getPtrIndexSeq
} else {
err = n.cfgErrorf("undefined selector: %s", n.child[1].ident)
}
} else if n.typ.cat == binPkgT {
// Resolve binary package symbol: a type or a value
name := n.child[1].ident
pkg := n.child[0].sym.path
pkg := n.child[0].sym.typ.path
if s, ok := interp.binPkg[pkg][name]; ok {
if isBinType(s) {
n.kind = rtypeExpr
@@ -1101,16 +1099,16 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
err = n.cfgErrorf("package %s \"%s\" has no symbol %s", n.child[0].ident, pkg, name)
}
} else if n.typ.cat == srcPkgT {
pkg, name := n.child[0].ident, n.child[1].ident
pkg, name := n.child[0].sym.typ.path, n.child[1].ident
// Resolve source package symbol
if sym, ok := interp.scopes[pkg].sym[name]; ok {
if sym, ok := interp.srcPkg[pkg][name]; ok {
n.findex = sym.index
n.val = sym.node
n.gen = nop
n.typ = sym.typ
n.sym = sym
} else {
err = n.cfgErrorf("undefined selector: %s", n.child[1].ident)
err = n.cfgErrorf("undefined selector: %s.%s", pkg, name)
}
} else if m, lind := n.typ.lookupMethod(n.child[1].ident); m != nil {
if n.child[0].isType(sc) {
@@ -1128,19 +1126,23 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
n.typ = m.typ
n.recv = &receiver{node: n.child[0], index: lind}
}
} else if m, lind, ok := n.typ.lookupBinMethod(n.child[1].ident); ok {
n.gen = getIndexSeqMethod
} else if m, lind, isPtr, ok := n.typ.lookupBinMethod(n.child[1].ident); ok {
if isPtr {
n.gen = getIndexSeqPtrMethod
} else {
n.gen = getIndexSeqMethod
}
n.val = append([]int{m.Index}, lind...)
n.typ = &itype{cat: valueT, rtype: m.Type}
} else if ti := n.typ.lookupField(n.child[1].ident); len(ti) > 0 {
// Handle struct field
n.val = ti
switch n.typ.cat {
case interfaceT:
switch {
case isInterfaceSrc(n.typ):
n.typ = n.typ.fieldSeq(ti)
n.gen = getMethodByName
n.action = aMethod
case ptrT:
case n.typ.cat == ptrT:
n.typ = n.typ.fieldSeq(ti)
n.gen = getPtrIndexSeq
if n.typ.cat == funcT {
@@ -1194,7 +1196,11 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
default:
// dereference expression
wireChild(n)
n.typ = n.child[0].typ.val
if c0 := n.child[0]; c0.typ.cat == valueT {
n.typ = &itype{cat: valueT, rtype: c0.typ.rtype.Elem()}
} else {
n.typ = c0.typ.val
}
n.findex = sc.add(n.typ)
}
@@ -1297,7 +1303,11 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
case sliceExpr:
wireChild(n)
if ctyp := n.child[0].typ; ctyp.size != 0 {
ctyp := n.child[0].typ
if ctyp.cat == ptrT {
ctyp = ctyp.val
}
if ctyp.size != 0 {
// Create a slice type from an array type
n.typ = &itype{}
*n.typ = *ctyp
@@ -1459,10 +1469,11 @@ func (n *node) isType(sc *scope) bool {
case selectorExpr:
pkg, name := n.child[0].ident, n.child[1].ident
if sym, _, ok := sc.lookup(pkg); ok {
if p, ok := n.interp.binPkg[sym.path]; ok && isBinType(p[name]) {
path := sym.typ.path
if p, ok := n.interp.binPkg[path]; ok && isBinType(p[name]) {
return true // Imported binary type
}
if p, ok := n.interp.scopes[pkg]; ok && p.sym[name] != nil && p.sym[name].kind == typeSym {
if p, ok := n.interp.srcPkg[path]; ok && p[name] != nil && p[name].kind == typeSym {
return true // Imported source type
}
}
@@ -1514,7 +1525,57 @@ func wireChild(n *node) {
}
}
// last returns the last child of a node
// isInteger returns true if node type is integer, false otherwise
func (n *node) isInteger() bool {
if isInt(n.typ.TypeOf()) {
return true
}
if n.typ.untyped && 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.Round(f) {
n.rval = reflect.ValueOf(int(f))
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()) {
return true
}
if n.typ.untyped && n.rval.IsValid() {
t := n.rval.Type()
if isUint(t) {
return true
}
if isInt(t) && n.rval.Int() >= 0 {
// positive untyped integer constant is ok
return true
}
if isFloat(t) {
// positive untyped float constant with null decimal part is ok
f := n.rval.Float()
if f == math.Round(f) && f >= 0 {
n.rval = reflect.ValueOf(uint(f))
n.typ.rtype = n.rval.Type()
return true
}
}
}
return false
}
// lastChild returns the last child of a node
func (n *node) lastChild() *node { return n.child[len(n.child)-1] }
func isKey(n *node) bool {
@@ -1552,10 +1613,6 @@ func isMapEntry(n *node) bool {
return n.action == aGetIndex && n.child[0].typ.cat == mapT
}
func isBuiltinCall(n *node) bool {
return n.kind == callExpr && n.child[0].sym != nil && n.child[0].sym.kind == bltnSym
}
func isBinCall(n *node) bool {
return n.kind == callExpr && n.child[0].typ.cat == valueT && n.child[0].typ.rtype.Kind() == reflect.Func
}
@@ -1569,7 +1626,7 @@ func variadicPos(n *node) int {
return -1
}
last := len(n.child[0].typ.arg) - 1
if n.child[0].typ.arg[last].variadic {
if n.child[0].typ.arg[last].cat == variadicT {
return last
}
return -1
@@ -1592,6 +1649,13 @@ func getExec(n *node) bltn {
return n.exec
}
func fileNode(n *node) *node {
if n == nil || n.kind == fileStmt {
return n
}
return fileNode(n.anc)
}
// setExec recursively sets the node exec builtin function by walking the CFG
// from the entry point (first node to exec).
func setExec(n *node) {
@@ -1644,7 +1708,8 @@ func gotoLabel(s *symbol) {
func compositeGenerator(n *node) (gen bltnGenerator) {
switch n.typ.cat {
case aliasT:
case aliasT, ptrT:
n.typ.val.untyped = n.typ.untyped
n.typ = n.typ.val
gen = compositeGenerator(n)
case arrayT:
@@ -1652,7 +1717,7 @@ func compositeGenerator(n *node) (gen bltnGenerator) {
case mapT:
gen = mapLit
case structT:
if n.lastChild().kind == keyValueExpr {
if len(n.child) > 0 && n.lastChild().kind == keyValueExpr {
gen = compositeSparse
} else {
gen = compositeLit

View File

@@ -9,10 +9,11 @@ import (
// variables and functions symbols at package level, prior to CFG.
// All function bodies are skipped. GTA is necessary to handle out of
// order declarations and multiple source files packages.
func (interp *Interpreter) gta(root *node, rpath string) error {
func (interp *Interpreter) gta(root *node, rpath string) ([]*node, error) {
sc, _ := interp.initScopePkg(root)
var err error
var iotaValue int
var revisit []*node
root.Walk(func(n *node) bool {
if err != nil {
@@ -42,23 +43,24 @@ func (interp *Interpreter) gta(root *node, rpath string) error {
for i := 0; i < n.nleft; i++ {
dest, src := n.child[i], n.child[sbase+i]
typ := atyp
val := reflect.ValueOf(iotaValue)
typ := atyp
if typ == nil {
if typ, err = nodeType(interp, sc, src); err != nil {
return false
}
val = src.rval
}
var index int
if !typ.incomplete {
if typ.cat == nilT {
err = n.cfgErrorf("use of untyped nil")
return false
}
index = sc.add(typ)
if typ.incomplete {
// Come back when type is known
revisit = append(revisit, n)
return false
}
sc.sym[dest.ident] = &symbol{kind: varSym, global: true, index: index, typ: typ, rval: val}
if typ.cat == nilT {
err = n.cfgErrorf("use of untyped nil")
return false
}
sc.sym[dest.ident] = &symbol{kind: varSym, global: true, index: sc.add(typ), typ: typ, rval: val}
if n.anc.kind == constDecl {
sc.sym[dest.ident].kind = constSym
iotaValue++
@@ -70,48 +72,50 @@ func (interp *Interpreter) gta(root *node, rpath string) error {
err = compDefineX(sc, n)
case valueSpec:
// TODO: handle global ValueSpec
//err = n.cfgError("global ValueSpec not implemented")
l := len(n.child) - 1
if n.typ = n.child[l].typ; n.typ == nil {
if n.typ, err = nodeType(interp, sc, n.child[l]); err != nil {
return false
}
}
for _, c := range n.child[:l] {
sc.sym[c.ident] = &symbol{index: sc.add(n.typ), kind: varSym, global: true, typ: n.typ}
}
case funcDecl:
if n.typ, err = nodeType(interp, sc, n.child[2]); err != nil {
return false
}
if !isMethod(n) {
sc.sym[n.child[1].ident] = &symbol{kind: funcSym, typ: n.typ, node: n, index: -1}
}
if len(n.child[0].child) > 0 {
// function is a method, add it to the related type
if isMethod(n) {
// Add a method symbol in the receiver type name space
var rcvrtype *itype
var typeName string
n.ident = n.child[1].ident
rcvr := n.child[0].child[0]
if len(rcvr.child) < 2 {
// Receiver var name is skipped in method declaration (fix that in AST ?)
typeName = rcvr.child[0].ident
} else {
typeName = rcvr.child[1].ident
}
rtn := rcvr.lastChild()
typeName := rtn.ident
if typeName == "" {
// The receiver is a pointer, retrieve typeName from indirection
typeName = rcvr.lastChild().child[0].ident
typeName = rtn.child[0].ident
elementType := sc.getType(typeName)
if elementType == nil {
// Add type if necessary, so method can be registered
sc.sym[typeName] = &symbol{kind: typeSym, typ: &itype{name: typeName, pkgPath: rpath}}
sc.sym[typeName] = &symbol{kind: typeSym, typ: &itype{name: typeName, path: rpath, incomplete: true, node: rtn.child[0], scope: sc}}
elementType = sc.sym[typeName].typ
}
rcvrtype = &itype{cat: ptrT, val: elementType}
rcvrtype = &itype{cat: ptrT, val: elementType, incomplete: elementType.incomplete, node: rtn, scope: sc}
elementType.method = append(elementType.method, n)
} else {
rcvrtype = sc.getType(typeName)
if rcvrtype == nil {
// Add type if necessary, so method can be registered
sc.sym[typeName] = &symbol{kind: typeSym, typ: &itype{name: typeName, pkgPath: rpath}}
sc.sym[typeName] = &symbol{kind: typeSym, typ: &itype{name: typeName, path: rpath, incomplete: true, node: rtn, scope: sc}}
rcvrtype = sc.sym[typeName].typ
}
}
rcvrtype.method = append(rcvrtype.method, n)
} else {
// Add a function symbol in the package name space
sc.sym[n.child[1].ident] = &symbol{kind: funcSym, typ: n.typ, node: n, index: -1}
}
return false
@@ -124,8 +128,11 @@ func (interp *Interpreter) gta(root *node, rpath string) error {
ipath = n.child[0].rval.String()
name = path.Base(ipath)
}
// Try to import a binary package first, or a source package
if interp.binPkg[ipath] != nil {
if name == "." {
switch name {
case "_": // no import of symbols
case ".": // import symbols in current scope
for n, v := range interp.binPkg[ipath] {
typ := v.Type()
if isBinType(v) {
@@ -133,14 +140,24 @@ func (interp *Interpreter) gta(root *node, rpath string) error {
}
sc.sym[n] = &symbol{kind: binSym, typ: &itype{cat: valueT, rtype: typ}, rval: v}
}
} else {
sc.sym[name] = &symbol{kind: pkgSym, typ: &itype{cat: binPkgT}, path: ipath}
default: // import symbols in package namespace
sc.sym[name] = &symbol{kind: pkgSym, typ: &itype{cat: binPkgT, path: ipath}}
}
} else if err = interp.importSrc(rpath, ipath, name); err == nil {
sc.types = interp.universe.types
switch name {
case "_": // no import of symbols
case ".": // import symbols in current namespace
for k, v := range interp.srcPkg[ipath] {
if canExport(k) {
sc.sym[k] = v
}
}
default: // import symbols in package namespace
sc.sym[name] = &symbol{kind: pkgSym, typ: &itype{cat: srcPkgT, path: ipath}}
}
} else {
// TODO: make sure we do not import a src package more than once
err = interp.importSrcFile(rpath, ipath, name)
sc.types = interp.universe.types
sc.sym[name] = &symbol{kind: pkgSym, typ: &itype{cat: srcPkgT}, path: ipath}
err = n.cfgErrorf("import %q error: %v", ipath, err)
}
case typeSpec:
@@ -150,19 +167,23 @@ func (interp *Interpreter) gta(root *node, rpath string) error {
return false
}
if n.child[1].kind == identExpr {
n.typ = &itype{cat: aliasT, val: typ, name: typeName, pkgPath: rpath}
n.typ = &itype{cat: aliasT, val: typ, name: typeName, path: rpath, field: typ.field, incomplete: typ.incomplete}
copy(n.typ.method, typ.method)
} else {
n.typ = typ
n.typ.name = typeName
n.typ.pkgPath = rpath
n.typ.path = rpath
}
// Type may already be declared for a receiver in a method function
// Type may be already declared for a receiver in a method function
if sc.sym[typeName] == nil {
sc.sym[typeName] = &symbol{kind: typeSym}
} else {
n.typ.method = append(n.typ.method, sc.sym[typeName].typ.method...)
}
sc.sym[typeName].typ = n.typ
if n.typ.incomplete {
revisit = append(revisit, n)
}
return false
}
return true
@@ -171,5 +192,5 @@ func (interp *Interpreter) gta(root *node, rpath string) error {
if sc != interp.universe {
sc.pop()
}
return err
return revisit, err
}

View File

@@ -3,6 +3,7 @@ package interp
import (
"bufio"
"fmt"
"go/build"
"go/scanner"
"go/token"
"os"
@@ -53,15 +54,18 @@ type frame struct {
recovered interface{} // to handle panic recover
}
// Exports stores the map of external values per package
// Exports stores the map of binary packages per package path
type Exports map[string]map[string]reflect.Value
// imports stores the map of source packages per package path
type imports map[string]map[string]*symbol
// opt stores interpreter options
type opt struct {
astDot bool // display AST graph (debug)
cfgDot bool // display CFG graph (debug)
noRun bool // compile, but do not run
goPath string // custom GOPATH
astDot bool // display AST graph (debug)
cfgDot bool // display CFG graph (debug)
noRun bool // compile, but do not run
context build.Context // build context: GOPATH, build constraints
}
// Interpreter contains global resources and state
@@ -73,7 +77,9 @@ type Interpreter struct {
fset *token.FileSet // fileset to locate node in source code
universe *scope // interpreter global level scope
scopes map[string]*scope // package level scopes, indexed by package name
binPkg Exports // runtime binary values used in interpreter
binPkg Exports // binary packages used in interpreter, indexed by path
srcPkg imports // source packages used in interpreter, indexed by path
rdir map[string]bool // for src import cycle detection
}
const (
@@ -118,17 +124,26 @@ func (n *node) Walk(in func(n *node) bool, out func(n *node)) {
type Options struct {
// GoPath sets GOPATH for the interpreter
GoPath string
// BuildTags sets build constraints for the interpreter
BuildTags []string
}
// New returns a new interpreter
func New(options Options) *Interpreter {
i := Interpreter{
opt: opt{goPath: options.GoPath},
opt: opt{context: build.Default},
frame: &frame{data: []reflect.Value{}},
fset: token.NewFileSet(),
universe: initUniverse(),
scopes: map[string]*scope{},
binPkg: Exports{"": map[string]reflect.Value{"_error": reflect.ValueOf((*_error)(nil))}},
frame: &frame{data: []reflect.Value{}},
srcPkg: imports{},
rdir: map[string]bool{},
}
i.opt.context.GOPATH = options.GoPath
if len(options.BuildTags) > 0 {
i.opt.context.BuildTags = options.BuildTags
}
// AstDot activates AST graph display for the interpreter
@@ -237,9 +252,15 @@ func (interp *Interpreter) Eval(src string) (reflect.Value, error) {
}
// Global type analysis
if err = interp.gta(root, pkgName); err != nil {
revisit, err := interp.gta(root, pkgName)
if err != nil {
return res, err
}
for _, n := range revisit {
if _, err = interp.gta(n, pkgName); err != nil {
return res, err
}
}
// Annotate AST with CFG infos
initNodes, err := interp.cfg(root)
@@ -258,7 +279,8 @@ func (interp *Interpreter) Eval(src string) (reflect.Value, error) {
}
if interp.universe.sym[pkgName] == nil {
// Make the package visible under a path identical to its name
interp.universe.sym[pkgName] = &symbol{typ: &itype{cat: srcPkgT}, path: pkgName}
interp.srcPkg[pkgName] = interp.scopes[pkgName].sym
interp.universe.sym[pkgName] = &symbol{kind: pkgSym, typ: &itype{cat: srcPkgT, path: pkgName}}
}
if interp.cfgDot {

View File

@@ -1,6 +1,7 @@
package interp_test
import (
"go/build"
"io/ioutil"
"os"
"os/exec"
@@ -31,8 +32,10 @@ func TestInterpConsistencyBuild(t *testing.T) {
for _, file := range files {
if filepath.Ext(file.Name()) != ".go" ||
file.Name() == "bad0.go" || // expect error
file.Name() == "export1.go" || // non-main package
file.Name() == "export0.go" || // non-main package
file.Name() == "import6.go" || // expect error
file.Name() == "io0.go" || // use random number
file.Name() == "op1.go" || // expect error
file.Name() == "bltn0.go" || // expect error
@@ -77,7 +80,7 @@ func TestInterpConsistencyBuild(t *testing.T) {
r, w, _ := os.Pipe()
os.Stdout = w
i := interp.New(interp.Options{})
i := interp.New(interp.Options{GoPath: build.Default.GOPATH})
i.Name = filePath
i.Use(stdlib.Symbols)
i.Use(interp.Symbols)
@@ -128,6 +131,11 @@ func TestInterpErrorConsistency(t *testing.T) {
expectedInterp string
expectedExec string
}{
{
fileName: "bad0.go",
expectedInterp: "1:1: expected 'package', found println",
expectedExec: "1:1: expected 'package', found println",
},
{
fileName: "op1.go",
expectedInterp: "5:2: illegal operand types for '+=' operator",
@@ -137,6 +145,11 @@ func TestInterpErrorConsistency(t *testing.T) {
fileName: "bltn0.go",
expectedInterp: "4:7: use of builtin println not in function call",
},
{
fileName: "import6.go",
expectedInterp: "import cycle not allowed",
expectedExec: "import cycle not allowed",
},
{
fileName: "switch8.go",
expectedInterp: "5:2: fallthrough statement out of place",
@@ -172,7 +185,7 @@ func TestInterpErrorConsistency(t *testing.T) {
t.Fatal(err)
}
i := interp.New(interp.Options{})
i := interp.New(interp.Options{GoPath: build.Default.GOPATH})
i.Name = filePath
i.Use(stdlib.Symbols)

View File

@@ -44,11 +44,13 @@ func TestEvalArithmetic(t *testing.T) {
{desc: "rem_FI", src: "8.0 % 4", err: "1:28: illegal operand types for '%' operator"},
{desc: "shl_II", src: "1 << 8", res: "256"},
{desc: "shl_IN", src: "1 << -1", err: "1:28: illegal operand types for '<<' operator"},
{desc: "shl_IF", src: "1 << 1.0", err: "1:28: illegal operand types for '<<' operator"},
{desc: "shl_IF", src: "1.0 << 1", err: "1:28: illegal operand types for '<<' operator"},
{desc: "shl_IF", src: "1 << 1.0", res: "2"},
{desc: "shl_IF1", src: "1 << 1.1", err: "1:28: illegal operand types for '<<' operator"},
{desc: "shl_IF", src: "1.0 << 1", res: "2"},
{desc: "shr_II", src: "1 >> 8", res: "0"},
{desc: "shr_IN", src: "1 >> -1", err: "1:28: illegal operand types for '>>' operator"},
{desc: "shr_IF", src: "1 >> 1.0", err: "1:28: illegal operand types for '>>' operator"},
{desc: "shr_IF", src: "1 >> 1.0", res: "0"},
{desc: "shr_IF1", src: "1 >> 1.1", err: "1:28: illegal operand types for '>>' operator"},
})
}
@@ -112,7 +114,7 @@ func TestEvalNil(t *testing.T) {
i := interp.New(interp.Options{})
i.Use(stdlib.Symbols)
runTests(t, i, []testCase{
{desc: "assign nil", src: "a := nil", err: "1:28: use of untyped nil"},
{desc: "assign nil", src: "a := nil", err: "1:33: use of untyped nil"},
{desc: "return nil", pre: func() { eval(t, i, "func getNil() error {return nil}") }, src: "getNil()", res: "<nil>"},
{
desc: "return func which return error",
@@ -368,6 +370,32 @@ func TestEvalChan(t *testing.T) {
})
}
func TestEvalMissingSymbol(t *testing.T) {
defer func() {
r := recover()
if r != nil {
t.Errorf("unexpected panic: %v", r)
}
}()
type S2 struct{}
type S1 struct {
F S2
}
i := interp.New(interp.Options{})
i.Use(interp.Exports{"p": map[string]reflect.Value{
"S1": reflect.Zero(reflect.TypeOf(&S1{})),
}})
_, err := i.Eval(`import "p"`)
if err != nil {
t.Fatalf("failed to import package: %v", err)
}
_, err = i.Eval(`p.S1{F: p.S2{}}`)
if err == nil {
t.Error("unexpected nil error for expression with undefined type")
}
}
func runTests(t *testing.T, i *interp.Interpreter, tests []testCase) {
for _, test := range tests {
t.Run(test.desc, func(t *testing.T) {

View File

@@ -1,6 +1,7 @@
package interp_test
import (
"go/build"
"go/parser"
"go/token"
"io/ioutil"
@@ -51,7 +52,7 @@ func runCheck(t *testing.T, p string) {
r, w, _ := os.Pipe()
os.Stdout = w
i := interp.New(interp.Options{})
i := interp.New(interp.Options{GoPath: build.Default.GOPATH})
i.Name = p
i.Use(interp.Symbols)
i.Use(stdlib.Symbols)

View File

@@ -198,12 +198,12 @@ func convert(n *node) {
}
}
func isRecursiveStruct(t *itype) bool {
if t.cat == structT && t.rtype.Kind() == reflect.Interface {
func isRecursiveStruct(t *itype, rtype reflect.Type) bool {
if t.cat == structT && rtype.Kind() == reflect.Interface {
return true
}
if t.cat == ptrT {
return isRecursiveStruct(t.val)
return isRecursiveStruct(t.val, t.rtype.Elem())
}
return false
}
@@ -230,7 +230,7 @@ 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):
case isRecursiveStruct(dest.typ, dest.typ.rtype):
svalue[i] = genValueInterfacePtr(src)
default:
svalue[i] = genValue(src)
@@ -502,7 +502,7 @@ func genInterfaceWrapper(n *node, typ reflect.Type) func(*frame) reflect.Value {
methods[i], indexes[i] = n.typ.lookupMethod(names[i])
if methods[i] == nil && n.typ.cat != nilT {
// interpreted method not found, look for binary method, possibly embedded
_, indexes[i], _ = n.typ.lookupBinMethod(names[i])
_, indexes[i], _, _ = n.typ.lookupBinMethod(names[i])
}
}
wrap := n.interp.getWrapper(typ)
@@ -614,7 +614,7 @@ func call(n *node) {
if c.kind == basicLit {
var argType reflect.Type
if variadic >= 0 && i >= variadic {
argType = n.child[0].typ.arg[variadic].TypeOf()
argType = n.child[0].typ.arg[variadic].val.TypeOf()
} else {
argType = n.child[0].typ.arg[i].TypeOf()
}
@@ -671,7 +671,11 @@ func call(n *node) {
// Init variadic argument vector
if variadic >= 0 {
vararg = nf.data[numRet+variadic]
if method {
vararg = nf.data[numRet+variadic+1]
} else {
vararg = nf.data[numRet+variadic]
}
}
// Copy input parameters from caller
@@ -887,7 +891,7 @@ func getIndexBinPtrMethod(n *node) {
// getIndexArray returns array value from index
func getIndexArray(n *node) {
tnext := getExec(n.tnext)
value0 := genValue(n.child[0]) // array
value0 := genValueArray(n.child[0]) // array
if n.child[1].rval.IsValid() { // constant array index
ai := int(vInt(n.child[1].rval))
@@ -1087,7 +1091,7 @@ func getPtrIndexSeq(n *node) {
index := n.val.([]int)
tnext := getExec(n.tnext)
var value func(*frame) reflect.Value
if isRecursiveStruct(n.child[0].typ) {
if isRecursiveStruct(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 {
@@ -1130,6 +1134,27 @@ func getIndexSeqField(n *node) {
}
}
func getIndexSeqPtrMethod(n *node) {
value := genValue(n.child[0])
index := n.val.([]int)
fi := index[1:]
mi := index[0]
i := n.findex
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)
return next
}
} else {
n.exec = func(f *frame) bltn {
f.data[i] = value(f).FieldByIndex(fi).Addr().Method(mi)
return next
}
}
}
func getIndexSeqMethod(n *node) {
value := genValue(n.child[0])
index := n.val.([]int)
@@ -1253,6 +1278,12 @@ func _return(n *node) {
switch t := def.typ.ret[i]; t.cat {
case errorT:
values[i] = genInterfaceWrapper(c, t.TypeOf())
case aliasT:
if isInterfaceSrc(t) {
values[i] = genValueInterface(c)
} else {
values[i] = genValue(c)
}
case interfaceT:
values[i] = genValueInterface(c)
default:
@@ -1453,7 +1484,11 @@ func compositeLit(n *node) {
for i, v := range values {
a.Field(i).Set(v(f))
}
value(f).Set(a)
if d := value(f); d.Type().Kind() == reflect.Ptr {
d.Set(a.Addr())
} else {
d.Set(a)
}
return next
}
}
@@ -1484,21 +1519,33 @@ func compositeSparse(n *node) {
for i, v := range values {
a.Field(i).Set(v(f))
}
value(f).Set(a)
if d := value(f); d.Type().Kind() == reflect.Ptr {
d.Set(a.Addr())
} else {
d.Set(a)
}
return next
}
}
func empty(n *node) {}
var rat = reflect.ValueOf((*[]rune)(nil)).Type().Elem() // runes array type
func _range(n *node) {
index0 := n.child[0].findex // array index location in frame
fnext := getExec(n.fnext)
tnext := getExec(n.tnext)
var value func(*frame) reflect.Value
if len(n.child) == 4 {
index1 := n.child[1].findex // array value location in frame
value := genValue(n.child[2]) // array
an := n.child[2]
index1 := n.child[1].findex // array value location in frame
if isString(an.typ.TypeOf()) {
value = genValueAs(an, rat) // range on string iterates over runes
} else {
value = genValueArray(an)
}
n.exec = func(f *frame) bltn {
a := value(f)
v0 := f.data[index0]
@@ -1511,7 +1558,12 @@ func _range(n *node) {
return tnext
}
} else {
value := genValue(n.child[1]) // array
an := n.child[1]
if isString(an.typ.TypeOf()) {
value = genValueAs(an, rat) // range on string iterates over runes
} else {
value = genValueArray(an)
}
n.exec = func(f *frame) bltn {
a := value(f)
v0 := f.data[index0]
@@ -1709,6 +1761,11 @@ func appendSlice(n *node) {
}
func _append(n *node) {
if c1, c2 := n.child[1], n.child[2]; len(n.child) == 3 && c2.typ.cat == arrayT && c2.typ.val.id() == n.typ.val.id() ||
isByteArray(c1.typ.TypeOf()) && isString(c2.typ.TypeOf()) {
appendSlice(n)
return
}
dest := genValue(n)
value := genValue(n.child[1])
next := getExec(n.tnext)
@@ -1719,7 +1776,7 @@ func _append(n *node) {
values := make([]func(*frame) reflect.Value, l)
for i, arg := range args {
switch {
case isRecursiveStruct(n.typ.val):
case isRecursiveStruct(n.typ.val, n.typ.val.rtype):
values[i] = genValueInterfacePtr(arg)
case arg.typ.untyped:
values[i] = genValueAs(arg, n.child[1].typ.TypeOf().Elem())
@@ -1739,7 +1796,7 @@ func _append(n *node) {
} else {
var value0 func(*frame) reflect.Value
switch {
case isRecursiveStruct(n.typ.val):
case isRecursiveStruct(n.typ.val, n.typ.val.rtype):
value0 = genValueInterfacePtr(n.child[2])
case n.child[2].typ.untyped:
value0 = genValueAs(n.child[2], n.child[1].typ.TypeOf().Elem())
@@ -1767,7 +1824,7 @@ func _cap(n *node) {
func _copy(n *node) {
dest := genValue(n)
value0 := genValue(n.child[1])
value0 := genValueArray(n.child[1])
value1 := genValue(n.child[2])
next := getExec(n.tnext)
@@ -1921,7 +1978,7 @@ func reset(n *node) {
switch l := len(n.child) - 1; l {
case 1:
typ := n.child[0].typ.TypeOf()
typ := n.child[0].typ.frameType()
i := n.child[0].findex
n.exec = func(f *frame) bltn {
f.data[i] = reflect.New(typ).Elem()
@@ -1930,7 +1987,7 @@ func reset(n *node) {
case 2:
c0, c1 := n.child[0], n.child[1]
i0, i1 := c0.findex, c1.findex
t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf()
t0, t1 := c0.typ.frameType(), c1.typ.frameType()
n.exec = func(f *frame) bltn {
f.data[i0] = reflect.New(t0).Elem()
f.data[i1] = reflect.New(t1).Elem()
@@ -1941,7 +1998,7 @@ func reset(n *node) {
index := make([]int, l)
for i, c := range n.child[:l] {
index[i] = c.findex
types[i] = c.typ.TypeOf()
types[i] = c.typ.frameType()
}
n.exec = func(f *frame) bltn {
for i, ind := range index {
@@ -2096,8 +2153,8 @@ func _select(n *node) {
func slice(n *node) {
i := n.findex
next := getExec(n.tnext)
value0 := genValue(n.child[0]) // array
value1 := genValue(n.child[1]) // low (if 2 or 3 args) or high (if 1 arg)
value0 := genValueArray(n.child[0]) // array
value1 := genValue(n.child[1]) // low (if 2 or 3 args) or high (if 1 arg)
switch len(n.child) {
case 2:
@@ -2130,7 +2187,7 @@ func slice(n *node) {
func slice0(n *node) {
i := n.findex
next := getExec(n.tnext)
value0 := genValue(n.child[0])
value0 := genValueArray(n.child[0])
switch len(n.child) {
case 1:

View File

@@ -44,17 +44,15 @@ func (k sKind) String() string {
// A symbol represents an interpreter object such as type, constant, var, func,
// label, builtin or binary object. Symbols are defined within a scope.
type symbol struct {
kind sKind
typ *itype // Type of value
node *node // Node value if index is negative
from []*node // list of nodes jumping to node if kind is label, or nil
recv *receiver // receiver node value, if sym refers to a method
index int // index of value in frame or -1
rval reflect.Value // default value (used for constants)
path string // package path if typ.cat is SrcPkgT or BinPkgT
builtin bltnGenerator // Builtin function or nil
global bool // true if symbol is defined in global space
recursive bool // true if symbol is a recursive type definition
kind sKind
typ *itype // Type of value
node *node // Node value if index is negative
from []*node // list of nodes jumping to node if kind is label, or nil
recv *receiver // receiver node value, if sym refers to a method
index int // index of value in frame or -1
rval reflect.Value // default value (used for constants)
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
}
@@ -162,8 +160,8 @@ func (interp *Interpreter) initScopePkg(n *node) (*scope, string) {
sc := interp.universe
pkgName := mainID
if n.kind == fileStmt {
pkgName = n.child[0].ident
if p := fileNode(n); p != nil {
pkgName = p.child[0].ident
}
if _, ok := interp.scopes[pkgName]; !ok {

View File

@@ -8,10 +8,14 @@ import (
"strings"
)
func (interp *Interpreter) importSrcFile(rPath, path, alias string) error {
func (interp *Interpreter) importSrc(rPath, path, alias string) error {
var dir string
var err error
if interp.srcPkg[path] != nil {
return nil
}
// For relative import paths in the form "./xxx" or "../xxx", the initial
// base path is the directory of the interpreter input file, or "." if no file
// was provided.
@@ -22,9 +26,13 @@ func (interp *Interpreter) importSrcFile(rPath, path, alias string) error {
rPath = "."
}
dir = filepath.Join(filepath.Dir(interp.Name), rPath, path)
} else if dir, rPath, err = pkgDir(interp.goPath, rPath, path); err != nil {
} else if dir, rPath, err = pkgDir(interp.context.GOPATH, rPath, path); err != nil {
return err
}
if interp.rdir[path] {
return fmt.Errorf("import cycle not allowed\n\timports %s", path)
}
interp.rdir[path] = true
files, err := ioutil.ReadDir(dir)
if err != nil {
@@ -33,6 +41,7 @@ func (interp *Interpreter) importSrcFile(rPath, path, alias string) error {
var initNodes []*node
var rootNodes []*node
revisit := make(map[string][]*node)
var root *node
var pkgName string
@@ -40,7 +49,7 @@ func (interp *Interpreter) importSrcFile(rPath, path, alias string) error {
// Parse source files
for _, file := range files {
name := file.Name()
if skipFile(name) {
if skipFile(interp.context, name) {
continue
}
@@ -65,9 +74,21 @@ func (interp *Interpreter) importSrcFile(rPath, path, alias string) error {
rootNodes = append(rootNodes, root)
subRPath := effectivePkg(rPath, path)
if err = interp.gta(root, subRPath); err != nil {
var list []*node
list, err = interp.gta(root, subRPath)
if err != nil {
return err
}
revisit[subRPath] = append(revisit[subRPath], list...)
}
// revisit incomplete nodes where GTA could not complete
for pkg, nodes := range revisit {
for _, n := range nodes {
if _, err = interp.gta(n, pkg); err != nil {
return err
}
}
}
// Generate control flow graphs
@@ -79,6 +100,10 @@ func (interp *Interpreter) importSrcFile(rPath, path, alias string) error {
initNodes = append(initNodes, nodes...)
}
// Register source package in the interpreter. The package contains only
// the global symbols in the package scope.
interp.srcPkg[path] = interp.scopes[pkgName].sym
// Rename imported pkgName to alias if they are different
if pkgName != alias {
interp.scopes[alias] = interp.scopes[pkgName]

View File

@@ -44,6 +44,7 @@ const (
uint64T
uintptrT
valueT
variadicT
maxT
)
@@ -82,6 +83,7 @@ var cats = [...]string{
uint64T: "uint64T",
uintptrT: "uintptrT",
valueT: "valueT",
variadicT: "variadicT",
}
func (c tcat) String() string {
@@ -102,17 +104,16 @@ type structField struct {
// itype defines the internal representation of types in the interpreter
type itype struct {
cat tcat // Type category
field []structField // Array of struct fields if StrucT or InterfaceT
field []structField // Array of struct fields if structT or interfaceT
key *itype // Type of key element if MapT or nil
val *itype // Type of value element if ChanT, MapT, PtrT, AliasT or ArrayT
arg []*itype // Argument types if FuncT or nil
ret []*itype // Return types if FuncT or nil
val *itype // Type of value element if chanT, mapT, ptrT, aliasT, arrayT or variadicT
arg []*itype // Argument types if funcT or nil
ret []*itype // Return types if funcT or nil
method []*node // Associated methods or nil
name string // name of type within its package for a defined type
pkgPath string // for a defined type, the package import path
path string // for a defined type, the package import path
size int // Size of array if ArrayT
rtype reflect.Type // Reflection type if ValueT, or nil
variadic bool // true if type is variadic
incomplete bool // true if type must be parsed again (out of order declarations)
untyped bool // true for a literal value (string or number)
sizedef bool // true if array size is computed from type definition
@@ -130,6 +131,16 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
var t = &itype{node: n, scope: sc}
if n.anc.kind == typeSpec {
name := n.anc.child[0].ident
if sym := sc.sym[name]; sym != nil {
// recover previously declared methods
t.method = sym.typ.method
t.path = sym.typ.path
t.name = name
}
}
switch n.kind {
case addressExpr, starExpr:
t.cat = ptrT
@@ -204,14 +215,12 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
t.name = "float64"
t.untyped = true
case int:
if isShiftOperand(n) && v >= 0 {
t.cat = uintT
t.name = "uint"
n.rval = reflect.ValueOf(uint(v))
} else {
t.cat = intT
t.name = "int"
}
t.cat = intT
t.name = "int"
t.untyped = true
case uint:
t.cat = uintT
t.name = "uint"
t.untyped = true
case rune:
t.cat = runeT
@@ -235,7 +244,9 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
if t, err = nodeType(interp, sc, n.child[0]); err != nil {
return nil, err
}
if t.untyped {
// Shift operator type is inherited from first parameter only
// For other operators, infer type in from 2nd parameter in case of untyped first
if t.untyped && !isShiftNode(n) {
var t1 *itype
t1, err = nodeType(interp, sc, n.child[1])
if !(t1.untyped && isInt(t1.TypeOf()) && isFloat(t.TypeOf())) {
@@ -245,17 +256,78 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
}
case callExpr:
if t, err = nodeType(interp, sc, n.child[0]); err != nil {
return nil, err
}
switch t.cat {
case valueT:
if t.rtype.NumOut() == 1 {
t = &itype{cat: valueT, rtype: t.rtype.Out(0)}
if interp.isBuiltinCall(n) {
// builtin types are special and may depend from their call arguments
t.cat = builtinT
switch n.child[0].ident {
case "complex":
var nt0, nt1 *itype
if nt0, err = nodeType(interp, sc, n.child[1]); err != nil {
return nil, err
}
if nt1, err = nodeType(interp, sc, n.child[2]); err != nil {
return nil, err
}
if nt0.incomplete || nt1.incomplete {
t.incomplete = true
} else {
switch t0, t1 := nt0.TypeOf(), nt1.TypeOf(); {
case isFloat32(t0) && isFloat32(t1):
t = sc.getType("complex64")
case isFloat64(t0) && isFloat64(t1):
t = sc.getType("complex128")
case nt0.untyped && isNumber(t0) && nt1.untyped && isNumber(t1):
t = &itype{cat: valueT, rtype: complexType}
case nt0.untyped && isFloat32(t1) || nt1.untyped && isFloat32(t0):
t = sc.getType("complex64")
case nt0.untyped && isFloat64(t1) || nt1.untyped && isFloat64(t0):
t = sc.getType("complex128")
default:
err = n.cfgErrorf("invalid types %s and %s", t0.Kind(), t1.Kind())
}
}
case "real", "imag":
if t, err = nodeType(interp, sc, n.child[1]); err != nil {
return nil, err
}
if !t.incomplete {
switch k := t.TypeOf().Kind(); {
case k == reflect.Complex64:
t = sc.getType("float32")
case k == reflect.Complex128:
t = sc.getType("float64")
case t.untyped && isNumber(t.TypeOf()):
t = &itype{cat: valueT, rtype: floatType}
default:
err = n.cfgErrorf("invalid complex type %s", k)
}
}
case "cap", "copy", "len":
t = sc.getType("int")
case "append", "make":
t, err = nodeType(interp, sc, n.child[1])
case "new":
t, err = nodeType(interp, sc, n.child[1])
t = &itype{cat: ptrT, val: t, incomplete: t.incomplete}
case "recover":
t = sc.getType("interface{}")
}
default:
if len(t.ret) == 1 {
t = t.ret[0]
if err != nil {
return nil, err
}
} else {
if t, err = nodeType(interp, sc, n.child[0]); err != nil {
return nil, err
}
switch t.cat {
case valueT:
if t.rtype.NumOut() == 1 {
t = &itype{cat: valueT, rtype: t.rtype.Out(0)}
}
default:
if len(t.ret) == 1 {
t = t.ret[0]
}
}
}
@@ -270,10 +342,11 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
t.incomplete = t.val.incomplete
case ellipsisExpr:
if t, err = nodeType(interp, sc, n.child[0]); err != nil {
t.cat = variadicT
if t.val, err = nodeType(interp, sc, n.child[0]); err != nil {
return nil, err
}
t.variadic = true
t.incomplete = t.val.incomplete
case funcLit:
t, err = nodeType(interp, sc, n.child[2])
@@ -314,11 +387,6 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
case identExpr:
if sym, _, found := sc.lookup(n.ident); found {
t = sym.typ
if sym.recursive && t.incomplete {
t.incomplete = false
t.rtype = reflect.TypeOf((*interface{})(nil)).Elem()
sym.typ = t
}
if t.incomplete && t.node != n {
m := t.method
if t, err = nodeType(interp, sc, t.node); err != nil {
@@ -334,6 +402,11 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
case interfaceType:
t.cat = interfaceT
if sname := typeName(n); sname != "" {
if sym, _, found := sc.lookup(sname); found && sym.kind == typeSym {
sym.typ = t
}
}
for _, field := range n.child[0].child {
if len(field.child) == 1 {
typ, err := nodeType(interp, sc, field.child[0])
@@ -366,44 +439,56 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
t, err = nodeType(interp, sc, n.child[0])
case selectorExpr:
pkg, name := n.child[0].ident, n.child[1].ident
if sym, _, found := sc.lookup(pkg); found {
if sym.typ == nil {
t.incomplete = true
break
}
switch sym.typ.cat {
case binPkgT:
pkg := interp.binPkg[sym.path]
if v, ok := pkg[name]; ok {
t.cat = valueT
t.rtype = v.Type()
if isBinType(v) {
t.rtype = t.rtype.Elem()
}
} else {
t.incomplete = true
}
case srcPkgT:
spkg := interp.scopes[pkg]
if st, ok := spkg.sym[name]; ok {
t = st.typ
} else {
t.incomplete = true
// Resolve the left part of selector, then lookup the right part on it
var lt *itype
if lt, err = nodeType(interp, sc, n.child[0]); err != nil {
return nil, err
}
if lt.incomplete {
t.incomplete = true
break
}
name := n.child[1].ident
switch lt.cat {
case binPkgT:
pkg := interp.binPkg[lt.path]
if v, ok := pkg[name]; ok {
t.cat = valueT
t.rtype = v.Type()
if isBinType(v) { // a bin type is encoded as a pointer on nil value
t.rtype = t.rtype.Elem()
}
} else {
err = n.cfgErrorf("undefined selector %s.%s", lt.path, name)
panic(err)
}
case srcPkgT:
pkg := interp.srcPkg[lt.path]
if s, ok := pkg[name]; ok {
t = s.typ
} else {
err = n.cfgErrorf("undefined selector %s.%s", lt.path, name)
}
default:
if m, _ := lt.lookupMethod(name); m != nil {
t, err = nodeType(interp, sc, m.child[2])
} else if bm, _, _, ok := lt.lookupBinMethod(name); ok {
t = &itype{cat: valueT, rtype: bm.Type}
} else if ti := lt.lookupField(name); len(ti) > 0 {
t = lt.fieldSeq(ti)
} else if bs, _, ok := lt.lookupBinField(name); ok {
t = &itype{cat: valueT, rtype: bs.Type}
} else {
err = lt.node.cfgErrorf("undefined selector %s", name)
}
} else {
err = n.cfgErrorf("undefined package: %s", pkg)
}
case structType:
t.cat = structT
var incomplete, found bool
var sym *symbol
if sname := structName(n); sname != "" {
if sym, _, found = sc.lookup(sname); found && sym.kind == typeSym {
sym.recursive = true
var incomplete bool
if sname := typeName(n); sname != "" {
if sym, _, found := sc.lookup(sname); found && sym.kind == typeSym {
sym.typ = t
}
}
for _, c := range n.child[0].child {
@@ -446,11 +531,23 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
err = n.cfgErrorf("type definition not implemented: %s", n.kind)
}
if err == nil && t.cat == nilT && !t.incomplete {
err = n.cfgErrorf("use of untyped nil %s", t.name)
}
return t, err
}
func (interp *Interpreter) isBuiltinCall(n *node) bool {
if n.kind != callExpr {
return false
}
s := interp.universe.sym[n.child[0].ident]
return s != nil && s.kind == bltnSym
}
// struct name returns the name of a struct type
func structName(n *node) string {
func typeName(n *node) string {
if n.anc.kind == typeSpec {
return n.anc.child[0].ident
}
@@ -500,15 +597,23 @@ func init() {
func (t *itype) finalize() (*itype, error) {
var err cfgError
if t.incomplete {
sym, _, found := t.scope.lookup(t.name)
if found && !sym.typ.incomplete {
sym.typ.method = append(sym.typ.method, t.method...)
return sym.typ, nil
}
m := t.method
if t, err = nodeType(t.node.interp, t.scope, t.node); err != nil {
return nil, err
}
if t.incomplete {
return nil, t.node.cfgErrorf("incomplete type")
return nil, t.node.cfgErrorf("incomplete type %s", t.name)
}
t.method = m
t.node.typ = t
if sym != nil {
sym.typ = t
}
}
return t, err
}
@@ -524,7 +629,7 @@ func (t *itype) id() string {
case ptrT:
res = "*" + t.val.id()
default:
res = t.pkgPath + "." + t.name
res = t.path + "." + t.name
}
return res
}
@@ -583,7 +688,7 @@ func (t *itype) lookupField(name string) []int {
for i, f := range t.field {
switch f.typ.cat {
case ptrT, structT:
case ptrT, structT, interfaceT, aliasT:
if index2 := f.typ.lookupField(name); len(index2) > 0 {
return append([]int{i}, index2...)
}
@@ -594,12 +699,14 @@ func (t *itype) lookupField(name string) []int {
}
// lookupBinField returns a structfield and a path to access an embedded binary field in a struct object
func (t *itype) lookupBinField(name string) (reflect.StructField, []int, bool) {
func (t *itype) lookupBinField(name string) (s reflect.StructField, index []int, ok bool) {
if t.cat == ptrT {
return t.val.lookupBinField(name)
}
var index []int
s, ok := t.TypeOf().FieldByName(name)
if !isStruct(t) {
return
}
s, ok = t.TypeOf().FieldByName(name)
if !ok {
for i, f := range t.field {
if f.embed {
@@ -645,23 +752,28 @@ func (t *itype) lookupMethod(name string) (*node, []int) {
}
// lookupBinMethod returns a method and a path to access a field in a struct object (the receiver)
func (t *itype) lookupBinMethod(name string) (reflect.Method, []int, bool) {
func (t *itype) lookupBinMethod(name string) (reflect.Method, []int, bool, bool) {
var isPtr bool
if t.cat == ptrT {
return t.val.lookupBinMethod(name)
}
var index []int
m, ok := t.TypeOf().MethodByName(name)
if !ok {
m, ok = reflect.PtrTo(t.TypeOf()).MethodByName(name)
isPtr = ok
}
if !ok {
for i, f := range t.field {
if f.embed {
if m2, index2, ok2 := f.typ.lookupBinMethod(name); ok2 {
if m2, index2, isPtr2, ok2 := f.typ.lookupBinMethod(name); ok2 {
index = append([]int{i}, index2...)
return m2, index, ok2
return m2, index, isPtr2, ok2
}
}
}
}
return m, index, ok
return m, index, isPtr, ok
}
func exportName(s string) string {
@@ -671,50 +783,64 @@ func exportName(s string) string {
return "X" + s
}
// TypeOf returns the reflection type of dynamic interpreter type t.
func (t *itype) TypeOf() reflect.Type {
var interf = reflect.TypeOf(new(interface{})).Elem()
func (t *itype) refType(defined map[string]bool) reflect.Type {
if t.rtype != nil {
return t.rtype
}
if t.incomplete {
if t.incomplete || t.cat == nilT {
var err error
if t, err = t.finalize(); err != nil {
panic(err)
}
}
if t.val != nil && defined[t.val.name] {
t.val.rtype = interf
}
switch t.cat {
case arrayT:
case aliasT:
t.rtype = t.val.refType(defined)
case arrayT, variadicT:
if t.size > 0 {
t.rtype = reflect.ArrayOf(t.size, t.val.TypeOf())
t.rtype = reflect.ArrayOf(t.size, t.val.refType(defined))
} else {
t.rtype = reflect.SliceOf(t.val.TypeOf())
t.rtype = reflect.SliceOf(t.val.refType(defined))
}
case chanT:
t.rtype = reflect.ChanOf(reflect.BothDir, t.val.TypeOf())
t.rtype = reflect.ChanOf(reflect.BothDir, t.val.refType(defined))
case errorT:
t.rtype = reflect.TypeOf(new(error)).Elem()
case funcT:
in := make([]reflect.Type, len(t.arg))
out := make([]reflect.Type, len(t.ret))
for i, v := range t.arg {
in[i] = v.TypeOf()
if defined[v.name] {
v.rtype = interf
}
in[i] = v.refType(defined)
}
for i, v := range t.ret {
out[i] = v.TypeOf()
if defined[v.name] {
v.rtype = interf
}
out[i] = v.refType(defined)
}
t.rtype = reflect.FuncOf(in, out, false)
case interfaceT:
t.rtype = reflect.TypeOf(new(interface{})).Elem()
t.rtype = interf
case mapT:
t.rtype = reflect.MapOf(t.key.TypeOf(), t.val.TypeOf())
case ptrT:
t.rtype = reflect.PtrTo(t.val.TypeOf())
t.rtype = reflect.PtrTo(t.val.refType(defined))
case structT:
if t.name != "" {
defined[t.name] = true
}
var fields []reflect.StructField
for _, f := range t.field {
field := reflect.StructField{Name: exportName(f.name), Type: f.typ.TypeOf(), Tag: reflect.StructTag(f.tag)}
field := reflect.StructField{Name: exportName(f.name), Type: f.typ.refType(defined), Tag: reflect.StructTag(f.tag)}
fields = append(fields, field)
}
t.rtype = reflect.StructOf(fields)
@@ -726,38 +852,30 @@ func (t *itype) TypeOf() reflect.Type {
return t.rtype
}
func (t *itype) frameType() reflect.Type {
var r reflect.Type
// TypeOf returns the reflection type of dynamic interpreter type t.
func (t *itype) TypeOf() reflect.Type {
return t.refType(map[string]bool{})
}
func (t *itype) frameType() (r reflect.Type) {
var err error
if t, err = t.finalize(); err != nil {
panic(err)
}
switch t.cat {
case arrayT:
case aliasT:
r = t.val.frameType()
case arrayT, variadicT:
if t.size > 0 {
r = reflect.ArrayOf(t.size, t.val.frameType())
} else {
r = reflect.SliceOf(t.val.frameType())
}
//case ChanT:
// r = reflect.ChanOf(reflect.BothDir, t.val.frameType())
//case ErrorT:
// r = reflect.TypeOf(new(error)).Elem()
case funcT:
r = reflect.TypeOf((*node)(nil))
case interfaceT:
r = reflect.TypeOf((*valueInterface)(nil)).Elem()
//case MapT:
// r = reflect.MapOf(t.key.frameType(), t.val.frameType())
//case PtrT:
// r = reflect.PtrTo(t.val.frameType())
//case StructT:
// var fields []reflect.StructField
// for _, f := range t.field {
// field := reflect.StructField{Name: exportName(f.name), Type: f.typ.frameType()}
// fields = append(fields, field)
// }
// r = reflect.StructOf(fields)
default:
// if z, _ := t.zero(); z.IsValid() {
// r = z.Type()
// }
r = t.TypeOf()
}
return r
@@ -781,15 +899,21 @@ func defRecvType(n *node) *itype {
return nil
}
func isShiftOperand(n *node) bool {
switch n.anc.action {
func isShiftNode(n *node) bool {
switch n.action {
case aShl, aShr, aShlAssign, aShrAssign:
return n.anc.lastChild() == n
return true
}
return false
}
func isInterface(t *itype) bool { return t.cat == interfaceT || t.TypeOf().Kind() == reflect.Interface }
func isInterfaceSrc(t *itype) bool {
return t.cat == interfaceT || (t.cat == aliasT && isInterfaceSrc(t.val))
}
func isInterface(t *itype) bool {
return isInterfaceSrc(t) || t.TypeOf().Kind() == reflect.Interface
}
func isStruct(t *itype) bool { return t.TypeOf().Kind() == reflect.Struct }

View File

@@ -107,6 +107,17 @@ func genValue(n *node) func(*frame) reflect.Value {
}
}
func genValueArray(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 {
return func(f *frame) reflect.Value {
return value(f).Elem()
}
}
return value
}
func genValueInterfacePtr(n *node) func(*frame) reflect.Value {
value := genValue(n)
it := reflect.TypeOf((*interface{})(nil)).Elem()

View File

@@ -1,9 +1,9 @@
// Code generated by 'goexports archive/tar'. DO NOT EDIT.
// +build go1.11,!go1.12
package stdlib
// Code generated by 'goexports archive/tar'. DO NOT EDIT.
import (
"archive/tar"
"reflect"
@@ -43,8 +43,5 @@ func init() {
"Header": reflect.ValueOf((*tar.Header)(nil)),
"Reader": reflect.ValueOf((*tar.Reader)(nil)),
"Writer": reflect.ValueOf((*tar.Writer)(nil)),
// interface wrapper definitions
}
}

View File

@@ -1,9 +1,9 @@
// Code generated by 'goexports archive/zip'. DO NOT EDIT.
// +build go1.11,!go1.12
package stdlib
// Code generated by 'goexports archive/zip'. DO NOT EDIT.
import (
"archive/zip"
"reflect"
@@ -32,8 +32,5 @@ func init() {
"ReadCloser": reflect.ValueOf((*zip.ReadCloser)(nil)),
"Reader": reflect.ValueOf((*zip.Reader)(nil)),
"Writer": reflect.ValueOf((*zip.Writer)(nil)),
// interface wrapper definitions
}
}

View File

@@ -1,9 +1,9 @@
// Code generated by 'goexports bufio'. DO NOT EDIT.
// +build go1.11,!go1.12
package stdlib
// Code generated by 'goexports bufio'. DO NOT EDIT.
import (
"bufio"
"reflect"
@@ -38,8 +38,5 @@ func init() {
"Scanner": reflect.ValueOf((*bufio.Scanner)(nil)),
"SplitFunc": reflect.ValueOf((*bufio.SplitFunc)(nil)),
"Writer": reflect.ValueOf((*bufio.Writer)(nil)),
// interface wrapper definitions
}
}

View File

@@ -1,9 +1,9 @@
// Code generated by 'goexports bytes'. DO NOT EDIT.
// +build go1.11,!go1.12
package stdlib
// Code generated by 'goexports bytes'. DO NOT EDIT.
import (
"bytes"
"reflect"
@@ -66,8 +66,5 @@ func init() {
// type definitions
"Buffer": reflect.ValueOf((*bytes.Buffer)(nil)),
"Reader": reflect.ValueOf((*bytes.Reader)(nil)),
// interface wrapper definitions
}
}

View File

@@ -1,9 +1,9 @@
// Code generated by 'goexports compress/bzip2'. DO NOT EDIT.
// +build go1.11,!go1.12
package stdlib
// Code generated by 'goexports compress/bzip2'. DO NOT EDIT.
import (
"compress/bzip2"
"reflect"
@@ -16,8 +16,5 @@ func init() {
// type definitions
"StructuralError": reflect.ValueOf((*bzip2.StructuralError)(nil)),
// interface wrapper definitions
}
}

View File

@@ -1,9 +1,9 @@
// Code generated by 'goexports compress/flate'. DO NOT EDIT.
// +build go1.11,!go1.12
package stdlib
// Code generated by 'goexports compress/flate'. DO NOT EDIT.
import (
"compress/flate"
"io"

View File

@@ -1,9 +1,9 @@
// Code generated by 'goexports compress/gzip'. DO NOT EDIT.
// +build go1.11,!go1.12
package stdlib
// Code generated by 'goexports compress/gzip'. DO NOT EDIT.
import (
"compress/gzip"
"reflect"
@@ -27,8 +27,5 @@ func init() {
"Header": reflect.ValueOf((*gzip.Header)(nil)),
"Reader": reflect.ValueOf((*gzip.Reader)(nil)),
"Writer": reflect.ValueOf((*gzip.Writer)(nil)),
// interface wrapper definitions
}
}

View File

@@ -1,9 +1,9 @@
// Code generated by 'goexports compress/lzw'. DO NOT EDIT.
// +build go1.11,!go1.12
package stdlib
// Code generated by 'goexports compress/lzw'. DO NOT EDIT.
import (
"compress/lzw"
"reflect"
@@ -19,8 +19,5 @@ func init() {
// type definitions
"Order": reflect.ValueOf((*lzw.Order)(nil)),
// interface wrapper definitions
}
}

View File

@@ -1,9 +1,9 @@
// Code generated by 'goexports compress/zlib'. DO NOT EDIT.
// +build go1.11,!go1.12
package stdlib
// Code generated by 'goexports compress/zlib'. DO NOT EDIT.
import (
"compress/zlib"
"io"

View File

@@ -1,9 +1,9 @@
// Code generated by 'goexports container/heap'. DO NOT EDIT.
// +build go1.11,!go1.12
package stdlib
// Code generated by 'goexports container/heap'. DO NOT EDIT.
import (
"container/heap"
"reflect"

View File

@@ -1,9 +1,9 @@
// Code generated by 'goexports container/list'. DO NOT EDIT.
// +build go1.11,!go1.12
package stdlib
// Code generated by 'goexports container/list'. DO NOT EDIT.
import (
"container/list"
"reflect"
@@ -17,8 +17,5 @@ func init() {
// type definitions
"Element": reflect.ValueOf((*list.Element)(nil)),
"List": reflect.ValueOf((*list.List)(nil)),
// interface wrapper definitions
}
}

View File

@@ -1,9 +1,9 @@
// Code generated by 'goexports container/ring'. DO NOT EDIT.
// +build go1.11,!go1.12
package stdlib
// Code generated by 'goexports container/ring'. DO NOT EDIT.
import (
"container/ring"
"reflect"
@@ -16,8 +16,5 @@ func init() {
// type definitions
"Ring": reflect.ValueOf((*ring.Ring)(nil)),
// interface wrapper definitions
}
}

View File

@@ -1,9 +1,9 @@
// Code generated by 'goexports context'. DO NOT EDIT.
// +build go1.11,!go1.12
package stdlib
// Code generated by 'goexports context'. DO NOT EDIT.
import (
"context"
"reflect"

View File

@@ -1,9 +1,9 @@
// Code generated by 'goexports crypto'. DO NOT EDIT.
// +build go1.11,!go1.12
package stdlib
// Code generated by 'goexports crypto'. DO NOT EDIT.
import (
"crypto"
"io"

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