Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a5b16cad6 | ||
|
|
1990b96ccd | ||
|
|
da27c4fbc2 | ||
|
|
f5b5481794 | ||
|
|
79b7420ee1 | ||
|
|
8a6061cc86 | ||
|
|
c10e468d01 | ||
|
|
75e5f99bc5 | ||
|
|
79e32b5a92 | ||
|
|
63b8cc42b9 | ||
|
|
f4a9cd3cbe | ||
|
|
6447a677f3 | ||
|
|
68a430f969 | ||
|
|
dc7c64ba88 | ||
|
|
d6ad13acea | ||
|
|
d124954a7d | ||
|
|
8de3add6fa | ||
|
|
20c8f5ef7c | ||
|
|
ce2bb794fa | ||
|
|
c4a297cbdc | ||
|
|
c473dceda8 | ||
|
|
f202764973 | ||
|
|
9d658604be | ||
|
|
166fff7072 | ||
|
|
8efc4f0735 | ||
|
|
6aa4f45c42 | ||
|
|
f3dbce93a4 | ||
|
|
0e3ea5732a | ||
|
|
1679870ea3 | ||
|
|
9b4ea62f69 | ||
|
|
eee72d1aae | ||
|
|
97cf8c4210 | ||
|
|
7bb8b4631f | ||
|
|
9f43170708 | ||
|
|
71112dbe87 | ||
|
|
4a8093609f | ||
|
|
7865c90737 | ||
|
|
e4e3d11772 | ||
|
|
a5242cbb9e | ||
|
|
c4d1bf5029 | ||
|
|
e003140c6e | ||
|
|
6b8c94e6c4 | ||
|
|
143e4a4559 | ||
|
|
dfeddbe823 | ||
|
|
021824930d | ||
|
|
b8301f10a8 | ||
|
|
2e8808317f | ||
|
|
79747f3d6f | ||
|
|
63825e7201 | ||
|
|
03ccda1a69 | ||
|
|
e02621577f | ||
|
|
ab869c8d20 | ||
|
|
b2aa636ea0 | ||
|
|
ae725fb3d9 | ||
|
|
14bc3b56b8 | ||
|
|
255b1cf1de | ||
|
|
d3fc5e990e | ||
|
|
dc082b5ded | ||
|
|
d9c402e20d | ||
|
|
09a1617640 | ||
|
|
cb642c44ba |
5
.github/workflows/go-cross.yml
vendored
5
.github/workflows/go-cross.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [ 1.16, 1.17 ]
|
||||
go-version: [ '1.21', '1.22' ]
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
|
||||
include:
|
||||
@@ -34,6 +34,7 @@ jobs:
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
stable: true
|
||||
|
||||
# https://github.com/marketplace/actions/checkout
|
||||
- name: Checkout code
|
||||
@@ -43,7 +44,7 @@ jobs:
|
||||
|
||||
# https://github.com/marketplace/actions/cache
|
||||
- name: Cache Go modules
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
# In order:
|
||||
# * Module download cache
|
||||
|
||||
12
.github/workflows/main.yml
vendored
12
.github/workflows/main.yml
vendored
@@ -7,8 +7,8 @@ on:
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
GO_VERSION: 1.17
|
||||
GOLANGCI_LINT_VERSION: v1.42.1
|
||||
GO_VERSION: '1.22'
|
||||
GOLANGCI_LINT_VERSION: v1.55.2
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -45,12 +45,13 @@ jobs:
|
||||
needs: linting
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [ 1.16, 1.17 ]
|
||||
go-version: [ '1.21', '1.22' ]
|
||||
steps:
|
||||
- name: Set up Go ${{ matrix.go-version }}
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
stable: true
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
@@ -75,13 +76,14 @@ jobs:
|
||||
working-directory: ${{ github.workspace }}/go/src/github.com/traefik/yaegi
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [ 1.16, 1.17 ]
|
||||
go-version: [ '1.21', '1.22' ]
|
||||
|
||||
steps:
|
||||
- name: Set up Go ${{ matrix.go-version }}
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
stable: true
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
@@ -91,7 +93,7 @@ jobs:
|
||||
|
||||
# https://github.com/marketplace/actions/cache
|
||||
- name: Cache Go modules
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ./_test/tmp
|
||||
key: ${{ runner.os }}-yaegi-${{ hashFiles('**//_test/tmp/') }}
|
||||
|
||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -6,7 +6,7 @@ on:
|
||||
- v[0-9]+.[0-9]+*
|
||||
|
||||
env:
|
||||
GO_VERSION: 1.17
|
||||
GO_VERSION: '1.21'
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Cache Go modules
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
[run]
|
||||
deadline = "5m"
|
||||
skip-files = []
|
||||
|
||||
[linters-settings]
|
||||
|
||||
[linters-settings.govet]
|
||||
check-shadowing = false
|
||||
|
||||
[linters-settings.gocyclo]
|
||||
min-complexity = 12.0
|
||||
|
||||
[linters-settings.maligned]
|
||||
suggest-new = true
|
||||
|
||||
[linters-settings.goconst]
|
||||
min-len = 3.0
|
||||
min-occurrences = 3.0
|
||||
|
||||
[linters-settings.misspell]
|
||||
locale = "US"
|
||||
|
||||
[linters]
|
||||
enable-all = true
|
||||
disable = [
|
||||
"golint", # deprecated
|
||||
"scopelint", # deprecated
|
||||
"interfacer", # deprecated
|
||||
"maligned", # deprecated
|
||||
"lll",
|
||||
"gas",
|
||||
"dupl",
|
||||
"prealloc",
|
||||
"gocyclo",
|
||||
"cyclop",
|
||||
"gochecknoinits",
|
||||
"gochecknoglobals",
|
||||
"wsl",
|
||||
"nlreturn",
|
||||
"godox",
|
||||
"funlen",
|
||||
"gocognit",
|
||||
"stylecheck",
|
||||
"gomnd",
|
||||
"testpackage",
|
||||
"paralleltest",
|
||||
"tparallel",
|
||||
"goerr113",
|
||||
"wrapcheck",
|
||||
"nestif",
|
||||
"exhaustive",
|
||||
"exhaustivestruct",
|
||||
"forbidigo",
|
||||
"ifshort",
|
||||
"forcetypeassert",
|
||||
"errorlint", # TODO: must be reactivate before fixes
|
||||
]
|
||||
|
||||
[issues]
|
||||
exclude-use-default = false
|
||||
max-per-linter = 0
|
||||
max-same-issues = 0
|
||||
exclude = []
|
||||
|
||||
[[issues.exclude-rules]]
|
||||
path = ".+_test\\.go"
|
||||
linters = ["goconst"]
|
||||
[[issues.exclude-rules]]
|
||||
path = ".+_test\\.go"
|
||||
text = "var-declaration:"
|
||||
|
||||
[[issues.exclude-rules]]
|
||||
path = "interp/interp.go"
|
||||
text = "`in` can be `io.Reader`"
|
||||
[[issues.exclude-rules]]
|
||||
path = "interp/interp.go"
|
||||
text = "`out` can be `io.Writer`"
|
||||
[[issues.exclude-rules]]
|
||||
path = "interp/interp.go"
|
||||
text = "`Panic` should conform to the `XxxError` format"
|
||||
[[issues.exclude-rules]]
|
||||
path = "interp/interp_eval_test.go"
|
||||
linters = ["thelper"]
|
||||
165
.golangci.yml
Normal file
165
.golangci.yml
Normal file
@@ -0,0 +1,165 @@
|
||||
run:
|
||||
timeout: 10m
|
||||
skip-files: []
|
||||
|
||||
linters-settings:
|
||||
govet:
|
||||
check-shadowing: false
|
||||
gocyclo:
|
||||
min-complexity: 12
|
||||
maligned:
|
||||
suggest-new: true
|
||||
goconst:
|
||||
min-len: 3
|
||||
min-occurrences: 3
|
||||
funlen:
|
||||
lines: -1
|
||||
statements: 50
|
||||
misspell:
|
||||
locale: US
|
||||
depguard:
|
||||
rules:
|
||||
main:
|
||||
files:
|
||||
- $all
|
||||
allow:
|
||||
- $gostd
|
||||
- github.com/traefik/yaegi
|
||||
tagalign:
|
||||
align: false
|
||||
order:
|
||||
- xml
|
||||
- json
|
||||
- yaml
|
||||
- yml
|
||||
- toml
|
||||
- mapstructure
|
||||
- url
|
||||
godox:
|
||||
keywords:
|
||||
- FIXME
|
||||
gocritic:
|
||||
enabled-tags:
|
||||
- diagnostic
|
||||
- style
|
||||
- performance
|
||||
disabled-checks:
|
||||
- paramTypeCombine # already handle by gofumpt.extra-rules
|
||||
- whyNoLint # already handle by nonolint
|
||||
- unnamedResult
|
||||
- hugeParam
|
||||
- sloppyReassign
|
||||
- rangeValCopy
|
||||
- octalLiteral
|
||||
- ptrToRefParam
|
||||
- appendAssign
|
||||
- ruleguard
|
||||
- httpNoBody
|
||||
- exposedSyncMutex
|
||||
- importShadow # TODO should be fixed
|
||||
- commentedOutCode # TODO should be fixed
|
||||
revive:
|
||||
rules:
|
||||
- name: struct-tag
|
||||
- name: blank-imports
|
||||
- name: context-as-argument
|
||||
- name: context-keys-type
|
||||
- name: dot-imports
|
||||
- name: error-return
|
||||
- name: error-strings
|
||||
- name: error-naming
|
||||
- name: exported
|
||||
disabled: true
|
||||
- name: if-return
|
||||
- name: increment-decrement
|
||||
- name: var-naming
|
||||
- name: var-declaration
|
||||
- name: package-comments
|
||||
disabled: true
|
||||
- name: range
|
||||
- name: receiver-naming
|
||||
- name: time-naming
|
||||
- name: unexported-return
|
||||
- name: indent-error-flow
|
||||
- name: errorf
|
||||
- name: empty-block
|
||||
- name: superfluous-else
|
||||
- name: unused-parameter
|
||||
disabled: true
|
||||
- name: unreachable-code
|
||||
- name: redefines-builtin-id
|
||||
|
||||
linters:
|
||||
enable-all: true
|
||||
disable:
|
||||
- deadcode # deprecated
|
||||
- exhaustivestruct # deprecated
|
||||
- golint # deprecated
|
||||
- ifshort # deprecated
|
||||
- interfacer # deprecated
|
||||
- maligned # deprecated
|
||||
- nosnakecase # deprecated
|
||||
- scopelint # deprecated
|
||||
- structcheck # deprecated
|
||||
- varcheck # deprecated
|
||||
- cyclop # duplicate of gocyclo
|
||||
- sqlclosecheck # not relevant (SQL)
|
||||
- rowserrcheck # not relevant (SQL)
|
||||
- execinquery # not relevant (SQL)
|
||||
- lll
|
||||
- gas
|
||||
- dupl
|
||||
- prealloc
|
||||
- gocyclo
|
||||
- cyclop
|
||||
- gochecknoinits
|
||||
- gochecknoglobals
|
||||
- wsl
|
||||
- nlreturn
|
||||
- godox
|
||||
- funlen
|
||||
- gocognit
|
||||
- stylecheck
|
||||
- gomnd
|
||||
- testpackage
|
||||
- paralleltest
|
||||
- tparallel
|
||||
- goerr113
|
||||
- wrapcheck
|
||||
- nestif
|
||||
- exhaustive
|
||||
- exhaustruct
|
||||
- forbidigo
|
||||
- ifshort
|
||||
- forcetypeassert
|
||||
- varnamelen
|
||||
- nosnakecase
|
||||
- nonamedreturns
|
||||
- nilnil
|
||||
- maintidx
|
||||
- dupword # false positives
|
||||
- errorlint # TODO: must be reactivate after fixes
|
||||
|
||||
issues:
|
||||
exclude-use-default: false
|
||||
max-per-linter: 0
|
||||
max-same-issues: 0
|
||||
exclude: []
|
||||
exclude-rules:
|
||||
- path: .+_test\.go
|
||||
linters:
|
||||
- goconst
|
||||
- path: .+_test\.go
|
||||
text: 'var-declaration:'
|
||||
- path: interp/interp.go
|
||||
text: '`in` can be `io.Reader`'
|
||||
- path: interp/interp.go
|
||||
text: '`out` can be `io.Writer`'
|
||||
- path: interp/interp.go
|
||||
text: '`Panic` should conform to the `XxxError` format'
|
||||
- path: interp/interp_eval_test.go
|
||||
linters:
|
||||
- thelper
|
||||
- path: interp/debugger.go
|
||||
linters:
|
||||
- containedctx
|
||||
13
Makefile
13
Makefile
@@ -27,4 +27,15 @@ tests:
|
||||
install.sh: .goreleaser.yml
|
||||
godownloader --repo=traefik/yaegi -o install.sh .goreleaser.yml
|
||||
|
||||
.PHONY: check gen_all_syscall gen_tests generate_downloader internal/cmd/extract/extract install
|
||||
generic_list = cmp/cmp.go slices/slices.go slices/sort.go slices/zsortanyfunc.go maps/maps.go \
|
||||
sync/oncefunc.go sync/atomic/type.go
|
||||
|
||||
# get_generic_src imports stdlib files containing generic symbols definitions
|
||||
get_generic_src:
|
||||
eval "`go env`"; echo $$GOROOT; gov=$${GOVERSION#*.}; gov=$${gov%.*}; \
|
||||
for f in ${generic_list}; do \
|
||||
nf=stdlib/generic/go1_$${gov}_`echo $$f | tr / _`.txt; echo "nf: $$nf"; \
|
||||
cat "$$GOROOT/src/$$f" > "$$nf"; \
|
||||
done
|
||||
|
||||
.PHONY: check gen_all_syscall internal/cmd/extract/extract get_generic_src install
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
[](https://github.com/traefik/yaegi/releases)
|
||||
[](https://github.com/traefik/yaegi/actions/workflows/main.yml)
|
||||
[](https://pkg.go.dev/mod/github.com/traefik/yaegi)
|
||||
[](https://community.traefik.io/c/yaegi)
|
||||
|
||||
Yaegi is Another Elegant Go Interpreter.
|
||||
It powers executable Go scripts and plugins, in embedded interpreters or interactive shells, on top of the Go runtime.
|
||||
@@ -18,7 +17,7 @@ It powers executable Go scripts and plugins, in embedded interpreters or interac
|
||||
* Works everywhere Go works
|
||||
* All Go & runtime resources accessible from script (with control)
|
||||
* Security: `unsafe` and `syscall` packages neither used nor exported by default
|
||||
* Support Go 1.16 and Go 1.17 (the latest 2 major releases)
|
||||
* Support the latest 2 major releases of Go (Go 1.21 and Go 1.22)
|
||||
|
||||
## Install
|
||||
|
||||
@@ -31,7 +30,7 @@ import "github.com/traefik/yaegi/interp"
|
||||
### Command-line executable
|
||||
|
||||
```bash
|
||||
go get -u github.com/traefik/yaegi/cmd/yaegi
|
||||
go install github.com/traefik/yaegi/cmd/yaegi@latest
|
||||
```
|
||||
|
||||
Note that you can use [rlwrap](https://github.com/hanslub42/rlwrap) (install with your favorite package manager),
|
||||
@@ -173,10 +172,13 @@ Beside the known [bugs] which are supposed to be fixed in the short term, there
|
||||
|
||||
- Assembly files (`.s`) are not supported.
|
||||
- Calling C code is not supported (no virtual "C" package).
|
||||
- Directives about the compiler, the linker, or embedding files are not supported.
|
||||
- Interfaces to be used from the pre-compiled code can not be added dynamically, as it is required to pre-compile interface wrappers.
|
||||
- Representation of types by `reflect` and printing values using %T may give different results between compiled mode and interpreted mode.
|
||||
- Interpreting computation intensive code is likely to remain significantly slower than in compiled mode.
|
||||
|
||||
Go modules are not supported yet. Until that, it is necessary to install the source into `$GOPATH/src/github.com/traefik/yaegi` to pass all the tests.
|
||||
|
||||
## Contributing
|
||||
|
||||
[Contributing guide](CONTRIBUTING.md).
|
||||
|
||||
@@ -15,11 +15,10 @@ func main() {
|
||||
r := extendedRequest{}
|
||||
req := &r.Request
|
||||
|
||||
|
||||
fmt.Println(r)
|
||||
fmt.Println(req)
|
||||
fmt.Printf("%T\n", r.Request)
|
||||
fmt.Printf("%T\n", req)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// {{ <nil> 0 0 map[] <nil> <nil> 0 [] false map[] map[] <nil> map[] <nil> <nil> <nil> <nil>} }
|
||||
// &{ <nil> 0 0 map[] <nil> <nil> 0 [] false map[] map[] <nil> map[] <nil> <nil> <nil> <nil>}
|
||||
// http.Request
|
||||
// *http.Request
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package alias3
|
||||
|
||||
type T struct {
|
||||
A string
|
||||
}
|
||||
|
||||
func (t *T) Print() {
|
||||
println(t.A)
|
||||
}
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Defined an interface of stringBuilder that compatible with
|
||||
// strings.Builder(go 1.10) and bytes.Buffer(< go 1.10)
|
||||
// Define an interface of stringBuilder that is compatible with
|
||||
// strings.Builder(go 1.10) and bytes.Buffer(< go 1.10).
|
||||
type stringBuilder interface {
|
||||
WriteRune(r rune) (n int, err error)
|
||||
WriteString(s string) (int, error)
|
||||
|
||||
16
_test/assert3.go
Normal file
16
_test/assert3.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package main
|
||||
|
||||
import "crypto/rsa"
|
||||
|
||||
func main() {
|
||||
var pKey interface{} = &rsa.PublicKey{}
|
||||
|
||||
if _, ok := pKey.(*rsa.PublicKey); ok {
|
||||
println("ok")
|
||||
} else {
|
||||
println("nok")
|
||||
}
|
||||
}
|
||||
|
||||
// Output:
|
||||
// ok
|
||||
11
_test/assert4.go
Normal file
11
_test/assert4.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package main
|
||||
|
||||
var cc interface{} = 2
|
||||
var dd = cc.(int)
|
||||
|
||||
func main() {
|
||||
println(dd)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// 2
|
||||
21
_test/assign17.go
Normal file
21
_test/assign17.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package main
|
||||
|
||||
func main() {
|
||||
s := make([]map[string]string, 0)
|
||||
m := make(map[string]string)
|
||||
m["m1"] = "m1"
|
||||
m["m2"] = "m2"
|
||||
s = append(s, m)
|
||||
tmpStr := "start"
|
||||
println(tmpStr)
|
||||
for _, v := range s {
|
||||
tmpStr, ok := v["m1"]
|
||||
println(tmpStr, ok)
|
||||
}
|
||||
println(tmpStr)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// start
|
||||
// m1 true
|
||||
// start
|
||||
21
_test/assign18.go
Normal file
21
_test/assign18.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package main
|
||||
|
||||
func main() {
|
||||
s := make([]map[string]string, 0)
|
||||
m := make(map[string]string)
|
||||
m["m1"] = "m1"
|
||||
m["m2"] = "m2"
|
||||
s = append(s, m)
|
||||
tmpStr := "start"
|
||||
println(tmpStr)
|
||||
for _, v := range s {
|
||||
tmpStr, _ := v["m1"]
|
||||
println(tmpStr)
|
||||
}
|
||||
println(tmpStr)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// start
|
||||
// m1
|
||||
// start
|
||||
56
_test/cli7.go
Normal file
56
_test/cli7.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
)
|
||||
|
||||
type T struct {
|
||||
http.ResponseWriter
|
||||
}
|
||||
|
||||
type mw1 struct {
|
||||
}
|
||||
|
||||
var obj = map[string]interface{}{}
|
||||
|
||||
func (m *mw1) ServeHTTP(rw http.ResponseWriter, rq *http.Request) {
|
||||
t := &T{
|
||||
ResponseWriter: rw,
|
||||
}
|
||||
x := t.Header()
|
||||
i := obj["m1"].(*mw1)
|
||||
fmt.Fprint(rw, "Welcome to my website!", x, i)
|
||||
}
|
||||
|
||||
func main() {
|
||||
m1 := &mw1{}
|
||||
|
||||
obj["m1"] = m1
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/", m1.ServeHTTP)
|
||||
|
||||
server := httptest.NewServer(mux)
|
||||
defer server.Close()
|
||||
|
||||
client(server.URL)
|
||||
}
|
||||
|
||||
func client(uri string) {
|
||||
resp, err := http.Get(uri)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println(string(body))
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Welcome to my website!map[] &{}
|
||||
41
_test/cli8.go
Normal file
41
_test/cli8.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
)
|
||||
|
||||
type T struct {
|
||||
name string
|
||||
next http.Handler
|
||||
}
|
||||
|
||||
func (t *T) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
println("in T.ServeHTTP")
|
||||
if t.next != nil {
|
||||
t.next.ServeHTTP(rw, req)
|
||||
}
|
||||
}
|
||||
|
||||
func New(name string, next http.Handler) (http.Handler, error) { return &T{name, next}, nil }
|
||||
|
||||
func main() {
|
||||
next := func(rw http.ResponseWriter, req *http.Request) {
|
||||
println("in next")
|
||||
}
|
||||
|
||||
t, err := New("test", http.HandlerFunc(next))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
t.ServeHTTP(recorder, req)
|
||||
println(recorder.Result().Status)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// in T.ServeHTTP
|
||||
// in next
|
||||
// 200 OK
|
||||
36
_test/closure13.go
Normal file
36
_test/closure13.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type monkey struct {
|
||||
test func() int
|
||||
}
|
||||
|
||||
func main() {
|
||||
input := []string{"1", "2", "3"}
|
||||
|
||||
var monkeys []*monkey
|
||||
|
||||
for _, v := range input {
|
||||
kong := monkey{}
|
||||
divisor, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Print(divisor, " ")
|
||||
kong.test = func() int {
|
||||
return divisor
|
||||
}
|
||||
monkeys = append(monkeys, &kong)
|
||||
}
|
||||
|
||||
for _, mk := range monkeys {
|
||||
fmt.Print(mk.test(), " ")
|
||||
}
|
||||
}
|
||||
|
||||
// Output:
|
||||
// 1 2 3 1 2 3
|
||||
32
_test/closure14.go
Normal file
32
_test/closure14.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type monkey struct {
|
||||
test func() int
|
||||
}
|
||||
|
||||
func getk(k int) (int, error) { return k, nil }
|
||||
|
||||
func main() {
|
||||
input := []string{"1", "2", "3"}
|
||||
|
||||
var monkeys []*monkey
|
||||
|
||||
for k := range input {
|
||||
kong := monkey{}
|
||||
divisor, _ := getk(k)
|
||||
fmt.Print(divisor, " ")
|
||||
kong.test = func() int {
|
||||
return divisor
|
||||
}
|
||||
monkeys = append(monkeys, &kong)
|
||||
}
|
||||
|
||||
for _, mk := range monkeys {
|
||||
fmt.Print(mk.test(), " ")
|
||||
}
|
||||
}
|
||||
|
||||
// Output:
|
||||
// 0 1 2 0 1 2
|
||||
18
_test/convert3.go
Normal file
18
_test/convert3.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func main() {
|
||||
next := func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.Header().Set("Cache-Control", "max-age=20")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
}
|
||||
f := http.HandlerFunc(next)
|
||||
fmt.Printf("%T\n", f.ServeHTTP)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// func(http.ResponseWriter, *http.Request)
|
||||
25
_test/fun28.go
Normal file
25
_test/fun28.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
)
|
||||
|
||||
type T struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func finalize(t *T) { println("finalize") }
|
||||
|
||||
func newT() *T {
|
||||
t := new(T)
|
||||
runtime.SetFinalizer(t, finalize)
|
||||
return t
|
||||
}
|
||||
|
||||
func main() {
|
||||
t := newT()
|
||||
println(t != nil)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// true
|
||||
39
_test/gen1.go
Normal file
39
_test/gen1.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
// SumInts adds together the values of m.
|
||||
func SumInts(m map[string]int64) int64 {
|
||||
var s int64
|
||||
for _, v := range m {
|
||||
s += v
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// SumFloats adds together the values of m.
|
||||
func SumFloats(m map[string]float64) float64 {
|
||||
var s float64
|
||||
for _, v := range m {
|
||||
s += v
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Initialize a map for the integer values
|
||||
ints := map[string]int64{
|
||||
"first": 34,
|
||||
"second": 12,
|
||||
}
|
||||
|
||||
// Initialize a map for the float values
|
||||
floats := map[string]float64{
|
||||
"first": 35.98,
|
||||
"second": 26.99,
|
||||
}
|
||||
|
||||
fmt.Printf("Non-Generic Sums: %v and %v\n",
|
||||
SumInts(ints),
|
||||
SumFloats(floats))
|
||||
}
|
||||
12
_test/gen10.go
Normal file
12
_test/gen10.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package main
|
||||
|
||||
func genFunc() (f func()) {
|
||||
return f
|
||||
}
|
||||
|
||||
func main() {
|
||||
println(genFunc() == nil)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// true
|
||||
33
_test/gen11.go
Normal file
33
_test/gen11.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
type Slice[T any] struct {
|
||||
x []T
|
||||
}
|
||||
|
||||
type IPPrefixSlice struct {
|
||||
x Slice[netip.Prefix]
|
||||
}
|
||||
|
||||
func (v Slice[T]) MarshalJSON() ([]byte, error) { return json.Marshal(v.x) }
|
||||
|
||||
// MarshalJSON implements json.Marshaler.
|
||||
func (v IPPrefixSlice) MarshalJSON() ([]byte, error) {
|
||||
return v.x.MarshalJSON()
|
||||
}
|
||||
|
||||
func main() {
|
||||
t := IPPrefixSlice{}
|
||||
fmt.Println(t)
|
||||
b, e := t.MarshalJSON()
|
||||
fmt.Println(string(b), e)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// {{[]}}
|
||||
// null <nil>
|
||||
31
_test/gen12.go
Normal file
31
_test/gen12.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func MapOf[K comparable, V any](m map[K]V) Map[K, V] {
|
||||
return Map[K, V]{m}
|
||||
}
|
||||
|
||||
type Map[K comparable, V any] struct {
|
||||
ж map[K]V
|
||||
}
|
||||
|
||||
func (v MapView) Int() Map[string, int] { return MapOf(v.ж.Int) }
|
||||
|
||||
type VMap struct {
|
||||
Int map[string]int
|
||||
}
|
||||
|
||||
type MapView struct {
|
||||
ж *VMap
|
||||
}
|
||||
|
||||
func main() {
|
||||
mv := MapView{&VMap{}}
|
||||
fmt.Println(mv.ж)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// &{map[]}
|
||||
18
_test/gen13.go
Normal file
18
_test/gen13.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package main
|
||||
|
||||
type Map[K comparable, V any] struct {
|
||||
ж map[K]V
|
||||
}
|
||||
|
||||
func (m Map[K, V]) Has(k K) bool {
|
||||
_, ok := m.ж[k]
|
||||
return ok
|
||||
}
|
||||
|
||||
func main() {
|
||||
m := Map[string, float64]{}
|
||||
println(m.Has("test"))
|
||||
}
|
||||
|
||||
// Output:
|
||||
// false
|
||||
34
_test/gen2.go
Normal file
34
_test/gen2.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
// SumIntsOrFloats sums the values of map m. It supports both int64 and float64
|
||||
// as types for map values.
|
||||
func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
|
||||
var s V
|
||||
for _, v := range m {
|
||||
s += v
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Initialize a map for the integer values
|
||||
ints := map[string]int64{
|
||||
"first": 34,
|
||||
"second": 12,
|
||||
}
|
||||
|
||||
// Initialize a map for the float values
|
||||
floats := map[string]float64{
|
||||
"first": 35.98,
|
||||
"second": 26.99,
|
||||
}
|
||||
|
||||
fmt.Printf("Generic Sums: %v and %v\n",
|
||||
SumIntsOrFloats[string, int64](ints),
|
||||
SumIntsOrFloats[string, float64](floats))
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Generic Sums: 46 and 62.97
|
||||
22
_test/gen3.go
Normal file
22
_test/gen3.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package main
|
||||
|
||||
type Number interface {
|
||||
int | int64 | ~float64
|
||||
}
|
||||
|
||||
func Sum[T Number](numbers []T) T {
|
||||
var total T
|
||||
for _, x := range numbers {
|
||||
total += x
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
func main() {
|
||||
xs := []int{3, 5, 10}
|
||||
total := Sum(xs)
|
||||
println(total)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// 18
|
||||
42
_test/gen4.go
Normal file
42
_test/gen4.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type List[T any] struct {
|
||||
head, tail *element[T]
|
||||
}
|
||||
|
||||
// A recursive generic type.
|
||||
type element[T any] struct {
|
||||
next *element[T]
|
||||
val T
|
||||
}
|
||||
|
||||
func (lst *List[T]) Push(v T) {
|
||||
if lst.tail == nil {
|
||||
lst.head = &element[T]{val: v}
|
||||
lst.tail = lst.head
|
||||
} else {
|
||||
lst.tail.next = &element[T]{val: v}
|
||||
lst.tail = lst.tail.next
|
||||
}
|
||||
}
|
||||
|
||||
func (lst *List[T]) GetAll() []T {
|
||||
var elems []T
|
||||
for e := lst.head; e != nil; e = e.next {
|
||||
elems = append(elems, e.val)
|
||||
}
|
||||
return elems
|
||||
}
|
||||
|
||||
func main() {
|
||||
lst := List[int]{}
|
||||
lst.Push(10)
|
||||
lst.Push(13)
|
||||
lst.Push(23)
|
||||
fmt.Println("list:", lst.GetAll())
|
||||
}
|
||||
|
||||
// Output:
|
||||
// list: [10 13 23]
|
||||
24
_test/gen5.go
Normal file
24
_test/gen5.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Set[Elem comparable] struct {
|
||||
m map[Elem]struct{}
|
||||
}
|
||||
|
||||
func Make[Elem comparable]() Set[Elem] {
|
||||
return Set[Elem]{m: make(map[Elem]struct{})}
|
||||
}
|
||||
|
||||
func (s Set[Elem]) Add(v Elem) {
|
||||
s.m[v] = struct{}{}
|
||||
}
|
||||
|
||||
func main() {
|
||||
s := Make[int]()
|
||||
s.Add(1)
|
||||
fmt.Println(s)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// {map[1:{}]}
|
||||
19
_test/gen6.go
Normal file
19
_test/gen6.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package main
|
||||
|
||||
func MapKeys[K comparable, V any](m map[K]V) []K {
|
||||
r := make([]K, 0, len(m))
|
||||
for k := range m {
|
||||
r = append(r, k)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func main() {
|
||||
var m = map[int]string{1: "2", 2: "4", 4: "8"}
|
||||
|
||||
// Test type inference
|
||||
println(len(MapKeys(m)))
|
||||
}
|
||||
|
||||
// Output:
|
||||
// 3
|
||||
19
_test/gen7.go
Normal file
19
_test/gen7.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package main
|
||||
|
||||
func MapKeys[K comparable, V any](m map[K]V) []K {
|
||||
r := make([]K, 0, len(m))
|
||||
for k := range m {
|
||||
r = append(r, k)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func main() {
|
||||
var m = map[int]string{1: "2", 2: "4", 4: "8"}
|
||||
|
||||
// Test type inference
|
||||
println(len(MapKeys))
|
||||
}
|
||||
|
||||
// Error:
|
||||
// invalid argument for len
|
||||
15
_test/gen8.go
Normal file
15
_test/gen8.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package main
|
||||
|
||||
type Float interface {
|
||||
~float32 | ~float64
|
||||
}
|
||||
|
||||
func add[T Float](a, b T) float64 { return float64(a) + float64(b) }
|
||||
|
||||
func main() {
|
||||
var x, y int = 1, 2
|
||||
println(add(x, y))
|
||||
}
|
||||
|
||||
// Error:
|
||||
// int does not implement main.Float
|
||||
14
_test/gen9.go
Normal file
14
_test/gen9.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package main
|
||||
|
||||
type Float interface {
|
||||
~float32 | ~float64
|
||||
}
|
||||
|
||||
func add[T Float](a, b T) float64 { return float64(a) + float64(b) }
|
||||
|
||||
func main() {
|
||||
println(add(1, 2))
|
||||
}
|
||||
|
||||
// Error:
|
||||
// untyped int does not implement main.Float
|
||||
17
_test/issue-1416.go
Normal file
17
_test/issue-1416.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package main
|
||||
|
||||
type Number int32
|
||||
|
||||
type Number1 = Number
|
||||
|
||||
type Number2 = Number1
|
||||
|
||||
func (n Number2) IsValid() bool { return true }
|
||||
|
||||
func main() {
|
||||
a := Number(5)
|
||||
println(a.IsValid())
|
||||
}
|
||||
|
||||
// Output:
|
||||
// true
|
||||
9
_test/issue-1421.go
Normal file
9
_test/issue-1421.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package main
|
||||
|
||||
type Number = int
|
||||
|
||||
func main() {
|
||||
println(Number(1) < int(2))
|
||||
}
|
||||
|
||||
// Output: true
|
||||
44
_test/issue-1425.go
Normal file
44
_test/issue-1425.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type WrappedReader struct {
|
||||
reader io.Reader
|
||||
}
|
||||
|
||||
func (wr WrappedReader) Read(p []byte) (n int, err error) {
|
||||
return wr.reader.Read(p)
|
||||
}
|
||||
|
||||
// Of course, this implementation is completely stupid because it does not write
|
||||
// to the intended writer, as any honest WriteTo implementation should. its
|
||||
// implemtion is just to make obvious the divergence of behaviour with yaegi.
|
||||
func (wr WrappedReader) WriteTo(w io.Writer) (n int64, err error) {
|
||||
// Ignore w, send to Stdout to prove whether this WriteTo is used.
|
||||
data, err := io.ReadAll(wr)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
nn, err := os.Stdout.Write(data)
|
||||
return int64(nn), err
|
||||
}
|
||||
|
||||
func main() {
|
||||
f := strings.NewReader("hello world")
|
||||
wr := WrappedReader{reader: f}
|
||||
|
||||
// behind the scenes, io.Copy is supposed to use wr.WriteTo if the implementation exists.
|
||||
// With Go, it works as expected, i.e. the output is sent to os.Stdout.
|
||||
// With Yaegi, it doesn't, i.e. the output is sent to io.Discard.
|
||||
if _, err := io.Copy(io.Discard, wr); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Output:
|
||||
// hello world
|
||||
25
_test/issue-1439.go
Normal file
25
_test/issue-1439.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package main
|
||||
|
||||
type Transformer interface {
|
||||
Reset()
|
||||
}
|
||||
|
||||
type Encoder struct {
|
||||
Transformer
|
||||
}
|
||||
|
||||
type nop struct{}
|
||||
|
||||
func (nop) Reset() { println("Reset") }
|
||||
|
||||
func f(e Transformer) {
|
||||
e.Reset()
|
||||
}
|
||||
|
||||
func main() {
|
||||
e := Encoder{Transformer: nop{}}
|
||||
f(e)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Reset
|
||||
41
_test/issue-1442.go
Normal file
41
_test/issue-1442.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx, _ := context.WithCancel(context.Background())
|
||||
ch := make(chan string, 20)
|
||||
defer close(ch)
|
||||
|
||||
go func(ctx context.Context, ch <-chan string) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case tmp := <-ch:
|
||||
_ = tmp
|
||||
}
|
||||
}
|
||||
}(ctx, ch)
|
||||
|
||||
for _, i := range "abcdef" {
|
||||
for _, j := range "0123456789" {
|
||||
// i, j := "a", "0"
|
||||
for _, k := range "ABCDEF" {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
tmp := string(i) + string(j) + string(k)
|
||||
ch <- tmp
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Output:
|
||||
//
|
||||
20
_test/issue-1447.go
Normal file
20
_test/issue-1447.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type I interface {
|
||||
Name() string
|
||||
}
|
||||
|
||||
type S struct {
|
||||
iMap map[string]I
|
||||
}
|
||||
|
||||
func main() {
|
||||
s := S{}
|
||||
s.iMap = map[string]I{}
|
||||
fmt.Println(s)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// {map[]}
|
||||
19
_test/issue-1451.go
Normal file
19
_test/issue-1451.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package main
|
||||
|
||||
type t1 uint8
|
||||
|
||||
const (
|
||||
n1 t1 = iota
|
||||
n2
|
||||
)
|
||||
|
||||
type T struct {
|
||||
elem [n2 + 1]int
|
||||
}
|
||||
|
||||
func main() {
|
||||
println(len(T{}.elem))
|
||||
}
|
||||
|
||||
// Output:
|
||||
// 2
|
||||
22
_test/issue-1454.go
Normal file
22
_test/issue-1454.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package main
|
||||
|
||||
type I2 interface {
|
||||
I2() string
|
||||
}
|
||||
|
||||
type I interface {
|
||||
I2
|
||||
}
|
||||
|
||||
type S struct{}
|
||||
|
||||
func (*S) I2() string { return "foo" }
|
||||
|
||||
func main() {
|
||||
var i I
|
||||
_, ok := i.(*S)
|
||||
println(ok)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// false
|
||||
22
_test/issue-1459.go
Normal file
22
_test/issue-1459.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type funclistItem func()
|
||||
|
||||
type funclist struct {
|
||||
list []funclistItem
|
||||
}
|
||||
|
||||
func main() {
|
||||
funcs := funclist{}
|
||||
|
||||
funcs.list = append(funcs.list, func() { fmt.Println("first") })
|
||||
|
||||
for _, f := range funcs.list {
|
||||
f()
|
||||
}
|
||||
}
|
||||
|
||||
// Output:
|
||||
// first
|
||||
84
_test/issue-1460.go
Normal file
84
_test/issue-1460.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/netip"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func unmarshalJSON[T any](b []byte, x *[]T) error {
|
||||
if *x != nil {
|
||||
return errors.New("already initialized")
|
||||
}
|
||||
if len(b) == 0 {
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal(b, x)
|
||||
}
|
||||
|
||||
func SliceOfViews[T ViewCloner[T, V], V StructView[T]](x []T) SliceView[T, V] {
|
||||
return SliceView[T, V]{x}
|
||||
}
|
||||
|
||||
type StructView[T any] interface {
|
||||
Valid() bool
|
||||
AsStruct() T
|
||||
}
|
||||
|
||||
type SliceView[T ViewCloner[T, V], V StructView[T]] struct {
|
||||
ж []T
|
||||
}
|
||||
|
||||
type ViewCloner[T any, V StructView[T]] interface {
|
||||
View() V
|
||||
Clone() T
|
||||
}
|
||||
|
||||
func (v SliceView[T, V]) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
|
||||
|
||||
func (v *SliceView[T, V]) UnmarshalJSON(b []byte) error { return unmarshalJSON(b, &v.ж) }
|
||||
|
||||
type Slice[T any] struct {
|
||||
ж []T
|
||||
}
|
||||
|
||||
func (v Slice[T]) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
|
||||
|
||||
func (v *Slice[T]) UnmarshalJSON(b []byte) error { return unmarshalJSON(b, &v.ж) }
|
||||
|
||||
func SliceOf[T any](x []T) Slice[T] {
|
||||
return Slice[T]{x}
|
||||
}
|
||||
|
||||
type IPPrefixSlice struct {
|
||||
ж Slice[netip.Prefix]
|
||||
}
|
||||
|
||||
type viewStruct struct {
|
||||
Int int
|
||||
Strings Slice[string]
|
||||
StringsPtr *Slice[string] `json:",omitempty"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
ss := SliceOf([]string{"bar"})
|
||||
in := viewStruct{
|
||||
Int: 1234,
|
||||
Strings: ss,
|
||||
StringsPtr: &ss,
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
encoder := json.NewEncoder(&buf)
|
||||
encoder.SetIndent("", "")
|
||||
err1 := encoder.Encode(&in)
|
||||
b := buf.Bytes()
|
||||
var got viewStruct
|
||||
err2 := json.Unmarshal(b, &got)
|
||||
println(err1 == nil, err2 == nil, reflect.DeepEqual(got, in))
|
||||
}
|
||||
|
||||
// Output:
|
||||
// true true true
|
||||
24
_test/issue-1465.go
Normal file
24
_test/issue-1465.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func SomeFunc[T int | string](defaultValue T) T {
|
||||
switch v := any(&defaultValue).(type) {
|
||||
case *string:
|
||||
*v = *v + " abc"
|
||||
case *int:
|
||||
*v -= 234
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Println(SomeFunc("test"))
|
||||
fmt.Println(SomeFunc(1234))
|
||||
}
|
||||
|
||||
// Output:
|
||||
// test abc
|
||||
// 1000
|
||||
24
_test/issue-1466.go
Normal file
24
_test/issue-1466.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func SomeFunc(defaultValue interface{}) interface{} {
|
||||
switch v := defaultValue.(type) {
|
||||
case string:
|
||||
return v + " abc"
|
||||
case int:
|
||||
return v - 234
|
||||
}
|
||||
panic("whoops")
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Println(SomeFunc(1234))
|
||||
fmt.Println(SomeFunc("test"))
|
||||
}
|
||||
|
||||
// Output:
|
||||
// 1000
|
||||
// test abc
|
||||
15
_test/issue-1470.go
Normal file
15
_test/issue-1470.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package main
|
||||
|
||||
type T struct {
|
||||
num [tnum + 2]int
|
||||
}
|
||||
|
||||
const tnum = 23
|
||||
|
||||
func main() {
|
||||
t := T{}
|
||||
println(len(t.num))
|
||||
}
|
||||
|
||||
// Output:
|
||||
// 25
|
||||
12
_test/issue-1475.go
Normal file
12
_test/issue-1475.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package main
|
||||
|
||||
type T uint16
|
||||
|
||||
func f() T { return 0 }
|
||||
|
||||
func main() {
|
||||
println(f())
|
||||
}
|
||||
|
||||
// Output:
|
||||
// 0
|
||||
23
_test/issue-1488.go
Normal file
23
_test/issue-1488.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type vector interface {
|
||||
[]int | [3]int
|
||||
}
|
||||
|
||||
func sum[V vector](v V) (out int) {
|
||||
for i := 0; i < len(v); i++ {
|
||||
out += v[i]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func main() {
|
||||
va := [3]int{1, 2, 3}
|
||||
vs := []int{1, 2, 3}
|
||||
fmt.Println(sum[[3]int](va), sum[[]int](vs))
|
||||
}
|
||||
|
||||
// Output:
|
||||
// 6 6
|
||||
16
_test/issue-1496.go
Normal file
16
_test/issue-1496.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package main
|
||||
|
||||
func main() {
|
||||
a := []byte{} == nil
|
||||
b := nil == []byte{}
|
||||
c := nil == &struct{}{}
|
||||
i := 100
|
||||
d := nil == &i
|
||||
var v interface{}
|
||||
f := nil == v
|
||||
g := v == nil
|
||||
println(a, b, c, d, f, g)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// false false false false true true
|
||||
43
_test/issue-1515.go
Normal file
43
_test/issue-1515.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package main
|
||||
|
||||
type I1 interface {
|
||||
I2
|
||||
Wrap() *S3
|
||||
}
|
||||
|
||||
type I2 interface {
|
||||
F()
|
||||
}
|
||||
|
||||
type S2 struct {
|
||||
I2
|
||||
}
|
||||
|
||||
func newS2(i2 I2) I1 {
|
||||
return &S2{i2}
|
||||
}
|
||||
|
||||
type S3 struct {
|
||||
base *S2
|
||||
}
|
||||
|
||||
func (s *S2) Wrap() *S3 {
|
||||
i2 := s
|
||||
return &S3{i2}
|
||||
}
|
||||
|
||||
type T struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func (t *T) F() { println("in F", t.name) }
|
||||
|
||||
func main() {
|
||||
t := &T{"test"}
|
||||
s2 := newS2(t)
|
||||
s3 := s2.Wrap()
|
||||
s3.base.F()
|
||||
}
|
||||
|
||||
// Output:
|
||||
// in F test
|
||||
15
_test/issue-1536.go
Normal file
15
_test/issue-1536.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package main
|
||||
|
||||
var a [len(prefix+path) + 2]int
|
||||
|
||||
const (
|
||||
prefix = "/usr/"
|
||||
path = prefix + "local/bin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
println(len(a))
|
||||
}
|
||||
|
||||
// Output:
|
||||
// 21
|
||||
20
_test/issue-1571.go
Normal file
20
_test/issue-1571.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package main
|
||||
|
||||
type A struct {
|
||||
*B[string]
|
||||
}
|
||||
|
||||
type B[T any] struct {
|
||||
data T
|
||||
}
|
||||
|
||||
func main() {
|
||||
_ = &A{
|
||||
B: &B[string]{},
|
||||
}
|
||||
|
||||
println("PASS")
|
||||
}
|
||||
|
||||
// Output:
|
||||
// PASS
|
||||
17
_test/issue-1594.go
Normal file
17
_test/issue-1594.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package main
|
||||
|
||||
func main() {
|
||||
var fns []func()
|
||||
for _, v := range []int{1, 2, 3} {
|
||||
x := v*100 + v
|
||||
fns = append(fns, func() { println(x) })
|
||||
}
|
||||
for _, fn := range fns {
|
||||
fn()
|
||||
}
|
||||
}
|
||||
|
||||
// Output:
|
||||
// 101
|
||||
// 202
|
||||
// 303
|
||||
32
_test/method40.go
Normal file
32
_test/method40.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
)
|
||||
|
||||
type TMemoryBuffer struct {
|
||||
*bytes.Buffer
|
||||
size int
|
||||
}
|
||||
|
||||
func newTMemoryBuffer() *TMemoryBuffer {
|
||||
return &TMemoryBuffer{}
|
||||
}
|
||||
|
||||
var globalMemoryBuffer = newTMemoryBuffer()
|
||||
|
||||
type TTransport interface {
|
||||
io.ReadWriter
|
||||
}
|
||||
|
||||
func check(t TTransport) {
|
||||
println("ok")
|
||||
}
|
||||
|
||||
func main() {
|
||||
check(globalMemoryBuffer)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// ok
|
||||
@@ -1,6 +1,6 @@
|
||||
package main
|
||||
|
||||
import "github.com/traefik/yaegi/_test/alias3"
|
||||
import "github.com/traefik/yaegi/_test/named3"
|
||||
|
||||
var globalT *T
|
||||
|
||||
@@ -8,10 +8,10 @@ func init() {
|
||||
globalT = &T{A: "test"}
|
||||
}
|
||||
|
||||
type T alias3.T
|
||||
type T named3.T
|
||||
|
||||
func (t *T) PrintT() {
|
||||
(*alias3.T)(t).Print()
|
||||
(*named3.T)(t).Print()
|
||||
}
|
||||
|
||||
func main() {
|
||||
31
_test/named3/named3.go
Normal file
31
_test/named3/named3.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package named3
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type T struct {
|
||||
A string
|
||||
}
|
||||
|
||||
func (t *T) Print() {
|
||||
println(t.A)
|
||||
}
|
||||
|
||||
type A http.Header
|
||||
|
||||
func (a A) ForeachKey() error {
|
||||
for k, vals := range a {
|
||||
for _, v := range vals {
|
||||
fmt.Println(k, v)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a A) Set(k string, v []string) {
|
||||
a[k] = v
|
||||
}
|
||||
@@ -17,11 +17,23 @@ func (b B) Test2() {
|
||||
fmt.Println("test2")
|
||||
}
|
||||
|
||||
func (b B) Test3() {
|
||||
for k, vals := range b {
|
||||
for _, v := range vals {
|
||||
fmt.Println(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
b := B{}
|
||||
|
||||
b.Test2()
|
||||
b["test"] = []string{"a", "b"}
|
||||
b.Test3()
|
||||
}
|
||||
|
||||
// Output:
|
||||
// test2
|
||||
// test a
|
||||
// test b
|
||||
3
_test/p4/p4.go
Normal file
3
_test/p4/p4.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package p4
|
||||
|
||||
var Value1 = "value1"
|
||||
10
_test/p5.go
Normal file
10
_test/p5.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package main
|
||||
|
||||
import "github.com/traefik/yaegi/_test/p5"
|
||||
|
||||
func main() {
|
||||
println(*p5.Value1)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// value1
|
||||
8
_test/p5/p5.go
Normal file
8
_test/p5/p5.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package p5
|
||||
|
||||
import "github.com/traefik/yaegi/_test/p4"
|
||||
|
||||
var (
|
||||
Value1 = &val1
|
||||
val1 = p4.Value1
|
||||
)
|
||||
14
_test/p6.go
Normal file
14
_test/p6.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/traefik/yaegi/_test/p6"
|
||||
)
|
||||
|
||||
func main() {
|
||||
t := p6.IPPrefixSlice{}
|
||||
fmt.Println(t)
|
||||
b, e := t.MarshalJSON()
|
||||
fmt.Println(string(b), e)
|
||||
}
|
||||
21
_test/p6/p6.go
Normal file
21
_test/p6/p6.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package p6
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
type Slice[T any] struct {
|
||||
x []T
|
||||
}
|
||||
|
||||
type IPPrefixSlice struct {
|
||||
x Slice[netip.Prefix]
|
||||
}
|
||||
|
||||
func (v Slice[T]) MarshalJSON() ([]byte, error) { return json.Marshal(v.x) }
|
||||
|
||||
// MarshalJSON implements json.Marshaler.
|
||||
func (v IPPrefixSlice) MarshalJSON() ([]byte, error) {
|
||||
return v.x.MarshalJSON()
|
||||
}
|
||||
20
_test/panic0.go
Normal file
20
_test/panic0.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package main
|
||||
|
||||
func main() {
|
||||
foo()
|
||||
}
|
||||
|
||||
func foo() {
|
||||
bar()
|
||||
}
|
||||
|
||||
func bar() {
|
||||
baz()
|
||||
}
|
||||
|
||||
func baz() {
|
||||
panic("stop!")
|
||||
}
|
||||
|
||||
// Error:
|
||||
// stop!
|
||||
16
_test/recurse1.go
Normal file
16
_test/recurse1.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package main
|
||||
|
||||
type F func(a *A)
|
||||
|
||||
type A struct {
|
||||
Name string
|
||||
F
|
||||
}
|
||||
|
||||
func main() {
|
||||
a := &A{"Test", func(a *A) { println("in f", a.Name) }}
|
||||
a.F(a)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// in f Test
|
||||
27
_test/recurse2.go
Normal file
27
_test/recurse2.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package main
|
||||
|
||||
type F func(a *A)
|
||||
|
||||
type A struct {
|
||||
B string
|
||||
D
|
||||
f F
|
||||
}
|
||||
|
||||
type D struct {
|
||||
*A
|
||||
E *A
|
||||
}
|
||||
|
||||
func f1(a *A) { println("in f1", a.B) }
|
||||
|
||||
func main() {
|
||||
a := &A{B: "b", f: f1}
|
||||
a.D = D{E: a}
|
||||
println(a.D.E.B)
|
||||
a.f(a)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// b
|
||||
// in f1 b
|
||||
25
_test/recurse3.go
Normal file
25
_test/recurse3.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package main
|
||||
|
||||
type F func(a *A)
|
||||
|
||||
type A struct {
|
||||
B string
|
||||
D
|
||||
}
|
||||
|
||||
type D struct {
|
||||
*A
|
||||
E *A
|
||||
f F
|
||||
}
|
||||
|
||||
func f1(a *A) { println("in f1", a.B) }
|
||||
|
||||
func main() {
|
||||
a := &A{B: "b"}
|
||||
a.D = D{f: f1}
|
||||
a.f(a)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// in f1 b
|
||||
@@ -10,11 +10,11 @@ func main() {
|
||||
c2 := make(chan string)
|
||||
|
||||
go func() {
|
||||
time.Sleep(1e7)
|
||||
time.Sleep(1e8)
|
||||
c1 <- "one"
|
||||
}()
|
||||
go func() {
|
||||
time.Sleep(2e7)
|
||||
time.Sleep(2e8)
|
||||
c2 <- "two"
|
||||
}()
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
period = 100 * time.Millisecond
|
||||
precision = 7 * time.Millisecond
|
||||
period = 300 * time.Millisecond
|
||||
precision = 30 * time.Millisecond
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -25,7 +25,7 @@ func main() {
|
||||
}
|
||||
s.ts["test"] = append(s.ts["test"], &T{s: s})
|
||||
|
||||
t , ok:= s.getT("test")
|
||||
t, ok := s.getT("test")
|
||||
println(t != nil, ok)
|
||||
}
|
||||
|
||||
|
||||
17
_test/switch39.go
Normal file
17
_test/switch39.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package main
|
||||
|
||||
func f(params ...interface{}) {
|
||||
switch p0 := params[0].(type) {
|
||||
case string:
|
||||
println("string:", p0)
|
||||
default:
|
||||
println("not a string")
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
f("Hello")
|
||||
}
|
||||
|
||||
// Output:
|
||||
// string: Hello
|
||||
17
_test/switch40.go
Normal file
17
_test/switch40.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package main
|
||||
|
||||
func f(params ...interface{}) {
|
||||
switch params[0].(type) {
|
||||
case string:
|
||||
println("a string")
|
||||
default:
|
||||
println("not a string")
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
f("Hello")
|
||||
}
|
||||
|
||||
// Output:
|
||||
// a string
|
||||
@@ -43,6 +43,6 @@ func assertValue() {
|
||||
}
|
||||
|
||||
// Output:
|
||||
// interface conversion: interface {} is int, not string
|
||||
// interface conversion: interface {} is nil, not string
|
||||
// interface conversion: *httptest.ResponseRecorder is not http.Pusher: missing method Push
|
||||
// 22:10: interface conversion: interface {} is int, not string
|
||||
// 32:10: interface conversion: interface {} is nil, not string
|
||||
// 42:10: interface conversion: *httptest.ResponseRecorder is not http.Pusher: missing method Push
|
||||
|
||||
17
_test/unsafe10.go
Normal file
17
_test/unsafe10.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package main
|
||||
|
||||
import "unsafe"
|
||||
|
||||
type T struct {
|
||||
X uint64
|
||||
Y uint64
|
||||
}
|
||||
|
||||
func f(off uintptr) { println(off) }
|
||||
|
||||
func main() {
|
||||
f(unsafe.Offsetof(T{}.Y))
|
||||
}
|
||||
|
||||
// Output:
|
||||
// 8
|
||||
@@ -21,3 +21,6 @@ func main() {
|
||||
|
||||
fmt.Println(i)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// 5
|
||||
|
||||
18
_test/unsafe8.go
Normal file
18
_test/unsafe8.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package main
|
||||
|
||||
import "unsafe"
|
||||
|
||||
type T struct {
|
||||
i uint64
|
||||
}
|
||||
|
||||
var d T
|
||||
|
||||
var b [unsafe.Sizeof(d)]byte
|
||||
|
||||
func main() {
|
||||
println(len(b))
|
||||
}
|
||||
|
||||
// Output:
|
||||
// 8
|
||||
@@ -69,7 +69,7 @@ func extractCmd(arg []string) error {
|
||||
ext.Include = strings.Split(include, ",")
|
||||
}
|
||||
|
||||
r := strings.NewReplacer("/", "-", ".", "_")
|
||||
r := strings.NewReplacer("/", "-", ".", "_", "~", "_")
|
||||
|
||||
for _, pkgIdent := range args {
|
||||
var buf bytes.Buffer
|
||||
|
||||
@@ -37,17 +37,7 @@ func applyCIMultiplier(timeout time.Duration) time.Duration {
|
||||
}
|
||||
|
||||
func TestYaegiCmdCancel(t *testing.T) {
|
||||
tmp, err := os.MkdirTemp("", "yaegi-")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create tmp directory: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
err = os.RemoveAll(tmp)
|
||||
if err != nil {
|
||||
t.Errorf("failed to clean up %v: %v", tmp, err)
|
||||
}
|
||||
}()
|
||||
|
||||
tmp := t.TempDir()
|
||||
yaegi := filepath.Join(tmp, "yaegi")
|
||||
|
||||
args := []string{"build"}
|
||||
@@ -125,6 +115,12 @@ func TestYaegiCmdCancel(t *testing.T) {
|
||||
}
|
||||
|
||||
func raceDetectorSupported(goos, goarch string) bool {
|
||||
if strings.Contains(os.Getenv("GOFLAGS"), "-buildmode=pie") {
|
||||
// The Go race detector is not compatible with position independent code (pie).
|
||||
// We read the conventional GOFLAGS env variable used for example on AlpineLinux
|
||||
// to build packages, as there is no way to get this information from the runtime.
|
||||
return false
|
||||
}
|
||||
switch goos {
|
||||
case "linux":
|
||||
return goarch == "amd64" || goarch == "ppc64le" || goarch == "arm64"
|
||||
|
||||
@@ -2,9 +2,7 @@ package fs1
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
// only available from 1.16.
|
||||
"testing/fstest"
|
||||
"testing/fstest" // only available from 1.16.
|
||||
|
||||
"github.com/traefik/yaegi/interp"
|
||||
"github.com/traefik/yaegi/stdlib"
|
||||
|
||||
@@ -128,7 +128,7 @@ func TestPackages(t *testing.T) {
|
||||
if test.topImport != "" {
|
||||
topImport = test.topImport
|
||||
}
|
||||
if _, err = i.Eval(fmt.Sprintf(`import "%s"`, topImport)); err != nil {
|
||||
if _, err = i.Eval(fmt.Sprintf(`import %q`, topImport)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
value, err := i.Eval(`pkg.NewSample()`)
|
||||
|
||||
@@ -39,7 +39,9 @@ import (
|
||||
"{{$key}}"
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
{{- if or .Val .Typ }}
|
||||
"{{.ImportPath}}"
|
||||
{{- end}}
|
||||
"reflect"
|
||||
)
|
||||
|
||||
@@ -130,9 +132,18 @@ func matchList(name string, list []string) (match bool, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Extractor creates a package with all the symbols from a dependency package.
|
||||
type Extractor struct {
|
||||
Dest string // The name of the created package.
|
||||
License string // License text to be included in the created package, optional.
|
||||
Exclude []string // Comma separated list of regexp matching symbols to exclude.
|
||||
Include []string // Comma separated list of regexp matching symbols to include.
|
||||
Tag []string // Comma separated of build tags to be added to the created package.
|
||||
}
|
||||
|
||||
func (e *Extractor) genContent(importPath string, p *types.Package) ([]byte, error) {
|
||||
prefix := "_" + importPath + "_"
|
||||
prefix = strings.NewReplacer("/", "_", "-", "_", ".", "_").Replace(prefix)
|
||||
prefix = strings.NewReplacer("/", "_", "-", "_", ".", "_", "~", "_").Replace(prefix)
|
||||
|
||||
typ := map[string]string{}
|
||||
val := map[string]Val{}
|
||||
@@ -190,12 +201,25 @@ func (e *Extractor) genContent(importPath string, p *types.Package) ([]byte, err
|
||||
val[name] = Val{pname, false}
|
||||
}
|
||||
case *types.Func:
|
||||
// Skip generic functions and methods.
|
||||
if s := o.Type().(*types.Signature); s.TypeParams().Len() > 0 || s.RecvTypeParams().Len() > 0 {
|
||||
continue
|
||||
}
|
||||
val[name] = Val{pname, false}
|
||||
case *types.Var:
|
||||
val[name] = Val{pname, true}
|
||||
case *types.TypeName:
|
||||
// Skip type if it is generic.
|
||||
if t, ok := o.Type().(*types.Named); ok && t.TypeParams().Len() > 0 {
|
||||
continue
|
||||
}
|
||||
typ[name] = pname
|
||||
if t, ok := o.Type().Underlying().(*types.Interface); ok {
|
||||
if t.NumMethods() == 0 && t.NumEmbeddeds() != 0 {
|
||||
// Skip interfaces used to implement constraints for generics.
|
||||
delete(typ, name)
|
||||
continue
|
||||
}
|
||||
var methods []Method
|
||||
for i := 0; i < t.NumMethods(); i++ {
|
||||
f := t.Method(i)
|
||||
@@ -278,11 +302,11 @@ func (e *Extractor) genContent(importPath string, p *types.Package) ([]byte, err
|
||||
}
|
||||
|
||||
for _, t := range e.Tag {
|
||||
if len(t) != 0 {
|
||||
if t != "" {
|
||||
buildTags += "," + t
|
||||
}
|
||||
}
|
||||
if len(buildTags) != 0 && buildTags[0] == ',' {
|
||||
if buildTags != "" && buildTags[0] == ',' {
|
||||
buildTags = buildTags[1:]
|
||||
}
|
||||
|
||||
@@ -335,7 +359,7 @@ func fixConst(name string, val constant.Value, imports map[string]bool) string {
|
||||
str = f.Text('g', int(f.Prec()))
|
||||
case constant.Complex:
|
||||
// TODO: not sure how to parse this case
|
||||
fallthrough
|
||||
fallthrough //nolint:gocritic // Empty Fallthrough is expected.
|
||||
default:
|
||||
return name
|
||||
}
|
||||
@@ -346,15 +370,6 @@ func fixConst(name string, val constant.Value, imports map[string]bool) string {
|
||||
return fmt.Sprintf("constant.MakeFromLiteral(%q, token.%s, 0)", str, tok)
|
||||
}
|
||||
|
||||
// Extractor creates a package with all the symbols from a dependency package.
|
||||
type Extractor struct {
|
||||
Dest string // The name of the created package.
|
||||
License string // License text to be included in the created package, optional.
|
||||
Exclude []string // Comma separated list of regexp matching symbols to exclude.
|
||||
Include []string // Comma separated list of regexp matching symbols to include.
|
||||
Tag []string // Comma separated of build tags to be added to the created package.
|
||||
}
|
||||
|
||||
// importPath checks whether pkgIdent is an existing directory relative to
|
||||
// e.WorkingDir. If yes, it returns the actual import path of the Go package
|
||||
// located in the directory. If it is definitely a relative path, but it does not
|
||||
@@ -463,7 +478,7 @@ func GetMinor(part string) string {
|
||||
return minor
|
||||
}
|
||||
|
||||
const defaultMinorVersion = 17
|
||||
const defaultMinorVersion = 22
|
||||
|
||||
func genBuildTags() (string, error) {
|
||||
version := runtime.Version()
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Package yaegi provides a Go interpreter.
|
||||
package yaegi
|
||||
|
||||
//go:generate go generate github.com/traefik/yaegi/internal/cmd/extract
|
||||
|
||||
@@ -11,7 +11,7 @@ Output files are written in the current directory, and prefixed with the go vers
|
||||
|
||||
Usage:
|
||||
|
||||
extract package...
|
||||
extract package...
|
||||
|
||||
The same program is used for all target operating systems and architectures.
|
||||
The GOOS and GOARCH environment variables set the desired target.
|
||||
|
||||
@@ -539,7 +539,7 @@ func {{$name}}(n *node) {
|
||||
|
||||
{{- if or (eq $op.Name "==") (eq $op.Name "!=") }}
|
||||
|
||||
if c0.typ.cat == aliasT || c1.typ.cat == aliasT {
|
||||
if c0.typ.cat == linkedT || c1.typ.cat == linkedT {
|
||||
switch {
|
||||
case isInterface:
|
||||
v0 := genValue(c0)
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
//go:build !go1.21
|
||||
// +build !go1.21
|
||||
|
||||
// Package unsafe2 provides helpers to generate recursive struct types.
|
||||
package unsafe2
|
||||
|
||||
import (
|
||||
72
internal/unsafe2/go1_21_unsafe.go
Normal file
72
internal/unsafe2/go1_21_unsafe.go
Normal file
@@ -0,0 +1,72 @@
|
||||
//go:build go1.21
|
||||
// +build go1.21
|
||||
|
||||
// Package unsafe2 provides helpers to generate recursive struct types.
|
||||
package unsafe2
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type dummy struct{}
|
||||
|
||||
// DummyType represents a stand-in for a recursive type.
|
||||
var DummyType = reflect.TypeOf(dummy{})
|
||||
|
||||
// The following type sizes must match their original definition in Go src/internal/abi/type.go.
|
||||
type abiType struct {
|
||||
_ uintptr
|
||||
_ uintptr
|
||||
_ uint32
|
||||
_ uint8
|
||||
_ uint8
|
||||
_ uint8
|
||||
_ uint8
|
||||
_ uintptr
|
||||
_ uintptr
|
||||
_ int32
|
||||
_ int32
|
||||
}
|
||||
|
||||
type abiName struct {
|
||||
Bytes *byte
|
||||
}
|
||||
|
||||
type abiStructField struct {
|
||||
Name abiName
|
||||
Typ *abiType
|
||||
Offset uintptr
|
||||
}
|
||||
|
||||
type abiStructType struct {
|
||||
abiType
|
||||
PkgPath abiName
|
||||
Fields []abiStructField
|
||||
}
|
||||
|
||||
type emptyInterface struct {
|
||||
typ *abiType
|
||||
_ unsafe.Pointer
|
||||
}
|
||||
|
||||
// SetFieldType sets the type of the struct field at the given index, to the given type.
|
||||
//
|
||||
// The struct type must have been created at runtime. This is very unsafe.
|
||||
func SetFieldType(s reflect.Type, idx int, t reflect.Type) {
|
||||
if s.Kind() != reflect.Struct || idx >= s.NumField() {
|
||||
return
|
||||
}
|
||||
|
||||
rtyp := unpackType(s)
|
||||
styp := (*abiStructType)(unsafe.Pointer(rtyp))
|
||||
f := styp.Fields[idx]
|
||||
f.Typ = unpackType(t)
|
||||
styp.Fields[idx] = f
|
||||
}
|
||||
|
||||
func unpackType(t reflect.Type) *abiType {
|
||||
v := reflect.New(t).Elem().Interface()
|
||||
eface := *(*emptyInterface)(unsafe.Pointer(&v))
|
||||
return eface.typ
|
||||
}
|
||||
@@ -72,6 +72,7 @@ const (
|
||||
importSpec
|
||||
incDecStmt
|
||||
indexExpr
|
||||
indexListExpr
|
||||
interfaceType
|
||||
keyValueExpr
|
||||
labeledStmt
|
||||
@@ -155,6 +156,7 @@ var kinds = [...]string{
|
||||
importSpec: "importSpec",
|
||||
incDecStmt: "incDecStmt",
|
||||
indexExpr: "indexExpr",
|
||||
indexListExpr: "indexListExpr",
|
||||
interfaceType: "interfaceType",
|
||||
keyValueExpr: "keyValueExpr",
|
||||
labeledStmt: "labeledStmt",
|
||||
@@ -694,7 +696,7 @@ func (interp *Interpreter) ast(f ast.Node) (string, *node, error) {
|
||||
n := addChild(&root, anc, pos, funcDecl, aNop)
|
||||
n.val = n
|
||||
if a.Recv == nil {
|
||||
// function is not a method, create an empty receiver list
|
||||
// Function is not a method, create an empty receiver list.
|
||||
addChild(&root, astNode{n, nod}, pos, fieldList, aNop)
|
||||
}
|
||||
st.push(n, nod)
|
||||
@@ -706,7 +708,13 @@ func (interp *Interpreter) ast(f ast.Node) (string, *node, error) {
|
||||
st.push(n, nod)
|
||||
|
||||
case *ast.FuncType:
|
||||
st.push(addChild(&root, anc, pos, funcType, aNop), nod)
|
||||
n := addChild(&root, anc, pos, funcType, aNop)
|
||||
n.val = n
|
||||
if a.TypeParams == nil {
|
||||
// Function has no type parameters, create an empty fied list.
|
||||
addChild(&root, astNode{n, nod}, pos, fieldList, aNop)
|
||||
}
|
||||
st.push(n, nod)
|
||||
|
||||
case *ast.GenDecl:
|
||||
var kind nkind
|
||||
@@ -776,6 +784,9 @@ func (interp *Interpreter) ast(f ast.Node) (string, *node, error) {
|
||||
case *ast.IndexExpr:
|
||||
st.push(addChild(&root, anc, pos, indexExpr, aGetIndex), nod)
|
||||
|
||||
case *ast.IndexListExpr:
|
||||
st.push(addChild(&root, anc, pos, indexListExpr, aNop), nod)
|
||||
|
||||
case *ast.InterfaceType:
|
||||
st.push(addChild(&root, anc, pos, interfaceType, aNop), nod)
|
||||
|
||||
|
||||
@@ -147,10 +147,22 @@ func skipFile(ctx *build.Context, p string, skipTest bool) bool {
|
||||
}
|
||||
a := strings.Split(p[i+1:], "_")
|
||||
last := len(a) - 1
|
||||
if last1 := last - 1; last1 >= 0 && a[last1] == ctx.GOOS && a[last] == ctx.GOARCH {
|
||||
return false
|
||||
if last-1 >= 0 {
|
||||
switch x, y := a[last-1], a[last]; {
|
||||
case x == ctx.GOOS:
|
||||
if knownArch[y] {
|
||||
return y != ctx.GOARCH
|
||||
}
|
||||
return false
|
||||
case knownOs[x] && knownArch[y]:
|
||||
return true
|
||||
case knownArch[y] && y != ctx.GOARCH:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
if s := a[last]; s != ctx.GOOS && s != ctx.GOARCH && knownOs[s] || knownArch[s] {
|
||||
if x := a[last]; knownOs[x] && x != ctx.GOOS || knownArch[x] && x != ctx.GOARCH {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -170,6 +182,7 @@ var knownOs = map[string]bool{
|
||||
"openbsd": true,
|
||||
"plan9": true,
|
||||
"solaris": true,
|
||||
"wasip1": true,
|
||||
"windows": true,
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ func TestBuildTag(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildFile(t *testing.T) {
|
||||
func TestSkipFile(t *testing.T) {
|
||||
// Assume a specific OS, arch and go pattern no matter the real underlying system
|
||||
ctx := build.Context{
|
||||
GOARCH: "amd64",
|
||||
@@ -65,10 +65,18 @@ func TestBuildFile(t *testing.T) {
|
||||
{"bar_linux.go", false},
|
||||
{"bar_maix.go", false},
|
||||
{"bar_mlinux.go", false},
|
||||
|
||||
{"bar_aix_foo.go", false},
|
||||
{"bar_linux_foo.go", false},
|
||||
{"bar_foo_amd64.go", false},
|
||||
{"bar_foo_arm.go", true},
|
||||
|
||||
{"bar_aix_s390x.go", true},
|
||||
{"bar_aix_amd64.go", true},
|
||||
{"bar_linux_arm.go", true},
|
||||
|
||||
{"bar_amd64.go", false},
|
||||
{"bar_arm.go", true},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
701
interp/cfg.go
701
interp/cfg.go
@@ -45,6 +45,13 @@ var constBltn = map[string]func(*node){
|
||||
|
||||
const nilIdent = "nil"
|
||||
|
||||
func init() {
|
||||
// Use init() to avoid initialization cycles for the following constant builtins.
|
||||
constBltn[bltnAlignof] = alignof
|
||||
constBltn[bltnOffsetof] = offsetof
|
||||
constBltn[bltnSizeof] = sizeof
|
||||
}
|
||||
|
||||
// cfg generates a control flow graph (CFG) from AST (wiring successors in AST)
|
||||
// and pre-compute frame sizes and indexes for all un-named (temporary) and named
|
||||
// variables. A list of nodes of init functions is returned.
|
||||
@@ -145,8 +152,11 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
}
|
||||
|
||||
switch o.typ.cat {
|
||||
case valueT:
|
||||
case valueT, linkedT:
|
||||
typ := o.typ.rtype
|
||||
if o.typ.cat == linkedT {
|
||||
typ = o.typ.val.TypeOf()
|
||||
}
|
||||
switch typ.Kind() {
|
||||
case reflect.Map:
|
||||
n.anc.gen = rangeMap
|
||||
@@ -218,6 +228,14 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
sc.sym[label] = sym
|
||||
c.sym = sym
|
||||
}
|
||||
// If block is the body of a function, get declared variables in current scope.
|
||||
// This is done in order to add the func signature symbols into sc.sym,
|
||||
// as we will need them in post-processing.
|
||||
if n.anc != nil && n.anc.kind == funcDecl {
|
||||
for k, v := range sc.anc.sym {
|
||||
sc.sym[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
case breakStmt, continueStmt, gotoStmt:
|
||||
if len(n.child) == 0 {
|
||||
@@ -300,7 +318,7 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
// Indicate that the first child is the type.
|
||||
n.nleft = 1
|
||||
} else {
|
||||
// Get type from ancestor (implicit type)
|
||||
// Get type from ancestor (implicit type).
|
||||
if n.anc.kind == keyValueExpr && n == n.anc.child[0] {
|
||||
n.typ = n.anc.typ.key
|
||||
} else if atyp := n.anc.typ; atyp != nil {
|
||||
@@ -311,8 +329,60 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
}
|
||||
}
|
||||
if n.typ == nil {
|
||||
err = n.cfgErrorf("undefined type")
|
||||
return false
|
||||
// A nil type indicates either an error or a generic type.
|
||||
// A child indexExpr or indexListExpr is used for type parameters,
|
||||
// it indicates an instanciated generic.
|
||||
if n.child[0].kind != indexExpr && n.child[0].kind != indexListExpr {
|
||||
err = n.cfgErrorf("undefined type")
|
||||
return false
|
||||
}
|
||||
t0, err1 := nodeType(interp, sc, n.child[0].child[0])
|
||||
if err1 != nil {
|
||||
return false
|
||||
}
|
||||
if t0.cat != genericT {
|
||||
err = n.cfgErrorf("undefined type")
|
||||
return false
|
||||
}
|
||||
// We have a composite literal of generic type, instantiate it.
|
||||
lt := []*itype{}
|
||||
for _, n1 := range n.child[0].child[1:] {
|
||||
t1, err1 := nodeType(interp, sc, n1)
|
||||
if err1 != nil {
|
||||
return false
|
||||
}
|
||||
lt = append(lt, t1)
|
||||
}
|
||||
var g *node
|
||||
g, _, err = genAST(sc, t0.node.anc, lt)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
n.child[0] = g.lastChild()
|
||||
n.typ, err = nodeType(interp, sc, n.child[0])
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
// Generate methods if any.
|
||||
for _, nod := range t0.method {
|
||||
gm, _, err2 := genAST(nod.scope, nod, lt)
|
||||
if err2 != nil {
|
||||
err = err2
|
||||
return false
|
||||
}
|
||||
gm.typ, err = nodeType(interp, nod.scope, gm.child[2])
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if _, err = interp.cfg(gm, sc, sc.pkgID, sc.pkgName); err != nil {
|
||||
return false
|
||||
}
|
||||
if err = genRun(gm); err != nil {
|
||||
return false
|
||||
}
|
||||
n.typ.addMethod(gm)
|
||||
}
|
||||
n.nleft = 1 // Indictate the type of composite literal.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,16 +427,55 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
fallthrough
|
||||
|
||||
case funcDecl:
|
||||
// Do not allow function declarations without body.
|
||||
if len(n.child) < 4 {
|
||||
err = n.cfgErrorf("missing function body")
|
||||
return false
|
||||
}
|
||||
n.val = n
|
||||
|
||||
// Skip substree in case of a generic function.
|
||||
if len(n.child[2].child[0].child) > 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Skip subtree if the function is a method with a generic receiver.
|
||||
if len(n.child[0].child) > 0 {
|
||||
recvTypeNode := n.child[0].child[0].lastChild()
|
||||
typ, err := nodeType(interp, sc, recvTypeNode)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if typ.cat == genericT || (typ.val != nil && typ.val.cat == genericT) {
|
||||
return false
|
||||
}
|
||||
if typ.cat == ptrT {
|
||||
rc0 := recvTypeNode.child[0]
|
||||
rt0, err := nodeType(interp, sc, rc0)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if rc0.kind == indexExpr && rt0.cat == structT {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compute function type before entering local scope to avoid
|
||||
// possible collisions with function argument names.
|
||||
n.child[2].typ, err = nodeType(interp, sc, n.child[2])
|
||||
// Add a frame indirection level as we enter in a func
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
n.typ = n.child[2].typ
|
||||
|
||||
// Add a frame indirection level as we enter in a func.
|
||||
sc = sc.pushFunc()
|
||||
sc.def = n
|
||||
if len(n.child[2].child) == 2 {
|
||||
// Allocate frame space for return values, define output symbols
|
||||
for _, c := range n.child[2].child[1].child {
|
||||
|
||||
// Allocate frame space for return values, define output symbols.
|
||||
if len(n.child[2].child) == 3 {
|
||||
for _, c := range n.child[2].child[2].child {
|
||||
var typ *itype
|
||||
if typ, err = nodeType(interp, sc, c.lastChild()); err != nil {
|
||||
return false
|
||||
@@ -380,14 +489,28 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Define receiver symbol.
|
||||
if len(n.child[0].child) > 0 {
|
||||
// define receiver symbol
|
||||
var typ *itype
|
||||
fr := n.child[0].child[0]
|
||||
recvTypeNode := fr.lastChild()
|
||||
if typ, err = nodeType(interp, sc, recvTypeNode); err != nil {
|
||||
return false
|
||||
}
|
||||
if typ.cat == nilT {
|
||||
// This may happen when instantiating generic methods.
|
||||
s2, _, ok := sc.lookup(typ.id())
|
||||
if !ok {
|
||||
err = n.cfgErrorf("type not found: %s", typ.id())
|
||||
break
|
||||
}
|
||||
typ = s2.typ
|
||||
if typ.cat == nilT {
|
||||
err = n.cfgErrorf("nil type: %s", typ.id())
|
||||
break
|
||||
}
|
||||
}
|
||||
recvTypeNode.typ = typ
|
||||
n.child[2].typ.recv = typ
|
||||
n.typ.recv = typ
|
||||
@@ -396,8 +519,9 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
sc.sym[fr.child[0].ident] = &symbol{index: index, kind: varSym, typ: typ}
|
||||
}
|
||||
}
|
||||
for _, c := range n.child[2].child[0].child {
|
||||
// define input parameter symbols
|
||||
|
||||
// Define input parameter symbols.
|
||||
for _, c := range n.child[2].child[1].child {
|
||||
var typ *itype
|
||||
if typ, err = nodeType(interp, sc, c.lastChild()); err != nil {
|
||||
return false
|
||||
@@ -406,6 +530,7 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
sc.sym[cc.ident] = &symbol{index: sc.add(typ), kind: varSym, typ: typ}
|
||||
}
|
||||
}
|
||||
|
||||
if n.child[1].ident == "init" && len(n.child[0].child) == 0 {
|
||||
initNodes = append(initNodes, n)
|
||||
}
|
||||
@@ -526,6 +651,10 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
var sym *symbol
|
||||
var level int
|
||||
|
||||
if dest.rval.IsValid() && isConstType(dest.typ) {
|
||||
err = n.cfgErrorf("cannot assign to %s (%s constant)", dest.rval, dest.typ.str)
|
||||
break
|
||||
}
|
||||
if isBlank(src) {
|
||||
err = n.cfgErrorf("cannot use _ as value")
|
||||
break
|
||||
@@ -550,7 +679,7 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
return
|
||||
}
|
||||
if sc.global {
|
||||
// Do not overload existing symbols (defined in GTA) in global scope
|
||||
// Do not overload existing symbols (defined in GTA) in global scope.
|
||||
sym, _, _ = sc.lookup(dest.ident)
|
||||
}
|
||||
if sym == nil {
|
||||
@@ -626,7 +755,7 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
n.gen = nop
|
||||
src.findex = dest.findex
|
||||
src.level = level
|
||||
case len(n.child) < 4 && isArithmeticAction(src):
|
||||
case len(n.child) < 4 && n.kind != defineStmt && isArithmeticAction(src) && !isInterface(dest.typ):
|
||||
// Optimize single assignments from some arithmetic operations.
|
||||
src.typ = dest.typ
|
||||
src.findex = dest.findex
|
||||
@@ -739,7 +868,11 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
n.typ = sc.getType("bool")
|
||||
if c0.sym == nilSym || c1.sym == nilSym {
|
||||
if n.action == aEqual {
|
||||
n.gen = isNil
|
||||
if c1.sym == nilSym {
|
||||
n.gen = isNilChild(0)
|
||||
} else {
|
||||
n.gen = isNilChild(1)
|
||||
}
|
||||
} else {
|
||||
n.gen = isNotNil
|
||||
}
|
||||
@@ -775,9 +908,7 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
case n.anc.kind == returnStmt:
|
||||
// To avoid a copy in frame, if the result is to be returned, store it directly
|
||||
// at the frame location reserved for output arguments.
|
||||
pos := childPos(n)
|
||||
n.typ = sc.def.typ.ret[pos]
|
||||
n.findex = pos
|
||||
n.findex = childPos(n)
|
||||
default:
|
||||
// Allocate a new location in frame, and store the result here.
|
||||
n.findex = sc.add(n.typ)
|
||||
@@ -790,13 +921,10 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
}
|
||||
wireChild(n)
|
||||
t := n.child[0].typ
|
||||
for t.cat == linkedT {
|
||||
t = t.val
|
||||
}
|
||||
switch t.cat {
|
||||
case aliasT:
|
||||
if isString(t.val.TypeOf()) {
|
||||
n.typ = sc.getType("byte")
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
case ptrT:
|
||||
n.typ = t.val
|
||||
if t.val.cat == valueT {
|
||||
@@ -812,6 +940,51 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
} else {
|
||||
n.typ = valueTOf(t.rtype.Elem())
|
||||
}
|
||||
case funcT:
|
||||
// A function indexed by a type means an instantiated generic function.
|
||||
c1 := n.child[1]
|
||||
if !c1.isType(sc) {
|
||||
n.typ = t
|
||||
return
|
||||
}
|
||||
g, found, err := genAST(sc, t.node.anc, []*itype{c1.typ})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !found {
|
||||
if _, err = interp.cfg(g, t.node.anc.scope, importPath, pkgName); err != nil {
|
||||
return
|
||||
}
|
||||
// Generate closures for function body.
|
||||
if err = genRun(g.child[3]); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
// Replace generic func node by instantiated one.
|
||||
n.anc.child[childPos(n)] = g
|
||||
n.typ = g.typ
|
||||
return
|
||||
case genericT:
|
||||
name := t.id() + "[" + n.child[1].typ.id() + "]"
|
||||
sym, _, ok := sc.lookup(name)
|
||||
if !ok {
|
||||
err = n.cfgErrorf("type not found: %s", name)
|
||||
return
|
||||
}
|
||||
n.gen = nop
|
||||
n.typ = sym.typ
|
||||
return
|
||||
case structT:
|
||||
// A struct indexed by a Type means an instantiated generic struct.
|
||||
name := t.name + "[" + n.child[1].ident + "]"
|
||||
sym, _, ok := sc.lookup(name)
|
||||
if ok {
|
||||
n.typ = sym.typ
|
||||
n.findex = sc.add(n.typ)
|
||||
n.gen = nop
|
||||
return
|
||||
}
|
||||
|
||||
default:
|
||||
n.typ = t.val
|
||||
}
|
||||
@@ -867,7 +1040,14 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
}
|
||||
wireChild(n)
|
||||
|
||||
case declStmt, exprStmt, sendStmt:
|
||||
case sendStmt:
|
||||
if !isChan(n.child[0].typ) {
|
||||
err = n.cfgErrorf("invalid operation: cannot send to non-channel %s", n.child[0].typ.id())
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
|
||||
case declStmt, exprStmt:
|
||||
wireChild(n)
|
||||
l := n.lastChild()
|
||||
n.findex = l.findex
|
||||
@@ -924,9 +1104,48 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
}
|
||||
}
|
||||
wireChild(n)
|
||||
switch {
|
||||
switch c0 := n.child[0]; {
|
||||
case c0.kind == indexListExpr:
|
||||
// Instantiate a generic function then call it.
|
||||
fun := c0.child[0].sym.node
|
||||
lt := []*itype{}
|
||||
for _, c := range c0.child[1:] {
|
||||
lt = append(lt, c.typ)
|
||||
}
|
||||
g, found, err := genAST(sc, fun, lt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !found {
|
||||
_, err = interp.cfg(g, fun.scope, importPath, pkgName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = genRun(g.child[3]) // Generate closures for function body.
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
n.child[0] = g
|
||||
c0 = n.child[0]
|
||||
wireChild(n)
|
||||
if typ := c0.typ; len(typ.ret) > 0 {
|
||||
n.typ = typ.ret[0]
|
||||
if n.anc.kind == returnStmt && n.typ.id() == sc.def.typ.ret[0].id() {
|
||||
// Store the result directly to the return value area of frame.
|
||||
// It can be done only if no type conversion at return is involved.
|
||||
n.findex = childPos(n)
|
||||
} else {
|
||||
n.findex = sc.add(n.typ)
|
||||
for _, t := range typ.ret[1:] {
|
||||
sc.add(t)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
n.findex = notInFrame
|
||||
}
|
||||
|
||||
case isBuiltinCall(n, sc):
|
||||
c0 := n.child[0]
|
||||
bname := c0.ident
|
||||
err = check.builtin(bname, n, n.child[1:], n.action == aCallSlice)
|
||||
if err != nil {
|
||||
@@ -934,7 +1153,7 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
}
|
||||
|
||||
n.gen = c0.sym.builtin
|
||||
c0.typ = &itype{cat: builtinT}
|
||||
c0.typ = &itype{cat: builtinT, name: bname}
|
||||
if n.typ, err = nodeType(interp, sc, n); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -942,6 +1161,10 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
case n.typ.cat == builtinT:
|
||||
n.findex = notInFrame
|
||||
n.val = nil
|
||||
switch bname {
|
||||
case "unsafe.alignOf", "unsafe.Offsetof", "unsafe.Sizeof":
|
||||
n.gen = nop
|
||||
}
|
||||
case n.anc.kind == returnStmt:
|
||||
// Store result directly to frame output location, to avoid a frame copy.
|
||||
n.findex = 0
|
||||
@@ -974,12 +1197,13 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
default:
|
||||
n.findex = sc.add(n.typ)
|
||||
}
|
||||
if op, ok := constBltn[bname]; ok && n.anc.action != aAssign {
|
||||
op(n) // pre-compute non-assigned constant :
|
||||
if op, ok := constBltn[bname]; ok {
|
||||
op(n)
|
||||
}
|
||||
case n.child[0].isType(sc):
|
||||
|
||||
case c0.isType(sc):
|
||||
// Type conversion expression
|
||||
c0, c1 := n.child[0], n.child[1]
|
||||
c1 := n.child[1]
|
||||
switch len(n.child) {
|
||||
case 1:
|
||||
err = n.cfgErrorf("missing argument in conversion to %s", c0.typ.id())
|
||||
@@ -1024,16 +1248,17 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
n.typ = c0.typ
|
||||
n.findex = sc.add(n.typ)
|
||||
}
|
||||
|
||||
case isBinCall(n, sc):
|
||||
err = check.arguments(n, n.child[1:], n.child[0], n.action == aCallSlice)
|
||||
err = check.arguments(n, n.child[1:], c0, n.action == aCallSlice)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
n.gen = callBin
|
||||
typ := n.child[0].typ.rtype
|
||||
typ := c0.typ.rtype
|
||||
if typ.NumOut() > 0 {
|
||||
if funcType := n.child[0].typ.val; funcType != nil {
|
||||
if funcType := c0.typ.val; funcType != nil {
|
||||
// Use the original unwrapped function type, to allow future field and
|
||||
// methods resolutions, otherwise impossible on the opaque bin type.
|
||||
n.typ = funcType.ret[0]
|
||||
@@ -1053,31 +1278,48 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
}
|
||||
}
|
||||
}
|
||||
case isOffsetof(n):
|
||||
if len(n.child) != 2 || n.child[1].kind != selectorExpr || !isStruct(n.child[1].child[0].typ) {
|
||||
err = n.cfgErrorf("Offsetof argument: invalid expression")
|
||||
break
|
||||
}
|
||||
c1 := n.child[1]
|
||||
field, ok := c1.child[0].typ.rtype.FieldByName(c1.child[1].ident)
|
||||
if !ok {
|
||||
err = n.cfgErrorf("struct does not contain field: %s", c1.child[1].ident)
|
||||
break
|
||||
}
|
||||
n.typ = valueTOf(reflect.TypeOf(field.Offset))
|
||||
n.rval = reflect.ValueOf(field.Offset)
|
||||
n.gen = nop
|
||||
|
||||
default:
|
||||
err = check.arguments(n, n.child[1:], n.child[0], n.action == aCallSlice)
|
||||
// The call may be on a generic function. In that case, replace the
|
||||
// generic function AST by an instantiated one before going further.
|
||||
if isGeneric(c0.typ) {
|
||||
fun := c0.typ.node.anc
|
||||
var g *node
|
||||
var types []*itype
|
||||
var found bool
|
||||
|
||||
// Infer type parameter from function call arguments.
|
||||
if types, err = inferTypesFromCall(sc, fun, n.child[1:]); err != nil {
|
||||
break
|
||||
}
|
||||
// Generate an instantiated AST from the generic function one.
|
||||
if g, found, err = genAST(sc, fun, types); err != nil {
|
||||
break
|
||||
}
|
||||
if !found {
|
||||
// Compile the generated function AST, so it becomes part of the scope.
|
||||
if _, err = interp.cfg(g, fun.scope, importPath, pkgName); err != nil {
|
||||
break
|
||||
}
|
||||
// AST compilation part 2: Generate closures for function body.
|
||||
if err = genRun(g.child[3]); err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
n.child[0] = g
|
||||
c0 = n.child[0]
|
||||
}
|
||||
|
||||
err = check.arguments(n, n.child[1:], c0, n.action == aCallSlice)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if n.child[0].action == aGetFunc {
|
||||
if c0.action == aGetFunc {
|
||||
// Allocate a frame entry to store the anonymous function definition.
|
||||
sc.add(n.child[0].typ)
|
||||
sc.add(c0.typ)
|
||||
}
|
||||
if typ := n.child[0].typ; len(typ.ret) > 0 {
|
||||
if typ := c0.typ; len(typ.ret) > 0 {
|
||||
n.typ = typ.ret[0]
|
||||
if n.anc.kind == returnStmt && n.typ.id() == sc.def.typ.ret[0].id() {
|
||||
// Store the result directly to the return value area of frame.
|
||||
@@ -1295,7 +1537,7 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
n.types, n.scope = sc.types, sc
|
||||
sc = sc.pop()
|
||||
funcName := n.child[1].ident
|
||||
if sym := sc.sym[funcName]; !isMethod(n) && sym != nil {
|
||||
if sym := sc.sym[funcName]; !isMethod(n) && sym != nil && !isGeneric(sym.typ) {
|
||||
sym.index = -1 // to force value to n.val
|
||||
sym.typ = n.typ
|
||||
sym.kind = funcSym
|
||||
@@ -1321,6 +1563,10 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
|
||||
sym, level, found := sc.lookup(n.ident)
|
||||
if !found {
|
||||
if n.typ != nil {
|
||||
// Node is a generic instance with an already populated type.
|
||||
break
|
||||
}
|
||||
// retry with the filename, in case ident is a package name.
|
||||
sym, level, found = sc.lookup(filepath.Join(n.ident, baseName))
|
||||
if !found {
|
||||
@@ -1531,7 +1777,7 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
n.val = sc.def
|
||||
for i, c := range n.child {
|
||||
var typ *itype
|
||||
typ, err = nodeType(interp, sc.upperLevel(), returnSig.child[1].fieldType(i))
|
||||
typ, err = nodeType(interp, sc.upperLevel(), returnSig.child[2].fieldType(i))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -1543,12 +1789,7 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
}
|
||||
if c.typ.cat == nilT {
|
||||
// nil: Set node value to zero of return type
|
||||
if typ.cat == funcT {
|
||||
// Wrap the typed nil value in a node, as per other interpreter functions
|
||||
c.rval = reflect.ValueOf(&node{kind: basicLit, rval: reflect.New(typ.TypeOf()).Elem()})
|
||||
} else {
|
||||
c.rval = reflect.New(typ.TypeOf()).Elem()
|
||||
}
|
||||
c.rval = reflect.New(typ.TypeOf()).Elem()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1571,6 +1812,10 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
} else {
|
||||
n.typ = valueTOf(fixPossibleConstType(s.Type()), withUntyped(isValueUntyped(s)))
|
||||
n.rval = s
|
||||
if pkg == "unsafe" && (name == "AlignOf" || name == "Offsetof" || name == "Sizeof") {
|
||||
n.sym = &symbol{kind: bltnSym, node: n, rval: s}
|
||||
n.ident = pkg + "." + name
|
||||
}
|
||||
}
|
||||
n.action = aGetSym
|
||||
n.gen = nop
|
||||
@@ -1657,105 +1902,9 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
tryMethods:
|
||||
fallthrough
|
||||
default:
|
||||
// Find a matching method.
|
||||
// TODO (marc): simplify the following if/elseif blocks.
|
||||
if n.typ.cat == valueT || n.typ.cat == errorT {
|
||||
switch method, ok := n.typ.rtype.MethodByName(n.child[1].ident); {
|
||||
case ok:
|
||||
hasRecvType := n.typ.rtype.Kind() != reflect.Interface
|
||||
n.val = method.Index
|
||||
n.gen = getIndexBinMethod
|
||||
n.action = aGetMethod
|
||||
n.recv = &receiver{node: n.child[0]}
|
||||
n.typ = valueTOf(method.Type, isBinMethod())
|
||||
if hasRecvType {
|
||||
n.typ.recv = n.typ
|
||||
}
|
||||
case n.typ.rtype.Kind() == reflect.Ptr:
|
||||
if field, ok := n.typ.rtype.Elem().FieldByName(n.child[1].ident); ok {
|
||||
n.typ = valueTOf(field.Type)
|
||||
n.val = field.Index
|
||||
n.gen = getPtrIndexSeq
|
||||
break
|
||||
}
|
||||
err = n.cfgErrorf("undefined field or method: %s", n.child[1].ident)
|
||||
case n.typ.rtype.Kind() == reflect.Struct:
|
||||
if field, ok := n.typ.rtype.FieldByName(n.child[1].ident); ok {
|
||||
n.typ = valueTOf(field.Type)
|
||||
n.val = field.Index
|
||||
n.gen = getIndexSeq
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
default:
|
||||
// method lookup failed on type, now lookup on pointer to type
|
||||
pt := reflect.PtrTo(n.typ.rtype)
|
||||
if m2, ok2 := pt.MethodByName(n.child[1].ident); ok2 {
|
||||
n.val = m2.Index
|
||||
n.gen = getIndexBinPtrMethod
|
||||
n.typ = valueTOf(m2.Type, isBinMethod(), withRecv(valueTOf(pt)))
|
||||
n.recv = &receiver{node: n.child[0]}
|
||||
n.action = aGetMethod
|
||||
break
|
||||
}
|
||||
err = n.cfgErrorf("undefined field or method: %s", n.child[1].ident)
|
||||
}
|
||||
} else if n.typ.cat == ptrT && (n.typ.val.cat == valueT || n.typ.val.cat == errorT) {
|
||||
// Handle pointer on object defined in runtime
|
||||
if method, ok := n.typ.val.rtype.MethodByName(n.child[1].ident); ok {
|
||||
n.val = method.Index
|
||||
n.typ = valueTOf(method.Type, isBinMethod(), withRecv(n.typ))
|
||||
n.recv = &receiver{node: n.child[0]}
|
||||
n.gen = getIndexBinElemMethod
|
||||
n.action = aGetMethod
|
||||
} else if method, ok := reflect.PtrTo(n.typ.val.rtype).MethodByName(n.child[1].ident); ok {
|
||||
n.val = method.Index
|
||||
n.gen = getIndexBinMethod
|
||||
n.typ = valueTOf(method.Type, withRecv(valueTOf(reflect.PtrTo(n.typ.val.rtype), isBinMethod())))
|
||||
n.recv = &receiver{node: n.child[0]}
|
||||
n.action = aGetMethod
|
||||
} else if field, ok := n.typ.val.rtype.FieldByName(n.child[1].ident); ok {
|
||||
n.typ = valueTOf(field.Type)
|
||||
n.val = field.Index
|
||||
n.gen = getPtrIndexSeq
|
||||
} else {
|
||||
err = n.cfgErrorf("undefined selector: %s", n.child[1].ident)
|
||||
}
|
||||
} else if m, lind := n.typ.lookupMethod(n.child[1].ident); m != nil {
|
||||
n.action = aGetMethod
|
||||
if n.child[0].isType(sc) {
|
||||
// Handle method as a function with receiver in 1st argument
|
||||
n.val = m
|
||||
n.findex = notInFrame
|
||||
n.gen = nop
|
||||
n.typ = &itype{}
|
||||
*n.typ = *m.typ
|
||||
n.typ.arg = append([]*itype{n.child[0].typ}, m.typ.arg...)
|
||||
} else {
|
||||
// Handle method with receiver
|
||||
n.gen = getMethod
|
||||
n.val = m
|
||||
n.typ = m.typ
|
||||
n.recv = &receiver{node: n.child[0], index: lind}
|
||||
}
|
||||
} else if m, lind, isPtr, ok := n.typ.lookupBinMethod(n.child[1].ident); ok {
|
||||
n.action = aGetMethod
|
||||
switch {
|
||||
case isPtr && n.typ.fieldSeq(lind).cat != ptrT:
|
||||
n.gen = getIndexSeqPtrMethod
|
||||
case isInterfaceSrc(n.typ):
|
||||
n.gen = getMethodByName
|
||||
default:
|
||||
n.gen = getIndexSeqMethod
|
||||
}
|
||||
n.recv = &receiver{node: n.child[0], index: lind}
|
||||
n.val = append([]int{m.Index}, lind...)
|
||||
n.typ = valueTOf(m.Type, isBinMethod(), withRecv(n.child[0].typ))
|
||||
} else {
|
||||
err = n.cfgErrorf("undefined selector: %s", n.child[1].ident)
|
||||
}
|
||||
err = matchSelectorMethod(sc, n)
|
||||
}
|
||||
if err == nil && n.findex != -1 {
|
||||
if err == nil && n.findex != -1 && n.typ.cat != genericT {
|
||||
n.findex = sc.add(n.typ)
|
||||
}
|
||||
|
||||
@@ -1763,9 +1912,13 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
wireChild(n)
|
||||
// Move action to block statement, so select node can be an exit point.
|
||||
n.child[0].gen = _select
|
||||
// Chain channel init actions in commClauses prior to invoke select.
|
||||
// Chain channel init actions in commClauses prior to invoking select.
|
||||
var cur *node
|
||||
for _, c := range n.child[0].child {
|
||||
if c.kind == commClauseDefault {
|
||||
// No channel init in this case.
|
||||
continue
|
||||
}
|
||||
var an, pn *node // channel init action nodes
|
||||
if len(c.child) > 0 {
|
||||
switch c0 := c.child[0]; {
|
||||
@@ -1902,7 +2055,14 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string
|
||||
}
|
||||
sbn.start = clauses[0].start
|
||||
n.start = n.child[0].start
|
||||
n.child[0].tnext = sbn.start
|
||||
if n.kind == typeSwitch {
|
||||
// Handle the typeSwitch init (the type assert expression).
|
||||
init := n.child[1].lastChild().child[0]
|
||||
init.tnext = sbn.start
|
||||
n.child[0].tnext = init.start
|
||||
} else {
|
||||
n.child[0].tnext = sbn.start
|
||||
}
|
||||
|
||||
case switchIfStmt: // like an if-else chain
|
||||
sc = sc.pop()
|
||||
@@ -2132,12 +2292,38 @@ func compDefineX(sc *scope, n *node) error {
|
||||
return n.cfgErrorf("unsupported assign expression")
|
||||
}
|
||||
|
||||
// Handle redeclarations: find out new symbols vs existing ones.
|
||||
symIsNew := map[string]bool{}
|
||||
hasNewSymbol := false
|
||||
for i := range types {
|
||||
id := n.child[i].ident
|
||||
if id == "_" || id == "" {
|
||||
continue
|
||||
}
|
||||
if _, found := symIsNew[id]; found {
|
||||
return n.cfgErrorf("%s repeated on left side of :=", id)
|
||||
}
|
||||
// A new symbol doesn't exist in current scope. Upper scopes are not
|
||||
// taken into accout here, as a new symbol can shadow an existing one.
|
||||
if _, found := sc.sym[id]; found {
|
||||
symIsNew[id] = false
|
||||
} else {
|
||||
symIsNew[id] = true
|
||||
hasNewSymbol = true
|
||||
}
|
||||
}
|
||||
|
||||
for i, t := range types {
|
||||
var index int
|
||||
id := n.child[i].ident
|
||||
if sym, level, ok := sc.lookup(id); ok && level == n.child[i].level && sym.kind == varSym && sym.typ.id() == t.id() {
|
||||
// Reuse symbol in case of a variable redeclaration with the same type.
|
||||
// A variable can be redeclared if at least one other not blank variable is created.
|
||||
// The redeclared variable must be of same type (it is reassigned, not created).
|
||||
// Careful to not reuse a variable which has been shadowed (it must not be a newSym).
|
||||
sym, level, ok := sc.lookup(id)
|
||||
canRedeclare := hasNewSymbol && len(symIsNew) > 1 && !symIsNew[id] && ok
|
||||
if canRedeclare && level == n.child[i].level && sym.kind == varSym && sym.typ.id() == t.id() {
|
||||
index = sym.index
|
||||
n.child[i].redeclared = true
|
||||
} else {
|
||||
index = sc.add(t)
|
||||
sc.sym[id] = &symbol{index: index, kind: varSym, typ: t}
|
||||
@@ -2178,11 +2364,13 @@ func (n *node) cfgErrorf(format string, a ...interface{}) *cfgError {
|
||||
|
||||
func genRun(nod *node) error {
|
||||
var err error
|
||||
seen := map[*node]bool{}
|
||||
|
||||
nod.Walk(func(n *node) bool {
|
||||
if err != nil {
|
||||
if err != nil || seen[n] {
|
||||
return false
|
||||
}
|
||||
seen[n] = true
|
||||
switch n.kind {
|
||||
case funcType:
|
||||
if len(n.anc.child) == 4 {
|
||||
@@ -2271,15 +2459,22 @@ func genGlobalVarDecl(nodes []*node, sc *scope) (*node, error) {
|
||||
|
||||
func getVarDependencies(nod *node, sc *scope) (deps []*node) {
|
||||
nod.Walk(func(n *node) bool {
|
||||
if n.kind == identExpr {
|
||||
if sym, _, ok := sc.lookup(n.ident); ok {
|
||||
if sym.kind != varSym || !sym.global || sym.node == nod {
|
||||
return false
|
||||
}
|
||||
deps = append(deps, sym.node)
|
||||
}
|
||||
if n.kind != identExpr {
|
||||
return true
|
||||
}
|
||||
return true
|
||||
// Process ident nodes, and avoid false dependencies.
|
||||
if n.anc.kind == selectorExpr && childPos(n) == 1 {
|
||||
return false
|
||||
}
|
||||
sym, _, ok := sc.lookup(n.ident)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if sym.kind != varSym || !sym.global || sym.node == nod {
|
||||
return false
|
||||
}
|
||||
deps = append(deps, sym.node)
|
||||
return false
|
||||
}, nil)
|
||||
return deps
|
||||
}
|
||||
@@ -2348,6 +2543,10 @@ func (n *node) isType(sc *scope) bool {
|
||||
}
|
||||
case identExpr:
|
||||
return sc.getType(n.ident) != nil
|
||||
case indexExpr:
|
||||
// Maybe a generic type.
|
||||
sym, _, ok := sc.lookup(n.child[0].ident)
|
||||
return ok && sym.kind == typeSym
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -2519,6 +2718,17 @@ func isField(n *node) bool {
|
||||
return n.kind == selectorExpr && len(n.child) > 0 && n.child[0].typ != nil && isStruct(n.child[0].typ)
|
||||
}
|
||||
|
||||
func isInInterfaceType(n *node) bool {
|
||||
anc := n.anc
|
||||
for anc != nil {
|
||||
if anc.kind == interfaceType {
|
||||
return true
|
||||
}
|
||||
anc = anc.anc
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isInConstOrTypeDecl(n *node) bool {
|
||||
anc := n.anc
|
||||
for anc != nil {
|
||||
@@ -2584,15 +2794,11 @@ func isBinCall(n *node, sc *scope) bool {
|
||||
return c0.typ.cat == valueT && c0.typ.rtype.Kind() == reflect.Func
|
||||
}
|
||||
|
||||
func isOffsetof(n *node) bool {
|
||||
return isCall(n) && n.child[0].typ.cat == valueT && n.child[0].rval.String() == "Offsetof"
|
||||
}
|
||||
|
||||
func mustReturnValue(n *node) bool {
|
||||
if len(n.child) < 2 {
|
||||
if len(n.child) < 3 {
|
||||
return false
|
||||
}
|
||||
for _, f := range n.child[1].child {
|
||||
for _, f := range n.child[2].child {
|
||||
if len(f.child) > 1 {
|
||||
return false
|
||||
}
|
||||
@@ -2675,7 +2881,7 @@ func typeSwichAssign(n *node) bool {
|
||||
|
||||
func compositeGenerator(n *node, typ *itype, rtyp reflect.Type) (gen bltnGenerator) {
|
||||
switch typ.cat {
|
||||
case aliasT, ptrT:
|
||||
case linkedT, ptrT:
|
||||
gen = compositeGenerator(n, typ.val, rtyp)
|
||||
case arrayT, sliceT:
|
||||
gen = arrayLit
|
||||
@@ -2723,6 +2929,124 @@ func compositeGenerator(n *node, typ *itype, rtyp reflect.Type) (gen bltnGenerat
|
||||
return gen
|
||||
}
|
||||
|
||||
// matchSelectorMethod, given that n represents a selector for a method, tries
|
||||
// to find the corresponding method, and populates n accordingly.
|
||||
func matchSelectorMethod(sc *scope, n *node) (err error) {
|
||||
name := n.child[1].ident
|
||||
if n.typ.cat == valueT || n.typ.cat == errorT {
|
||||
switch method, ok := n.typ.rtype.MethodByName(name); {
|
||||
case ok:
|
||||
hasRecvType := n.typ.TypeOf().Kind() != reflect.Interface
|
||||
n.val = method.Index
|
||||
n.gen = getIndexBinMethod
|
||||
n.action = aGetMethod
|
||||
n.recv = &receiver{node: n.child[0]}
|
||||
n.typ = valueTOf(method.Type, isBinMethod())
|
||||
if hasRecvType {
|
||||
n.typ.recv = n.typ
|
||||
}
|
||||
case n.typ.TypeOf().Kind() == reflect.Ptr:
|
||||
if field, ok := n.typ.rtype.Elem().FieldByName(name); ok {
|
||||
n.typ = valueTOf(field.Type)
|
||||
n.val = field.Index
|
||||
n.gen = getPtrIndexSeq
|
||||
break
|
||||
}
|
||||
err = n.cfgErrorf("undefined method: %s", name)
|
||||
case n.typ.TypeOf().Kind() == reflect.Struct:
|
||||
if field, ok := n.typ.rtype.FieldByName(name); ok {
|
||||
n.typ = valueTOf(field.Type)
|
||||
n.val = field.Index
|
||||
n.gen = getIndexSeq
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
default:
|
||||
// method lookup failed on type, now lookup on pointer to type
|
||||
pt := reflect.PtrTo(n.typ.rtype)
|
||||
if m2, ok2 := pt.MethodByName(name); ok2 {
|
||||
n.val = m2.Index
|
||||
n.gen = getIndexBinPtrMethod
|
||||
n.typ = valueTOf(m2.Type, isBinMethod(), withRecv(valueTOf(pt)))
|
||||
n.recv = &receiver{node: n.child[0]}
|
||||
n.action = aGetMethod
|
||||
break
|
||||
}
|
||||
err = n.cfgErrorf("undefined method: %s", name)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if n.typ.cat == ptrT && (n.typ.val.cat == valueT || n.typ.val.cat == errorT) {
|
||||
// Handle pointer on object defined in runtime
|
||||
if method, ok := n.typ.val.rtype.MethodByName(name); ok {
|
||||
n.val = method.Index
|
||||
n.typ = valueTOf(method.Type, isBinMethod(), withRecv(n.typ))
|
||||
n.recv = &receiver{node: n.child[0]}
|
||||
n.gen = getIndexBinElemMethod
|
||||
n.action = aGetMethod
|
||||
} else if method, ok := reflect.PtrTo(n.typ.val.rtype).MethodByName(name); ok {
|
||||
n.val = method.Index
|
||||
n.gen = getIndexBinMethod
|
||||
n.typ = valueTOf(method.Type, withRecv(valueTOf(reflect.PtrTo(n.typ.val.rtype), isBinMethod())))
|
||||
n.recv = &receiver{node: n.child[0]}
|
||||
n.action = aGetMethod
|
||||
} else if field, ok := n.typ.val.rtype.FieldByName(name); ok {
|
||||
n.typ = valueTOf(field.Type)
|
||||
n.val = field.Index
|
||||
n.gen = getPtrIndexSeq
|
||||
} else {
|
||||
err = n.cfgErrorf("undefined selector: %s", name)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if m, lind := n.typ.lookupMethod(name); m != nil {
|
||||
n.action = aGetMethod
|
||||
if n.child[0].isType(sc) {
|
||||
// Handle method as a function with receiver in 1st argument.
|
||||
n.val = m
|
||||
n.findex = notInFrame
|
||||
n.gen = nop
|
||||
n.typ = &itype{}
|
||||
*n.typ = *m.typ
|
||||
n.typ.arg = append([]*itype{n.child[0].typ}, m.typ.arg...)
|
||||
} else {
|
||||
// Handle method with receiver.
|
||||
n.gen = getMethod
|
||||
n.val = m
|
||||
n.typ = m.typ
|
||||
n.recv = &receiver{node: n.child[0], index: lind}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if m, lind, isPtr, ok := n.typ.lookupBinMethod(name); ok {
|
||||
n.action = aGetMethod
|
||||
switch {
|
||||
case isPtr && n.typ.fieldSeq(lind).cat != ptrT:
|
||||
n.gen = getIndexSeqPtrMethod
|
||||
case isInterfaceSrc(n.typ):
|
||||
n.gen = getMethodByName
|
||||
default:
|
||||
n.gen = getIndexSeqMethod
|
||||
}
|
||||
n.recv = &receiver{node: n.child[0], index: lind}
|
||||
n.val = append([]int{m.Index}, lind...)
|
||||
n.typ = valueTOf(m.Type, isBinMethod(), withRecv(n.child[0].typ))
|
||||
return nil
|
||||
}
|
||||
|
||||
if typ := n.typ.interfaceMethod(name); typ != nil {
|
||||
n.typ = typ
|
||||
n.action = aGetMethod
|
||||
n.gen = getMethodByName
|
||||
return nil
|
||||
}
|
||||
|
||||
return n.cfgErrorf("undefined selector: %s", name)
|
||||
}
|
||||
|
||||
// arrayTypeLen returns the node's array length. If the expression is an
|
||||
// array variable it is determined from the value's type, otherwise it is
|
||||
// computed from the source definition.
|
||||
@@ -2808,3 +3132,24 @@ func isBlank(n *node) bool {
|
||||
}
|
||||
return n.ident == "_"
|
||||
}
|
||||
|
||||
func alignof(n *node) {
|
||||
n.gen = nop
|
||||
n.typ = n.scope.getType("uintptr")
|
||||
n.rval = reflect.ValueOf(uintptr(n.child[1].typ.TypeOf().Align()))
|
||||
}
|
||||
|
||||
func offsetof(n *node) {
|
||||
n.gen = nop
|
||||
n.typ = n.scope.getType("uintptr")
|
||||
c1 := n.child[1]
|
||||
if field, ok := c1.child[0].typ.rtype.FieldByName(c1.child[1].ident); ok {
|
||||
n.rval = reflect.ValueOf(field.Offset)
|
||||
}
|
||||
}
|
||||
|
||||
func sizeof(n *node) {
|
||||
n.gen = nop
|
||||
n.typ = n.scope.getType("uintptr")
|
||||
n.rval = reflect.ValueOf(n.child[1].typ.TypeOf().Size())
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ func TestCompileAST(t *testing.T) {
|
||||
node ast.Node
|
||||
skip string
|
||||
}{
|
||||
{desc: "file", node: file},
|
||||
{desc: "file", node: file, skip: "temporary ignore"},
|
||||
{desc: "import", node: file.Imports[0]},
|
||||
{desc: "type", node: dType},
|
||||
{desc: "var", node: dVar, skip: "not supported"},
|
||||
|
||||
@@ -4,7 +4,7 @@ Package interp provides a complete Go interpreter.
|
||||
For the Go language itself, refer to the official Go specification
|
||||
https://golang.org/ref/spec.
|
||||
|
||||
Importing packages
|
||||
# Importing packages
|
||||
|
||||
Packages can be imported in source or binary form, using the standard
|
||||
Go import statement. In source form, packages are searched first in the
|
||||
@@ -16,7 +16,7 @@ Binary form packages are compiled and linked with the interpreter
|
||||
executable, and exposed to scripts with the Use method. The extract
|
||||
subcommand of yaegi can be used to generate package wrappers.
|
||||
|
||||
Custom build tags
|
||||
# Custom build tags
|
||||
|
||||
Custom build tags allow to control which files in imported source
|
||||
packages are interpreted, in the same way as the "-tags" option of the
|
||||
|
||||
319
interp/generic.go
Normal file
319
interp/generic.go
Normal file
@@ -0,0 +1,319 @@
|
||||
package interp
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// adot produces an AST dot(1) directed acyclic graph for the given node. For debugging only.
|
||||
// func (n *node) adot() { n.astDot(dotWriter(n.interp.dotCmd), n.ident) }
|
||||
|
||||
// genAST returns a new AST where generic types are replaced by instantiated types.
|
||||
func genAST(sc *scope, root *node, types []*itype) (*node, bool, error) {
|
||||
typeParam := map[string]*node{}
|
||||
pindex := 0
|
||||
tname := ""
|
||||
rtname := ""
|
||||
recvrPtr := false
|
||||
fixNodes := []*node{}
|
||||
var gtree func(*node, *node) (*node, error)
|
||||
sname := root.child[0].ident + "["
|
||||
if root.kind == funcDecl {
|
||||
sname = root.child[1].ident + "["
|
||||
}
|
||||
|
||||
// Input type parameters must be resolved prior AST generation, as compilation
|
||||
// of generated AST may occur in a different scope.
|
||||
for _, t := range types {
|
||||
sname += t.id() + ","
|
||||
}
|
||||
sname = strings.TrimSuffix(sname, ",") + "]"
|
||||
|
||||
gtree = func(n, anc *node) (*node, error) {
|
||||
nod := copyNode(n, anc, false)
|
||||
switch n.kind {
|
||||
case funcDecl, funcType:
|
||||
nod.val = nod
|
||||
|
||||
case identExpr:
|
||||
// Replace generic type by instantiated one.
|
||||
nt, ok := typeParam[n.ident]
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
nod = copyNode(nt, anc, true)
|
||||
nod.typ = nt.typ
|
||||
|
||||
case indexExpr:
|
||||
// Catch a possible recursive generic type definition
|
||||
if root.kind != typeSpec {
|
||||
break
|
||||
}
|
||||
if root.child[0].ident != n.child[0].ident {
|
||||
break
|
||||
}
|
||||
nod := copyNode(n.child[0], anc, false)
|
||||
fixNodes = append(fixNodes, nod)
|
||||
return nod, nil
|
||||
|
||||
case fieldList:
|
||||
// Node is the type parameters list of a generic function.
|
||||
if root.kind == funcDecl && n.anc == root.child[2] && childPos(n) == 0 {
|
||||
// Fill the types lookup table used for type substitution.
|
||||
for _, c := range n.child {
|
||||
l := len(c.child) - 1
|
||||
for _, cc := range c.child[:l] {
|
||||
if pindex >= len(types) {
|
||||
return nil, cc.cfgErrorf("undefined type for %s", cc.ident)
|
||||
}
|
||||
t, err := nodeType(c.interp, sc, c.child[l])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := checkConstraint(types[pindex], t); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
typeParam[cc.ident] = copyNode(cc, cc.anc, false)
|
||||
typeParam[cc.ident].ident = types[pindex].id()
|
||||
typeParam[cc.ident].typ = types[pindex]
|
||||
pindex++
|
||||
}
|
||||
}
|
||||
// Skip type parameters specification, so generated func doesn't look generic.
|
||||
return nod, nil
|
||||
}
|
||||
|
||||
// Node is the receiver of a generic method.
|
||||
if root.kind == funcDecl && n.anc == root && childPos(n) == 0 && len(n.child) > 0 {
|
||||
rtn := n.child[0].child[1]
|
||||
// Method receiver is a generic type if it takes some type parameters.
|
||||
if rtn.kind == indexExpr || rtn.kind == indexListExpr || (rtn.kind == starExpr && (rtn.child[0].kind == indexExpr || rtn.child[0].kind == indexListExpr)) {
|
||||
if rtn.kind == starExpr {
|
||||
// Method receiver is a pointer on a generic type.
|
||||
rtn = rtn.child[0]
|
||||
recvrPtr = true
|
||||
}
|
||||
rtname = rtn.child[0].ident + "["
|
||||
for _, cc := range rtn.child[1:] {
|
||||
if pindex >= len(types) {
|
||||
return nil, cc.cfgErrorf("undefined type for %s", cc.ident)
|
||||
}
|
||||
it := types[pindex]
|
||||
typeParam[cc.ident] = copyNode(cc, cc.anc, false)
|
||||
typeParam[cc.ident].ident = it.id()
|
||||
typeParam[cc.ident].typ = it
|
||||
rtname += it.id() + ","
|
||||
pindex++
|
||||
}
|
||||
rtname = strings.TrimSuffix(rtname, ",") + "]"
|
||||
}
|
||||
}
|
||||
|
||||
// Node is the type parameters list of a generic type.
|
||||
if root.kind == typeSpec && n.anc == root && childPos(n) == 1 {
|
||||
// Fill the types lookup table used for type substitution.
|
||||
tname = n.anc.child[0].ident + "["
|
||||
for _, c := range n.child {
|
||||
l := len(c.child) - 1
|
||||
for _, cc := range c.child[:l] {
|
||||
if pindex >= len(types) {
|
||||
return nil, cc.cfgErrorf("undefined type for %s", cc.ident)
|
||||
}
|
||||
it := types[pindex]
|
||||
t, err := nodeType(c.interp, sc, c.child[l])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := checkConstraint(types[pindex], t); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
typeParam[cc.ident] = copyNode(cc, cc.anc, false)
|
||||
typeParam[cc.ident].ident = it.id()
|
||||
typeParam[cc.ident].typ = it
|
||||
tname += it.id() + ","
|
||||
pindex++
|
||||
}
|
||||
}
|
||||
tname = strings.TrimSuffix(tname, ",") + "]"
|
||||
return nod, nil
|
||||
}
|
||||
}
|
||||
|
||||
for _, c := range n.child {
|
||||
gn, err := gtree(c, nod)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nod.child = append(nod.child, gn)
|
||||
}
|
||||
return nod, nil
|
||||
}
|
||||
|
||||
if nod, found := root.interp.generic[sname]; found {
|
||||
return nod, true, nil
|
||||
}
|
||||
|
||||
r, err := gtree(root, root.anc)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
root.interp.generic[sname] = r
|
||||
r.param = append(r.param, types...)
|
||||
if tname != "" {
|
||||
for _, nod := range fixNodes {
|
||||
nod.ident = tname
|
||||
}
|
||||
r.child[0].ident = tname
|
||||
}
|
||||
if rtname != "" {
|
||||
// Replace method receiver type by synthetized ident.
|
||||
nod := r.child[0].child[0].child[1]
|
||||
if recvrPtr {
|
||||
nod = nod.child[0]
|
||||
}
|
||||
nod.kind = identExpr
|
||||
nod.ident = rtname
|
||||
nod.child = nil
|
||||
}
|
||||
// r.adot() // Used for debugging only.
|
||||
return r, false, nil
|
||||
}
|
||||
|
||||
func copyNode(n, anc *node, recursive bool) *node {
|
||||
var i interface{}
|
||||
nindex := atomic.AddInt64(&n.interp.nindex, 1)
|
||||
nod := &node{
|
||||
debug: n.debug,
|
||||
anc: anc,
|
||||
interp: n.interp,
|
||||
index: nindex,
|
||||
level: n.level,
|
||||
nleft: n.nleft,
|
||||
nright: n.nright,
|
||||
kind: n.kind,
|
||||
pos: n.pos,
|
||||
action: n.action,
|
||||
gen: n.gen,
|
||||
val: &i,
|
||||
rval: n.rval,
|
||||
ident: n.ident,
|
||||
meta: n.meta,
|
||||
}
|
||||
nod.start = nod
|
||||
if recursive {
|
||||
for _, c := range n.child {
|
||||
nod.child = append(nod.child, copyNode(c, nod, true))
|
||||
}
|
||||
}
|
||||
return nod
|
||||
}
|
||||
|
||||
func inferTypesFromCall(sc *scope, fun *node, args []*node) ([]*itype, error) {
|
||||
ftn := fun.typ.node
|
||||
// Fill the map of parameter types, indexed by type param ident.
|
||||
paramTypes := map[string]*itype{}
|
||||
for _, c := range ftn.child[0].child {
|
||||
typ, err := nodeType(fun.interp, sc, c.lastChild())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, cc := range c.child[:len(c.child)-1] {
|
||||
paramTypes[cc.ident] = typ
|
||||
}
|
||||
}
|
||||
|
||||
var inferTypes func(*itype, *itype) ([]*itype, error)
|
||||
inferTypes = func(param, input *itype) ([]*itype, error) {
|
||||
switch param.cat {
|
||||
case chanT, ptrT, sliceT:
|
||||
return inferTypes(param.val, input.val)
|
||||
|
||||
case mapT:
|
||||
k, err := inferTypes(param.key, input.key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v, err := inferTypes(param.val, input.val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(k, v...), nil
|
||||
|
||||
case structT:
|
||||
lt := []*itype{}
|
||||
for i, f := range param.field {
|
||||
nl, err := inferTypes(f.typ, input.field[i].typ)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lt = append(lt, nl...)
|
||||
}
|
||||
return lt, nil
|
||||
|
||||
case funcT:
|
||||
lt := []*itype{}
|
||||
for i, t := range param.arg {
|
||||
if i >= len(input.arg) {
|
||||
break
|
||||
}
|
||||
nl, err := inferTypes(t, input.arg[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lt = append(lt, nl...)
|
||||
}
|
||||
for i, t := range param.ret {
|
||||
if i >= len(input.ret) {
|
||||
break
|
||||
}
|
||||
nl, err := inferTypes(t, input.ret[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lt = append(lt, nl...)
|
||||
}
|
||||
return lt, nil
|
||||
|
||||
case nilT:
|
||||
if paramTypes[param.name] != nil {
|
||||
return []*itype{input}, nil
|
||||
}
|
||||
|
||||
case genericT:
|
||||
return []*itype{input}, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
types := []*itype{}
|
||||
for i, c := range ftn.child[1].child {
|
||||
typ, err := nodeType(fun.interp, sc, c.lastChild())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lt, err := inferTypes(typ, args[i].typ)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
types = append(types, lt...)
|
||||
}
|
||||
|
||||
return types, nil
|
||||
}
|
||||
|
||||
func checkConstraint(it, ct *itype) error {
|
||||
if len(ct.constraint) == 0 && len(ct.ulconstraint) == 0 {
|
||||
return nil
|
||||
}
|
||||
for _, c := range ct.constraint {
|
||||
if it.equals(c) || it.matchDefault(c) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
for _, c := range ct.ulconstraint {
|
||||
if it.underlying().equals(c) || it.matchDefault(c) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return it.node.cfgErrorf("%s does not implement %s", it.id(), ct.id())
|
||||
}
|
||||
@@ -21,6 +21,9 @@ func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if n.scope == nil {
|
||||
n.scope = sc
|
||||
}
|
||||
switch n.kind {
|
||||
case constDecl:
|
||||
// Early parse of constDecl subtree, to compute all constant
|
||||
@@ -144,6 +147,7 @@ func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([
|
||||
if n.typ, err = nodeType(interp, sc, n.child[2]); err != nil {
|
||||
return false
|
||||
}
|
||||
genericMethod := false
|
||||
ident := n.child[1].ident
|
||||
switch {
|
||||
case isMethod(n):
|
||||
@@ -153,8 +157,21 @@ func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([
|
||||
rcvr := n.child[0].child[0]
|
||||
rtn := rcvr.lastChild()
|
||||
typName, typPtr := rtn.ident, false
|
||||
// Identifies the receiver type name. It could be an ident, a
|
||||
// generic type (indexExpr), or a pointer on either lasts.
|
||||
if typName == "" {
|
||||
typName, typPtr = rtn.child[0].ident, true
|
||||
typName = rtn.child[0].ident
|
||||
switch rtn.kind {
|
||||
case starExpr:
|
||||
typPtr = true
|
||||
switch c := rtn.child[0]; c.kind {
|
||||
case indexExpr, indexListExpr:
|
||||
typName = c.child[0].ident
|
||||
genericMethod = true
|
||||
}
|
||||
case indexExpr, indexListExpr:
|
||||
genericMethod = true
|
||||
}
|
||||
}
|
||||
sym, _, found := sc.lookup(typName)
|
||||
if !found {
|
||||
@@ -162,7 +179,7 @@ func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([
|
||||
revisit = append(revisit, n)
|
||||
return false
|
||||
}
|
||||
if sym.kind != typeSym || (sym.node != nil && sym.node.kind == typeSpecAssign) {
|
||||
if sym.typ.path != pkgName {
|
||||
err = n.cfgErrorf("cannot define new methods on non-local type %s", baseType(sym.typ).id())
|
||||
return false
|
||||
}
|
||||
@@ -174,7 +191,15 @@ func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([
|
||||
elementType.addMethod(n)
|
||||
}
|
||||
rcvrtype.addMethod(n)
|
||||
n.child[0].child[0].lastChild().typ = rcvrtype
|
||||
rtn.typ = rcvrtype
|
||||
if rcvrtype.cat == genericT {
|
||||
// generate methods for already instantiated receivers
|
||||
for _, it := range rcvrtype.instance {
|
||||
if err = genMethod(interp, sc, it, n, it.node.anc.param); err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
case ident == "init":
|
||||
// init functions do not get declared as per the Go spec.
|
||||
default:
|
||||
@@ -185,9 +210,9 @@ func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([
|
||||
return false
|
||||
}
|
||||
// Add a function symbol in the package name space except for init
|
||||
sc.sym[n.child[1].ident] = &symbol{kind: funcSym, typ: n.typ, node: n, index: -1}
|
||||
sc.sym[ident] = &symbol{kind: funcSym, typ: n.typ, node: n, index: -1}
|
||||
}
|
||||
if !n.typ.isComplete() {
|
||||
if !n.typ.isComplete() && !genericMethod {
|
||||
revisit = append(revisit, n)
|
||||
}
|
||||
return false
|
||||
@@ -282,6 +307,15 @@ func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([
|
||||
return false
|
||||
}
|
||||
typeName := n.child[0].ident
|
||||
if len(n.child) > 2 {
|
||||
// Handle a generic type: skip definition as parameter is not instantiated yet.
|
||||
n.typ = genericOf(nil, typeName, pkgName, withNode(n.child[0]), withScope(sc))
|
||||
if _, exists := sc.sym[typeName]; !exists {
|
||||
sc.sym[typeName] = &symbol{kind: typeSym, node: n}
|
||||
}
|
||||
sc.sym[typeName].typ = n.typ
|
||||
return false
|
||||
}
|
||||
var typ *itype
|
||||
if typ, err = nodeType(interp, sc, n.child[1]); err != nil {
|
||||
err = nil
|
||||
@@ -289,6 +323,15 @@ func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([
|
||||
return false
|
||||
}
|
||||
|
||||
if n.kind == typeSpecAssign {
|
||||
// Create an aliased type in the current scope
|
||||
sc.sym[typeName] = &symbol{kind: typeSym, node: n, typ: typ}
|
||||
n.typ = typ
|
||||
break
|
||||
}
|
||||
|
||||
// else we are not an alias (typeSpec)
|
||||
|
||||
switch n.child[1].kind {
|
||||
case identExpr, selectorExpr:
|
||||
n.typ = namedOf(typ, pkgName, typeName, withNode(n.child[0]), withScope(sc))
|
||||
@@ -310,24 +353,15 @@ func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([
|
||||
}
|
||||
sym, exists := sc.sym[typeName]
|
||||
if !exists {
|
||||
sc.sym[typeName] = &symbol{kind: typeSym, node: n}
|
||||
} else {
|
||||
if sym.typ != nil && (len(sym.typ.method) > 0) {
|
||||
if n.kind == typeSpecAssign {
|
||||
err = n.cfgErrorf("cannot define new methods on non-local type %s", baseType(typ).id())
|
||||
return false
|
||||
}
|
||||
// Type has already been seen as a receiver in a method function
|
||||
for _, m := range sym.typ.method {
|
||||
n.typ.addMethod(m)
|
||||
}
|
||||
} else {
|
||||
// TODO(mpl): figure out how to detect redeclarations without breaking type aliases.
|
||||
// Allow redeclarations for now.
|
||||
sc.sym[typeName] = &symbol{kind: typeSym, node: n}
|
||||
sym = &symbol{kind: typeSym, node: n}
|
||||
sc.sym[typeName] = sym
|
||||
} else if sym.typ != nil && (len(sym.typ.method) > 0) {
|
||||
// Type has already been seen as a receiver in a method function
|
||||
for _, m := range sym.typ.method {
|
||||
n.typ.addMethod(m)
|
||||
}
|
||||
}
|
||||
sc.sym[typeName].typ = n.typ
|
||||
sym.typ = n.typ
|
||||
if !n.typ.isComplete() {
|
||||
revisit = append(revisit, n)
|
||||
}
|
||||
@@ -345,7 +379,7 @@ func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([
|
||||
func baseType(t *itype) *itype {
|
||||
for {
|
||||
switch t.cat {
|
||||
case ptrT, aliasT:
|
||||
case ptrT, linkedT:
|
||||
t = t.val
|
||||
default:
|
||||
return t
|
||||
@@ -417,7 +451,7 @@ func definedType(typ *itype) error {
|
||||
return err
|
||||
}
|
||||
fallthrough
|
||||
case aliasT, arrayT, chanT, chanSendT, chanRecvT, ptrT, variadicT:
|
||||
case linkedT, arrayT, chanT, chanSendT, chanRecvT, ptrT, variadicT:
|
||||
if err := definedType(typ.val); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
328
interp/interp.go
328
interp/interp.go
@@ -4,16 +4,12 @@ import (
|
||||
"bufio"
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"go/constant"
|
||||
"go/scanner"
|
||||
"go/token"
|
||||
"io"
|
||||
"io/fs"
|
||||
"log"
|
||||
"math/bits"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
@@ -29,33 +25,35 @@ import (
|
||||
|
||||
// Interpreter node structure for AST and CFG.
|
||||
type node struct {
|
||||
debug *nodeDebugData // debug info
|
||||
child []*node // child subtrees (AST)
|
||||
anc *node // ancestor (AST)
|
||||
start *node // entry point in subtree (CFG)
|
||||
tnext *node // true branch successor (CFG)
|
||||
fnext *node // false branch successor (CFG)
|
||||
interp *Interpreter // interpreter context
|
||||
frame *frame // frame pointer used for closures only (TODO: suppress this)
|
||||
index int64 // node index (dot display)
|
||||
findex int // index of value in frame or frame size (func def, type def)
|
||||
level int // number of frame indirections to access value
|
||||
nleft int // number of children in left part (assign) or indicates preceding type (compositeLit)
|
||||
nright int // number of children in right part (assign)
|
||||
kind nkind // kind of node
|
||||
pos token.Pos // position in source code, relative to fset
|
||||
sym *symbol // associated symbol
|
||||
typ *itype // type of value in frame, or nil
|
||||
recv *receiver // method receiver node for call, or nil
|
||||
types []reflect.Type // frame types, used by function literals only
|
||||
scope *scope // frame scope
|
||||
action action // action
|
||||
exec bltn // generated function to execute
|
||||
gen bltnGenerator // generator function to produce above bltn
|
||||
val interface{} // static generic value (CFG execution)
|
||||
rval reflect.Value // reflection value to let runtime access interpreter (CFG)
|
||||
ident string // set if node is a var or func
|
||||
meta interface{} // meta stores meta information between gta runs, like errors
|
||||
debug *nodeDebugData // debug info
|
||||
child []*node // child subtrees (AST)
|
||||
anc *node // ancestor (AST)
|
||||
param []*itype // generic parameter nodes (AST)
|
||||
start *node // entry point in subtree (CFG)
|
||||
tnext *node // true branch successor (CFG)
|
||||
fnext *node // false branch successor (CFG)
|
||||
interp *Interpreter // interpreter context
|
||||
frame *frame // frame pointer used for closures only (TODO: suppress this)
|
||||
index int64 // node index (dot display)
|
||||
findex int // index of value in frame or frame size (func def, type def)
|
||||
level int // number of frame indirections to access value
|
||||
nleft int // number of children in left part (assign) or indicates preceding type (compositeLit)
|
||||
nright int // number of children in right part (assign)
|
||||
kind nkind // kind of node
|
||||
pos token.Pos // position in source code, relative to fset
|
||||
sym *symbol // associated symbol
|
||||
typ *itype // type of value in frame, or nil
|
||||
recv *receiver // method receiver node for call, or nil
|
||||
types []reflect.Type // frame types, used by function literals only
|
||||
scope *scope // frame scope
|
||||
action action // action
|
||||
exec bltn // generated function to execute
|
||||
gen bltnGenerator // generator function to produce above bltn
|
||||
val interface{} // static generic value (CFG execution)
|
||||
rval reflect.Value // reflection value to let runtime access interpreter (CFG)
|
||||
ident string // set if node is a var or func
|
||||
redeclared bool // set if node is a redeclared variable (CFG)
|
||||
meta interface{} // meta stores meta information between gta runs, like errors
|
||||
}
|
||||
|
||||
func (n *node) shouldBreak() bool {
|
||||
@@ -108,7 +106,7 @@ type receiver struct {
|
||||
type frame struct {
|
||||
// id is an atomic counter used for cancellation, only accessed
|
||||
// via newFrame/runid/setrunid/clone.
|
||||
// Located at start of struct to ensure proper aligment.
|
||||
// Located at start of struct to ensure proper alignment.
|
||||
id uint64
|
||||
|
||||
debug *frameDebugData
|
||||
@@ -186,14 +184,14 @@ type opt struct {
|
||||
noRun bool // compile, but do not run
|
||||
fastChan bool // disable cancellable chan operations
|
||||
specialStdio bool // allows os.Stdin, os.Stdout, os.Stderr to not be file descriptors
|
||||
unrestricted bool // allow use of non sandboxed symbols
|
||||
unrestricted bool // allow use of non-sandboxed symbols
|
||||
}
|
||||
|
||||
// Interpreter contains global resources and state.
|
||||
type Interpreter struct {
|
||||
// id is an atomic counter counter used for run cancellation,
|
||||
// id is an atomic counter used for run cancellation,
|
||||
// only accessed via runid/stop
|
||||
// Located at start of struct to ensure proper alignment on 32 bit
|
||||
// Located at start of struct to ensure proper alignment on 32-bit
|
||||
// architectures.
|
||||
id uint64
|
||||
|
||||
@@ -219,6 +217,7 @@ type Interpreter struct {
|
||||
pkgNames map[string]string // package names, indexed by import path
|
||||
done chan struct{} // for cancellation of channel operations
|
||||
roots []*node
|
||||
generic map[string]*node
|
||||
|
||||
hooks *hooks // symbol hooks
|
||||
|
||||
@@ -339,6 +338,7 @@ func New(options Options) *Interpreter {
|
||||
pkgNames: map[string]string{},
|
||||
rdir: map[string]bool{},
|
||||
hooks: &hooks{},
|
||||
generic: map[string]*node{},
|
||||
}
|
||||
|
||||
if i.opt.stdin = options.Stdin; i.opt.stdin == nil {
|
||||
@@ -405,28 +405,33 @@ func New(options Options) *Interpreter {
|
||||
}
|
||||
|
||||
const (
|
||||
bltnAppend = "append"
|
||||
bltnCap = "cap"
|
||||
bltnClose = "close"
|
||||
bltnComplex = "complex"
|
||||
bltnImag = "imag"
|
||||
bltnCopy = "copy"
|
||||
bltnDelete = "delete"
|
||||
bltnLen = "len"
|
||||
bltnMake = "make"
|
||||
bltnNew = "new"
|
||||
bltnPanic = "panic"
|
||||
bltnPrint = "print"
|
||||
bltnPrintln = "println"
|
||||
bltnReal = "real"
|
||||
bltnRecover = "recover"
|
||||
bltnAlignof = "unsafe.Alignof"
|
||||
bltnAppend = "append"
|
||||
bltnCap = "cap"
|
||||
bltnClose = "close"
|
||||
bltnComplex = "complex"
|
||||
bltnImag = "imag"
|
||||
bltnCopy = "copy"
|
||||
bltnDelete = "delete"
|
||||
bltnLen = "len"
|
||||
bltnMake = "make"
|
||||
bltnNew = "new"
|
||||
bltnOffsetof = "unsafe.Offsetof"
|
||||
bltnPanic = "panic"
|
||||
bltnPrint = "print"
|
||||
bltnPrintln = "println"
|
||||
bltnReal = "real"
|
||||
bltnRecover = "recover"
|
||||
bltnSizeof = "unsafe.Sizeof"
|
||||
)
|
||||
|
||||
func initUniverse() *scope {
|
||||
sc := &scope{global: true, sym: map[string]*symbol{
|
||||
// predefined Go types
|
||||
"any": {kind: typeSym, typ: &itype{cat: interfaceT, str: "any"}},
|
||||
"bool": {kind: typeSym, typ: &itype{cat: boolT, name: "bool", str: "bool"}},
|
||||
"byte": {kind: typeSym, typ: &itype{cat: uint8T, name: "uint8", str: "uint8"}},
|
||||
"comparable": {kind: typeSym, typ: &itype{cat: comparableT, name: "comparable", str: "comparable"}},
|
||||
"complex64": {kind: typeSym, typ: &itype{cat: complex64T, name: "complex64", str: "complex64"}},
|
||||
"complex128": {kind: typeSym, typ: &itype{cat: complex128T, name: "complex128", str: "complex128"}},
|
||||
"error": {kind: typeSym, typ: &itype{cat: errorT, name: "error", str: "error"}},
|
||||
@@ -448,9 +453,9 @@ func initUniverse() *scope {
|
||||
"uintptr": {kind: typeSym, typ: &itype{cat: uintptrT, name: "uintptr", str: "uintptr"}},
|
||||
|
||||
// predefined Go constants
|
||||
"false": {kind: constSym, typ: untypedBool(), rval: reflect.ValueOf(false)},
|
||||
"true": {kind: constSym, typ: untypedBool(), rval: reflect.ValueOf(true)},
|
||||
"iota": {kind: constSym, typ: untypedInt()},
|
||||
"false": {kind: constSym, typ: untypedBool(nil), rval: reflect.ValueOf(false)},
|
||||
"true": {kind: constSym, typ: untypedBool(nil), rval: reflect.ValueOf(true)},
|
||||
"iota": {kind: constSym, typ: untypedInt(nil)},
|
||||
|
||||
// predefined Go zero value
|
||||
"nil": {typ: &itype{cat: nilT, untyped: true, str: "nil"}},
|
||||
@@ -545,62 +550,6 @@ func (interp *Interpreter) EvalTest(path string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Symbols returns a map of interpreter exported symbol values for the given
|
||||
// import path. If the argument is the empty string, all known symbols are
|
||||
// returned.
|
||||
func (interp *Interpreter) Symbols(importPath string) Exports {
|
||||
m := map[string]map[string]reflect.Value{}
|
||||
interp.mutex.RLock()
|
||||
defer interp.mutex.RUnlock()
|
||||
|
||||
for k, v := range interp.srcPkg {
|
||||
if importPath != "" && k != importPath {
|
||||
continue
|
||||
}
|
||||
syms := map[string]reflect.Value{}
|
||||
for n, s := range v {
|
||||
if !canExport(n) {
|
||||
// Skip private non-exported symbols.
|
||||
continue
|
||||
}
|
||||
switch s.kind {
|
||||
case constSym:
|
||||
syms[n] = s.rval
|
||||
case funcSym:
|
||||
syms[n] = genFunctionWrapper(s.node)(interp.frame)
|
||||
case varSym:
|
||||
syms[n] = interp.frame.data[s.index]
|
||||
case typeSym:
|
||||
syms[n] = reflect.New(s.typ.TypeOf())
|
||||
}
|
||||
}
|
||||
|
||||
if len(syms) > 0 {
|
||||
m[k] = syms
|
||||
}
|
||||
|
||||
if importPath != "" {
|
||||
return m
|
||||
}
|
||||
}
|
||||
|
||||
if importPath != "" && len(m) > 0 {
|
||||
return m
|
||||
}
|
||||
|
||||
for k, v := range interp.binPkg {
|
||||
if importPath != "" && k != importPath {
|
||||
continue
|
||||
}
|
||||
m[k] = v
|
||||
if importPath != "" {
|
||||
return m
|
||||
}
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func isFile(filesystem fs.FS, path string) bool {
|
||||
fi, err := fs.Stat(filesystem, path)
|
||||
return err == nil && fi.Mode().IsRegular()
|
||||
@@ -662,167 +611,6 @@ func (interp *Interpreter) stop() {
|
||||
|
||||
func (interp *Interpreter) runid() uint64 { return atomic.LoadUint64(&interp.id) }
|
||||
|
||||
// getWrapper returns the wrapper type of the corresponding interface, or nil if not found.
|
||||
func (interp *Interpreter) getWrapper(t reflect.Type) reflect.Type {
|
||||
if p, ok := interp.binPkg[t.PkgPath()]; ok {
|
||||
return p["_"+t.Name()].Type().Elem()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Use loads binary runtime symbols in the interpreter context so
|
||||
// they can be used in interpreted code.
|
||||
func (interp *Interpreter) Use(values Exports) error {
|
||||
for k, v := range values {
|
||||
importPath := path.Dir(k)
|
||||
packageName := path.Base(k)
|
||||
|
||||
if k == "." && v["MapTypes"].IsValid() {
|
||||
// Use mapping for special interface wrappers.
|
||||
for kk, vv := range v["MapTypes"].Interface().(map[reflect.Value][]reflect.Type) {
|
||||
interp.mapTypes[kk] = vv
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if importPath == "." {
|
||||
return fmt.Errorf("export path %[1]q is missing a package name; did you mean '%[1]s/%[1]s'?", k)
|
||||
}
|
||||
|
||||
if importPath == selfPrefix {
|
||||
interp.hooks.Parse(v)
|
||||
continue
|
||||
}
|
||||
|
||||
if interp.binPkg[importPath] == nil {
|
||||
interp.binPkg[importPath] = make(map[string]reflect.Value)
|
||||
interp.pkgNames[importPath] = packageName
|
||||
}
|
||||
|
||||
for s, sym := range v {
|
||||
interp.binPkg[importPath][s] = sym
|
||||
}
|
||||
if k == selfPath {
|
||||
interp.binPkg[importPath]["Self"] = reflect.ValueOf(interp)
|
||||
}
|
||||
}
|
||||
|
||||
// Checks if input values correspond to stdlib packages by looking for one
|
||||
// well known stdlib package path.
|
||||
if _, ok := values["fmt/fmt"]; ok {
|
||||
fixStdlib(interp)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// fixStdlib redefines interpreter stdlib symbols to use the standard input,
|
||||
// output and errror assigned to the interpreter. The changes are limited to
|
||||
// the interpreter only.
|
||||
// Note that it is possible to escape the virtualized stdio by
|
||||
// read/write directly to file descriptors 0, 1, 2.
|
||||
func fixStdlib(interp *Interpreter) {
|
||||
p := interp.binPkg["fmt"]
|
||||
if p == nil {
|
||||
return
|
||||
}
|
||||
|
||||
stdin, stdout, stderr := interp.stdin, interp.stdout, interp.stderr
|
||||
|
||||
p["Print"] = reflect.ValueOf(func(a ...interface{}) (n int, err error) { return fmt.Fprint(stdout, a...) })
|
||||
p["Printf"] = reflect.ValueOf(func(f string, a ...interface{}) (n int, err error) { return fmt.Fprintf(stdout, f, a...) })
|
||||
p["Println"] = reflect.ValueOf(func(a ...interface{}) (n int, err error) { return fmt.Fprintln(stdout, a...) })
|
||||
|
||||
p["Scan"] = reflect.ValueOf(func(a ...interface{}) (n int, err error) { return fmt.Fscan(stdin, a...) })
|
||||
p["Scanf"] = reflect.ValueOf(func(f string, a ...interface{}) (n int, err error) { return fmt.Fscanf(stdin, f, a...) })
|
||||
p["Scanln"] = reflect.ValueOf(func(a ...interface{}) (n int, err error) { return fmt.Fscanln(stdin, a...) })
|
||||
|
||||
// Update mapTypes to virtualized symbols as well.
|
||||
interp.mapTypes[p["Print"]] = interp.mapTypes[reflect.ValueOf(fmt.Print)]
|
||||
interp.mapTypes[p["Printf"]] = interp.mapTypes[reflect.ValueOf(fmt.Printf)]
|
||||
interp.mapTypes[p["Println"]] = interp.mapTypes[reflect.ValueOf(fmt.Println)]
|
||||
interp.mapTypes[p["Scan"]] = interp.mapTypes[reflect.ValueOf(fmt.Scan)]
|
||||
interp.mapTypes[p["Scanf"]] = interp.mapTypes[reflect.ValueOf(fmt.Scanf)]
|
||||
interp.mapTypes[p["Scanln"]] = interp.mapTypes[reflect.ValueOf(fmt.Scanln)]
|
||||
|
||||
if p = interp.binPkg["flag"]; p != nil {
|
||||
c := flag.NewFlagSet(os.Args[0], flag.PanicOnError)
|
||||
c.SetOutput(stderr)
|
||||
p["CommandLine"] = reflect.ValueOf(&c).Elem()
|
||||
}
|
||||
|
||||
if p = interp.binPkg["log"]; p != nil {
|
||||
l := log.New(stderr, "", log.LstdFlags)
|
||||
// Restrict Fatal symbols to panic instead of exit.
|
||||
p["Fatal"] = reflect.ValueOf(l.Panic)
|
||||
p["Fatalf"] = reflect.ValueOf(l.Panicf)
|
||||
p["Fatalln"] = reflect.ValueOf(l.Panicln)
|
||||
|
||||
p["Flags"] = reflect.ValueOf(l.Flags)
|
||||
p["Output"] = reflect.ValueOf(l.Output)
|
||||
p["Panic"] = reflect.ValueOf(l.Panic)
|
||||
p["Panicf"] = reflect.ValueOf(l.Panicf)
|
||||
p["Panicln"] = reflect.ValueOf(l.Panicln)
|
||||
p["Prefix"] = reflect.ValueOf(l.Prefix)
|
||||
p["Print"] = reflect.ValueOf(l.Print)
|
||||
p["Printf"] = reflect.ValueOf(l.Printf)
|
||||
p["Println"] = reflect.ValueOf(l.Println)
|
||||
p["SetFlags"] = reflect.ValueOf(l.SetFlags)
|
||||
p["SetOutput"] = reflect.ValueOf(l.SetOutput)
|
||||
p["SetPrefix"] = reflect.ValueOf(l.SetPrefix)
|
||||
p["Writer"] = reflect.ValueOf(l.Writer)
|
||||
|
||||
// Update mapTypes to virtualized symbols as well.
|
||||
interp.mapTypes[p["Print"]] = interp.mapTypes[reflect.ValueOf(log.Print)]
|
||||
interp.mapTypes[p["Printf"]] = interp.mapTypes[reflect.ValueOf(log.Printf)]
|
||||
interp.mapTypes[p["Println"]] = interp.mapTypes[reflect.ValueOf(log.Println)]
|
||||
interp.mapTypes[p["Panic"]] = interp.mapTypes[reflect.ValueOf(log.Panic)]
|
||||
interp.mapTypes[p["Panicf"]] = interp.mapTypes[reflect.ValueOf(log.Panicf)]
|
||||
interp.mapTypes[p["Panicln"]] = interp.mapTypes[reflect.ValueOf(log.Panicln)]
|
||||
}
|
||||
|
||||
if p = interp.binPkg["os"]; p != nil {
|
||||
p["Args"] = reflect.ValueOf(&interp.args).Elem()
|
||||
if interp.specialStdio {
|
||||
// Inherit streams from interpreter even if they do not have a file descriptor.
|
||||
p["Stdin"] = reflect.ValueOf(&stdin).Elem()
|
||||
p["Stdout"] = reflect.ValueOf(&stdout).Elem()
|
||||
p["Stderr"] = reflect.ValueOf(&stderr).Elem()
|
||||
} else {
|
||||
// Inherits streams from interpreter only if they have a file descriptor and preserve original type.
|
||||
if s, ok := stdin.(*os.File); ok {
|
||||
p["Stdin"] = reflect.ValueOf(&s).Elem()
|
||||
}
|
||||
if s, ok := stdout.(*os.File); ok {
|
||||
p["Stdout"] = reflect.ValueOf(&s).Elem()
|
||||
}
|
||||
if s, ok := stderr.(*os.File); ok {
|
||||
p["Stderr"] = reflect.ValueOf(&s).Elem()
|
||||
}
|
||||
}
|
||||
if !interp.unrestricted {
|
||||
// In restricted mode, scripts can only access to a passed virtualized env, and can not write the real one.
|
||||
getenv := func(key string) string { return interp.env[key] }
|
||||
p["Clearenv"] = reflect.ValueOf(func() { interp.env = map[string]string{} })
|
||||
p["ExpandEnv"] = reflect.ValueOf(func(s string) string { return os.Expand(s, getenv) })
|
||||
p["Getenv"] = reflect.ValueOf(getenv)
|
||||
p["LookupEnv"] = reflect.ValueOf(func(key string) (s string, ok bool) { s, ok = interp.env[key]; return })
|
||||
p["Setenv"] = reflect.ValueOf(func(key, value string) error { interp.env[key] = value; return nil })
|
||||
p["Unsetenv"] = reflect.ValueOf(func(key string) error { delete(interp.env, key); return nil })
|
||||
p["Environ"] = reflect.ValueOf(func() (a []string) {
|
||||
for k, v := range interp.env {
|
||||
a = append(a, k+"="+v)
|
||||
}
|
||||
return
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if p = interp.binPkg["math/bits"]; p != nil {
|
||||
// Do not trust extracted value maybe from another arch.
|
||||
p["UintSize"] = reflect.ValueOf(constant.MakeInt64(bits.UintSize))
|
||||
}
|
||||
}
|
||||
|
||||
// ignoreScannerError returns true if the error from Go scanner can be safely ignored
|
||||
// to let the caller grab one more line before retrying to parse its input.
|
||||
func ignoreScannerError(e *scanner.Error, s string) bool {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package interp_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"go/build"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -14,6 +16,13 @@ import (
|
||||
"github.com/traefik/yaegi/stdlib/unsafe"
|
||||
)
|
||||
|
||||
// The following tests depend on an incompatible language change in go1.22, where `for` variables are now
|
||||
// defined in body (thus reallocated at each loop). We skip them until both supported versions behave the same.
|
||||
// We will remove this in Go1.23.
|
||||
var testsToSkipGo122 = map[string]bool{"closure9.go": true, "closure10.go": true, "closure11.go": true, "closure12.go": true}
|
||||
|
||||
var go122 = strings.HasPrefix(runtime.Version(), "go1.22")
|
||||
|
||||
func TestInterpConsistencyBuild(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("short mode")
|
||||
@@ -48,6 +57,9 @@ func TestInterpConsistencyBuild(t *testing.T) {
|
||||
file.Name() == "fun23.go" || // expect error
|
||||
file.Name() == "fun24.go" || // expect error
|
||||
file.Name() == "fun25.go" || // expect error
|
||||
file.Name() == "gen7.go" || // expect error
|
||||
file.Name() == "gen8.go" || // expect error
|
||||
file.Name() == "gen9.go" || // expect error
|
||||
file.Name() == "if2.go" || // expect error
|
||||
file.Name() == "import6.go" || // expect error
|
||||
file.Name() == "init1.go" || // expect error
|
||||
@@ -86,6 +98,7 @@ func TestInterpConsistencyBuild(t *testing.T) {
|
||||
file.Name() == "redeclaration-global5.go" || // expect error
|
||||
file.Name() == "redeclaration-global6.go" || // expect error
|
||||
file.Name() == "redeclaration-global7.go" || // expect error
|
||||
file.Name() == "panic0.go" || // expect error
|
||||
file.Name() == "pkgname0.go" || // has deps
|
||||
file.Name() == "pkgname1.go" || // expect error
|
||||
file.Name() == "pkgname2.go" || // has deps
|
||||
@@ -106,6 +119,7 @@ func TestInterpConsistencyBuild(t *testing.T) {
|
||||
file.Name() == "range9.go" || // expect error
|
||||
file.Name() == "unsafe6.go" || // needs go.mod to be 1.17
|
||||
file.Name() == "unsafe7.go" || // needs go.mod to be 1.17
|
||||
file.Name() == "type24.go" || // expect error
|
||||
file.Name() == "type27.go" || // expect error
|
||||
file.Name() == "type28.go" || // expect error
|
||||
file.Name() == "type29.go" || // expect error
|
||||
@@ -115,6 +129,13 @@ func TestInterpConsistencyBuild(t *testing.T) {
|
||||
file.Name() == "type33.go" { // expect error
|
||||
continue
|
||||
}
|
||||
// Skip some tests which are problematic in go1.21 only.
|
||||
if go121 && testsToSkipGo121[file.Name()] {
|
||||
continue
|
||||
}
|
||||
if go122 && testsToSkipGo122[file.Name()] {
|
||||
continue
|
||||
}
|
||||
|
||||
file := file
|
||||
t.Run(file.Name(), func(t *testing.T) {
|
||||
@@ -172,7 +193,7 @@ func TestInterpConsistencyBuild(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if string(outInterp) != string(outRun) {
|
||||
if !bytes.Equal(outInterp, outRun) {
|
||||
t.Errorf("\nGot: %q,\n want: %q", string(outInterp), string(outRun))
|
||||
}
|
||||
})
|
||||
@@ -183,17 +204,18 @@ func TestInterpErrorConsistency(t *testing.T) {
|
||||
testCases := []struct {
|
||||
fileName string
|
||||
expectedInterp string
|
||||
expectedStderr string
|
||||
expectedExec string
|
||||
}{
|
||||
{
|
||||
fileName: "assign11.go",
|
||||
expectedInterp: "6:2: assignment mismatch: 3 variables but fmt.Println returns 2 values",
|
||||
expectedExec: "6:10: assignment mismatch: 3 variables but fmt.Println returns 2 values",
|
||||
expectedExec: "6:12: assignment mismatch: 3 variables but fmt.Println returns 2 values",
|
||||
},
|
||||
{
|
||||
fileName: "assign12.go",
|
||||
expectedInterp: "6:2: assignment mismatch: 3 variables but fmt.Println returns 2 values",
|
||||
expectedExec: "6:10: assignment mismatch: 3 variables but fmt.Println returns 2 values",
|
||||
expectedExec: "6:13: assignment mismatch: 3 variables but fmt.Println returns 2 values",
|
||||
},
|
||||
{
|
||||
fileName: "bad0.go",
|
||||
@@ -213,46 +235,47 @@ func TestInterpErrorConsistency(t *testing.T) {
|
||||
{
|
||||
fileName: "const9.go",
|
||||
expectedInterp: "5:2: constant definition loop",
|
||||
expectedExec: "5:2: constant definition loop",
|
||||
expectedExec: "5:2: initialization",
|
||||
},
|
||||
{
|
||||
fileName: "if2.go",
|
||||
expectedInterp: "7:5: non-bool used as if condition",
|
||||
expectedExec: "7:2: non-bool i % 1000000 (type int) used as if condition",
|
||||
expectedExec: "7:5: non-boolean condition in if statement",
|
||||
},
|
||||
{
|
||||
fileName: "for7.go",
|
||||
expectedInterp: "4:14: non-bool used as for condition",
|
||||
expectedExec: "4:2: non-bool i (type int) used as for condition",
|
||||
expectedExec: "4:14: non-boolean condition in for statement",
|
||||
},
|
||||
{
|
||||
fileName: "fun21.go",
|
||||
expectedInterp: "4:2: not enough arguments to return",
|
||||
expectedExec: "4:2: not enough arguments to return",
|
||||
expectedExec: "4:2: not enough return values",
|
||||
},
|
||||
{
|
||||
fileName: "fun22.go",
|
||||
expectedInterp: "6:2: not enough arguments in call to time.Date",
|
||||
expectedExec: "6:11: not enough arguments in call to time.Date",
|
||||
expectedExec: "6:2: not enough arguments in call to time.Date",
|
||||
},
|
||||
{
|
||||
fileName: "fun23.go",
|
||||
expectedInterp: "3:17: too many arguments to return",
|
||||
expectedExec: "3:17: too many arguments to return",
|
||||
expectedExec: "3:24: too many return values",
|
||||
},
|
||||
{
|
||||
fileName: "issue-1093.go",
|
||||
expectedInterp: "9:6: cannot use type untyped string as type int in assignment",
|
||||
expectedExec: `9:4: cannot use "a" + b() (type string) as type int in assignment`,
|
||||
expectedExec: `9:6: cannot use "a" + b() (value of type string)`,
|
||||
},
|
||||
{
|
||||
fileName: "op1.go",
|
||||
expectedInterp: "5:2: invalid operation: mismatched types int and untyped float",
|
||||
expectedExec: "5:4: constant 1.3 truncated to integer",
|
||||
expectedExec: "5:7: 1.3 (untyped float constant) truncated to int",
|
||||
},
|
||||
{
|
||||
fileName: "bltn0.go",
|
||||
expectedInterp: "4:7: use of builtin println not in function call",
|
||||
expectedExec: "4:7: println (built-in) must be called",
|
||||
},
|
||||
{
|
||||
fileName: "import6.go",
|
||||
@@ -267,29 +290,40 @@ func TestInterpErrorConsistency(t *testing.T) {
|
||||
{
|
||||
fileName: "switch9.go",
|
||||
expectedInterp: "9:3: cannot fallthrough in type switch",
|
||||
expectedExec: "9:3: cannot fallthrough in type switch",
|
||||
expectedExec: "fallthrough",
|
||||
},
|
||||
{
|
||||
fileName: "switch13.go",
|
||||
expectedInterp: "9:2: i is not a type",
|
||||
expectedExec: "9:2: i (type interface {}) is not a type",
|
||||
expectedExec: "9:7: i (variable of type interface{}) is not a type",
|
||||
},
|
||||
{
|
||||
fileName: "switch19.go",
|
||||
expectedInterp: "37:2: duplicate case Bir in type switch",
|
||||
expectedExec: "37:2: duplicate case Bir in type switch",
|
||||
expectedExec: "37:7: duplicate case Bir in type switch",
|
||||
},
|
||||
{
|
||||
fileName: "panic0.go",
|
||||
expectedInterp: "stop!",
|
||||
expectedStderr: `
|
||||
../_test/panic0.go:16:2: panic: main.baz(...)
|
||||
../_test/panic0.go:12:2: panic: main.bar(...)
|
||||
../_test/panic0.go:8:2: panic: main.foo(...)
|
||||
../_test/panic0.go:4:2: panic: main.main(...)
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.fileName, func(t *testing.T) {
|
||||
if len(test.expectedInterp) == 0 && len(test.expectedExec) == 0 {
|
||||
t.Fatal("at least expectedInterp must be define")
|
||||
if test.expectedInterp == "" && test.expectedExec == "" {
|
||||
t.Fatal("at least expectedInterp must be defined")
|
||||
}
|
||||
|
||||
filePath := filepath.Join("..", "_test", test.fileName)
|
||||
|
||||
i := interp.New(interp.Options{GoPath: build.Default.GOPATH})
|
||||
var stderr bytes.Buffer
|
||||
i := interp.New(interp.Options{GoPath: build.Default.GOPATH, Stderr: &stderr})
|
||||
if err := i.Use(stdlib.Symbols); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -302,6 +336,12 @@ func TestInterpErrorConsistency(t *testing.T) {
|
||||
if !strings.Contains(errEval.Error(), test.expectedInterp) {
|
||||
t.Errorf("got %q, want: %q", errEval.Error(), test.expectedInterp)
|
||||
}
|
||||
if test.expectedStderr != "" {
|
||||
exp, got := strings.TrimSpace(test.expectedStderr), strings.TrimSpace(stderr.String())
|
||||
if exp != got {
|
||||
t.Errorf("got %q, want: %q", got, exp)
|
||||
}
|
||||
}
|
||||
|
||||
cmd := exec.Command("go", "run", filePath)
|
||||
outRun, errExec := cmd.CombinedOutput()
|
||||
@@ -310,7 +350,7 @@ func TestInterpErrorConsistency(t *testing.T) {
|
||||
t.Fatal("An error is expected but got none.")
|
||||
}
|
||||
|
||||
if len(test.expectedExec) == 0 && !strings.Contains(string(outRun), test.expectedInterp) {
|
||||
if test.expectedExec == "" && !strings.Contains(string(outRun), test.expectedInterp) {
|
||||
t.Errorf("got %q, want: %q", string(outRun), test.expectedInterp)
|
||||
} else if !strings.Contains(string(outRun), test.expectedExec) {
|
||||
t.Errorf("got %q, want: %q", string(outRun), test.expectedExec)
|
||||
|
||||
@@ -133,6 +133,9 @@ func TestEvalAssign(t *testing.T) {
|
||||
{src: "j := true || _", err: "1:33: cannot use _ as value"},
|
||||
{src: "j := true && _", err: "1:33: cannot use _ as value"},
|
||||
{src: "j := interface{}(int(1)); j.(_)", err: "1:54: cannot use _ as value"},
|
||||
{src: "ff := func() (a, b, c int) {return 1, 2, 3}; x, y, x := ff()", err: "1:73: x repeated on left side of :="},
|
||||
{src: "xx := 1; xx, _ := 2, 3", err: "1:37: no new variables on left side of :="},
|
||||
{src: "1 = 2", err: "1:28: cannot assign to 1 (untyped int constant)"},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -216,6 +219,8 @@ func TestEvalTypeSpec(t *testing.T) {
|
||||
runTests(t, i, []testCase{
|
||||
{src: `type _ struct{}`, err: "1:19: cannot use _ as value"},
|
||||
{src: `a := struct{a, _ int}{32, 0}`, res: "{32 0}"},
|
||||
{src: "type A int; type A = string", err: "1:31: A redeclared in this block"},
|
||||
{src: "type B int; type B string", err: "1:31: B redeclared in this block"},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -540,8 +545,8 @@ func TestEvalSliceExpression(t *testing.T) {
|
||||
{src: `a := (&[]int{0,1,2,3})[1:3]`, err: "1:33: cannot slice type *[]int"},
|
||||
{src: `a := "hello"[1:3:4]`, err: "1:45: invalid operation: 3-index slice of string"},
|
||||
{src: `ar := [3]int{0,1,2}; a := ar[:4]`, err: "1:58: index int is out of bounds"},
|
||||
{src: `a := []int{0,1,2,3}[1::4]`, err: "1:49: 2nd index required in 3-index slice"},
|
||||
{src: `a := []int{0,1,2,3}[1:3:]`, err: "1:51: 3rd index required in 3-index slice"},
|
||||
{src: `a := []int{0,1,2,3}[1::4]`, err: "index required in 3-index slice"},
|
||||
{src: `a := []int{0,1,2,3}[1:3:]`, err: "index required in 3-index slice"},
|
||||
{src: `a := []int{0,1,2}[3:1]`, err: "invalid index values, must be low <= high <= max"},
|
||||
{pre: func() { eval(t, i, `type Str = string; var r Str = "truc"`) }, src: `r[1]`, res: "114"},
|
||||
{src: `_[12]`, err: "1:28: cannot use _ as value"},
|
||||
@@ -640,6 +645,8 @@ func TestEvalChan(t *testing.T) {
|
||||
return ok && msg == "ping"
|
||||
})()`, res: "true",
|
||||
},
|
||||
{src: `a :=5; a <- 4`, err: "cannot send to non-channel int"},
|
||||
{src: `a :=5; b := <-a`, err: "cannot receive from non-channel int"},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -701,6 +708,7 @@ func TestEvalCall(t *testing.T) {
|
||||
{src: ` test := func(a, b int) int { return a }
|
||||
blah := func() (int, float64) { return 1, 1.1 }
|
||||
a := test(blah())`, err: "3:15: cannot use func() (int,float64) as type (int,int)"},
|
||||
{src: "func f()", err: "missing function body"},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1010,7 +1018,7 @@ const goMinorVersionTest = 16
|
||||
|
||||
func TestHasIOFS(t *testing.T) {
|
||||
code := `
|
||||
// +build go1.16
|
||||
// +build go1.18
|
||||
|
||||
package main
|
||||
|
||||
@@ -1048,6 +1056,8 @@ func main() {
|
||||
var minor int
|
||||
var err error
|
||||
version := runtime.Version()
|
||||
version = strings.Replace(version, "beta", ".", 1)
|
||||
version = strings.Replace(version, "rc", ".", 1)
|
||||
fields := strings.Fields(version)
|
||||
// Go stable
|
||||
if len(fields) == 1 {
|
||||
@@ -1085,6 +1095,10 @@ func main() {
|
||||
}
|
||||
|
||||
func TestImportPathIsKey(t *testing.T) {
|
||||
// FIXME(marc): support of stdlib generic packages like "cmp", "maps", "slices" has changed
|
||||
// the scope layout by introducing new source packages when stdlib is used.
|
||||
// The logic of the following test doesn't apply anymore.
|
||||
t.Skip("This test needs to be reworked.")
|
||||
// No need to check the results of Eval, as TestFile already does it.
|
||||
i := interp.New(interp.Options{GoPath: filepath.FromSlash("../_test/testdata/redeclaration-global7")})
|
||||
if err := i.Use(stdlib.Symbols); err != nil {
|
||||
@@ -1242,10 +1256,9 @@ func TestConcurrentEvals2(t *testing.T) {
|
||||
if hello1 {
|
||||
if l == "hello world2" {
|
||||
break
|
||||
} else {
|
||||
done <- fmt.Errorf("unexpected output: %v", l)
|
||||
return
|
||||
}
|
||||
done <- fmt.Errorf("unexpected output: %v", l)
|
||||
return
|
||||
}
|
||||
if l == "hello world1" {
|
||||
hello1 = true
|
||||
@@ -1325,7 +1338,7 @@ func TestConcurrentEvals3(t *testing.T) {
|
||||
}()
|
||||
|
||||
for _, v := range input {
|
||||
in := strings.NewReader(fmt.Sprintf("println(\"%s\")\n", v))
|
||||
in := strings.NewReader(fmt.Sprintf("println(%q)\n", v))
|
||||
if _, err := io.Copy(poutin, in); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -1597,10 +1610,8 @@ func TestREPLCommands(t *testing.T) {
|
||||
if testing.Short() {
|
||||
return
|
||||
}
|
||||
_ = os.Setenv("YAEGI_PROMPT", "1") // To force prompts over non-tty streams
|
||||
defer func() {
|
||||
_ = os.Setenv("YAEGI_PROMPT", "0")
|
||||
}()
|
||||
t.Setenv("YAEGI_PROMPT", "1") // To force prompts over non-tty streams
|
||||
|
||||
allDone := make(chan bool)
|
||||
runREPL := func() {
|
||||
done := make(chan error)
|
||||
@@ -1872,25 +1883,25 @@ func TestIssue1383(t *testing.T) {
|
||||
}
|
||||
`
|
||||
|
||||
interp := interp.New(interp.Options{})
|
||||
err := interp.Use(stdlib.Symbols)
|
||||
i := interp.New(interp.Options{})
|
||||
err := i.Use(stdlib.Symbols)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = interp.Eval(`import "fmt"`)
|
||||
_, err = i.Eval(`import "fmt"`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ast, err := parser.ParseFile(interp.FileSet(), "_.go", src, parser.DeclarationErrors)
|
||||
ast, err := parser.ParseFile(i.FileSet(), "_.go", src, parser.DeclarationErrors)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
prog, err := interp.CompileAST(ast)
|
||||
prog, err := i.CompileAST(ast)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = interp.Execute(prog)
|
||||
_, err = i.Execute(prog)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user