Repository: goplus/gop
Pull request #481 (2020-07-19 08:57:14)

cl/context
- cl/context.go
- more AutoProperty testcase


codecov-commenter: # [Codecov](https://codecov.io/gh/goplus/gop/pull/481?src=pr&el=h1) Report
> Merging [#481](https://codecov.io/gh/goplus/gop/pull/481?src=pr&el=desc) into [master](https://codecov.io/gh/goplus/gop/commit/fe640942de26095a7577cb0036b0b65ae598c489&el=desc) will **not change** coverage.
> The diff coverage is `81.46%`.

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

```diff
@@           Coverage Diff           @@
##           master     #481   +/-   ##
=======================================
  Coverage   88.90%   88.90%           
=======================================
  Files          37       38    +1     
  Lines        9590     9590           
=======================================
  Hits         8526     8526           
  Misses        785      785           
  Partials      279      279           
```


| [Impacted Files](https://codecov.io/gh/goplus/gop/pull/481?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [cl/compile.go](https://codecov.io/gh/goplus/gop/pull/481/diff?src=pr&el=tree#diff-Y2wvY29tcGlsZS5nbw==) | `67.64% <ø> (-8.31%)` | :arrow_down: |
| [cl/context.go](https://codecov.io/gh/goplus/gop/pull/481/diff?src=pr&el=tree#diff-Y2wvY29udGV4dC5nbw==) | `81.46% <81.46%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/goplus/gop/pull/481?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/481?src=pr&el=footer). Last update [fe64094...51fb920](https://codecov.io/gh/goplus/gop/pull/481?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

xushiwei: https://github.com/goplus/gop/issues/474

Patch:
diff --git a/ast/gopq/gop_dom.go b/ast/gopq/gop_dom.go
index aaca44b9..e3e9bb2f 100644
--- a/ast/gopq/gop_dom.go
+++ b/ast/gopq/gop_dom.go
@@ -125,6 +125,11 @@ func NameOf(node Node) string {
 	switch v := node.Obj().(type) {
 	case *ast.FuncDecl:
 		return v.Name.Name
+	case *ast.ImportSpec:
+		if v.Name == nil {
+			return ""
+		}
+		return v.Name.Name
 	default:
 		panic("node doesn't contain the `name` property")
 	}
diff --git a/cl/compile.go b/cl/compile.go
index 8f2560a1..bcfb19a3 100644
--- a/cl/compile.go
+++ b/cl/compile.go
@@ -94,70 +94,6 @@ func logIllTypeMapIndexPanic(ctx *blockCtx, v ast.Node, t, typIdx reflect.Type)
 
 // -----------------------------------------------------------------------------
 
-type pkgCtx struct {
-	exec.Package
-	infer   exec.Stack
-	builtin exec.GoPackage
-	out     exec.Builder
-	usedfns []*funcDecl
-	pkg     *ast.Package
-	fset    *token.FileSet
-}
-
-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.infer.Init()
-	return p
-}
-
-func (p *pkgCtx) code(v ast.Node) string {
-	_, code := p.getCodeInfo(v)
-	return code
-}
-
-func (p *pkgCtx) getCodeInfo(v ast.Node) (token.Position, string) {
-	start, end := v.Pos(), v.End()
-	pos := p.fset.Position(start)
-	if f, ok := p.pkg.Files[pos.Filename]; ok {
-		return pos, string(f.Code[pos.Offset : pos.Offset+int(end-start)])
-	}
-	log.Panicln("pkgCtx.getCodeInfo failed: file not found -", pos.Filename)
-	return pos, ""
-}
-
-func (p *pkgCtx) use(f *funcDecl) {
-	if f.used {
-		return
-	}
-	p.usedfns = append(p.usedfns, f)
-	f.used = true
-}
-
-func (p *pkgCtx) resolveFuncs() {
-	for {
-		n := len(p.usedfns)
-		if n == 0 {
-			break
-		}
-		f := p.usedfns[n-1]
-		p.usedfns = p.usedfns[:n-1]
-		f.Compile()
-	}
-}
-
-type fileCtx struct {
-	*blockCtx // it's global blockCtx
-	imports   map[string]string
-}
-
-func newFileCtx(block *blockCtx) *fileCtx {
-	return &fileCtx{blockCtx: block, imports: make(map[string]string)}
-}
-
-// -----------------------------------------------------------------------------
-
 // - varName => *exec.Var
 // - stkVarName => *stackVar
 // - pkgName => pkgPath
@@ -198,312 +134,6 @@ func (p *stackVar) getType() reflect.Type {
 
 // -----------------------------------------------------------------------------
 
-type funcCtx struct {
-	fun          exec.FuncInfo
-	labels       map[string]*flowLabel
-	currentFlow  *flowCtx
-	currentLabel *ast.LabeledStmt
-}
-
-func newFuncCtx(fun exec.FuncInfo) *funcCtx {
-	return &funcCtx{labels: map[string]*flowLabel{}, fun: fun}
-}
-
-type flowLabel struct {
-	ctx *blockCtx
-	exec.Label
-	jumps []*blockCtx
-}
-type flowCtx struct {
-	parent    *flowCtx
-	name      string
-	postLabel exec.Label
-	doneLabel exec.Label
-}
-
-func (fc *funcCtx) nextFlow(post, done exec.Label, name string) {
-	fc.currentFlow = &flowCtx{
-		parent:    fc.currentFlow,
-		name:      name,
-		postLabel: post,
-		doneLabel: done,
-	}
-}
-
-func (fc *funcCtx) getBreakLabel(labelName string) (label exec.Label, rangeFor bool) {
-	if fc.currentFlow == nil {
-		return nil, false
-	}
-	if fc.currentFlow.postLabel == nil && fc.currentFlow.doneLabel == nil {
-		return nil, true
-	}
-	for i := fc.currentFlow; i != nil; i = i.parent {
-		if i.doneLabel != nil {
-			if labelName == "" || i.name == labelName {
-				return i.doneLabel, false
-			}
-		}
-	}
-	return nil, false
-}
-func (fc *funcCtx) getContinueLabel(labelName string) (label exec.Label, rangeFor bool) {
-	if fc.currentFlow == nil {
-		return nil, false
-	}
-	if fc.currentFlow.postLabel == nil && fc.currentFlow.doneLabel == nil {
-		return nil, true
-	}
-	for i := fc.currentFlow; i != nil; i = i.parent {
-		if i.postLabel != nil {
-			if labelName == "" || i.name == labelName {
-				return i.postLabel, false
-			}
-		}
-	}
-	return nil, false
-}
-
-func (fc *funcCtx) checkLabels() {
-	for name, fl := range fc.labels {
-		if fl.ctx == nil {
-			log.Panicf("label %s not defined\n", name)
-		}
-		if !checkLabel(fl) {
-			log.Panicf("goto %s jumps into illegal block\n", name)
-		}
-	}
-	fc.labels = map[string]*flowLabel{}
-}
-
-func checkLabel(fl *flowLabel) bool {
-	to := fl.ctx
-	for _, j := range fl.jumps {
-		if !j.canJmpTo(to) {
-			return false
-		}
-	}
-	return true
-}
-
-type blockCtx struct {
-	*pkgCtx
-	*funcCtx
-	file      *fileCtx
-	parent    *blockCtx
-	syms      map[string]iSymbol
-	noExecCtx bool
-	checkFlag bool
-	inLHS     bool
-}
-
-// function block ctx
-func newExecBlockCtx(parent *blockCtx) *blockCtx {
-	return &blockCtx{
-		pkgCtx:    parent.pkgCtx,
-		file:      parent.file,
-		parent:    parent,
-		syms:      make(map[string]iSymbol),
-		noExecCtx: false,
-	}
-}
-
-// normal block ctx, eg. if/switch/for/etc.
-func newNormBlockCtx(parent *blockCtx) *blockCtx {
-	return newNormBlockCtxEx(parent, true)
-}
-
-func newNormBlockCtxEx(parent *blockCtx, noExecCtx bool) *blockCtx {
-	return &blockCtx{
-		pkgCtx:    parent.pkgCtx,
-		file:      parent.file,
-		parent:    parent,
-		funcCtx:   parent.funcCtx,
-		syms:      make(map[string]iSymbol),
-		noExecCtx: noExecCtx,
-	}
-}
-
-// global block ctx
-func newGblBlockCtx(pkg *pkgCtx) *blockCtx {
-	return &blockCtx{
-		pkgCtx:    pkg,
-		parent:    nil,
-		syms:      make(map[string]iSymbol),
-		noExecCtx: true,
-		funcCtx:   newFuncCtx(nil),
-	}
-}
-
-func (p *blockCtx) requireLabel(name string) exec.Label {
-	fl := p.labels[name]
-	if fl == nil {
-		fl = &flowLabel{
-			Label: p.NewLabel(name),
-		}
-		p.labels[name] = fl
-	}
-	fl.jumps = append(fl.jumps, p)
-	return fl.Label
-}
-
-func (p *blockCtx) defineLabel(name string) exec.Label {
-	fl, ok := p.labels[name]
-	if ok {
-		if fl.ctx != nil {
-			log.Panicf("label %s already defined at other position \n", name)
-		}
-		fl.ctx = p
-	} else {
-		fl = &flowLabel{
-			ctx:   p,
-			Label: p.NewLabel(name),
-		}
-		p.labels[name] = fl
-	}
-	return fl.Label
-}
-
-func (p *blockCtx) canJmpTo(to *blockCtx) bool {
-	for from := p; from != nil; from = from.parent {
-		if from == to {
-			return true
-		}
-	}
-	return false
-}
-
-func (p *blockCtx) getNestDepth() (nestDepth uint32) {
-	for {
-		if !p.noExecCtx {
-			nestDepth++
-		}
-		if p = p.parent; p == nil {
-			return
-		}
-	}
-}
-
-func (p *blockCtx) exists(name string) (ok bool) {
-	if _, ok = p.syms[name]; ok {
-		return
-	}
-	if p.parent == nil { // it's global blockCtx
-		_, ok = p.file.imports[name]
-	}
-	return
-}
-
-func (p *blockCtx) find(name string) (sym interface{}, ok bool) {
-	ctx := p
-	for ; p != nil; p = p.parent {
-		if sym, ok = p.syms[name]; ok {
-			return
-		}
-	}
-	sym, ok = ctx.file.imports[name]
-	return
-}
-
-func (p *blockCtx) findType(name string) (decl *typeDecl, err error) {
-	v, ok := p.find(name)
-	if !ok {
-		return nil, ErrNotFound
-	}
-	if decl, ok = v.(*typeDecl); ok {
-		return
-	}
-	return nil, ErrSymbolNotType
-}
-
-func (p *blockCtx) findFunc(name string) (addr *funcDecl, err error) {
-	v, ok := p.find(name)
-	if !ok {
-		return nil, ErrNotFound
-	}
-	if addr, ok = v.(*funcDecl); ok {
-		return
-	}
-	return nil, ErrSymbolNotFunc
-}
-
-func (p *blockCtx) findVar(name string) (addr iVar, err error) {
-	v, ok := p.find(name)
-	if !ok {
-		return nil, ErrNotFound
-	}
-	if addr, ok = v.(iVar); ok {
-		return
-	}
-	return nil, ErrSymbolNotVariable
-}
-
-func (p *blockCtx) insertFuncVars(in []reflect.Type, args []string, rets []exec.Var) {
-	n := len(args)
-	if n > 0 {
-		for i := n - 1; i >= 0; i-- {
-			name := args[i]
-			if name == "" { // unnamed argument
-				continue
-			}
-			if p.exists(name) {
-				log.Panicln("insertStkVars failed: symbol exists -", name)
-			}
-			p.syms[name] = &stackVar{index: int32(i - n), typ: in[i]}
-		}
-	}
-	for _, ret := range rets {
-		if ret.IsUnnamedOut() {
-			continue
-		}
-		p.syms[ret.Name()] = &execVar{ret}
-	}
-}
-
-func (p *blockCtx) insertVar(name string, typ reflect.Type, inferOnly ...bool) *execVar {
-	if p.exists(name) {
-		log.Panicln("insertVar failed: symbol exists -", name)
-	}
-	v := p.NewVar(typ, name)
-	if inferOnly == nil {
-		p.out.DefineVar(v)
-	}
-	ev := &execVar{v}
-	p.syms[name] = ev
-	return ev
-}
-
-func (p *blockCtx) insertFunc(name string, fun *funcDecl) {
-	if p.exists(name) {
-		log.Panicln("insertFunc failed: symbol exists -", name)
-	}
-	p.syms[name] = fun
-}
-
-func (p *blockCtx) insertMethod(typeName, methodName string, method *methodDecl) {
-	if p.parent != nil {
-		log.Panicln("insertMethod failed: unexpected - non global method declaration?")
-	}
-	typ, err := p.findType(typeName)
-	if err == ErrNotFound {
-		typ = new(typeDecl)
-		p.syms[typeName] = typ
-	} else if err != nil {
-		log.Panicln("insertMethod failed:", err)
-	} else if typ.Alias {
-		log.Panicln("insertMethod failed: alias?")
-	}
-	if typ.Methods == nil {
-		typ.Methods = map[string]*methodDecl{methodName: method}
-	} else {
-		if _, ok := typ.Methods[methodName]; ok {
-			log.Panicln("insertMethod failed: method exists -", typeName, methodName)
-		}
-		typ.Methods[methodName] = method
-	}
-}
-
-// -----------------------------------------------------------------------------
-
 // A Package represents a Go+ package.
 type Package struct {
 	syms map[string]iSymbol
diff --git a/cl/context.go b/cl/context.go
new file mode 100644
index 00000000..5c4e1191
--- /dev/null
+++ b/cl/context.go
@@ -0,0 +1,402 @@
+/*
+ Copyright 2020 The GoPlus Authors (goplus.org)
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+// Package cl compiles Go+ syntax trees (ast) into a backend code.
+// For now the supported backends are `bytecode` and `golang`.
+package cl
+
+import (
+	"reflect"
+
+	"github.com/goplus/gop/ast"
+	"github.com/goplus/gop/exec.spec"
+	"github.com/goplus/gop/token"
+	"github.com/qiniu/x/log"
+)
+
+// -----------------------------------------------------------------------------
+
+type pkgCtx struct {
+	exec.Package
+	infer   exec.Stack
+	builtin exec.GoPackage
+	out     exec.Builder
+	usedfns []*funcDecl
+	pkg     *ast.Package
+	fset    *token.FileSet
+}
+
+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.infer.Init()
+	return p
+}
+
+func (p *pkgCtx) code(v ast.Node) string {
+	_, code := p.getCodeInfo(v)
+	return code
+}
+
+func (p *pkgCtx) getCodeInfo(v ast.Node) (token.Position, string) {
+	start, end := v.Pos(), v.End()
+	pos := p.fset.Position(start)
+	if f, ok := p.pkg.Files[pos.Filename]; ok {
+		return pos, string(f.Code[pos.Offset : pos.Offset+int(end-start)])
+	}
+	log.Panicln("pkgCtx.getCodeInfo failed: file not found -", pos.Filename)
+	return pos, ""
+}
+
+func (p *pkgCtx) use(f *funcDecl) {
+	if f.used {
+		return
+	}
+	p.usedfns = append(p.usedfns, f)
+	f.used = true
+}
+
+func (p *pkgCtx) resolveFuncs() {
+	for {
+		n := len(p.usedfns)
+		if n == 0 {
+			break
+		}
+		f := p.usedfns[n-1]
+		p.usedfns = p.usedfns[:n-1]
+		f.Compile()
+	}
+}
+
+type fileCtx struct {
+	*blockCtx // it's global blockCtx
+	imports   map[string]string
+}
+
+func newFileCtx(block *blockCtx) *fileCtx {
+	return &fileCtx{blockCtx: block, imports: make(map[string]string)}
+}
+
+// -----------------------------------------------------------------------------
+
+type funcCtx struct {
+	fun          exec.FuncInfo
+	labels       map[string]*flowLabel
+	currentFlow  *flowCtx
+	currentLabel *ast.LabeledStmt
+}
+
+func newFuncCtx(fun exec.FuncInfo) *funcCtx {
+	return &funcCtx{labels: map[string]*flowLabel{}, fun: fun}
+}
+
+type flowLabel struct {
+	ctx *blockCtx
+	exec.Label
+	jumps []*blockCtx
+}
+
+type flowCtx struct {
+	parent    *flowCtx
+	name      string
+	postLabel exec.Label
+	doneLabel exec.Label
+}
+
+func (fc *funcCtx) nextFlow(post, done exec.Label, name string) {
+	fc.currentFlow = &flowCtx{
+		parent:    fc.currentFlow,
+		name:      name,
+		postLabel: post,
+		doneLabel: done,
+	}
+}
+
+func (fc *funcCtx) getBreakLabel(labelName string) (label exec.Label, rangeFor bool) {
+	if fc.currentFlow == nil {
+		return nil, false
+	}
+	if fc.currentFlow.postLabel == nil && fc.currentFlow.doneLabel == nil {
+		return nil, true
+	}
+	for i := fc.currentFlow; i != nil; i = i.parent {
+		if i.doneLabel != nil {
+			if labelName == "" || i.name == labelName {
+				return i.doneLabel, false
+			}
+		}
+	}
+	return nil, false
+}
+
+func (fc *funcCtx) getContinueLabel(labelName string) (label exec.Label, rangeFor bool) {
+	if fc.currentFlow == nil {
+		return nil, false
+	}
+	if fc.currentFlow.postLabel == nil && fc.currentFlow.doneLabel == nil {
+		return nil, true
+	}
+	for i := fc.currentFlow; i != nil; i = i.parent {
+		if i.postLabel != nil {
+			if labelName == "" || i.name == labelName {
+				return i.postLabel, false
+			}
+		}
+	}
+	return nil, false
+}
+
+func (fc *funcCtx) checkLabels() {
+	for name, fl := range fc.labels {
+		if fl.ctx == nil {
+			log.Panicf("label %s not defined\n", name)
+		}
+		if !checkLabel(fl) {
+			log.Panicf("goto %s jumps into illegal block\n", name)
+		}
+	}
+	fc.labels = map[string]*flowLabel{}
+}
+
+func checkLabel(fl *flowLabel) bool {
+	to := fl.ctx
+	for _, j := range fl.jumps {
+		if !j.canJmpTo(to) {
+			return false
+		}
+	}
+	return true
+}
+
+type blockCtx struct {
+	*pkgCtx
+	*funcCtx
+	file      *fileCtx
+	parent    *blockCtx
+	syms      map[string]iSymbol
+	noExecCtx bool
+	checkFlag bool
+	inLHS     bool
+}
+
+// function block ctx
+func newExecBlockCtx(parent *blockCtx) *blockCtx {
+	return &blockCtx{
+		pkgCtx:    parent.pkgCtx,
+		file:      parent.file,
+		parent:    parent,
+		syms:      make(map[string]iSymbol),
+		noExecCtx: false,
+	}
+}
+
+// normal block ctx, eg. if/switch/for/etc.
+func newNormBlockCtx(parent *blockCtx) *blockCtx {
+	return newNormBlockCtxEx(parent, true)
+}
+
+func newNormBlockCtxEx(parent *blockCtx, noExecCtx bool) *blockCtx {
+	return &blockCtx{
+		pkgCtx:    parent.pkgCtx,
+		file:      parent.file,
+		parent:    parent,
+		funcCtx:   parent.funcCtx,
+		syms:      make(map[string]iSymbol),
+		noExecCtx: noExecCtx,
+	}
+}
+
+// global block ctx
+func newGblBlockCtx(pkg *pkgCtx) *blockCtx {
+	return &blockCtx{
+		pkgCtx:    pkg,
+		parent:    nil,
+		syms:      make(map[string]iSymbol),
+		noExecCtx: true,
+		funcCtx:   newFuncCtx(nil),
+	}
+}
+
+func (p *blockCtx) requireLabel(name string) exec.Label {
+	fl := p.labels[name]
+	if fl == nil {
+		fl = &flowLabel{
+			Label: p.NewLabel(name),
+		}
+		p.labels[name] = fl
+	}
+	fl.jumps = append(fl.jumps, p)
+	return fl.Label
+}
+
+func (p *blockCtx) defineLabel(name string) exec.Label {
+	fl, ok := p.labels[name]
+	if ok {
+		if fl.ctx != nil {
+			log.Panicf("label %s already defined at other position \n", name)
+		}
+		fl.ctx = p
+	} else {
+		fl = &flowLabel{
+			ctx:   p,
+			Label: p.NewLabel(name),
+		}
+		p.labels[name] = fl
+	}
+	return fl.Label
+}
+
+func (p *blockCtx) canJmpTo(to *blockCtx) bool {
+	for from := p; from != nil; from = from.parent {
+		if from == to {
+			return true
+		}
+	}
+	return false
+}
+
+func (p *blockCtx) getNestDepth() (nestDepth uint32) {
+	for {
+		if !p.noExecCtx {
+			nestDepth++
+		}
+		if p = p.parent; p == nil {
+			return
+		}
+	}
+}
+
+func (p *blockCtx) exists(name string) (ok bool) {
+	if _, ok = p.syms[name]; ok {
+		return
+	}
+	if p.parent == nil { // it's global blockCtx
+		_, ok = p.file.imports[name]
+	}
+	return
+}
+
+func (p *blockCtx) find(name string) (sym interface{}, ok bool) {
+	ctx := p
+	for ; p != nil; p = p.parent {
+		if sym, ok = p.syms[name]; ok {
+			return
+		}
+	}
+	sym, ok = ctx.file.imports[name]
+	return
+}
+
+func (p *blockCtx) findType(name string) (decl *typeDecl, err error) {
+	v, ok := p.find(name)
+	if !ok {
+		return nil, ErrNotFound
+	}
+	if decl, ok = v.(*typeDecl); ok {
+		return
+	}
+	return nil, ErrSymbolNotType
+}
+
+func (p *blockCtx) findFunc(name string) (addr *funcDecl, err error) {
+	v, ok := p.find(name)
+	if !ok {
+		return nil, ErrNotFound
+	}
+	if addr, ok = v.(*funcDecl); ok {
+		return
+	}
+	return nil, ErrSymbolNotFunc
+}
+
+func (p *blockCtx) findVar(name string) (addr iVar, err error) {
+	v, ok := p.find(name)
+	if !ok {
+		return nil, ErrNotFound
+	}
+	if addr, ok = v.(iVar); ok {
+		return
+	}
+	return nil, ErrSymbolNotVariable
+}
+
+func (p *blockCtx) insertFuncVars(in []reflect.Type, args []string, rets []exec.Var) {
+	n := len(args)
+	if n > 0 {
+		for i := n - 1; i >= 0; i-- {
+			name := args[i]
+			if name == "" { // unnamed argument
+				continue
+			}
+			if p.exists(name) {
+				log.Panicln("insertStkVars failed: symbol exists -", name)
+			}
+			p.syms[name] = &stackVar{index: int32(i - n), typ: in[i]}
+		}
+	}
+	for _, ret := range rets {
+		if ret.IsUnnamedOut() {
+			continue
+		}
+		p.syms[ret.Name()] = &execVar{ret}
+	}
+}
+
+func (p *blockCtx) insertVar(name string, typ reflect.Type, inferOnly ...bool) *execVar {
+	if p.exists(name) {
+		log.Panicln("insertVar failed: symbol exists -", name)
+	}
+	v := p.NewVar(typ, name)
+	if inferOnly == nil {
+		p.out.DefineVar(v)
+	}
+	ev := &execVar{v}
+	p.syms[name] = ev
+	return ev
+}
+
+func (p *blockCtx) insertFunc(name string, fun *funcDecl) {
+	if p.exists(name) {
+		log.Panicln("insertFunc failed: symbol exists -", name)
+	}
+	p.syms[name] = fun
+}
+
+func (p *blockCtx) insertMethod(typeName, methodName string, method *methodDecl) {
+	if p.parent != nil {
+		log.Panicln("insertMethod failed: unexpected - non global method declaration?")
+	}
+	typ, err := p.findType(typeName)
+	if err == ErrNotFound {
+		typ = new(typeDecl)
+		p.syms[typeName] = typ
+	} else if err != nil {
+		log.Panicln("insertMethod failed:", err)
+	} else if typ.Alias {
+		log.Panicln("insertMethod failed: alias?")
+	}
+	if typ.Methods == nil {
+		typ.Methods = map[string]*methodDecl{methodName: method}
+	} else {
+		if _, ok := typ.Methods[methodName]; ok {
+			log.Panicln("insertMethod failed: method exists -", typeName, methodName)
+		}
+		typ.Methods[methodName] = method
+	}
+}
+
+// -----------------------------------------------------------------------------
diff --git a/cl/expr.go b/cl/expr.go
index d1880279..8874e5a6 100644
--- a/cl/expr.go
+++ b/cl/expr.go
@@ -69,7 +69,7 @@ func compileExpr(ctx *blockCtx, expr ast.Expr) func() {
 	case *ast.UnaryExpr:
 		return compileUnaryExpr(ctx, v)
 	case *ast.SelectorExpr:
-		return compileSelectorExpr(ctx, v)
+		return compileSelectorExpr(ctx, v, true)
 	case *ast.ErrWrapExpr:
 		return compileErrWrapExpr(ctx, v)
 	case *ast.IndexExpr:
@@ -619,7 +619,17 @@ var binaryOps = [...]exec.Operator{
 }
 
 func compileCallExpr(ctx *blockCtx, v *ast.CallExpr) func() {
-	exprFun := compileExpr(ctx, v.Fun)
+	var exprFun func()
+	switch f := v.Fun.(type) {
+	case *ast.SelectorExpr:
+		exprFun = compileSelectorExpr(ctx, f, false)
+	default:
+		exprFun = compileExpr(ctx, f)
+	}
+	return compileCallExprCall(ctx, exprFun, v)
+}
+
+func compileCallExprCall(ctx *blockCtx, exprFun func(), v *ast.CallExpr) func() {
 	fn := ctx.infer.Pop()
 	switch vfn := fn.(type) {
 	case *qlFunc:
@@ -943,8 +953,11 @@ func compileSelectorExprLHS(ctx *blockCtx, v *ast.SelectorExpr, mode compleMode)
 	_ = exprX
 }
 
-func compileSelectorExpr(ctx *blockCtx, v *ast.SelectorExpr) func() {
+func compileSelectorExpr(ctx *blockCtx, v *ast.SelectorExpr, allowAutoCall bool) func() {
 	exprX := compileExpr(ctx, v.X)
+	if v.Sel == nil {
+		return exprX
+	}
 	x := ctx.infer.Get(-1)
 	switch vx := x.(type) {
 	case *nonValue:
@@ -987,17 +1000,16 @@ func compileSelectorExpr(ctx *blockCtx, v *ast.SelectorExpr) func() {
 		}
 	case *goValue:
 		n, t := countPtr(vx.t)
+		autoCall := false
 		name := v.Sel.Name
 		if sf, ok := t.FieldByName(name); ok {
 			log.Panicln("compileSelectorExpr todo: structField -", t, sf)
 		}
 		if _, ok := vx.t.MethodByName(name); !ok && isLower(name) {
 			name = strings.Title(name)
-			if m, ok := vx.t.MethodByName(name); ok {
-				if m.Type.NumIn() == 1 {
-					v.Sel.Name = name
-					return compileCallExpr(ctx, &ast.CallExpr{Fun: v})
-				}
+			if _, ok = vx.t.MethodByName(name); ok {
+				v.Sel.Name = name
+				autoCall = allowAutoCall
 			} else {
 				log.Panicln("compileSelectorExpr: symbol not found -", v.Sel.Name)
 			}
@@ -1012,6 +1024,13 @@ func compileSelectorExpr(ctx *blockCtx, v *ast.SelectorExpr) func() {
 			log.Panicln("compileSelectorExpr: method not found -", method)
 		}
 		ctx.infer.Ret(1, newGoFunc(addr, kind, 1, ctx))
+		if autoCall { // change AST tree
+			copy := *v
+			call := &ast.CallExpr{Fun: &copy}
+			v.X = call
+			v.Sel = nil
+			return compileCallExprCall(ctx, nil, call)
+		}
 		return func() {
 			log.Panicln("compileSelectorExpr: todo")
 		}
diff --git a/lib/default.go b/lib/default.go
index f4a57842..f5916d05 100644
--- a/lib/default.go
+++ b/lib/default.go
@@ -7,6 +7,7 @@ import (
 	_ "github.com/goplus/gop/lib/flag"
 	_ "github.com/goplus/gop/lib/fmt"
 	_ "github.com/goplus/gop/lib/github.com/goplus/gop/ast/gopq"
+	_ "github.com/goplus/gop/lib/github.com/goplus/gop/ast/goptest"
 	_ "github.com/goplus/gop/lib/io"
 	_ "github.com/goplus/gop/lib/io/ioutil"
 	_ "github.com/goplus/gop/lib/log"
diff --git a/tutorial/22-Auto-Property/autoprop.gop b/tutorial/22-Auto-Property/autoprop.gop
new file mode 100644
index 00000000..04aa1294
--- /dev/null
+++ b/tutorial/22-Auto-Property/autoprop.gop
@@ -0,0 +1,23 @@
+import (
+	"github.com/goplus/gop/ast/goptest"
+)
+
+script := `
+    import (
+        gio "io"
+    )
+
+    func New() (*Bar, error) {
+        return nil, gio.EOF
+    }
+
+    bar, err := New()
+    if err != nil {
+        log.Println(err)
+    }
+`
+
+doc := goptest.New(script)!
+
+println(doc.any.funcDecl.name)
+println(doc.any.importSpec.name)


Test patch:
diff --git a/cl/expr_test.go b/cl/expr_test.go
index 3b17d033..8d71ad66 100644
--- a/cl/expr_test.go
+++ b/cl/expr_test.go
@@ -16,6 +16,7 @@
 package cl_test
 
 import (
+	"fmt"
 	"math/big"
 	"testing"
 
@@ -25,6 +26,35 @@ import (
 // -----------------------------------------------------------------------------
 
 func TestAutoProperty(t *testing.T) {
+	script := `
+		import "io"
+
+		func New() (*Bar, error) {
+			return nil, io.EOF
+		}
+
+		bar, err := New()
+		if err != nil {
+			log.Println(err)
+		}
+	`
+	gopcode := `
+		import (
+			"github.com/goplus/gop/ast/goptest"
+		)
+
+		script := %s
+
+		doc := goptest.New(script)!
+		println(doc.any.funcDecl.name)
+	`
+	cltest.Expect(t,
+		fmt.Sprintf(gopcode, "`"+script+"`"),
+		"[New main]\n",
+	)
+}
+
+func TestAutoProperty2(t *testing.T) {
 	cltest.Expect(t, `
 		import "bytes"
 		import "os"
diff --git a/lib/github.com/goplus/gop/ast/goptest/gomod_export.go b/lib/github.com/goplus/gop/ast/goptest/gomod_export.go
new file mode 100644
index 00000000..d68651ed
--- /dev/null
+++ b/lib/github.com/goplus/gop/ast/goptest/gomod_export.go
@@ -0,0 +1,22 @@
+// Package goptest provide Go+ "github.com/goplus/gop/ast/goptest" package, as "github.com/goplus/gop/ast/goptest" package in Go.
+package goptest
+
+import (
+	gop "github.com/goplus/gop"
+	goptest "github.com/goplus/gop/ast/goptest"
+)
+
+func execNew(_ int, p *gop.Context) {
+	args := p.GetArgs(1)
+	ret0, ret1 := goptest.New(args[0].(string))
+	p.Ret(1, ret0, ret1)
+}
+
+// I is a Go package instance.
+var I = gop.NewGoPackage("github.com/goplus/gop/ast/goptest")
+
+func init() {
+	I.RegisterFuncs(
+		I.Func("New", goptest.New, execNew),
+	)
+}