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", "gochecknoinits",
"gochecknoglobals", "gochecknoglobals",
"wsl", "wsl",
"nlreturn",
"godox", "godox",
"funlen", "funlen",
"gocognit", "gocognit",
"stylecheck", "stylecheck",
"gomnd", "gomnd",
"testpackage", "testpackage",
"paralleltest",
"tparallel",
"goerr113", "goerr113",
"wrapcheck",
"nestif", "nestif",
"exhaustive", "exhaustive",
"nlreturn", "exhaustivestruct",
"forbidigo",
"ifshort",
"errorlint", # TODO: must be reactivate before fixes
] ]
[issues] [issues]
@@ -61,3 +68,6 @@
[[issues.exclude-rules]] [[issues.exclude-rules]]
path = "interp/interp.go" path = "interp/interp.go"
text = "`out` can be `io.Writer`" 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 package main
func main() { func main() {
b := 2 b := 2 // int
var a interface{} = 5 + b
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)) println(a.(int))
} }
// Output: // Output:
// 7 // 7
// 8
// 9
// 10
// 11
// 12

View File

@@ -2,6 +2,7 @@ package main
import ( import (
"encoding/xml" "encoding/xml"
"errors"
"fmt" "fmt"
) )
@@ -10,7 +11,30 @@ type Email struct {
Addr string 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) return xml.Unmarshal([]byte(s), &r)
} }
@@ -21,9 +45,19 @@ func main() {
</Email> </Email>
` `
v := Email{} v := Email{}
err := f(data, &v) err := f(&v)
fmt.Println(err, 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: // Ouput:
// <nil> {work bob@work.com} // <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 ( import (
"fmt" "fmt"
"reflect"
"time" "time"
) )
@@ -10,6 +9,10 @@ type MyWriter interface {
Write(p []byte) (i int, err error) Write(p []byte) (i int, err error)
} }
type DummyWriter interface {
Write(p []byte) (i int, err error)
}
type TestStruct struct{} type TestStruct struct{}
func (t TestStruct) Write(p []byte) (n int, err error) { func (t TestStruct) Write(p []byte) (n int, err error) {
@@ -25,14 +28,18 @@ type MyStringer interface {
String() string String() string
} }
type DummyStringer interface {
String() string
}
func usesStringer(s MyStringer) { func usesStringer(s MyStringer) {
fmt.Println(s.String()) fmt.Println(s.String())
} }
func main() { func main() {
aType := reflect.TypeOf((*MyWriter)(nil)).Elem() // TODO(mpl): restore when we can deal with empty interface.
// var t interface{}
var t interface{} var t DummyWriter
t = TestStruct{} t = TestStruct{}
var tw MyWriter var tw MyWriter
var ok bool var ok bool
@@ -45,8 +52,6 @@ func main() {
} }
n, _ := t.(MyWriter).Write([]byte("hello world")) n, _ := t.(MyWriter).Write([]byte("hello world"))
fmt.Println(n) 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. // not redundant with the above, because it goes through a slightly different code path.
if _, ok := t.(MyWriter); !ok { if _, ok := t.(MyWriter); !ok {
@@ -56,6 +61,8 @@ func main() {
fmt.Println("TestStruct implements MyWriter") fmt.Println("TestStruct implements MyWriter")
} }
// TODO(mpl): restore
/*
t = 42 t = 42
foo, ok := t.(MyWriter) foo, ok := t.(MyWriter)
if !ok { if !ok {
@@ -70,8 +77,10 @@ func main() {
} else { } else {
fmt.Println("42 implements MyWriter") fmt.Println("42 implements MyWriter")
} }
*/
var tt interface{} // var tt interface{}
var tt DummyStringer
tt = time.Nanosecond tt = time.Nanosecond
var myD MyStringer var myD MyStringer
myD, ok = tt.(MyStringer) myD, ok = tt.(MyStringer)
@@ -82,9 +91,6 @@ func main() {
usesStringer(myD) usesStringer(myD)
} }
fmt.Println(tt.(MyStringer).String()) 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 { if _, ok := tt.(MyStringer); !ok {
fmt.Println("time.Nanosecond does not implement MyStringer") fmt.Println("time.Nanosecond does not implement MyStringer")
@@ -92,6 +98,8 @@ func main() {
fmt.Println("time.Nanosecond implements MyStringer") fmt.Println("time.Nanosecond implements MyStringer")
} }
// TODO(mpl): restore
/*
tt = 42 tt = 42
bar, ok := tt.(MyStringer) bar, ok := tt.(MyStringer)
if !ok { if !ok {
@@ -106,20 +114,15 @@ func main() {
} else { } else {
fmt.Println("42 implements MyStringer") fmt.Println("42 implements MyStringer")
} }
*/
} }
// Output: // Output:
// TestStruct implements MyWriter // TestStruct implements MyWriter
// 11 // 11
// 11 // 11
// true
// TestStruct implements MyWriter // TestStruct implements MyWriter
// 42 does not implement MyWriter
// 42 does not implement MyWriter
// time.Nanosecond implements MyStringer // time.Nanosecond implements MyStringer
// 1ns // 1ns
// 1ns // 1ns
// true
// time.Nanosecond implements MyStringer // 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" return "hello world"
} }
type DummyStringer interface{
String() string
}
func main() { func main() {
aType := reflect.TypeOf((*fmt.Stringer)(nil)).Elem() aType := reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
@@ -52,7 +56,9 @@ func main() {
return return
} }
var tt interface{} // TODO(mpl): restore when fixed
// var tt interface{}
var tt DummyStringer
tt = TestStruct{} tt = TestStruct{}
ss, ok := tt.(fmt.Stringer) ss, ok := tt.(fmt.Stringer)
if !ok { if !ok {
@@ -61,9 +67,6 @@ func main() {
} }
fmt.Println(ss.String()) fmt.Println(ss.String())
fmt.Println(tt.(fmt.Stringer).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 { if _, ok := tt.(fmt.Stringer); !ok {
fmt.Println("TestStuct does not implement fmt.Stringer") fmt.Println("TestStuct does not implement fmt.Stringer")

View File

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

View File

@@ -32,6 +32,7 @@ func interfaceAsInterfaces() {
println("nope") println("nope")
return return
} }
fmt.Println(d)
for _, v := range d { for _, v := range d {
fmt.Println(v) fmt.Println(v)
@@ -46,5 +47,6 @@ func main() {
// Output: // Output:
// 2 // 2
// 3 // 3
// [2 3]
// 2 // 2
// 3 // 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 f1(a int) interface{} { return a + 1 }
func f2(a int64) interface{} { return a + 1 }
func main() { func main() {
c := f1(3) c := f1(3)
println(c.(int)) println(c.(int))
b := f2(3)
println(b.(int64))
} }
// Output: // Output:
// 4 // 4
// 4

View File

@@ -2,7 +2,13 @@ package main
func f1(a int) int { return a + 1 } 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() { func main() {
c := f2(3) 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 Hello() string
} }
type Hey interface {
Hello() string
}
func (r *Root) Hello() string { return "Hello " + r.Name } func (r *Root) Hello() string { return "Hello " + r.Name }
func main() { 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()) println(one.(Hi).Hello())
} }

View File

@@ -2,12 +2,26 @@ package main
func main() { func main() {
var a interface{} var a interface{}
a = []int{3}
switch a.(type) { switch a.(type) {
case []int: case []int:
println("a is []int")
case []string: 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") println("bye")
} }
// Output: // Output:
// a is []int
// b is []string
// bye // 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) f, err := os.Open(fname)
if err != nil { 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() }() defer func() { _ = f.Close() }()
@@ -116,7 +116,7 @@ func genLicense(fname string) (string, error) {
license.WriteString("//" + txt + "\n") license.WriteString("//" + txt + "\n")
} }
if sc.Err() != nil { 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 return license.String(), nil

View File

@@ -50,9 +50,9 @@ func TestYaegiCmdCancel(t *testing.T) {
yaegi := filepath.Join(tmp, "yaegi") yaegi := filepath.Join(tmp, "yaegi")
build := exec.Command("go", "build", "-race", "-o", yaegi, ".") build := exec.Command("go", "build", "-race", "-o", yaegi, ".")
err = build.Run() out, err := build.CombinedOutput()
if err != nil { 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. // Test src must be terminated by a single newline.

View File

@@ -104,7 +104,7 @@ func TestPackages(t *testing.T) {
for _, test := range testCases { for _, test := range testCases {
test := test test := test
t.Run(test.desc, func(t *testing.T) { 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -115,7 +115,7 @@ func TestPackages(t *testing.T) {
var msg string var msg string
if test.evalFile != "" { 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) fatalStderrf(t, "%v", err)
} }
msg = stdout.String() msg = stdout.String()
@@ -146,6 +146,8 @@ func TestPackages(t *testing.T) {
} }
func fatalStderrf(t *testing.T, format string, args ...interface{}) { func fatalStderrf(t *testing.T, format string, args ...interface{}) {
t.Helper()
fmt.Fprintf(os.Stderr, format+"\n", args...) fmt.Fprintf(os.Stderr, format+"\n", args...)
t.FailNow() t.FailNow()
} }
@@ -159,7 +161,7 @@ func TestPackagesError(t *testing.T) {
{ {
desc: "different packages in the same directory", desc: "different packages in the same directory",
goPath: "./_pkg9/", goPath: "./_pkg9/",
expected: "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") base := template.New("extract")
parse, err := base.Parse(model) parse, err := base.Parse(model)
if err != nil { 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" { if importPath == "log/syslog" {
@@ -274,13 +274,13 @@ func (e *Extractor) genContent(importPath string, p *types.Package) ([]byte, err
} }
err = parse.Execute(b, data) err = parse.Execute(b, data)
if err != nil { if err != nil {
return nil, fmt.Errorf("template error: %v", err) return nil, fmt.Errorf("template error: %w", err)
} }
// gofmt // gofmt
source, err := format.Source(b.Bytes()) source, err := format.Source(b.Bytes())
if err != nil { 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 return source, nil
} }
@@ -451,7 +451,7 @@ func genBuildTags() (string, error) {
minor, err := strconv.Atoi(minorRaw) minor, err := strconv.Atoi(minorRaw)
if err != nil { 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 // Only append an upper bound if we are not on the latest go

View File

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

View File

@@ -564,9 +564,11 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
// //
switch { switch {
case n.action != aAssign: 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): 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. // As we only write, skip the default useless getIndexMap dest action.
dest.gen = nop dest.gen = nop
case isCall(src) && dest.typ.cat != interfaceT && !isRecursiveField(dest): case isCall(src) && dest.typ.cat != interfaceT && !isRecursiveField(dest):
@@ -588,19 +590,19 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
break break
} }
if dest.action == aGetIndex { 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 break
} }
n.gen = nop n.gen = nop
src.findex = dest.findex src.findex = dest.findex
src.level = level 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. // Optimize single assignments from some arithmetic operations.
src.typ = dest.typ src.typ = dest.typ
src.findex = dest.findex src.findex = dest.findex
src.level = level src.level = level
n.gen = nop n.gen = nop
case src.kind == basicLit && !src.rval.IsValid(): case src.kind == basicLit:
// Assign to nil. // Assign to nil.
src.rval = reflect.New(dest.typ.TypeOf()).Elem() 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.typ = n.typ
sym.recv = src.recv sym.recv = src.recv
} }
n.level = level n.level = level
if n.anc.kind == constDecl { if n.anc.kind == constDecl {
@@ -711,7 +714,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
break 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. n.typ.TypeOf() // Force compute of reflection type.
constOp[n.action](n) // Compute a constant result now rather than during exec. 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) wireChild(n)
switch { switch {
case interp.isBuiltinCall(n): 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 { if err != nil {
break break
} }
n.gen = n.child[0].sym.builtin n.gen = c0.sym.builtin
n.child[0].typ = &itype{cat: builtinT} c0.typ = &itype{cat: builtinT}
if n.typ, err = nodeType(interp, sc, n); err != nil { if n.typ, err = nodeType(interp, sc, n); err != nil {
return return
} }
@@ -872,10 +877,28 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
case n.anc.kind == returnStmt: case n.anc.kind == returnStmt:
// Store result directly to frame output location, to avoid a frame copy. // Store result directly to frame output location, to avoid a frame copy.
n.findex = 0 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: default:
n.findex = sc.add(n.typ) 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 : op(n) // pre-compute non-assigned constant :
} }
case n.child[0].isType(sc): case n.child[0].isType(sc):
@@ -1863,6 +1886,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
return return
} }
} }
for _, c := range n.child[:l] { for _, c := range n.child[:l] {
var index int var index int
if sc.global { if sc.global {
@@ -2326,6 +2350,20 @@ func isRecursiveField(n *node) bool {
return false 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. // isNewDefine returns true if node refers to a new definition.
func isNewDefine(n *node, sc *scope) bool { func isNewDefine(n *node, sc *scope) bool {
if n.ident == "_" { if n.ident == "_" {
@@ -2485,6 +2523,10 @@ func compositeGenerator(n *node, typ *itype, rtyp reflect.Type) (gen bltnGenerat
if rtyp == nil { if rtyp == nil {
rtyp = n.typ.rtype 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 { switch k := rtyp.Kind(); k {
case reflect.Struct: case reflect.Struct:
if n.nleft == 1 { if n.nleft == 1 {
@@ -2534,28 +2576,22 @@ func isValueUntyped(v reflect.Value) bool {
if v.CanSet() { if v.CanSet() {
return false return false
} }
t := v.Type() return v.Type().Implements(constVal)
if t.Implements(constVal) {
return true
}
return t.String() == t.Kind().String()
} }
// isArithmeticAction returns true if the node action is an arithmetic operator. // isArithmeticAction returns true if the node action is an arithmetic operator.
func isArithmeticAction(n *node) bool { func isArithmeticAction(n *node) bool {
switch n.action { 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 return true
default:
return false
} }
return false
} }
func isBoolAction(n *node) bool { func isBoolAction(n *node) bool {
switch n.action { switch n.action {
case aEqual, aGreater, aGreaterEqual, aLand, aLor, aLower, aLowerEqual, aNot, aNotEqual: case aEqual, aGreater, aGreaterEqual, aLand, aLor, aLower, aLowerEqual, aNot, aNotEqual:
return true 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. // values which may be used in further declarations.
if _, err = interp.cfg(n, importPath); err != nil { if _, err = interp.cfg(n, importPath); err != nil {
// No error processing here, to allow recovery in subtree nodes. // 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 err = nil
} }

View File

@@ -75,10 +75,10 @@ type frame struct {
done reflect.SelectCase // for cancellation of channel operations 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{ f := &frame{
anc: anc, anc: anc,
data: make([]reflect.Value, len), data: make([]reflect.Value, length),
id: id, id: id,
} }
if anc == nil { 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); a+b", res: "15"},
{src: "b := uint(5); b+a", res: "15"}, {src: "b := uint(5); b+a", res: "15"},
{src: "b := uint(5); b>a", res: "false"}, {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) { func TestEvalAssign(t *testing.T) {
i := interp.New(interp.Options{}) 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{ runTests(t, i, []testCase{
{src: `a := "Hello"; a += " world"`, res: "Hello world"}, {src: `a := "Hello"; a += " world"`, res: "Hello world"},
{src: `b := "Hello"; b += 1`, err: "1:42: invalid operation: mismatched types string and int"}, {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: "g := 1; g <<= 8", res: "256"},
{src: "h := 1; h >>= 8", res: "0"}, {src: "h := 1; h >>= 8", res: "0"},
{src: "i := 1; j := &i; (*j) = 2", res: "2"}, {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 Hello() string
} }
type Hey interface {
Hello() string
}
func (r *Root) Hello() string { return "Hello " + r.Name } func (r *Root) Hello() string { return "Hello " + r.Name }
var r = Root{"R"} var r = Root{"R"}
var o = One{r} var o = One{r}
var root interface{} = &Root{Name: "test1"} // TODO(mpl): restore empty interfaces when type assertions work (again) on them.
var one interface{} = &One{Root{Name: "test2"}} // 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{ runTests(t, i, []testCase{
{src: "r.Hello()", res: "Hello R"}, {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) { func runTests(t *testing.T, i *interp.Interpreter, tests []testCase) {
t.Helper()
for _, test := range tests { for _, test := range tests {
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
if test.skip != "" { 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) { func assertEval(t *testing.T, i *interp.Interpreter, src, expectedError, expectedRes string) {
t.Helper()
res, err := i.Eval(src) res, err := i.Eval(src)
if expectedError != "" { if expectedError != "" {
@@ -1148,6 +1171,8 @@ func TestConcurrentComposite2(t *testing.T) {
} }
func testConcurrentComposite(t *testing.T, filePath string) { func testConcurrentComposite(t *testing.T, filePath string) {
t.Helper()
if testing.Short() { if testing.Short() {
return return
} }

View File

@@ -32,7 +32,7 @@ func Hi(h Helloer) {
// The method calls will be forwarded to the interpreter. // The method calls will be forwarded to the interpreter.
// //
// Only the Wrap type definition needs to be exported to the interpreter (not // 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 { type Wrap struct {
DoHello func() // related to the Hello() method. 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) { func runCheck(t *testing.T, p string) {
t.Helper()
wanted, goPath, errWanted := wantedFromComment(p) wanted, goPath, errWanted := wantedFromComment(p)
if wanted == "" { if wanted == "" {
t.Skip(p, "has no comment 'Output:' or 'Error:'") 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 = "." rPath = "."
} }
dir = filepath.Join(filepath.Dir(interp.name), rPath, importPath) 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. // Try again, assuming a root dir at the source location.
if rPath, err = interp.rootFromSourceLocation(); err != nil { if rPath, err = interp.rootFromSourceLocation(); err != nil {
return "", err 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 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 // pkgDir returns the absolute path in filesystem for a package given its import path
// and the root of the subtree dependencies. // 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") rPath := filepath.Join(root, "vendor")
dir := filepath.Join(goPath, "src", rPath, importPath) 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 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) 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 "", "", err
} }
return pkgDir(goPath, prevRoot, importPath) return interp.pkgDir(goPath, prevRoot, importPath)
} }
const vendor = "vendor" const vendor = "vendor"

View File

@@ -161,6 +161,8 @@ func Test_pkgDir(t *testing.T) {
}, },
} }
interp := &Interpreter{}
for _, test := range testCases { for _, test := range testCases {
test := test test := test
t.Run(test.desc, func(t *testing.T) { 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@@ -198,7 +198,7 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
if sym.kind != constSym { if sym.kind != constSym {
return nil, c0.cfgErrorf("non-constant array bound %q", c0.ident) 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 t.incomplete = true
break break
} }
@@ -274,8 +274,33 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
t = t1 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. // If the node is to be assigned or returned, the node type is the destination type.
dt := t dt := t
switch a := n.anc; { switch a := n.anc; {
case a.kind == defineStmt && len(a.child) > a.nleft+a.nright: case a.kind == defineStmt && len(a.child) > a.nleft+a.nright:
if dt, err = nodeType(interp, sc, a.child[a.nleft]); err != nil { 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: case funcT:
r = reflect.TypeOf((*node)(nil)) r = reflect.TypeOf((*node)(nil))
case interfaceT: 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() r = reflect.TypeOf((*valueInterface)(nil)).Elem()
case ptrT:
r = reflect.PtrTo(t.val.frameType())
default: default:
r = t.TypeOf() r = t.TypeOf()
} }
@@ -1502,10 +1534,25 @@ func (t *itype) implements(it *itype) bool {
} }
// defaultType returns the default type of an untyped type. // 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 { if !t.untyped {
return t 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 := *t
typ.untyped = false typ.untyped = false
return &typ return &typ
@@ -1625,6 +1672,10 @@ func isInterfaceSrc(t *itype) bool {
return t.cat == interfaceT || (t.cat == aliasT && isInterfaceSrc(t.val)) 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 { func isInterface(t *itype) bool {
return isInterfaceSrc(t) || t.TypeOf() != nil && t.TypeOf().Kind() == reflect.Interface 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 { if typ == nil && n.typ.cat == nilT {
return n.cfgErrorf("use of untyped nil in %s", context) 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 { if err := check.convertUntyped(n, typ); err != nil {
return err return err
@@ -65,7 +65,7 @@ func (check typecheck) assignExpr(n, dest, src *node) error {
isConst := n.anc.kind == constDecl isConst := n.anc.kind == constDecl
if !isConst { if !isConst {
// var operations must be typed // var operations must be typed
dest.typ = dest.typ.defaultType() dest.typ = dest.typ.defaultType(src.rval)
} }
return check.assignment(src, dest.typ, "assignment") return check.assignment(src, dest.typ, "assignment")
@@ -636,7 +636,7 @@ func (check typecheck) conversion(n *node, typ *itype) error {
return nil return nil
} }
if isInterface(typ) || !isConstType(typ) { if isInterface(typ) || !isConstType(typ) {
typ = n.typ.defaultType() typ = n.typ.defaultType(n.rval)
} }
return check.convertUntyped(n, typ) 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 if len(n.typ.methods()) > 0 { // untyped cannot be set to iface
return convErr return convErr
} }
ityp = n.typ.defaultType() ityp = n.typ.defaultType(n.rval)
rtyp = ntyp rtyp = ntyp
case isArray(typ) || isMap(typ) || isChan(typ) || isFunc(typ) || isPtr(typ): 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 // 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. // 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() return value(f).Elem()
} }
case n.typ.val != nil && n.typ.val.cat == interfaceT: case n.typ.val != nil && n.typ.val.cat == interfaceT:
return func(f *frame) reflect.Value { if len(n.typ.val.field) > 0 {
val := value(f) return func(f *frame) reflect.Value {
v := []valueInterface{} val := value(f)
for i := 0; i < val.Len(); i++ { v := []valueInterface{}
switch av := val.Index(i).Interface().(type) { for i := 0; i < val.Len(); i++ {
case []valueInterface: switch av := val.Index(i).Interface().(type) {
v = append(v, av...) case []valueInterface:
case valueInterface: v = append(v, av...)
v = append(v, av) case valueInterface:
default: v = append(v, av)
panic(n.cfgErrorf("invalid type %v", val.Index(i).Type())) 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: default:
return func(f *frame) reflect.Value { return func(f *frame) reflect.Value {
// This is necessary to prevent changes in the returned // 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 { return func(f *frame) reflect.Value {
v := value(f) v := value(f)
nod := n nod := n
for v.IsValid() { for v.IsValid() {
// traverse interface indirections to find out concrete type // traverse interface indirections to find out concrete type
vi, ok := v.Interface().(valueInterface) vi, ok := v.Interface().(valueInterface)
@@ -274,6 +279,12 @@ func genValueInterface(n *node) func(*frame) reflect.Value {
v = vi.value v = vi.value
nod = vi.node 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}) return reflect.ValueOf(valueInterface{nod, v})
} }
} }
@@ -284,12 +295,26 @@ func zeroInterfaceValue() reflect.Value {
return reflect.ValueOf(valueInterface{n, v}) 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 { func genValueOutput(n *node, t reflect.Type) func(*frame) reflect.Value {
value := genValue(n) value := genValue(n)
switch { switch {
case n.anc.action == aAssign && n.anc.typ.cat == interfaceT: 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 fallthrough
case n.anc.kind == returnStmt && n.anc.val.(*node).typ.ret[0].cat == interfaceT: 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. // The result of the builtin has to be returned as an interface type.
// Wrap it in a valueInterface and return the dereferenced value. // Wrap it in a valueInterface and return the dereferenced value.
return func(f *frame) reflect.Value { return func(f *frame) reflect.Value {