Compare commits

...

11 Commits

Author SHA1 Message Date
Marc Vertes
ac80d1b3ed interp: fix setting interface objects from operators
This is a follow-up of #1017, generalizing the use of reflect.Set
method to set, and possibly overwrite, the concrete value of
interface objects all accross the implementation of operators.
Previous optimized implementation for non-interface objects is
preserved.
2021-02-08 18:00:04 +01:00
mpl
2e17cfab4f interp: do not wrap empty interface
The empty interface (interface{}), and its variants (such as []interface{} and map[string]interface{}), are commonly used in Go to (json) Unmarshal arbitrary data. Within Yaegi, all interface types are wrapped in a valueInterface struct in order to retain all the information needed for a consistent internal state (as reflect is not enough to achieve that). However, this wrapping ends up being problematic when it comes to the type assertions related to the aforementioned Unmarshaling.

Therefore, this PR is an attempt to consider the empty interface (and its variants) as an exception within Yaegi, that should never be wrapped within a valueInterface, and to treat it similarly to the other basic Go types. The assumption is that the wrapping should not be needed, as there is no information about implemented methods to maintain.

Fixes #984 
Fixes #829 
Fixes #1015
2021-02-04 12:08:04 +01:00
Marc Vertes
3f4e1665b1 interp: fix default type for constants from runtime
The default type must be derived from the constant value when necessary,
otherwise the type check fails wrongly.

Fixes #1026.
2021-02-03 11:48:03 +01:00
Marc Vertes
b9b0897d95 interp: fix nil value check in case of interface
A wrong logic was leading to panic in recover. Simplify the
workflow for clarity.

Fixes #1022.
2021-02-02 13:28:04 +01:00
mpl
6337f8bc01 interp: clarify error about GOPATH probably not set 2021-02-02 10:10:03 +01:00
Ludovic Fernandez
ccb8072759 chore: use GitHub Actions.
- use GitHub Actions instead of TravisCI
- updates golangci-lint to v1.36.0 and applies my rules
2021-02-01 12:23:29 +01:00
Nicholas Wiersma
d73111cda1 fix: untyped check
When checking for untyped values, we can be sure at this stage that they must be a const value or already untyped. Checking for type string equality is no longer a good measure.

Fixes #1000
2021-01-28 16:20:04 +01:00
Nicholas Wiersma
ff521ecb1a fix: handle function references in composite bin map
When passing a function reference as an interface in a composite binary map, the case should be handled to not take the value of the the node.

Related to #886
2021-01-28 15:42:03 +01:00
Marc Vertes
61b4980077 interp: do not panic in case of invalid constant definition
This is a follow-up of #1014, where an invalid constant definition  involving a builtin is now checked at CFG. In addition, some missing arithmetic operators are now detected for assign optimization.
2021-01-27 12:58:03 +01:00
Marc Vertes
100d090853 interp: fix sending object implementing an interface through channel
A channel can be used to interchange data with the pre-compiled
runtime and therefore objects impletementing interfaces must be
wrapped if necessary, using genInterfaceWrapper.

A similar treatment could be applied when sending interpreted
functions over a channel, to be provided in a new PR.

Fixes #1010.
2021-01-26 18:58:04 +01:00
Marc Vertes
bd60de5995 interp: allow early constant evaluation from builtin call
One builtin has been identified to be used for constant definition:
len(), with a constant string argument. Add support for this.

Fixes #1012.
2021-01-26 11:12:04 +01:00
43 changed files with 2182 additions and 429 deletions

67
.github/workflows/go-cross.yml vendored Normal file
View File

@@ -0,0 +1,67 @@
name: Build Cross OS
on:
push:
branches:
- master
pull_request:
jobs:
cross:
name: Go
runs-on: ${{ matrix.os }}
defaults:
run:
working-directory: ${{ github.workspace }}/go/src/github.com/traefik/yaegi
strategy:
matrix:
go-version: [ 1.14, 1.15 ]
os: [ubuntu-latest, macos-latest, windows-latest]
include:
- os: ubuntu-latest
go-path-suffix: /go
- os: macos-latest
go-path-suffix: /go
- os: windows-latest
go-path-suffix: \go
steps:
# https://github.com/marketplace/actions/setup-go-environment
- name: Set up Go ${{ matrix.go-version }}
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
# https://github.com/marketplace/actions/checkout
- name: Checkout code
uses: actions/checkout@v2
with:
path: go/src/github.com/traefik/yaegi
# https://github.com/marketplace/actions/cache
- name: Cache Go modules
uses: actions/cache@v2
with:
path: |
~/go/pkg/mod # Module download cache
~/.cache/go-build # Build cache (Linux)
~/Library/Caches/go-build # Build cache (Mac)
'%LocalAppData%\go-build' # Build cache (Windows)
key: ${{ runner.os }}-${{ matrix.go-version }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-${{ matrix.go-version }}-go-
- name: Setup GOPATH
run: go env -w GOPATH=${{ github.workspace }}${{ matrix.go-path-suffix }}
# TODO fail on windows
# - name: Tests
# run: go test -v -cover ./...
# env:
# GOPATH: ${{ github.workspace }}${{ matrix.go-path }}
- name: Build
run: go build -race -v -ldflags "-s -w" -trimpath

110
.github/workflows/main.yml vendored Normal file
View File

@@ -0,0 +1,110 @@
name: Main
on:
push:
branches:
- master
pull_request:
env:
GO_VERSION: 1.15
GOLANGCI_LINT_VERSION: v1.36.0
jobs:
linting:
name: Linting
runs-on: ubuntu-latest
steps:
- name: Set up Go ${{ env.GO_VERSION }}
uses: actions/setup-go@v2
with:
go-version: ${{ env.GO_VERSION }}
- name: Check out code
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Check and get dependencies
run: |
go mod tidy
git diff --exit-code go.mod
# git diff --exit-code go.sum
go mod download
- name: Install golangci-lint ${{ env.GOLANGCI_LINT_VERSION }}
run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin ${GOLANGCI_LINT_VERSION}
- name: Run golangci-lint ${{ env.GOLANGCI_LINT_VERSION }}
run: make check
generate:
name: Checks code and generated code
runs-on: ubuntu-latest
needs: linting
strategy:
matrix:
go-version: [ 1.14, 1.15 ]
steps:
- name: Set up Go ${{ matrix.go-version }}
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- name: Check out code
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Check generated code
run: |
rm -f interp/op.go
make generate
git update-index -q --refresh
CHANGED=$(git diff-index --name-only HEAD --)
test -z "$CHANGED" || echo $CHANGED
test -z "$CHANGED"
main:
name: Build and Test
runs-on: ubuntu-latest
needs: linting
defaults:
run:
working-directory: ${{ github.workspace }}/go/src/github.com/traefik/yaegi
strategy:
matrix:
go-version: [ 1.14, 1.15 ]
steps:
- name: Set up Go ${{ matrix.go-version }}
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- name: Check out code
uses: actions/checkout@v2
with:
path: go/src/github.com/traefik/yaegi
fetch-depth: 0
# https://github.com/marketplace/actions/cache
- name: Cache Go modules
uses: actions/cache@v2
with:
path: ./_test/tmp
key: ${{ runner.os }}-yaegi-${{ hashFiles('**//_test/tmp/') }}
restore-keys: |
${{ runner.os }}-yaegi-
- name: Setup GOPATH
run: go env -w GOPATH=${{ github.workspace }}/go
- name: Build
run: go build -v ./...
- name: Run tests
run: make tests
env:
GOPATH: ${{ github.workspace }}/go

42
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,42 @@
name: Release
on:
push:
tags:
- v[0-9]+.[0-9]+*
env:
GO_VERSION: 1.15
jobs:
release:
name: Create a release
runs-on: ubuntu-latest
steps:
- name: Set up Go ${{ env.GO_VERSION }}
uses: actions/setup-go@v2
with:
go-version: ${{ env.GO_VERSION }}
- name: Check out code
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Cache Go modules
uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
version: latest
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN_REPO }}

View File

@@ -33,16 +33,23 @@
"gochecknoinits",
"gochecknoglobals",
"wsl",
"nlreturn",
"godox",
"funlen",
"gocognit",
"stylecheck",
"gomnd",
"testpackage",
"paralleltest",
"tparallel",
"goerr113",
"wrapcheck",
"nestif",
"exhaustive",
"nlreturn",
"exhaustivestruct",
"forbidigo",
"ifshort",
"errorlint", # TODO: must be reactivate before fixes
]
[issues]
@@ -61,3 +68,6 @@
[[issues.exclude-rules]]
path = "interp/interp.go"
text = "`out` can be `io.Writer`"
[[issues.exclude-rules]]
path = "interp/interp_eval_test.go"
linters = ["thelper"]

View File

@@ -1,60 +0,0 @@
language: go
dist: xenial
branches:
only:
- master
- /^v\d+\.\d+(\.\d+)?(-\S*)?$/
notifications:
email:
on_success: never
on_failure: change
cache:
directories:
- $GOPATH/pkg/mod
matrix:
fast_finish: true
include:
- go: 1.14.x
- go: 1.15.x
env: STABLE=true
env:
global:
- GO111MODULE=on
go_import_path: github.com/traefik/yaegi
before_install:
# Install linters and misspell
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b $GOPATH/bin ${GOLANGCI_LINT_VERSION}
- golangci-lint --version
install:
- echo "TRAVIS_GO_VERSION=$TRAVIS_GO_VERSION"
- go mod download
before_script:
- rm -f interp/op.go
- make generate
- git update-index -q --refresh
- CHANGED=$(git diff-index --name-only HEAD --)
- test -z "$CHANGED" || echo $CHANGED
- test -z "$CHANGED"
script:
- make check
- go build -v ./...
- make tests
deploy:
- provider: script
skip_cleanup: true
script: curl -sL https://git.io/goreleaser | bash
on:
tags: true
condition: $STABLE = true

View File

@@ -1,10 +1,33 @@
package main
func main() {
b := 2
var a interface{} = 5 + b
b := 2 // int
var c int = 5 + b
println(c)
var d int32 = 6 + int32(b)
println(d)
var a interface{} = 7 + b
println(a.(int))
var e int32 = 2
var f interface{} = 8 + e
println(f.(int32))
a = 9 + e
println(a.(int32))
var g int = 2
a = 10 + g
println(a.(int))
}
// Output:
// 7
// 8
// 9
// 10
// 11
// 12

View File

@@ -2,6 +2,7 @@ package main
import (
"encoding/xml"
"errors"
"fmt"
)
@@ -10,7 +11,30 @@ type Email struct {
Addr string
}
func f(s string, r interface{}) error {
func f(r interface{}) error {
return withPointerAsInterface(&r)
}
func withPointerAsInterface(r interface{}) error {
_ = (r).(*interface{})
rp, ok := (r).(*interface{})
if !ok {
return errors.New("cannot assert to *interface{}")
}
em, ok := (*rp).(*Email)
if !ok {
return errors.New("cannot assert to *Email")
}
em.Where = "work"
em.Addr = "bob@work.com"
return nil
}
func ff(s string, r interface{}) error {
return xml.Unmarshal([]byte(s), r)
}
func fff(s string, r interface{}) error {
return xml.Unmarshal([]byte(s), &r)
}
@@ -21,9 +45,19 @@ func main() {
</Email>
`
v := Email{}
err := f(data, &v)
err := f(&v)
fmt.Println(err, v)
vv := Email{}
err = ff(data, &vv)
fmt.Println(err, vv)
vvv := Email{}
err = ff(data, &vvv)
fmt.Println(err, vvv)
}
// Ouput:
// <nil> {work bob@work.com}
// <nil> {work bob@work.com}
// <nil> {work bob@work.com}

24
_test/addr3.go Normal file
View File

@@ -0,0 +1,24 @@
package main
import (
"fmt"
)
func main() {
var a interface{}
a = 2
fmt.Println(a)
var b *interface{}
b = &a
fmt.Println(*b)
var c **interface{}
c = &b
fmt.Println(**c)
}
// Output:
// 2
// 2
// 2

114
_test/addr4.go Normal file
View File

@@ -0,0 +1,114 @@
package main
import (
"encoding/json"
"fmt"
"log"
)
const jsonData = `[
"foo",
"bar"
]`
const jsonData2 = `[
{"foo": "foo"},
{"bar": "bar"}
]`
const jsonData3 = `{
"foo": "foo",
"bar": "bar"
}`
func fromSlice() {
var a []interface{}
var c, d interface{}
c = 2
d = 3
a = []interface{}{c, d}
if err := json.Unmarshal([]byte(jsonData), &a); err != nil {
log.Fatalln(err)
}
for k, v := range a {
fmt.Println(k, ":", v)
}
}
func fromEmpty() {
var a interface{}
var c, d interface{}
c = 2
d = 3
a = []interface{}{c, d}
if err := json.Unmarshal([]byte(jsonData), &a); err != nil {
log.Fatalln(err)
}
b := a.([]interface{})
for k, v := range b {
fmt.Println(k, ":", v)
}
}
func sliceOfObjects() {
var a interface{}
if err := json.Unmarshal([]byte(jsonData2), &a); err != nil {
log.Fatalln(err)
}
b := a.([]interface{})
for k, v := range b {
fmt.Println(k, ":", v)
}
}
func intoMap() {
var a interface{}
if err := json.Unmarshal([]byte(jsonData3), &a); err != nil {
log.Fatalln(err)
}
b := a.(map[string]interface{})
seenFoo := false
for k, v := range b {
vv := v.(string)
if vv != "foo" {
if seenFoo {
fmt.Println(k, ":", vv)
break
}
kk := k
vvv := vv
defer fmt.Println(kk, ":", vvv)
continue
}
seenFoo = true
fmt.Println(k, ":", vv)
}
}
func main() {
fromSlice()
fromEmpty()
sliceOfObjects()
intoMap()
}
// Ouput:
// 0 : foo
// 1 : bar
// 0 : foo
// 1 : bar
// 0 : map[foo:foo]
// 1 : map[bar:bar]
// foo : foo
// bar : bar

62
_test/addr5.go Normal file
View File

@@ -0,0 +1,62 @@
package main
import (
"encoding/json"
"fmt"
"net/url"
)
func main() {
body := []byte(`{
"BODY_1": "VALUE_1",
"BODY_2": "VALUE_2",
"BODY_3": null,
"BODY_4": {
"BODY_1": "VALUE_1",
"BODY_2": "VALUE_2",
"BODY_3": null
},
"BODY_5": [
"VALUE_1",
"VALUE_2",
"VALUE_3"
]
}`)
values := url.Values{}
var rawData map[string]interface{}
err := json.Unmarshal(body, &rawData)
if err != nil {
fmt.Println("can't parse body")
return
}
for key, val := range rawData {
switch val.(type) {
case string, bool, float64:
values.Add(key, fmt.Sprint(val))
case nil:
values.Add(key, "")
case map[string]interface{}, []interface{}:
jsonVal, err := json.Marshal(val)
if err != nil {
fmt.Println("can't encode json")
return
}
values.Add(key, string(jsonVal))
}
}
fmt.Println(values.Get("BODY_1"))
fmt.Println(values.Get("BODY_2"))
fmt.Println(values.Get("BODY_3"))
fmt.Println(values.Get("BODY_4"))
fmt.Println(values.Get("BODY_5"))
}
// Output:
// VALUE_1
// VALUE_2
//
// {"BODY_1":"VALUE_1","BODY_2":"VALUE_2","BODY_3":null}
// ["VALUE_1","VALUE_2","VALUE_3"]

View File

@@ -2,7 +2,6 @@ package main
import (
"fmt"
"reflect"
"time"
)
@@ -10,6 +9,10 @@ type MyWriter interface {
Write(p []byte) (i int, err error)
}
type DummyWriter interface {
Write(p []byte) (i int, err error)
}
type TestStruct struct{}
func (t TestStruct) Write(p []byte) (n int, err error) {
@@ -25,14 +28,18 @@ type MyStringer interface {
String() string
}
type DummyStringer interface {
String() string
}
func usesStringer(s MyStringer) {
fmt.Println(s.String())
}
func main() {
aType := reflect.TypeOf((*MyWriter)(nil)).Elem()
var t interface{}
// TODO(mpl): restore when we can deal with empty interface.
// var t interface{}
var t DummyWriter
t = TestStruct{}
var tw MyWriter
var ok bool
@@ -45,8 +52,6 @@ func main() {
}
n, _ := t.(MyWriter).Write([]byte("hello world"))
fmt.Println(n)
bType := reflect.TypeOf(TestStruct{})
fmt.Println(bType.Implements(aType))
// not redundant with the above, because it goes through a slightly different code path.
if _, ok := t.(MyWriter); !ok {
@@ -56,6 +61,8 @@ func main() {
fmt.Println("TestStruct implements MyWriter")
}
// TODO(mpl): restore
/*
t = 42
foo, ok := t.(MyWriter)
if !ok {
@@ -70,8 +77,10 @@ func main() {
} else {
fmt.Println("42 implements MyWriter")
}
*/
var tt interface{}
// var tt interface{}
var tt DummyStringer
tt = time.Nanosecond
var myD MyStringer
myD, ok = tt.(MyStringer)
@@ -82,9 +91,6 @@ func main() {
usesStringer(myD)
}
fmt.Println(tt.(MyStringer).String())
cType := reflect.TypeOf((*MyStringer)(nil)).Elem()
dType := reflect.TypeOf(time.Nanosecond)
fmt.Println(dType.Implements(cType))
if _, ok := tt.(MyStringer); !ok {
fmt.Println("time.Nanosecond does not implement MyStringer")
@@ -92,6 +98,8 @@ func main() {
fmt.Println("time.Nanosecond implements MyStringer")
}
// TODO(mpl): restore
/*
tt = 42
bar, ok := tt.(MyStringer)
if !ok {
@@ -106,20 +114,15 @@ func main() {
} else {
fmt.Println("42 implements MyStringer")
}
*/
}
// Output:
// TestStruct implements MyWriter
// 11
// 11
// true
// TestStruct implements MyWriter
// 42 does not implement MyWriter
// 42 does not implement MyWriter
// time.Nanosecond implements MyStringer
// 1ns
// 1ns
// true
// time.Nanosecond implements MyStringer
// 42 does not implement MyStringer
// 42 does not implement MyStringer

View File

@@ -12,6 +12,10 @@ func (t TestStruct) String() string {
return "hello world"
}
type DummyStringer interface{
String() string
}
func main() {
aType := reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
@@ -52,7 +56,9 @@ func main() {
return
}
var tt interface{}
// TODO(mpl): restore when fixed
// var tt interface{}
var tt DummyStringer
tt = TestStruct{}
ss, ok := tt.(fmt.Stringer)
if !ok {
@@ -61,9 +67,6 @@ func main() {
}
fmt.Println(ss.String())
fmt.Println(tt.(fmt.Stringer).String())
// TODO(mpl): uncomment when fixed
// cType := reflect.TypeOf(TestStruct{})
// fmt.Println(cType.Implements(aType))
if _, ok := tt.(fmt.Stringer); !ok {
fmt.Println("TestStuct does not implement fmt.Stringer")

View File

@@ -4,10 +4,16 @@ func f(a []int) interface{} {
return cap(a)
}
func g(a []int) int {
return cap(a)
}
func main() {
a := []int{1, 2}
println(g(a))
println(f(a).(int))
}
// Output:
// 2
// 2

View File

@@ -32,6 +32,7 @@ func interfaceAsInterfaces() {
println("nope")
return
}
fmt.Println(d)
for _, v := range d {
fmt.Println(v)
@@ -46,5 +47,6 @@ func main() {
// Output:
// 2
// 3
// [2 3]
// 2
// 3

30
_test/composite17.go Normal file
View File

@@ -0,0 +1,30 @@
package main
import (
"html/template"
)
var str = `{{ stringOr .Data "test" }}`
func main() {
_, err := template.New("test").
Funcs(template.FuncMap{
"stringOr": stringOr,
}).
Parse(str)
if err != nil {
println(err.Error())
return
}
println("success")
}
func stringOr(v, def string) string {
if v == "" {
return def
}
return v
}
// Output:
// success

12
_test/const23.go Normal file
View File

@@ -0,0 +1,12 @@
package main
const maxlen = len("hello")
var gfm = [maxlen]byte{}
func main() {
println(len(gfm))
}
// Output:
// 5

14
_test/const24.go Normal file
View File

@@ -0,0 +1,14 @@
package main
var aa = [...]int{1, 2, 3}
const maxlen = cap(aa)
var gfm = [maxlen]byte{}
func main() {
println(len(gfm))
}
// Output:
// 3

View File

@@ -2,10 +2,15 @@ package main
func f1(a int) interface{} { return a + 1 }
func f2(a int64) interface{} { return a + 1 }
func main() {
c := f1(3)
println(c.(int))
b := f2(3)
println(b.(int64))
}
// Output:
// 4
// 4

View File

@@ -2,7 +2,13 @@ package main
func f1(a int) int { return a + 1 }
func f2(a int) interface{} { return f1(a) }
func f2(a int) interface{} {
// TODO: re-enable the optimized case below, once we've figured out why it
// interferes with the empty interface model.
// return f1(a)
var foo interface{} = f1(a)
return foo
}
func main() {
c := f2(3)

13
_test/interface50.go Normal file
View File

@@ -0,0 +1,13 @@
package main
func main() {
a := true
var b interface{} = 5
println(b.(int))
b = a == true
println(b.(bool))
}
// Output:
// 5
// true

22
_test/issue-1010.go Normal file
View File

@@ -0,0 +1,22 @@
package main
import (
"encoding/json"
"fmt"
)
type MyJsonMarshaler struct{ n int }
func (m MyJsonMarshaler) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf(`{"num": %d}`, m.n)), nil
}
func main() {
ch := make(chan json.Marshaler, 1)
ch <- MyJsonMarshaler{2}
m, err := json.Marshal(<-ch)
fmt.Println(string(m), err)
}
// Output:
// {"num":2} <nil>

17
_test/issue-1022.go Normal file
View File

@@ -0,0 +1,17 @@
package main
import "fmt"
func main() {
defer func() {
r := recover()
if r != nil {
fmt.Println(r)
}
}()
panic("Ho Ho Ho!")
}
// Output:
// Ho Ho Ho!

View File

@@ -12,10 +12,16 @@ type Hi interface {
Hello() string
}
type Hey interface {
Hello() string
}
func (r *Root) Hello() string { return "Hello " + r.Name }
func main() {
var one interface{} = &One{Root{Name: "test2"}}
// TODO(mpl): restore when type assertions work again.
// var one interface{} = &One{Root{Name: "test2"}}
var one Hey = &One{Root{Name: "test2"}}
println(one.(Hi).Hello())
}

View File

@@ -2,12 +2,26 @@ package main
func main() {
var a interface{}
a = []int{3}
switch a.(type) {
case []int:
println("a is []int")
case []string:
println("a is []string")
}
var b interface{}
b = []string{"hello"}
switch b.(type) {
case []int:
println("b is []int")
case []string:
println("b is []string")
}
println("bye")
}
// Output:
// a is []int
// b is []string
// bye

11
_test/time16.go Normal file
View File

@@ -0,0 +1,11 @@
package main
import "time"
func main() {
localTime := time.ANSIC
println(localTime)
}
// Output:
// Mon Jan _2 15:04:05 2006

View File

@@ -102,7 +102,7 @@ func genLicense(fname string) (string, error) {
f, err := os.Open(fname)
if err != nil {
return "", fmt.Errorf("could not open LICENSE file: %v", err)
return "", fmt.Errorf("could not open LICENSE file: %w", err)
}
defer func() { _ = f.Close() }()
@@ -116,7 +116,7 @@ func genLicense(fname string) (string, error) {
license.WriteString("//" + txt + "\n")
}
if sc.Err() != nil {
return "", fmt.Errorf("could not scan LICENSE file: %v", err)
return "", fmt.Errorf("could not scan LICENSE file: %w", err)
}
return license.String(), nil

View File

@@ -50,9 +50,9 @@ func TestYaegiCmdCancel(t *testing.T) {
yaegi := filepath.Join(tmp, "yaegi")
build := exec.Command("go", "build", "-race", "-o", yaegi, ".")
err = build.Run()
out, err := build.CombinedOutput()
if err != nil {
t.Fatalf("failed to build yaegi command: %v", err)
t.Fatalf("failed to build yaegi command: %v: %s", err, out)
}
// Test src must be terminated by a single newline.

View File

@@ -104,7 +104,7 @@ func TestPackages(t *testing.T) {
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
goPath, err := filepath.Abs(test.goPath)
goPath, err := filepath.Abs(filepath.FromSlash(test.goPath))
if err != nil {
t.Fatal(err)
}
@@ -115,7 +115,7 @@ func TestPackages(t *testing.T) {
var msg string
if test.evalFile != "" {
if _, err := i.EvalPath(test.evalFile); err != nil {
if _, err := i.EvalPath(filepath.FromSlash(test.evalFile)); err != nil {
fatalStderrf(t, "%v", err)
}
msg = stdout.String()
@@ -146,6 +146,8 @@ func TestPackages(t *testing.T) {
}
func fatalStderrf(t *testing.T, format string, args ...interface{}) {
t.Helper()
fmt.Fprintf(os.Stderr, format+"\n", args...)
t.FailNow()
}
@@ -159,7 +161,7 @@ func TestPackagesError(t *testing.T) {
{
desc: "different packages in the same directory",
goPath: "./_pkg9/",
expected: "1:21: import \"github.com/foo/pkg\" error: found packages pkg and pkgfalse in _pkg9/src/github.com/foo/pkg",
expected: `1:21: import "github.com/foo/pkg" error: found packages pkg and pkgfalse in ` + filepath.FromSlash("_pkg9/src/github.com/foo/pkg"),
},
}

View File

@@ -241,7 +241,7 @@ func (e *Extractor) genContent(importPath string, p *types.Package) ([]byte, err
base := template.New("extract")
parse, err := base.Parse(model)
if err != nil {
return nil, fmt.Errorf("template parsing error: %v", err)
return nil, fmt.Errorf("template parsing error: %w", err)
}
if importPath == "log/syslog" {
@@ -274,13 +274,13 @@ func (e *Extractor) genContent(importPath string, p *types.Package) ([]byte, err
}
err = parse.Execute(b, data)
if err != nil {
return nil, fmt.Errorf("template error: %v", err)
return nil, fmt.Errorf("template error: %w", err)
}
// gofmt
source, err := format.Source(b.Bytes())
if err != nil {
return nil, fmt.Errorf("failed to format source: %v: %s", err, b.Bytes())
return nil, fmt.Errorf("failed to format source: %w: %s", err, b.Bytes())
}
return source, nil
}
@@ -451,7 +451,7 @@ func genBuildTags() (string, error) {
minor, err := strconv.Atoi(minorRaw)
if err != nil {
return "", fmt.Errorf("failed to parse version: %v", err)
return "", fmt.Errorf("failed to parse version: %w", err)
}
// Only append an upper bound if we are not on the latest go

View File

@@ -11,7 +11,7 @@ import (
const model = `package interp
// Code generated by 'go run ../internal/genop/genop.go'. DO NOT EDIT.
// Code generated by 'go run ../internal/cmd/genop/genop.go'. DO NOT EDIT.
import (
"go/constant"
@@ -24,6 +24,7 @@ import (
func {{$name}}(n *node) {
next := getExec(n.tnext)
typ := n.typ.concrete().TypeOf()
isInterface := n.typ.TypeOf().Kind() == reflect.Interface
dest := genValueOutput(n, typ)
c0, c1 := n.child[0], n.child[1]
@@ -31,6 +32,13 @@ func {{$name}}(n *node) {
{{- if $op.Str}}
case reflect.String:
switch {
case isInterface:
v0 := genValue(c0)
v1 := genValue(c1)
n.exec = func(f *frame) bltn {
dest(f).Set(reflect.ValueOf(v0(f).String() {{$op.Name}} v1(f).String()).Convert(typ))
return next
}
case c0.rval.IsValid():
s0 := vString(c0.rval)
v1 := genValue(c1)
@@ -56,6 +64,19 @@ func {{$name}}(n *node) {
{{- end}}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
switch {
case isInterface:
v0 := genValueInt(c0)
{{- if $op.Shift}}
v1 := genValueUint(c1)
{{else}}
v1 := genValueInt(c1)
{{end -}}
n.exec = func(f *frame) bltn {
_, i := v0(f)
_, j := v1(f)
dest(f).Set(reflect.ValueOf(i {{$op.Name}} j).Convert(typ))
return next
}
case c0.rval.IsValid():
i := vInt(c0.rval)
{{- if $op.Shift}}
@@ -96,6 +117,15 @@ func {{$name}}(n *node) {
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
switch {
case isInterface:
v0 := genValueUint(c0)
v1 := genValueUint(c1)
n.exec = func(f *frame) bltn {
_, i := v0(f)
_, j := v1(f)
dest(f).Set(reflect.ValueOf(i {{$op.Name}} j).Convert(typ))
return next
}
case c0.rval.IsValid():
i := vUint(c0.rval)
v1 := genValueUint(c1)
@@ -125,6 +155,15 @@ func {{$name}}(n *node) {
{{- if $op.Float}}
case reflect.Float32, reflect.Float64:
switch {
case isInterface:
v0 := genValueFloat(c0)
v1 := genValueFloat(c1)
n.exec = func(f *frame) bltn {
_, i := v0(f)
_, j := v1(f)
dest(f).Set(reflect.ValueOf(i {{$op.Name}} j).Convert(typ))
return next
}
case c0.rval.IsValid():
i := vFloat(c0.rval)
v1 := genValueFloat(c1)
@@ -153,25 +192,32 @@ func {{$name}}(n *node) {
}
case reflect.Complex64, reflect.Complex128:
switch {
case isInterface:
v0 := genComplex(c0)
v1 := genComplex(c1)
n.exec = func(f *frame) bltn {
dest(f).Set(reflect.ValueOf(v0(f) {{$op.Name}} v1(f)).Convert(typ))
return next
}
case c0.rval.IsValid():
r0 := vComplex(c0.rval)
v1 := genValue(c1)
v1 := genComplex(c1)
n.exec = func(f *frame) bltn {
dest(f).SetComplex(r0 {{$op.Name}} v1(f).Complex())
dest(f).SetComplex(r0 {{$op.Name}} v1(f))
return next
}
case c1.rval.IsValid():
r1 := vComplex(c1.rval)
v0 := genValue(c0)
v0 := genComplex(c0)
n.exec = func(f *frame) bltn {
dest(f).SetComplex(v0(f).Complex() {{$op.Name}} r1)
dest(f).SetComplex(v0(f) {{$op.Name}} r1)
return next
}
default:
v0 := genValue(c0)
v1 := genValue(c1)
v0 := genComplex(c0)
v1 := genComplex(c1)
n.exec = func(f *frame) bltn {
dest(f).SetComplex(v0(f).Complex() {{$op.Name}} v1(f).Complex())
dest(f).SetComplex(v0(f) {{$op.Name}} v1(f))
return next
}
}
@@ -429,12 +475,24 @@ func {{$name}}Const(n *node) {
func {{$name}}(n *node) {
tnext := getExec(n.tnext)
dest := genValueOutput(n, reflect.TypeOf(true))
typ := n.typ.concrete().TypeOf()
isInterface := n.typ.TypeOf().Kind() == reflect.Interface
c0, c1 := n.child[0], n.child[1]
{{- if or (eq $op.Name "==") (eq $op.Name "!=") }}
if c0.typ.cat == aliasT || c1.typ.cat == aliasT {
switch {
case isInterface:
v0 := genValue(c0)
v1 := genValue(c1)
dest := genValue(n)
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
i1 := v1(f).Interface()
dest(f).Set(reflect.ValueOf(i0 {{$op.Name}} i1).Convert(typ))
return tnext
}
case c0.rval.IsValid():
i0 := c0.rval.Interface()
v1 := genValue(c1)
@@ -511,9 +569,18 @@ func {{$name}}(n *node) {
switch t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf(); {
case isString(t0) || isString(t1):
switch {
case isInterface:
v0 := genValueString(c0)
v1 := genValueString(c1)
n.exec = func(f *frame) bltn {
_, s0 := v0(f)
_, s1 := v1(f)
dest(f).Set(reflect.ValueOf(s0 {{$op.Name}} s1).Convert(typ))
return tnext
}
case c0.rval.IsValid():
s0 := vString(c0.rval)
v1 := genValueString(n.child[1])
v1 := genValueString(c1)
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
@@ -534,7 +601,7 @@ func {{$name}}(n *node) {
}
case c1.rval.IsValid():
s1 := vString(c1.rval)
v0 := genValueString(n.child[0])
v0 := genValueString(c0)
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
@@ -554,8 +621,8 @@ func {{$name}}(n *node) {
}
}
default:
v0 := genValueString(n.child[0])
v1 := genValueString(n.child[1])
v0 := genValueString(c0)
v1 := genValueString(c1)
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
@@ -579,6 +646,15 @@ func {{$name}}(n *node) {
}
case isFloat(t0) || isFloat(t1):
switch {
case isInterface:
v0 := genValueFloat(c0)
v1 := genValueFloat(c1)
n.exec = func(f *frame) bltn {
_, s0 := v0(f)
_, s1 := v1(f)
dest(f).Set(reflect.ValueOf(s0 {{$op.Name}} s1).Convert(typ))
return tnext
}
case c0.rval.IsValid():
s0 := vFloat(c0.rval)
v1 := genValueFloat(c1)
@@ -649,6 +725,15 @@ func {{$name}}(n *node) {
}
case isUint(t0) || isUint(t1):
switch {
case isInterface:
v0 := genValueUint(c0)
v1 := genValueUint(c1)
n.exec = func(f *frame) bltn {
_, s0 := v0(f)
_, s1 := v1(f)
dest(f).Set(reflect.ValueOf(s0 {{$op.Name}} s1).Convert(typ))
return tnext
}
case c0.rval.IsValid():
s0 := vUint(c0.rval)
v1 := genValueUint(c1)
@@ -720,6 +805,15 @@ func {{$name}}(n *node) {
}
case isInt(t0) || isInt(t1):
switch {
case isInterface:
v0 := genValueInt(c0)
v1 := genValueInt(c1)
n.exec = func(f *frame) bltn {
_, s0 := v0(f)
_, s1 := v1(f)
dest(f).Set(reflect.ValueOf(s0 {{$op.Name}} s1).Convert(typ))
return tnext
}
case c0.rval.IsValid():
s0 := vInt(c0.rval)
v1 := genValueInt(c1)
@@ -792,6 +886,15 @@ func {{$name}}(n *node) {
{{- if $op.Complex}}
case isComplex(t0) || isComplex(t1):
switch {
case isInterface:
v0 := genComplex(c0)
v1 := genComplex(c1)
n.exec = func(f *frame) bltn {
s0 := v0(f)
s1 := v1(f)
dest(f).Set(reflect.ValueOf(s0 {{$op.Name}} s1).Convert(typ))
return tnext
}
case c0.rval.IsValid():
s0 := vComplex(c0.rval)
v1 := genComplex(c1)
@@ -836,8 +939,8 @@ func {{$name}}(n *node) {
}
}
default:
v0 := genComplex(n.child[0])
v1 := genComplex(n.child[1])
v0 := genComplex(c0)
v1 := genComplex(c1)
if n.fnext != nil {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
@@ -861,6 +964,15 @@ func {{$name}}(n *node) {
}
default:
switch {
case isInterface:
v0 := genValue(c0)
v1 := genValue(c1)
n.exec = func(f *frame) bltn {
i0 := v0(f).Interface()
i1 := v1(f).Interface()
dest(f).Set(reflect.ValueOf(i0 {{$op.Name}} i1).Convert(typ))
return tnext
}
case c0.rval.IsValid():
i0 := c0.rval.Interface()
v1 := genValue(c1)

View File

@@ -564,9 +564,11 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
//
switch {
case n.action != aAssign:
// Do not optimize assign combined with another operator.
// Do not skip assign operation if it is combined with another operator.
case src.rval.IsValid():
// Do not skip assign operation if setting from a constant value.
case isMapEntry(dest):
// Setting a map entry needs an additional step, do not optimize.
// Setting a map entry requires an additional step, do not optimize.
// As we only write, skip the default useless getIndexMap dest action.
dest.gen = nop
case isCall(src) && dest.typ.cat != interfaceT && !isRecursiveField(dest):
@@ -588,19 +590,19 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
break
}
if dest.action == aGetIndex {
// Optimization does not work when assigning to a struct field.
// Skip optimization, as it does not work when assigning to a struct field.
break
}
n.gen = nop
src.findex = dest.findex
src.level = level
case len(n.child) < 4 && !src.rval.IsValid() && isArithmeticAction(src):
case len(n.child) < 4 && isArithmeticAction(src):
// Optimize single assignments from some arithmetic operations.
src.typ = dest.typ
src.findex = dest.findex
src.level = level
n.gen = nop
case src.kind == basicLit && !src.rval.IsValid():
case src.kind == basicLit:
// Assign to nil.
src.rval = reflect.New(dest.typ.TypeOf()).Elem()
}
@@ -610,6 +612,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
sym.typ = n.typ
sym.recv = src.recv
}
n.level = level
if n.anc.kind == constDecl {
@@ -711,7 +714,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
break
}
}
if c0.rval.IsValid() && c1.rval.IsValid() && !isInterface(n.typ) && constOp[n.action] != nil {
if c0.rval.IsValid() && c1.rval.IsValid() && (!isInterface(n.typ)) && constOp[n.action] != nil {
n.typ.TypeOf() // Force compute of reflection type.
constOp[n.action](n) // Compute a constant result now rather than during exec.
}
@@ -855,13 +858,15 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
wireChild(n)
switch {
case interp.isBuiltinCall(n):
err = check.builtin(n.child[0].ident, n, n.child[1:], n.action == aCallSlice)
c0 := n.child[0]
bname := c0.ident
err = check.builtin(bname, n, n.child[1:], n.action == aCallSlice)
if err != nil {
break
}
n.gen = n.child[0].sym.builtin
n.child[0].typ = &itype{cat: builtinT}
n.gen = c0.sym.builtin
c0.typ = &itype{cat: builtinT}
if n.typ, err = nodeType(interp, sc, n); err != nil {
return
}
@@ -872,10 +877,28 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
case n.anc.kind == returnStmt:
// Store result directly to frame output location, to avoid a frame copy.
n.findex = 0
case bname == "cap" && isInConstOrTypeDecl(n):
switch n.child[1].typ.TypeOf().Kind() {
case reflect.Array, reflect.Chan:
capConst(n)
default:
err = n.cfgErrorf("cap argument is not an array or channel")
}
n.findex = notInFrame
n.gen = nop
case bname == "len" && isInConstOrTypeDecl(n):
switch n.child[1].typ.TypeOf().Kind() {
case reflect.Array, reflect.Chan, reflect.String:
lenConst(n)
default:
err = n.cfgErrorf("len argument is not an array, channel or string")
}
n.findex = notInFrame
n.gen = nop
default:
n.findex = sc.add(n.typ)
}
if op, ok := constBltn[n.child[0].ident]; ok && n.anc.action != aAssign {
if op, ok := constBltn[bname]; ok && n.anc.action != aAssign {
op(n) // pre-compute non-assigned constant :
}
case n.child[0].isType(sc):
@@ -1863,6 +1886,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
return
}
}
for _, c := range n.child[:l] {
var index int
if sc.global {
@@ -2326,6 +2350,20 @@ func isRecursiveField(n *node) bool {
return false
}
func isInConstOrTypeDecl(n *node) bool {
anc := n.anc
for anc != nil {
switch anc.kind {
case constDecl, typeDecl:
return true
case varDecl, funcDecl:
return false
}
anc = anc.anc
}
return false
}
// isNewDefine returns true if node refers to a new definition.
func isNewDefine(n *node, sc *scope) bool {
if n.ident == "_" {
@@ -2485,6 +2523,10 @@ func compositeGenerator(n *node, typ *itype, rtyp reflect.Type) (gen bltnGenerat
if rtyp == nil {
rtyp = n.typ.rtype
}
// TODO(mpl): I do not understand where this side-effect is coming from, and why it happens. quickfix for now.
if rtyp == nil {
rtyp = n.typ.val.rtype
}
switch k := rtyp.Kind(); k {
case reflect.Struct:
if n.nleft == 1 {
@@ -2534,28 +2576,22 @@ func isValueUntyped(v reflect.Value) bool {
if v.CanSet() {
return false
}
t := v.Type()
if t.Implements(constVal) {
return true
}
return t.String() == t.Kind().String()
return v.Type().Implements(constVal)
}
// isArithmeticAction returns true if the node action is an arithmetic operator.
func isArithmeticAction(n *node) bool {
switch n.action {
case aAdd, aAnd, aAndNot, aBitNot, aMul, aQuo, aRem, aShl, aShr, aSub, aXor:
case aAdd, aAnd, aAndNot, aBitNot, aMul, aNeg, aOr, aPos, aQuo, aRem, aShl, aShr, aSub, aXor:
return true
default:
return false
}
return false
}
func isBoolAction(n *node) bool {
switch n.action {
case aEqual, aGreater, aGreaterEqual, aLand, aLor, aLower, aLowerEqual, aNot, aNotEqual:
return true
default:
return false
}
return false
}

View File

@@ -27,6 +27,7 @@ func (interp *Interpreter) gta(root *node, rpath, importPath string) ([]*node, e
// values which may be used in further declarations.
if _, err = interp.cfg(n, importPath); err != nil {
// No error processing here, to allow recovery in subtree nodes.
// TODO(marc): check for a non recoverable error and return it for better diagnostic.
err = nil
}

View File

@@ -75,10 +75,10 @@ type frame struct {
done reflect.SelectCase // for cancellation of channel operations
}
func newFrame(anc *frame, len int, id uint64) *frame {
func newFrame(anc *frame, length int, id uint64) *frame {
f := &frame{
anc: anc,
data: make([]reflect.Value, len),
data: make([]reflect.Value, length),
id: id,
}
if anc == nil {

View File

@@ -89,6 +89,7 @@ func TestOpVarConst(t *testing.T) {
{src: "b := uint(5); a+b", res: "15"},
{src: "b := uint(5); b+a", res: "15"},
{src: "b := uint(5); b>a", res: "false"},
{src: "const maxlen = cap(aa); var aa = []int{1,2}", err: "1:20: constant definition loop"},
})
}
@@ -102,6 +103,16 @@ func TestEvalStar(t *testing.T) {
func TestEvalAssign(t *testing.T) {
i := interp.New(interp.Options{})
i.Use(interp.Exports{
"testpkg": {
"val": reflect.ValueOf(int64(11)),
},
})
_, e := i.Eval(`import "testpkg"`)
if e != nil {
t.Fatal(e)
}
runTests(t, i, []testCase{
{src: `a := "Hello"; a += " world"`, res: "Hello world"},
{src: `b := "Hello"; b += 1`, err: "1:42: invalid operation: mismatched types string and int"},
@@ -111,6 +122,7 @@ func TestEvalAssign(t *testing.T) {
{src: "g := 1; g <<= 8", res: "256"},
{src: "h := 1; h >>= 8", res: "0"},
{src: "i := 1; j := &i; (*j) = 2", res: "2"},
{src: "i64 := testpkg.val; i64 == 11", res: "true"},
})
}
@@ -495,12 +507,19 @@ func TestEvalMethod(t *testing.T) {
Hello() string
}
type Hey interface {
Hello() string
}
func (r *Root) Hello() string { return "Hello " + r.Name }
var r = Root{"R"}
var o = One{r}
var root interface{} = &Root{Name: "test1"}
var one interface{} = &One{Root{Name: "test2"}}
// TODO(mpl): restore empty interfaces when type assertions work (again) on them.
// var root interface{} = &Root{Name: "test1"}
// var one interface{} = &One{Root{Name: "test2"}}
var root Hey = &Root{Name: "test1"}
var one Hey = &One{Root{Name: "test2"}}
`)
runTests(t, i, []testCase{
{src: "r.Hello()", res: "Hello R"},
@@ -741,6 +760,8 @@ func TestEvalWithContext(t *testing.T) {
}
func runTests(t *testing.T, i *interp.Interpreter, tests []testCase) {
t.Helper()
for _, test := range tests {
t.Run(test.desc, func(t *testing.T) {
if test.skip != "" {
@@ -770,6 +791,8 @@ func eval(t *testing.T, i *interp.Interpreter, src string) reflect.Value {
}
func assertEval(t *testing.T, i *interp.Interpreter, src, expectedError, expectedRes string) {
t.Helper()
res, err := i.Eval(src)
if expectedError != "" {
@@ -1148,6 +1171,8 @@ func TestConcurrentComposite2(t *testing.T) {
}
func testConcurrentComposite(t *testing.T, filePath string) {
t.Helper()
if testing.Short() {
return
}

View File

@@ -32,7 +32,7 @@ func Hi(h Helloer) {
// The method calls will be forwarded to the interpreter.
//
// Only the Wrap type definition needs to be exported to the interpreter (not
// the interfaces and methods definitions)
// the interfaces and methods definitions).
//
type Wrap struct {
DoHello func() // related to the Hello() method.

View File

@@ -37,6 +37,8 @@ func TestFile(t *testing.T) {
}
func runCheck(t *testing.T, p string) {
t.Helper()
wanted, goPath, errWanted := wantedFromComment(p)
if wanted == "" {
t.Skip(p, "has no comment 'Output:' or 'Error:'")

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -33,12 +33,12 @@ func (interp *Interpreter) importSrc(rPath, importPath string, skipTest bool) (s
rPath = "."
}
dir = filepath.Join(filepath.Dir(interp.name), rPath, importPath)
} else if dir, rPath, err = pkgDir(interp.context.GOPATH, rPath, importPath); err != nil {
} else if dir, rPath, err = interp.pkgDir(interp.context.GOPATH, rPath, importPath); err != nil {
// Try again, assuming a root dir at the source location.
if rPath, err = interp.rootFromSourceLocation(); err != nil {
return "", err
}
if dir, rPath, err = pkgDir(interp.context.GOPATH, rPath, importPath); err != nil {
if dir, rPath, err = interp.pkgDir(interp.context.GOPATH, rPath, importPath); err != nil {
return "", err
}
}
@@ -181,7 +181,7 @@ func (interp *Interpreter) rootFromSourceLocation() (string, error) {
// pkgDir returns the absolute path in filesystem for a package given its import path
// and the root of the subtree dependencies.
func pkgDir(goPath string, root, importPath string) (string, string, error) {
func (interp *Interpreter) pkgDir(goPath string, root, importPath string) (string, string, error) {
rPath := filepath.Join(root, "vendor")
dir := filepath.Join(goPath, "src", rPath, importPath)
@@ -196,6 +196,9 @@ func pkgDir(goPath string, root, importPath string) (string, string, error) {
}
if len(root) == 0 {
if interp.context.GOPATH == "" {
return "", "", fmt.Errorf("unable to find source related to: %q. Either the GOPATH environment variable, or the Interpreter.Options.GoPath needs to be set", importPath)
}
return "", "", fmt.Errorf("unable to find source related to: %q", importPath)
}
@@ -205,7 +208,7 @@ func pkgDir(goPath string, root, importPath string) (string, string, error) {
return "", "", err
}
return pkgDir(goPath, prevRoot, importPath)
return interp.pkgDir(goPath, prevRoot, importPath)
}
const vendor = "vendor"

View File

@@ -161,6 +161,8 @@ func Test_pkgDir(t *testing.T) {
},
}
interp := &Interpreter{}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
@@ -178,7 +180,7 @@ func Test_pkgDir(t *testing.T) {
}
}
dir, rPath, err := pkgDir(goPath, test.root, test.path)
dir, rPath, err := interp.pkgDir(goPath, test.root, test.path)
if err != nil {
t.Fatal(err)
}

View File

@@ -198,7 +198,7 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
if sym.kind != constSym {
return nil, c0.cfgErrorf("non-constant array bound %q", c0.ident)
}
if sym.typ == nil || sym.typ.cat != intT {
if sym.typ == nil || sym.typ.cat != intT || !sym.rval.IsValid() {
t.incomplete = true
break
}
@@ -274,8 +274,33 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
t = t1
}
}
// Because an empty interface concrete type "mutates" as different values are
// assigned to it, we need to make a new itype from scratch everytime a new
// assignment is made, and not let different nodes (of the same variable) share the
// same itype. Otherwise they would overwrite each other.
if n.anc.kind == assignStmt && isInterface(n.anc.child[0].typ) && len(n.anc.child[0].typ.field) == 0 {
// TODO(mpl): do the indexes properly for multiple assignments on the same line.
// Also, maybe we should use nodeType to figure out dt.cat? but isn't it always
// gonna be an interfaceT anyway?
dt := new(itype)
dt.cat = interfaceT
val := new(itype)
val.cat = t.cat
dt.val = val
// TODO(mpl): do the indexes properly for multiple assignments on the same line.
// Also, maybe we should use nodeType to figure out dt.cat? but isn't it always
// gonna be an interfaceT anyway?
n.anc.child[0].typ = dt
// TODO(mpl): not sure yet whether we should do that last step. It doesn't seem
// to change anything either way though.
// t = dt
break
}
// If the node is to be assigned or returned, the node type is the destination type.
dt := t
switch a := n.anc; {
case a.kind == defineStmt && len(a.child) > a.nleft+a.nright:
if dt, err = nodeType(interp, sc, a.child[a.nleft]); err != nil {
@@ -1487,7 +1512,14 @@ func (t *itype) frameType() (r reflect.Type) {
case funcT:
r = reflect.TypeOf((*node)(nil))
case interfaceT:
if len(t.field) == 0 {
// empty interface, do not wrap it
r = reflect.TypeOf((*interface{})(nil)).Elem()
break
}
r = reflect.TypeOf((*valueInterface)(nil)).Elem()
case ptrT:
r = reflect.PtrTo(t.val.frameType())
default:
r = t.TypeOf()
}
@@ -1502,10 +1534,25 @@ func (t *itype) implements(it *itype) bool {
}
// defaultType returns the default type of an untyped type.
func (t *itype) defaultType() *itype {
func (t *itype) defaultType(v reflect.Value) *itype {
if !t.untyped {
return t
}
// The default type can also be derived from a constant value.
if v.IsValid() && t.TypeOf().Implements(constVal) {
switch v.Interface().(constant.Value).Kind() {
case constant.String:
t = untypedString()
case constant.Bool:
t = untypedBool()
case constant.Int:
t = untypedInt()
case constant.Float:
t = untypedFloat()
case constant.Complex:
t = untypedComplex()
}
}
typ := *t
typ.untyped = false
return &typ
@@ -1625,6 +1672,10 @@ func isInterfaceSrc(t *itype) bool {
return t.cat == interfaceT || (t.cat == aliasT && isInterfaceSrc(t.val))
}
func isInterfaceBin(t *itype) bool {
return t.cat == valueT && t.rtype.Kind() == reflect.Interface
}
func isInterface(t *itype) bool {
return isInterfaceSrc(t) || t.TypeOf() != nil && t.TypeOf().Kind() == reflect.Interface
}

View File

@@ -37,7 +37,7 @@ func (check typecheck) assignment(n *node, typ *itype, context string) error {
if typ == nil && n.typ.cat == nilT {
return n.cfgErrorf("use of untyped nil in %s", context)
}
typ = n.typ.defaultType()
typ = n.typ.defaultType(n.rval)
}
if err := check.convertUntyped(n, typ); err != nil {
return err
@@ -65,7 +65,7 @@ func (check typecheck) assignExpr(n, dest, src *node) error {
isConst := n.anc.kind == constDecl
if !isConst {
// var operations must be typed
dest.typ = dest.typ.defaultType()
dest.typ = dest.typ.defaultType(src.rval)
}
return check.assignment(src, dest.typ, "assignment")
@@ -636,7 +636,7 @@ func (check typecheck) conversion(n *node, typ *itype) error {
return nil
}
if isInterface(typ) || !isConstType(typ) {
typ = n.typ.defaultType()
typ = n.typ.defaultType(n.rval)
}
return check.convertUntyped(n, typ)
}
@@ -1037,9 +1037,8 @@ func (check typecheck) convertUntyped(n *node, typ *itype) error {
if len(n.typ.methods()) > 0 { // untyped cannot be set to iface
return convErr
}
ityp = n.typ.defaultType()
ityp = n.typ.defaultType(n.rval)
rtyp = ntyp
case isArray(typ) || isMap(typ) || isChan(typ) || isFunc(typ) || isPtr(typ):
// TODO(nick): above we are acting on itype, but really it is an rtype check. This is not clear which type
// plain we are in. Fix this later.

View File

@@ -221,21 +221,25 @@ func genValueRangeArray(n *node) func(*frame) reflect.Value {
return value(f).Elem()
}
case n.typ.val != nil && n.typ.val.cat == interfaceT:
return func(f *frame) reflect.Value {
val := value(f)
v := []valueInterface{}
for i := 0; i < val.Len(); i++ {
switch av := val.Index(i).Interface().(type) {
case []valueInterface:
v = append(v, av...)
case valueInterface:
v = append(v, av)
default:
panic(n.cfgErrorf("invalid type %v", val.Index(i).Type()))
if len(n.typ.val.field) > 0 {
return func(f *frame) reflect.Value {
val := value(f)
v := []valueInterface{}
for i := 0; i < val.Len(); i++ {
switch av := val.Index(i).Interface().(type) {
case []valueInterface:
v = append(v, av...)
case valueInterface:
v = append(v, av)
default:
panic(n.cfgErrorf("invalid type %v", val.Index(i).Type()))
}
}
return reflect.ValueOf(v)
}
return reflect.ValueOf(v)
}
// empty interface, do not wrap.
fallthrough
default:
return func(f *frame) reflect.Value {
// This is necessary to prevent changes in the returned
@@ -265,6 +269,7 @@ func genValueInterface(n *node) func(*frame) reflect.Value {
return func(f *frame) reflect.Value {
v := value(f)
nod := n
for v.IsValid() {
// traverse interface indirections to find out concrete type
vi, ok := v.Interface().(valueInterface)
@@ -274,6 +279,12 @@ func genValueInterface(n *node) func(*frame) reflect.Value {
v = vi.value
nod = vi.node
}
// empty interface, do not wrap.
if nod.typ.cat == interfaceT && len(nod.typ.field) == 0 {
return v
}
return reflect.ValueOf(valueInterface{nod, v})
}
}
@@ -284,12 +295,26 @@ func zeroInterfaceValue() reflect.Value {
return reflect.ValueOf(valueInterface{n, v})
}
func wantEmptyInterface(n *node) bool {
return n.typ.cat == interfaceT && len(n.typ.field) == 0 ||
n.anc.action == aAssign && n.anc.typ.cat == interfaceT && len(n.anc.typ.field) == 0 ||
n.anc.kind == returnStmt && n.anc.val.(*node).typ.ret[0].cat == interfaceT && len(n.anc.val.(*node).typ.ret[0].field) == 0
}
func genValueOutput(n *node, t reflect.Type) func(*frame) reflect.Value {
value := genValue(n)
switch {
case n.anc.action == aAssign && n.anc.typ.cat == interfaceT:
if len(n.anc.typ.field) == 0 {
// empty interface, do not wrap
return value
}
fallthrough
case n.anc.kind == returnStmt && n.anc.val.(*node).typ.ret[0].cat == interfaceT:
if len(n.anc.val.(*node).typ.ret[0].field) == 0 {
// empty interface, do not wrap
return value
}
// The result of the builtin has to be returned as an interface type.
// Wrap it in a valueInterface and return the dereferenced value.
return func(f *frame) reflect.Value {