Compare commits

..

44 Commits

Author SHA1 Message Date
Dan Kortschak
47923866ff interp: fix array size assignment type inference 2019-09-30 22:58:04 +02:00
Dan Kortschak
bb2921b42f interp: fix range expression handling 2019-09-30 22:44:04 +02:00
Marc Vertes
2c2b471cb9 fix: goexports to convert value type only for untyped constants 2019-09-27 15:44:05 +02:00
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
636 changed files with 99373 additions and 1740 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 cmd/goexports/goexports
example/inception/inception example/inception/inception
_test/tmp/ _test/tmp/
/dist

View File

@@ -32,6 +32,7 @@
"gocyclo", "gocyclo",
"gochecknoinits", "gochecknoinits",
"gochecknoglobals", "gochecknoglobals",
"typecheck", # v1.17.1 and Go1.13 => bug
] ]
[issues] [issues]
@@ -44,7 +45,6 @@
path = "cmd/goexports/goexports.go" 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." 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]] [[issues.exclude-rules]]
path = "interp/interp.go" path = "interp/.+_test\\.go"
text = "`(astDot|cfgDot|noRun)` is unused" linters = ["goconst"]

View File

@@ -1,7 +1,8 @@
project_name: yaegi project_name: yaegi
builds: builds:
- binary: yaegi - id: yaegi
binary: yaegi
main: ./cmd/yaegi/yaegi.go main: ./cmd/yaegi/yaegi.go
goos: goos:
@@ -25,6 +26,31 @@ builds:
- goos: darwin - goos: darwin
goarch: 386 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: changelog:
sort: asc sort: asc
filters: filters:
@@ -35,11 +61,12 @@ changelog:
- '^test:' - '^test:'
- '^tests:' - '^tests:'
archive: archives:
name_template: '{{ .ProjectName }}_v{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}' - id: archive
format: tar.gz name_template: '{{ .ProjectName }}_v{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
format_overrides: format: tar.gz
- goos: windows format_overrides:
format: zip - goos: windows
files: format: zip
- LICENSE files:
- LICENSE

View File

@@ -2,23 +2,37 @@ language: go
dist: xenial dist: xenial
env: branches:
- GO111MODULE=on only:
- master
- /^v\d+\.\d+(\.\d+)?(-\S*)?$/
notifications: notifications:
email: email:
on_success: never on_success: never
on_failure: change on_failure: change
go: cache:
- 1.11.x directories:
- 1.12.x - $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 go_import_path: github.com/containous/yaegi
before_install: before_install:
# Install linters and misspell # 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 - golangci-lint --version
install: install:
@@ -44,4 +58,4 @@ deploy:
script: curl -sL https://git.io/goreleaser | bash script: curl -sL https://git.io/goreleaser | bash
on: on:
tags: true 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) [![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) [![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) [![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. 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. 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 * Works everywhere Go works
* All Go & runtime resources accessible from script (with control) * All Go & runtime resources accessible from script (with control)
* Security: `unsafe` and `syscall` packages neither used nor exported by default * 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 ## Install
@@ -144,9 +145,7 @@ Beside the known [bugs] which are supposed to be fixed in the short term, there
## Contributing ## Contributing
Yaegi is an open source project, and your feedback and contributions are needed and always welcome. [Contributing guide](CONTRIBUTING.md).
[Issues] and [pull requests] are opened at https://github.com/containous/yaegi
## License ## 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 [docs]: https://godoc.org/github.com/containous/yaegi
[license]: https://github.com/containous/yaegi/blob/master/LICENSE [license]: https://github.com/containous/yaegi/blob/master/LICENSE
[github]: https://github.com/containous/yaegi [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 [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:
// [{}]

12
_test/a33.go Normal file
View File

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

12
_test/a34.go Normal file
View File

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

13
_test/a35.go Normal file
View File

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

13
_test/a36.go Normal file
View File

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

11
_test/a37.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]

11
_test/a38.go Normal file
View File

@@ -0,0 +1,11 @@
package main
import "fmt"
func main() {
a := [...]byte{}
fmt.Printf("%T\n", a)
}
// Output:
// [0]uint8

12
_test/a39.go Normal file
View File

@@ -0,0 +1,12 @@
package main
import "fmt"
func main() {
a := [...]byte{}
b := a
fmt.Printf("%T %T\n", a, b)
}
// Output:
// [0]uint8 [0]uint8

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 package foo
import "./boo" import "github.com/containous/yaegi/_test/foo/boo"
var Bar = "BARR" var Bar = "BARR"
var Boo = boo.Boo 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 package boo
var Boo = "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 package main
import "./foo" import "github.com/containous/yaegi/_test/foo"
func main() { println(foo.Bar, foo.Boo) } func main() { println(foo.Bar, foo.Boo) }
// Output: // Output:
// init boo
// init foo
// BARR Boo // BARR Boo

View File

@@ -1,6 +1,6 @@
package main package main
import "./p1" import "github.com/containous/yaegi/_test/p1"
func main() { println("num:", p1.Uint32()) } 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() { func main() {
fmt.Println(&Hello{}) 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]

14
_test/range0.go Normal file
View File

@@ -0,0 +1,14 @@
package main
import "fmt"
func main() {
v := []int{1, 2, 3}
for i := range v {
v = append(v, i)
}
fmt.Println(v)
}
// Output:
// [1 2 3 0 1 2]

14
_test/range1.go Normal file
View File

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

14
_test/range2.go Normal file
View File

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

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() { func main() {
go forever() go forever()
time.Sleep(1e9) time.Sleep(1e4)
println("bye") 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() { func main() {
fmt.Println(S2{}) fmt.Println(S2{})
} }
// Output:
// {<nil>}

View File

@@ -11,8 +11,8 @@ type Fromage struct {
func main() { func main() {
a := Fromage{} a := Fromage{}
fmt.Println(a.Server) fmt.Println(a.Server.WriteTimeout)
} }
// Output: // 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() { func main() {
fmt.Println("hello") fmt.Println("hello")
} }
// Output:
// hello

View File

@@ -21,3 +21,6 @@ func main() {
c := CreateConfig() c := CreateConfig()
fmt.Println(c) 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() { func main() {
t := time.Unix(1e9, 0) t := time.Unix(1e9, 0).In(time.UTC)
fmt.Println(t.Minute()) 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

13
_test/time9.go Normal file
View File

@@ -0,0 +1,13 @@
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println((5 * time.Minute).Seconds())
}
// Output:
// 300

View File

@@ -25,3 +25,6 @@ func main() {
t := &T1{} t := &T1{}
println(t.Get()) 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 package main
import ( import (
"bufio"
"bytes" "bytes"
"flag"
"fmt" "fmt"
"go/constant" "go/constant"
"go/format" "go/format"
@@ -35,12 +37,14 @@ import (
"text/template" "text/template"
) )
const model = `// +build {{.BuildTags}} const model = `// Code generated by 'goexports {{.PkgName}}'. DO NOT EDIT.
{{.License}}
{{if .BuildTags}}// +build {{.BuildTags}}{{end}}
package {{.Dest}} package {{.Dest}}
// Code generated by 'goexports {{.PkgName}}'. DO NOT EDIT.
import ( import (
{{- range $key, $value := .Imports }} {{- range $key, $value := .Imports }}
{{- if $value}} {{- if $value}}
@@ -53,6 +57,7 @@ import (
func init() { func init() {
Symbols["{{.PkgName}}"] = map[string]reflect.Value{ Symbols["{{.PkgName}}"] = map[string]reflect.Value{
{{- if .Val}}
// function, constant and variable definitions // function, constant and variable definitions
{{range $key, $value := .Val -}} {{range $key, $value := .Val -}}
{{- if $value.Addr -}} {{- if $value.Addr -}}
@@ -62,15 +67,20 @@ func init() {
{{end -}} {{end -}}
{{end}} {{end}}
{{- end}}
{{- if .Typ}}
// type definitions // type definitions
{{range $key, $value := .Typ -}} {{range $key, $value := .Typ -}}
"{{$key}}": reflect.ValueOf((*{{$value}})(nil)), "{{$key}}": reflect.ValueOf((*{{$value}})(nil)),
{{end}} {{end}}
{{- end}}
{{- if .Wrap}}
// interface wrapper definitions // interface wrapper definitions
{{range $key, $value := .Wrap -}} {{range $key, $value := .Wrap -}}
"_{{$key}}": reflect.ValueOf((*{{$value.Name}})(nil)), "_{{$key}}": reflect.ValueOf((*{{$value.Name}})(nil)),
{{end}} {{end}}
{{- end}}
} }
} }
{{range $key, $value := .Wrap -}} {{range $key, $value := .Wrap -}}
@@ -103,7 +113,7 @@ type Wrap struct {
Method []Method 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) p, err := importer.For("source", nil).Import(pkgName)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -137,7 +147,12 @@ func genContent(dest, pkgName string) ([]byte, error) {
pname := path.Base(pkgName) + "." + name pname := path.Base(pkgName) + "." + name
switch o := o.(type) { switch o := o.(type) {
case *types.Const: case *types.Const:
val[name] = Val{fixConst(pname, o.Val()), false} if b, ok := o.Type().(*types.Basic); ok && (b.Info()&types.IsUntyped) != 0 {
// convert untyped constant to right type to avoid overflow
val[name] = Val{fixConst(pname, o.Val()), false}
} else {
val[name] = Val{pname, false}
}
case *types.Func: case *types.Func:
val[name] = Val{pname, false} val[name] = Val{pname, false}
case *types.Var: case *types.Var:
@@ -184,14 +199,23 @@ func genContent(dest, pkgName string) ([]byte, error) {
} }
} }
parts := strings.Split(runtime.Version(), ".") var buildTags string
currentGoVersion := parts[0] + "." + parts[1] if runtime.Version() != "devel" {
parts := strings.Split(runtime.Version(), ".")
minor, err := strconv.Atoi(parts[1]) minorRaw := getMinor(parts[1])
if err != nil {
return nil, fmt.Errorf("failed to parse version: %v", err) 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") base := template.New("goexports")
parse, err := base.Parse(model) parse, err := base.Parse(model)
@@ -199,12 +223,11 @@ func genContent(dest, pkgName string) ([]byte, error) {
return nil, fmt.Errorf("template parsing error: %v", err) return nil, fmt.Errorf("template parsing error: %v", err)
} }
buildTags := currentGoVersion + ",!" + nextGoVersion
if pkgName == "log/syslog" { if pkgName == "log/syslog" {
buildTags += ",!windows,!nacl,!plan9" buildTags += ",!windows,!nacl,!plan9"
} }
b := &bytes.Buffer{} b := new(bytes.Buffer)
data := map[string]interface{}{ data := map[string]interface{}{
"Dest": dest, "Dest": dest,
"Imports": imports, "Imports": imports,
@@ -213,6 +236,7 @@ func genContent(dest, pkgName string) ([]byte, error) {
"Typ": typ, "Typ": typ,
"Wrap": wrap, "Wrap": wrap,
"BuildTags": buildTags, "BuildTags": buildTags,
"License": license,
} }
err = parse.Execute(b, data) err = parse.Execute(b, data)
if err != nil { if err != nil {
@@ -250,17 +274,61 @@ func fixConst(name string, val constant.Value) string {
return name 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() { 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() dir, err := os.Getwd()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
dest := path.Base(dir) dest := path.Base(dir)
for _, pkg := range os.Args[1:] { for _, pkg := range flag.Args() {
content, err := genContent(dest, pkg) content, err := genContent(dest, pkg, license)
if err != nil { if err != nil {
log.Fatal(err) log.Println(err)
continue
} }
var oFile string var oFile string
@@ -271,11 +339,29 @@ func main() {
oFile = strings.Replace(pkg, "/", "_", -1) + ".go" 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 { if err != nil {
log.Fatal(err) 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", desc: "different packages in the same directory",
goPath: "./_pkg9/", 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. // ast parses src string containing Go code and generates the corresponding AST.
// The package name and the AST root node are returned. // The package name and the AST root node are returned.
func (interp *Interpreter) ast(src, name string) (string, *node, error) { func (interp *Interpreter) ast(src, name string) (string, *node, error) {
inRepl := name == ""
var inFunc bool var inFunc bool
// Allow incremental parsing of declarations or statements, by inserting // Allow incremental parsing of declarations or statements, by inserting
// them in a pseudo file package or function. Those statements or // them in a pseudo file package or function. Those statements or
// declarations will be always evaluated in the global scope // declarations will be always evaluated in the global scope
switch interp.firstToken(src) { if inRepl {
case token.PACKAGE: switch interp.firstToken(src) {
// nothing to do case token.PACKAGE:
case token.CONST, token.FUNC, token.IMPORT, token.TYPE, token.VAR: // nothing to do
src = "package main;" + src case token.CONST, token.FUNC, token.IMPORT, token.TYPE, token.VAR:
default: src = "package main;" + src
inFunc = true default:
src = "package main; func main() {" + src + "}" inFunc = true
src = "package main; func main() {" + src + "}"
}
} }
if !interp.buildOk(name, src) { if ok, err := interp.buildOk(interp.context, name, src); !ok || err != nil {
return "", nil, nil // skip source not matching build constraints return "", nil, err // skip source not matching build constraints
} }
f, err := parser.ParseFile(interp.fset, name, src, 0) 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: case *ast.RangeStmt:
// Insert a missing ForRangeStmt for AST correctness // Insert a missing ForRangeStmt for AST correctness
n := addChild(&root, anc, pos, forRangeStmt, aNop) 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: case *ast.ReturnStmt:
st.push(addChild(&root, anc, pos, returnStmt, aReturn), nod) st.push(addChild(&root, anc, pos, returnStmt, aReturn), nod)

View File

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

View File

@@ -1,6 +1,7 @@
package interp package interp
import ( import (
"go/build"
"testing" "testing"
) )
@@ -11,18 +12,21 @@ type testBuild struct {
func TestBuildTag(t *testing.T) { func TestBuildTag(t *testing.T) {
// Assume a specific OS, arch and go version no matter the real underlying system // Assume a specific OS, arch and go version no matter the real underlying system
oo, oa, ov := goos, goarch, goversion ctx := build.Context{
goos, goarch, goversion = "linux", "amd64", 11 GOARCH: "amd64",
defer func() { goos, goarch, goversion = oo, oa, ov }() GOOS: "linux",
BuildTags: []string{"foo"},
ReleaseTags: []string{"go1.11"},
}
tests := []testBuild{ tests := []testBuild{
{"// +build linux", true}, {"// +build linux", true},
{"// +build windows", false}, {"// +build windows", false},
{"// +build go1.9", true},
{"// +build go1.11", true}, {"// +build go1.11", true},
{"// +build !go1.12", true},
{"// +build go1.12", false}, {"// +build go1.12", false},
{"// +build !go1.10", false}, {"// +build !go1.10", false},
{"// +build go1.9", true}, {"// +build !go1.12", true},
{"// +build ignore", false}, {"// +build ignore", false},
{"// +build linux,amd64", true}, {"// +build linux,amd64", true},
{"// +build linux,i386", false}, {"// +build linux,i386", false},
@@ -30,14 +34,17 @@ func TestBuildTag(t *testing.T) {
{"// +build linux\n// +build amd64", true}, {"// +build linux\n// +build amd64", true},
{"// +build linux\n\n\n// +build amd64", true}, {"// +build linux\n\n\n// +build amd64", true},
{"// +build linux\n// +build i386", false}, {"// +build linux\n// +build i386", false},
{"// +build foo", true},
{"// +build !foo", false},
{"// +build bar", false},
} }
i := New(Options{}) i := New(Options{})
for _, test := range tests { for _, test := range tests {
test := test test := test
src := test.src + "\npackage x" src := test.src + "\npackage x"
t.Run("", func(t *testing.T) { t.Run(test.src, func(t *testing.T) {
if r := i.buildOk("", src); r != test.res { if r, _ := i.buildOk(ctx, "", src); r != test.res {
t.Errorf("got %v, want %v", 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) { func TestBuildFile(t *testing.T) {
// Assume a specific OS, arch and go pattern no matter the real underlying system // Assume a specific OS, arch and go pattern no matter the real underlying system
oo, oa := goos, goarch ctx := build.Context{
goos, goarch = "linux", "amd64" GOARCH: "amd64",
defer func() { goos, goarch = oo, oa }() GOOS: "linux",
}
tests := []testBuild{ tests := []testBuild{
{"foo/bar_linux_amd64.go", false}, {"foo/bar_linux_amd64.go", false},
@@ -66,9 +74,43 @@ func TestBuildFile(t *testing.T) {
for _, test := range tests { for _, test := range tests {
test := test test := test
t.Run(test.src, func(t *testing.T) { 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) 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 ( import (
"fmt" "fmt"
"log" "log"
"math"
"path" "path"
"reflect" "reflect"
"unicode" "unicode"
@@ -76,9 +77,11 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
ktyp = &itype{cat: valueT, rtype: typ.Key()} ktyp = &itype{cat: valueT, rtype: typ.Key()}
vtyp = &itype{cat: valueT, rtype: typ.Elem()} vtyp = &itype{cat: valueT, rtype: typ.Elem()}
case reflect.String: case reflect.String:
sc.add(sc.getType("int")) // Add a dummy type to store array shallow copy for range
ktyp = sc.getType("int") ktyp = sc.getType("int")
vtyp = sc.getType("byte") vtyp = sc.getType("rune")
case reflect.Array, reflect.Slice: case reflect.Array, reflect.Slice:
sc.add(sc.getType("int")) // Add a dummy type to store array shallow copy for range
ktyp = sc.getType("int") ktyp = sc.getType("int")
vtyp = &itype{cat: valueT, rtype: typ.Elem()} vtyp = &itype{cat: valueT, rtype: typ.Elem()}
} }
@@ -86,10 +89,20 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
n.anc.gen = rangeMap n.anc.gen = rangeMap
ktyp = o.typ.key ktyp = o.typ.key
vtyp = o.typ.val vtyp = o.typ.val
case stringT: case ptrT:
ktyp = sc.getType("int") ktyp = sc.getType("int")
vtyp = sc.getType("byte") vtyp = o.typ.val
case arrayT: if vtyp.cat == valueT {
vtyp = &itype{cat: valueT, rtype: vtyp.rtype.Elem()}
} else {
vtyp = vtyp.val
}
case stringT:
sc.add(sc.getType("int")) // Add a dummy type to store array shallow copy for range
ktyp = sc.getType("int")
vtyp = sc.getType("rune")
case arrayT, variadicT:
sc.add(sc.getType("int")) // Add a dummy type to store array shallow copy for range
ktyp = sc.getType("int") ktyp = sc.getType("int")
vtyp = o.typ.val vtyp = o.typ.val
} }
@@ -183,7 +196,7 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
} }
case compositeLitExpr: case compositeLitExpr:
if n.child[0].isType(sc) { if len(n.child) > 0 && n.child[0].isType(sc) {
// Get type from 1st child // Get type from 1st child
if n.typ, err = nodeType(interp, sc, n.child[0]); err != nil { if n.typ, err = nodeType(interp, sc, n.child[0]); err != nil {
return false return false
@@ -195,7 +208,10 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
} else if n.anc.typ != nil { } else if n.anc.typ != nil {
n.typ = n.anc.typ.val 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 n.typ.untyped = true
} }
// Propagate type to children, to handle implicit types // Propagate type to children, to handle implicit types
@@ -221,6 +237,9 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
case funcDecl: case funcDecl:
n.val = n 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 // Add a frame indirection level as we enter in a func
sc = sc.pushFunc() sc = sc.pushFunc()
sc.def = n sc.def = n
@@ -257,9 +276,6 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
if typ, err = nodeType(interp, sc, c.lastChild()); err != nil { if typ, err = nodeType(interp, sc, c.lastChild()); err != nil {
return false return false
} }
if typ.variadic {
typ = &itype{cat: arrayT, val: typ}
}
for _, cc := range c.child[:len(c.child)-1] { for _, cc := range c.child[:len(c.child)-1] {
sc.sym[cc.ident] = &symbol{index: sc.add(typ), kind: varSym, typ: typ} sc.sym[cc.ident] = &symbol{index: sc.add(typ), kind: varSym, typ: typ}
} }
@@ -290,14 +306,32 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
name = path.Base(ipath) name = path.Base(ipath)
} }
if interp.binPkg[ipath] != nil && name != "." { 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 { } 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 return false
case typeSpec: 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 return false
case arrayType, basicLit, chanType, funcType, mapType, structType: case arrayType, basicLit, chanType, funcType, mapType, structType:
@@ -310,6 +344,14 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
if err != nil { if err != nil {
return 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 { switch n.kind {
case addressExpr: case addressExpr:
wireChild(n) wireChild(n)
@@ -344,21 +386,20 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
var sym *symbol var sym *symbol
var level int var level int
if n.kind == defineStmt || (n.kind == assignStmt && dest.ident == "_") { 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 { if atyp != nil {
dest.typ = atyp dest.typ = atyp
} else { } else {
if src.typ, err = nodeType(interp, sc, src); err != nil {
return
}
dest.typ = src.typ dest.typ = src.typ
} }
if dest.typ.sizedef { if dest.typ.sizedef {
dest.typ.size = compositeArrayLen(src) dest.typ.size = arrayTypeLen(src)
dest.typ.rtype = nil dest.typ.rtype = nil
} }
if sc.global { if sc.global {
// Do not overload existings symbols (defined in GTA) in global scope // Do not overload existing symbols (defined in GTA) in global scope
sym, _, _ = sc.lookup(dest.ident) sym, _, _ = sc.lookup(dest.ident)
} else { } else {
sym = &symbol{index: sc.add(dest.typ), kind: varSym, typ: dest.typ} sym = &symbol{index: sc.add(dest.typ), kind: varSym, typ: dest.typ}
@@ -385,7 +426,7 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
err = n.cfgErrorf("illegal operand types for '%v' operator", n.action) err = n.cfgErrorf("illegal operand types for '%v' operator", n.action)
} }
case aShlAssign, aShrAssign: case aShlAssign, aShrAssign:
if !(isInt(t0) && isUint(t1)) { if !(dest.isInteger() && src.isNatural()) {
err = n.cfgErrorf("illegal operand types for '%v' operator", n.action) err = n.cfgErrorf("illegal operand types for '%v' operator", n.action)
} }
default: default:
@@ -483,7 +524,9 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
nilSym := interp.universe.sym["nil"] nilSym := interp.universe.sym["nil"]
c0, c1 := n.child[0], n.child[1] c0, c1 := n.child[0], n.child[1]
t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf() 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()) err = n.cfgErrorf("mismatched types %s and %s", c0.typ.id(), c1.typ.id())
break break
} }
@@ -501,7 +544,7 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
err = n.cfgErrorf("illegal operand types for '%v' operator", n.action) err = n.cfgErrorf("illegal operand types for '%v' operator", n.action)
} }
case aShl, aShr: case aShl, aShr:
if !(isInt(t0) && isUint(t1)) { if !(c0.isInteger() && c1.isNatural()) {
err = n.cfgErrorf("illegal operand types for '%v' operator", n.action) err = n.cfgErrorf("illegal operand types for '%v' operator", n.action)
} }
n.typ = c0.typ n.typ = c0.typ
@@ -562,20 +605,34 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
wireChild(n) wireChild(n)
t := n.child[0].typ t := n.child[0].typ
switch t.cat { switch t.cat {
case valueT: case ptrT:
n.typ = &itype{cat: valueT, rtype: t.rtype.Elem()} 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: case stringT:
n.typ = sc.getType("byte") n.typ = sc.getType("byte")
case valueT:
n.typ = &itype{cat: valueT, rtype: t.rtype.Elem()}
default: default:
n.typ = t.val n.typ = t.val
} }
n.findex = sc.add(n.typ) n.findex = sc.add(n.typ)
n.recv = &receiver{node: n} n.recv = &receiver{node: n}
switch k := t.TypeOf().Kind(); k { typ := t.TypeOf()
switch k := typ.Kind(); k {
case reflect.Map: case reflect.Map:
n.gen = getIndexMap n.gen = getIndexMap
case reflect.Array, reflect.Slice, reflect.String: case reflect.Array, reflect.Slice, reflect.String:
n.gen = getIndexArray 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: default:
err = n.cfgErrorf("type is not an array, slice, string or map: %v", t.id()) err = n.cfgErrorf("type is not an array, slice, string or map: %v", t.id())
} }
@@ -633,73 +690,17 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
case callExpr: case callExpr:
wireChild(n) wireChild(n)
switch { switch {
case isBuiltinCall(n): case interp.isBuiltinCall(n):
n.gen = n.child[0].sym.builtin n.gen = n.child[0].sym.builtin
n.child[0].typ = &itype{cat: builtinT} n.child[0].typ = &itype{cat: builtinT}
switch n.child[0].ident { if n.typ, err = nodeType(interp, sc, n); err != nil {
case "append": return
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 != nil { if n.typ.cat == builtinT {
n.findex = sc.add(n.typ)
} else {
n.findex = -1 n.findex = -1
n.val = nil n.val = nil
} else {
n.findex = sc.add(n.typ)
} }
case n.child[0].isType(sc): case n.child[0].isType(sc):
// Type conversion expression // Type conversion expression
@@ -1066,11 +1067,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) { } else if n.typ.cat == ptrT && (n.typ.val.cat == valueT || n.typ.val.cat == errorT) {
// Handle pointer on object defined in runtime // Handle pointer on object defined in runtime
if field, ok := n.typ.val.rtype.FieldByName(n.child[1].ident); ok { if method, ok := n.typ.val.rtype.MethodByName(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 {
n.val = method.Index n.val = method.Index
n.typ = &itype{cat: valueT, rtype: method.Type} n.typ = &itype{cat: valueT, rtype: method.Type}
n.recv = &receiver{node: n.child[0]} n.recv = &receiver{node: n.child[0]}
@@ -1080,13 +1077,18 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
n.gen = getIndexBinMethod n.gen = getIndexBinMethod
n.typ = &itype{cat: valueT, rtype: method.Type} n.typ = &itype{cat: valueT, rtype: method.Type}
n.recv = &receiver{node: n.child[0]} 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 { } else {
err = n.cfgErrorf("undefined selector: %s", n.child[1].ident) err = n.cfgErrorf("undefined selector: %s", n.child[1].ident)
} }
} else if n.typ.cat == binPkgT { } else if n.typ.cat == binPkgT {
// Resolve binary package symbol: a type or a value // Resolve binary package symbol: a type or a value
name := n.child[1].ident 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 s, ok := interp.binPkg[pkg][name]; ok {
if isBinType(s) { if isBinType(s) {
n.kind = rtypeExpr n.kind = rtypeExpr
@@ -1101,16 +1103,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) err = n.cfgErrorf("package %s \"%s\" has no symbol %s", n.child[0].ident, pkg, name)
} }
} else if n.typ.cat == srcPkgT { } 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 // 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.findex = sym.index
n.val = sym.node n.val = sym.node
n.gen = nop n.gen = nop
n.typ = sym.typ n.typ = sym.typ
n.sym = sym n.sym = sym
} else { } 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 { } else if m, lind := n.typ.lookupMethod(n.child[1].ident); m != nil {
if n.child[0].isType(sc) { if n.child[0].isType(sc) {
@@ -1128,19 +1130,23 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
n.typ = m.typ n.typ = m.typ
n.recv = &receiver{node: n.child[0], index: lind} n.recv = &receiver{node: n.child[0], index: lind}
} }
} else if m, lind, ok := n.typ.lookupBinMethod(n.child[1].ident); ok { } else if m, lind, isPtr, ok := n.typ.lookupBinMethod(n.child[1].ident); ok {
n.gen = getIndexSeqMethod if isPtr {
n.gen = getIndexSeqPtrMethod
} else {
n.gen = getIndexSeqMethod
}
n.val = append([]int{m.Index}, lind...) n.val = append([]int{m.Index}, lind...)
n.typ = &itype{cat: valueT, rtype: m.Type} n.typ = &itype{cat: valueT, rtype: m.Type}
} else if ti := n.typ.lookupField(n.child[1].ident); len(ti) > 0 { } else if ti := n.typ.lookupField(n.child[1].ident); len(ti) > 0 {
// Handle struct field // Handle struct field
n.val = ti n.val = ti
switch n.typ.cat { switch {
case interfaceT: case isInterfaceSrc(n.typ):
n.typ = n.typ.fieldSeq(ti) n.typ = n.typ.fieldSeq(ti)
n.gen = getMethodByName n.gen = getMethodByName
n.action = aMethod n.action = aMethod
case ptrT: case n.typ.cat == ptrT:
n.typ = n.typ.fieldSeq(ti) n.typ = n.typ.fieldSeq(ti)
n.gen = getPtrIndexSeq n.gen = getPtrIndexSeq
if n.typ.cat == funcT { if n.typ.cat == funcT {
@@ -1194,7 +1200,11 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
default: default:
// dereference expression // dereference expression
wireChild(n) 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) n.findex = sc.add(n.typ)
} }
@@ -1297,7 +1307,11 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
case sliceExpr: case sliceExpr:
wireChild(n) 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 // Create a slice type from an array type
n.typ = &itype{} n.typ = &itype{}
*n.typ = *ctyp *n.typ = *ctyp
@@ -1459,10 +1473,11 @@ func (n *node) isType(sc *scope) bool {
case selectorExpr: case selectorExpr:
pkg, name := n.child[0].ident, n.child[1].ident pkg, name := n.child[0].ident, n.child[1].ident
if sym, _, ok := sc.lookup(pkg); ok { 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 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 return true // Imported source type
} }
} }
@@ -1514,7 +1529,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 (n *node) lastChild() *node { return n.child[len(n.child)-1] }
func isKey(n *node) bool { func isKey(n *node) bool {
@@ -1552,10 +1617,6 @@ func isMapEntry(n *node) bool {
return n.action == aGetIndex && n.child[0].typ.cat == mapT 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 { func isBinCall(n *node) bool {
return n.kind == callExpr && n.child[0].typ.cat == valueT && n.child[0].typ.rtype.Kind() == reflect.Func return n.kind == callExpr && n.child[0].typ.cat == valueT && n.child[0].typ.rtype.Kind() == reflect.Func
} }
@@ -1569,7 +1630,7 @@ func variadicPos(n *node) int {
return -1 return -1
} }
last := len(n.child[0].typ.arg) - 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 last
} }
return -1 return -1
@@ -1592,6 +1653,13 @@ func getExec(n *node) bltn {
return n.exec 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 // setExec recursively sets the node exec builtin function by walking the CFG
// from the entry point (first node to exec). // from the entry point (first node to exec).
func setExec(n *node) { func setExec(n *node) {
@@ -1644,7 +1712,8 @@ func gotoLabel(s *symbol) {
func compositeGenerator(n *node) (gen bltnGenerator) { func compositeGenerator(n *node) (gen bltnGenerator) {
switch n.typ.cat { switch n.typ.cat {
case aliasT: case aliasT, ptrT:
n.typ.val.untyped = n.typ.untyped
n.typ = n.typ.val n.typ = n.typ.val
gen = compositeGenerator(n) gen = compositeGenerator(n)
case arrayT: case arrayT:
@@ -1652,7 +1721,7 @@ func compositeGenerator(n *node) (gen bltnGenerator) {
case mapT: case mapT:
gen = mapLit gen = mapLit
case structT: case structT:
if n.lastChild().kind == keyValueExpr { if len(n.child) > 0 && n.lastChild().kind == keyValueExpr {
gen = compositeSparse gen = compositeSparse
} else { } else {
gen = compositeLit gen = compositeLit
@@ -1670,13 +1739,20 @@ func compositeGenerator(n *node) (gen bltnGenerator) {
return return
} }
// compositeArrayLen return the litteral array length, computed from definition // arrayTypeLen returns the node's array length. If the expression is an
func compositeArrayLen(n *node) int { // array variable it is determined from the value's type, otherwise it is
// computed from the source definition.
func arrayTypeLen(n *node) int {
if n.typ != nil && n.typ.sizedef {
return n.typ.size
}
max := -1 max := -1
for i, c := range n.child[1:] { for i, c := range n.child[1:] {
r := i r := i
if c.kind == keyValueExpr { if c.kind == keyValueExpr {
r = int(c.child[0].rval.Int()) if v := c.child[0].rval; v.IsValid() {
r = int(c.child[0].rval.Int())
}
} }
if r > max { if r > max {
max = r max = r

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
package interp_test package interp_test
import ( import (
"go/build"
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec" "os/exec"
@@ -31,8 +32,10 @@ func TestInterpConsistencyBuild(t *testing.T) {
for _, file := range files { for _, file := range files {
if filepath.Ext(file.Name()) != ".go" || if filepath.Ext(file.Name()) != ".go" ||
file.Name() == "bad0.go" || // expect error
file.Name() == "export1.go" || // non-main package file.Name() == "export1.go" || // non-main package
file.Name() == "export0.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() == "io0.go" || // use random number
file.Name() == "op1.go" || // expect error file.Name() == "op1.go" || // expect error
file.Name() == "bltn0.go" || // expect error file.Name() == "bltn0.go" || // expect error
@@ -77,7 +80,7 @@ func TestInterpConsistencyBuild(t *testing.T) {
r, w, _ := os.Pipe() r, w, _ := os.Pipe()
os.Stdout = w os.Stdout = w
i := interp.New(interp.Options{}) i := interp.New(interp.Options{GoPath: build.Default.GOPATH})
i.Name = filePath i.Name = filePath
i.Use(stdlib.Symbols) i.Use(stdlib.Symbols)
i.Use(interp.Symbols) i.Use(interp.Symbols)
@@ -128,6 +131,11 @@ func TestInterpErrorConsistency(t *testing.T) {
expectedInterp string expectedInterp string
expectedExec string expectedExec string
}{ }{
{
fileName: "bad0.go",
expectedInterp: "1:1: expected 'package', found println",
expectedExec: "1:1: expected 'package', found println",
},
{ {
fileName: "op1.go", fileName: "op1.go",
expectedInterp: "5:2: illegal operand types for '+=' operator", expectedInterp: "5:2: illegal operand types for '+=' operator",
@@ -137,6 +145,11 @@ func TestInterpErrorConsistency(t *testing.T) {
fileName: "bltn0.go", fileName: "bltn0.go",
expectedInterp: "4:7: use of builtin println not in function call", 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", fileName: "switch8.go",
expectedInterp: "5:2: fallthrough statement out of place", expectedInterp: "5:2: fallthrough statement out of place",
@@ -172,7 +185,7 @@ func TestInterpErrorConsistency(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
i := interp.New(interp.Options{}) i := interp.New(interp.Options{GoPath: build.Default.GOPATH})
i.Name = filePath i.Name = filePath
i.Use(stdlib.Symbols) 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: "rem_FI", src: "8.0 % 4", err: "1:28: illegal operand types for '%' operator"},
{desc: "shl_II", src: "1 << 8", res: "256"}, {desc: "shl_II", src: "1 << 8", res: "256"},
{desc: "shl_IN", src: "1 << -1", err: "1:28: illegal operand types for '<<' operator"}, {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 << 1.0", res: "2"},
{desc: "shl_IF", src: "1.0 << 1", err: "1:28: illegal operand types for '<<' operator"}, {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_II", src: "1 >> 8", res: "0"},
{desc: "shr_IN", src: "1 >> -1", err: "1:28: illegal operand types for '>>' operator"}, {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 := interp.New(interp.Options{})
i.Use(stdlib.Symbols) i.Use(stdlib.Symbols)
runTests(t, i, []testCase{ 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 nil", pre: func() { eval(t, i, "func getNil() error {return nil}") }, src: "getNil()", res: "<nil>"},
{ {
desc: "return func which return error", 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) { func runTests(t *testing.T, i *interp.Interpreter, tests []testCase) {
for _, test := range tests { for _, test := range tests {
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {

View File

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

View File

@@ -198,12 +198,12 @@ func convert(n *node) {
} }
} }
func isRecursiveStruct(t *itype) bool { func isRecursiveStruct(t *itype, rtype reflect.Type) bool {
if t.cat == structT && t.rtype.Kind() == reflect.Interface { if t.cat == structT && rtype.Kind() == reflect.Interface {
return true return true
} }
if t.cat == ptrT { if t.cat == ptrT {
return isRecursiveStruct(t.val) return isRecursiveStruct(t.val, t.rtype.Elem())
} }
return false return false
} }
@@ -230,7 +230,7 @@ func assign(n *node) {
case src.kind == basicLit && src.val == nil: case src.kind == basicLit && src.val == nil:
t := dest.typ.TypeOf() t := dest.typ.TypeOf()
svalue[i] = func(*frame) reflect.Value { return reflect.New(t).Elem() } 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) svalue[i] = genValueInterfacePtr(src)
default: default:
svalue[i] = genValue(src) 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]) methods[i], indexes[i] = n.typ.lookupMethod(names[i])
if methods[i] == nil && n.typ.cat != nilT { if methods[i] == nil && n.typ.cat != nilT {
// interpreted method not found, look for binary method, possibly embedded // 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) wrap := n.interp.getWrapper(typ)
@@ -614,7 +614,7 @@ func call(n *node) {
if c.kind == basicLit { if c.kind == basicLit {
var argType reflect.Type var argType reflect.Type
if variadic >= 0 && i >= variadic { if variadic >= 0 && i >= variadic {
argType = n.child[0].typ.arg[variadic].TypeOf() argType = n.child[0].typ.arg[variadic].val.TypeOf()
} else { } else {
argType = n.child[0].typ.arg[i].TypeOf() argType = n.child[0].typ.arg[i].TypeOf()
} }
@@ -671,7 +671,11 @@ func call(n *node) {
// Init variadic argument vector // Init variadic argument vector
if variadic >= 0 { 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 // Copy input parameters from caller
@@ -887,7 +891,7 @@ func getIndexBinPtrMethod(n *node) {
// getIndexArray returns array value from index // getIndexArray returns array value from index
func getIndexArray(n *node) { func getIndexArray(n *node) {
tnext := getExec(n.tnext) 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 if n.child[1].rval.IsValid() { // constant array index
ai := int(vInt(n.child[1].rval)) ai := int(vInt(n.child[1].rval))
@@ -1087,7 +1091,7 @@ func getPtrIndexSeq(n *node) {
index := n.val.([]int) index := n.val.([]int)
tnext := getExec(n.tnext) tnext := getExec(n.tnext)
var value func(*frame) reflect.Value 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]) v := genValue(n.child[0])
value = func(f *frame) reflect.Value { return v(f).Elem().Elem() } value = func(f *frame) reflect.Value { return v(f).Elem().Elem() }
} else { } 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) { func getIndexSeqMethod(n *node) {
value := genValue(n.child[0]) value := genValue(n.child[0])
index := n.val.([]int) index := n.val.([]int)
@@ -1253,6 +1278,12 @@ func _return(n *node) {
switch t := def.typ.ret[i]; t.cat { switch t := def.typ.ret[i]; t.cat {
case errorT: case errorT:
values[i] = genInterfaceWrapper(c, t.TypeOf()) values[i] = genInterfaceWrapper(c, t.TypeOf())
case aliasT:
if isInterfaceSrc(t) {
values[i] = genValueInterface(c)
} else {
values[i] = genValue(c)
}
case interfaceT: case interfaceT:
values[i] = genValueInterface(c) values[i] = genValueInterface(c)
default: default:
@@ -1320,7 +1351,7 @@ func arrayLit(n *node) {
} }
var a reflect.Value var a reflect.Value
if n.typ.size > 0 { if n.typ.sizedef {
a, _ = n.typ.zero() a, _ = n.typ.zero()
} else { } else {
a = reflect.MakeSlice(n.typ.TypeOf(), max, max) a = reflect.MakeSlice(n.typ.TypeOf(), max, max)
@@ -1453,7 +1484,11 @@ func compositeLit(n *node) {
for i, v := range values { for i, v := range values {
a.Field(i).Set(v(f)) 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 return next
} }
} }
@@ -1484,23 +1519,36 @@ func compositeSparse(n *node) {
for i, v := range values { for i, v := range values {
a.Field(i).Set(v(f)) 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 return next
} }
} }
func empty(n *node) {} func empty(n *node) {}
var rat = reflect.ValueOf((*[]rune)(nil)).Type().Elem() // runes array type
func _range(n *node) { func _range(n *node) {
index0 := n.child[0].findex // array index location in frame index0 := n.child[0].findex // array index location in frame
index2 := index0 - 1 // shallow array for range, always just behind index0
fnext := getExec(n.fnext) fnext := getExec(n.fnext)
tnext := getExec(n.tnext) tnext := getExec(n.tnext)
var value func(*frame) reflect.Value
if len(n.child) == 4 { if len(n.child) == 4 {
index1 := n.child[1].findex // array value location in frame an := n.child[2]
value := genValue(n.child[2]) // array 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 = genValueRangeArray(an)
}
n.exec = func(f *frame) bltn { n.exec = func(f *frame) bltn {
a := value(f) a := f.data[index2]
v0 := f.data[index0] v0 := f.data[index0]
v0.SetInt(v0.Int() + 1) v0.SetInt(v0.Int() + 1)
i := int(v0.Int()) i := int(v0.Int())
@@ -1511,12 +1559,16 @@ func _range(n *node) {
return tnext return tnext
} }
} else { } 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 = genValueRangeArray(an)
}
n.exec = func(f *frame) bltn { n.exec = func(f *frame) bltn {
a := value(f)
v0 := f.data[index0] v0 := f.data[index0]
v0.SetInt(v0.Int() + 1) v0.SetInt(v0.Int() + 1)
if int(v0.Int()) >= a.Len() { if int(v0.Int()) >= f.data[index2].Len() {
return fnext return fnext
} }
return tnext return tnext
@@ -1526,7 +1578,8 @@ func _range(n *node) {
// Init sequence // Init sequence
next := n.exec next := n.exec
n.child[0].exec = func(f *frame) bltn { n.child[0].exec = func(f *frame) bltn {
f.data[index0].SetInt(-1) f.data[index2] = value(f) // set array shallow copy for range
f.data[index0].SetInt(-1) // assing index value
return next return next
} }
} }
@@ -1709,6 +1762,11 @@ func appendSlice(n *node) {
} }
func _append(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) dest := genValue(n)
value := genValue(n.child[1]) value := genValue(n.child[1])
next := getExec(n.tnext) next := getExec(n.tnext)
@@ -1719,7 +1777,7 @@ func _append(n *node) {
values := make([]func(*frame) reflect.Value, l) values := make([]func(*frame) reflect.Value, l)
for i, arg := range args { for i, arg := range args {
switch { switch {
case isRecursiveStruct(n.typ.val): case isRecursiveStruct(n.typ.val, n.typ.val.rtype):
values[i] = genValueInterfacePtr(arg) values[i] = genValueInterfacePtr(arg)
case arg.typ.untyped: case arg.typ.untyped:
values[i] = genValueAs(arg, n.child[1].typ.TypeOf().Elem()) values[i] = genValueAs(arg, n.child[1].typ.TypeOf().Elem())
@@ -1739,7 +1797,7 @@ func _append(n *node) {
} else { } else {
var value0 func(*frame) reflect.Value var value0 func(*frame) reflect.Value
switch { switch {
case isRecursiveStruct(n.typ.val): case isRecursiveStruct(n.typ.val, n.typ.val.rtype):
value0 = genValueInterfacePtr(n.child[2]) value0 = genValueInterfacePtr(n.child[2])
case n.child[2].typ.untyped: case n.child[2].typ.untyped:
value0 = genValueAs(n.child[2], n.child[1].typ.TypeOf().Elem()) value0 = genValueAs(n.child[2], n.child[1].typ.TypeOf().Elem())
@@ -1767,7 +1825,7 @@ func _cap(n *node) {
func _copy(n *node) { func _copy(n *node) {
dest := genValue(n) dest := genValue(n)
value0 := genValue(n.child[1]) value0 := genValueArray(n.child[1])
value1 := genValue(n.child[2]) value1 := genValue(n.child[2])
next := getExec(n.tnext) next := getExec(n.tnext)
@@ -1921,7 +1979,7 @@ func reset(n *node) {
switch l := len(n.child) - 1; l { switch l := len(n.child) - 1; l {
case 1: case 1:
typ := n.child[0].typ.TypeOf() typ := n.child[0].typ.frameType()
i := n.child[0].findex i := n.child[0].findex
n.exec = func(f *frame) bltn { n.exec = func(f *frame) bltn {
f.data[i] = reflect.New(typ).Elem() f.data[i] = reflect.New(typ).Elem()
@@ -1930,7 +1988,7 @@ func reset(n *node) {
case 2: case 2:
c0, c1 := n.child[0], n.child[1] c0, c1 := n.child[0], n.child[1]
i0, i1 := c0.findex, c1.findex 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 { n.exec = func(f *frame) bltn {
f.data[i0] = reflect.New(t0).Elem() f.data[i0] = reflect.New(t0).Elem()
f.data[i1] = reflect.New(t1).Elem() f.data[i1] = reflect.New(t1).Elem()
@@ -1941,7 +1999,7 @@ func reset(n *node) {
index := make([]int, l) index := make([]int, l)
for i, c := range n.child[:l] { for i, c := range n.child[:l] {
index[i] = c.findex index[i] = c.findex
types[i] = c.typ.TypeOf() types[i] = c.typ.frameType()
} }
n.exec = func(f *frame) bltn { n.exec = func(f *frame) bltn {
for i, ind := range index { for i, ind := range index {
@@ -2096,8 +2154,8 @@ func _select(n *node) {
func slice(n *node) { func slice(n *node) {
i := n.findex i := n.findex
next := getExec(n.tnext) next := getExec(n.tnext)
value0 := genValue(n.child[0]) // array value0 := genValueArray(n.child[0]) // array
value1 := genValue(n.child[1]) // low (if 2 or 3 args) or high (if 1 arg) value1 := genValue(n.child[1]) // low (if 2 or 3 args) or high (if 1 arg)
switch len(n.child) { switch len(n.child) {
case 2: case 2:
@@ -2130,7 +2188,7 @@ func slice(n *node) {
func slice0(n *node) { func slice0(n *node) {
i := n.findex i := n.findex
next := getExec(n.tnext) next := getExec(n.tnext)
value0 := genValue(n.child[0]) value0 := genValueArray(n.child[0])
switch len(n.child) { switch len(n.child) {
case 1: 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, // A symbol represents an interpreter object such as type, constant, var, func,
// label, builtin or binary object. Symbols are defined within a scope. // label, builtin or binary object. Symbols are defined within a scope.
type symbol struct { type symbol struct {
kind sKind kind sKind
typ *itype // Type of value typ *itype // Type of value
node *node // Node value if index is negative node *node // Node value if index is negative
from []*node // list of nodes jumping to node if kind is label, or nil from []*node // list of nodes jumping to node if kind is label, or nil
recv *receiver // receiver node value, if sym refers to a method recv *receiver // receiver node value, if sym refers to a method
index int // index of value in frame or -1 index int // index of value in frame or -1
rval reflect.Value // default value (used for constants) 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
builtin bltnGenerator // Builtin function or nil global bool // true if symbol is defined in global space
global bool // true if symbol is defined in global space
recursive bool // true if symbol is a recursive type definition
// TODO: implement constant checking // TODO: implement constant checking
//constant bool // true if symbol value is constant //constant bool // true if symbol value is constant
} }
@@ -162,8 +160,8 @@ func (interp *Interpreter) initScopePkg(n *node) (*scope, string) {
sc := interp.universe sc := interp.universe
pkgName := mainID pkgName := mainID
if n.kind == fileStmt { if p := fileNode(n); p != nil {
pkgName = n.child[0].ident pkgName = p.child[0].ident
} }
if _, ok := interp.scopes[pkgName]; !ok { if _, ok := interp.scopes[pkgName]; !ok {

View File

@@ -8,10 +8,14 @@ import (
"strings" "strings"
) )
func (interp *Interpreter) importSrcFile(rPath, path, alias string) error { func (interp *Interpreter) importSrc(rPath, path, alias string) error {
var dir string var dir string
var err error var err error
if interp.srcPkg[path] != nil {
return nil
}
// For relative import paths in the form "./xxx" or "../xxx", the initial // 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 // base path is the directory of the interpreter input file, or "." if no file
// was provided. // was provided.
@@ -22,9 +26,13 @@ func (interp *Interpreter) importSrcFile(rPath, path, alias string) error {
rPath = "." rPath = "."
} }
dir = filepath.Join(filepath.Dir(interp.Name), rPath, path) 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 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) files, err := ioutil.ReadDir(dir)
if err != nil { if err != nil {
@@ -33,6 +41,7 @@ func (interp *Interpreter) importSrcFile(rPath, path, alias string) error {
var initNodes []*node var initNodes []*node
var rootNodes []*node var rootNodes []*node
revisit := make(map[string][]*node)
var root *node var root *node
var pkgName string var pkgName string
@@ -40,7 +49,7 @@ func (interp *Interpreter) importSrcFile(rPath, path, alias string) error {
// Parse source files // Parse source files
for _, file := range files { for _, file := range files {
name := file.Name() name := file.Name()
if skipFile(name) { if skipFile(interp.context, name) {
continue continue
} }
@@ -65,9 +74,21 @@ func (interp *Interpreter) importSrcFile(rPath, path, alias string) error {
rootNodes = append(rootNodes, root) rootNodes = append(rootNodes, root)
subRPath := effectivePkg(rPath, path) 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 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 // Generate control flow graphs
@@ -79,6 +100,10 @@ func (interp *Interpreter) importSrcFile(rPath, path, alias string) error {
initNodes = append(initNodes, nodes...) 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 // Rename imported pkgName to alias if they are different
if pkgName != alias { if pkgName != alias {
interp.scopes[alias] = interp.scopes[pkgName] interp.scopes[alias] = interp.scopes[pkgName]

View File

@@ -44,6 +44,7 @@ const (
uint64T uint64T
uintptrT uintptrT
valueT valueT
variadicT
maxT maxT
) )
@@ -82,6 +83,7 @@ var cats = [...]string{
uint64T: "uint64T", uint64T: "uint64T",
uintptrT: "uintptrT", uintptrT: "uintptrT",
valueT: "valueT", valueT: "valueT",
variadicT: "variadicT",
} }
func (c tcat) String() string { func (c tcat) String() string {
@@ -102,17 +104,16 @@ type structField struct {
// itype defines the internal representation of types in the interpreter // itype defines the internal representation of types in the interpreter
type itype struct { type itype struct {
cat tcat // Type category 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 key *itype // Type of key element if MapT or nil
val *itype // Type of value element if ChanT, MapT, PtrT, AliasT or ArrayT val *itype // Type of value element if chanT, mapT, ptrT, aliasT, arrayT or variadicT
arg []*itype // Argument types if FuncT or nil arg []*itype // Argument types if funcT or nil
ret []*itype // Return types if FuncT or nil ret []*itype // Return types if funcT or nil
method []*node // Associated methods or nil method []*node // Associated methods or nil
name string // name of type within its package for a defined type 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 size int // Size of array if ArrayT
rtype reflect.Type // Reflection type if ValueT, or nil 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) incomplete bool // true if type must be parsed again (out of order declarations)
untyped bool // true for a literal value (string or number) untyped bool // true for a literal value (string or number)
sizedef bool // true if array size is computed from type definition sizedef bool // true if array size is computed from type definition
@@ -122,14 +123,26 @@ type itype struct {
// nodeType returns a type definition for the corresponding AST subtree // nodeType returns a type definition for the corresponding AST subtree
func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) { func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
var err cfgError
if n.typ != nil && !n.typ.incomplete { if n.typ != nil && !n.typ.incomplete {
return n.typ, err if n.kind == sliceExpr {
n.typ.sizedef = false
}
return n.typ, nil
} }
var t = &itype{node: n, scope: sc} 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
}
}
var err cfgError
switch n.kind { switch n.kind {
case addressExpr, starExpr: case addressExpr, starExpr:
t.cat = ptrT t.cat = ptrT
@@ -147,7 +160,7 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
t.size = int(n.child[0].rval.Int()) t.size = int(n.child[0].rval.Int())
case n.child[0].kind == ellipsisExpr: case n.child[0].kind == ellipsisExpr:
// [...]T expression // [...]T expression
t.sizedef = true t.size = arrayTypeLen(n.anc)
default: default:
if sym, _, ok := sc.lookup(n.child[0].ident); ok { if sym, _, ok := sc.lookup(n.child[0].ident); ok {
// Resolve symbol to get size value // Resolve symbol to get size value
@@ -171,6 +184,7 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
if t.val, err = nodeType(interp, sc, n.child[1]); err != nil { if t.val, err = nodeType(interp, sc, n.child[1]); err != nil {
return nil, err return nil, err
} }
t.sizedef = true
t.incomplete = t.incomplete || t.val.incomplete t.incomplete = t.incomplete || t.val.incomplete
} else { } else {
if t.val, err = nodeType(interp, sc, n.child[0]); err != nil { if t.val, err = nodeType(interp, sc, n.child[0]); err != nil {
@@ -204,14 +218,12 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
t.name = "float64" t.name = "float64"
t.untyped = true t.untyped = true
case int: case int:
if isShiftOperand(n) && v >= 0 { t.cat = intT
t.cat = uintT t.name = "int"
t.name = "uint" t.untyped = true
n.rval = reflect.ValueOf(uint(v)) case uint:
} else { t.cat = uintT
t.cat = intT t.name = "uint"
t.name = "int"
}
t.untyped = true t.untyped = true
case rune: case rune:
t.cat = runeT t.cat = runeT
@@ -235,7 +247,9 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
if t, err = nodeType(interp, sc, n.child[0]); err != nil { if t, err = nodeType(interp, sc, n.child[0]); err != nil {
return nil, err 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 var t1 *itype
t1, err = nodeType(interp, sc, n.child[1]) t1, err = nodeType(interp, sc, n.child[1])
if !(t1.untyped && isInt(t1.TypeOf()) && isFloat(t.TypeOf())) { if !(t1.untyped && isInt(t1.TypeOf()) && isFloat(t.TypeOf())) {
@@ -245,17 +259,78 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
} }
case callExpr: case callExpr:
if t, err = nodeType(interp, sc, n.child[0]); err != nil { if interp.isBuiltinCall(n) {
return nil, err // builtin types are special and may depend from their call arguments
} t.cat = builtinT
switch t.cat { switch n.child[0].ident {
case valueT: case "complex":
if t.rtype.NumOut() == 1 { var nt0, nt1 *itype
t = &itype{cat: valueT, rtype: t.rtype.Out(0)} 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 err != nil {
if len(t.ret) == 1 { return nil, err
t = t.ret[0] }
} 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 +345,11 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
t.incomplete = t.val.incomplete t.incomplete = t.val.incomplete
case ellipsisExpr: 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 return nil, err
} }
t.variadic = true t.incomplete = t.val.incomplete
case funcLit: case funcLit:
t, err = nodeType(interp, sc, n.child[2]) t, err = nodeType(interp, sc, n.child[2])
@@ -314,11 +390,6 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
case identExpr: case identExpr:
if sym, _, found := sc.lookup(n.ident); found { if sym, _, found := sc.lookup(n.ident); found {
t = sym.typ 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 { if t.incomplete && t.node != n {
m := t.method m := t.method
if t, err = nodeType(interp, sc, t.node); err != nil { if t, err = nodeType(interp, sc, t.node); err != nil {
@@ -334,6 +405,11 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
case interfaceType: case interfaceType:
t.cat = interfaceT 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 { for _, field := range n.child[0].child {
if len(field.child) == 1 { if len(field.child) == 1 {
typ, err := nodeType(interp, sc, field.child[0]) typ, err := nodeType(interp, sc, field.child[0])
@@ -366,44 +442,56 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
t, err = nodeType(interp, sc, n.child[0]) t, err = nodeType(interp, sc, n.child[0])
case selectorExpr: case selectorExpr:
pkg, name := n.child[0].ident, n.child[1].ident // Resolve the left part of selector, then lookup the right part on it
if sym, _, found := sc.lookup(pkg); found { var lt *itype
if sym.typ == nil { if lt, err = nodeType(interp, sc, n.child[0]); err != nil {
t.incomplete = true return nil, err
break }
} if lt.incomplete {
switch sym.typ.cat { t.incomplete = true
case binPkgT: break
pkg := interp.binPkg[sym.path] }
if v, ok := pkg[name]; ok { name := n.child[1].ident
t.cat = valueT switch lt.cat {
t.rtype = v.Type() case binPkgT:
if isBinType(v) { pkg := interp.binPkg[lt.path]
t.rtype = t.rtype.Elem() if v, ok := pkg[name]; ok {
} t.cat = valueT
} else { t.rtype = v.Type()
t.incomplete = true if isBinType(v) { // a bin type is encoded as a pointer on nil value
} t.rtype = t.rtype.Elem()
case srcPkgT:
spkg := interp.scopes[pkg]
if st, ok := spkg.sym[name]; ok {
t = st.typ
} else {
t.incomplete = true
} }
} 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: case structType:
t.cat = structT t.cat = structT
var incomplete, found bool var incomplete bool
var sym *symbol if sname := typeName(n); sname != "" {
if sname := structName(n); sname != "" { if sym, _, found := sc.lookup(sname); found && sym.kind == typeSym {
if sym, _, found = sc.lookup(sname); found && sym.kind == typeSym { sym.typ = t
sym.recursive = true
} }
} }
for _, c := range n.child[0].child { for _, c := range n.child[0].child {
@@ -446,11 +534,23 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
err = n.cfgErrorf("type definition not implemented: %s", n.kind) 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 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 // struct name returns the name of a struct type
func structName(n *node) string { func typeName(n *node) string {
if n.anc.kind == typeSpec { if n.anc.kind == typeSpec {
return n.anc.child[0].ident return n.anc.child[0].ident
} }
@@ -500,15 +600,23 @@ func init() {
func (t *itype) finalize() (*itype, error) { func (t *itype) finalize() (*itype, error) {
var err cfgError var err cfgError
if t.incomplete { 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 m := t.method
if t, err = nodeType(t.node.interp, t.scope, t.node); err != nil { if t, err = nodeType(t.node.interp, t.scope, t.node); err != nil {
return nil, err return nil, err
} }
if t.incomplete { if t.incomplete {
return nil, t.node.cfgErrorf("incomplete type") return nil, t.node.cfgErrorf("incomplete type %s", t.name)
} }
t.method = m t.method = m
t.node.typ = t t.node.typ = t
if sym != nil {
sym.typ = t
}
} }
return t, err return t, err
} }
@@ -524,7 +632,7 @@ func (t *itype) id() string {
case ptrT: case ptrT:
res = "*" + t.val.id() res = "*" + t.val.id()
default: default:
res = t.pkgPath + "." + t.name res = t.path + "." + t.name
} }
return res return res
} }
@@ -583,7 +691,7 @@ func (t *itype) lookupField(name string) []int {
for i, f := range t.field { for i, f := range t.field {
switch f.typ.cat { switch f.typ.cat {
case ptrT, structT: case ptrT, structT, interfaceT, aliasT:
if index2 := f.typ.lookupField(name); len(index2) > 0 { if index2 := f.typ.lookupField(name); len(index2) > 0 {
return append([]int{i}, index2...) return append([]int{i}, index2...)
} }
@@ -594,12 +702,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 // 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 { if t.cat == ptrT {
return t.val.lookupBinField(name) return t.val.lookupBinField(name)
} }
var index []int if !isStruct(t) {
s, ok := t.TypeOf().FieldByName(name) return
}
s, ok = t.TypeOf().FieldByName(name)
if !ok { if !ok {
for i, f := range t.field { for i, f := range t.field {
if f.embed { if f.embed {
@@ -645,23 +755,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) // 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 { if t.cat == ptrT {
return t.val.lookupBinMethod(name) return t.val.lookupBinMethod(name)
} }
var index []int var index []int
m, ok := t.TypeOf().MethodByName(name) m, ok := t.TypeOf().MethodByName(name)
if !ok {
m, ok = reflect.PtrTo(t.TypeOf()).MethodByName(name)
isPtr = ok
}
if !ok { if !ok {
for i, f := range t.field { for i, f := range t.field {
if f.embed { 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...) 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 { func exportName(s string) string {
@@ -671,50 +786,64 @@ func exportName(s string) string {
return "X" + s return "X" + s
} }
// TypeOf returns the reflection type of dynamic interpreter type t. var interf = reflect.TypeOf(new(interface{})).Elem()
func (t *itype) TypeOf() reflect.Type {
func (t *itype) refType(defined map[string]bool) reflect.Type {
if t.rtype != nil { if t.rtype != nil {
return t.rtype return t.rtype
} }
if t.incomplete { if t.incomplete || t.cat == nilT {
var err error var err error
if t, err = t.finalize(); err != nil { if t, err = t.finalize(); err != nil {
panic(err) panic(err)
} }
} }
if t.val != nil && defined[t.val.name] {
t.val.rtype = interf
}
switch t.cat { switch t.cat {
case arrayT: case aliasT:
if t.size > 0 { t.rtype = t.val.refType(defined)
t.rtype = reflect.ArrayOf(t.size, t.val.TypeOf()) case arrayT, variadicT:
if t.sizedef {
t.rtype = reflect.ArrayOf(t.size, t.val.refType(defined))
} else { } else {
t.rtype = reflect.SliceOf(t.val.TypeOf()) t.rtype = reflect.SliceOf(t.val.refType(defined))
} }
case chanT: case chanT:
t.rtype = reflect.ChanOf(reflect.BothDir, t.val.TypeOf()) t.rtype = reflect.ChanOf(reflect.BothDir, t.val.refType(defined))
case errorT: case errorT:
t.rtype = reflect.TypeOf(new(error)).Elem() t.rtype = reflect.TypeOf(new(error)).Elem()
case funcT: case funcT:
in := make([]reflect.Type, len(t.arg)) in := make([]reflect.Type, len(t.arg))
out := make([]reflect.Type, len(t.ret)) out := make([]reflect.Type, len(t.ret))
for i, v := range t.arg { 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 { 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) t.rtype = reflect.FuncOf(in, out, false)
case interfaceT: case interfaceT:
t.rtype = reflect.TypeOf(new(interface{})).Elem() t.rtype = interf
case mapT: case mapT:
t.rtype = reflect.MapOf(t.key.TypeOf(), t.val.TypeOf()) t.rtype = reflect.MapOf(t.key.TypeOf(), t.val.TypeOf())
case ptrT: case ptrT:
t.rtype = reflect.PtrTo(t.val.TypeOf()) t.rtype = reflect.PtrTo(t.val.refType(defined))
case structT: case structT:
if t.name != "" {
defined[t.name] = true
}
var fields []reflect.StructField var fields []reflect.StructField
for _, f := range t.field { 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) fields = append(fields, field)
} }
t.rtype = reflect.StructOf(fields) t.rtype = reflect.StructOf(fields)
@@ -726,38 +855,30 @@ func (t *itype) TypeOf() reflect.Type {
return t.rtype return t.rtype
} }
func (t *itype) frameType() reflect.Type { // TypeOf returns the reflection type of dynamic interpreter type t.
var r reflect.Type 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 { switch t.cat {
case arrayT: case aliasT:
if t.size > 0 { r = t.val.frameType()
case arrayT, variadicT:
if t.sizedef {
r = reflect.ArrayOf(t.size, t.val.frameType()) r = reflect.ArrayOf(t.size, t.val.frameType())
} else { } else {
r = reflect.SliceOf(t.val.frameType()) 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: case funcT:
r = reflect.TypeOf((*node)(nil)) r = reflect.TypeOf((*node)(nil))
case interfaceT: case interfaceT:
r = reflect.TypeOf((*valueInterface)(nil)).Elem() 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: default:
// if z, _ := t.zero(); z.IsValid() {
// r = z.Type()
// }
r = t.TypeOf() r = t.TypeOf()
} }
return r return r
@@ -781,15 +902,21 @@ func defRecvType(n *node) *itype {
return nil return nil
} }
func isShiftOperand(n *node) bool { func isShiftNode(n *node) bool {
switch n.anc.action { switch n.action {
case aShl, aShr, aShlAssign, aShrAssign: case aShl, aShr, aShlAssign, aShrAssign:
return n.anc.lastChild() == n return true
} }
return false 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 } func isStruct(t *itype) bool { return t.TypeOf().Kind() == reflect.Struct }

View File

@@ -107,6 +107,33 @@ 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 genValueRangeArray(n *node) func(*frame) reflect.Value {
value := genValue(n)
// dereference array pointer, to support array operations on array pointer
if n.typ.TypeOf().Kind() == reflect.Ptr {
return func(f *frame) reflect.Value {
return value(f).Elem()
}
}
return func(f *frame) reflect.Value {
// This is necessary to prevent changes in the returned
// reflect.Value being reflected back to the value used
// for the range expression.
return reflect.ValueOf(value(f).Interface())
}
}
func genValueInterfacePtr(n *node) func(*frame) reflect.Value { func genValueInterfacePtr(n *node) func(*frame) reflect.Value {
value := genValue(n) value := genValue(n)
it := reflect.TypeOf((*interface{})(nil)).Elem() 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 // +build go1.11,!go1.12
package stdlib package stdlib
// Code generated by 'goexports archive/tar'. DO NOT EDIT.
import ( import (
"archive/tar" "archive/tar"
"reflect" "reflect"
@@ -43,8 +43,5 @@ func init() {
"Header": reflect.ValueOf((*tar.Header)(nil)), "Header": reflect.ValueOf((*tar.Header)(nil)),
"Reader": reflect.ValueOf((*tar.Reader)(nil)), "Reader": reflect.ValueOf((*tar.Reader)(nil)),
"Writer": reflect.ValueOf((*tar.Writer)(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 // +build go1.11,!go1.12
package stdlib package stdlib
// Code generated by 'goexports archive/zip'. DO NOT EDIT.
import ( import (
"archive/zip" "archive/zip"
"reflect" "reflect"
@@ -32,8 +32,5 @@ func init() {
"ReadCloser": reflect.ValueOf((*zip.ReadCloser)(nil)), "ReadCloser": reflect.ValueOf((*zip.ReadCloser)(nil)),
"Reader": reflect.ValueOf((*zip.Reader)(nil)), "Reader": reflect.ValueOf((*zip.Reader)(nil)),
"Writer": reflect.ValueOf((*zip.Writer)(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 // +build go1.11,!go1.12
package stdlib package stdlib
// Code generated by 'goexports bufio'. DO NOT EDIT.
import ( import (
"bufio" "bufio"
"reflect" "reflect"
@@ -38,8 +38,5 @@ func init() {
"Scanner": reflect.ValueOf((*bufio.Scanner)(nil)), "Scanner": reflect.ValueOf((*bufio.Scanner)(nil)),
"SplitFunc": reflect.ValueOf((*bufio.SplitFunc)(nil)), "SplitFunc": reflect.ValueOf((*bufio.SplitFunc)(nil)),
"Writer": reflect.ValueOf((*bufio.Writer)(nil)), "Writer": reflect.ValueOf((*bufio.Writer)(nil)),
// interface wrapper definitions
} }
} }

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