Compare commits

..

10 Commits

Author SHA1 Message Date
Marc Vertes
0f46cd5efb fix: handle type declaration inside function 2019-09-26 00:50:04 +02:00
Marc Vertes
35e645c690 fix: correct handling of types alias of interfaces 2019-09-25 15:24:04 +02:00
Marc Vertes
03596dac45 fix: correct automatic type conversion for untyped constants 2019-09-25 15:02:04 +02:00
Dan Kortschak
8db7a815e3 interp: don't panic for undefined types 2019-09-25 12:24:05 +02:00
Dan Kortschak
424e7ac90d test: ensure hour-aligned timezone 2019-09-25 02:40:03 +02:00
Marc Vertes
effd64c980 fix: handle forward declaration of alias type 2019-09-24 16:10:04 +02:00
Marc Vertes
8a88a1ab8a fix: range over string iterates on runes instead of bytes 2019-09-23 17:02:04 +02:00
Marc Vertes
030dd3cbc2 fix: support variadic parameters on methods 2019-09-23 15:20:04 +02:00
Marc Vertes
bee21968c7 doc: README, supported go versions: 1.12 and 1.13 2019-09-19 15:00:05 +02:00
Marc Vertes
9abaeeb729 fix: binary method lookup works for struct field pointer receiver 2019-09-19 14:02:06 +02:00
15 changed files with 324 additions and 45 deletions

View File

@@ -18,7 +18,7 @@ It powers executable Go scripts and plugins, in embedded interpreters or interac
* Works everywhere Go works
* All Go & runtime resources accessible from script (with control)
* Security: `unsafe` and `syscall` packages neither used nor exported by default
* Support Go 1.11 and Go 1.12 (the latest 2 major releases)
* Support Go 1.12 and Go 1.13 (the latest 2 major releases)
## Install

16
_test/alias1.go Normal file
View File

@@ -0,0 +1,16 @@
package main
import "fmt"
type MyT T
type T struct {
Name string
}
func main() {
fmt.Println(MyT{})
}
// Output:
// {}

19
_test/for6.go Normal file
View File

@@ -0,0 +1,19 @@
package main
import "fmt"
func main() {
s := "三"
for i := 0; i < len(s); i++ {
fmt.Printf("byte %d: %d\n", i, s[i])
}
for i, r := range s {
fmt.Printf("rune %d: %d\n", i, r)
}
}
// Output:
// byte 0: 228
// byte 1: 184
// byte 2: 137
// rune 0: 19977

12
_test/fun9.go Normal file
View File

@@ -0,0 +1,12 @@
package main
type T uint
func main() {
type myint int
var i = myint(1)
println(i)
}
// Output:
// 1

27
_test/interface11.go Normal file
View File

@@ -0,0 +1,27 @@
package main
import "fmt"
type Error interface {
error
Code() string
}
type MyError Error
type T struct {
Name string
}
func (t *T) Error() string { return "err: " + t.Name }
func (t *T) Code() string { return "code: " + t.Name }
func newT(s string) MyError { return &T{s} }
func main() {
t := newT("foo")
fmt.Println(t.Code())
}
// Output:
// code: foo

20
_test/method27.go Normal file
View File

@@ -0,0 +1,20 @@
package main
import (
"fmt"
"net/http"
)
type AuthenticatedRequest struct {
http.Request
Username string
}
func main() {
a := &AuthenticatedRequest{}
fmt.Println("ua:", a.UserAgent())
}
// Output:
// ua:

10
_test/shift3.go Normal file
View File

@@ -0,0 +1,10 @@
package main
const a = 1.0
const b = a + 3
func main() { println(b << (1)) }
// Output:
// 8

View File

@@ -6,7 +6,7 @@ import (
)
func main() {
t := time.Unix(1e9, 0)
t := time.Unix(1e9, 0).In(time.UTC)
fmt.Println(t.Minute())
}

22
_test/variadic5.go Normal file
View File

@@ -0,0 +1,22 @@
package main
import (
"fmt"
)
type A struct {
}
func (a A) f(vals ...bool) {
for _, v := range vals {
fmt.Println(v)
}
}
func main() {
a := A{}
a.f(true)
}
// Output:
// true

View File

@@ -109,7 +109,7 @@ func TestPackagesError(t *testing.T) {
{
desc: "different packages in the same directory",
goPath: "./_pkg9/",
expected: "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 _pkg9/src/github.com/foo/pkg",
},
}

View File

@@ -3,6 +3,7 @@ package interp
import (
"fmt"
"log"
"math"
"path"
"reflect"
"unicode"
@@ -77,7 +78,7 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
vtyp = &itype{cat: valueT, rtype: typ.Elem()}
case reflect.String:
ktyp = sc.getType("int")
vtyp = sc.getType("byte")
vtyp = sc.getType("rune")
case reflect.Array, reflect.Slice:
ktyp = sc.getType("int")
vtyp = &itype{cat: valueT, rtype: typ.Elem()}
@@ -96,7 +97,7 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
}
case stringT:
ktyp = sc.getType("int")
vtyp = sc.getType("byte")
vtyp = sc.getType("rune")
case arrayT, variadicT:
ktyp = sc.getType("int")
vtyp = o.typ.val
@@ -203,7 +204,10 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
} else if n.anc.typ != nil {
n.typ = n.anc.typ.val
}
// FIXME n.typ can be nil.
if n.typ == nil {
err = n.cfgErrorf("undefined type")
return false
}
n.typ.untyped = true
}
// Propagate type to children, to handle implicit types
@@ -305,7 +309,25 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
return false
case typeSpec:
// processing already done in GTA pass
// processing already done in GTA pass for global types, only parses inlined types
if sc.def != nil {
typeName := n.child[0].ident
var typ *itype
if typ, err = nodeType(interp, sc, n.child[1]); err != nil {
return false
}
if typ.incomplete {
err = n.cfgErrorf("invalid type declaration")
return false
}
if n.child[1].kind == identExpr {
n.typ = &itype{cat: aliasT, val: typ, name: typeName}
} else {
n.typ = typ
n.typ.name = typeName
}
sc.sym[typeName] = &symbol{kind: typeSym, typ: n.typ}
}
return false
case arrayType, basicLit, chanType, funcType, mapType, structType:
@@ -400,7 +422,7 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
err = n.cfgErrorf("illegal operand types for '%v' operator", n.action)
}
case aShlAssign, aShrAssign:
if !(isInt(t0) && isUint(t1)) {
if !(dest.isInteger() && src.isNatural()) {
err = n.cfgErrorf("illegal operand types for '%v' operator", n.action)
}
default:
@@ -518,7 +540,7 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
err = n.cfgErrorf("illegal operand types for '%v' operator", n.action)
}
case aShl, aShr:
if !(isInt(t0) && isUint(t1)) {
if !(c0.isInteger() && c1.isNatural()) {
err = n.cfgErrorf("illegal operand types for '%v' operator", n.action)
}
n.typ = c0.typ
@@ -1104,19 +1126,23 @@ func (interp *Interpreter) cfg(root *node) ([]*node, error) {
n.typ = m.typ
n.recv = &receiver{node: n.child[0], index: lind}
}
} else if m, lind, ok := n.typ.lookupBinMethod(n.child[1].ident); ok {
n.gen = getIndexSeqMethod
} else if m, lind, isPtr, ok := n.typ.lookupBinMethod(n.child[1].ident); ok {
if isPtr {
n.gen = getIndexSeqPtrMethod
} else {
n.gen = getIndexSeqMethod
}
n.val = append([]int{m.Index}, lind...)
n.typ = &itype{cat: valueT, rtype: m.Type}
} else if ti := n.typ.lookupField(n.child[1].ident); len(ti) > 0 {
// Handle struct field
n.val = ti
switch n.typ.cat {
case interfaceT:
switch {
case isInterfaceSrc(n.typ):
n.typ = n.typ.fieldSeq(ti)
n.gen = getMethodByName
n.action = aMethod
case ptrT:
case n.typ.cat == ptrT:
n.typ = n.typ.fieldSeq(ti)
n.gen = getPtrIndexSeq
if n.typ.cat == funcT {
@@ -1499,7 +1525,57 @@ func wireChild(n *node) {
}
}
// last returns the last child of a node
// isInteger returns true if node type is integer, false otherwise
func (n *node) isInteger() bool {
if isInt(n.typ.TypeOf()) {
return true
}
if n.typ.untyped && n.rval.IsValid() {
t := n.rval.Type()
if isInt(t) {
return true
}
if isFloat(t) {
// untyped float constant with null decimal part is ok
f := n.rval.Float()
if f == math.Round(f) {
n.rval = reflect.ValueOf(int(f))
n.typ.rtype = n.rval.Type()
return true
}
}
}
return false
}
// isNatural returns true if node type is natural, false otherwise
func (n *node) isNatural() bool {
if isUint(n.typ.TypeOf()) {
return true
}
if n.typ.untyped && n.rval.IsValid() {
t := n.rval.Type()
if isUint(t) {
return true
}
if isInt(t) && n.rval.Int() >= 0 {
// positive untyped integer constant is ok
return true
}
if isFloat(t) {
// positive untyped float constant with null decimal part is ok
f := n.rval.Float()
if f == math.Round(f) && f >= 0 {
n.rval = reflect.ValueOf(uint(f))
n.typ.rtype = n.rval.Type()
return true
}
}
}
return false
}
// lastChild returns the last child of a node
func (n *node) lastChild() *node { return n.child[len(n.child)-1] }
func isKey(n *node) bool {

View File

@@ -156,6 +156,8 @@ func (interp *Interpreter) gta(root *node, rpath string) ([]*node, error) {
default: // import symbols in package namespace
sc.sym[name] = &symbol{kind: pkgSym, typ: &itype{cat: srcPkgT, path: ipath}}
}
} else {
err = n.cfgErrorf("import %q error: %v", ipath, err)
}
case typeSpec:
@@ -165,13 +167,14 @@ func (interp *Interpreter) gta(root *node, rpath string) ([]*node, error) {
return false
}
if n.child[1].kind == identExpr {
n.typ = &itype{cat: aliasT, val: typ, name: typeName, path: rpath}
n.typ = &itype{cat: aliasT, val: typ, name: typeName, path: rpath, field: typ.field, incomplete: typ.incomplete}
copy(n.typ.method, typ.method)
} else {
n.typ = typ
n.typ.name = typeName
n.typ.path = rpath
}
// Type may already be declared for a receiver in a method function
// Type may be already declared for a receiver in a method function
if sc.sym[typeName] == nil {
sc.sym[typeName] = &symbol{kind: typeSym}
} else {

View File

@@ -44,11 +44,13 @@ func TestEvalArithmetic(t *testing.T) {
{desc: "rem_FI", src: "8.0 % 4", err: "1:28: illegal operand types for '%' operator"},
{desc: "shl_II", src: "1 << 8", res: "256"},
{desc: "shl_IN", src: "1 << -1", err: "1:28: illegal operand types for '<<' operator"},
{desc: "shl_IF", src: "1 << 1.0", err: "1:28: illegal operand types for '<<' operator"},
{desc: "shl_IF", src: "1.0 << 1", err: "1:28: illegal operand types for '<<' operator"},
{desc: "shl_IF", src: "1 << 1.0", res: "2"},
{desc: "shl_IF1", src: "1 << 1.1", err: "1:28: illegal operand types for '<<' operator"},
{desc: "shl_IF", src: "1.0 << 1", res: "2"},
{desc: "shr_II", src: "1 >> 8", res: "0"},
{desc: "shr_IN", src: "1 >> -1", err: "1:28: illegal operand types for '>>' operator"},
{desc: "shr_IF", src: "1 >> 1.0", err: "1:28: illegal operand types for '>>' operator"},
{desc: "shr_IF", src: "1 >> 1.0", res: "0"},
{desc: "shr_IF1", src: "1 >> 1.1", err: "1:28: illegal operand types for '>>' operator"},
})
}
@@ -368,6 +370,32 @@ func TestEvalChan(t *testing.T) {
})
}
func TestEvalMissingSymbol(t *testing.T) {
defer func() {
r := recover()
if r != nil {
t.Errorf("unexpected panic: %v", r)
}
}()
type S2 struct{}
type S1 struct {
F S2
}
i := interp.New(interp.Options{})
i.Use(interp.Exports{"p": map[string]reflect.Value{
"S1": reflect.Zero(reflect.TypeOf(&S1{})),
}})
_, err := i.Eval(`import "p"`)
if err != nil {
t.Fatalf("failed to import package: %v", err)
}
_, err = i.Eval(`p.S1{F: p.S2{}}`)
if err == nil {
t.Error("unexpected nil error for expression with undefined type")
}
}
func runTests(t *testing.T, i *interp.Interpreter, tests []testCase) {
for _, test := range tests {
t.Run(test.desc, func(t *testing.T) {

View File

@@ -502,7 +502,7 @@ func genInterfaceWrapper(n *node, typ reflect.Type) func(*frame) reflect.Value {
methods[i], indexes[i] = n.typ.lookupMethod(names[i])
if methods[i] == nil && n.typ.cat != nilT {
// interpreted method not found, look for binary method, possibly embedded
_, indexes[i], _ = n.typ.lookupBinMethod(names[i])
_, indexes[i], _, _ = n.typ.lookupBinMethod(names[i])
}
}
wrap := n.interp.getWrapper(typ)
@@ -671,7 +671,11 @@ func call(n *node) {
// Init variadic argument vector
if variadic >= 0 {
vararg = nf.data[numRet+variadic]
if method {
vararg = nf.data[numRet+variadic+1]
} else {
vararg = nf.data[numRet+variadic]
}
}
// Copy input parameters from caller
@@ -1130,6 +1134,27 @@ func getIndexSeqField(n *node) {
}
}
func getIndexSeqPtrMethod(n *node) {
value := genValue(n.child[0])
index := n.val.([]int)
fi := index[1:]
mi := index[0]
i := n.findex
next := getExec(n.tnext)
if n.child[0].typ.TypeOf().Kind() == reflect.Ptr {
n.exec = func(f *frame) bltn {
f.data[i] = value(f).Elem().FieldByIndex(fi).Addr().Method(mi)
return next
}
} else {
n.exec = func(f *frame) bltn {
f.data[i] = value(f).FieldByIndex(fi).Addr().Method(mi)
return next
}
}
}
func getIndexSeqMethod(n *node) {
value := genValue(n.child[0])
index := n.val.([]int)
@@ -1253,6 +1278,12 @@ func _return(n *node) {
switch t := def.typ.ret[i]; t.cat {
case errorT:
values[i] = genInterfaceWrapper(c, t.TypeOf())
case aliasT:
if isInterfaceSrc(t) {
values[i] = genValueInterface(c)
} else {
values[i] = genValue(c)
}
case interfaceT:
values[i] = genValueInterface(c)
default:
@@ -1499,14 +1530,22 @@ func compositeSparse(n *node) {
func empty(n *node) {}
var rat = reflect.ValueOf((*[]rune)(nil)).Type().Elem() // runes array type
func _range(n *node) {
index0 := n.child[0].findex // array index location in frame
fnext := getExec(n.fnext)
tnext := getExec(n.tnext)
var value func(*frame) reflect.Value
if len(n.child) == 4 {
index1 := n.child[1].findex // array value location in frame
value := genValueArray(n.child[2]) // array
an := n.child[2]
index1 := n.child[1].findex // array value location in frame
if isString(an.typ.TypeOf()) {
value = genValueAs(an, rat) // range on string iterates over runes
} else {
value = genValueArray(an)
}
n.exec = func(f *frame) bltn {
a := value(f)
v0 := f.data[index0]
@@ -1519,7 +1558,12 @@ func _range(n *node) {
return tnext
}
} else {
value := genValueArray(n.child[1]) // array
an := n.child[1]
if isString(an.typ.TypeOf()) {
value = genValueAs(an, rat) // range on string iterates over runes
} else {
value = genValueArray(an)
}
n.exec = func(f *frame) bltn {
a := value(f)
v0 := f.data[index0]

View File

@@ -215,14 +215,8 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
t.name = "float64"
t.untyped = true
case int:
if isShiftOperand(n) && v >= 0 {
t.cat = uintT
t.name = "uint"
n.rval = reflect.ValueOf(uint(v))
} else {
t.cat = intT
t.name = "int"
}
t.cat = intT
t.name = "int"
t.untyped = true
case uint:
t.cat = uintT
@@ -478,7 +472,7 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
default:
if m, _ := lt.lookupMethod(name); m != nil {
t, err = nodeType(interp, sc, m.child[2])
} else if bm, _, ok := lt.lookupBinMethod(name); ok {
} else if bm, _, _, ok := lt.lookupBinMethod(name); ok {
t = &itype{cat: valueT, rtype: bm.Type}
} else if ti := lt.lookupField(name); len(ti) > 0 {
t = lt.fieldSeq(ti)
@@ -694,7 +688,7 @@ func (t *itype) lookupField(name string) []int {
for i, f := range t.field {
switch f.typ.cat {
case ptrT, structT:
case ptrT, structT, interfaceT, aliasT:
if index2 := f.typ.lookupField(name); len(index2) > 0 {
return append([]int{i}, index2...)
}
@@ -758,23 +752,28 @@ func (t *itype) lookupMethod(name string) (*node, []int) {
}
// lookupBinMethod returns a method and a path to access a field in a struct object (the receiver)
func (t *itype) lookupBinMethod(name string) (reflect.Method, []int, bool) {
func (t *itype) lookupBinMethod(name string) (reflect.Method, []int, bool, bool) {
var isPtr bool
if t.cat == ptrT {
return t.val.lookupBinMethod(name)
}
var index []int
m, ok := t.TypeOf().MethodByName(name)
if !ok {
m, ok = reflect.PtrTo(t.TypeOf()).MethodByName(name)
isPtr = ok
}
if !ok {
for i, f := range t.field {
if f.embed {
if m2, index2, ok2 := f.typ.lookupBinMethod(name); ok2 {
if m2, index2, isPtr2, ok2 := f.typ.lookupBinMethod(name); ok2 {
index = append([]int{i}, index2...)
return m2, index, ok2
return m2, index, isPtr2, ok2
}
}
}
}
return m, index, ok
return m, index, isPtr, ok
}
func exportName(s string) string {
@@ -801,6 +800,8 @@ func (t *itype) refType(defined map[string]bool) reflect.Type {
t.val.rtype = interf
}
switch t.cat {
case aliasT:
t.rtype = t.val.refType(defined)
case arrayT, variadicT:
if t.size > 0 {
t.rtype = reflect.ArrayOf(t.size, t.val.refType(defined))
@@ -862,6 +863,8 @@ func (t *itype) frameType() (r reflect.Type) {
panic(err)
}
switch t.cat {
case aliasT:
r = t.val.frameType()
case arrayT, variadicT:
if t.size > 0 {
r = reflect.ArrayOf(t.size, t.val.frameType())
@@ -904,14 +907,13 @@ func isShiftNode(n *node) bool {
return false
}
func isShiftOperand(n *node) bool {
if isShiftNode(n.anc) {
return n.anc.lastChild() == n
}
return false
func isInterfaceSrc(t *itype) bool {
return t.cat == interfaceT || (t.cat == aliasT && isInterfaceSrc(t.val))
}
func isInterface(t *itype) bool { return t.cat == interfaceT || t.TypeOf().Kind() == reflect.Interface }
func isInterface(t *itype) bool {
return isInterfaceSrc(t) || t.TypeOf().Kind() == reflect.Interface
}
func isStruct(t *itype) bool { return t.TypeOf().Kind() == reflect.Struct }