Repository: goplus/gop
Pull request #548 (2020-08-13 15:24:38)

Method support

codecov-commenter: # [Codecov](https://codecov.io/gh/goplus/gop/pull/548?src=pr&el=h1) Report
> Merging [#548](https://codecov.io/gh/goplus/gop/pull/548?src=pr&el=desc) into [master](https://codecov.io/gh/goplus/gop/commit/4ee8417fa1cb23a057aca51b104c90f469b91365&el=desc) will **decrease** coverage by `0.84%`.
> The diff coverage is `44.57%`.

[![Impacted file tree graph](https://codecov.io/gh/goplus/gop/pull/548/graphs/tree.svg?width=650&height=150&src=pr&token=4qln9Wk3Pn)](https://codecov.io/gh/goplus/gop/pull/548?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #548      +/-   ##
==========================================
- Coverage   89.88%   89.04%   -0.85%     
==========================================
  Files          40       41       +1     
  Lines       10190    10394     +204     
==========================================
+ Hits         9159     9255      +96     
- Misses        752      838      +86     
- Partials      279      301      +22     
```


| [Impacted Files](https://codecov.io/gh/goplus/gop/pull/548?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [cl/check\_type.go](https://codecov.io/gh/goplus/gop/pull/548/diff?src=pr&el=tree#diff-Y2wvY2hlY2tfdHlwZS5nbw==) | `66.98% <0.00%> (ø)` | |
| [exec/bytecode/code.go](https://codecov.io/gh/goplus/gop/pull/548/diff?src=pr&el=tree#diff-ZXhlYy9ieXRlY29kZS9jb2RlLmdv) | `100.00% <ø> (ø)` | |
| [exec/bytecode/context.go](https://codecov.io/gh/goplus/gop/pull/548/diff?src=pr&el=tree#diff-ZXhlYy9ieXRlY29kZS9jb250ZXh0Lmdv) | `94.54% <ø> (ø)` | |
| [exec/bytecode/interface.go](https://codecov.io/gh/goplus/gop/pull/548/diff?src=pr&el=tree#diff-ZXhlYy9ieXRlY29kZS9pbnRlcmZhY2UuZ28=) | `95.74% <0.00%> (-3.38%)` | :arrow_down: |
| [exec/bytecode/struct.go](https://codecov.io/gh/goplus/gop/pull/548/diff?src=pr&el=tree#diff-ZXhlYy9ieXRlY29kZS9zdHJ1Y3QuZ28=) | `75.00% <0.00%> (-7.42%)` | :arrow_down: |
| [exec/bytecode/type.go](https://codecov.io/gh/goplus/gop/pull/548/diff?src=pr&el=tree#diff-ZXhlYy9ieXRlY29kZS90eXBlLmdv) | `0.00% <0.00%> (ø)` | |
| [exec/golang/type.go](https://codecov.io/gh/goplus/gop/pull/548/diff?src=pr&el=tree#diff-ZXhlYy9nb2xhbmcvdHlwZS5nbw==) | `81.73% <0.00%> (-11.68%)` | :arrow_down: |
| [exec/golang/var.go](https://codecov.io/gh/goplus/gop/pull/548/diff?src=pr&el=tree#diff-ZXhlYy9nb2xhbmcvdmFyLmdv) | `80.90% <0.00%> (-1.50%)` | :arrow_down: |
| [exec/golang/func.go](https://codecov.io/gh/goplus/gop/pull/548/diff?src=pr&el=tree#diff-ZXhlYy9nb2xhbmcvZnVuYy5nbw==) | `81.87% <19.23%> (-13.36%)` | :arrow_down: |
| [exec/golang/interface.go](https://codecov.io/gh/goplus/gop/pull/548/diff?src=pr&el=tree#diff-ZXhlYy9nb2xhbmcvaW50ZXJmYWNlLmdv) | `90.55% <20.00%> (-3.22%)` | :arrow_down: |
| ... and [12 more](https://codecov.io/gh/goplus/gop/pull/548/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/goplus/gop/pull/548?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/goplus/gop/pull/548?src=pr&el=footer). Last update [4ee8417...f1b627a](https://codecov.io/gh/goplus/gop/pull/548?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/cl/compile.go b/cl/compile.go
index bcfb19a3..f8694ec5 100644
--- a/cl/compile.go
+++ b/cl/compile.go
@@ -289,6 +289,18 @@ func loadTypes(ctx *blockCtx, d *ast.GenDecl) {
 }
 
 func loadType(ctx *blockCtx, spec *ast.TypeSpec) {
+	if ctx.exists(spec.Name.Name) {
+		log.Panicln("loadType failed: symbol exists -", spec.Name.Name)
+	}
+	t := toType(ctx, spec.Type).(reflect.Type)
+
+	ctx.out.DefineType(t, spec.Name.Name)
+
+	tDecl := &typeDecl{
+		Type: t,
+	}
+	ctx.syms[spec.Name.Name] = tDecl
+	ctx.types[t] = tDecl
 }
 
 func loadConsts(ctx *blockCtx, d *ast.GenDecl) {
@@ -307,20 +319,17 @@ func loadFunc(ctx *blockCtx, d *ast.FuncDecl, isUnnamed bool) {
 	var name = d.Name.Name
 	if d.Recv != nil {
 		recv := astutil.ToRecv(d.Recv)
-		ctx.insertMethod(recv.Type, name, &methodDecl{
-			recv:    recv.Name,
-			pointer: recv.Pointer,
-			typ:     d.Type,
-			body:    d.Body,
-			file:    ctx.file,
-		})
+		funCtx := newExecBlockCtx(ctx)
+		funCtx.noExecCtx = isUnnamed
+		funCtx.funcCtx = newFuncCtx(nil)
+		ctx.insertMethod(recv, name, d, funCtx)
 	} else if name == "init" {
 		log.Panicln("loadFunc TODO: init")
 	} else {
 		funCtx := newExecBlockCtx(ctx)
 		funCtx.noExecCtx = isUnnamed
 		funCtx.funcCtx = newFuncCtx(nil)
-		ctx.insertFunc(name, newFuncDecl(name, d.Type, d.Body, funCtx))
+		ctx.insertFunc(name, newFuncDecl(name, nil, d.Type, d.Body, funCtx))
 	}
 }
 
diff --git a/cl/context.go b/cl/context.go
index d3919c4f..a24cdf88 100644
--- a/cl/context.go
+++ b/cl/context.go
@@ -22,6 +22,7 @@ import (
 	"reflect"
 
 	"github.com/goplus/gop/ast"
+	"github.com/goplus/gop/ast/astutil"
 	"github.com/goplus/gop/exec.spec"
 	"github.com/goplus/gop/token"
 	"github.com/qiniu/x/log"
@@ -35,6 +36,7 @@ type pkgCtx struct {
 	builtin exec.GoPackage
 	out     exec.Builder
 	usedfns []*funcDecl
+	types   map[reflect.Type]*typeDecl
 	pkg     *ast.Package
 	fset    *token.FileSet
 }
@@ -43,6 +45,7 @@ func newPkgCtx(out exec.Builder, pkg *ast.Package, fset *token.FileSet) *pkgCtx
 	pkgOut := out.GetPackage()
 	builtin := pkgOut.FindGoPackage("")
 	p := &pkgCtx{Package: pkgOut, builtin: builtin, out: out, pkg: pkg, fset: fset}
+	p.types = make(map[reflect.Type]*typeDecl)
 	p.infer.Init()
 	return p
 }
@@ -398,27 +401,48 @@ func (p *blockCtx) insertFunc(name string, fun *funcDecl) {
 	p.syms[name] = fun
 }
 
-func (p *blockCtx) insertMethod(typeName, methodName string, method *methodDecl) {
+func (p *blockCtx) insertMethod(recv astutil.RecvInfo, methodName string, decl *ast.FuncDecl, ctx *blockCtx) {
 	if p.parent != nil {
 		log.Panicln("insertMethod failed: unexpected - non global method declaration?")
 	}
-	typ, err := p.findType(typeName)
+	typ, err := p.findType(recv.Type)
 	if err == ErrNotFound {
 		typ = new(typeDecl)
-		p.syms[typeName] = typ
+		p.syms[recv.Type] = typ
 	} else if err != nil {
 		log.Panicln("insertMethod failed:", err)
 	} else if typ.Alias {
 		log.Panicln("insertMethod failed: alias?")
 	}
+
+	var t reflect.Type = typ.Type
+	pointer := recv.Pointer
+	for pointer > 0 {
+		t = reflect.PtrTo(t)
+		pointer--
+	}
+
+	method := newFuncDecl(methodName, decl.Recv, decl.Type, decl.Body, ctx)
+
 	if typ.Methods == nil {
-		typ.Methods = map[string]*methodDecl{methodName: method}
+		typ.Methods = map[string]*funcDecl{methodName: method}
 	} else {
 		if _, ok := typ.Methods[methodName]; ok {
-			log.Panicln("insertMethod failed: method exists -", typeName, methodName)
+			log.Panicln("insertMethod failed: method exists -", recv.Type, methodName)
 		}
 		typ.Methods[methodName] = method
 	}
 }
 
 // -----------------------------------------------------------------------------
+
+func (c *blockCtx) findMethod(typ reflect.Type, methodName string) (*funcDecl, bool) {
+	if typ.Kind() == reflect.Ptr {
+		typ = typ.Elem()
+	}
+	if tDecl, ok := c.types[typ]; ok {
+		fDecl, ok := tDecl.Methods[methodName]
+		return fDecl, ok
+	}
+	return nil, false
+}
diff --git a/cl/expr.go b/cl/expr.go
index 10f6a110..41fcfbc3 100644
--- a/cl/expr.go
+++ b/cl/expr.go
@@ -163,12 +163,15 @@ func compileIdent(ctx *blockCtx, name string) func() {
 			return func() {
 				if ctx.checkLoadAddr && kind != reflect.Slice && kind != reflect.Map {
 					ctx.out.AddrVar(v.v)
+				} else if ctx.takeAddr {
+					ctx.out.AddrVar(v.v)
 				} else {
 					ctx.out.LoadVar(v.v)
 				}
 			}
 		case *stackVar:
 			ctx.infer.Push(&goValue{t: v.typ})
+			ctx.resetFieldIndex()
 			return func() {
 				ctx.out.Load(v.index)
 			}
@@ -502,7 +505,7 @@ func compileMapLit(ctx *blockCtx, v *ast.CompositeLit) func() {
 
 func compileFuncLit(ctx *blockCtx, v *ast.FuncLit) func() {
 	funCtx := newExecBlockCtx(ctx)
-	decl := newFuncDecl("", v.Type, v.Body, funCtx)
+	decl := newFuncDecl("", nil, v.Type, v.Body, funCtx)
 	ctx.use(decl)
 	ctx.infer.Push(newQlFunc(decl))
 	return func() { // TODO: maybe slowly, use Closure instead of GoClosure
@@ -692,10 +695,33 @@ func compileCallExprCall(ctx *blockCtx, exprFun func(), v *ast.CallExpr, ct call
 			ctx.infer.Push(ret)
 		}
 		return func() {
+			var isMethod int
+			if vfn.recv != nil {
+				isMethod = 1
+				exprX := compileExpr(ctx, v.Fun.(*ast.SelectorExpr).X)
+				recv := ctx.infer.Get(-1).(*goValue)
+
+				if astutil.ToRecv(vfn.recv).Pointer == 0 {
+					exprX()
+					if recv.Kind() == reflect.Ptr {
+						recv.t = recv.t.Elem()
+						ctx.infer.Ret(1, recv)
+						ctx.out.AddrOp(recv.t.Kind(), exec.OpAddrVal) // Ptr => Elem()
+					}
+				} else {
+					ctx.checkLoadAddr = true
+					exprX()
+					ctx.checkLoadAddr = false
+					if recv.Kind() != reflect.Ptr {
+						recv.t = reflect.PtrTo(recv.t)
+						ctx.infer.Ret(1, recv)
+					}
+				}
+			}
 			for _, arg := range v.Args {
 				compileExpr(ctx, arg)()
 			}
-			arity := checkFuncCall(vfn.Proto(), 0, v, ctx)
+			arity := checkFuncCall(vfn.Proto(), isMethod, v, ctx)
 			fun := vfn.FuncInfo()
 			if fun.IsVariadic() {
 				builder(ctx, ct).CallFuncv(fun, len(v.Args), arity)
@@ -1133,6 +1159,14 @@ func compileSelectorExpr(ctx *blockCtx, v *ast.SelectorExpr, allowAutoCall bool)
 				}
 			}
 		}
+
+		if fDecl, ok := ctx.findMethod(t, name); ok {
+			ctx.infer.Pop()
+			fn := newQlFunc(fDecl)
+			ctx.use(fDecl)
+			ctx.infer.Push(fn)
+			return func() {}
+		}
 		if _, ok := vx.t.MethodByName(name); !ok && isLower(name) {
 			name = strings.Title(name)
 			if _, ok = vx.t.MethodByName(name); ok {
diff --git a/cl/type_decl.go b/cl/type_decl.go
index 61e02b23..b6b2f129 100644
--- a/cl/type_decl.go
+++ b/cl/type_decl.go
@@ -105,8 +105,8 @@ func toFuncType(ctx *blockCtx, t *ast.FuncType) iType {
 	return reflect.FuncOf(in, out, variadic)
 }
 
-func buildFuncType(fi exec.FuncInfo, ctx *blockCtx, t *ast.FuncType) {
-	in, args, variadic := toArgTypes(ctx, t.Params)
+func buildFuncType(recv *ast.FieldList, fi exec.FuncInfo, ctx *blockCtx, t *ast.FuncType) {
+	in, args, variadic := toArgTypes(ctx, recv, t.Params)
 	rets := toReturnTypes(ctx, t.Results)
 	if variadic {
 		fi.Vargs(in...)
@@ -184,9 +184,17 @@ func toReturnTypes(ctx *blockCtx, fields *ast.FieldList) (vars []exec.Var) {
 	return
 }
 
-func toArgTypes(ctx *blockCtx, fields *ast.FieldList) ([]reflect.Type, []string, bool) {
+func toArgTypes(ctx *blockCtx, recv, fields *ast.FieldList) ([]reflect.Type, []string, bool) {
 	var types []reflect.Type
 	var names []string
+	if recv != nil {
+		for _, fld := range recv.List[0].Names {
+			names = append(names, fld.Name)
+			break
+		}
+		typ, _ := toTypeEx(ctx, recv.List[0].Type)
+		types = append(types, typ.(reflect.Type))
+	}
 	last := len(fields.List) - 1
 	for i := 0; i <= last; i++ {
 		field := fields.List[i]
@@ -237,8 +245,12 @@ func toIdentType(ctx *blockCtx, ident string) iType {
 	if typ, ok := ctx.builtin.FindType(ident); ok {
 		return typ
 	}
-	log.Panicln("toIdentType failed: unknown ident -", ident)
-	return nil
+	typ, err := ctx.findType(ident)
+	if err != nil {
+		log.Panicln("toIdentType failed: findType error", err)
+		return nil
+	}
+	return typ.Type
 }
 
 func toArrayType(ctx *blockCtx, v *ast.ArrayType) iType {
@@ -317,7 +329,9 @@ func buildField(ctx *blockCtx, field *ast.Field, anonymous bool, fieldName strin
 // -----------------------------------------------------------------------------
 
 type typeDecl struct {
-	Methods map[string]*methodDecl
+	Methods map[string]*funcDecl
+	Type    reflect.Type
+	Name    string
 	Alias   bool
 }
 
@@ -334,6 +348,7 @@ type FuncDecl struct {
 	typ      *ast.FuncType
 	body     *ast.BlockStmt
 	ctx      *blockCtx
+	recv     *ast.FieldList
 	fi       exec.FuncInfo
 	used     bool
 	cached   bool
@@ -343,16 +358,20 @@ type FuncDecl struct {
 
 type funcDecl = FuncDecl
 
-func newFuncDecl(name string, typ *ast.FuncType, body *ast.BlockStmt, ctx *blockCtx) *FuncDecl {
+func newFuncDecl(name string, recv *ast.FieldList, typ *ast.FuncType, body *ast.BlockStmt, ctx *blockCtx) *FuncDecl {
 	nestDepth := ctx.getNestDepth()
-	fi := ctx.NewFunc(name, nestDepth)
-	return &FuncDecl{typ: typ, body: body, ctx: ctx, fi: fi}
+	var isMethod int
+	if recv != nil {
+		isMethod = 1
+	}
+	fi := ctx.NewFunc(name, nestDepth, isMethod)
+	return &FuncDecl{typ: typ, recv: recv, body: body, ctx: ctx, fi: fi}
 }
 
 // Get returns function information.
 func (p *FuncDecl) Get() exec.FuncInfo {
 	if !p.cached {
-		buildFuncType(p.fi, p.ctx, p.typ)
+		buildFuncType(p.recv, p.fi, p.ctx, p.typ)
 		p.cached = true
 	}
 	return p.fi
diff --git a/exec.spec/interface.go b/exec.spec/interface.go
index 45e2fc6b..373e03c8 100644
--- a/exec.spec/interface.go
+++ b/exec.spec/interface.go
@@ -269,6 +269,9 @@ type Builder interface {
 	// DefineFunc instr
 	DefineFunc(fun FuncInfo) Builder
 
+	// DefineType name string,reflect.Typeinstr
+	DefineType(typ reflect.Type, name string) Builder
+
 	// Return instr
 	Return(n int32) Builder
 
@@ -402,7 +405,7 @@ type Package interface {
 	NewComprehension(out reflect.Type) Comprehension
 
 	// NewFunc creates a Go+ function.
-	NewFunc(name string, nestDepth uint32) FuncInfo
+	NewFunc(name string, nestDepth uint32, funcType ...int) FuncInfo
 
 	// FindGoPackage lookups a Go package by pkgPath. It returns nil if not found.
 	FindGoPackage(pkgPath string) GoPackage
diff --git a/exec/bytecode/gopkg.go b/exec/bytecode/gopkg.go
index 06735ac6..81086e59 100644
--- a/exec/bytecode/gopkg.go
+++ b/exec/bytecode/gopkg.go
@@ -89,11 +89,15 @@ func execStoreField(i Instr, p *Context) {
 	val := p.Pop()
 	value := p.Pop()
 	v := reflect.ValueOf(val)
+	storeField(v, index.([]int), value)
+}
+
+func storeField(v reflect.Value, index []int, value interface{}) {
 	v = toElem(v)
 	if !v.CanSet() {
 		log.Panicf("cannot assign to %v\n", v)
 	}
-	setValue(v.FieldByIndex(index.([]int)), value)
+	setValue(v.FieldByIndex(index), value)
 }
 
 // -----------------------------------------------------------------------------
diff --git a/exec/bytecode/interface.go b/exec/bytecode/interface.go
index 807575bf..4f27c73a 100644
--- a/exec/bytecode/interface.go
+++ b/exec/bytecode/interface.go
@@ -283,6 +283,12 @@ func (p *iBuilder) DefineVar(vars ...exec.Var) exec.Builder {
 	return p
 }
 
+// DefineType name string,reflect.Type instr
+func (p *iBuilder) DefineType(typ reflect.Type, name string) exec.Builder {
+	// Nothing todo in bytecode
+	return p
+}
+
 // InCurrentCtx returns if a variable is in current context or not.
 func (p *iBuilder) InCurrentCtx(v exec.Var) bool {
 	return ((*Builder)(p)).InCurrentCtx(v.(*Var))
diff --git a/exec/bytecode/module.go b/exec/bytecode/module.go
index 7e2dfb67..4a3bb9c7 100644
--- a/exec/bytecode/module.go
+++ b/exec/bytecode/module.go
@@ -55,7 +55,7 @@ func (p *Package) NewComprehension(out reflect.Type) exec.Comprehension {
 }
 
 // NewFunc create a Go+ function.
-func (p *Package) NewFunc(name string, nestDepth uint32) exec.FuncInfo {
+func (p *Package) NewFunc(name string, nestDepth uint32, funcType ...int) exec.FuncInfo {
 	return (*iFuncInfo)(newFuncWith(p, name, nestDepth))
 }
 
diff --git a/exec/golang/builder.go b/exec/golang/builder.go
index 3ca9a3e4..56c7fc96 100644
--- a/exec/golang/builder.go
+++ b/exec/golang/builder.go
@@ -90,15 +90,16 @@ const (
 // Builder is a class that generates go code.
 type Builder struct {
 	lhs, rhs    exec.Stack
-	out         *Code             // golang code
-	pkgName     string            // package name
-	imports     map[string]string // pkgPath => aliasName
-	importPaths map[string]string // aliasName => pkgPath
-	gblScope    scopeCtx          // global scope
-	gblDecls    []ast.Decl        // global declarations
-	fset        *token.FileSet    // fileset of Go+ code
-	cfun        *FuncInfo         // current function
-	cstmt       interface{}       // current statement
+	out         *Code                    // golang code
+	pkgName     string                   // package name
+	types       map[reflect.Type]*GoType // type => gotype
+	imports     map[string]string        // pkgPath => aliasName
+	importPaths map[string]string        // aliasName => pkgPath
+	gblScope    scopeCtx                 // global scope
+	gblDecls    []ast.Decl               // global declarations
+	fset        *token.FileSet           // fileset of Go+ code
+	cfun        *FuncInfo                // current function
+	cstmt       interface{}              // current statement
 	reserveds   []*printer.ReservedExpr
 	comprehens  func()   // current comprehension
 	identBase   int      // auo-increasement ident index
@@ -114,6 +115,7 @@ func NewBuilder(pkgName string, code *Code, fset *token.FileSet) *Builder {
 	p := &Builder{
 		out:         code,
 		gblDecls:    make([]ast.Decl, 0, 4),
+		types:       make(map[reflect.Type]*GoType),
 		imports:     make(map[string]string),
 		importPaths: make(map[string]string),
 		fset:        fset,
@@ -147,6 +149,10 @@ func (p *Builder) Resolve() *Code {
 	if imports != nil {
 		decls = append(decls, imports)
 	}
+	types := p.resolveTypes()
+	if types != nil {
+		decls = append(decls, types)
+	}
 	gblvars := p.gblScope.toGenDecl(p)
 	if gblvars != nil {
 		decls = append(decls, gblvars)
@@ -201,6 +207,39 @@ func (p *Builder) resolveImports() *ast.GenDecl {
 	}
 }
 
+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,
+		}
+		specs = append(specs, spec)
+	}
+	if len(specs) > 0 {
+		return &ast.GenDecl{
+			Tok:   token.TYPE,
+			Specs: specs,
+		}
+	}
+	return nil
+}
+
 // Comment instr
 func Comment(text string) *ast.CommentGroup {
 	return &ast.CommentGroup{
diff --git a/exec/golang/expr.go b/exec/golang/expr.go
index 95c536be..82d185c5 100644
--- a/exec/golang/expr.go
+++ b/exec/golang/expr.go
@@ -441,6 +441,9 @@ func (p *Builder) AddrGoVar(addr exec.GoVarAddr) *Builder {
 func (p *Builder) fieldExpr(typ reflect.Type, index []int) ast.Expr {
 	expr := &ast.SelectorExpr{}
 	expr.X = p.rhs.Pop().(ast.Expr)
+	if unary, ok := expr.X.(*ast.UnaryExpr); ok {
+		expr.X = unary.X
+	}
 	if typ.Kind() == reflect.Ptr {
 		typ = typ.Elem()
 	}
@@ -454,6 +457,7 @@ func (p *Builder) fieldExpr(typ reflect.Type, index []int) ast.Expr {
 		}
 		expr.Sel = Ident(sf.Name)
 	}
+
 	return expr
 }
 
diff --git a/exec/golang/func.go b/exec/golang/func.go
index 548a6eef..98ae7e47 100644
--- a/exec/golang/func.go
+++ b/exec/golang/func.go
@@ -37,24 +37,37 @@ const (
 
 // FuncInfo represents a Go+ function information.
 type FuncInfo struct {
-	name    string
-	closure *printer.ReservedExpr // only when name="" (closure)
-	t       reflect.Type
-	in      []reflect.Type
-	out     []exec.Var
+	isMethod int
+	name     string
+	closure  *printer.ReservedExpr // only when name="" (closure)
+	recv     reflect.Type
+	t        reflect.Type
+	in       []reflect.Type
+	out      []exec.Var
 	scopeCtx
 	nVariadic uint16
 }
 
 // NewFunc create a Go+ function.
-func NewFunc(name string, nestDepth uint32) *FuncInfo {
+func NewFunc(name string, nestDepth uint32, isMethod int) *FuncInfo {
 	if name != "" {
-		return &FuncInfo{name: name}
+		return &FuncInfo{name: name, isMethod: isMethod}
 	}
 	return &FuncInfo{closure: &printer.ReservedExpr{}}
 }
 
-func (p *FuncInfo) getFuncExpr() ast.Expr {
+func (p *FuncInfo) getFuncExpr(recv ast.Expr) ast.Expr {
+	if p.isMethod == 1 {
+		if p.name != "" {
+			if unary, ok := recv.(*ast.UnaryExpr); ok {
+				recv = unary.X
+			}
+			return &ast.SelectorExpr{
+				X:   recv,
+				Sel: Ident(p.name),
+			}
+		}
+	}
 	if p.name != "" {
 		return Ident(p.name)
 	}
@@ -73,14 +86,19 @@ func (p *FuncInfo) Type() reflect.Type {
 		for i, v := range p.out {
 			out[i] = v.(*Var).typ
 		}
-		p.t = reflect.FuncOf(p.in, out, p.IsVariadic())
+		in := make([]reflect.Type, 0, p.NumIn())
+		if p.isMethod == 1 {
+			in = append(in, p.recv)
+		}
+		in = append(in, p.in...)
+		p.t = reflect.FuncOf(in, out, p.IsVariadic())
 	}
 	return p.t
 }
 
 // NumIn returns a function's input parameter count.
 func (p *FuncInfo) NumIn() int {
-	return len(p.in)
+	return len(p.in) + p.isMethod
 }
 
 // NumOut returns a function's output parameter count.
@@ -96,6 +114,10 @@ func (p *FuncInfo) Out(i int) exec.Var {
 
 // Args sets argument types of a Go+ function.
 func (p *FuncInfo) Args(in ...reflect.Type) exec.FuncInfo {
+	if p.isMethod == 1 {
+		p.recv = in[0]
+		in = in[1:]
+	}
 	p.in = in
 	p.setVariadic(nVariadicFixedArgs)
 	return p
@@ -106,6 +128,10 @@ func (p *FuncInfo) Vargs(in ...reflect.Type) exec.FuncInfo {
 	if in[len(in)-1].Kind() != reflect.Slice {
 		log.Panicln("Vargs failed: last argument must be a slice.")
 	}
+	if p.isMethod == 1 {
+		p.recv = in[0]
+		in = in[1:]
+	}
 	p.in = in
 	p.setVariadic(nVariadicVariadicArgs)
 	return p
@@ -145,19 +171,35 @@ func (p *FuncInfo) setVariadic(nVariadic uint16) {
 
 // Closure instr
 func (p *Builder) Closure(fun *FuncInfo) *Builder {
-	p.rhs.Push(fun.getFuncExpr())
+	p.rhs.Push(fun.getFuncExpr(nil))
 	return p
 }
 
 // CallFunc instr
 func (p *Builder) CallFunc(fun *FuncInfo, nexpr int) *Builder {
-	p.rhs.Push(fun.getFuncExpr())
+	if fun.isMethod == 1 {
+		args := p.rhs.GetArgs(len(fun.in) + 1)
+		recv := args[0]
+		args = args[1:]
+		p.rhs.Ret(len(fun.in)+1, args...)
+		p.rhs.Push(fun.getFuncExpr(recv.(ast.Expr)))
+	} else {
+		p.rhs.Push(fun.getFuncExpr(nil))
+	}
 	return p.Call(nexpr, false)
 }
 
 // CallFuncv instr
 func (p *Builder) CallFuncv(fun *FuncInfo, nexpr, arity int) *Builder {
-	p.rhs.Push(fun.getFuncExpr())
+	if fun.isMethod == 1 {
+		args := p.rhs.GetArgs(arity)
+		recv := args[0]
+		args = args[1:]
+		p.rhs.Ret(arity, args...)
+		p.rhs.Push(fun.getFuncExpr(recv.(ast.Expr)))
+	} else {
+		p.rhs.Push(fun.getFuncExpr(nil))
+	}
 	return p.Call(nexpr, arity == -1)
 }
 
@@ -210,6 +252,11 @@ func (p *Builder) EndFunc(fun *FuncInfo) *Builder {
 			Type: toFuncType(p, fun),
 			Body: body,
 		}
+		if fun.isMethod == 1 {
+			params := make([]*ast.Field, 1)
+			params[0] = Field(p, "recv", fun.recv, "", false)
+			fn.Recv = &ast.FieldList{Opening: 1, List: params, Closing: 1}
+		}
 		p.gblDecls = append(p.gblDecls, fn)
 	} else {
 		fun.closure.Expr = &ast.FuncLit{
diff --git a/exec/golang/interface.go b/exec/golang/interface.go
index 95831546..02e0d5fc 100644
--- a/exec/golang/interface.go
+++ b/exec/golang/interface.go
@@ -54,11 +54,11 @@ func (p *interfaceImpl) NewComprehension(out reflect.Type) exec.Comprehension {
 }
 
 // NewFunc create a Go+ function.
-func (p *interfaceImpl) NewFunc(name string, nestDepth uint32) exec.FuncInfo {
+func (p *interfaceImpl) NewFunc(name string, nestDepth uint32, funcType ...int) exec.FuncInfo {
 	if nestDepth == 0 {
 		return nil
 	}
-	return NewFunc(name, nestDepth)
+	return NewFunc(name, nestDepth, funcType[0])
 }
 
 // FindGoPackage lookups a Go package by pkgPath. It returns nil if not found.
@@ -299,6 +299,12 @@ func (p *iBuilder) DefineVar(vars ...exec.Var) exec.Builder {
 	return p
 }
 
+// DefineType defines variables.
+func (p *iBuilder) DefineType(typ reflect.Type, name string) exec.Builder {
+	((*Builder)(p)).DefineType(typ, name)
+	return p
+}
+
 // InCurrentCtx returns if a variable is in current context or not.
 func (p *iBuilder) InCurrentCtx(v exec.Var) bool {
 	return ((*Builder)(p)).InCurrentCtx(v.(*Var))
diff --git a/exec/golang/type.go b/exec/golang/type.go
index c6f9738d..04fc006d 100644
--- a/exec/golang/type.go
+++ b/exec/golang/type.go
@@ -28,6 +28,9 @@ 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)))
@@ -165,3 +168,25 @@ func Type(p *Builder, typ reflect.Type) ast.Expr {
 }
 
 // -----------------------------------------------------------------------------
+
+type GoType struct {
+	name string
+	typ  reflect.Type
+}
+
+// DefineVar defines types.
+func (p *Builder) DefineType(typ reflect.Type, name string) *Builder {
+	p.types[typ] = &GoType{
+		name: name,
+		typ:  typ,
+	}
+	return p
+}
+
+func (p *GoType) Type() reflect.Type {
+	return p.typ
+}
+
+func (p *GoType) Name() string {
+	return p.name
+}
diff --git a/exec/golang/var.go b/exec/golang/var.go
index a4b7e31c..9a9410ee 100644
--- a/exec/golang/var.go
+++ b/exec/golang/var.go
@@ -141,6 +141,9 @@ func (p *Builder) Store(idx int32) *Builder {
 
 func (p *Builder) argIdent(idx int32) *ast.Ident {
 	i := len(p.cfun.in) + int(idx)
+	if i == -1 {
+		return Ident("recv")
+	}
 	return Ident(toArg(i))
 }
 
diff --git a/tutorial/26-Method/method.gop b/tutorial/26-Method/method.gop
new file mode 100644
index 00000000..cb0128e2
--- /dev/null
+++ b/tutorial/26-Method/method.gop
@@ -0,0 +1,35 @@
+type Person struct {
+	Name    string
+	Age     int
+	Friends []string
+}
+
+func (p *Person) SetName(name string) {
+	p.Name = name
+	println(p.Name)
+}
+
+func (p *Person) SetAge(age int) {
+	age = age + 5
+	p.Age = age
+	println(p.Age)
+}
+
+func (p *Person) AddFriends(args ...string) {
+	p.Friends = append(p.Friends, args...)
+}
+
+p := Person{
+	Name: "bar",
+	Age:  30,
+}
+
+p.Name, p.Age = "bar2", 31
+
+p.SetName("foo")
+p.SetAge(32)
+p.AddFriends("a", "b", "c")
+
+println(p.Name)
+println(p.Age)
+println(p.Friends)


Test patch:
diff --git a/cl/expr_test.go b/cl/expr_test.go
index 2975904d..10e8f7b8 100644
--- a/cl/expr_test.go
+++ b/cl/expr_test.go
@@ -861,6 +861,150 @@ func TestStruct2(t *testing.T) {
 	testScripts(t, "TestStruct", testStructClauses)
 }
 
+// -----------------------------------------------------------------------------
+var testMethodClauses = map[string]testData{
+	"method set": {`
+					type Person struct {
+						Name string
+						Age  int
+					}
+					func (p *Person) SetName(name string) {
+						p.Name = name
+					}
+
+					p := &Person{
+						Name: "bar",
+						Age:  30,
+					}
+
+					p.SetName("foo")
+					println(p.Name)
+					`, "foo\n", false},
+	"method get": {`
+					type Person struct {
+						Name string
+						Age  int
+					}
+					func (p *Person) GetName() string {
+						return p.Name
+					}
+
+					p := &Person{
+						Name: "bar",
+						Age:  30,
+					}
+
+					println(p.GetName())
+					`, "bar\n", false},
+
+	"struct set ptr": {`
+	type Person struct {
+		Name string
+		Age  int
+	}
+
+	p := &Person{
+		Name: "bar",
+		Age:  30,
+	}
+	p.Name = "foo"
+
+	println(p)
+	`, "&{foo 30}\n", false},
+
+	"struct set": {`
+	type Person struct {
+		Name string
+		Age  int
+	}
+
+	p := Person{
+		Name: "bar",
+		Age:  30,
+	}
+	p.Name = "foo"
+
+	println(p)
+	`, "{foo 30}\n", false},
+
+	"struct set ptr arg": {`
+	type Person struct {
+		Name string
+		Age  int
+	}
+	func SetName(p *Person,name string) {
+		p.Name = name
+	}
+
+	p := Person{
+		Name: "bar",
+		Age:  30,
+	}
+	SetName(&p,"foo")
+
+	println(p)
+	`, "{foo 30}\n", false},
+
+	"method func no args": {`
+					type Person struct {
+						Name string ` + "`json:\"name\"`" + `
+						Age  int
+					}
+					func (p *Person) PrintName() {
+						println(p.Name)
+					}
+
+					p := &Person{
+						Name: "bar",
+						Age:  30,
+					}
+
+					p.PrintName()
+					`, "bar\n", false},
+	"method ptr struct no prt": {`
+					type Person struct {
+						Name string ` + "`json:\"name\"`" + `
+						Age  int
+					}
+					func (p *Person) PrintName() {
+						println(p.Name)
+					}
+
+					p := Person{
+						Name: "bar",
+						Age:  30,
+					}
+
+					p.PrintName()
+					`, "bar\n", false},
+
+	"method load field": {`
+					type Person struct {
+						Name string
+						Age  int
+					}
+					func (p *Person) SetName(name string,age int) {
+						p.Name = name
+						p.Age = age
+						println(name)
+						println(p.Age)
+					}
+
+					p := Person{
+						Name: "bar",
+						Age:  30,
+					}
+
+					p.SetName("foo",31)
+					`, "foo\n31\n", false},
+}
+
+func TestMethodCases(t *testing.T) {
+	testScripts(t, "TestMethod", testMethodClauses)
+}
+
+// -----------------------------------------------------------------------------
+
 func testScripts(t *testing.T, testName string, scripts map[string]testData) {
 	for name, script := range scripts {
 		t.Log("Run " + testName + "---" + name)
diff --git a/exec/bytecode/gopkg_test.go b/exec/bytecode/gopkg_test.go
index e4218ffc..d4fbfc79 100644
--- a/exec/bytecode/gopkg_test.go
+++ b/exec/bytecode/gopkg_test.go
@@ -407,7 +407,7 @@ func TestMapBadStoreField(t *testing.T) {
 func TestSliceField(t *testing.T) {
 	pkg := NewGoPackage("pkg_slice_field")
 
-	rcm := []testPoint{testPoint{10, 20}, testPoint{100, 200}, testPoint{200, 300}}
+	rcm := []testPoint{{10, 20}, {100, 200}, {200, 300}}
 
 	pkg.RegisterVars(pkg.Var("M", &rcm))
 	x, ok := pkg.FindVar("M")
@@ -450,7 +450,7 @@ func TestArrayBadStoreField(t *testing.T) {
 
 	pkg := NewGoPackage("pkg_array_bad_store_field")
 
-	rcm := [2]testPoint{testPoint{10, 20}, testPoint{100, 200}}
+	rcm := [2]testPoint{{10, 20}, {100, 200}}
 
 	pkg.RegisterVars(pkg.Var("M", &rcm))
 	x, ok := pkg.FindVar("M")
diff --git a/exec/golang/builder_test.go b/exec/golang/builder_test.go
index c784382b..0647d235 100644
--- a/exec/golang/builder_test.go
+++ b/exec/golang/builder_test.go
@@ -211,17 +211,17 @@ var y golang.testRect
 
 func main() {
 	y = pkg_field.Rect2
-	(&pkg_field.Rect).Info = "hello"
-	(&pkg_field.Rect).Pt1.X = -1
-	(&pkg_field.Rect).Pt2.Y = -2
-	(&y).Info = "world"
-	(&y).Pt1.X = -10
-	(&y).Pt2.Y = -20
+	pkg_field.Rect.Info = "hello"
+	pkg_field.Rect.Pt1.X = -1
+	pkg_field.Rect.Pt2.Y = -2
+	y.Info = "world"
+	y.Pt1.X = -10
+	y.Pt2.Y = -20
 	pkg_field.GetRect().Info = "next"
 	pkg_field.GetRect().Pt1.X = 101
 	pkg_field.GetRect().Pt2.Y = 102
 	pkg_field.Rect2 = y
-	fmt.Println(pkg_field.Rect.Info, pkg_field.Rect.Pt2.Y, y.Info, y.Pt2.Y, pkg_field.GetRect().Info, pkg_field.GetRect().Pt2.Y, &(&pkg_field.Rect).Pt1, &(&y).Pt1, &pkg_field.GetRect().Pt1)
+	fmt.Println(pkg_field.Rect.Info, pkg_field.Rect.Pt2.Y, y.Info, y.Pt2.Y, pkg_field.GetRect().Info, pkg_field.GetRect().Pt2.Y, &pkg_field.Rect.Pt1, &y.Pt1, &pkg_field.GetRect().Pt1)
 }
 `
 	println, _ := I.FindFuncv("println")
@@ -427,6 +427,61 @@ 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"
+)
+
+type Person struct {
+	Name string
+	Age  int
+}
+
+var p golang.Person
+
+func main() {
+	p = golang.Person{Name: "bar", Age: 30}
+	fmt.Println(p)
+}
+`
+	typ := reflect.TypeOf(Person{})
+
+	p := NewVar(typ, "p")
+
+	code := NewBuilder("main", nil, nil).Interface().
+		DefineVar(p).
+		DefineType(typ, "Person").
+		EndStmt(nil, &stmtState{rhsBase: 0}).
+		Push("Name").
+		Push("bar").
+		Push("Age").
+		Push(30).
+		Struct(typ, 2).
+		StoreVar(p).
+		EndStmt(nil, &stmtState{rhsBase: 0}).
+		LoadVar(p).
+		CallGoFuncv(println, 1, 1).
+		EndStmt(nil, &stmtState{rhsBase: 0}).
+		Resolve()
+
+	codeGen := code.(*Code).String()
+	if codeGen != codeExp {
+		fmt.Println(codeGen)
+		fmt.Println(codeExp)
+		t.Fatal("TestStruct failed: codeGen != codeExp")
+	}
+}
+
 func TestReserved(t *testing.T) {
 	code := NewBuilder("main", nil, nil)
 	off := code.Reserve()
diff --git a/exec/golang/testdata/26.struct/struct.gop b/exec/golang/testdata/26.struct/struct.gop
index 4bd5a4c8..6111c45c 100644
--- a/exec/golang/testdata/26.struct/struct.gop
+++ b/exec/golang/testdata/26.struct/struct.gop
@@ -1,9 +1,19 @@
-println(struct {
+a := struct {
 	A int
 	B string
-}{1, "Hello"})
+}{1, "Hello"}
 
-println(&struct {
+println(a)
+
+b := &struct {
 	A int
 	B string
-}{1, "Hello"})
+}{1, "Hello"}
+
+println(b)
+
+a.A, a.B = 2, "Hi"
+println(a)
+
+b.B = "Hi"
+println(b)
diff --git a/exec/golang/testdata/28.method/method.gop b/exec/golang/testdata/28.method/method.gop
new file mode 100644
index 00000000..84497952
--- /dev/null
+++ b/exec/golang/testdata/28.method/method.gop
@@ -0,0 +1,35 @@
+type Person struct {
+	Name    string
+	Age     int
+	Friends []string
+}
+
+func (p *Person) SetName(name string) {
+	p.Name = name
+	println(p.Name)
+}
+
+func (p *Person) SetAge(age int) {
+	age = age + 5
+	p.Age = age
+	println(p.Age)
+}
+
+func (p *Person) AddFriends(args ...string) {
+	p.Friends = append(p.Friends, args...)
+}
+
+p := &Person{
+	Name: "bar",
+	Age:  30,
+}
+
+p.Name, p.Age = "bar2", 31
+
+p.SetName("foo")
+p.SetAge(32)
+p.AddFriends("a", "b", "c")
+
+println(p.Name)
+println(p.Age)
+println(p.Friends)