Compare commits

..

23 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
Marc Vertes
274eecdf18 interp: fix type recursivity detection
Fix the logic to detect recursive struct types, which was giving a false positive.
We now use the local type name  as key in tracker map.

A non-regression test case is included (_test/struct49.go).

This completes #1008.
2021-01-19 19:32:05 +01:00
Marc Vertes
8fa00f826c interp: fix map assignment from arithmetic operations
The logic to trigger assigment optimizations has been refactored for
clarity, and to exclude assignments to map entries.

Fixes #981.
2021-01-18 19:04:05 +01:00
Marc Vertes
a64fe5b210 interp: fix detection of type recursivity
If a struct contains several fields of the same temporary incomplete
type, it could be detected incorrectly as a recursive struct. Pass
a copy of defined types map to avoid this issue.

Fixes #1007.
2021-01-15 12:14:04 +01:00
Marc Vertes
5c59dc425f interp: fix operators working on integer constants
Always attempt to obtain an integer constant value for operators
expecting so. It allows to use '/' in integer constant defintions,
instead of default big.Rat.

Fixes #1005
2021-01-14 17:26:06 +01:00
Marc Vertes
8ad14d8ea4 interp: handle aliased string used as a slice
Fixes #1002.
2021-01-14 16:48:06 +01:00
Marc Vertes
8a1f9ef44e interp: parse circular interface definitions
An undefined type detection function has been added to better diagnose
incomplete type definitions. Implicit type names in interface or struct
declarations are now better handled. The incomplete status is not
fowarded to aliased type declarations to handle circular definitions.

Fixes #999 and #995. Improves #260 (goes farther, but still fails).
2021-01-14 15:46:04 +01:00
Marc Vertes
5cd1e11379 chore: rename github to tap, following goreleaser deprecation notice
The release of v0.9.9 failed, due to
https://goreleaser.com/deprecations/#brewsgithub.
2021-01-06 09:14:04 +01:00
Marc Vertes
24b5375636 interp: fix memory handling of global values
In some cases, the global character of a value was lost, leading to
undefined behaviour. Now a node level field of -1 means that the value
is global, and that it should be accessed from the root data frame.

Fixes #993.
2021-01-05 17:28:03 +01:00
Marc Vertes
a83f492309 interp: add support for binary composite slice
Fixes #987.
2020-12-15 18:20:04 +01:00
mpl
02c30482cc interp: enable type assertion from empty interface into slice
Fixes #985
2020-12-15 17:28:04 +01:00
Marc Vertes
9e1da978b0 interp: fix handling interface types in wrapped functions
The interpreter interface type was replaced by a reflect.Value in
objects passed or return to function wrappers, losing the ability
to retrieve methods.

The valueInterface is now preserved, and correctly accessed if
wrapped multiple times.

Fixes #977.
2020-12-15 16:14:05 +01:00
mpl
662838fd80 interp: fix and refactor typeAssertStatus in
typeAssertStatus deals with the 3rd form of type assertion ("_, ok"), for
when one does not care about the result of the assertion itself.
Some cases for it, which are already fixed for the two other forms of
type assertions, had not been fixed for this form yet.

Therefore, this change fixes such cases for this form, while integrating
typeAssertStatus to the same code path as for the two other forms.
2020-12-07 15:58:04 +01:00
55 changed files with 2809 additions and 544 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

@@ -47,7 +47,7 @@ archives:
- LICENSE
brews:
- github:
- tap:
owner: traefik
name: homebrew-tap
commit_author:

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,9 +52,17 @@ 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 {
fmt.Println("TestStruct does not implement MyWriter")
return
} else {
fmt.Println("TestStruct implements MyWriter")
}
// TODO(mpl): restore
/*
t = 42
foo, ok := t.(MyWriter)
if !ok {
@@ -57,7 +72,15 @@ func main() {
}
_ = foo
var tt interface{}
if _, ok := t.(MyWriter); !ok {
fmt.Println("42 does not implement MyWriter")
} else {
fmt.Println("42 implements MyWriter")
}
*/
// var tt interface{}
var tt DummyStringer
tt = time.Nanosecond
var myD MyStringer
myD, ok = tt.(MyStringer)
@@ -68,10 +91,15 @@ 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")
} else {
fmt.Println("time.Nanosecond implements MyStringer")
}
// TODO(mpl): restore
/*
tt = 42
bar, ok := tt.(MyStringer)
if !ok {
@@ -81,16 +109,20 @@ func main() {
}
_ = bar
if _, ok := tt.(MyStringer); !ok {
fmt.Println("42 does not implement MyStringer")
} else {
fmt.Println("42 implements MyStringer")
}
*/
}
// Output:
// TestStruct implements MyWriter
// 11
// 11
// true
// 42 does not implement MyWriter
// TestStruct implements MyWriter
// time.Nanosecond implements MyStringer
// 1ns
// 1ns
// true
// 42 does not implement MyStringer
// time.Nanosecond implements 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()
@@ -27,6 +31,13 @@ func main() {
bType := reflect.TypeOf(time.Nanosecond)
fmt.Println(bType.Implements(aType))
// not redundant with the above, because it goes through a slightly different code path.
if _, ok := t.(fmt.Stringer); !ok {
fmt.Println("time.Nanosecond does not implement fmt.Stringer")
return
} else {
fmt.Println("time.Nanosecond implements fmt.Stringer")
}
t = 42
foo, ok := t.(fmt.Stringer)
@@ -34,10 +45,20 @@ func main() {
fmt.Println("42 does not implement fmt.Stringer")
} else {
fmt.Println("42 implements fmt.Stringer")
return
}
_ = foo
var tt interface{}
if _, ok := t.(fmt.Stringer); !ok {
fmt.Println("42 does not implement fmt.Stringer")
} else {
fmt.Println("42 implements fmt.Stringer")
return
}
// TODO(mpl): restore when fixed
// var tt interface{}
var tt DummyStringer
tt = TestStruct{}
ss, ok := tt.(fmt.Stringer)
if !ok {
@@ -46,15 +67,22 @@ 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")
return
} else {
fmt.Println("TestStuct implements fmt.Stringer")
}
}
// Output:
// 1ns
// 1ns
// true
// time.Nanosecond implements fmt.Stringer
// 42 does not implement fmt.Stringer
// 42 does not implement fmt.Stringer
// hello world
// hello world
// TestStuct implements 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

52
_test/composite15.go Normal file
View File

@@ -0,0 +1,52 @@
package main
import (
"fmt"
)
func interfaceAsInts() {
var a interface{}
b := 2
c := 3
a = []int{b, c}
d, ok := a.([]int)
if !ok {
println("nope")
return
}
for _, v := range d {
fmt.Println(v)
}
}
func interfaceAsInterfaces() {
var a, b, c interface{}
b = 2
c = 3
a = []interface{}{b, c}
d, ok := a.([]interface{})
if !ok {
println("nope")
return
}
fmt.Println(d)
for _, v := range d {
fmt.Println(v)
}
}
func main() {
interfaceAsInts()
interfaceAsInterfaces()
}
// Output:
// 2
// 3
// [2 3]
// 2
// 3

16
_test/composite16.go Normal file
View File

@@ -0,0 +1,16 @@
package main
import (
"fmt"
"net/url"
)
func main() {
body := url.Values{
"Action": {"none"},
}
fmt.Println(body)
}
// Output:
// map[Action:[none]]

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

15
_test/const22.go Normal file
View File

@@ -0,0 +1,15 @@
package main
const (
numDec uint8 = (1 << iota) / 2
numHex
numOct
numFloat
)
func main() {
println(13 & (numHex | numOct))
}
// Output:
// 1

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)

32
_test/interface47.go Normal file
View File

@@ -0,0 +1,32 @@
package main
type Doer interface {
Do() error
}
type T struct {
Name string
}
func (t *T) Do() error { println("in do"); return nil }
func f() (Doer, error) { return &T{"truc"}, nil }
type Ev struct {
doer func() (Doer, error)
}
func (e *Ev) do() {
d, _ := e.doer()
d.Do()
}
func main() {
e := &Ev{f}
println(e != nil)
e.do()
}
// Output:
// true
// in do

17
_test/interface48.go Normal file
View File

@@ -0,0 +1,17 @@
package main
import "fmt"
type I1 interface{ A }
type A = I2
type I2 interface{ F() I1 }
func main() {
var i I1
fmt.Println(i)
}
// Output:
// <nil>

45
_test/interface49.go Normal file
View File

@@ -0,0 +1,45 @@
package main
type Descriptor interface {
ParentFile() FileDescriptor
}
type FileDescriptor interface {
Enums() EnumDescriptors
Services() ServiceDescriptors
}
type EnumDescriptors interface {
Get(i int) EnumDescriptor
}
type EnumDescriptor interface {
Values() EnumValueDescriptors
}
type EnumValueDescriptors interface {
Get(i int) EnumValueDescriptor
}
type EnumValueDescriptor interface {
Descriptor
}
type ServiceDescriptors interface {
Get(i int) ServiceDescriptor
}
type ServiceDescriptor interface {
Descriptor
isServiceDescriptor
}
type isServiceDescriptor interface{ ProtoType(ServiceDescriptor) }
func main() {
var d Descriptor
println(d == nil)
}
// Output:
// true

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

40
_test/issue-1007.go Normal file
View File

@@ -0,0 +1,40 @@
package main
type TypeA struct {
B TypeB
}
type TypeB struct {
C1 *TypeC
C2 *TypeC
}
type TypeC struct {
Val string
D *TypeD
D2 *TypeD
}
type TypeD struct {
Name string
}
func build() *TypeA {
return &TypeA{
B: TypeB{
C2: &TypeC{Val: "22"},
},
}
}
func Bar(s string) string {
a := build()
return s + "-" + a.B.C2.Val
}
func main() {
println(Bar("test"))
}
// Output:
// test-22

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!

15
_test/issue-981.go Normal file
View File

@@ -0,0 +1,15 @@
package main
import "fmt"
func main() {
dp := make(map[int]int)
dp[0] = 1
for i := 1; i < 10; i++ {
dp[i] = dp[i-1] + dp[i-2]
}
fmt.Printf("%v\n", dp)
}
// Output:
// map[0:1 1:1 2:2 3:3 4:5 5:8 6:13 7:21 8:34 9:55]

15
_test/issue-993.go Normal file
View File

@@ -0,0 +1,15 @@
package main
var m map[string]int64
func initVar() {
m = make(map[string]int64)
}
func main() {
initVar()
println(len(m))
}
// Output:
// 0

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())
}

41
_test/struct59.go Normal file
View File

@@ -0,0 +1,41 @@
package main
import (
"fmt"
)
type A struct {
B map[string]*B
C map[string]*C
}
type C struct {
D *D
E *E
}
type D struct {
F *F
G []G
}
type E struct {
H []H
F *F
}
type B struct{}
type F struct{}
type G struct{}
type H struct{}
func main() {
conf := &A{
B: make(map[string]*B),
C: make(map[string]*C),
}
fmt.Println(conf)
}
// Output:
// &{map[] map[]}

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

15
_test/var15.go Normal file
View File

@@ -0,0 +1,15 @@
package main
var a int = 2
func inca() {
a = a + 1
}
func main() {
inca()
println(a)
}
// Output:
// 3

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
}
}
@@ -209,7 +255,11 @@ func {{$name}}Const(n *node) {
v := constant.BinaryOp(vConstantValue(v0), operator, vConstantValue(v1))
n.rval.Set(reflect.ValueOf(v))
{{- else}}
{{- if $op.Int}}
v := constant.BinaryOp(constant.ToInt(vConstantValue(v0)), token.{{tokenFromName $name}}, constant.ToInt(vConstantValue(v1)))
{{- else}}
v := constant.BinaryOp(vConstantValue(v0), token.{{tokenFromName $name}}, vConstantValue(v1))
{{- end}}
n.rval.Set(reflect.ValueOf(v))
{{- end}}
{{- if $op.Str}}
@@ -425,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)
@@ -507,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 {
@@ -530,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 {
@@ -550,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 {
@@ -575,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)
@@ -645,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)
@@ -716,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)
@@ -788,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)
@@ -832,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 {
@@ -857,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)
@@ -940,6 +1056,7 @@ type Op struct {
Complex bool // true if operator applies to complex
Shift bool // true if operator is a shift operation
Bool bool // true if operator applies to bool
Int bool // true if operator applies to int only
}
func main() {
@@ -968,17 +1085,17 @@ func main() {
b := &bytes.Buffer{}
data := map[string]interface{}{
"Arithmetic": map[string]Op{
"add": {"+", true, true, true, false, false},
"sub": {"-", false, true, true, false, false},
"mul": {"*", false, true, true, false, false},
"quo": {"/", false, true, true, false, false},
"rem": {"%", false, false, false, false, false},
"shl": {"<<", false, false, false, true, false},
"shr": {">>", false, false, false, true, false},
"and": {"&", false, false, false, false, false},
"or": {"|", false, false, false, false, false},
"xor": {"^", false, false, false, false, false},
"andNot": {"&^", false, false, false, false, false},
"add": {"+", true, true, true, false, false, false},
"sub": {"-", false, true, true, false, false, false},
"mul": {"*", false, true, true, false, false, false},
"quo": {"/", false, true, true, false, false, false},
"rem": {"%", false, false, false, false, false, true},
"shl": {"<<", false, false, false, true, false, true},
"shr": {">>", false, false, false, true, false, true},
"and": {"&", false, false, false, false, false, true},
"or": {"|", false, false, false, false, false, true},
"xor": {"^", false, false, false, false, false, true},
"andNot": {"&^", false, false, false, false, false, true},
},
"IncDec": map[string]Op{
"inc": {Name: "+"},
@@ -996,7 +1113,7 @@ func main() {
"not": {Name: "!", Float: false, Bool: true},
"neg": {Name: "-", Float: true, Bool: false},
"pos": {Name: "+", Float: true, Bool: false},
"bitNot": {Name: "^", Float: false, Bool: false},
"bitNot": {Name: "^", Float: false, Bool: false, Int: true},
},
}
if err = parse.Execute(b, data); err != nil {

View File

@@ -288,8 +288,12 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
// Get type from ancestor (implicit type)
if n.anc.kind == keyValueExpr && n == n.anc.child[0] {
n.typ = n.anc.typ.key
} else if n.anc.typ != nil {
n.typ = n.anc.typ.val
} else if atyp := n.anc.typ; atyp != nil {
if atyp.cat == valueT {
n.typ = &itype{cat: valueT, rtype: atyp.rtype.Elem()}
} else {
n.typ = atyp.val
}
}
if n.typ == nil {
err = n.cfgErrorf("undefined type")
@@ -551,10 +555,23 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
n.findex = dest.findex
n.level = dest.level
// Propagate type
// TODO: Check that existing destination type matches source type
// Propagate type.
// TODO: Check that existing destination type matches source type.
// In the following, we attempt to optimize by skipping the assign
// operation and setting the source location directly to the destination
// location in the frame.
//
switch {
case n.action == aAssign && isCall(src) && dest.typ.cat != interfaceT && !isMapEntry(dest) && !isRecursiveField(dest):
case n.action != aAssign:
// 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 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):
// Call action may perform the assignment directly.
n.gen = nop
src.level = level
@@ -562,52 +579,45 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
if src.typ.untyped && !dest.typ.untyped {
src.typ = dest.typ
}
case n.action == aAssign && src.action == aRecv:
case src.action == aRecv:
// Assign by reading from a receiving channel.
n.gen = nop
src.findex = dest.findex // Set recv address to LHS
src.findex = dest.findex // Set recv address to LHS.
dest.typ = src.typ
case n.action == aAssign && src.action == aCompositeLit && !isMapEntry(dest):
case src.action == aCompositeLit:
if dest.typ.cat == valueT && dest.typ.rtype.Kind() == reflect.Interface {
// Skip optimisation for assigned binary interface or map entry
// which require and additional operation to set the value
// Skip optimisation for assigned interface.
break
}
if dest.action == aGetIndex {
// optimization does not work when assigning to a struct field. Maybe we're not
// setting the right frame index or something, and we would end up not writing at
// the right place. So disabling it for now.
// Skip optimization, as it does not work when assigning to a struct field.
break
}
// Skip the assign operation entirely, the source frame index is set
// to destination index, avoiding extra memory alloc and duplication.
n.gen = nop
src.findex = dest.findex
src.level = level
case n.action == aAssign && len(n.child) < 4 && !src.rval.IsValid() && isArithmeticAction(src):
case len(n.child) < 4 && isArithmeticAction(src):
// Optimize single assignments from some arithmetic operations.
// Skip the assign operation entirely, the source frame index is set
// to destination index, avoiding extra memory alloc and duplication.
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()
}
n.typ = dest.typ
if sym != nil {
sym.typ = n.typ
sym.recv = src.recv
}
n.level = level
if isMapEntry(dest) {
dest.gen = nop // skip getIndexMap
}
if n.anc.kind == constDecl {
n.gen = nop
n.findex = -1
n.findex = notInFrame
if sym, _, ok := sc.lookup(dest.ident); ok {
sym.kind = constSym
}
@@ -648,7 +658,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
if n.child[0].ident == "_" {
lc.gen = typeAssertStatus
} else {
lc.gen = typeAssert2
lc.gen = typeAssertLong
}
n.gen = nop
case unaryExpr:
@@ -704,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.
}
@@ -713,7 +723,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
// This operation involved constants, and the result is already computed
// by constOp and available in n.rval. Nothing else to do at execution.
n.gen = nop
n.findex = -1
n.findex = notInFrame
case n.anc.kind == assignStmt && n.anc.action == aAssign:
// To avoid a copy in frame, if the result is to be assigned, store it directly
// at the frame location of destination.
@@ -736,7 +746,13 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
wireChild(n)
t := n.child[0].typ
switch t.cat {
case aliasT, ptrT:
case aliasT:
if isString(t.val.TypeOf()) {
n.typ = sc.getType("byte")
break
}
fallthrough
case ptrT:
n.typ = t.val
if t.val.cat == valueT {
n.typ = &itype{cat: valueT, rtype: t.val.rtype.Elem()}
@@ -842,27 +858,47 @@ 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
}
switch {
case n.typ.cat == builtinT:
n.findex = -1
n.findex = notInFrame
n.val = nil
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):
@@ -896,7 +932,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
n.rval = c1.rval
case c1.rval.IsValid() && isConstType(c0.typ):
n.gen = nop
n.findex = -1
n.findex = notInFrame
n.typ = c0.typ
if c, ok := c1.rval.Interface().(constant.Value); ok {
i, _ := constant.Int64Val(constant.ToInt(c))
@@ -959,7 +995,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
}
}
} else {
n.findex = -1
n.findex = notInFrame
}
}
@@ -1040,7 +1076,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
case fileStmt:
wireChild(n, varDecl)
sc = sc.pop()
n.findex = -1
n.findex = notInFrame
case forStmt0: // for {}
body := n.child[0]
@@ -1498,6 +1534,9 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
// Resolve source package symbol
if sym, ok := interp.srcPkg[pkg][name]; ok {
n.findex = sym.index
if sym.global {
n.level = globalFrame
}
n.val = sym.node
n.gen = nop
n.action = aGetSym
@@ -1512,7 +1551,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
if n.child[0].isType(sc) {
// Handle method as a function with receiver in 1st argument
n.val = m
n.findex = -1
n.findex = notInFrame
n.gen = nop
n.typ = &itype{}
*n.typ = *m.typ
@@ -1825,7 +1864,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
switch {
case n.rval.IsValid():
n.gen = nop
n.findex = -1
n.findex = notInFrame
case n.anc.kind == assignStmt && n.anc.action == aAssign:
dest := n.anc.child[childPos(n)-n.anc.nright]
n.typ = dest.typ
@@ -1847,11 +1886,13 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
return
}
}
for _, c := range n.child[:l] {
var index int
if sc.global {
// Global object allocation is already performed in GTA.
index = sc.sym[c.ident].index
c.level = globalFrame
} else {
index = sc.add(n.typ)
sc.sym[c.ident] = &symbol{index: index, kind: varSym, typ: n.typ}
@@ -1878,8 +1919,15 @@ func compDefineX(sc *scope, n *node) error {
if err != nil {
return err
}
for funtype.cat == valueT && funtype.val != nil {
// Retrieve original interpreter type from a wrapped function.
// Struct fields of function types are always wrapped in valueT to ensure
// their possible use in runtime. In that case, the val field retains the
// original interpreter type, which is used now.
funtype = funtype.val
}
if funtype.cat == valueT {
// Handle functions imported from runtime
// Handle functions imported from runtime.
for i := 0; i < funtype.rtype.NumOut(); i++ {
types = append(types, &itype{cat: valueT, rtype: funtype.rtype.Out(i)})
}
@@ -1903,7 +1951,7 @@ func compDefineX(sc *scope, n *node) error {
if n.child[0].ident == "_" {
n.child[l].gen = typeAssertStatus
} else {
n.child[l].gen = typeAssert2
n.child[l].gen = typeAssertLong
}
types = append(types, n.child[l].child[1].typ, sc.getType("bool"))
n.gen = nop
@@ -2302,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 == "_" {
@@ -2461,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 {
@@ -2473,6 +2539,8 @@ func compositeGenerator(n *node, typ *itype, rtyp reflect.Type) (gen bltnGenerat
gen = compositeBinMap
case reflect.Ptr:
gen = compositeGenerator(n, typ, n.typ.val.rtype)
case reflect.Slice:
gen = compositeBinSlice
default:
log.Panic(n.cfgErrorf("compositeGenerator not implemented for type kind: %s", k))
}
@@ -2508,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
}
@@ -115,6 +116,7 @@ func (interp *Interpreter) gta(root *node, rpath, importPath string) ([]*node, e
sc.sym[c.ident] = &symbol{index: sc.add(n.typ), kind: varSym, global: true, typ: n.typ, node: n}
continue
}
c.level = globalFrame
// redeclaration error
if sym.typ.node != nil && sym.typ.node.anc != nil {
@@ -318,7 +320,50 @@ func (interp *Interpreter) gtaRetry(nodes []*node, importPath string) error {
}
if len(revisit) > 0 {
return revisit[0].cfgErrorf("constant definition loop")
n := revisit[0]
if n.kind == typeSpec {
if err := definedType(n.typ); err != nil {
return err
}
}
return n.cfgErrorf("constant definition loop")
}
return nil
}
func definedType(typ *itype) error {
if !typ.incomplete {
return nil
}
switch typ.cat {
case interfaceT, structT:
for _, f := range typ.field {
if err := definedType(f.typ); err != nil {
return err
}
}
case funcT:
for _, t := range typ.arg {
if err := definedType(t); err != nil {
return err
}
}
for _, t := range typ.ret {
if err := definedType(t); err != nil {
return err
}
}
case mapT:
if err := definedType(typ.key); err != nil {
return err
}
fallthrough
case aliasT, arrayT, chanT, chanSendT, chanRecvT, ptrT, variadicT:
if err := definedType(typ.val); err != nil {
return err
}
case nilT:
return typ.node.cfgErrorf("undefined: %s", typ.node.ident)
}
return nil
}

View File

@@ -65,7 +65,8 @@ type frame struct {
// Located at start of struct to ensure proper aligment.
id uint64
anc *frame // ancestor frame (global space)
root *frame // global space
anc *frame // ancestor frame (caller space)
data []reflect.Value // values
mutex sync.RWMutex
@@ -74,14 +75,17 @@ 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 {
if anc == nil {
f.root = f
} else {
f.done = anc.done
f.root = anc.root
}
return f
}
@@ -93,6 +97,7 @@ func (f *frame) clone() *frame {
defer f.mutex.RUnlock()
return &frame{
anc: f.anc,
root: f.root,
data: f.data,
deferred: f.deferred,
recovered: f.recovered,
@@ -239,7 +244,7 @@ type Options struct {
func New(options Options) *Interpreter {
i := Interpreter{
opt: opt{context: build.Default},
frame: &frame{data: []reflect.Value{}},
frame: newFrame(nil, 0, 0),
fset: token.NewFileSet(),
universe: initUniverse(),
scopes: map[string]*scope{},

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"},
})
}
@@ -457,6 +469,7 @@ func TestEvalSliceExpression(t *testing.T) {
{src: `a := []int{0,1,2,3}[1::4]`, err: "1:49: 2nd index required in 3-index slice"},
{src: `a := []int{0,1,2,3}[1:3:]`, err: "1:51: 3rd index required in 3-index slice"},
{src: `a := []int{0,1,2}[3:1]`, err: "invalid index values, must be low <= high <= max"},
{pre: func() { eval(t, i, `type Str = string; var r Str = "truc"`) }, src: `r[1]`, res: "114"},
})
}
@@ -494,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"},
@@ -740,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 != "" {
@@ -769,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 != "" {
@@ -1147,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

@@ -130,6 +130,9 @@ func (s *scope) lookup(ident string) (*symbol, int, bool) {
level := s.level
for {
if sym, ok := s.sym[ident]; ok {
if sym.global {
return sym, globalFrame, true
}
return sym, level - s.level, true
}
if s.anc == nil {

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

@@ -109,7 +109,7 @@ type itype struct {
cat tcat // Type category
field []structField // Array of struct fields if structT or interfaceT
key *itype // Type of key element if MapT or nil
val *itype // Type of value element if chanT,chanSendT, chanRecvT, mapT, ptrT, aliasT, arrayT or variadicT
val *itype // Type of value element if chanT, chanSendT, chanRecvT, mapT, ptrT, aliasT, arrayT or variadicT
recv *itype // Receiver type for funcT or nil
arg []*itype // Argument types if funcT or nil
ret []*itype // Return types if funcT or nil
@@ -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 {
@@ -449,6 +474,9 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
}
}
t = sym.typ
if t.incomplete && t.cat == aliasT && t.val != nil && t.val.cat != nilT {
t.incomplete = false
}
if t.incomplete && t.node != n {
m := t.method
if t, err = nodeType(interp, sc, t.node); err != nil {
@@ -879,13 +907,19 @@ func isComplete(t *itype, visited map[string]bool) bool {
}
name := t.path + "/" + t.name
if visited[name] {
return !t.incomplete
return true
}
if t.name != "" {
visited[name] = true
}
switch t.cat {
case aliasT, arrayT, chanT, chanRecvT, chanSendT, ptrT:
case aliasT:
if t.val != nil && t.val.cat != nilT {
// A type aliased to a partially defined type is considered complete, to allow recursivity.
return true
}
fallthrough
case arrayT, chanT, chanRecvT, chanSendT, ptrT:
return isComplete(t.val, visited)
case funcT:
complete := true
@@ -899,6 +933,8 @@ func isComplete(t *itype, visited map[string]bool) bool {
case interfaceT, structT:
complete := true
for _, f := range t.field {
// Field implicit type names must be marked as visited, to break false circles.
visited[f.typ.path+"/"+f.typ.name] = true
complete = complete && isComplete(f.typ, visited)
}
return complete
@@ -1403,7 +1439,7 @@ func (t *itype) refType(defined map[string]*itype, wrapRecursive bool) reflect.T
t.rtype = reflect.TypeOf(new(error)).Elem()
case funcT:
if t.name != "" {
defined[name] = t
defined[name] = t // TODO(marc): make sure that key is name and not t.name.
}
variadic := false
in := make([]reflect.Type, len(t.arg))
@@ -1424,10 +1460,11 @@ func (t *itype) refType(defined map[string]*itype, wrapRecursive bool) reflect.T
t.rtype = reflect.PtrTo(t.val.refType(defined, wrapRecursive))
case structT:
if t.name != "" {
if defined[name] != nil {
// Check against local t.name and not name to catch recursive type definitions.
if defined[t.name] != nil {
recursive = true
}
defined[name] = t
defined[t.name] = t
}
var fields []reflect.StructField
// TODO(mpl): make Anonymous work for recursive types too. Maybe not worth the
@@ -1475,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()
}
@@ -1490,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
@@ -1540,7 +1599,7 @@ func hasRecursiveStruct(t *itype, defined map[string]*itype) bool {
defined[typ.path+"/"+typ.name] = typ
for _, f := range typ.field {
if hasRecursiveStruct(f.typ, defined) {
if hasRecursiveStruct(f.typ, copyDefined(defined)) {
return true
}
}
@@ -1613,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

@@ -5,8 +5,15 @@ import (
"reflect"
)
const (
notInFrame = -1 // value of node.findex for literal values (not in frame)
globalFrame = -1 // value of node.level for global symbols
)
func valueGenerator(n *node, i int) func(*frame) reflect.Value {
switch n.level {
case globalFrame:
return func(f *frame) reflect.Value { return valueOf(f.root.data, i) }
case 0:
return func(f *frame) reflect.Value { return valueOf(f.data, i) }
case 1:
@@ -171,18 +178,16 @@ func genValue(n *node) func(*frame) reflect.Value {
return func(f *frame) reflect.Value { return v }
}
if n.sym != nil {
if n.sym.index < 0 {
i := n.sym.index
if i < 0 {
return genValue(n.sym.node)
}
i := n.sym.index
if n.sym.global {
return func(f *frame) reflect.Value {
return n.interp.frame.data[i]
}
return func(f *frame) reflect.Value { return f.root.data[i] }
}
return valueGenerator(n, i)
}
if n.findex < 0 {
if n.findex == notInFrame {
var v reflect.Value
if w, ok := n.val.(reflect.Value); ok {
v = w
@@ -216,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
@@ -260,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)
@@ -269,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})
}
}
@@ -279,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 {
@@ -297,6 +327,17 @@ func genValueOutput(n *node, t reflect.Type) func(*frame) reflect.Value {
return value
}
func valueInterfaceValue(v reflect.Value) reflect.Value {
for {
vv, ok := v.Interface().(valueInterface)
if !ok {
break
}
v = vv.value
}
return v
}
func genValueInterfaceValue(n *node) func(*frame) reflect.Value {
value := genValue(n)
@@ -307,7 +348,7 @@ func genValueInterfaceValue(n *node) func(*frame) reflect.Value {
v.Set(zeroInterfaceValue())
v = value(f)
}
return v.Interface().(valueInterface).value
return valueInterfaceValue(v)
}
}