Repository: goplus/gop
Pull request #567 (2020-08-22 03:47:30)

support user-defined type convert
support user-defined type convert

```
type M int

func (m M) Foo() {
	println("foo", m)
}

m := M(0)
m.Foo()
println(m)
```

now:

```
[PANIC] github.com/goplus/gop/cl/expr.go:191: compileIdent failed: unknown - *cl.typeDecl
panic: compileIdent failed: unknown - *cl.typeDecl
```

fixed:

```
foo 0
0
```




Patch:
diff --git a/ast/gopiter/gop_iter.go b/ast/gopiter/gop_iter.go
new file mode 100644
index 00000000..cb998058
--- /dev/null
+++ b/ast/gopiter/gop_iter.go
@@ -0,0 +1,67 @@
+package gopiter
+
+import (
+	"log"
+	"reflect"
+)
+
+type sliceIter struct {
+	slice    reflect.Value
+	sliceLen int
+	cursor   int
+}
+
+func newSliceIter(val reflect.Value) *sliceIter {
+	return &sliceIter{
+		slice:    val,
+		sliceLen: val.Len(),
+		cursor:   0,
+	}
+}
+
+func (p *sliceIter) Next() bool {
+	p.cursor++
+	return p.cursor-1 < p.sliceLen
+}
+
+func (p *sliceIter) Key() reflect.Value {
+	return reflect.ValueOf(p.cursor - 1)
+}
+
+func (p *sliceIter) Value() reflect.Value {
+	return p.slice.Index(p.cursor - 1)
+}
+
+type Iterator interface {
+	Next() bool
+	Key() reflect.Value
+	Value() reflect.Value
+}
+
+func NewIter(container interface{}) (iter Iterator) {
+	data := reflect.ValueOf(container)
+	switch data.Kind() {
+	case reflect.Map:
+		iter = data.MapRange()
+	case reflect.Slice, reflect.Array, reflect.String:
+		iter = newSliceIter(reflect.Indirect(data))
+	case reflect.Struct:
+		if v, ok := container.(Iterator); ok {
+			iter = v
+		} else {
+			log.Panicln("iter must be Iterator")
+		}
+	}
+	return
+}
+
+func Key(i Iterator, key interface{}) {
+	reflect.ValueOf(key).Elem().Set(i.Key())
+}
+
+func Value(i Iterator, value interface{}) {
+	reflect.ValueOf(value).Elem().Set(i.Value())
+}
+func Next(i Iterator) bool {
+	return i.Next()
+}
diff --git a/cl/expr.go b/cl/expr.go
index 91e99542..5c9095a2 100644
--- a/cl/expr.go
+++ b/cl/expr.go
@@ -187,6 +187,9 @@ func compileIdent(ctx *blockCtx, name string) func() {
 			return func() { // TODO: maybe slowly, use Closure instead of GoClosure
 				ctx.out.GoClosure(fn.fi)
 			}
+		case *typeDecl:
+			ctx.infer.Push(&nonValue{v.Type})
+			return nil
 		default:
 			log.Panicln("compileIdent failed: unknown -", reflect.TypeOf(sym))
 		}
@@ -1075,21 +1078,23 @@ func compileSelectorExprLHS(ctx *blockCtx, v *ast.SelectorExpr, mode compileMode
 	case *goValue:
 		_, t := countPtr(vx.t)
 		name := v.Sel.Name
-		if sf, ok := t.FieldByName(name); ok {
-			checkType(sf.Type, in, ctx.out)
-			if ctx.fieldIndex == nil {
-				ctx.fieldStructType = vx.t
-			}
-			fieldIndex := append(ctx.fieldIndex, sf.Index...)
-			fieldStructType := ctx.fieldStructType
-			ctx.checkLoadAddr = true
-			if ctx.fieldExprX != nil {
-				ctx.fieldExprX()
-			} else {
-				exprX()
+		if t.Kind() == reflect.Struct {
+			if sf, ok := t.FieldByName(name); ok {
+				checkType(sf.Type, in, ctx.out)
+				if ctx.fieldIndex == nil {
+					ctx.fieldStructType = vx.t
+				}
+				fieldIndex := append(ctx.fieldIndex, sf.Index...)
+				fieldStructType := ctx.fieldStructType
+				ctx.checkLoadAddr = true
+				if ctx.fieldExprX != nil {
+					ctx.fieldExprX()
+				} else {
+					exprX()
+				}
+				ctx.checkLoadAddr = false
+				ctx.out.StoreField(fieldStructType, fieldIndex)
 			}
-			ctx.checkLoadAddr = false
-			ctx.out.StoreField(fieldStructType, fieldIndex)
 		}
 	default:
 		log.Panicln("compileSelectorExprLHS failed: unknown -", reflect.TypeOf(vx))
@@ -1150,24 +1155,26 @@ func compileSelectorExpr(ctx *blockCtx, v *ast.SelectorExpr, allowAutoCall bool)
 		n, t := countPtr(vx.t)
 		autoCall := false
 		name := v.Sel.Name
-		if sf, ok := t.FieldByName(name); ok {
-			ctx.infer.Ret(1, &goValue{t: sf.Type})
-			if ctx.fieldIndex == nil {
-				ctx.fieldExprX = exprX
-				ctx.fieldStructType = vx.t
-			}
-			ctx.fieldIndex = append(ctx.fieldIndex, sf.Index...)
-			fieldIndex := ctx.fieldIndex
-			fieldExprX := ctx.fieldExprX
-			fieldStructType := ctx.fieldStructType
-			return func() {
-				if fieldExprX != nil {
-					fieldExprX()
+		if t.Kind() == reflect.Struct {
+			if sf, ok := t.FieldByName(name); ok {
+				ctx.infer.Ret(1, &goValue{t: sf.Type})
+				if ctx.fieldIndex == nil {
+					ctx.fieldExprX = exprX
+					ctx.fieldStructType = vx.t
 				}
-				if ctx.takeAddr || ctx.checkLoadAddr {
-					ctx.out.AddrField(fieldStructType, fieldIndex)
-				} else {
-					ctx.out.LoadField(fieldStructType, fieldIndex)
+				ctx.fieldIndex = append(ctx.fieldIndex, sf.Index...)
+				fieldIndex := ctx.fieldIndex
+				fieldExprX := ctx.fieldExprX
+				fieldStructType := ctx.fieldStructType
+				return func() {
+					if fieldExprX != nil {
+						fieldExprX()
+					}
+					if ctx.takeAddr || ctx.checkLoadAddr {
+						ctx.out.AddrField(fieldStructType, fieldIndex)
+					} else {
+						ctx.out.LoadField(fieldStructType, fieldIndex)
+					}
 				}
 			}
 		}
diff --git a/cl/stmt.go b/cl/stmt.go
index 1f83a98f..5ddd64d3 100644
--- a/cl/stmt.go
+++ b/cl/stmt.go
@@ -98,16 +98,18 @@ func compileForPhraseStmt(parent *blockCtx, v *ast.ForPhraseStmt) {
 }
 
 func compileRangeStmt(parent *blockCtx, v *ast.RangeStmt) {
-	noExecCtx := isNoExecCtx(parent, v.Body)
-	f := ast.ForPhrase{
-		For:    v.For,
-		TokPos: v.TokPos,
-		X:      v.X,
-	}
+	kvDef := map[string]reflect.Type{}
+	newIterName := "_gop_NewIter"
+	nextName := "_gop_Next"
+	keyName := "_gop_Key"
+	valueName := "_gop_Value"
+	iter := &ast.Ident{Name: "_gop_iter", NamePos: v.For}
+
+	var keyIdent, valIdent *ast.Ident
 	switch v.Tok {
 	case token.DEFINE:
-		f.Key = toIdent(v.Key)
-		f.Value = toIdent(v.Value)
+		keyIdent = toIdent(v.Key)
+		valIdent = toIdent(v.Value)
 	case token.ASSIGN:
 		var lhs, rhs [2]ast.Expr
 		var idx int
@@ -118,8 +120,8 @@ func compileRangeStmt(parent *blockCtx, v *ast.RangeStmt) {
 			}
 			if assign {
 				k0 := ast.NewObj(ast.Var, "_gop_k")
-				f.Key = &ast.Ident{Name: k0.Name, Obj: k0}
-				lhs[idx], rhs[idx] = v.Key, f.Key
+				keyIdent = &ast.Ident{Name: k0.Name, Obj: k0}
+				lhs[idx], rhs[idx] = v.Key, keyIdent
 				idx++
 			}
 		}
@@ -130,8 +132,8 @@ func compileRangeStmt(parent *blockCtx, v *ast.RangeStmt) {
 			}
 			if assign {
 				v0 := ast.NewObj(ast.Var, "_gop_v")
-				f.Value = &ast.Ident{Name: v0.Name, Obj: v0}
-				lhs[idx], rhs[idx] = v.Value, f.Value
+				valIdent = &ast.Ident{Name: v0.Name, Obj: v0}
+				lhs[idx], rhs[idx] = v.Value, valIdent
 				idx++
 			}
 		}
@@ -141,10 +143,86 @@ func compileRangeStmt(parent *blockCtx, v *ast.RangeStmt) {
 			Rhs: rhs[0:idx],
 		}}, v.Body.List...)
 	}
-	ctx, exprFor := compileForPhrase(parent, f, noExecCtx)
-	exprFor(func() {
-		compileBlockStmtWithout(ctx, v.Body)
-	})
+
+	compileExpr(parent, v.X)
+	typData := boundType(parent.infer.Pop().(iValue))
+	var typKey, typVal reflect.Type
+	var forStmts []ast.Stmt
+	switch kind := typData.Kind(); kind {
+	case reflect.String:
+		typKey = exec.TyInt
+		typVal = exec.TyByte
+	case reflect.Slice, reflect.Array:
+		typKey = exec.TyInt
+		typVal = typData.Elem()
+	case reflect.Map:
+		typKey = typData.Key()
+		typVal = typData.Elem()
+	default:
+		log.Panicln("compileRangeStmt: require slice, array or map")
+	}
+	if keyIdent != nil {
+		// Key(iter,&k)
+		if id, ok := v.Key.(*ast.Ident); !ok || (ok && id.Name != "_") {
+			kvDef[keyIdent.Name] = typKey
+			forStmts = append(forStmts, &ast.ExprStmt{X: &ast.CallExpr{
+				Fun: &ast.Ident{Name: keyName, NamePos: v.For},
+				Args: []ast.Expr{iter, &ast.UnaryExpr{
+					Op: token.AND,
+					X:  keyIdent,
+				}},
+			}})
+		}
+	}
+	if valIdent != nil {
+		// Value(iter,&v)
+		if id, ok := v.Value.(*ast.Ident); !ok || (ok && id.Name != "_") {
+			kvDef[valIdent.Name] = typVal
+			forStmts = append(forStmts, &ast.ExprStmt{X: &ast.CallExpr{
+				Fun: &ast.Ident{Name: valueName, NamePos: v.For},
+				Args: []ast.Expr{iter, &ast.UnaryExpr{
+					Op: token.AND,
+					X:  valIdent,
+				}},
+			}})
+		}
+	}
+	// iter:=_gop_NewIter(obj)
+	init := &ast.AssignStmt{
+		Lhs: []ast.Expr{iter},
+		Tok: token.DEFINE,
+		Rhs: []ast.Expr{&ast.CallExpr{
+			Fun:    &ast.Ident{Name: newIterName},
+			Lparen: v.For,
+			Rparen: v.For,
+			Args:   []ast.Expr{v.X},
+		}},
+		TokPos: v.For,
+	}
+	// range => for
+	fs := &ast.ForStmt{
+		For: v.For,
+		Cond: &ast.CallExpr{
+			Fun:    &ast.Ident{Name: nextName},
+			Lparen: v.For,
+			Rparen: v.For,
+			Args:   []ast.Expr{iter},
+		},
+		Post: nil,
+		Body: &ast.BlockStmt{
+			Lbrace: v.Body.Lbrace,
+			List:   append(forStmts, v.Body.List...),
+			Rbrace: v.Body.Rbrace,
+		},
+	}
+	parent.out.DefineBlock()
+	defer parent.out.EndBlock()
+	ctx := newNormBlockCtx(parent)
+	for k, v := range kvDef {
+		ctx.insertVar(k, v)
+	}
+	compileStmt(ctx, init)
+	compileStmt(ctx, fs)
 }
 
 func toIdent(e ast.Expr) *ast.Ident {
diff --git a/exec/golang/builder.go b/exec/golang/builder.go
index 56c7fc96..ba6beacd 100644
--- a/exec/golang/builder.go
+++ b/exec/golang/builder.go
@@ -211,23 +211,14 @@ func (p *Builder) resolveTypes() *ast.GenDecl {
 	n := len(p.types)
 	specs := make([]ast.Spec, 0, n)
 	for _, t := range p.types {
-		fieldList := &ast.FieldList{}
 		typ := t.Type()
 		if typ.Kind() == reflect.Ptr {
 			typ = typ.Elem()
 		}
-		for i := 0; i < typ.NumField(); i++ {
-			field := typ.Field(i)
-			fieldList.List = append(fieldList.List, &ast.Field{
-				Names: []*ast.Ident{Ident(field.Name)},
-				Type:  Type(p, field.Type),
-			})
-		}
 
-		StructType := &ast.StructType{Fields: fieldList}
 		spec := &ast.TypeSpec{
 			Name: Ident(t.Name()),
-			Type: StructType,
+			Type: Type(p, typ, true),
 		}
 		specs = append(specs, spec)
 	}
diff --git a/exec/golang/expr.go b/exec/golang/expr.go
index 82d185c5..9c206c32 100644
--- a/exec/golang/expr.go
+++ b/exec/golang/expr.go
@@ -387,6 +387,11 @@ func (p *Builder) Call(narg int, ellipsis bool, args ...ast.Expr) *Builder {
 func (p *Builder) CallGoFunc(fun exec.GoFuncAddr, nexpr int) *Builder {
 	gfi := defaultImpl.GetGoFuncInfo(fun)
 	pkgPath, name := gfi.Pkg.PkgPath(), gfi.Name
+	if pkgPath == "" {
+		if alias, ok := builtin.FuncGoInfo(name); ok {
+			pkgPath, name = alias[0], alias[1]
+		}
+	}
 	fn := p.GoSymIdent(pkgPath, name)
 	p.rhs.Push(fn)
 	return p.Call(nexpr, false)
@@ -406,14 +411,6 @@ func (p *Builder) CallGoFuncv(fun exec.GoFuncvAddr, nexpr, arity int) *Builder {
 	return p.Call(nexpr, arity == -1)
 }
 
-var builtinFnvs = map[string][2]string{
-	"errorf":  {"fmt", "Errorf"},
-	"print":   {"fmt", "Print"},
-	"printf":  {"fmt", "Printf"},
-	"println": {"fmt", "Println"},
-	"fprintf": {"fmt", "Fprintf"},
-}
-
 // LoadGoVar instr
 func (p *Builder) LoadGoVar(addr exec.GoVarAddr) *Builder {
 	gvi := defaultImpl.GetGoVarInfo(addr)
diff --git a/exec/golang/type.go b/exec/golang/type.go
index 04fc006d..632537f4 100644
--- a/exec/golang/type.go
+++ b/exec/golang/type.go
@@ -28,9 +28,6 @@ import (
 
 // StructType instr
 func StructType(p *Builder, typ reflect.Type) ast.Expr {
-	if gtype, ok := p.types[typ]; ok {
-		return Ident(gtype.Name())
-	}
 	var fields = &ast.FieldList{}
 	for i := 0; i < typ.NumField(); i++ {
 		fields.List = append(fields.List, toStructField(p, typ.Field(i)))
@@ -137,7 +134,12 @@ func FuncType(p *Builder, typ reflect.Type) *ast.FuncType {
 }
 
 // Type instr
-func Type(p *Builder, typ reflect.Type) ast.Expr {
+func Type(p *Builder, typ reflect.Type, actualTypes ...bool) ast.Expr {
+	if len(actualTypes) == 0 || (len(actualTypes) > 0 && !actualTypes[0]) {
+		if gtype, ok := p.types[typ]; ok {
+			return Ident(gtype.Name())
+		}
+	}
 	pkgPath, name := typ.PkgPath(), typ.Name()
 	log.Debug(typ, "-", "pkgPath:", pkgPath, "name:", name)
 	if name != "" {
diff --git a/lib/builtin/exports.go b/lib/builtin/exports.go
index 10f51b36..b0696b9a 100644
--- a/lib/builtin/exports.go
+++ b/lib/builtin/exports.go
@@ -25,6 +25,7 @@ import (
 	"reflect"
 
 	"github.com/goplus/gop"
+	"github.com/goplus/gop/ast/gopiter"
 
 	qspec "github.com/goplus/gop/exec.spec"
 	exec "github.com/goplus/gop/exec/bytecode"
@@ -74,6 +75,32 @@ func QexecIs(_ int, p *gop.Context) {
 	p.Ret(2, is)
 }
 
+// QNewIter instr
+func QNewIter(_ int, p *gop.Context) {
+	args := p.GetArgs(1)
+	p.Ret(1, gopiter.NewIter(args[0]))
+}
+
+// QNext instr
+func QNext(_ int, p *gop.Context) {
+	args := p.GetArgs(1)
+	p.Ret(1, gopiter.Next(args[0].(gopiter.Iterator)))
+}
+
+// QKey instr
+func QKey(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	gopiter.Key(args[0].(gopiter.Iterator), args[1])
+	p.PopN(2)
+}
+
+// QValue instr
+func QValue(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	gopiter.Value(args[0].(gopiter.Iterator), args[1])
+	p.PopN(2)
+}
+
 // FuncGoInfo returns Go package and function name of a Go+ builtin function.
 func FuncGoInfo(f string) ([2]string, bool) {
 	fi, ok := builtinFnvs[f]
@@ -92,17 +119,25 @@ func execPanic(_ int, p *gop.Context) {
 var I = gop.NewGoPackage("")
 
 var builtinFnvs = map[string][2]string{
-	"errorf":  {"fmt", "Errorf"},
-	"print":   {"fmt", "Print"},
-	"printf":  {"fmt", "Printf"},
-	"println": {"fmt", "Println"},
-	"fprintf": {"fmt", "Fprintf"},
+	"errorf":       {"fmt", "Errorf"},
+	"print":        {"fmt", "Print"},
+	"printf":       {"fmt", "Printf"},
+	"println":      {"fmt", "Println"},
+	"fprintf":      {"fmt", "Fprintf"},
+	"_gop_NewIter": {"github.com/goplus/gop/ast/gopiter", "NewIter"},
+	"_gop_Next":    {"github.com/goplus/gop/ast/gopiter", "Next"},
+	"_gop_Key":     {"github.com/goplus/gop/ast/gopiter", "Key"},
+	"_gop_Value":   {"github.com/goplus/gop/ast/gopiter", "Value"},
 }
 
 func init() {
 	I.RegisterFuncs(
 		I.Func("panic", qlPanic, execPanic),
 		I.Func("is", errors.Is, QexecIs),
+		I.Func("_gop_NewIter", gopiter.NewIter, QNewIter),
+		I.Func("_gop_Next", gopiter.Next, QNext),
+		I.Func("_gop_Key", gopiter.Key, QKey),
+		I.Func("_gop_Value", gopiter.Value, QValue),
 	)
 	I.RegisterFuncvs(
 		I.Funcv("errorf", fmt.Errorf, QexecErrorf),
diff --git a/tutorial/26-Method/method.gop b/tutorial/26-Method/method.gop
index cb0128e2..7e38e952 100644
--- a/tutorial/26-Method/method.gop
+++ b/tutorial/26-Method/method.gop
@@ -19,6 +19,12 @@ func (p *Person) AddFriends(args ...string) {
 	p.Friends = append(p.Friends, args...)
 }
 
+type M int
+
+func (m M) Foo() {
+	println("foo", m)
+}
+
 p := Person{
 	Name: "bar",
 	Age:  30,
@@ -30,6 +36,11 @@ p.SetName("foo")
 p.SetAge(32)
 p.AddFriends("a", "b", "c")
 
+a := int32(0)
+m := M(a)
+m.Foo()
+
 println(p.Name)
 println(p.Age)
 println(p.Friends)
+println(m)


Test patch:
diff --git a/ast/gopiter/gop_iter_test.go b/ast/gopiter/gop_iter_test.go
new file mode 100644
index 00000000..1df3ec02
--- /dev/null
+++ b/ast/gopiter/gop_iter_test.go
@@ -0,0 +1,27 @@
+package gopiter
+
+import (
+	"fmt"
+	"testing"
+)
+
+func Test_Slice(t *testing.T) {
+	c := []int{1, 2, 3, 4, 5}
+	iter := NewIter(c)
+	cnt := 0
+	keySum := 0
+	valSum := 0
+	k := 0
+	v := 0
+	for iter.Next() {
+		Key(iter, &k)
+		Value(iter, &v)
+		keySum += k
+		valSum += v
+		cnt++
+	}
+	fmt.Println(cnt, keySum, valSum)
+	if cnt != 5 || keySum != 10 || valSum != 15 {
+		t.Fail()
+	}
+}
diff --git a/cl/expr_test.go b/cl/expr_test.go
index a8f19caf..9f55b4b3 100644
--- a/cl/expr_test.go
+++ b/cl/expr_test.go
@@ -1024,6 +1024,18 @@ var testMethodClauses = map[string]testData{
 
 					p.SetName("foo",31)
 					`, "foo\n31\n", false},
+	"method int type": {`
+					
+					type M int
+
+					func (m M) Foo() {
+						println("foo", m)
+					}
+
+					m := M(0)
+					m.Foo()
+					println(m)
+					`, "foo 0\n0\n", false},
 }
 
 func TestMethodCases(t *testing.T) {
diff --git a/exec/golang/builder_test.go b/exec/golang/builder_test.go
index 0647d235..07276d0c 100644
--- a/exec/golang/builder_test.go
+++ b/exec/golang/builder_test.go
@@ -427,34 +427,35 @@ func main() {
 	}
 }
 
-type Person struct {
-	Name string
-	Age  int
-}
-
 func TestType(t *testing.T) {
 	println, _ := I.FindFuncv("println")
 
 	codeExp := `package main
 
-import (
-	fmt "fmt"
-	golang "github.com/goplus/gop/exec/golang"
-)
+import fmt "fmt"
 
 type Person struct {
 	Name string
 	Age  int
 }
 
-var p golang.Person
+var p Person
 
 func main() {
-	p = golang.Person{Name: "bar", Age: 30}
+	p = Person{Name: "bar", Age: 30}
 	fmt.Println(p)
 }
 `
-	typ := reflect.TypeOf(Person{})
+	typ := reflect.StructOf([]reflect.StructField{
+		{
+			Name: "Name",
+			Type: exec.TyString,
+		},
+		{
+			Name: "Age",
+			Type: exec.TyInt,
+		},
+	})
 
 	p := NewVar(typ, "p")
 
diff --git a/exec/golang/testdata/28.method/method.gop b/exec/golang/testdata/28.method/method.gop
index 84497952..ba1c6455 100644
--- a/exec/golang/testdata/28.method/method.gop
+++ b/exec/golang/testdata/28.method/method.gop
@@ -19,6 +19,12 @@ func (p *Person) AddFriends(args ...string) {
 	p.Friends = append(p.Friends, args...)
 }
 
+type M int
+
+func (m M) Foo() {
+	println("foo", m)
+}
+
 p := &Person{
 	Name: "bar",
 	Age:  30,
@@ -26,10 +32,15 @@ p := &Person{
 
 p.Name, p.Age = "bar2", 31
 
+a := int32(0)
+m := M(a)
+
 p.SetName("foo")
 p.SetAge(32)
 p.AddFriends("a", "b", "c")
+m.Foo()
 
 println(p.Name)
 println(p.Age)
 println(p.Friends)
+println(m)