Repository: goplus/gop
Pull request #1381 (2023-06-16 13:57:54)

Release v1.1.5


Patch:
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index d2aab151..2d810abb 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -10,7 +10,7 @@ jobs:
   Test:
     strategy:
       matrix:
-        go-version: [1.16.x, 1.17.x]
+        go-version: [1.16.x, 1.17.x, 1.18.x]
         os: [ubuntu-latest, windows-latest, macos-11]
     runs-on: ${{ matrix.os }}
     steps:
diff --git a/README.md b/README.md
index 4bab1e3d..257303ea 100644
--- a/README.md
+++ b/README.md
@@ -63,10 +63,11 @@ cd gop
 all.bat
 ```
 
-### on macOS
+### on macOS/Linux
 
+Install via [brew](https://brew.sh/)
 ```sh
-brew install goplus
+$ brew install goplus
 ```
 
 
@@ -101,3 +102,8 @@ brew install goplus
 The Go+ project welcomes all contributors. We appreciate your help!
 
 For more details, see [Contributing & compiler design](doc/contributing.md).
+
+
+## Give a Star! ⭐
+
+If you like or are using Go+ to learn or start your projects, please give it a star. Thanks!
diff --git a/VERSION b/VERSION
deleted file mode 100644
index 99a4aef0..00000000
--- a/VERSION
+++ /dev/null
@@ -1,1 +0,0 @@
-v1.1.3
diff --git a/ast/ast.go b/ast/ast.go
index 28e546bd..77a55156 100644
--- a/ast/ast.go
+++ b/ast/ast.go
@@ -221,6 +221,15 @@ type (
 		Rbrack token.Pos // position of "]"
 	}
 
+	// An IndexListExpr node represents an expression followed by multiple
+	// indices.
+	IndexListExpr struct {
+		X       Expr      // expression
+		Lbrack  token.Pos // position of "["
+		Indices []Expr    // index expressions
+		Rbrack  token.Pos // position of "]"
+	}
+
 	// A SliceExpr node represents an expression followed by slice indices.
 	SliceExpr struct {
 		X      Expr      // expression
@@ -316,9 +325,10 @@ type (
 
 	// A FuncType node represents a function type.
 	FuncType struct {
-		Func    token.Pos  // position of "func" keyword (token.NoPos if there is no "func")
-		Params  *FieldList // (incoming) parameters; non-nil
-		Results *FieldList // (outgoing) results; or nil
+		Func       token.Pos  // position of "func" keyword (token.NoPos if there is no "func")
+		TypeParams *FieldList // type parameters; or nil
+		Params     *FieldList // (incoming) parameters; non-nil
+		Results    *FieldList // (outgoing) results; or nil
 	}
 
 	// An InterfaceType node represents an interface type.
@@ -378,6 +388,9 @@ func (x *SelectorExpr) Pos() token.Pos { return x.X.Pos() }
 // Pos returns position of first character belonging to the node.
 func (x *IndexExpr) Pos() token.Pos { return x.X.Pos() }
 
+// Pos returns position of first character belonging to the node.
+func (x *IndexListExpr) Pos() token.Pos { return x.X.Pos() }
+
 // Pos returns position of first character belonging to the node.
 func (x *SliceExpr) Pos() token.Pos { return x.X.Pos() }
 
@@ -454,6 +467,9 @@ func (x *SelectorExpr) End() token.Pos { return x.Sel.End() }
 // End returns position of first character immediately after the node.
 func (x *IndexExpr) End() token.Pos { return x.Rbrack + 1 }
 
+// End returns position of first character immediately after the node.
+func (x *IndexListExpr) End() token.Pos { return x.Rbrack + 1 }
+
 // End returns position of first character immediately after the node.
 func (x *SliceExpr) End() token.Pos { return x.Rbrack + 1 }
 
@@ -520,6 +536,7 @@ func (*CompositeLit) exprNode()   {}
 func (*ParenExpr) exprNode()      {}
 func (*SelectorExpr) exprNode()   {}
 func (*IndexExpr) exprNode()      {}
+func (*IndexListExpr) exprNode()  {}
 func (*SliceExpr) exprNode()      {}
 func (*TypeAssertExpr) exprNode() {}
 func (*CallExpr) exprNode()       {}
@@ -951,11 +968,12 @@ type (
 
 	// A TypeSpec node represents a type declaration (TypeSpec production).
 	TypeSpec struct {
-		Doc     *CommentGroup // associated documentation; or nil
-		Name    *Ident        // type name
-		Assign  token.Pos     // position of '=', if any
-		Type    Expr          // *Ident, *ParenExpr, *SelectorExpr, *StarExpr, or any of the *XxxTypes
-		Comment *CommentGroup // line comments; or nil
+		Doc        *CommentGroup // associated documentation; or nil
+		Name       *Ident        // type name
+		TypeParams *FieldList    // type parameters; or nil
+		Assign     token.Pos     // position of '=', if any
+		Type       Expr          // *Ident, *ParenExpr, *SelectorExpr, *StarExpr, or any of the *XxxTypes
+		Comment    *CommentGroup // line comments; or nil
 	}
 )
 
diff --git a/ast/ast_gop.go b/ast/ast_gop.go
index 9d4f717a..b0c398b6 100644
--- a/ast/ast_gop.go
+++ b/ast/ast_gop.go
@@ -125,7 +125,7 @@ type ForPhrase struct {
 	Key, Value *Ident    // Key may be nil
 	TokPos     token.Pos // position of "<-" operator
 	X          Expr      // value to range over
-	IfPos      token.Pos // postion of if or comma; or NoPos
+	IfPos      token.Pos // position of if or comma; or NoPos
 	Init       Stmt      // initialization statement; or nil
 	Cond       Expr      // value filter, can be nil
 }
diff --git a/ast/fromgo/gopast.go b/ast/fromgo/gopast.go
index aa1618e9..f15b3b3e 100644
--- a/ast/fromgo/gopast.go
+++ b/ast/fromgo/gopast.go
@@ -24,6 +24,8 @@ import (
 
 	gopast "github.com/goplus/gop/ast"
 	goptoken "github.com/goplus/gop/token"
+
+	"github.com/goplus/gop/ast/fromgo/typeparams"
 )
 
 // ----------------------------------------------------------------------------
@@ -116,6 +118,13 @@ func gopExpr(val ast.Expr) gopast.Expr {
 			Index:  gopExpr(v.Index),
 			Rbrack: v.Rbrack,
 		}
+	case *typeparams.IndexListExpr:
+		return &gopast.IndexListExpr{
+			X:       gopExpr(v.X),
+			Lbrack:  v.Lbrack,
+			Indices: gopExprs(v.Indices),
+			Rbrack:  v.Rbrack,
+		}
 	case *ast.ParenExpr:
 		return &gopast.ParenExpr{
 			Lparen: v.Lparen,
@@ -173,9 +182,10 @@ func gopExprs(vals []ast.Expr) []gopast.Expr {
 
 func gopFuncType(v *ast.FuncType) *gopast.FuncType {
 	return &gopast.FuncType{
-		Func:    v.Func,
-		Params:  gopFieldList(v.Params),
-		Results: gopFieldList(v.Results),
+		Func:       v.Func,
+		TypeParams: gopFieldList(typeparams.ForFuncType(v)),
+		Params:     gopFieldList(v.Params),
+		Results:    gopFieldList(v.Results),
 	}
 }
 
@@ -254,9 +264,10 @@ func gopImportSpec(spec *ast.ImportSpec) *gopast.ImportSpec {
 
 func gopTypeSpec(spec *ast.TypeSpec) *gopast.TypeSpec {
 	return &gopast.TypeSpec{
-		Name:   gopIdent(spec.Name),
-		Assign: spec.Assign,
-		Type:   gopType(spec.Type),
+		Name:       gopIdent(spec.Name),
+		TypeParams: gopFieldList(typeparams.ForTypeSpec(spec)),
+		Assign:     spec.Assign,
+		Type:       gopType(spec.Type),
 	}
 }
 
diff --git a/ast/fromgo/typeparams/typeparams_go117.go b/ast/fromgo/typeparams/typeparams_go117.go
new file mode 100644
index 00000000..d3377028
--- /dev/null
+++ b/ast/fromgo/typeparams/typeparams_go117.go
@@ -0,0 +1,38 @@
+//go:build !go1.18
+// +build !go1.18
+
+package typeparams
+
+import (
+	"go/ast"
+	"go/token"
+)
+
+func unsupported() {
+	panic("type parameters are unsupported at this go version")
+}
+
+// IndexListExpr is a placeholder type, as type parameters are not supported at
+// this Go version. Its methods panic on use.
+type IndexListExpr struct {
+	ast.Expr
+	X       ast.Expr   // expression
+	Lbrack  token.Pos  // position of "["
+	Indices []ast.Expr // index expressions
+	Rbrack  token.Pos  // position of "]"
+}
+
+func (*IndexListExpr) Pos() token.Pos { unsupported(); return token.NoPos }
+func (*IndexListExpr) End() token.Pos { unsupported(); return token.NoPos }
+
+// ForFuncType returns an empty field list, as type parameters are not
+// supported at this Go version.
+func ForFuncType(*ast.FuncType) *ast.FieldList {
+	return nil
+}
+
+// ForTypeSpec returns an empty field list, as type parameters are not
+// supported at this Go version.
+func ForTypeSpec(*ast.TypeSpec) *ast.FieldList {
+	return nil
+}
diff --git a/ast/fromgo/typeparams/typeparams_go118.go b/ast/fromgo/typeparams/typeparams_go118.go
new file mode 100644
index 00000000..1e5d9876
--- /dev/null
+++ b/ast/fromgo/typeparams/typeparams_go118.go
@@ -0,0 +1,25 @@
+//go:build go1.18
+// +build go1.18
+
+package typeparams
+
+import "go/ast"
+
+// IndexListExpr is an alias for ast.IndexListExpr.
+type IndexListExpr = ast.IndexListExpr
+
+// ForFuncType returns n.TypeParams.
+func ForFuncType(n *ast.FuncType) *ast.FieldList {
+	if n == nil {
+		return nil
+	}
+	return n.TypeParams
+}
+
+// ForTypeSpec returns n.TypeParams.
+func ForTypeSpec(n *ast.TypeSpec) *ast.FieldList {
+	if n == nil {
+		return nil
+	}
+	return n.TypeParams
+}
diff --git a/cl/compile.go b/cl/compile.go
index d0d070e5..c472b751 100644
--- a/cl/compile.go
+++ b/cl/compile.go
@@ -128,6 +128,9 @@ func (p *nodeInterp) LoadExpr(node ast.Node) (src string, pos token.Position) {
 	start := node.Pos()
 	pos = p.fset.Position(start)
 	f := p.files[pos.Filename]
+	if f == nil { // not found
+		return
+	}
 	n := int(node.End() - start)
 	pos.Filename = relFile(p.workingDir, pos.Filename)
 	if pos.Offset+n < 0 {
@@ -238,11 +241,14 @@ func doInitMethods(ld *typeLoader) {
 type pkgCtx struct {
 	*nodeInterp
 	*gmxSettings
-	cpkgs *cpackages.Importer
-	syms  map[string]loader
-	inits []func()
-	tylds []*typeLoader
-	errs  errors.List
+	cpkgs    *cpackages.Importer
+	syms     map[string]loader
+	generics map[string]bool // generic type record
+	inits    []func()
+	tylds    []*typeLoader
+	idents   []*ast.Ident // toType ident recored
+	errs     errors.List
+	inInst   int // toType in generic instance
 }
 
 type blockCtx struct {
@@ -253,6 +259,7 @@ type blockCtx struct {
 	imports      map[string]*gox.PkgRef
 	lookups      []*gox.PkgRef
 	clookups     []*cpackages.PkgRef
+	tlookup      *typeParamLookup
 	c2goBase     string // default is `github.com/goplus/`
 	targetDir    string
 	classRecv    *ast.FieldList // available when gmxSettings != nil
@@ -365,7 +372,7 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gox.Package,
 		fset: fset, files: files, workingDir: workingDir,
 	}
 	ctx := &pkgCtx{
-		syms: make(map[string]loader), nodeInterp: interp,
+		syms: make(map[string]loader), nodeInterp: interp, generics: make(map[string]bool),
 	}
 	confGox := &gox.Config{
 		Fset:            fset,
@@ -529,16 +536,19 @@ func preloadGopFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, con
 			classType = getDefaultClass(file)
 			o := parent.sprite
 			baseTypeName, baseType, spxClass = o.Name(), o.Type(), true
+		} else {
+			classType = getDefaultClass(file)
 		}
-		// TODO: panic
 	}
 	if classType != "" {
 		if debugLoad {
 			log.Println("==> Preload type", classType)
 		}
-		ctx.lookups = make([]*gox.PkgRef, len(parent.pkgPaths))
-		for i, pkgPath := range parent.pkgPaths {
-			ctx.lookups[i] = p.Import(pkgPath)
+		if parent.gmxSettings != nil {
+			ctx.lookups = make([]*gox.PkgRef, len(parent.pkgPaths))
+			for i, pkgPath := range parent.pkgPaths {
+				ctx.lookups[i] = p.Import(pkgPath)
+			}
 		}
 		syms := parent.syms
 		pos := f.Pos()
@@ -554,27 +564,40 @@ func preloadGopFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, con
 					log.Println("==> Load > InitType", classType)
 				}
 				pkg := p.Types
-				flds := make([]*types.Var, 1, 2)
-				flds[0] = types.NewField(pos, pkg, baseTypeName, baseType, true)
+				var flds []*types.Var
+				chk := newCheckRedecl()
+				if len(baseTypeName) != 0 {
+					flds = append(flds, types.NewField(pos, pkg, baseTypeName, baseType, true))
+					chk.chkRedecl(ctx, baseTypeName, pos)
+				}
 				if spxClass {
 					typ := toType(ctx, &ast.StarExpr{X: &ast.Ident{Name: parent.gameClass}})
-					fld := types.NewField(pos, pkg, getTypeName(typ), typ, true)
-					flds = append(flds, fld)
+					name := getTypeName(typ)
+					if !chk.chkRedecl(ctx, name, pos) {
+						fld := types.NewField(pos, pkg, name, typ, true)
+						flds = append(flds, fld)
+					}
 				}
 				for _, v := range specs {
 					spec := v.(*ast.ValueSpec)
-					if spec.Type == nil {
-						pos := ctx.Position(v.Pos())
-						ctx.handleCodeErrorf(&pos, "missing field type in class file")
-						continue
-					}
 					if len(spec.Values) > 0 {
 						pos := ctx.Position(v.Pos())
 						ctx.handleCodeErrorf(&pos, "cannot assign value to field in class file")
+						continue
+					}
+					var embed bool
+					var typ types.Type
+					if spec.Type == nil {
+						typ = toType(ctx, spec.Names[0])
+						embed = true
+					} else {
+						typ = toType(ctx, spec.Type)
 					}
-					typ := toType(ctx, spec.Type)
 					for _, name := range spec.Names {
-						flds = append(flds, types.NewField(name.Pos(), pkg, name.Name, typ, false))
+						if chk.chkRedecl(ctx, name.Name, name.Pos()) {
+							continue
+						}
+						flds = append(flds, types.NewField(name.Pos(), pkg, name.Name, typ, embed))
 					}
 				}
 				decl.InitType(p, types.NewStruct(flds, nil))
@@ -724,6 +747,7 @@ func preloadFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, genCod
 							}
 						}
 					} else {
+						ctx.generics[name] = true
 						ld.typ = func() {
 							pkg := ctx.pkg.Types
 							if t.Assign != token.NoPos { // alias type
@@ -737,11 +761,12 @@ func preloadFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, genCod
 								log.Println("==> Load > NewType", name)
 							}
 							named := newType(pkg, t.Pos(), name)
+
 							ld.typInit = func() { // decycle
 								if debugLoad {
 									log.Println("==> Load > InitType", name)
 								}
-								initType(ctx, named, toType(ctx, t.Type))
+								initType(ctx, named, t)
 							}
 						}
 					}
@@ -798,13 +823,6 @@ func newType(pkg *types.Package, pos token.Pos, name string) *types.Named {
 	return types.NewNamed(typName, nil, nil)
 }
 
-func initType(ctx *blockCtx, named *types.Named, typ types.Type) {
-	if named, ok := typ.(*types.Named); ok {
-		typ = getUnderlying(ctx, named)
-	}
-	named.SetUnderlying(typ)
-}
-
 func aliasType(pkg *types.Package, pos token.Pos, name string, typ types.Type) {
 	o := types.NewTypeName(pos, pkg, name, typ)
 	pkg.Scope().Insert(o)
@@ -823,7 +841,7 @@ func declFunc(ctx *blockCtx, recv *types.Var, d *ast.FuncDecl) {
 		return
 	}
 	pkg := ctx.pkg.Types
-	sig := toFuncType(ctx, d.Type, recv)
+	sig := toFuncType(ctx, d.Type, recv, d)
 	fn := types.NewFunc(d.Pos(), pkg, name, sig)
 	if recv != nil {
 		typ := recv.Type()
@@ -868,7 +886,7 @@ func loadFunc(ctx *blockCtx, recv *types.Var, d *ast.FuncDecl) {
 			}
 		}
 	}
-	sig := toFuncType(ctx, d.Type, recv)
+	sig := toFuncType(ctx, d.Type, recv, d)
 	fn, err := ctx.pkg.NewFuncWith(d.Pos(), name, sig, func() token.Pos {
 		return d.Recv.List[0].Type.Pos()
 	})
@@ -1040,6 +1058,14 @@ func loadVars(ctx *blockCtx, v *ast.ValueSpec, global bool) {
 	varDecl := ctx.pkg.NewVarEx(scope, v.Names[0].Pos(), typ, names...)
 	if nv := len(v.Values); nv > 0 {
 		cb := varDecl.InitStart(ctx.pkg)
+		if enableRecover {
+			defer func() {
+				if e := recover(); e != nil {
+					cb.ResetInit()
+					panic(e)
+				}
+			}()
+		}
 		if nv == 1 && len(names) == 2 {
 			compileExpr(ctx, v.Values[0], clCallWithTwoValue)
 		} else {
diff --git a/cl/expr.go b/cl/expr.go
index 7e3b1bc2..5d185db1 100644
--- a/cl/expr.go
+++ b/cl/expr.go
@@ -17,6 +17,7 @@
 package cl
 
 import (
+	"bytes"
 	goast "go/ast"
 	gotoken "go/token"
 	"go/types"
@@ -27,6 +28,7 @@ import (
 	"strings"
 
 	"github.com/goplus/gop/ast"
+	"github.com/goplus/gop/printer"
 	"github.com/goplus/gop/token"
 	"github.com/goplus/gox"
 	"github.com/goplus/gox/cpackages"
@@ -70,6 +72,8 @@ const (
 	objCPkgRef
 )
 
+const errorPkgPath = "github.com/qiniu/x/errors"
+
 func compileIdent(ctx *blockCtx, ident *ast.Ident, flags int) (obj *gox.PkgRef, kind int) {
 	fvalue := (flags&clIdentSelectorExpr) != 0 || (flags&clIdentLHS) == 0
 	name := ident.Name
@@ -226,6 +230,8 @@ func compileExpr(ctx *blockCtx, expr ast.Expr, inFlags ...int) {
 		compileRangeExpr(ctx, v)
 	case *ast.IndexExpr:
 		compileIndexExpr(ctx, v, twoValue(inFlags))
+	case *ast.IndexListExpr:
+		compileIndexListExpr(ctx, v, twoValue(inFlags))
 	case *ast.SliceExpr:
 		compileSliceExpr(ctx, v)
 	case *ast.StarExpr:
@@ -249,7 +255,7 @@ func compileExpr(ctx *blockCtx, expr ast.Expr, inFlags ...int) {
 	case *ast.ErrWrapExpr:
 		compileErrWrapExpr(ctx, v, 0)
 	case *ast.FuncType:
-		ctx.cb.Typ(toFuncType(ctx, v, nil), v)
+		ctx.cb.Typ(toFuncType(ctx, v, nil, nil), v)
 	case *ast.Ellipsis:
 		panic("compileEllipsis: ast.Ellipsis unexpected")
 	case *ast.KeyValueExpr:
@@ -309,6 +315,15 @@ func compileIndexExpr(ctx *blockCtx, v *ast.IndexExpr, twoValue bool) { // x[i]
 	ctx.cb.Index(1, twoValue, v)
 }
 
+func compileIndexListExpr(ctx *blockCtx, v *ast.IndexListExpr, twoValue bool) { // fn[t1,t2]
+	compileExpr(ctx, v.X)
+	n := len(v.Indices)
+	for i := 0; i < n; i++ {
+		compileExpr(ctx, v.Indices[i])
+	}
+	ctx.cb.Index(n, twoValue, v)
+}
+
 func compileSliceExpr(ctx *blockCtx, v *ast.SliceExpr) { // x[i:j:k]
 	compileExpr(ctx, v.X)
 	compileExprOrNone(ctx, v.Low)
@@ -630,7 +645,7 @@ func compileLambdaExpr2(ctx *blockCtx, v *ast.LambdaExpr2, sig *types.Signature)
 func compileFuncLit(ctx *blockCtx, v *ast.FuncLit) {
 	cb := ctx.cb
 	comments, once := cb.BackupComments()
-	sig := toFuncType(ctx, v.Type, nil)
+	sig := toFuncType(ctx, v.Type, nil, nil)
 	fn := cb.NewClosureWith(sig)
 	if body := v.Body; body != nil {
 		loadFuncBody(ctx, fn, body)
@@ -668,12 +683,15 @@ const (
 	compositeLitKeyVal = 1
 )
 
-func checkCompositeLitElts(ctx *blockCtx, elts []ast.Expr) (kind int) {
+func checkCompositeLitElts(ctx *blockCtx, elts []ast.Expr, onlyStruct bool) (kind int) {
 	for _, elt := range elts {
 		if _, ok := elt.(*ast.KeyValueExpr); ok {
 			return compositeLitKeyVal
 		}
 	}
+	if len(elts) == 0 && onlyStruct {
+		return compositeLitKeyVal
+	}
 	return compositeLitVal
 }
 
@@ -778,7 +796,7 @@ func getUnderlying(ctx *blockCtx, typ types.Type) types.Type {
 func compileCompositeLit(ctx *blockCtx, v *ast.CompositeLit, expected types.Type, onlyStruct bool) {
 	var hasPtr bool
 	var typ, underlying types.Type
-	var kind = checkCompositeLitElts(ctx, v.Elts)
+	var kind = checkCompositeLitElts(ctx, v.Elts, onlyStruct)
 	if v.Type != nil {
 		typ = toType(ctx, v.Type)
 		underlying = getUnderlying(ctx, typ)
@@ -835,7 +853,11 @@ func compileSliceLit(ctx *blockCtx, v *ast.SliceLit, typ types.Type) {
 	for _, elt := range v.Elts {
 		compileExpr(ctx, elt)
 	}
-	ctx.cb.SliceLit(typ, n)
+	if sliceHasTypeParam(ctx, typ) {
+		ctx.cb.SliceLit(nil, n)
+	} else {
+		ctx.cb.SliceLit(typ, n)
+	}
 }
 
 func compileRangeExpr(ctx *blockCtx, v *ast.RangeExpr) {
@@ -1012,10 +1034,34 @@ func compileErrWrapExpr(ctx *blockCtx, v *ast.ErrWrapExpr, inFlags int) {
 	cb.Assign(n+1, 1)
 
 	cb.If().Val(err).CompareNil(gotoken.NEQ).Then()
+	if v.Default == nil {
+		pos := pkg.Fset.Position(v.Pos())
+		currentFunc := ctx.cb.Func().Ancestor()
+		const newFrameArgs = 5
+
+		currentFuncName := currentFunc.Name()
+		if currentFuncName == "" {
+			currentFuncName = "main"
+		}
+
+		currentFuncName = strings.Join([]string{currentFunc.Pkg().Name(), currentFuncName}, ".")
+
+		cb.
+			VarRef(err).
+			Val(pkg.Import(errorPkgPath).Ref("NewFrame")).
+			Val(err).
+			Val(sprintAst(pkg.Fset, v.X)).
+			Val(pos.Filename).
+			Val(pos.Line).
+			Val(currentFuncName).
+			Call(newFrameArgs).
+			Assign(1)
+	}
+
 	if v.Tok == token.NOT { // expr!
-		cb.Val(pkg.Builtin().Ref("panic")).Val(err).Call(1).EndStmt() // TODO: wrap err
+		cb.Val(pkg.Builtin().Ref("panic")).Val(err).Call(1).EndStmt()
 	} else if v.Default == nil { // expr?
-		cb.Val(err).ReturnErr(true) // TODO: wrap err & return err
+		cb.Val(err).ReturnErr(true)
 	} else { // expr?:val
 		compileExpr(ctx, v.Default)
 		cb.Return(1)
@@ -1026,4 +1072,14 @@ func compileErrWrapExpr(ctx *blockCtx, v *ast.ErrWrapExpr, inFlags int) {
 	}
 }
 
+func sprintAst(fset *token.FileSet, x ast.Node) string {
+	var buf bytes.Buffer
+	err := printer.Fprint(&buf, fset, x)
+	if err != nil {
+		panic("Unexpected error: " + err.Error())
+	}
+
+	return buf.String()
+}
+
 // -----------------------------------------------------------------------------
diff --git a/cl/func_type_and_var.go b/cl/func_type_and_var.go
index 277c163e..503b1053 100644
--- a/cl/func_type_and_var.go
+++ b/cl/func_type_and_var.go
@@ -31,26 +31,22 @@ import (
 
 // -----------------------------------------------------------------------------
 
-func toFuncType(ctx *blockCtx, typ *ast.FuncType, recv *types.Var) *types.Signature {
-	params, variadic := toParams(ctx, typ.Params.List)
-	results := toResults(ctx, typ.Results)
-	return types.NewSignature(recv, params, results, variadic)
-}
-
 func toRecv(ctx *blockCtx, recv *ast.FieldList) *types.Var {
 	v := recv.List[0]
 	var name string
 	if len(v.Names) > 0 {
 		name = v.Names[0].Name
 	}
-	return ctx.pkg.NewParam(v.Pos(), name, toType(ctx, v.Type))
+	typ, star := getRecvType(v.Type)
+	t := toType(ctx, typ)
+	if star {
+		t = types.NewPointer(t)
+	}
+	return ctx.pkg.NewParam(v.Pos(), name, t)
 }
 
 func getRecvTypeName(ctx *pkgCtx, recv *ast.FieldList, handleErr bool) (string, bool) {
-	typ := recv.List[0].Type
-	if t, ok := typ.(*ast.StarExpr); ok {
-		typ = t.X
-	}
+	typ, _ := getRecvType(recv.List[0].Type)
 	if t, ok := typ.(*ast.Ident); ok {
 		return t.Name, true
 	}
@@ -104,7 +100,28 @@ func toParam(ctx *blockCtx, fld *ast.Field, args []*gox.Param) []*gox.Param {
 func toType(ctx *blockCtx, typ ast.Expr) types.Type {
 	switch v := typ.(type) {
 	case *ast.Ident:
-		return toIdentType(ctx, v)
+		if enableTypeParams {
+			ctx.idents = append(ctx.idents, v)
+			defer func() {
+				ctx.idents = ctx.idents[:len(ctx.idents)-1]
+			}()
+		}
+		typ := toIdentType(ctx, v)
+		if enableTypeParams && ctx.inInst == 0 {
+			if t, ok := typ.(*types.Named); ok {
+				if namedIsTypeParams(ctx, t) {
+					pos := ctx.idents[0].Pos()
+					for _, i := range ctx.idents {
+						if i.Name == v.Name {
+							pos = i.Pos()
+							break
+						}
+					}
+					panic(ctx.newCodeErrorf(pos, "cannot use generic type %v without instantiation", t.Obj().Type()))
+				}
+			}
+		}
+		return typ
 	case *ast.StarExpr:
 		elem := toType(ctx, v.X)
 		return types.NewPointer(elem)
@@ -122,11 +139,27 @@ func toType(ctx *blockCtx, typ ast.Expr) types.Type {
 	case *ast.ChanType:
 		return toChanType(ctx, v)
 	case *ast.FuncType:
-		return toFuncType(ctx, v, nil)
+		return toFuncType(ctx, v, nil, nil)
 	case *ast.SelectorExpr:
-		return toExternalType(ctx, v)
+		typ := toExternalType(ctx, v)
+		if enableTypeParams && ctx.inInst == 0 {
+			if t, ok := typ.(*types.Named); ok {
+				if namedIsTypeParams(ctx, t) {
+					panic(ctx.newCodeErrorf(v.Pos(), "cannot use generic type %v without instantiation", t.Obj().Type()))
+				}
+			}
+		}
+		return typ
 	case *ast.ParenExpr:
 		return toType(ctx, v.X)
+	case *ast.BinaryExpr:
+		return toBinaryExprType(ctx, v)
+	case *ast.UnaryExpr:
+		return toUnaryExprType(ctx, v)
+	case *ast.IndexExpr:
+		return toIndexType(ctx, v)
+	case *ast.IndexListExpr:
+		return toIndexListType(ctx, v)
 	}
 	log.Panicln("toType: unknown -", reflect.TypeOf(typ))
 	return nil
@@ -166,6 +199,11 @@ Name context:
 // ---------------------------------------------------------------------------*/
 
 func toIdentType(ctx *blockCtx, ident *ast.Ident) types.Type {
+	if ctx.tlookup != nil {
+		if typ := ctx.tlookup.Lookup(ident.Name); typ != nil {
+			return typ
+		}
+	}
 	v, builtin := lookupType(ctx, ident.Name)
 	if isBuiltin(builtin) {
 		panic(ctx.newCodeErrorf(ident.Pos(), "use of builtin %s not in function call", ident.Name))
@@ -204,30 +242,41 @@ func lookupType(ctx *blockCtx, name string) (types.Object, types.Object) {
 	return o, o
 }
 
+type checkRedecl struct {
+	ctx   *blockCtx
+	names map[string]token.Pos
+}
+
+func newCheckRedecl() *checkRedecl {
+	p := &checkRedecl{names: make(map[string]token.Pos)}
+	return p
+}
+
+func (p *checkRedecl) chkRedecl(ctx *blockCtx, name string, pos token.Pos) bool {
+	if name == "_" {
+		return false
+	}
+	if opos, ok := p.names[name]; ok {
+		npos := ctx.Position(pos)
+		ctx.handleCodeErrorf(&npos, "%v redeclared\n\t%v other declaration of %v",
+			name, ctx.Position(opos), name)
+		return true
+	}
+	p.names[name] = pos
+	return false
+}
+
 func toStructType(ctx *blockCtx, v *ast.StructType) *types.Struct {
 	pkg := ctx.pkg.Types
 	fieldList := v.Fields.List
 	fields := make([]*types.Var, 0, len(fieldList))
 	tags := make([]string, 0, len(fieldList))
-	names := make(map[string]token.Pos)
-	chkRedecl := func(name string, pos token.Pos) bool {
-		if name == "_" {
-			return false
-		}
-		if opos, ok := names[name]; ok {
-			npos := ctx.Position(pos)
-			ctx.handleCodeErrorf(&npos, "%v redeclared\n\t%v other declaration of %v",
-				name, ctx.Position(opos), name)
-			return true
-		}
-		names[name] = pos
-		return false
-	}
+	chk := newCheckRedecl()
 	for _, field := range fieldList {
 		typ := toType(ctx, field.Type)
-		if field.Names == nil { // embedded
+		if len(field.Names) == 0 { // embedded
 			name := getTypeName(typ)
-			if chkRedecl(name, field.Type.Pos()) {
+			if chk.chkRedecl(ctx, name, field.Type.Pos()) {
 				continue
 			}
 			if t, ok := typ.(*types.Named); ok { // #1196: embedded type should ensure loaded
@@ -239,7 +288,7 @@ func toStructType(ctx *blockCtx, v *ast.StructType) *types.Struct {
 			continue
 		}
 		for _, name := range field.Names {
-			if chkRedecl(name.Name, name.NamePos) {
+			if chk.chkRedecl(ctx, name.Name, name.NamePos) {
 				continue
 			}
 			fld := types.NewField(token.NoPos, pkg, name.Name, typ, false)
@@ -318,7 +367,7 @@ func toInterfaceType(ctx *blockCtx, v *ast.InterfaceType) types.Type {
 	var methods []*types.Func
 	var embeddeds []types.Type
 	for _, m := range methodsList {
-		if m.Names == nil { // embedded
+		if len(m.Names) == 0 { // embedded
 			typ := toType(ctx, m.Type)
 			if t, ok := typ.(*types.Named); ok { // #1198: embedded type should ensure loaded
 				ctx.loadNamed(ctx.pkg, t)
@@ -327,13 +376,37 @@ func toInterfaceType(ctx *blockCtx, v *ast.InterfaceType) types.Type {
 			continue
 		}
 		name := m.Names[0].Name
-		sig := toFuncType(ctx, m.Type.(*ast.FuncType), nil)
+		sig := toFuncType(ctx, m.Type.(*ast.FuncType), nil, nil)
 		methods = append(methods, types.NewFunc(token.NoPos, pkg, name, sig))
 	}
 	intf := types.NewInterfaceType(methods, embeddeds).Complete()
 	return intf
 }
 
+func toIndexType(ctx *blockCtx, v *ast.IndexExpr) types.Type {
+	ctx.inInst++
+	defer func() {
+		ctx.inInst--
+	}()
+	ctx.cb.Typ(toType(ctx, v.X), v.X)
+	ctx.cb.Typ(toType(ctx, v.Index), v.Index)
+	ctx.cb.Index(1, false, v)
+	return ctx.cb.InternalStack().Pop().Type.(*gox.TypeType).Type()
+}
+
+func toIndexListType(ctx *blockCtx, v *ast.IndexListExpr) types.Type {
+	ctx.inInst++
+	defer func() {
+		ctx.inInst--
+	}()
+	ctx.cb.Typ(toType(ctx, v.X), v.X)
+	for _, index := range v.Indices {
+		ctx.cb.Typ(toType(ctx, index), index)
+	}
+	ctx.cb.Index(len(v.Indices), false, v)
+	return ctx.cb.InternalStack().Pop().Type.(*gox.TypeType).Type()
+}
+
 // -----------------------------------------------------------------------------
 
 func toString(l *ast.BasicLit) string {
diff --git a/cl/types_go117.go b/cl/types_go117.go
new file mode 100644
index 00000000..2f32f916
--- /dev/null
+++ b/cl/types_go117.go
@@ -0,0 +1,81 @@
+//go:build !go1.18
+// +build !go1.18
+
+/*
+ * Copyright (c) 2021 The GoPlus Authors (goplus.org). All rights reserved.
+ *
+ * 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
+
+import (
+	"go/types"
+
+	"github.com/goplus/gop/ast"
+)
+
+const enableTypeParams = false
+
+func toBinaryExprType(ctx *blockCtx, v *ast.BinaryExpr) types.Type {
+	panic("type parameters are unsupported at this go version")
+}
+
+func toUnaryExprType(ctx *blockCtx, v *ast.UnaryExpr) types.Type {
+	panic("type parameters are unsupported at this go version")
+}
+
+type typeParamLookup struct {
+}
+
+func (p *typeParamLookup) Lookup(name string) types.Type {
+	return nil
+}
+
+func toFuncType(ctx *blockCtx, typ *ast.FuncType, recv *types.Var, d *ast.FuncDecl) *types.Signature {
+	params, variadic := toParams(ctx, typ.Params.List)
+	results := toResults(ctx, typ.Results)
+	return types.NewSignature(recv, params, results, variadic)
+}
+
+func initType(ctx *blockCtx, named *types.Named, spec *ast.TypeSpec) {
+	typ := toType(ctx, spec.Type)
+	if named, ok := typ.(*types.Named); ok {
+		typ = getUnderlying(ctx, named)
+	}
+	named.SetUnderlying(typ)
+}
+
+func getRecvType(typ ast.Expr) (ast.Expr, bool) {
+	var ptr bool
+L:
+	for {
+		switch t := typ.(type) {
+		case *ast.ParenExpr:
+			typ = t.X
+		case *ast.StarExpr:
+			ptr = true
+			typ = t.X
+		default:
+			break L
+		}
+	}
+	return typ, ptr
+}
+
+func sliceHasTypeParam(ctx *blockCtx, typ types.Type) bool {
+	return false
+}
+
+func namedIsTypeParams(ctx *blockCtx, t *types.Named) bool {
+	return false
+}
diff --git a/cl/types_go118.go b/cl/types_go118.go
new file mode 100644
index 00000000..4c723913
--- /dev/null
+++ b/cl/types_go118.go
@@ -0,0 +1,305 @@
+//go:build go1.18
+// +build go1.18
+
+/*
+ * Copyright (c) 2022 The GoPlus Authors (goplus.org). All rights reserved.
+ *
+ * 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
+
+import (
+	"go/types"
+
+	"github.com/goplus/gop/ast"
+	"github.com/goplus/gop/token"
+)
+
+const enableTypeParams = true
+
+func toTermList(ctx *blockCtx, expr ast.Expr) []*types.Term {
+retry:
+	switch v := expr.(type) {
+	case *ast.UnaryExpr:
+		if v.Op != token.TILDE {
+			panic(ctx.newCodeErrorf(v.Pos(), "invalid op %v must ~", v.Op))
+		}
+		return []*types.Term{types.NewTerm(true, toType(ctx, v.X))}
+	case *ast.BinaryExpr:
+		if v.Op != token.OR {
+			panic(ctx.newCodeErrorf(v.Pos(), "invalid op %v must |", v.Op))
+		}
+		return append(toTermList(ctx, v.X), toTermList(ctx, v.Y)...)
+	case *ast.ParenExpr:
+		expr = v.X
+		goto retry
+	}
+	return []*types.Term{types.NewTerm(false, toType(ctx, expr))}
+}
+
+func toBinaryExprType(ctx *blockCtx, v *ast.BinaryExpr) types.Type {
+	return types.NewInterfaceType(nil, []types.Type{types.NewUnion(toTermList(ctx, v))})
+}
+
+func toUnaryExprType(ctx *blockCtx, v *ast.UnaryExpr) types.Type {
+	return types.NewInterfaceType(nil, []types.Type{types.NewUnion(toTermList(ctx, v))})
+}
+
+func toTypeParams(ctx *blockCtx, params *ast.FieldList) []*types.TypeParam {
+	if params == nil {
+		return nil
+	}
+	return collectTypeParams(ctx, params)
+}
+
+func recvTypeParams(ctx *blockCtx, typ ast.Expr, named *types.Named) (tparams []*types.TypeParam) {
+	orgTypeParams := named.TypeParams()
+	if orgTypeParams == nil {
+		return nil
+	}
+L:
+	for {
+		switch t := typ.(type) {
+		case *ast.ParenExpr:
+			typ = t.X
+		case *ast.StarExpr:
+			typ = t.X
+		default:
+			break L
+		}
+	}
+	switch t := typ.(type) {
+	case *ast.IndexExpr:
+		if 1 != orgTypeParams.Len() {
+			panic(ctx.newCodeErrorf(typ.Pos(), "got 1 type parameter, but receiver base type declares %v", orgTypeParams.Len()))
+		}
+		v := t.Index.(*ast.Ident)
+		tp := orgTypeParams.At(0)
+		obj := types.NewTypeName(v.Pos(), tp.Obj().Pkg(), v.Name, nil)
+		tparams = []*types.TypeParam{types.NewTypeParam(obj, tp.Constraint())}
+	case *ast.IndexListExpr:
+		n := len(t.Indices)
+		if n != orgTypeParams.Len() {
+			panic(ctx.newCodeErrorf(typ.Pos(), "got %v arguments but %v type parameters", n, orgTypeParams.Len()))
+		}
+		tparams = make([]*types.TypeParam, n)
+		for i := 0; i < n; i++ {
+			v := t.Indices[i].(*ast.Ident)
+			tp := orgTypeParams.At(i)
+			obj := types.NewTypeName(v.Pos(), tp.Obj().Pkg(), v.Name, nil)
+			tparams[i] = types.NewTypeParam(obj, tp.Constraint())
+		}
+	default:
+		panic(ctx.newCodeErrorf(typ.Pos(), "cannot use generic type %v without instantiation", named))
+	}
+	return
+}
+
+func toFuncType(ctx *blockCtx, typ *ast.FuncType, recv *types.Var, d *ast.FuncDecl) *types.Signature {
+	var typeParams []*types.TypeParam
+	if recv != nil && d.Recv != nil {
+		typ := recv.Type()
+		if pt, ok := typ.(*types.Pointer); ok {
+			typ = pt.Elem()
+		}
+		typeParams = recvTypeParams(ctx, d.Recv.List[0].Type, typ.(*types.Named))
+	} else {
+		typeParams = toTypeParams(ctx, typ.TypeParams)
+	}
+	if len(typeParams) > 0 {
+		ctx.tlookup = &typeParamLookup{typeParams}
+		defer func() {
+			ctx.tlookup = nil
+		}()
+	}
+	params, variadic := toParams(ctx, typ.Params.List)
+	results := toResults(ctx, typ.Results)
+	if recv != nil {
+		return types.NewSignatureType(recv, typeParams, nil, params, results, variadic)
+	}
+	return types.NewSignatureType(recv, nil, typeParams, params, results, variadic)
+}
+
+type typeParamLookup struct {
+	typeParams []*types.TypeParam
+}
+
+func (p *typeParamLookup) Lookup(name string) types.Type {
+	for _, t := range p.typeParams {
+		tname := t.Obj().Name()
+		if tname != "_" && name == tname {
+			return t
+		}
+	}
+	return nil
+}
+
+func initType(ctx *blockCtx, named *types.Named, spec *ast.TypeSpec) {
+	typeParams := toTypeParams(ctx, spec.TypeParams)
+	if len(typeParams) > 0 {
+		named.SetTypeParams(typeParams)
+		ctx.tlookup = &typeParamLookup{typeParams}
+		defer func() {
+			ctx.tlookup = nil
+		}()
+	}
+	org := ctx.inInst
+	ctx.inInst = 0
+	defer func() {
+		ctx.inInst = org
+	}()
+	typ := toType(ctx, spec.Type)
+	if named, ok := typ.(*types.Named); ok {
+		typ = getUnderlying(ctx, named)
+	}
+	named.SetUnderlying(typ)
+}
+
+func getRecvType(typ ast.Expr) (ast.Expr, bool) {
+	var ptr bool
+L:
+	for {
+		switch t := typ.(type) {
+		case *ast.ParenExpr:
+			typ = t.X
+		case *ast.StarExpr:
+			ptr = true
+			typ = t.X
+		default:
+			break L
+		}
+	}
+	switch t := typ.(type) {
+	case *ast.IndexExpr:
+		typ = t.X
+	case *ast.IndexListExpr:
+		typ = t.X
+	}
+	return typ, ptr
+}
+
+func collectTypeParams(ctx *blockCtx, list *ast.FieldList) []*types.TypeParam {
+	var tparams []*types.TypeParam
+	// Declare type parameters up-front, with empty interface as type bound.
+	// The scope of type parameters starts at the beginning of the type parameter
+	// list (so we can have mutually recursive parameterized interfaces).
+	for _, f := range list.List {
+		tparams = declareTypeParams(ctx, tparams, f.Names)
+	}
+
+	ctx.tlookup = &typeParamLookup{tparams}
+	defer func() {
+		ctx.tlookup = nil
+	}()
+
+	index := 0
+	for _, f := range list.List {
+		var bound types.Type
+		// NOTE: we may be able to assert that f.Type != nil here, but this is not
+		// an invariant of the AST, so we are cautious.
+		if f.Type != nil {
+			bound = boundTypeParam(ctx, f.Type)
+			if isTypeParam(bound) {
+				// We may be able to allow this since it is now well-defined what
+				// the underlying type and thus type set of a type parameter is.
+				// But we may need some additional form of cycle detection within
+				// type parameter lists.
+				//check.error(f.Type, MisplacedTypeParam, "cannot use a type parameter as constraint")
+				bound = types.Typ[types.Invalid]
+			} else if t, ok := bound.(*types.Named); ok {
+				if t.Underlying() == nil { // check named underlying is nil
+					ctx.loadNamed(ctx.pkg, t)
+				}
+			}
+		} else {
+			bound = types.Typ[types.Invalid]
+		}
+		for i := range f.Names {
+			tparams[index+i].SetConstraint(bound)
+		}
+		index += len(f.Names)
+	}
+	return tparams
+}
+
+func declareTypeParams(ctx *blockCtx, tparams []*types.TypeParam, names []*ast.Ident) []*types.TypeParam {
+	// Use Typ[Invalid] for the type constraint to ensure that a type
+	// is present even if the actual constraint has not been assigned
+	// yet.
+	// TODO(gri) Need to systematically review all uses of type parameter
+	//           constraints to make sure we don't rely on them if they
+	//           are not properly set yet.
+	for _, name := range names {
+		tname := types.NewTypeName(name.Pos(), ctx.pkg.Types, name.Name, nil)
+		tpar := types.NewTypeParam(tname, types.Typ[types.Invalid]) // assigns type to tpar as a side-effect
+		// check.declare(check.scope, name, tname, check.scope.pos)    // TODO(gri) check scope position
+		tparams = append(tparams, tpar)
+	}
+
+	return tparams
+}
+
+func isTypeParam(t types.Type) bool {
+	_, ok := t.(*types.TypeParam)
+	return ok
+}
+
+func sliceHasTypeParam(ctx *blockCtx, typ types.Type) bool {
+	if typ == nil {
+		return false
+	}
+	var t *types.Slice
+	switch tt := typ.(type) {
+	case *types.Named:
+		t = getUnderlying(ctx, tt).(*types.Slice)
+	case *types.Slice:
+		t = tt
+	}
+	_, ok := t.Elem().(*types.TypeParam)
+	return ok
+}
+
+func boundTypeParam(ctx *blockCtx, x ast.Expr) types.Type {
+	// A type set literal of the form ~T and A|B may only appear as constraint;
+	// embed it in an implicit interface so that only interface type-checking
+	// needs to take care of such type expressions.
+	wrap := false
+	switch op := x.(type) {
+	case *ast.UnaryExpr:
+		wrap = op.Op == token.TILDE
+	case *ast.BinaryExpr:
+		wrap = op.Op == token.OR
+	}
+	if wrap {
+		x = &ast.InterfaceType{Methods: &ast.FieldList{List: []*ast.Field{{Type: x}}}}
+		t := toType(ctx, x)
+		// mark t as implicit interface if all went well
+		if t, _ := t.(*types.Interface); t != nil {
+			t.MarkImplicit()
+		}
+		return t
+	}
+	return toType(ctx, x)
+}
+
+func namedIsTypeParams(ctx *blockCtx, t *types.Named) bool {
+	o := t.Obj()
+	if o.Pkg() == ctx.pkg.Types {
+		if _, ok := ctx.generics[o.Name()]; !ok {
+			return false
+		}
+		ctx.loadType(o.Name())
+	}
+	return t.Obj() != nil && t.TypeArgs() == nil && t.TypeParams() != nil
+}
diff --git a/cmd/gopfmt/gopfmt.go b/cmd/gopfmt/gopfmt.go
index e29bcb13..e627fc86 100644
--- a/cmd/gopfmt/gopfmt.go
+++ b/cmd/gopfmt/gopfmt.go
@@ -17,8 +17,11 @@
 package main
 
 import (
+	"bytes"
 	"flag"
 	"fmt"
+	"go/parser"
+	"go/token"
 	"io"
 	"io/fs"
 	"io/ioutil"
@@ -26,19 +29,16 @@ import (
 	"path/filepath"
 	"strings"
 
+	goformat "go/format"
+
+	"github.com/goplus/gop"
 	"github.com/goplus/gop/format"
 )
 
 var (
 	procCnt    = 0
 	walkSubDir = false
-	extGops    = map[string]struct{}{
-		".go":  {},
-		".gop": {},
-		".spx": {},
-		".gmx": {},
-	}
-	rootDir = ""
+	rootDir    = ""
 )
 
 var (
@@ -56,7 +56,7 @@ func report(err error) {
 	os.Exit(2)
 }
 
-func processFile(filename string, in io.Reader, out io.Writer) error {
+func processFile(filename string, class bool, in io.Reader, out io.Writer) error {
 	if in == nil {
 		var err error
 		in, err = os.Open(filename)
@@ -68,8 +68,22 @@ func processFile(filename string, in io.Reader, out io.Writer) error {
 	if err != nil {
 		return err
 	}
-
-	res, err := format.Source(src, filename)
+	var res []byte
+	if filepath.Ext(filename) == ".go" {
+		fset := token.NewFileSet()
+		f, err := parser.ParseFile(fset, filename, src, parser.ParseComments)
+		if err != nil {
+			return err
+		}
+		var buf bytes.Buffer
+		err = goformat.Node(&buf, fset, f)
+		if err != nil {
+			return err
+		}
+		res = buf.Bytes()
+	} else {
+		res, err = format.Source(src, class, filename)
+	}
 	if err != nil {
 		return err
 	}
@@ -99,7 +113,15 @@ func processFile(filename string, in io.Reader, out io.Writer) error {
 	return err
 }
 
-func walk(path string, d fs.DirEntry, err error) error {
+type walker struct {
+	dirMap map[string]func(ext string) (ok bool, class bool)
+}
+
+func newWalker() *walker {
+	return &walker{dirMap: make(map[string]func(ext string) (ok bool, class bool))}
+}
+
+func (w *walker) walk(path string, d fs.DirEntry, err error) error {
 	if err != nil {
 		fmt.Fprintln(os.Stderr, err)
 	} else if d.IsDir() {
@@ -108,10 +130,41 @@ func walk(path string, d fs.DirEntry, err error) error {
 		}
 	} else {
 		// Directories are walked, ignoring non-Gop files.
+		dir, _ := filepath.Split(path)
+		fn, ok := w.dirMap[dir]
+		if !ok {
+			if mod, err := gop.LoadMod(path, nil, &gop.Config{DontUpdateGoMod: true}); err == nil {
+				fn = func(ext string) (ok bool, class bool) {
+					switch ext {
+					case ".go", ".gop":
+						ok = true
+					case ".gopx", ".spx", ".gmx":
+						ok, class = true, true
+					default:
+						_, class = mod.IsClass(ext)
+						if class {
+							ok = true
+						}
+					}
+					return
+				}
+			} else {
+				fn = func(ext string) (ok bool, class bool) {
+					switch ext {
+					case ".go", ".gop":
+						ok = true
+					case ".gopx", ".spx", ".gmx":
+						ok, class = true, true
+					}
+					return
+				}
+			}
+			w.dirMap[dir] = fn
+		}
 		ext := filepath.Ext(path)
-		if _, ok := extGops[ext]; ok {
+		if ok, class := fn(ext); ok {
 			procCnt++
-			if err = processFile(path, nil, os.Stdout); err != nil {
+			if err = processFile(path, class, nil, os.Stdout); err != nil {
 				report(err)
 			}
 		}
@@ -129,12 +182,12 @@ func main() {
 			report(fmt.Errorf("error: cannot use -w with standard input"))
 			return
 		}
-		if err := processFile("<standard input>", os.Stdin, os.Stdout); err != nil {
+		if err := processFile("<standard input>", false, os.Stdin, os.Stdout); err != nil {
 			report(err)
 		}
 		return
 	}
-
+	walker := newWalker()
 	for _, path := range args {
 		walkSubDir = strings.HasSuffix(path, "/...")
 		if walkSubDir {
@@ -142,7 +195,7 @@ func main() {
 		}
 		procCnt = 0
 		rootDir = path
-		filepath.WalkDir(path, walk)
+		filepath.WalkDir(path, walker.walk)
 		if procCnt == 0 {
 			fmt.Println("no Go+ files in", path)
 		}
diff --git a/cmd/internal/gopfmt/fmt.go b/cmd/internal/gopfmt/fmt.go
index e721b740..41a1e5bb 100644
--- a/cmd/internal/gopfmt/fmt.go
+++ b/cmd/internal/gopfmt/fmt.go
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-// Package gopfmt implements the ``gop fmt'' command.
+// Package gopfmt implements the “gop fmt” command.
 package gopfmt
 
 import (
@@ -27,9 +27,15 @@ import (
 	"path/filepath"
 	"strings"
 
+	"github.com/goplus/gop"
+
 	"github.com/goplus/gop/cmd/internal/base"
 	"github.com/goplus/gop/format"
 
+	goformat "go/format"
+	"go/parser"
+	"go/token"
+
 	xformat "github.com/goplus/gop/x/format"
 )
 
@@ -53,16 +59,10 @@ func init() {
 var (
 	procCnt    = 0
 	walkSubDir = false
-	extGops    = map[string]struct{}{
-		".go":  {},
-		".gop": {},
-		".spx": {},
-		".gmx": {},
-	}
-	rootDir = ""
+	rootDir    = ""
 )
 
-func gopfmt(path string, smart, mvgo bool) (err error) {
+func gopfmt(path string, class, smart, mvgo bool) (err error) {
 	src, err := ioutil.ReadFile(path)
 	if err != nil {
 		return
@@ -71,7 +71,21 @@ func gopfmt(path string, smart, mvgo bool) (err error) {
 	if smart {
 		target, err = xformat.GopstyleSource(src, path)
 	} else {
-		target, err = format.Source(src, path)
+		if !mvgo && filepath.Ext(path) == ".go" {
+			fset := token.NewFileSet()
+			f, err := parser.ParseFile(fset, path, src, parser.ParseComments)
+			if err != nil {
+				return err
+			}
+			var buf bytes.Buffer
+			err = goformat.Node(&buf, fset, f)
+			if err != nil {
+				return err
+			}
+			target = buf.Bytes()
+		} else {
+			target, err = format.Source(src, class, path)
+		}
 	}
 	if err != nil {
 		return
@@ -109,7 +123,15 @@ func writeFileWithBackup(path string, target []byte) (err error) {
 	return os.Rename(tmpfile, path)
 }
 
-func walk(path string, d fs.DirEntry, err error) error {
+type walker struct {
+	dirMap map[string]func(ext string) (ok, class bool)
+}
+
+func newWalker() *walker {
+	return &walker{dirMap: make(map[string]func(ext string) (ok, class bool))}
+}
+
+func (w *walker) walk(path string, d fs.DirEntry, err error) error {
 	if err != nil {
 		fmt.Fprintln(os.Stderr, err)
 	} else if d.IsDir() {
@@ -117,15 +139,46 @@ func walk(path string, d fs.DirEntry, err error) error {
 			return filepath.SkipDir
 		}
 	} else {
+		dir, _ := filepath.Split(path)
+		fn, ok := w.dirMap[dir]
+		if !ok {
+			if mod, err := gop.LoadMod(path, nil, &gop.Config{DontUpdateGoMod: true}); err == nil {
+				fn = func(ext string) (ok bool, class bool) {
+					switch ext {
+					case ".go", ".gop":
+						ok = true
+					case ".gopx", ".spx", ".gmx":
+						ok, class = true, true
+					default:
+						_, class = mod.IsClass(ext)
+						if class {
+							ok = true
+						}
+					}
+					return
+				}
+			} else {
+				fn = func(ext string) (ok bool, class bool) {
+					switch ext {
+					case ".go", ".gop":
+						ok = true
+					case ".gopx", ".spx", ".gmx":
+						ok, class = true, true
+					}
+					return
+				}
+			}
+			w.dirMap[dir] = fn
+		}
 		ext := filepath.Ext(path)
 		smart := *flagSmart
 		mvgo := smart && *flagMoveGo
-		if _, ok := extGops[ext]; ok && (!mvgo || ext == ".go") {
+		if ok, class := fn(ext); ok && (!mvgo || ext == ".go") {
 			procCnt++
 			if *flagNotExec {
 				fmt.Println("gop fmt", path)
 			} else {
-				err = gopfmt(path, smart && (mvgo || ext != ".go"), mvgo)
+				err = gopfmt(path, class, smart && (mvgo || ext != ".go"), mvgo)
 				if err != nil {
 					report(err)
 				}
@@ -149,6 +202,7 @@ func runCmd(cmd *base.Command, args []string) {
 	if narg < 1 {
 		cmd.Usage(os.Stderr)
 	}
+	walker := newWalker()
 	for i := 0; i < narg; i++ {
 		path := flag.Arg(i)
 		walkSubDir = strings.HasSuffix(path, "/...")
@@ -157,7 +211,7 @@ func runCmd(cmd *base.Command, args []string) {
 		}
 		procCnt = 0
 		rootDir = path
-		filepath.WalkDir(path, walk)
+		filepath.WalkDir(path, walker.walk)
 		if procCnt == 0 {
 			fmt.Println("no Go+ files in", path)
 		}
diff --git a/doc/goplus-vs-go.md b/doc/goplus-vs-go.md
index 6aca7d2f..1fdddaf9 100644
--- a/doc/goplus-vs-go.md
+++ b/doc/goplus-vs-go.md
@@ -14,3 +14,7 @@ The Python language appeared very early (1991), but its design idea is very far-
 Python is very popular today, but it's hard to call it an engineering language. Most people just take it as a tool for rapid prototyping.
 
 Is it possible to minimize the complexity of engineering and present it in a low-code form? Go+ hopes to explore a new path at this point.
+
+### Give a Star! ⭐
+
+If you like or are using Go+ to learn or start your projects, please give it a star. Thanks!
diff --git a/format/format.go b/format/format.go
index cae090ae..77d8075e 100644
--- a/format/format.go
+++ b/format/format.go
@@ -50,7 +50,6 @@ const parserMode = parser.ParseComments
 //
 // The function may return early (before the entire result is written)
 // and return a formatting error, for instance due to an incorrect AST.
-//
 func Node(dst io.Writer, fset *token.FileSet, node interface{}) error {
 	// Determine if we have a complete source file (file != nil).
 	var file *ast.File
@@ -101,14 +100,13 @@ func Node(dst io.Writer, fset *token.FileSet, node interface{}) error {
 // is applied to the result (such that it has the same leading and trailing
 // space as src), and the result is indented by the same amount as the first
 // line of src containing code. Imports are not sorted for partial source files.
-//
-func Source(src []byte, filename ...string) ([]byte, error) {
+func Source(src []byte, class bool, filename ...string) ([]byte, error) {
 	var fname string
 	if filename != nil {
 		fname = filename[0]
 	}
 	fset := token.NewFileSet()
-	file, sourceAdj, indentAdj, err := parse(fset, fname, src, true)
+	file, sourceAdj, indentAdj, err := parse(fset, fname, src, class, true)
 	if err != nil {
 		return nil, err
 	}
diff --git a/format/internal.go b/format/internal.go
index 9802b9e0..ded55ece 100644
--- a/format/internal.go
+++ b/format/internal.go
@@ -32,13 +32,17 @@ import (
 
 // parse parses src, which was read from the named file,
 // as a Go source file, declaration, or statement list.
-func parse(fset *token.FileSet, filename string, src []byte, fragmentOk bool) (
+func parse(fset *token.FileSet, filename string, src []byte, class, fragmentOk bool) (
 	file *ast.File,
 	sourceAdj func(src []byte, indent int) []byte,
 	indentAdj int,
 	err error,
 ) {
-	file, err = parser.ParseFile(fset, filename, src, parserMode)
+	mode := parserMode
+	if class {
+		mode |= parser.ParseGoPlusClass
+	}
+	file, err = parser.ParseFile(fset, filename, src, mode)
 	return
 }
 
diff --git a/go.mod b/go.mod
index b57d9558..fad1c8e7 100644
--- a/go.mod
+++ b/go.mod
@@ -3,9 +3,9 @@ module github.com/goplus/gop
 go 1.16
 
 require (
-	github.com/goplus/c2go v0.7.10
-	github.com/goplus/gox v1.11.21
-	github.com/goplus/libc v0.3.12
+	github.com/goplus/c2go v0.7.13
+	github.com/goplus/gox v1.11.35
+	github.com/goplus/libc v0.3.13
 	github.com/goplus/mod v0.9.12
 	github.com/qiniu/x v1.11.9
 )
diff --git a/go.sum b/go.sum
index c5362add..0ff584da 100644
--- a/go.sum
+++ b/go.sum
@@ -2,12 +2,15 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/goplus/c2go v0.7.10 h1:ov8ubqk39iVPnFNmw6GMIvIVVWFqaOEgd4Olk81gcrE=
 github.com/goplus/c2go v0.7.10/go.mod h1:ryOieVZqh0jmlPp45AMh9/sXIreTnyjDREP0EMX+pF0=
-github.com/goplus/gox v1.11.21 h1:zxWg7kZRDnLSxJZ4G8XPIlbvdGugrB3jgRb+MzV296o=
+github.com/goplus/c2go v0.7.13 h1:188dkHVVS3Qn/cwFtFwNgvlgjY2tBrZRjugv8icolt4=
+github.com/goplus/c2go v0.7.13/go.mod h1:s5NULWzUKi3erd3l4ahvpq+3qDGOvMjGb00a5o3h6zk=
 github.com/goplus/gox v1.11.21/go.mod h1:wRCRSNukie4cDqADF4w0Btc2Gk6V3p3V6hI5+rsVqa8=
-github.com/goplus/libc v0.3.12 h1:A0OeavtivMwHHo4te2GMg3OpLrTRc7T/t5sFCLjVIhU=
-github.com/goplus/libc v0.3.12/go.mod h1:xqG4/g3ilKBE/UDn5vkaE7RRQPQPyspj7ecuMuvlQJ8=
+github.com/goplus/gox v1.11.32/go.mod h1:hdKq5ghywtKWnBJNQNVBkPITmWCqCFRwwd2LTYTfg2U=
+github.com/goplus/gox v1.11.35 h1:IZtsYhHyjClJx5IRBbdYXCtiVoaoX4koYyv+/t7rXXA=
+github.com/goplus/gox v1.11.35/go.mod h1:GcmPyIyC2t4Nfam7x6YRpe1IW9rf4nchgRpE19hF4L4=
+github.com/goplus/libc v0.3.13 h1:2yHG8ghezBTK3Da7Fw1lNiitmt+Va+RzwKJ5GuqypCQ=
+github.com/goplus/libc v0.3.13/go.mod h1:xqG4/g3ilKBE/UDn5vkaE7RRQPQPyspj7ecuMuvlQJ8=
 github.com/goplus/mod v0.9.12 h1:CjgBGQIYqUTPGl3MrAS5CICzJwxbIfSa4OlEb141Gs4=
 github.com/goplus/mod v0.9.12/go.mod h1:YoPIowz71rnLLROA4YG0AC8bzDtPRyMaQwgTRLr8ri4=
 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
@@ -26,28 +29,61 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
 github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
+golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
+golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
+golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
+golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0=
 golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
+golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
+golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.1.11 h1:loJ25fNOEhSXfHrpoGj91eCUThwdNX6u24rO1xnNteY=
 golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
+golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg=
+golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/load.go b/load.go
index b0283adb..ad568952 100644
--- a/load.go
+++ b/load.go
@@ -51,10 +51,10 @@ func NotFound(err error) bool {
 	return errors.Err(err) == syscall.ENOENT
 }
 
-func loadMod(dir string, gop *env.Gop, conf *Config) (mod *gopmod.Module, err error) {
+func LoadMod(dir string, gop *env.Gop, conf *Config) (mod *gopmod.Module, err error) {
 	mod, err = gopmod.Load(dir, 0)
 	if err != nil && !NotFound(err) {
-		err = errors.NewWith(err, `gopmod.Load(dir, 0`, -2, "gopmod.Load", dir, 0)
+		err = errors.NewWith(err, `gopmod.Load(dir, 0)`, -2, "gopmod.Load", dir, 0)
 		return
 	}
 	if mod != nil {
@@ -101,7 +101,7 @@ func LoadDir(dir string, conf *Config, genTestPkg bool, promptGenGo ...bool) (ou
 	if gop == nil {
 		gop = gopenv.Get()
 	}
-	mod, err := loadMod(dir, gop, conf)
+	mod, err := LoadMod(dir, gop, conf)
 	if err != nil {
 		return
 	}
@@ -177,7 +177,7 @@ func LoadFiles(files []string, conf *Config) (out *gox.Package, err error) {
 	if gop == nil {
 		gop = gopenv.Get()
 	}
-	mod, err := loadMod("", gop, conf)
+	mod, err := LoadMod("", gop, conf)
 	if err != nil {
 		err = errors.NewWith(err, `loadMod("", gop, conf)`, -2, "gop.loadMod", "", gop, conf)
 		return
diff --git a/parser/_instance/instance1/cmd.gop b/parser/_instance/instance1/cmd.gop
new file mode 100644
index 00000000..32596eb3
--- /dev/null
+++ b/parser/_instance/instance1/cmd.gop
@@ -0,0 +1,22 @@
+type T1 P1[int]
+
+type T2 P2[int, string]
+
+type T3 *P1[P2[int, P1[int]]]
+
+type S1 struct {
+	v1 P1[int]
+	v2 P2[int, string]
+}
+
+type S2 struct {
+	P1[int]
+}
+
+type A1 []P1[int]
+
+type A2 [2]P2[int, string]
+
+type M1 map[P1[int]]P2[int, string]
+
+type C1 chan P1[int]
diff --git a/parser/_instance/instance1/parser.expect b/parser/_instance/instance1/parser.expect
new file mode 100644
index 00000000..8293981f
--- /dev/null
+++ b/parser/_instance/instance1/parser.expect
@@ -0,0 +1,207 @@
+package main
+
+file cmd.gop
+ast.GenDecl:
+  Tok: type
+  Specs:
+    ast.TypeSpec:
+      Name:
+        ast.Ident:
+          Name: T1
+      Type:
+        ast.IndexExpr:
+          X:
+            ast.Ident:
+              Name: P1
+          Index:
+            ast.Ident:
+              Name: int
+ast.GenDecl:
+  Tok: type
+  Specs:
+    ast.TypeSpec:
+      Name:
+        ast.Ident:
+          Name: T2
+      Type:
+        ast.IndexListExpr:
+          X:
+            ast.Ident:
+              Name: P2
+          Indices:
+            ast.Ident:
+              Name: int
+            ast.Ident:
+              Name: string
+ast.GenDecl:
+  Tok: type
+  Specs:
+    ast.TypeSpec:
+      Name:
+        ast.Ident:
+          Name: T3
+      Type:
+        ast.StarExpr:
+          X:
+            ast.IndexExpr:
+              X:
+                ast.Ident:
+                  Name: P1
+              Index:
+                ast.IndexListExpr:
+                  X:
+                    ast.Ident:
+                      Name: P2
+                  Indices:
+                    ast.Ident:
+                      Name: int
+                    ast.IndexExpr:
+                      X:
+                        ast.Ident:
+                          Name: P1
+                      Index:
+                        ast.Ident:
+                          Name: int
+ast.GenDecl:
+  Tok: type
+  Specs:
+    ast.TypeSpec:
+      Name:
+        ast.Ident:
+          Name: S1
+      Type:
+        ast.StructType:
+          Fields:
+            ast.FieldList:
+              List:
+                ast.Field:
+                  Names:
+                    ast.Ident:
+                      Name: v1
+                  Type:
+                    ast.IndexExpr:
+                      X:
+                        ast.Ident:
+                          Name: P1
+                      Index:
+                        ast.Ident:
+                          Name: int
+                ast.Field:
+                  Names:
+                    ast.Ident:
+                      Name: v2
+                  Type:
+                    ast.IndexListExpr:
+                      X:
+                        ast.Ident:
+                          Name: P2
+                      Indices:
+                        ast.Ident:
+                          Name: int
+                        ast.Ident:
+                          Name: string
+ast.GenDecl:
+  Tok: type
+  Specs:
+    ast.TypeSpec:
+      Name:
+        ast.Ident:
+          Name: S2
+      Type:
+        ast.StructType:
+          Fields:
+            ast.FieldList:
+              List:
+                ast.Field:
+                  Type:
+                    ast.IndexExpr:
+                      X:
+                        ast.Ident:
+                          Name: P1
+                      Index:
+                        ast.Ident:
+                          Name: int
+ast.GenDecl:
+  Tok: type
+  Specs:
+    ast.TypeSpec:
+      Name:
+        ast.Ident:
+          Name: A1
+      Type:
+        ast.ArrayType:
+          Elt:
+            ast.IndexExpr:
+              X:
+                ast.Ident:
+                  Name: P1
+              Index:
+                ast.Ident:
+                  Name: int
+ast.GenDecl:
+  Tok: type
+  Specs:
+    ast.TypeSpec:
+      Name:
+        ast.Ident:
+          Name: A2
+      Type:
+        ast.ArrayType:
+          Len:
+            ast.BasicLit:
+              Kind: INT
+              Value: 2
+          Elt:
+            ast.IndexListExpr:
+              X:
+                ast.Ident:
+                  Name: P2
+              Indices:
+                ast.Ident:
+                  Name: int
+                ast.Ident:
+                  Name: string
+ast.GenDecl:
+  Tok: type
+  Specs:
+    ast.TypeSpec:
+      Name:
+        ast.Ident:
+          Name: M1
+      Type:
+        ast.MapType:
+          Key:
+            ast.IndexExpr:
+              X:
+                ast.Ident:
+                  Name: P1
+              Index:
+                ast.Ident:
+                  Name: int
+          Value:
+            ast.IndexListExpr:
+              X:
+                ast.Ident:
+                  Name: P2
+              Indices:
+                ast.Ident:
+                  Name: int
+                ast.Ident:
+                  Name: string
+ast.GenDecl:
+  Tok: type
+  Specs:
+    ast.TypeSpec:
+      Name:
+        ast.Ident:
+          Name: C1
+      Type:
+        ast.ChanType:
+          Value:
+            ast.IndexExpr:
+              X:
+                ast.Ident:
+                  Name: P1
+              Index:
+                ast.Ident:
+                  Name: int
diff --git a/parser/_instance/instance2/cmd.gop b/parser/_instance/instance2/cmd.gop
new file mode 100644
index 00000000..0da552ae
--- /dev/null
+++ b/parser/_instance/instance2/cmd.gop
@@ -0,0 +1,16 @@
+func fn1(P1[int], P2[int, string])
+
+func fn2(p1 P1[int], p2 P2[int, string])
+
+func fn3(p1 P1[int], p2 ...P2[int, string])
+
+func fn4(p1 []P1[int], p3 []P2[int, string]) *P1[P2[int, P1[int]]]
+
+func fn5(p1 []P1[int], p2 [2]P2[int, string])
+
+func fn6(p1 chan P1[int], p2 map[P1[int]]P2[int, string])
+
+func fn7(p1 struct {
+	v1 P1[int]
+	v2 P2[int, string]
+})
diff --git a/parser/_instance/instance2/parser.expect b/parser/_instance/instance2/parser.expect
new file mode 100644
index 00000000..681c44e9
--- /dev/null
+++ b/parser/_instance/instance2/parser.expect
@@ -0,0 +1,303 @@
+package main
+
+file cmd.gop
+ast.FuncDecl:
+  Name:
+    ast.Ident:
+      Name: fn1
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+          List:
+            ast.Field:
+              Type:
+                ast.IndexExpr:
+                  X:
+                    ast.Ident:
+                      Name: P1
+                  Index:
+                    ast.Ident:
+                      Name: int
+            ast.Field:
+              Type:
+                ast.IndexListExpr:
+                  X:
+                    ast.Ident:
+                      Name: P2
+                  Indices:
+                    ast.Ident:
+                      Name: int
+                    ast.Ident:
+                      Name: string
+ast.FuncDecl:
+  Name:
+    ast.Ident:
+      Name: fn2
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+          List:
+            ast.Field:
+              Names:
+                ast.Ident:
+                  Name: p1
+              Type:
+                ast.IndexExpr:
+                  X:
+                    ast.Ident:
+                      Name: P1
+                  Index:
+                    ast.Ident:
+                      Name: int
+            ast.Field:
+              Names:
+                ast.Ident:
+                  Name: p2
+              Type:
+                ast.IndexListExpr:
+                  X:
+                    ast.Ident:
+                      Name: P2
+                  Indices:
+                    ast.Ident:
+                      Name: int
+                    ast.Ident:
+                      Name: string
+ast.FuncDecl:
+  Name:
+    ast.Ident:
+      Name: fn3
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+          List:
+            ast.Field:
+              Names:
+                ast.Ident:
+                  Name: p1
+              Type:
+                ast.IndexExpr:
+                  X:
+                    ast.Ident:
+                      Name: P1
+                  Index:
+                    ast.Ident:
+                      Name: int
+            ast.Field:
+              Names:
+                ast.Ident:
+                  Name: p2
+              Type:
+                ast.Ellipsis:
+                  Elt:
+                    ast.IndexListExpr:
+                      X:
+                        ast.Ident:
+                          Name: P2
+                      Indices:
+                        ast.Ident:
+                          Name: int
+                        ast.Ident:
+                          Name: string
+ast.FuncDecl:
+  Name:
+    ast.Ident:
+      Name: fn4
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+          List:
+            ast.Field:
+              Names:
+                ast.Ident:
+                  Name: p1
+              Type:
+                ast.ArrayType:
+                  Elt:
+                    ast.IndexExpr:
+                      X:
+                        ast.Ident:
+                          Name: P1
+                      Index:
+                        ast.Ident:
+                          Name: int
+            ast.Field:
+              Names:
+                ast.Ident:
+                  Name: p3
+              Type:
+                ast.ArrayType:
+                  Elt:
+                    ast.IndexListExpr:
+                      X:
+                        ast.Ident:
+                          Name: P2
+                      Indices:
+                        ast.Ident:
+                          Name: int
+                        ast.Ident:
+                          Name: string
+      Results:
+        ast.FieldList:
+          List:
+            ast.Field:
+              Type:
+                ast.StarExpr:
+                  X:
+                    ast.IndexExpr:
+                      X:
+                        ast.Ident:
+                          Name: P1
+                      Index:
+                        ast.IndexListExpr:
+                          X:
+                            ast.Ident:
+                              Name: P2
+                          Indices:
+                            ast.Ident:
+                              Name: int
+                            ast.IndexExpr:
+                              X:
+                                ast.Ident:
+                                  Name: P1
+                              Index:
+                                ast.Ident:
+                                  Name: int
+ast.FuncDecl:
+  Name:
+    ast.Ident:
+      Name: fn5
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+          List:
+            ast.Field:
+              Names:
+                ast.Ident:
+                  Name: p1
+              Type:
+                ast.ArrayType:
+                  Elt:
+                    ast.IndexExpr:
+                      X:
+                        ast.Ident:
+                          Name: P1
+                      Index:
+                        ast.Ident:
+                          Name: int
+            ast.Field:
+              Names:
+                ast.Ident:
+                  Name: p2
+              Type:
+                ast.ArrayType:
+                  Len:
+                    ast.BasicLit:
+                      Kind: INT
+                      Value: 2
+                  Elt:
+                    ast.IndexListExpr:
+                      X:
+                        ast.Ident:
+                          Name: P2
+                      Indices:
+                        ast.Ident:
+                          Name: int
+                        ast.Ident:
+                          Name: string
+ast.FuncDecl:
+  Name:
+    ast.Ident:
+      Name: fn6
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+          List:
+            ast.Field:
+              Names:
+                ast.Ident:
+                  Name: p1
+              Type:
+                ast.ChanType:
+                  Value:
+                    ast.IndexExpr:
+                      X:
+                        ast.Ident:
+                          Name: P1
+                      Index:
+                        ast.Ident:
+                          Name: int
+            ast.Field:
+              Names:
+                ast.Ident:
+                  Name: p2
+              Type:
+                ast.MapType:
+                  Key:
+                    ast.IndexExpr:
+                      X:
+                        ast.Ident:
+                          Name: P1
+                      Index:
+                        ast.Ident:
+                          Name: int
+                  Value:
+                    ast.IndexListExpr:
+                      X:
+                        ast.Ident:
+                          Name: P2
+                      Indices:
+                        ast.Ident:
+                          Name: int
+                        ast.Ident:
+                          Name: string
+ast.FuncDecl:
+  Name:
+    ast.Ident:
+      Name: fn7
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+          List:
+            ast.Field:
+              Names:
+                ast.Ident:
+                  Name: p1
+              Type:
+                ast.StructType:
+                  Fields:
+                    ast.FieldList:
+                      List:
+                        ast.Field:
+                          Names:
+                            ast.Ident:
+                              Name: v1
+                          Type:
+                            ast.IndexExpr:
+                              X:
+                                ast.Ident:
+                                  Name: P1
+                              Index:
+                                ast.Ident:
+                                  Name: int
+                        ast.Field:
+                          Names:
+                            ast.Ident:
+                              Name: v2
+                          Type:
+                            ast.IndexListExpr:
+                              X:
+                                ast.Ident:
+                                  Name: P2
+                              Indices:
+                                ast.Ident:
+                                  Name: int
+                                ast.Ident:
+                                  Name: string
diff --git a/parser/_instance/instance3/cmd.gop b/parser/_instance/instance3/cmd.gop
new file mode 100644
index 00000000..923ecb18
--- /dev/null
+++ b/parser/_instance/instance3/cmd.gop
@@ -0,0 +1,22 @@
+println(P1[int]{}, P2[int, string]{})
+println((*P1[int])(nil), (*P2[int, string])(nil))
+
+println P1[int]{}, P2[int, string]{}
+println (*P1[int])(nil), (*P2[int, string])(nil)
+
+func(x P1[int], y *P2[int, string]) *int {
+	return nil
+}(P1[int]{1}, &P2[int, string]{})
+
+foo(=> P1[int]{1})
+foo(=> {
+	println(&P2[int, string]{})
+})
+
+fn1[int]([]P1[int]{P1[int]{0}, P1[int]{1}}, &P2[int, string]{})
+fn2[int, string](&P1[int]{}, &P2[int, string]{})
+
+foo1[int](=> P1[int]{1})
+foo2[int, string](=> {
+	println(&P2[int, string]{})
+})
diff --git a/parser/_instance/instance3/parser.expect b/parser/_instance/instance3/parser.expect
new file mode 100644
index 00000000..53e872cb
--- /dev/null
+++ b/parser/_instance/instance3/parser.expect
@@ -0,0 +1,457 @@
+package main
+
+file cmd.gop
+noEntrypoint
+ast.FuncDecl:
+  Name:
+    ast.Ident:
+      Name: main
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+  Body:
+    ast.BlockStmt:
+      List:
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: println
+              Args:
+                ast.CompositeLit:
+                  Type:
+                    ast.IndexExpr:
+                      X:
+                        ast.Ident:
+                          Name: P1
+                      Index:
+                        ast.Ident:
+                          Name: int
+                ast.CompositeLit:
+                  Type:
+                    ast.IndexListExpr:
+                      X:
+                        ast.Ident:
+                          Name: P2
+                      Indices:
+                        ast.Ident:
+                          Name: int
+                        ast.Ident:
+                          Name: string
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: println
+              Args:
+                ast.CallExpr:
+                  Fun:
+                    ast.ParenExpr:
+                      X:
+                        ast.StarExpr:
+                          X:
+                            ast.IndexExpr:
+                              X:
+                                ast.Ident:
+                                  Name: P1
+                              Index:
+                                ast.Ident:
+                                  Name: int
+                  Args:
+                    ast.Ident:
+                      Name: nil
+                ast.CallExpr:
+                  Fun:
+                    ast.ParenExpr:
+                      X:
+                        ast.StarExpr:
+                          X:
+                            ast.IndexListExpr:
+                              X:
+                                ast.Ident:
+                                  Name: P2
+                              Indices:
+                                ast.Ident:
+                                  Name: int
+                                ast.Ident:
+                                  Name: string
+                  Args:
+                    ast.Ident:
+                      Name: nil
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: println
+              Args:
+                ast.CompositeLit:
+                  Type:
+                    ast.IndexExpr:
+                      X:
+                        ast.Ident:
+                          Name: P1
+                      Index:
+                        ast.Ident:
+                          Name: int
+                ast.CompositeLit:
+                  Type:
+                    ast.IndexListExpr:
+                      X:
+                        ast.Ident:
+                          Name: P2
+                      Indices:
+                        ast.Ident:
+                          Name: int
+                        ast.Ident:
+                          Name: string
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: println
+              Args:
+                ast.CallExpr:
+                  Fun:
+                    ast.ParenExpr:
+                      X:
+                        ast.StarExpr:
+                          X:
+                            ast.IndexExpr:
+                              X:
+                                ast.Ident:
+                                  Name: P1
+                              Index:
+                                ast.Ident:
+                                  Name: int
+                  Args:
+                    ast.Ident:
+                      Name: nil
+                ast.CallExpr:
+                  Fun:
+                    ast.ParenExpr:
+                      X:
+                        ast.StarExpr:
+                          X:
+                            ast.IndexListExpr:
+                              X:
+                                ast.Ident:
+                                  Name: P2
+                              Indices:
+                                ast.Ident:
+                                  Name: int
+                                ast.Ident:
+                                  Name: string
+                  Args:
+                    ast.Ident:
+                      Name: nil
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.FuncLit:
+                  Type:
+                    ast.FuncType:
+                      Params:
+                        ast.FieldList:
+                          List:
+                            ast.Field:
+                              Names:
+                                ast.Ident:
+                                  Name: x
+                              Type:
+                                ast.IndexExpr:
+                                  X:
+                                    ast.Ident:
+                                      Name: P1
+                                  Index:
+                                    ast.Ident:
+                                      Name: int
+                            ast.Field:
+                              Names:
+                                ast.Ident:
+                                  Name: y
+                              Type:
+                                ast.StarExpr:
+                                  X:
+                                    ast.IndexListExpr:
+                                      X:
+                                        ast.Ident:
+                                          Name: P2
+                                      Indices:
+                                        ast.Ident:
+                                          Name: int
+                                        ast.Ident:
+                                          Name: string
+                      Results:
+                        ast.FieldList:
+                          List:
+                            ast.Field:
+                              Type:
+                                ast.StarExpr:
+                                  X:
+                                    ast.Ident:
+                                      Name: int
+                  Body:
+                    ast.BlockStmt:
+                      List:
+                        ast.ReturnStmt:
+                          Results:
+                            ast.Ident:
+                              Name: nil
+              Args:
+                ast.CompositeLit:
+                  Type:
+                    ast.IndexExpr:
+                      X:
+                        ast.Ident:
+                          Name: P1
+                      Index:
+                        ast.Ident:
+                          Name: int
+                  Elts:
+                    ast.BasicLit:
+                      Kind: INT
+                      Value: 1
+                ast.UnaryExpr:
+                  Op: &
+                  X:
+                    ast.CompositeLit:
+                      Type:
+                        ast.IndexListExpr:
+                          X:
+                            ast.Ident:
+                              Name: P2
+                          Indices:
+                            ast.Ident:
+                              Name: int
+                            ast.Ident:
+                              Name: string
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: foo
+              Args:
+                ast.LambdaExpr:
+                  Rhs:
+                    ast.CompositeLit:
+                      Type:
+                        ast.IndexExpr:
+                          X:
+                            ast.Ident:
+                              Name: P1
+                          Index:
+                            ast.Ident:
+                              Name: int
+                      Elts:
+                        ast.BasicLit:
+                          Kind: INT
+                          Value: 1
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: foo
+              Args:
+                ast.LambdaExpr2:
+                  Body:
+                    ast.BlockStmt:
+                      List:
+                        ast.ExprStmt:
+                          X:
+                            ast.CallExpr:
+                              Fun:
+                                ast.Ident:
+                                  Name: println
+                              Args:
+                                ast.UnaryExpr:
+                                  Op: &
+                                  X:
+                                    ast.CompositeLit:
+                                      Type:
+                                        ast.IndexListExpr:
+                                          X:
+                                            ast.Ident:
+                                              Name: P2
+                                          Indices:
+                                            ast.Ident:
+                                              Name: int
+                                            ast.Ident:
+                                              Name: string
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.IndexExpr:
+                  X:
+                    ast.Ident:
+                      Name: fn1
+                  Index:
+                    ast.Ident:
+                      Name: int
+              Args:
+                ast.CompositeLit:
+                  Type:
+                    ast.ArrayType:
+                      Elt:
+                        ast.IndexExpr:
+                          X:
+                            ast.Ident:
+                              Name: P1
+                          Index:
+                            ast.Ident:
+                              Name: int
+                  Elts:
+                    ast.CompositeLit:
+                      Type:
+                        ast.IndexExpr:
+                          X:
+                            ast.Ident:
+                              Name: P1
+                          Index:
+                            ast.Ident:
+                              Name: int
+                      Elts:
+                        ast.BasicLit:
+                          Kind: INT
+                          Value: 0
+                    ast.CompositeLit:
+                      Type:
+                        ast.IndexExpr:
+                          X:
+                            ast.Ident:
+                              Name: P1
+                          Index:
+                            ast.Ident:
+                              Name: int
+                      Elts:
+                        ast.BasicLit:
+                          Kind: INT
+                          Value: 1
+                ast.UnaryExpr:
+                  Op: &
+                  X:
+                    ast.CompositeLit:
+                      Type:
+                        ast.IndexListExpr:
+                          X:
+                            ast.Ident:
+                              Name: P2
+                          Indices:
+                            ast.Ident:
+                              Name: int
+                            ast.Ident:
+                              Name: string
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.IndexListExpr:
+                  X:
+                    ast.Ident:
+                      Name: fn2
+                  Indices:
+                    ast.Ident:
+                      Name: int
+                    ast.Ident:
+                      Name: string
+              Args:
+                ast.UnaryExpr:
+                  Op: &
+                  X:
+                    ast.CompositeLit:
+                      Type:
+                        ast.IndexExpr:
+                          X:
+                            ast.Ident:
+                              Name: P1
+                          Index:
+                            ast.Ident:
+                              Name: int
+                ast.UnaryExpr:
+                  Op: &
+                  X:
+                    ast.CompositeLit:
+                      Type:
+                        ast.IndexListExpr:
+                          X:
+                            ast.Ident:
+                              Name: P2
+                          Indices:
+                            ast.Ident:
+                              Name: int
+                            ast.Ident:
+                              Name: string
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.IndexExpr:
+                  X:
+                    ast.Ident:
+                      Name: foo1
+                  Index:
+                    ast.Ident:
+                      Name: int
+              Args:
+                ast.LambdaExpr:
+                  Rhs:
+                    ast.CompositeLit:
+                      Type:
+                        ast.IndexExpr:
+                          X:
+                            ast.Ident:
+                              Name: P1
+                          Index:
+                            ast.Ident:
+                              Name: int
+                      Elts:
+                        ast.BasicLit:
+                          Kind: INT
+                          Value: 1
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.IndexListExpr:
+                  X:
+                    ast.Ident:
+                      Name: foo2
+                  Indices:
+                    ast.Ident:
+                      Name: int
+                    ast.Ident:
+                      Name: string
+              Args:
+                ast.LambdaExpr2:
+                  Body:
+                    ast.BlockStmt:
+                      List:
+                        ast.ExprStmt:
+                          X:
+                            ast.CallExpr:
+                              Fun:
+                                ast.Ident:
+                                  Name: println
+                              Args:
+                                ast.UnaryExpr:
+                                  Op: &
+                                  X:
+                                    ast.CompositeLit:
+                                      Type:
+                                        ast.IndexListExpr:
+                                          X:
+                                            ast.Ident:
+                                              Name: P2
+                                          Indices:
+                                            ast.Ident:
+                                              Name: int
+                                            ast.Ident:
+                                              Name: string
diff --git a/parser/_instance/instance4/cmd.gop b/parser/_instance/instance4/cmd.gop
new file mode 100644
index 00000000..fb8c6688
--- /dev/null
+++ b/parser/_instance/instance4/cmd.gop
@@ -0,0 +1,15 @@
+fn[int]()
+fn[[]int]()
+fn[struct {
+	X int
+	Y int
+}]()
+fn[map[int]string]()
+fn[chan int]()
+fn[chan struct{}]()
+fn[interface{}]()
+fn[interface{ Method() }]()
+fn[[]struct {
+	X int
+	Y int
+}, map[int]string]()
diff --git a/parser/_instance/instance4/parser.expect b/parser/_instance/instance4/parser.expect
new file mode 100644
index 00000000..7e01f3fa
--- /dev/null
+++ b/parser/_instance/instance4/parser.expect
@@ -0,0 +1,178 @@
+package main
+
+file cmd.gop
+noEntrypoint
+ast.FuncDecl:
+  Name:
+    ast.Ident:
+      Name: main
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+  Body:
+    ast.BlockStmt:
+      List:
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.IndexExpr:
+                  X:
+                    ast.Ident:
+                      Name: fn
+                  Index:
+                    ast.Ident:
+                      Name: int
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.IndexExpr:
+                  X:
+                    ast.Ident:
+                      Name: fn
+                  Index:
+                    ast.ArrayType:
+                      Elt:
+                        ast.Ident:
+                          Name: int
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.IndexExpr:
+                  X:
+                    ast.Ident:
+                      Name: fn
+                  Index:
+                    ast.StructType:
+                      Fields:
+                        ast.FieldList:
+                          List:
+                            ast.Field:
+                              Names:
+                                ast.Ident:
+                                  Name: X
+                              Type:
+                                ast.Ident:
+                                  Name: int
+                            ast.Field:
+                              Names:
+                                ast.Ident:
+                                  Name: Y
+                              Type:
+                                ast.Ident:
+                                  Name: int
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.IndexExpr:
+                  X:
+                    ast.Ident:
+                      Name: fn
+                  Index:
+                    ast.MapType:
+                      Key:
+                        ast.Ident:
+                          Name: int
+                      Value:
+                        ast.Ident:
+                          Name: string
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.IndexExpr:
+                  X:
+                    ast.Ident:
+                      Name: fn
+                  Index:
+                    ast.ChanType:
+                      Value:
+                        ast.Ident:
+                          Name: int
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.IndexExpr:
+                  X:
+                    ast.Ident:
+                      Name: fn
+                  Index:
+                    ast.ChanType:
+                      Value:
+                        ast.StructType:
+                          Fields:
+                            ast.FieldList:
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.IndexExpr:
+                  X:
+                    ast.Ident:
+                      Name: fn
+                  Index:
+                    ast.InterfaceType:
+                      Methods:
+                        ast.FieldList:
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.IndexExpr:
+                  X:
+                    ast.Ident:
+                      Name: fn
+                  Index:
+                    ast.InterfaceType:
+                      Methods:
+                        ast.FieldList:
+                          List:
+                            ast.Field:
+                              Names:
+                                ast.Ident:
+                                  Name: Method
+                              Type:
+                                ast.FuncType:
+                                  Params:
+                                    ast.FieldList:
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.IndexListExpr:
+                  X:
+                    ast.Ident:
+                      Name: fn
+                  Indices:
+                    ast.ArrayType:
+                      Elt:
+                        ast.StructType:
+                          Fields:
+                            ast.FieldList:
+                              List:
+                                ast.Field:
+                                  Names:
+                                    ast.Ident:
+                                      Name: X
+                                  Type:
+                                    ast.Ident:
+                                      Name: int
+                                ast.Field:
+                                  Names:
+                                    ast.Ident:
+                                      Name: Y
+                                  Type:
+                                    ast.Ident:
+                                      Name: int
+                    ast.MapType:
+                      Key:
+                        ast.Ident:
+                          Name: int
+                      Value:
+                        ast.Ident:
+                          Name: string
diff --git a/parser/interface.go b/parser/interface.go
index a0fd6bff..ef62b016 100644
--- a/parser/interface.go
+++ b/parser/interface.go
@@ -26,7 +26,6 @@ import (
 // A Mode value is a set of flags (or 0).
 // They control the amount of source code parsed and other optional
 // parser functionality.
-//
 type Mode uint
 
 const (
@@ -44,6 +43,8 @@ const (
 	AllErrors
 	// ParseGoAsGoPlus - parse Go files by gop/parser
 	ParseGoAsGoPlus
+	// ParserGoPlusClass - parse Go+ classfile by gop/parser
+	ParseGoPlusClass
 )
 
 // ParseFile parses the source code of a single Go source file and returns
@@ -64,7 +65,6 @@ const (
 // errors were found, the result is a partial AST (with ast.Bad* nodes
 // representing the fragments of erroneous source code). Multiple errors
 // are returned via a scanner.ErrorList which is sorted by source position.
-//
 func parseFile(fset *token.FileSet, filename string, src interface{}, mode Mode) (f *ast.File, err error) {
 	if fset == nil {
 		panic("parser.ParseFile: no token.FileSet provided (fset == nil)")
diff --git a/parser/parser.go b/parser/parser.go
index 711904fc..75d3709b 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -25,7 +25,6 @@
 // treated like an ordinary parameter list and thus may contain multiple
 // entries where the spec permits exactly one. Consequently, the corresponding
 // field in the AST (ast.FuncDecl.Recv) field is not restricted to one entry.
-//
 package parser
 
 import (
@@ -198,7 +197,6 @@ var unresolved = new(ast.Object)
 // the object it denotes. If no object is found and collectUnresolved is
 // set, x is marked as unresolved and collected in the list of unresolved
 // identifiers.
-//
 func (p *parser) tryResolve(x ast.Expr, collectUnresolved bool) {
 	// nothing to do if x is not an identifier or the blank identifier
 	ident, _ := x.(*ast.Ident)
@@ -316,7 +314,6 @@ func (p *parser) consumeComment() (comment *ast.Comment, endline int) {
 // comments list, and return it together with the line at which
 // the last comment in the group ends. A non-comment token or n
 // empty lines terminate a comment group.
-//
 func (p *parser) consumeCommentGroup(n int) (comments *ast.CommentGroup, endline int) {
 	var list []*ast.Comment
 	endline = p.file.Line(p.pos)
@@ -347,7 +344,6 @@ func (p *parser) consumeCommentGroup(n int) (comments *ast.CommentGroup, endline
 //
 // Lead and line comments may be considered documentation that is
 // stored in the AST.
-//
 func (p *parser) next() {
 	p.leadComment = nil
 	p.lineComment = nil
@@ -449,7 +445,6 @@ func (p *parser) expect2(tok token.Token) (pos token.Pos) {
 
 // expectClosing is like expect but provides a better error message
 // for the common case of a missing comma before a newline.
-//
 func (p *parser) expectClosing(tok token.Token, context string) token.Pos {
 	if p.tok != tok && p.tok == token.SEMICOLON && p.lit == "\n" {
 		p.error(p.pos, "missing ',' before newline in "+context)
@@ -571,7 +566,6 @@ var exprEnd = map[token.Token]bool{
 // token positions are invalid due to parse errors, the resulting end position
 // may be past the file's EOF position, which would lead to panics if used
 // later on.
-//
 func (p *parser) safePos(pos token.Pos) (res token.Pos) {
 	defer func() {
 		if recover() != nil {
@@ -756,12 +750,14 @@ func (p *parser) parseType() ast.Expr {
 }
 
 // If the result is an identifier, it is not resolved.
-func (p *parser) parseTypeName() ast.Expr {
+func (p *parser) parseTypeName(ident *ast.Ident) ast.Expr {
 	if p.trace {
 		defer un(trace(p, "TypeName"))
 	}
 
-	ident := p.parseIdent()
+	if ident == nil {
+		ident = p.parseIdent()
+	}
 	// don't resolve ident yet - it may be a parameter or field name
 
 	if p.tok == token.PERIOD {
@@ -902,22 +898,6 @@ func newSliceLit(lbrack, rbrack token.Pos, len ast.Expr) *ast.SliceLit {
 	return &ast.SliceLit{Lbrack: lbrack, Elts: elts, Rbrack: rbrack}
 }
 
-func (p *parser) makeIdentList(list []ast.Expr) []*ast.Ident {
-	idents := make([]*ast.Ident, len(list))
-	for i, x := range list {
-		ident, isIdent := x.(*ast.Ident)
-		if !isIdent {
-			if _, isBad := x.(*ast.BadExpr); !isBad {
-				// only report error if it's a new one
-				p.errorExpected(x.Pos(), "identifier", 2)
-			}
-			ident = &ast.Ident{NamePos: x.Pos(), Name: "_"}
-		}
-		idents[i] = ident
-	}
-	return idents
-}
-
 func (p *parser) parseFieldDecl(scope *ast.Scope) *ast.Field {
 	if p.trace {
 		defer un(trace(p, "FieldDecl"))
@@ -925,48 +905,88 @@ func (p *parser) parseFieldDecl(scope *ast.Scope) *ast.Field {
 
 	doc := p.leadComment
 
-	// 1st FieldDecl
-	// A type name used as an anonymous field looks like a field identifier.
-	var list []ast.Expr
-	for {
-		list = append(list, p.parseVarType(false))
-		if p.tok != token.COMMA {
-			break
+	var names []*ast.Ident
+	var typ ast.Expr
+	switch p.tok {
+	case token.IDENT:
+		name := p.parseIdent()
+		if p.tok == token.PERIOD || p.tok == token.STRING || p.tok == token.SEMICOLON || p.tok == token.RBRACE {
+			// embedded type
+			typ = name
+			if p.tok == token.PERIOD {
+				typ = p.parseQualifiedIdent(name)
+			}
+		} else {
+			// name1, name2, ... T
+			names = []*ast.Ident{name}
+			for p.tok == token.COMMA {
+				p.next()
+				names = append(names, p.parseIdent())
+			}
+			// Careful dance: We don't know if we have an embedded instantiated
+			// type T[P1, P2, ...] or a field T of array type []E or [P]E.
+			if len(names) == 1 && p.tok == token.LBRACK {
+				name, typ = p.parseArrayFieldOrTypeInstance(name, stateType)
+				if name == nil {
+					names = nil
+				}
+			} else {
+				// T P
+				typ = p.parseType()
+			}
 		}
+	case token.MUL:
+		star := p.pos
 		p.next()
-	}
-
-	typ := p.tryVarType(false)
+		if p.tok == token.LPAREN {
+			// *(T)
+			p.error(p.pos, "cannot parenthesize embedded type")
+			p.next()
+			typ = p.parseQualifiedIdent(nil)
+			// expect closing ')' but no need to complain if missing
+			if p.tok == token.RPAREN {
+				p.next()
+			}
+		} else {
+			// *T
+			typ = p.parseQualifiedIdent(nil)
+		}
+		typ = &ast.StarExpr{Star: star, X: typ}
 
-	// analyze case
-	var idents []*ast.Ident
-	if typ != nil {
-		// IdentifierList Type
-		idents = p.makeIdentList(list)
-	} else {
-		// ["*"] TypeName (AnonymousField)
-		typ = list[0] // we always have at least one element
-		if n := len(list); n > 1 {
-			p.errorExpected(p.pos, "type", 2)
-			typ = &ast.BadExpr{From: p.pos, To: p.pos}
-		} else if !isTypeName(deref(typ)) {
-			p.errorExpected(typ.Pos(), "anonymous field", 2)
-			typ = &ast.BadExpr{From: typ.Pos(), To: p.safePos(typ.End())}
+	case token.LPAREN:
+		p.error(p.pos, "cannot parenthesize embedded type")
+		p.next()
+		if p.tok == token.MUL {
+			// (*T)
+			star := p.pos
+			p.next()
+			typ = &ast.StarExpr{Star: star, X: p.parseQualifiedIdent(nil)}
+		} else {
+			// (T)
+			typ = p.parseQualifiedIdent(nil)
 		}
+		// expect closing ')' but no need to complain if missing
+		if p.tok == token.RPAREN {
+			p.next()
+		}
+
+	default:
+		pos := p.pos
+		p.errorExpected(pos, "field name or embedded type", 2)
+		p.advance(exprEnd)
+		typ = &ast.BadExpr{From: pos, To: p.pos}
 	}
 
-	// Tag
 	var tag *ast.BasicLit
 	if p.tok == token.STRING {
 		tag = &ast.BasicLit{ValuePos: p.pos, Kind: p.tok, Value: p.lit}
 		p.next()
 	}
 
-	p.expectSemi() // call before accessing p.linecomment
+	p.expectSemi()
 
-	field := &ast.Field{Doc: doc, Names: idents, Type: typ, Tag: tag, Comment: p.lineComment}
-	p.declare(field, nil, scope, ast.Var, idents...)
-	p.resolve(typ)
+	field := &ast.Field{Doc: doc, Names: names, Type: typ, Tag: tag, Comment: p.lineComment}
+	p.declare(field, nil, scope, ast.Var, names...)
 
 	return field
 }
@@ -1009,95 +1029,224 @@ func (p *parser) parsePointerType() *ast.StarExpr {
 	return &ast.StarExpr{Star: star, X: base}
 }
 
-// If the result is an identifier, it is not resolved.
-func (p *parser) tryVarType(isParam bool) ast.Expr {
-	if isParam && p.tok == token.ELLIPSIS {
-		pos := p.pos
-		p.next()
-		typ, _ := p.tryIdentOrType(stateType, nil) // don't use parseType so we can provide better error message
-		if typ != nil {
-			p.resolve(typ)
-		} else {
-			p.error(pos, "'...' parameter is missing type")
-			typ = &ast.BadExpr{From: pos, To: p.pos}
-		}
-		return &ast.Ellipsis{Ellipsis: pos, Elt: typ}
-	}
-	typ, _ := p.tryIdentOrType(stateType, nil)
-	return typ
+type field struct {
+	name *ast.Ident
+	typ  ast.Expr
 }
 
-// If the result is an identifier, it is not resolved.
-func (p *parser) parseVarType(isParam bool) ast.Expr {
-	typ := p.tryVarType(isParam)
-	if typ == nil {
-		pos := p.pos
-		p.errorExpected(pos, "type", 2)
-		p.next() // make progress
-		typ = &ast.BadExpr{From: pos, To: p.pos}
-	}
-	return typ
-}
-
-func (p *parser) parseParameterList(scope *ast.Scope, ellipsisOk bool) (params []*ast.Field) {
+func (p *parser) parseParameterList(scope *ast.Scope, name0 *ast.Ident, typ0 ast.Expr, closing token.Token) (params []*ast.Field) {
 	if p.trace {
 		defer un(trace(p, "ParameterList"))
 	}
 
-	// 1st ParameterDecl
-	// A list of identifiers looks like a list of type names.
-	var list []ast.Expr
-	for {
-		list = append(list, p.parseVarType(ellipsisOk))
-		if p.tok != token.COMMA {
+	// Type parameters are the only parameter list closed by ']'.
+	tparams := closing == token.RBRACK
+	// Type set notation is ok in type parameter lists.
+
+	pos := p.pos
+	if name0 != nil {
+		pos = name0.Pos()
+	}
+
+	var list []field
+	var named int // number of parameters that have an explicit name and type
+
+	for name0 != nil || p.tok != closing && p.tok != token.EOF {
+		var par field
+		if typ0 != nil {
+			par = field{name0, typ0}
+		} else {
+			par = p.parseParamDecl(name0)
+		}
+		name0 = nil // 1st name was consumed if present
+		typ0 = nil  // 1st typ was consumed if present
+		if par.name != nil || par.typ != nil {
+			list = append(list, par)
+			if par.name != nil && par.typ != nil {
+				named++
+			}
+		}
+		if !p.atComma("parameter list", closing) {
 			break
 		}
 		p.next()
-		if p.tok == token.RPAREN {
-			break
+	}
+
+	if len(list) == 0 {
+		return // not uncommon
+	}
+
+	// TODO(gri) parameter distribution and conversion to []*ast.Field
+	//           can be combined and made more efficient
+
+	// distribute parameter types
+	if named == 0 {
+		// all unnamed => found names are type names
+		for i := 0; i < len(list); i++ {
+			par := &list[i]
+			if typ := par.name; typ != nil {
+				par.typ = typ
+				par.name = nil
+			}
+		}
+		if tparams {
+			p.error(pos, "type parameters must be named")
+		}
+	} else if named != len(list) {
+		// some named => all must be named
+		ok := true
+		var typ ast.Expr
+		missingName := pos
+		for i := len(list) - 1; i >= 0; i-- {
+			if par := &list[i]; par.typ != nil {
+				typ = par.typ
+				if par.name == nil {
+					ok = false
+					missingName = par.typ.Pos()
+					n := ast.NewIdent("_")
+					n.NamePos = typ.Pos() // correct position
+					par.name = n
+				}
+			} else if typ != nil {
+				par.typ = typ
+			} else {
+				// par.typ == nil && typ == nil => we only have a par.name
+				ok = false
+				missingName = par.name.Pos()
+				par.typ = &ast.BadExpr{From: par.name.Pos(), To: p.pos}
+			}
+		}
+		if !ok {
+			if tparams {
+				p.error(missingName, "type parameters must be named")
+			} else {
+				p.error(pos, "mixed named and unnamed parameters")
+			}
 		}
 	}
 
-	// analyze case
-	if typ := p.tryVarType(ellipsisOk); typ != nil {
-		// IdentifierList Type
-		idents := p.makeIdentList(list)
-		field := &ast.Field{Names: idents, Type: typ}
-		params = append(params, field)
+	// convert list []*ast.Field
+	if named == 0 {
+		// parameter list consists of types only
+		for _, par := range list {
+			assert(par.typ != nil, "nil type in unnamed parameter list")
+			params = append(params, &ast.Field{Type: par.typ})
+		}
+		return
+	}
+
+	// parameter list consists of named parameters with types
+	var names []*ast.Ident
+	var typ ast.Expr
+	addParams := func() {
+		assert(typ != nil, "nil type in named parameter list")
+		field := &ast.Field{Names: names, Type: typ}
 		// Go spec: The scope of an identifier denoting a function
 		// parameter or result variable is the function body.
-		p.declare(field, nil, scope, ast.Var, idents...)
-		p.resolve(typ)
-		if !p.atComma("parameter list", token.RPAREN) {
-			return
-		}
-		p.next()
-		for p.tok != token.RPAREN && p.tok != token.EOF {
-			idents := p.parseIdentList()
-			typ := p.parseVarType(ellipsisOk)
-			field := &ast.Field{Names: idents, Type: typ}
-			params = append(params, field)
-			// Go spec: The scope of an identifier denoting a function
-			// parameter or result variable is the function body.
-			p.declare(field, nil, scope, ast.Var, idents...)
-			p.resolve(typ)
-			if !p.atComma("parameter list", token.RPAREN) {
-				break
+		p.declare(field, nil, scope, ast.Var, names...)
+		params = append(params, field)
+		names = nil
+	}
+	for _, par := range list {
+		if par.typ != typ {
+			if len(names) > 0 {
+				addParams()
 			}
-			p.next()
+			typ = par.typ
 		}
-		return
+		names = append(names, par.name)
 	}
+	if len(names) > 0 {
+		addParams()
+	}
+	return
+}
 
-	// Type { "," Type } (anonymous parameters)
-	params = make([]*ast.Field, len(list))
-	for i, typ := range list {
-		p.resolve(typ)
-		params[i] = &ast.Field{Type: typ}
+func (p *parser) parseParamDecl(name *ast.Ident) (f field) {
+	// TODO(rFindley) refactor to be more similar to paramDeclOrNil in the syntax
+	// package
+	if p.trace {
+		defer un(trace(p, "ParamDeclOrNil"))
 	}
+
+	ptok := p.tok
+	if name != nil {
+		p.tok = token.IDENT // force token.IDENT case in switch below
+	}
+
+	switch p.tok {
+	case token.IDENT:
+		// name
+		if name != nil {
+			f.name = name
+			p.tok = ptok
+		} else {
+			f.name = p.parseIdent()
+		}
+		switch p.tok {
+		case token.IDENT, token.MUL, token.ARROW, token.FUNC, token.CHAN, token.MAP, token.STRUCT, token.INTERFACE, token.LPAREN:
+			// name type
+			f.typ = p.parseType()
+
+		case token.LBRACK:
+			// name "[" type1, ..., typeN "]" or name "[" n "]" type
+			f.name, f.typ = p.parseArrayFieldOrTypeInstance(f.name, stateType)
+
+		case token.ELLIPSIS:
+			// name "..." type
+			f.typ = p.parseDotsType()
+			return // don't allow ...type "|" ...
+
+		case token.PERIOD:
+			// name "." ...
+			f.typ = p.parseQualifiedIdent(f.name)
+			f.name = nil
+		}
+
+	case token.MUL, token.ARROW, token.FUNC, token.LBRACK, token.CHAN, token.MAP, token.STRUCT, token.INTERFACE, token.LPAREN:
+		// type
+		f.typ = p.parseType()
+
+	case token.ELLIPSIS:
+		// "..." type
+		// (always accepted)
+		f.typ = p.parseDotsType()
+		return // don't allow ...type "|" ...
+
+	default:
+		// TODO(rfindley): this is incorrect in the case of type parameter lists
+		//                 (should be "']'" in that case)
+		p.errorExpected(p.pos, "')'", 2)
+		p.advance(exprEnd)
+	}
+
 	return
 }
 
+func (p *parser) parseQualifiedIdent(ident *ast.Ident) ast.Expr {
+	if p.trace {
+		defer un(trace(p, "QualifiedIdent"))
+	}
+
+	typ := p.parseTypeName(ident)
+	if p.tok == token.LBRACK {
+		typ = p.parseTypeInstance(typ)
+	}
+
+	return typ
+}
+
+func (p *parser) parseDotsType() *ast.Ellipsis {
+	if p.trace {
+		defer un(trace(p, "DotsType"))
+	}
+
+	pos := p.expect(token.ELLIPSIS)
+	elt := p.parseType()
+
+	return &ast.Ellipsis{Ellipsis: pos, Elt: elt}
+}
+
 func (p *parser) parseParameters(scope *ast.Scope, ellipsisOk bool) *ast.FieldList {
 	if p.trace {
 		defer un(trace(p, "Parameters"))
@@ -1105,8 +1254,9 @@ func (p *parser) parseParameters(scope *ast.Scope, ellipsisOk bool) *ast.FieldLi
 
 	var params []*ast.Field
 	lparen := p.expect(token.LPAREN)
+
 	if p.tok != token.RPAREN {
-		params = p.parseParameterList(scope, ellipsisOk)
+		params = p.parseParameterList(scope, nil, nil, token.RPAREN)
 	}
 	rparen := p.expect(token.RPAREN)
 
@@ -1163,7 +1313,7 @@ func (p *parser) parseMethodSpec(scope *ast.Scope) *ast.Field {
 	doc := p.leadComment
 	var idents []*ast.Ident
 	var typ ast.Expr
-	x := p.parseTypeName()
+	x := p.parseTypeName(nil)
 	if ident, isIdent := x.(*ast.Ident); isIdent && p.tok == token.LPAREN {
 		// method
 		idents = []*ast.Ident{ident}
@@ -1248,12 +1398,95 @@ func (p *parser) parseChanType() *ast.ChanType {
 	return &ast.ChanType{Begin: pos, Arrow: arrow, Dir: dir, Value: value}
 }
 
+func (p *parser) parseTypeInstance(typ ast.Expr) ast.Expr {
+	if p.trace {
+		defer un(trace(p, "TypeInstance"))
+	}
+
+	opening := p.expect(token.LBRACK)
+	p.exprLev++
+	var list []ast.Expr
+	for p.tok != token.RBRACK && p.tok != token.EOF {
+		list = append(list, p.parseType())
+		if !p.atComma("type argument list", token.RBRACK) {
+			break
+		}
+		p.next()
+	}
+	p.exprLev--
+
+	closing := p.expectClosing(token.RBRACK, "type argument list")
+
+	if len(list) == 0 {
+		p.errorExpected(closing, "type argument list", 2)
+		return &ast.IndexExpr{
+			X:      typ,
+			Lbrack: opening,
+			Index:  &ast.BadExpr{From: opening + 1, To: closing},
+			Rbrack: closing,
+		}
+	}
+
+	return packIndexExpr(typ, opening, list, closing)
+}
+
+func (p *parser) parseArrayFieldOrTypeInstance(x *ast.Ident, state int) (*ast.Ident, ast.Expr) {
+	if p.trace {
+		defer un(trace(p, "ArrayFieldOrTypeInstance"))
+	}
+
+	lbrack := p.expect(token.LBRACK)
+	trailingComma := token.NoPos // if valid, the position of a trailing comma preceding the ']'
+	var args []ast.Expr
+	if p.tok != token.RBRACK {
+		p.exprLev++
+		args = append(args, p.parseRHS())
+		for p.tok == token.COMMA {
+			comma := p.pos
+			p.next()
+			if p.tok == token.RBRACK {
+				trailingComma = comma
+				break
+			}
+			args = append(args, p.parseRHS())
+		}
+		p.exprLev--
+	}
+	rbrack := p.expect(token.RBRACK)
+
+	if len(args) == 0 {
+		// x []E
+		elt := p.parseType()
+		return x, &ast.ArrayType{Lbrack: lbrack, Elt: elt}
+	}
+
+	// x [P]E or x[P]
+	if len(args) == 1 {
+		elt, _ := p.tryIdentOrType(state, nil)
+		if elt != nil {
+			// x [P]E
+			if trailingComma.IsValid() {
+				// Trailing commas are invalid in array type fields.
+				p.error(trailingComma, "unexpected comma; expecting ]")
+			}
+			return x, &ast.ArrayType{Lbrack: lbrack, Len: args[0], Elt: elt}
+		}
+	}
+
+	// x[P], x[P1, P2], ...
+	return nil, packIndexExpr(x, lbrack, args, rbrack)
+}
+
 // state = stateArrayTypeOrSliceLit | stateTypeOrSliceOp | stateType | ...
 // If the result is an identifier, it is not resolved.
 func (p *parser) tryIdentOrType(state int, len ast.Expr) (ast.Expr, int) {
 	switch p.tok {
 	case token.IDENT:
-		return p.parseTypeName(), resultIdent
+		typ := p.parseTypeName(nil)
+		if p.tok == token.LBRACK {
+			typ = p.parseTypeInstance(typ)
+		}
+		return typ, resultIdent
 	case token.LBRACK:
 		return p.parseArrayTypeOrSliceLit(state, len)
 	case token.STRUCT:
@@ -1358,7 +1591,6 @@ func (p *parser) parseFuncTypeOrLit() ast.Expr {
 // parseOperand may return an expression or a raw type (incl. array
 // types of the form [...]T. Callers must verify the result.
 // If lhs is set and the result is an identifier, it is not resolved.
-//
 func (p *parser) parseOperand(lhs, allowTuple, allowCmd bool) (x ast.Expr, isTuple bool) {
 	if p.trace {
 		defer un(trace(p, "Operand"))
@@ -1503,20 +1735,35 @@ func (p *parser) parseIndexOrSlice(x ast.Expr) ast.Expr {
 
 func (p *parser) parseIndexOrSliceContinue(x ast.Expr, lbrack token.Pos, idx ast.Expr) ast.Expr {
 	const N = 3 // change the 3 to 2 to disable 3-index slices
+	var args []ast.Expr
 	var index [N]ast.Expr
 	var colons [N - 1]token.Pos
 	if idx != nil {
 		index[0] = idx
 	}
 	ncolons := 0
-	for p.tok == token.COLON && ncolons < len(colons) {
-		colons[ncolons] = p.pos
-		ncolons++
-		p.next()
-		if p.tok != token.COLON && p.tok != token.RBRACK && p.tok != token.EOF {
-			index[ncolons] = p.parseRHS()
+	switch p.tok {
+	case token.COLON:
+		// slice expression
+		for p.tok == token.COLON && ncolons < len(colons) {
+			colons[ncolons] = p.pos
+			ncolons++
+			p.next()
+			if p.tok != token.COLON && p.tok != token.RBRACK && p.tok != token.EOF {
+				index[ncolons] = p.parseRHS()
+			}
+		}
+	case token.COMMA:
+		// instance expression
+		args = append(args, index[0])
+		for p.tok == token.COMMA {
+			p.next()
+			if p.tok != token.RBRACK && p.tok != token.EOF {
+				args = append(args, p.parseType())
+			}
 		}
 	}
+
 	p.exprLev--
 	rbrack := p.expect(token.RBRACK)
 
@@ -1525,27 +1772,57 @@ func (p *parser) parseIndexOrSliceContinue(x ast.Expr, lbrack token.Pos, idx ast
 		slice3 := false
 		if ncolons == 2 {
 			slice3 = true
-			// Check presence of 2nd and 3rd index here rather than during type-checking
+			// Check presence of middle and final index here rather than during type-checking
 			// to prevent erroneous programs from passing through gofmt (was issue 7305).
 			if index[1] == nil {
-				p.error(colons[0], "2nd index required in 3-index slice")
+				p.error(colons[0], "middle index required in 3-index slice")
 				index[1] = &ast.BadExpr{From: colons[0] + 1, To: colons[1]}
 			}
 			if index[2] == nil {
-				p.error(colons[1], "3rd index required in 3-index slice")
+				p.error(colons[1], "final index required in 3-index slice")
 				index[2] = &ast.BadExpr{From: colons[1] + 1, To: rbrack}
 			}
 		}
+		return &ast.SliceExpr{X: x, Lbrack: lbrack, Low: index[0], High: index[1], Max: index[2], Slice3: slice3, Rbrack: rbrack}
+	}
+
+	if len(args) == 0 {
 		if debugParseOutput {
-			log.Printf("ast.SliceExpr{X: %v, Low: %v, High: %v, Max: %v, Slice3: %v}\n", x, index[0], index[1], index[2], slice3)
+			log.Printf("ast.IndexExpr{X: %v, Index: %v}\n", x, index[0])
 		}
-		return &ast.SliceExpr{X: x, Lbrack: lbrack, Low: index[0], High: index[1], Max: index[2], Slice3: slice3, Rbrack: rbrack}
+		// index expression
+		return &ast.IndexExpr{X: x, Lbrack: lbrack, Index: index[0], Rbrack: rbrack}
 	}
 
-	if debugParseOutput {
-		log.Printf("ast.IndexExpr{X: %v, Index: %v}\n", x, index[0])
+	// instance expression
+	return packIndexExpr(x, lbrack, args, rbrack)
+}
+
+func packIndexExpr(x ast.Expr, lbrack token.Pos, exprs []ast.Expr, rbrack token.Pos) ast.Expr {
+	switch len(exprs) {
+	case 0:
+		panic("internal error: packIndexExpr with empty expr slice")
+	case 1:
+		if debugParseOutput {
+			log.Printf("ast.IndexExpr{X: %v, Index: %v}\n", x, exprs[0])
+		}
+		return &ast.IndexExpr{
+			X:      x,
+			Lbrack: lbrack,
+			Index:  exprs[0],
+			Rbrack: rbrack,
+		}
+	default:
+		if debugParseOutput {
+			log.Printf("ast.IndexListExpr{X: %v, Index: %v}\n", x, exprs)
+		}
+		return &ast.IndexListExpr{
+			X:       x,
+			Lbrack:  lbrack,
+			Indices: exprs,
+			Rbrack:  rbrack,
+		}
 	}
-	return &ast.IndexExpr{X: x, Lbrack: lbrack, Index: index[0], Rbrack: rbrack}
 }
 
 func (p *parser) parseCallOrConversion(fun ast.Expr, isCmd bool) *ast.CallExpr {
@@ -1756,6 +2033,13 @@ func (p *parser) checkExpr(x ast.Expr) ast.Expr {
 	case *ast.ComprehensionExpr:
 	case *ast.SelectorExpr:
 	case *ast.IndexExpr:
+	case *ast.IndexListExpr:
+	case *ast.ArrayType:
+	case *ast.StructType:
+	case *ast.InterfaceType:
+	case *ast.FuncType:
+	case *ast.MapType:
+	case *ast.ChanType:
 	case *ast.SliceExpr:
 	case *ast.TypeAssertExpr:
 		// If t.Type == nil we have a type assertion of the form
@@ -1801,6 +2085,12 @@ func isLiteralType(x ast.Expr) bool {
 	switch t := x.(type) {
 	case *ast.BadExpr:
 	case *ast.Ident:
+	case *ast.IndexExpr:
+		_, isIdent := t.X.(*ast.Ident)
+		return isIdent
+	case *ast.IndexListExpr:
+		_, isIdent := t.X.(*ast.Ident)
+		return isIdent
 	case *ast.SelectorExpr:
 		_, isIdent := t.X.(*ast.Ident)
 		return isIdent
@@ -1902,7 +2192,14 @@ L:
 			} else {
 				break L
 			}
-		case token.NOT, token.QUESTION:
+		case token.NOT:
+			if allowCmd && p.isCmd(x) {
+				x = p.parseCallOrConversion(p.checkExprOrType(x), true)
+			} else {
+				x = &ast.ErrWrapExpr{X: x, Tok: token.NOT, TokPos: p.pos}
+				p.next()
+			}
+		case token.QUESTION:
 			x = &ast.ErrWrapExpr{X: x, Tok: p.tok, TokPos: p.pos}
 			p.next()
 		default:
@@ -3067,7 +3364,13 @@ func (p *parser) parseValueSpec(doc *ast.CommentGroup, keyword token.Token, iota
 	switch keyword {
 	case token.VAR:
 		if typ == nil && values == nil {
-			p.error(pos, "missing variable type or initialization")
+			if p.mode&ParseGoPlusClass != 0 {
+				if len(idents) != 1 {
+					p.error(pos, "syntax error: unexpected newline, expected type")
+				}
+			} else {
+				p.error(pos, "missing variable type or initialization")
+			}
 		}
 	case token.CONST:
 		if values == nil && (iota == 0 || typ != nil) {
diff --git a/parser/parser_gop.go b/parser/parser_gop.go
index 5b80f460..8642ecd4 100644
--- a/parser/parser_gop.go
+++ b/parser/parser_gop.go
@@ -99,7 +99,6 @@ func astFileToPkg(file *ast.File, fileName string) (pkg *ast.Package) {
 // -----------------------------------------------------------------------------
 
 // ParseDir calls ParseFSDir by passing a local filesystem.
-//
 func ParseDir(fset *token.FileSet, path string, filter func(fs.FileInfo) bool, mode Mode) (pkgs map[string]*ast.Package, first error) {
 	return ParseFSDir(fset, local, path, Config{Filter: filter, Mode: mode})
 }
@@ -111,7 +110,6 @@ type Config struct {
 }
 
 // ParseDirEx calls ParseFSDir by passing a local filesystem.
-//
 func ParseDirEx(fset *token.FileSet, path string, conf Config) (pkgs map[string]*ast.Package, first error) {
 	return ParseFSDir(fset, local, path, conf)
 }
@@ -128,7 +126,6 @@ func ParseDirEx(fset *token.FileSet, path string, conf Config) (pkgs map[string]
 // If the directory couldn't be read, a nil map and the respective error are
 // returned. If a parse error occurred, a non-nil but incomplete map and the
 // first error encountered are returned.
-//
 func ParseFSDir(fset *token.FileSet, fs FileSystem, path string, conf Config) (pkgs map[string]*ast.Package, first error) {
 	list, err := fs.ReadDir(path)
 	if err != nil {
@@ -152,11 +149,17 @@ func ParseFSDir(fset *token.FileSet, fs FileSystem, path string, conf Config) (p
 				continue
 			}
 			useGoParser = (conf.Mode & ParseGoAsGoPlus) == 0
+		case ".gopx":
+			isClass = true
 		default:
 			if isProj, isClass = conf.IsClass(ext); !isClass {
 				continue
 			}
 		}
+		mode := conf.Mode
+		if isProj || isClass {
+			mode |= ParseGoPlusClass
+		}
 		if !strings.HasPrefix(fname, "_") && (conf.Filter == nil || conf.Filter(d)) {
 			filename := fs.Join(path, fname)
 			if useGoParser {
@@ -173,7 +176,7 @@ func ParseFSDir(fset *token.FileSet, fs FileSystem, path string, conf Config) (p
 				} else {
 					first = err
 				}
-			} else if src, err := ParseFSFile(fset, fs, filename, nil, conf.Mode); err == nil {
+			} else if src, err := ParseFSFile(fset, fs, filename, nil, mode); err == nil {
 				src.IsProj, src.IsClass = isProj, isClass
 				pkg := reqPkg(pkgs, src.Name.Name)
 				pkg.Files[filename] = src
diff --git a/printer/nodes.go b/printer/nodes.go
index 0cdd255d..c575c291 100644
--- a/printer/nodes.go
+++ b/printer/nodes.go
@@ -862,6 +862,13 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int) {
 		p.expr0(x.Index, depth+1)
 		p.print(x.Rbrack, token.RBRACK)
 
+	case *ast.IndexListExpr:
+		// TODO(gri): should treat[] like parentheses and undo one level of depth
+		p.expr1(x.X, token.HighestPrec, 1)
+		p.print(x.Lbrack, token.LBRACK)
+		p.exprList(x.Lbrack, x.Indices, depth+1, commaTerm, x.Rbrack, false)
+		p.print(x.Rbrack, token.RBRACK)
+
 	case *ast.SliceExpr:
 		// TODO(gri): should treat[] like parentheses and undo one level of depth
 		p.expr1(x.X, token.HighestPrec, 1)
diff --git a/printer/printer.go b/printer/printer.go
index 4088e860..4040b208 100644
--- a/printer/printer.go
+++ b/printer/printer.go
@@ -808,7 +808,7 @@ func (p *printer) intersperseComments(next token.Position, tok token.Token) (wro
 	return
 }
 
-// whiteWhitespace writes the first n whitespace entries.
+// writeWhitespace writes the first n whitespace entries.
 func (p *printer) writeWhitespace(n int) {
 	// write entries
 	for i := 0; i < n; i++ {
@@ -854,7 +854,7 @@ func (p *printer) writeWhitespace(n int) {
 // ----------------------------------------------------------------------------
 // Printing interface
 
-// nlines limits n to maxNewlines.
+// nlimit limits n to maxNewlines.
 func nlimit(n int) int {
 	if n > maxNewlines {
 		n = maxNewlines
@@ -1045,7 +1045,7 @@ func (p *printer) flush(next token.Position, tok token.Token) (wroteNewline, dro
 	return
 }
 
-// getNode returns the ast.CommentGroup associated with n, if any.
+// getDoc returns the ast.CommentGroup associated with n, if any.
 func getDoc(n ast.Node) *ast.CommentGroup {
 	switch n := n.(type) {
 	case *ast.Field:


Test patch:
diff --git a/ast/fromgo/typeparams_test.go b/ast/fromgo/typeparams_test.go
new file mode 100644
index 00000000..3d5e3dc2
--- /dev/null
+++ b/ast/fromgo/typeparams_test.go
@@ -0,0 +1,17 @@
+//go:build go1.18
+// +build go1.18
+
+package fromgo
+
+import "testing"
+
+func TestIndexListExpr(t *testing.T) {
+	test(t, `package main
+
+type N1 = T1[int]
+
+type N2 = T2[string, int]
+
+var f = test[string, int](1, 2)
+`)
+}
diff --git a/cl/compile_spx_test.go b/cl/compile_spx_test.go
index 41d14455..5cf85672 100644
--- a/cl/compile_spx_test.go
+++ b/cl/compile_spx_test.go
@@ -121,7 +121,7 @@ var (
 println "hi"
 `, "Game.tgmx", "Kai.tspx")
 
-	gopSpxErrorTestEx(t, `./Kai.tspx:3:2: missing field type in class file`, `
+	gopSpxErrorTestEx(t, `./Kai.tspx:3:2: cannot assign value to field in class file`, `
 var (
 	Kai Kai
 	userScore int
diff --git a/cl/compile_test.go b/cl/compile_test.go
index 064fedf6..e799c9cf 100644
--- a/cl/compile_test.go
+++ b/cl/compile_test.go
@@ -68,6 +68,11 @@ func gopClTest(t *testing.T, gopcode, expected string) {
 	gopClTestEx(t, gblConf, "main", gopcode, expected)
 }
 
+func gopClTestFile(t *testing.T, gopcode, expected string, fname string) {
+	fs := parsertest.NewSingleFileFS("/foo", fname, gopcode)
+	gopClTestFS(t, gblConf, fs, "main", expected)
+}
+
 func gopClTestEx(t *testing.T, conf *cl.Config, pkgname, gopcode, expected string) {
 	fs := parsertest.NewSingleFileFS("/foo", "bar.gop", gopcode)
 	gopClTestFS(t, conf, fs, pkgname, expected)
@@ -148,6 +153,7 @@ import (
 	fmt "fmt"
 	os "os"
 	iox "github.com/goplus/gop/builtin/iox"
+	errors "github.com/qiniu/x/errors"
 )
 
 func main() {
@@ -155,6 +161,7 @@ func main() {
 		var _gop_err error
 		_gop_ret, _gop_err = os.Open("foo.txt")
 		if _gop_err != nil {
+			_gop_err = errors.NewFrame(_gop_err, "open(\"foo.txt\")", "/foo/bar.gop", 2, "main.main")
 			panic(_gop_err)
 		}
 		return
@@ -912,7 +919,10 @@ func main() {
 	println(a, b)
 }`, `package main
 
-import fmt "fmt"
+import (
+	fmt "fmt"
+	errors "github.com/qiniu/x/errors"
+)
 
 func t() (int, int, error) {
 	return 0, 0, nil
@@ -922,6 +932,7 @@ func main() {
 		var _gop_err error
 		_gop_ret, _gop_ret2, _gop_err = t()
 		if _gop_err != nil {
+			_gop_err = errors.NewFrame(_gop_err, "t()", "/foo/bar.gop", 9, "main.main")
 			panic(_gop_err)
 		}
 		return
@@ -943,6 +954,8 @@ func main() {
 	t()!
 }`, `package main
 
+import errors "github.com/qiniu/x/errors"
+
 func t() error {
 	return nil
 }
@@ -951,6 +964,7 @@ func main() {
 		var _gop_err error
 		_gop_err = t()
 		if _gop_err != nil {
+			_gop_err = errors.NewFrame(_gop_err, "t()", "/foo/bar.gop", 9, "main.main")
 			panic(_gop_err)
 		}
 		return
@@ -1454,6 +1468,7 @@ func foo(script string) {
 import (
 	fmt "fmt"
 	goptest "github.com/goplus/gop/ast/goptest"
+	errors "github.com/qiniu/x/errors"
 	gopq "github.com/goplus/gop/ast/gopq"
 )
 
@@ -1462,6 +1477,7 @@ func foo(script string) {
 		var _gop_err error
 		_gop_ret, _gop_err = goptest.New(script)
 		if _gop_err != nil {
+			_gop_err = errors.NewFrame(_gop_err, "goptest.New(script)", "/foo/bar.gop", 4, "main.foo")
 			panic(_gop_err)
 		}
 		return
@@ -1486,6 +1502,7 @@ func foo(script string) {
 import (
 	fmt "fmt"
 	goptest "github.com/goplus/gop/ast/goptest"
+	errors "github.com/qiniu/x/errors"
 	gopq "github.com/goplus/gop/ast/gopq"
 )
 
@@ -1494,6 +1511,7 @@ func foo(script string) {
 		var _gop_err error
 		_gop_ret, _gop_err = goptest.New(script)
 		if _gop_err != nil {
+			_gop_err = errors.NewFrame(_gop_err, "goptest.New(script)", "/foo/bar.gop", 4, "main.foo")
 			panic(_gop_err)
 		}
 		return
@@ -1513,7 +1531,10 @@ func add(x, y string) (int, error) {
 }
 `, `package main
 
-import strconv "strconv"
+import (
+	strconv "strconv"
+	errors "github.com/qiniu/x/errors"
+)
 
 func add(x string, y string) (int, error) {
 	var _autoGo_1 int
@@ -1521,6 +1542,7 @@ func add(x string, y string) (int, error) {
 		var _gop_err error
 		_autoGo_1, _gop_err = strconv.Atoi(x)
 		if _gop_err != nil {
+			_gop_err = errors.NewFrame(_gop_err, "strconv.Atoi(x)", "/foo/bar.gop", 5, "main.add")
 			return 0, _gop_err
 		}
 		goto _autoGo_2
@@ -1531,6 +1553,7 @@ func add(x string, y string) (int, error) {
 		var _gop_err error
 		_autoGo_3, _gop_err = strconv.Atoi(y)
 		if _gop_err != nil {
+			_gop_err = errors.NewFrame(_gop_err, "strconv.Atoi(y)", "/foo/bar.gop", 5, "main.add")
 			return 0, _gop_err
 		}
 		goto _autoGo_4
@@ -1577,12 +1600,16 @@ func TestErrWrapPanic(t *testing.T) {
 var ret int = println("Hi")!
 `, `package main
 
-import fmt "fmt"
+import (
+	fmt "fmt"
+	errors "github.com/qiniu/x/errors"
+)
 
 var ret int = func() (_gop_ret int) {
 	var _gop_err error
 	_gop_ret, _gop_err = fmt.Println("Hi")
 	if _gop_err != nil {
+		_gop_err = errors.NewFrame(_gop_err, "println(\"Hi\")", "/foo/bar.gop", 2, "main.main")
 		panic(_gop_err)
 	}
 	return
@@ -1599,6 +1626,8 @@ func mkdir(name string) error {
 mkdir! "foo"
 `, `package main
 
+import errors "github.com/qiniu/x/errors"
+
 func mkdir(name string) error {
 	return nil
 }
@@ -1607,6 +1636,7 @@ func main() {
 		var _gop_err error
 		_gop_err = mkdir("foo")
 		if _gop_err != nil {
+			_gop_err = errors.NewFrame(_gop_err, "mkdir \"foo\"", "/foo/bar.gop", 6, "main.main")
 			panic(_gop_err)
 		}
 		return
@@ -1624,6 +1654,8 @@ func foo() (func(), error) {
 foo()!()
 `, `package main
 
+import errors "github.com/qiniu/x/errors"
+
 func foo() (func(), error) {
 	return nil, nil
 }
@@ -1632,6 +1664,7 @@ func main() {
 		var _gop_err error
 		_gop_ret, _gop_err = foo()
 		if _gop_err != nil {
+			_gop_err = errors.NewFrame(_gop_err, "foo()", "/foo/bar.gop", 6, "main.main")
 			panic(_gop_err)
 		}
 		return
@@ -1983,6 +2016,8 @@ func bar(conf ...Config) {
 
 foo({A: 1})
 bar({A: 2})
+foo({})
+bar({})
 `, `package main
 
 type Config struct {
@@ -1996,6 +2031,8 @@ func bar(conf ...Config) {
 func main() {
 	foo(&Config{A: 1})
 	bar(Config{A: 2})
+	foo(&Config{})
+	bar(Config{})
 }
 `)
 }
@@ -4239,3 +4276,78 @@ func main() {
 }
 `)
 }
+
+func TestCommandNotExpr(t *testing.T) {
+	gopClTest(t, `
+println !true
+`, `package main
+
+import fmt "fmt"
+
+func main() {
+	fmt.Println(false)
+}
+`)
+	gopClTest(t, `
+a := true
+println !a
+`, `package main
+
+import fmt "fmt"
+
+func main() {
+	a := true
+	fmt.Println(!a)
+}
+`)
+	gopClTest(t, `
+println !func() bool { return true }()
+`, `package main
+
+import fmt "fmt"
+
+func main() {
+	fmt.Println(!func() bool {
+		return true
+	}())
+}
+`)
+}
+
+func TestClassFileGopx(t *testing.T) {
+	gopClTestFile(t, `
+var (
+	BaseClass
+	Width, Height float64
+	AggClass
+)
+
+type BaseClass struct{
+	x int
+	y int
+}
+type AggClass struct{}
+
+func Area() float64 {
+	return Width * Height
+}
+`, `package main
+
+type BaseClass struct {
+	x int
+	y int
+}
+type AggClass struct {
+}
+type Rect struct {
+	BaseClass
+	Width  float64
+	Height float64
+	AggClass
+}
+
+func (this *Rect) Area() float64 {
+	return this.Width * this.Height
+}
+`, "Rect.gopx")
+}
diff --git a/cl/error_msg_test.go b/cl/error_msg_test.go
index 16385ee9..31085b14 100644
--- a/cl/error_msg_test.go
+++ b/cl/error_msg_test.go
@@ -30,11 +30,11 @@ import (
 )
 
 func codeErrorTest(t *testing.T, msg, src string) {
-	codeErrorTestEx(t, "main", msg, src)
+	codeErrorTestEx(t, "main", "bar.gop", msg, src)
 }
 
-func codeErrorTestEx(t *testing.T, pkgname, msg, src string) {
-	fs := parsertest.NewSingleFileFS("/foo", "bar.gop", src)
+func codeErrorTestEx(t *testing.T, pkgname, filename, msg, src string) {
+	fs := parsertest.NewSingleFileFS("/foo", filename, src)
 	pkgs, err := parser.ParseFSDir(gblFset, fs, "/foo", parser.Config{})
 	if err != nil {
 		scanner.PrintError(os.Stderr, err)
@@ -684,7 +684,7 @@ func TestErrNoEntrypoint(t *testing.T) {
 		`./bar.gop:1:9: undefined: abc`,
 		`println abc
 `)
-	codeErrorTestEx(t, "bar",
+	codeErrorTestEx(t, "bar", "bar.gop",
 		`./bar.gop:2:9: undefined: abc`,
 		`package bar
 println abc
@@ -866,3 +866,39 @@ import (
 )
 `)
 }
+
+func TestErrClassFileGopx(t *testing.T) {
+	codeErrorTestEx(t, "main", "Rect.gopx",
+		`./Rect.gopx:3:2: cannot assign value to field in class file`, `
+var (
+	i int = 1
+)
+println "hello"
+`)
+	codeErrorTestEx(t, "main", "Rect.gopx",
+		`./Rect.gopx:5:2: A redeclared
+	./Rect.gopx:3:2 other declaration of A`, `
+var (
+	A
+	i int
+	A
+)
+type A struct{}
+println "hello"
+`)
+}
+
+func TestErrVarInFunc(t *testing.T) {
+	codeErrorTest(t, `./bar.gop:6:10: too few arguments in call to set("box")
+	have (untyped string)
+	want (name string, v int)
+./bar.gop:7:10: undefined: a`, `
+func set(name string, v int) string {
+	return name
+}
+func test() {
+	var a = set("box")
+	println(a)
+}
+`)
+}
diff --git a/cl/typeparams_test.go b/cl/typeparams_test.go
new file mode 100644
index 00000000..64adabba
--- /dev/null
+++ b/cl/typeparams_test.go
@@ -0,0 +1,569 @@
+//go:build go1.18
+// +build go1.18
+
+package cl_test
+
+import (
+	"go/scanner"
+	"os"
+	"runtime"
+	"testing"
+
+	"github.com/goplus/gop/cl"
+	"github.com/goplus/gop/parser"
+	"github.com/goplus/gop/parser/parsertest"
+)
+
+func TestTypeParamsFunc(t *testing.T) {
+	gopMixedClTest(t, "main", `package main
+
+type Number interface {
+	~int | ~uint | (float64)
+}
+
+func Sum[T Number](vec []T) T {
+	var sum T
+	for _, elt := range vec {
+		sum = sum + elt
+	}
+	return sum
+}
+
+func At[T interface{ ~[]E }, E any](x T, i int) E {
+	return x[i]
+}
+
+func Loader[T1 any, T2 any](p1 T1, p2 T2) T1 {
+	return p1
+}
+
+func Add[T1 any, T2 ~int|~uint](v1 T1, v2 ...T2) (sum T2) {
+	println(v1)
+	for _, v := range v2 {
+		sum += v
+	}
+	return sum
+}
+
+type Int []int
+var MyInts = Int{1,2,3,4}
+`, `
+_ = At[[]int]
+_ = At[[]int,int]
+_ = Sum[int]
+_ = Loader[*int,int]
+_ = Add[string,int]
+var s1 int = Sum([1, 2, 3])
+var s2 int = Sum[int]([1, 2, 3])
+var v1 int = At([1, 2, 3], 1)
+var v2 int = At[[]int]([1, 2, 3], 1)
+var v3 int = At[[]int, int]([1, 2, 3], 1)
+var n1 int = Add("hello", 1, 2, 3)
+var n2 int = Add[string]("hello", 1, 2, 3)
+var n3 int = Add[string, int]("hello", 1, 2, 3)
+var n4 int = Add("hello", [1, 2, 3]...)
+var n5 int = Add("hello", [1, 2, 3]...)
+var n6 int = Add[string]("hello", MyInts...)
+var n7 int = Add[string, int]("hello", [1, 2, 3]...)
+var p1 *int = Loader[*int](nil, 1)
+var p2 *int = Loader[*int, int](nil, 1)
+var fn1 func(p1 *int, p2 int) *int
+fn1 = Loader[*int, int]
+fn1(nil, 1)
+`, `package main
+
+func main() {
+	_ = At[[]int]
+	_ = At[[]int, int]
+	_ = Sum[int]
+	_ = Loader[*int, int]
+	_ = Add[string, int]
+	var s1 int = Sum([]int{1, 2, 3})
+	var s2 int = Sum[int]([]int{1, 2, 3})
+	var v1 int = At([]int{1, 2, 3}, 1)
+	var v2 int = At[[]int]([]int{1, 2, 3}, 1)
+	var v3 int = At[[]int, int]([]int{1, 2, 3}, 1)
+	var n1 int = Add("hello", 1, 2, 3)
+	var n2 int = Add[string]("hello", 1, 2, 3)
+	var n3 int = Add[string, int]("hello", 1, 2, 3)
+	var n4 int = Add("hello", []int{1, 2, 3}...)
+	var n5 int = Add("hello", []int{1, 2, 3}...)
+	var n6 int = Add[string]("hello", MyInts...)
+	var n7 int = Add[string, int]("hello", []int{1, 2, 3}...)
+	var p1 *int = Loader[*int](nil, 1)
+	var p2 *int = Loader[*int, int](nil, 1)
+	var fn1 func(p1 *int, p2 int) *int
+	fn1 = Loader[*int, int]
+	fn1(nil, 1)
+}
+`)
+}
+
+func TestTypeParamsType(t *testing.T) {
+	gopMixedClTest(t, "main", `package main
+type Data[T any] struct {
+	v T
+}
+func(p *Data[T]) Set(v T) {
+	p.v = v
+}
+func(p *(Data[T1])) Set2(v T1) {
+	p.v = v
+}
+type sliceOf[E any] interface {
+	~[]E
+}
+type Slice[S sliceOf[T], T any] struct {
+	Data S
+}
+func (p *Slice[S, T]) Append(t ...T) S {
+	p.Data = append(p.Data, t...)
+	return p.Data
+}
+func (p *Slice[S1, T1]) Append2(t ...T1) S1 {
+	p.Data = append(p.Data, t...)
+	return p.Data
+}
+type (
+	DataInt = Data[int]
+	SliceInt = Slice[[]int,int]
+)
+`, `
+type DataString = Data[string]
+type SliceString = Slice[[]string,string]
+println(DataInt{1}.v)
+println(DataString{"hello"}.v)
+println(Data[int]{100}.v)
+println(Data[string]{"hello"}.v)
+println(Data[struct{X int;Y int}]{}.v.X)
+
+v1 := SliceInt{}
+v2 := SliceString{}
+v3 := Slice[[]int,int]{}
+v3.Append([1,2,3,4]...)
+v3.Append2([1,2,3,4]...)
+`, `package main
+
+import fmt "fmt"
+
+type DataString = Data[string]
+type SliceString = Slice[[]string, string]
+
+func main() {
+	fmt.Println(Data[int]{1}.v)
+	fmt.Println(Data[string]{"hello"}.v)
+	fmt.Println(Data[int]{100}.v)
+	fmt.Println(Data[string]{"hello"}.v)
+	fmt.Println(Data[struct {
+		X int
+		Y int
+	}]{}.v.X)
+	v1 := Slice[[]int, int]{}
+	v2 := Slice[[]string, string]{}
+	v3 := Slice[[]int, int]{}
+	v3.Append([]int{1, 2, 3, 4}...)
+	v3.Append2([]int{1, 2, 3, 4}...)
+}
+`)
+}
+
+func TestTypeParamsComparable(t *testing.T) {
+	gopMixedClTest(t, "main", `package main
+// Index returns the index of x in s, or -1 if not found.
+func Index[T comparable](s []T, x T) int {
+	for i, v := range s {
+		// v and x are type T, which has the comparable
+		// constraint, so we can use == here.
+		if v == x {
+			return i
+		}
+	}
+	return -1
+}
+
+var IndexInt = Index[int]
+`, `
+v1 := IndexInt([1,2,3,4],1)
+v2 := Index([1,2,3,4],1)
+v3 := Index[int]([1,2,3,4],1)
+v4 := Index(["a","b","c","d"],"b")
+`, `package main
+
+func main() {
+	v1 := IndexInt([]int{1, 2, 3, 4}, 1)
+	v2 := Index([]int{1, 2, 3, 4}, 1)
+	v3 := Index[int]([]int{1, 2, 3, 4}, 1)
+	v4 := Index([]string{"a", "b", "c", "d"}, "b")
+}
+`)
+}
+
+func mixedErrorTest(t *testing.T, msg, gocode, gopcode string) {
+	mixedErrorTestEx(t, "main", msg, gocode, gopcode)
+}
+
+func mixedErrorTestEx(t *testing.T, pkgname, msg, gocode, gopcode string) {
+	fs := parsertest.NewTwoFilesFS("/foo", "a.go", gocode, "b.gop", gopcode)
+	pkgs, err := parser.ParseFSDir(gblFset, fs, "/foo", parser.Config{})
+	if err != nil {
+		scanner.PrintError(os.Stderr, err)
+		t.Fatal("parser.ParseFSDir failed")
+	}
+	conf := *gblConf
+	conf.NoFileLine = false
+	conf.WorkingDir = "/foo"
+	conf.TargetDir = "/foo"
+	bar := pkgs[pkgname]
+	_, err = cl.NewPackage("", bar, &conf)
+	if err == nil {
+		t.Fatal("no error?")
+	}
+	if ret := err.Error(); ret != msg {
+		t.Fatalf("\nError: \"%s\"\nExpected: \"%s\"\n", ret, msg)
+	}
+}
+
+func TestTypeParamsErrorInstantiate(t *testing.T) {
+	var msg string
+	switch runtime.Version()[:6] {
+	case "go1.18":
+		msg = `./b.gop:2:1: uint does not implement Number`
+	case "go1.19":
+		msg = `./b.gop:2:1: uint does not implement Number (uint missing in ~int | float64)`
+	default:
+		msg = `./b.gop:2:1: uint does not satisfy Number (uint missing in ~int | float64)`
+	}
+
+	mixedErrorTest(t, msg, `
+package main
+
+type Number interface {
+	~int | float64
+}
+
+func Sum[T Number](vec []T) T {
+	var sum T
+	for _, elt := range vec {
+		sum = sum + elt
+	}
+	return sum
+}
+
+var	SumInt = Sum[int]
+`, `
+Sum[uint]
+`)
+}
+
+func TestTypeParamsErrorMatch(t *testing.T) {
+	var msg string
+	switch runtime.Version()[:6] {
+	case "go1.18", "go1.19":
+		msg = `./b.gop:2:5: T does not match ~[]E`
+	default:
+		msg = `./b.gop:2:5: int does not match ~[]E`
+	}
+	mixedErrorTest(t, msg, `
+package main
+
+func At[T interface{ ~[]E }, E any](x T, i int) E {
+	return x[i]
+}
+
+var	AtInt = At[[]int]
+`, `
+_ = At[int]
+`)
+}
+
+func TestTypeParamsErrInferFunc(t *testing.T) {
+	mixedErrorTest(t, `./b.gop:2:5: cannot infer T2 (/foo/a.go:4:21)`, `
+package main
+
+func Loader[T1 any, T2 any](p1 T1, p2 T2) T1 {
+	return p1
+}
+`, `
+_ = Loader[int]
+`)
+}
+
+func TestTypeParamsErrArgumentsParameters1(t *testing.T) {
+	mixedErrorTest(t, `./b.gop:2:7: got 1 type arguments but Data[T1, T2 interface{}] has 2 type parameters`, `
+package main
+
+type Data[T1 any, T2 any] struct {
+	v1 T1
+	v2 T2
+}
+`, `
+var v Data[int]
+`)
+}
+
+func TestTypeParamsErrArgumentsParameters2(t *testing.T) {
+	mixedErrorTest(t, `./b.gop:2:7: got 3 type arguments but Data[T1, T2 interface{}] has 2 type parameters`, `
+package main
+
+type Data[T1 any, T2 any] struct {
+	v1 T1
+	v2 T2
+}
+`, `
+var v Data[int,int,int]
+`)
+}
+
+func TestTypeParamsErrArgumentsParameters3(t *testing.T) {
+	mixedErrorTest(t, `./b.gop:2:1: got 3 type arguments but func[T1, T2 interface{}](t1 T1, t2 T2) has 2 type parameters`, `
+package main
+
+func Test[T1 any, T2 any](t1 T1, t2 T2) {
+	println(t1,t2)
+}
+`, `
+Test[int,int,int](1,2)
+`)
+}
+
+func TestTypeParamsErrCallArguments1(t *testing.T) {
+	mixedErrorTest(t, `./b.gop:2:1: not enough arguments in call to Test
+	have (untyped int)
+	want (T1, T2)`, `
+package main
+
+func Test[T1 any, T2 any](t1 T1, t2 T2) {
+	println(t1,t2)
+}
+`, `
+Test(1)
+`)
+}
+
+func TestTypeParamsErrCallArguments2(t *testing.T) {
+	mixedErrorTest(t, `./b.gop:2:1: too many arguments in call to Test
+	have (untyped int, untyped int, untyped int)
+	want (T1, T2)`, `
+package main
+
+func Test[T1 any, T2 any](t1 T1, t2 T2) {
+	println(t1,t2)
+}
+`, `
+Test(1,2,3)
+`)
+}
+
+func TestTypeParamsErrCallArguments3(t *testing.T) {
+	mixedErrorTest(t, `./b.gop:2:1: too many arguments in call to Test
+	have (untyped int, untyped int)
+	want ()`, `
+package main
+
+func Test[T1 any, T2 any]() {
+	var t1 T1
+	var t2 T2
+	println(t1,t2)
+}
+`, `
+Test(1,2)
+`)
+}
+
+func TestTypeParamsErrCallVariadicArguments1(t *testing.T) {
+	mixedErrorTest(t, `./b.gop:2:1: not enough arguments in call to Add
+	have ()
+	want (T1, ...T2)`, `
+package main
+
+func Add[T1 any, T2 ~int|~uint](v1 T1, v2 ...T2) (sum T2) {
+	println(v1)
+	for _, v := range v2 {
+		sum += v
+	}
+	return sum
+}
+`, `
+Add()
+`)
+}
+
+func TestTypeParamsErrCallVariadicArguments2(t *testing.T) {
+	mixedErrorTest(t, `./b.gop:2:1: cannot infer T2 (./a.go:4:18)`, `
+package main
+
+func Add[T1 any, T2 ~int|~uint](v1 T1, v2 ...T2) (sum T2) {
+	println(v1)
+	for _, v := range v2 {
+		sum += v
+	}
+	return sum
+}
+`, `
+Add(1)
+`)
+}
+
+func TestTypeParamsRecvTypeError1(t *testing.T) {
+	mixedErrorTest(t, `./a.go:7:9: cannot use generic type Data[T interface{}] without instantiation`, `
+package main
+
+type Data[T any] struct {
+	v T
+}
+func(p *Data) Test() {
+}
+`, `
+Data[int]{}.Test()
+`)
+}
+
+func TestTypeParamsRecvTypeError2(t *testing.T) {
+	mixedErrorTest(t, `./a.go:7:9: got 2 arguments but 1 type parameters`, `
+package main
+
+type Data[T any] struct {
+	v T
+}
+func(p *Data[T1,T2]) Test() {
+}
+`, `
+Data[int]{}.Test()
+`)
+}
+
+func TestTypeParamsRecvTypeError3(t *testing.T) {
+	mixedErrorTest(t, `./a.go:8:9: got 1 type parameter, but receiver base type declares 2`, `
+package main
+
+type Data[T1 any, T2 any] struct {
+	v1 T1
+	v2 T2
+}
+func(p *Data[T1]) Test() {
+}
+`, `
+Data[int,int]{}.Test()
+`)
+}
+
+func TestGenericTypeWithoutInst1(t *testing.T) {
+	mixedErrorTest(t, `./a.go:8:9: cannot use generic type Data[T1, T2 interface{}] without instantiation`, `
+package main
+
+type Data[T1 any, T2 any] struct {
+	v1 T1
+	v2 T2
+}
+func(p *Data) Test() {
+}
+`, `
+var v Data[int,int]
+`)
+}
+
+func TestGenericTypeWithoutInst2(t *testing.T) {
+	mixedErrorTest(t, `./a.go:10:2: cannot use generic type Data[T1, T2 interface{}] without instantiation`, `
+package main
+
+type Data[T1 any, T2 any] struct {
+	v1 T1
+	v2 T2
+}
+
+type My[T any] struct {
+	Data
+}
+`, `
+var v My[int]
+`)
+}
+
+func TestGenericTypeWithoutInst3(t *testing.T) {
+	mixedErrorTest(t, `./a.go:10:2: cannot use generic type Data[T1, T2 interface{}] without instantiation`, `
+package main
+
+type Data[T1 any, T2 any] struct {
+	v1 T1
+	v2 T2
+}
+
+type My struct {
+	Data
+}
+`, `
+var v My
+`)
+}
+
+func TestGenericTypeWithoutInst4(t *testing.T) {
+	mixedErrorTest(t, `./a.go:10:15: cannot use generic type Data[T1, T2 interface{}] without instantiation`, `
+package main
+
+type Data[T1 any, T2 any] struct {
+	v1 T1
+	v2 T2
+}
+
+type My struct {
+	v map[string]Data
+}
+`, `
+var v My
+`)
+}
+
+func TestGenericTypeWithoutInst5(t *testing.T) {
+	mixedErrorTest(t, `./b.gop:2:7: cannot use generic type Data[T1, T2 interface{}] without instantiation`, `
+package main
+
+type Data[T1 any, T2 any] struct {
+	v1 T1
+	v2 T2
+}
+`, `
+var v Data
+`)
+}
+
+func TestGenericTypeWithoutInst6(t *testing.T) {
+	mixedErrorTest(t, `./b.gop:2:8: cannot use generic type Data[T1, T2 interface{}] without instantiation`, `
+package main
+
+type Data[T1 any, T2 any] struct {
+	v1 T1
+	v2 T2
+}
+`, `
+type T Data
+`)
+}
+
+func TestGenericTypeWithoutInst7(t *testing.T) {
+	mixedErrorTest(t, `./b.gop:3:2: cannot use generic type Data[T1, T2 interface{}] without instantiation`, `
+package main
+
+type Data[T1 any, T2 any] struct {
+	v1 T1
+	v2 T2
+}
+`, `
+type My struct {
+	Data
+}
+`)
+}
+
+func TestGenericTypeWithoutInst8(t *testing.T) {
+	mixedErrorTest(t, `./b.gop:2:23: cannot use generic type Data[T1, T2 interface{}] without instantiation`, `
+package main
+
+type Data[T1 any, T2 any] struct {
+	v1 T1
+	v2 T2
+}
+`, `
+func test(v1 int, v2 *Data) {
+}
+`)
+}
diff --git a/parser/_testdata/cmdlinestyle3/cmd3.gop b/parser/_testdata/cmdlinestyle3/cmd3.gop
index b1f81ffd..921db5f9 100644
--- a/parser/_testdata/cmdlinestyle3/cmd3.gop
+++ b/parser/_testdata/cmdlinestyle3/cmd3.gop
@@ -1,1 +1,2 @@
 println &x
+println !x
diff --git a/parser/_testdata/cmdlinestyle3/parser.expect b/parser/_testdata/cmdlinestyle3/parser.expect
index 762e943c..8335924b 100644
--- a/parser/_testdata/cmdlinestyle3/parser.expect
+++ b/parser/_testdata/cmdlinestyle3/parser.expect
@@ -25,3 +25,15 @@ ast.FuncDecl:
                   X:
                     ast.Ident:
                       Name: x
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: println
+              Args:
+                ast.UnaryExpr:
+                  Op: !
+                  X:
+                    ast.Ident:
+                      Name: x
diff --git a/parser/_testdata/gopxtest/bar.gopx b/parser/_testdata/gopxtest/bar.gopx
new file mode 100644
index 00000000..783e9630
--- /dev/null
+++ b/parser/_testdata/gopxtest/bar.gopx
@@ -0,0 +1,9 @@
+var (
+	A
+	v int
+	B
+)
+
+type A struct{}
+
+type B struct{}
diff --git a/parser/_testdata/gopxtest/parser.expect b/parser/_testdata/gopxtest/parser.expect
new file mode 100644
index 00000000..a4a5dabf
--- /dev/null
+++ b/parser/_testdata/gopxtest/parser.expect
@@ -0,0 +1,43 @@
+package main
+
+file bar.gopx
+ast.GenDecl:
+  Tok: var
+  Specs:
+    ast.ValueSpec:
+      Names:
+        ast.Ident:
+          Name: A
+    ast.ValueSpec:
+      Names:
+        ast.Ident:
+          Name: v
+      Type:
+        ast.Ident:
+          Name: int
+    ast.ValueSpec:
+      Names:
+        ast.Ident:
+          Name: B
+ast.GenDecl:
+  Tok: type
+  Specs:
+    ast.TypeSpec:
+      Name:
+        ast.Ident:
+          Name: A
+      Type:
+        ast.StructType:
+          Fields:
+            ast.FieldList:
+ast.GenDecl:
+  Tok: type
+  Specs:
+    ast.TypeSpec:
+      Name:
+        ast.Ident:
+          Name: B
+      Type:
+        ast.StructType:
+          Fields:
+            ast.FieldList:
diff --git a/parser/parser_test.go b/parser/parser_test.go
index c8995720..d96c6c86 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -50,6 +50,21 @@ func testErrCode(t *testing.T, code string, errExp, panicExp string) {
 	}
 }
 
+func testClassErrCode(t *testing.T, code string, errExp, panicExp string) {
+	defer func() {
+		if e := recover(); e != nil {
+			if panicMsg(e) != panicExp {
+				t.Fatal("testErrCode panic:", e)
+			}
+		}
+	}()
+	fset := token.NewFileSet()
+	_, err := Parse(fset, "/foo/bar.gopx", code, ParseGoPlusClass)
+	if err == nil || err.Error() != errExp {
+		t.Fatal("testErrCode error:", err)
+	}
+}
+
 func TestErrTuple(t *testing.T) {
 	testErrCode(t, `println (1,2)*2`, `/foo/bar.gop:1:9: tuple is not supported`, ``)
 	testErrCode(t, `println 2*(1,2)`, `/foo/bar.gop:1:13: expected ')', found ','`, ``)
@@ -375,4 +390,12 @@ ast.FuncDecl:
 `)
 }
 
+func TestClassErrCode(t *testing.T) {
+	testClassErrCode(t, `var (
+	A,B
+	v int
+)
+`, `/foo/bar.gopx:2:2: syntax error: unexpected newline, expected type`, ``)
+}
+
 // -----------------------------------------------------------------------------
diff --git a/parser/parserdir_go118_test.go b/parser/parserdir_go118_test.go
new file mode 100644
index 00000000..d4e0bf05
--- /dev/null
+++ b/parser/parserdir_go118_test.go
@@ -0,0 +1,30 @@
+//go:build go1.18
+// +build go1.18
+
+/*
+ * Copyright (c) 2022 The GoPlus Authors (goplus.org). All rights reserved.
+ *
+ * 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 parser
+
+import (
+	"testing"
+)
+
+func TestFromInstance(t *testing.T) {
+	testFromDir(t, "", "./_instance")
+}
+
+// -----------------------------------------------------------------------------
diff --git a/printer/gop_test.go b/printer/gop_test.go
index 70293574..f9996874 100644
--- a/printer/gop_test.go
+++ b/printer/gop_test.go
@@ -121,7 +121,7 @@ func testFrom(t *testing.T, fpath, sel string, mode int) {
 
 	if (mode & excludeFormatSource) == 0 {
 		t.Run("format.Source "+fpath, func(t *testing.T) {
-			res, err := format.Source(src, fpath)
+			res, err := format.Source(src, false, fpath)
 			if err != nil {
 				t.Fatal("Source failed:", err)
 			}
diff --git a/testdata/helloc2go/README.md b/testdata/helloc2go/README.md
index b94b584a..2071d303 100644
--- a/testdata/helloc2go/README.md
+++ b/testdata/helloc2go/README.md
@@ -24,3 +24,7 @@ Run `gop run .` to see the output of this example:
 Hello, c2go!
 Hi,     3.1
 ```
+
+### Give a Star! ⭐
+
+If you like or are using Go+ to learn or start your projects, please give it a star. Thanks!
diff --git a/testdata/mixgo/README.md b/testdata/mixgo/README.md
index 12edcc48..a995f3df 100644
--- a/testdata/mixgo/README.md
+++ b/testdata/mixgo/README.md
@@ -31,3 +31,7 @@ Run `gop run .` to see the output of this example:
 Mix Go and Go+
 Hello, world
 ```
+
+### Give a Star! ⭐
+
+If you like or are using Go+ to learn or start your projects, please give it a star. Thanks!