Skip to content

Commit

Permalink
interp: do not wrap empty interface
Browse files Browse the repository at this point in the history
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
  • Loading branch information
mpl authored Feb 4, 2021
1 parent 3f4e166 commit 2e17cfa
Show file tree
Hide file tree
Showing 20 changed files with 800 additions and 156 deletions.
27 changes: 25 additions & 2 deletions _test/add1.go
Original file line number Diff line number Diff line change
@@ -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
38 changes: 36 additions & 2 deletions _test/addr2.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"encoding/xml"
"errors"
"fmt"
)

Expand All @@ -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)
}

Expand All @@ -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 changes: 24 additions & 0 deletions _test/addr3.go
Original file line number Diff line number Diff line change
@@ -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 changes: 114 additions & 0 deletions _test/addr4.go
Original file line number Diff line number Diff line change
@@ -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 changes: 62 additions & 0 deletions _test/addr5.go
Original file line number Diff line number Diff line change
@@ -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"]
Loading

0 comments on commit 2e17cfa

Please sign in to comment.