Repository: goplus/gop
Pull request #2179 (2025-03-15 23:21:03)

v1.3.6


Patch:
diff --git a/README.md b/README.md
index 10ca954c..d1daa298 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
 </p>
 <h1>The Go+ Programming Language</h1>
 
-[goplus.org](https://goplus.org) | [Docs](doc/docs.md) | [Go+ vs. Go](doc/goplus-vs-go.md) | [Tutorials](https://tutorial.goplus.org/) | [Playground](https://play.goplus.org) | [iGo+ Playground](https://repl.goplus.org/) | [Contributing & compiler design](doc/contributing.md)
+[goplus.org](https://goplus.org) | [Docs](doc/docs.md) | [Go+ vs. Go](doc/goplus-vs-go.md) | [Tutorials](https://tutorial.goplus.org/) | [Playground](https://play.goplus.org) | [Go+ REPL (iGo+)](https://repl.goplus.org/) | [Contributing & compiler design](doc/contributing.md)
 
 </div>
 
diff --git a/VERSION b/VERSION
deleted file mode 100644
index 3252f641..00000000
--- a/VERSION
+++ /dev/null
@@ -1,1 +0,0 @@
-v1.3.5
diff --git a/ast/ast.go b/ast/ast.go
index e3ee3c9e..d7257687 100644
--- a/ast/ast.go
+++ b/ast/ast.go
@@ -1079,75 +1079,6 @@ func (*GenDecl) declNode()  {}
 func (*FuncDecl) declNode() {}
 
 // ----------------------------------------------------------------------------
-// Files and packages
-
-type FileType = int16
-
-// A File node represents a Go+ source file.
-//
-// The Comments list contains all comments in the source file in order of
-// appearance, including the comments that are pointed to from other nodes
-// via Doc and Comment fields.
-//
-// For correct printing of source code containing comments (using packages
-// go/format and go/printer), special care must be taken to update comments
-// when a File's syntax tree is modified: For printing, comments are interspersed
-// between tokens based on their position. If syntax tree nodes are
-// removed or moved, relevant comments in their vicinity must also be removed
-// (from the File.Comments list) or moved accordingly (by updating their
-// positions). A CommentMap may be used to facilitate some of these operations.
-//
-// Whether and how a comment is associated with a node depends on the
-// interpretation of the syntax tree by the manipulating program: Except for Doc
-// and Comment comments directly associated with nodes, the remaining comments
-// are "free-floating" (see also issues #18593, #20744).
-type File struct {
-	Doc     *CommentGroup // associated documentation; or nil
-	Package token.Pos     // position of "package" keyword; or NoPos
-	Name    *Ident        // package name
-	Decls   []Decl        // top-level declarations; or nil
-
-	Scope       *Scope          // package scope (this file only)
-	Imports     []*ImportSpec   // imports in this file
-	Unresolved  []*Ident        // unresolved identifiers in this file
-	Comments    []*CommentGroup // list of all comments in the source file
-	Code        []byte
-	ShadowEntry *FuncDecl // no entrypoint func to indicate the module entry point.
-	NoPkgDecl   bool      // no `package xxx` declaration
-	IsClass     bool      // is a classfile (including normal .gox file)
-	IsProj      bool      // is a project classfile
-	IsNormalGox bool      // is a normal .gox file
-}
-
-// There is no entrypoint func to indicate the module entry point.
-func (f *File) HasShadowEntry() bool {
-	return f.ShadowEntry != nil
-}
-
-// HasPkgDecl checks if `package xxx` exists or not.
-func (f *File) HasPkgDecl() bool {
-	return f.Package != token.NoPos
-}
-
-// Pos returns position of first character belonging to the node.
-func (f *File) Pos() token.Pos {
-	if f.Package != token.NoPos {
-		return f.Package
-	}
-	return f.Name.NamePos
-}
-
-// End returns position of first character immediately after the node.
-func (f *File) End() token.Pos {
-	for n := len(f.Decls) - 1; n >= 0; n-- {
-		d := f.Decls[n]
-		if fn, ok := d.(*FuncDecl); ok && fn.Shadow && fn != f.ShadowEntry {
-			continue
-		}
-		return d.End()
-	}
-	return f.Name.End()
-}
 
 // A Package node represents a set of source files
 // collectively building a Go+ package.
@@ -1164,3 +1095,5 @@ func (p *Package) Pos() token.Pos { return token.NoPos }
 
 // End returns position of first character immediately after the node.
 func (p *Package) End() token.Pos { return token.NoPos }
+
+// ----------------------------------------------------------------------------
diff --git a/ast/ast_gop.go b/ast/ast_gop.go
index f464b5a1..8e66ff5c 100644
--- a/ast/ast_gop.go
+++ b/ast/ast_gop.go
@@ -59,7 +59,8 @@ func (*OverloadFuncDecl) declNode() {}
 
 // -----------------------------------------------------------------------------
 
-// A DomainTextLit node represents a domain text literal.
+// A DomainTextLit node represents a domain-specific text literal.
+// https://github.com/goplus/gop/issues/2143
 //
 //	tpl`...`
 type DomainTextLit struct {
@@ -345,11 +346,11 @@ func (*RangeExpr) exprNode() {}
 
 // -----------------------------------------------------------------------------
 
-// ForPhrase represents `for k, v <- container if init; cond` phrase.
+// ForPhrase represents `for k, v in container if init; cond` phrase.
 type ForPhrase struct {
 	For        token.Pos // position of "for" keyword
 	Key, Value *Ident    // Key may be nil
-	TokPos     token.Pos // position of "<-" operator
+	TokPos     token.Pos // position of "in" operator
 	X          Expr      // value to range over
 	IfPos      token.Pos // position of if or comma; or NoPos
 	Init       Stmt      // initialization statement; or nil
@@ -371,10 +372,10 @@ func (p *ForPhrase) exprNode() {}
 
 // ComprehensionExpr represents one of the following expressions:
 //
-//	`[vexpr for k1, v1 <- container1, cond1 ...]` or
-//	`{vexpr for k1, v1 <- container1, cond1 ...}` or
-//	`{kexpr: vexpr for k1, v1 <- container1, cond1 ...}` or
-//	`{for k1, v1 <- container1, cond1 ...}` or
+//	`[vexpr for k1, v1 in container1, cond1 ...]` or
+//	`{vexpr for k1, v1 in container1, cond1 ...}` or
+//	`{kexpr: vexpr for k1, v1 in container1, cond1 ...}` or
+//	`{for k1, v1 in container1, cond1 ...}` or
 type ComprehensionExpr struct {
 	Lpos token.Pos   // position of "[" or "{"
 	Tok  token.Token // token.LBRACK '[' or token.LBRACE '{'
@@ -397,7 +398,7 @@ func (*ComprehensionExpr) exprNode() {}
 
 // -----------------------------------------------------------------------------
 
-// A ForPhraseStmt represents a for statement with a for <- clause.
+// A ForPhraseStmt represents a for statement with a for..in clause.
 type ForPhraseStmt struct {
 	*ForPhrase
 	Body *BlockStmt
@@ -435,3 +436,79 @@ func (s *SendStmt) End() token.Pos {
 }
 
 // -----------------------------------------------------------------------------
+
+// A File node represents a Go+ source file.
+//
+// The Comments list contains all comments in the source file in order of
+// appearance, including the comments that are pointed to from other nodes
+// via Doc and Comment fields.
+//
+// For correct printing of source code containing comments (using packages
+// go/format and go/printer), special care must be taken to update comments
+// when a File's syntax tree is modified: For printing, comments are interspersed
+// between tokens based on their position. If syntax tree nodes are
+// removed or moved, relevant comments in their vicinity must also be removed
+// (from the File.Comments list) or moved accordingly (by updating their
+// positions). A CommentMap may be used to facilitate some of these operations.
+//
+// Whether and how a comment is associated with a node depends on the
+// interpretation of the syntax tree by the manipulating program: Except for Doc
+// and Comment comments directly associated with nodes, the remaining comments
+// are "free-floating" (see also issues #18593, #20744).
+type File struct {
+	Doc     *CommentGroup // associated documentation; or nil
+	Package token.Pos     // position of "package" keyword; or NoPos
+	Name    *Ident        // package name
+	Decls   []Decl        // top-level declarations; or nil
+
+	Scope       *Scope          // package scope (this file only)
+	Imports     []*ImportSpec   // imports in this file
+	Unresolved  []*Ident        // unresolved identifiers in this file
+	Comments    []*CommentGroup // list of all comments in the source file
+	Code        []byte
+	ShadowEntry *FuncDecl // indicate the module entry point.
+	NoPkgDecl   bool      // no `package xxx` declaration
+	IsClass     bool      // is a classfile (including normal .gox file)
+	IsProj      bool      // is a project classfile
+	IsNormalGox bool      // is a normal .gox file
+}
+
+// There is no entrypoint func to indicate the module entry point.
+func (f *File) HasShadowEntry() bool {
+	return f.ShadowEntry != nil
+}
+
+// HasPkgDecl checks if `package xxx` exists or not.
+func (f *File) HasPkgDecl() bool {
+	return f.Package != token.NoPos
+}
+
+// Pos returns position of first character belonging to the node.
+func (f *File) Pos() token.Pos {
+	if f.Package != token.NoPos {
+		return f.Package
+	}
+	// if no package clause, name records the position of the first token in the file
+	return f.Name.NamePos
+}
+
+// End returns position of first character immediately after the node.
+func (f *File) End() token.Pos {
+	if f.ShadowEntry != nil { // has shadow entry
+		return f.ShadowEntry.End()
+	}
+	for n := len(f.Decls) - 1; n >= 0; n-- {
+		d := f.Decls[n]
+		if fn, ok := d.(*FuncDecl); ok && fn.Shadow {
+			// skip shadow functions like Classfname (see cl.astFnClassfname)
+			continue
+		}
+		return d.End()
+	}
+	if f.Package != token.NoPos { // has package clause
+		return f.Name.End()
+	}
+	return f.Name.Pos()
+}
+
+// -----------------------------------------------------------------------------
diff --git a/ast/gopq/dom.go b/ast/gopq/dom.go
index 440ebdf4..9f063e30 100644
--- a/ast/gopq/dom.go
+++ b/ast/gopq/dom.go
@@ -38,7 +38,7 @@ func (p astPackages) ForEach(filter func(node Node) error) error {
 	return nil
 }
 
-func (p astPackages) Obj() interface{} {
+func (p astPackages) Obj() any {
 	return p
 }
 
@@ -58,7 +58,7 @@ func (p astPackage) ForEach(filter func(node Node) error) error {
 	return nil
 }
 
-func (p astPackage) Obj() interface{} {
+func (p astPackage) Obj() any {
 	return p.Package
 }
 
@@ -78,7 +78,7 @@ func (p astFile) ForEach(filter func(node Node) error) error {
 	return nil
 }
 
-func (p astFile) Obj() interface{} {
+func (p astFile) Obj() any {
 	return p.File
 }
 
@@ -100,7 +100,7 @@ func (p *astDecl) ForEach(filter func(node Node) error) error {
 	return nil
 }
 
-func (p *astDecl) Obj() interface{} {
+func (p *astDecl) Obj() any {
 	return p.Decl
 }
 
@@ -114,7 +114,7 @@ func (p *astSpec) ForEach(filter func(node Node) error) error {
 	return nil
 }
 
-func (p *astSpec) Obj() interface{} {
+func (p *astSpec) Obj() any {
 	return p.Spec
 }
 
@@ -155,7 +155,7 @@ func (p *astStmt) ForEach(filter func(node Node) error) error {
 	return nil
 }
 
-func (p *astStmt) Obj() interface{} {
+func (p *astStmt) Obj() any {
 	return p.Stmt
 }
 
@@ -169,7 +169,7 @@ func (p *astExpr) ForEach(filter func(node Node) error) error {
 	return nil
 }
 
-func (p *astExpr) Obj() interface{} {
+func (p *astExpr) Obj() any {
 	return p.Expr
 }
 
diff --git a/ast/gopq/gopq.go b/ast/gopq/gopq.go
index 8e86c96c..49c22a65 100644
--- a/ast/gopq/gopq.go
+++ b/ast/gopq/gopq.go
@@ -51,7 +51,7 @@ var (
 type Node interface {
 	ast.Node
 	ForEach(filter func(node Node) error) error
-	Obj() interface{}
+	Obj() any
 }
 
 // NodeEnum - node enumerator
@@ -66,7 +66,7 @@ type NodeSet struct {
 }
 
 // FromFile calls ParseFile for a single file and returns *ast.File node set.
-func FromFile(fset *token.FileSet, filename string, src interface{}, mode parser.Mode) (doc NodeSet, err error) {
+func FromFile(fset *token.FileSet, filename string, src any, mode parser.Mode) (doc NodeSet, err error) {
 	file, err := parser.ParseFile(fset, filename, src, mode)
 	if err != nil {
 		return
@@ -77,7 +77,7 @@ func FromFile(fset *token.FileSet, filename string, src interface{}, mode parser
 // FromFSFile calls ParseFSFile for a single file and returns *ast.File node set.
 func FromFSFile(
 	fset *token.FileSet, fs fsx.FileSystem,
-	filename string, src interface{}, mode parser.Mode) (doc NodeSet, err error) {
+	filename string, src any, mode parser.Mode) (doc NodeSet, err error) {
 	file, err := parser.ParseFSFile(fset, fs, filename, src, mode)
 	if err != nil {
 		return
diff --git a/ast/gopq/helper.go b/ast/gopq/helper.go
index ffeda30a..de81bf57 100644
--- a/ast/gopq/helper.go
+++ b/ast/gopq/helper.go
@@ -148,7 +148,7 @@ func NameOf(node Node) string {
 	return getName(node.Obj(), false)
 }
 
-func getName(v interface{}, useEmpty bool) string {
+func getName(v any, useEmpty bool) string {
 	switch v := v.(type) {
 	case *ast.FuncDecl:
 		return v.Name.Name
diff --git a/ast/print.go b/ast/print.go
index de68a208..4544e176 100644
--- a/ast/print.go
+++ b/ast/print.go
@@ -49,17 +49,17 @@ func NotNilFilter(_ string, v reflect.Value) bool {
 // struct fields for which f(fieldname, fieldvalue) is true are
 // printed; all others are filtered from the output. Unexported
 // struct fields are never printed.
-func Fprint(w io.Writer, fset *token.FileSet, x interface{}, f FieldFilter) error {
+func Fprint(w io.Writer, fset *token.FileSet, x any, f FieldFilter) error {
 	return fprint(w, fset, x, f)
 }
 
-func fprint(w io.Writer, fset *token.FileSet, x interface{}, f FieldFilter) (err error) {
+func fprint(w io.Writer, fset *token.FileSet, x any, f FieldFilter) (err error) {
 	// setup printer
 	p := printer{
 		output: w,
 		fset:   fset,
 		filter: f,
-		ptrmap: make(map[interface{}]int),
+		ptrmap: make(map[any]int),
 		last:   '\n', // force printing of line number on first line
 	}
 
@@ -83,7 +83,7 @@ func fprint(w io.Writer, fset *token.FileSet, x interface{}, f FieldFilter) (err
 
 // Print prints x to standard output, skipping nil fields.
 // Print(fset, x) is the same as Fprint(os.Stdout, fset, x, NotNilFilter).
-func Print(fset *token.FileSet, x interface{}) error {
+func Print(fset *token.FileSet, x any) error {
 	return Fprint(os.Stdout, fset, x, NotNilFilter)
 }
 
@@ -91,10 +91,10 @@ type printer struct {
 	output io.Writer
 	fset   *token.FileSet
 	filter FieldFilter
-	ptrmap map[interface{}]int // *T -> line number
-	indent int                 // current indentation level
-	last   byte                // the last byte processed by Write
-	line   int                 // current line number
+	ptrmap map[any]int // *T -> line number
+	indent int         // current indentation level
+	last   byte        // the last byte processed by Write
+	line   int         // current line number
 }
 
 var indent = []byte(".  ")
@@ -138,7 +138,7 @@ type localError struct {
 }
 
 // printf is a convenience wrapper that takes care of print errors.
-func (p *printer) printf(format string, args ...interface{}) {
+func (p *printer) printf(format string, args ...any) {
 	if _, err := fmt.Fprintf(p, format, args...); err != nil {
 		panic(localError{err})
 	}
diff --git a/ast/resolve.go b/ast/resolve.go
index 54e3696f..770177e5 100644
--- a/ast/resolve.go
+++ b/ast/resolve.go
@@ -35,7 +35,7 @@ func (p *pkgBuilder) error(pos token.Pos, msg string) {
 	p.errors.Add(p.fset.Position(pos), msg)
 }
 
-func (p *pkgBuilder) errorf(pos token.Pos, format string, args ...interface{}) {
+func (p *pkgBuilder) errorf(pos token.Pos, format string, args ...any) {
 	p.error(pos, fmt.Sprintf(format, args...))
 }
 
diff --git a/ast/scope.go b/ast/scope.go
index cd0a7025..992d3802 100644
--- a/ast/scope.go
+++ b/ast/scope.go
@@ -84,10 +84,10 @@ func (s *Scope) String() string {
 //	Con     int               iota for the respective declaration
 type Object struct {
 	Kind ObjKind
-	Name string      // declared name
-	Decl interface{} // corresponding Field, XxxSpec, FuncDecl, LabeledStmt, AssignStmt, Scope; or nil
-	Data interface{} // object-specific data; or nil
-	Type interface{} // placeholder for type information; may be nil
+	Name string // declared name
+	Decl any    // corresponding Field, XxxSpec, FuncDecl, LabeledStmt, AssignStmt, Scope; or nil
+	Data any    // object-specific data; or nil
+	Type any    // placeholder for type information; may be nil
 }
 
 // NewObj creates a new object of a given kind and name.
diff --git a/ast/walk.go b/ast/walk.go
index 38cfc3fc..b3287fc3 100644
--- a/ast/walk.go
+++ b/ast/walk.go
@@ -27,29 +27,9 @@ type Visitor interface {
 	Visit(node Node) (w Visitor)
 }
 
-// Helper functions for common node lists. They may be empty.
-
-func walkIdentList(v Visitor, list []*Ident) {
-	for _, x := range list {
-		Walk(v, x)
-	}
-}
-
-func walkExprList(v Visitor, list []Expr) {
-	for _, x := range list {
-		Walk(v, x)
-	}
-}
-
-func walkStmtList(v Visitor, list []Stmt) {
-	for _, x := range list {
-		Walk(v, x)
-	}
-}
-
-func walkDeclList(v Visitor, list []Decl) {
-	for _, x := range list {
-		Walk(v, x)
+func walkList[N Node](v Visitor, list []N) {
+	for _, node := range list {
+		Walk(v, node)
 	}
 }
 
@@ -75,16 +55,16 @@ func Walk(v Visitor, node Node) {
 		// nothing to do
 
 	case *CommentGroup:
-		for _, c := range n.List {
-			Walk(v, c)
-		}
+		walkList(v, n.List)
 
 	case *Field:
 		if n.Doc != nil {
 			Walk(v, n.Doc)
 		}
-		walkIdentList(v, n.Names)
-		Walk(v, n.Type)
+		walkList(v, n.Names)
+		if n.Type != nil {
+			Walk(v, n.Type)
+		}
 		if n.Tag != nil {
 			Walk(v, n.Tag)
 		}
@@ -93,9 +73,7 @@ func Walk(v Visitor, node Node) {
 		}
 
 	case *FieldList:
-		for _, f := range n.List {
-			Walk(v, f)
-		}
+		walkList(v, n.List)
 
 	// Expressions
 	case *BadExpr, *Ident, *NumberUnitLit:
@@ -133,7 +111,7 @@ func Walk(v Visitor, node Node) {
 		if n.Type != nil {
 			Walk(v, n.Type)
 		}
-		walkExprList(v, n.Elts)
+		walkList(v, n.Elts)
 
 	case *ParenExpr:
 		Walk(v, n.X)
@@ -148,7 +126,7 @@ func Walk(v Visitor, node Node) {
 
 	case *IndexListExpr:
 		Walk(v, n.X)
-		walkExprList(v, n.Indices)
+		walkList(v, n.Indices)
 
 	case *SliceExpr:
 		Walk(v, n.X)
@@ -170,7 +148,7 @@ func Walk(v Visitor, node Node) {
 
 	case *CallExpr:
 		Walk(v, n.Fun)
-		walkExprList(v, n.Args)
+		walkList(v, n.Args)
 
 	case *StarExpr:
 		Walk(v, n.X)
@@ -197,6 +175,9 @@ func Walk(v Visitor, node Node) {
 		Walk(v, n.Fields)
 
 	case *FuncType:
+		if n.TypeParams != nil {
+			Walk(v, n.TypeParams)
+		}
 		if n.Params != nil {
 			Walk(v, n.Params)
 		}
@@ -233,16 +214,14 @@ func Walk(v Visitor, node Node) {
 
 	case *SendStmt:
 		Walk(v, n.Chan)
-		for _, val := range n.Values {
-			Walk(v, val)
-		}
+		walkList(v, n.Values)
 
 	case *IncDecStmt:
 		Walk(v, n.X)
 
 	case *AssignStmt:
-		walkExprList(v, n.Lhs)
-		walkExprList(v, n.Rhs)
+		walkList(v, n.Lhs)
+		walkList(v, n.Rhs)
 
 	case *GoStmt:
 		Walk(v, n.Call)
@@ -251,7 +230,7 @@ func Walk(v Visitor, node Node) {
 		Walk(v, n.Call)
 
 	case *ReturnStmt:
-		walkExprList(v, n.Results)
+		walkList(v, n.Results)
 
 	case *BranchStmt:
 		if n.Label != nil {
@@ -259,7 +238,7 @@ func Walk(v Visitor, node Node) {
 		}
 
 	case *BlockStmt:
-		walkStmtList(v, n.List)
+		walkList(v, n.List)
 
 	case *IfStmt:
 		if n.Init != nil {
@@ -272,8 +251,8 @@ func Walk(v Visitor, node Node) {
 		}
 
 	case *CaseClause:
-		walkExprList(v, n.List)
-		walkStmtList(v, n.Body)
+		walkList(v, n.List)
+		walkList(v, n.Body)
 
 	case *SwitchStmt:
 		if n.Init != nil {
@@ -295,7 +274,7 @@ func Walk(v Visitor, node Node) {
 		if n.Comm != nil {
 			Walk(v, n.Comm)
 		}
-		walkStmtList(v, n.Body)
+		walkList(v, n.Body)
 
 	case *SelectStmt:
 		Walk(v, n.Body)
@@ -339,11 +318,11 @@ func Walk(v Visitor, node Node) {
 		if n.Doc != nil {
 			Walk(v, n.Doc)
 		}
-		walkIdentList(v, n.Names)
+		walkList(v, n.Names)
 		if n.Type != nil {
 			Walk(v, n.Type)
 		}
-		walkExprList(v, n.Values)
+		walkList(v, n.Values)
 		if n.Comment != nil {
 			Walk(v, n.Comment)
 		}
@@ -353,6 +332,9 @@ func Walk(v Visitor, node Node) {
 			Walk(v, n.Doc)
 		}
 		Walk(v, n.Name)
+		if n.TypeParams != nil {
+			Walk(v, n.TypeParams)
+		}
 		Walk(v, n.Type)
 		if n.Comment != nil {
 			Walk(v, n.Comment)
@@ -365,9 +347,7 @@ func Walk(v Visitor, node Node) {
 		if n.Doc != nil {
 			Walk(v, n.Doc)
 		}
-		for _, s := range n.Specs {
-			Walk(v, s)
-		}
+		walkList(v, n.Specs)
 
 	case *FuncDecl:
 		if !n.Shadow { // not a shadow entry
@@ -392,7 +372,7 @@ func Walk(v Visitor, node Node) {
 		if !n.NoPkgDecl {
 			Walk(v, n.Name)
 		}
-		walkDeclList(v, n.Decls)
+		walkList(v, n.Decls)
 		// don't walk n.Comments - they have been
 		// visited already through the individual
 		// nodes
@@ -404,14 +384,14 @@ func Walk(v Visitor, node Node) {
 
 	// Go+ extended expr and stmt
 	case *SliceLit:
-		walkExprList(v, n.Elts)
+		walkList(v, n.Elts)
 
 	case *LambdaExpr:
-		walkIdentList(v, n.Lhs)
-		walkExprList(v, n.Rhs)
+		walkList(v, n.Lhs)
+		walkList(v, n.Rhs)
 
 	case *LambdaExpr2:
-		walkIdentList(v, n.Lhs)
+		walkList(v, n.Lhs)
 		Walk(v, n.Body)
 
 	case *ForPhrase:
@@ -433,9 +413,7 @@ func Walk(v Visitor, node Node) {
 		if n.Elt != nil {
 			Walk(v, n.Elt)
 		}
-		for _, x := range n.Fors {
-			Walk(v, x)
-		}
+		walkList(v, n.Fors)
 
 	case *ForPhraseStmt:
 		Walk(v, n.ForPhrase)
@@ -466,7 +444,7 @@ func Walk(v Visitor, node Node) {
 			Walk(v, n.Recv)
 		}
 		Walk(v, n.Name)
-		walkExprList(v, n.Funcs)
+		walkList(v, n.Funcs)
 
 	case *EnvExpr:
 		Walk(v, n.Name)
diff --git a/builtin/ng/big.go b/builtin/ng/big.go
index 73a70a26..00ebeee9 100644
--- a/builtin/ng/big.go
+++ b/builtin/ng/big.go
@@ -20,7 +20,7 @@ import (
 	"math/big"
 )
 
-func Gop_istmp(a interface{}) bool {
+func Gop_istmp(a any) bool {
 	return false
 }
 
diff --git a/cl/classfile.go b/cl/classfile.go
index badebbc2..0bdf1c56 100644
--- a/cl/classfile.go
+++ b/cl/classfile.go
@@ -47,8 +47,9 @@ type spxObj struct {
 }
 
 type gmxProject struct {
-	gameClass  string            // <gmtype>.gmx
-	game       gogen.Ref         // Game (project base class)
+	gameClass  string    // <gmtype>.gmx
+	game       gogen.Ref // Game (project base class)
+	spfeats    spriteFeat
 	sprite     map[string]spxObj // .spx => Sprite
 	sptypes    []string          // <sptype>.spx
 	scheds     []string
@@ -62,6 +63,43 @@ type gmxProject struct {
 	hasMain_   bool
 }
 
+type spriteFeat uint
+
+const (
+	spriteClassfname spriteFeat = 1 << iota
+	spriteClassclone
+)
+
+func spriteFeatures(game gogen.Ref) (feats spriteFeat) {
+	if mainFn := findMethod(game, "Main"); mainFn != nil {
+		sig := mainFn.Type().(*types.Signature)
+		if t, ok := gogen.CheckSigFuncEx(sig); ok {
+			if t, ok := t.(*gogen.TyTemplateRecvMethod); ok {
+				sig = t.Func.Type().(*types.Signature)
+			}
+		}
+		if sig.Variadic() {
+			in := sig.Params()
+			last := in.At(in.Len() - 1)
+			elt := last.Type().(*types.Slice).Elem()
+			if tn, ok := elt.(*types.Named); ok {
+				elt = tn.Underlying()
+			}
+			if intf, ok := elt.(*types.Interface); ok {
+				for i, n := 0, intf.NumMethods(); i < n; i++ {
+					switch intf.Method(i).Name() {
+					case "Classfname":
+						feats |= spriteClassfname
+					case "Classclone":
+						feats |= spriteClassclone
+					}
+				}
+			}
+		}
+	}
+	return
+}
+
 func (p *gmxProject) hasMain() bool {
 	if !p.hasMain_ {
 		imps := p.pkgImps
@@ -165,6 +203,7 @@ func loadClass(ctx *pkgCtx, pkg *gogen.Package, file string, f *ast.File, conf *
 		spx := p.pkgImps[0]
 		if gt.Class != "" {
 			p.game, p.gameIsPtr = spxRef(spx, gt.Class)
+			p.spfeats = spriteFeatures(p.game)
 		}
 		p.sprite = make(map[string]spxObj)
 		for _, v := range gt.Works {
@@ -460,6 +499,40 @@ func astFnClassfname(c *gmxClass) *ast.FuncDecl {
 	}
 }
 
+func astFnClassclone() *ast.FuncDecl {
+	ret := &ast.Ident{Name: "_gop_ret"}
+	return &ast.FuncDecl{
+		Name: &ast.Ident{
+			Name: "Classclone",
+		},
+		Type: &ast.FuncType{
+			Params: &ast.FieldList{},
+			Results: &ast.FieldList{
+				List: []*ast.Field{
+					{Type: &ast.Ident{Name: "any"}},
+				},
+			},
+		},
+		Body: &ast.BlockStmt{
+			List: []ast.Stmt{
+				&ast.AssignStmt{
+					Lhs: []ast.Expr{ret},
+					Tok: token.DEFINE,
+					Rhs: []ast.Expr{
+						&ast.StarExpr{X: &ast.Ident{Name: "this"}},
+					},
+				},
+				&ast.ReturnStmt{
+					Results: []ast.Expr{
+						&ast.UnaryExpr{Op: token.AND, X: ret},
+					},
+				},
+			},
+		},
+		Shadow: true,
+	}
+}
+
 func astEmptyEntrypoint(f *ast.File) {
 	var entry = getEntrypoint(f)
 	var hasEntry bool
diff --git a/cl/compile.go b/cl/compile.go
index 24e22e87..2c56fbb3 100644
--- a/cl/compile.go
+++ b/cl/compile.go
@@ -226,6 +226,9 @@ func (p *nodeInterp) Caller(node ast.Node) string {
 
 func (p *nodeInterp) LoadExpr(node ast.Node) string {
 	start := node.Pos()
+	if start == token.NoPos {
+		return ""
+	}
 	pos := p.fset.Position(start)
 	f := p.files[pos.Filename]
 	n := int(node.End() - start)
@@ -409,11 +412,11 @@ func (p *pkgCtx) newCodeError(pos token.Pos, msg string) error {
 	return &gogen.CodeError{Fset: p.nodeInterp, Pos: pos, Msg: msg}
 }
 
-func (p *pkgCtx) newCodeErrorf(pos token.Pos, format string, args ...interface{}) error {
+func (p *pkgCtx) newCodeErrorf(pos token.Pos, format string, args ...any) error {
 	return &gogen.CodeError{Fset: p.nodeInterp, Pos: pos, Msg: fmt.Sprintf(format, args...)}
 }
 
-func (p *pkgCtx) handleErrorf(pos token.Pos, format string, args ...interface{}) {
+func (p *pkgCtx) handleErrorf(pos token.Pos, format string, args ...any) {
 	p.handleErr(p.newCodeErrorf(pos, format, args...))
 }
 
@@ -460,12 +463,12 @@ func (p *pkgCtx) loadSymbol(name string) bool {
 	return false
 }
 
-func (p *pkgCtx) handleRecover(e interface{}, src ast.Node) {
+func (p *pkgCtx) handleRecover(e any, src ast.Node) {
 	err := p.recoverErr(e, src)
 	p.handleErr(err)
 }
 
-func (p *pkgCtx) recoverErr(e interface{}, src ast.Node) error {
+func (p *pkgCtx) recoverErr(e any, src ast.Node) error {
 	err, ok := e.(error)
 	if !ok {
 		if src != nil {
@@ -725,6 +728,8 @@ func preloadGopFile(p *gogen.Package, ctx *blockCtx, file string, f *ast.File, c
 	var baseType types.Type
 	var spxProj string
 	var spxClass bool
+	var spxClassfname bool
+	var spxClassclone bool
 	var goxTestFile bool
 	var parent = ctx.pkgCtx
 	if f.IsClass {
@@ -760,7 +765,10 @@ func preloadGopFile(p *gogen.Package, ctx *blockCtx, file string, f *ast.File, c
 				sp := proj.sprite[c.ext]
 				o := sp.obj
 				ctx.baseClass = o
-				baseTypeName, baseType, spxProj, spxClass = o.Name(), o.Type(), sp.proj, true
+				baseTypeName, baseType, spxProj = o.Name(), o.Type(), sp.proj
+				spxClassfname = (proj.spfeats & spriteClassfname) != 0
+				spxClassclone = (proj.spfeats & spriteClassclone) != 0
+				spxClass = true
 			}
 		}
 	}
@@ -864,9 +872,13 @@ func preloadGopFile(p *gogen.Package, ctx *blockCtx, file string, f *ast.File, c
 			},
 		}}}
 		// func Classfname() string
-		if spxClass {
+		if spxClassfname {
 			f.Decls = append(f.Decls, astFnClassfname(c))
 		}
+		// func Classclone() any
+		if spxClassclone {
+			f.Decls = append(f.Decls, astFnClassclone())
+		}
 	}
 
 	if d := f.ShadowEntry; d != nil {
@@ -1298,7 +1310,7 @@ func loadFunc(ctx *blockCtx, recv *types.Var, name string, d *ast.FuncDecl, genB
 	var pkg = ctx.pkg
 	var sigBase *types.Signature
 	if d.Shadow {
-		if recv != nil {
+		if recv != nil && (name == "Main" || name == "MainEntry") {
 			if base := ctx.baseClass; base != nil {
 				if f := findMethod(base, name); f != nil {
 					sigBase = makeMainSig(recv, f)
diff --git a/cl/expr.go b/cl/expr.go
index da4db1b8..f82ac816 100644
--- a/cl/expr.go
+++ b/cl/expr.go
@@ -1077,13 +1077,40 @@ const (
 	tplPkgPath = "github.com/goplus/gop/tpl"
 )
 
+// https://github.com/goplus/gop/issues/2143
+// domainTag`...` => domainTag.new(`...`)
 func compileDomainTextLit(ctx *blockCtx, v *ast.DomainTextLit) {
-	switch v.Domain.Name {
-	case "tpl": // tpl`...` => tpl.new(`...`)
-		ctx.cb.Val(ctx.pkg.Import(tplPkgPath).Ref("New")).
-			Val(&goast.BasicLit{Kind: gotoken.STRING, Value: v.Value}, v).
-			Call(1)
+	var cb = ctx.cb
+	var imp gogen.PkgRef
+	var name = v.Domain.Name
+	if pi, ok := ctx.findImport(name); ok {
+		imp = pi.PkgRef
+		if pi.Path() == "golang.org/x/net/html" {
+			// html`...` => html.Parse(strings.NewReader(`...`))
+			cb.Val(imp.Ref("Parse")).
+				Val(ctx.pkg.Import("strings").Ref("NewReader")).
+				Val(&goast.BasicLit{Kind: gotoken.STRING, Value: v.Value}, v).
+				CallWith(1, 0, v).
+				CallWith(1, 0, v)
+			return
+		}
+	} else {
+		var path string
+		if name == "tpl" {
+			path = tplPkgPath
+		} else {
+			path = tplPkgPath + "/encoding/" + name
+		}
+		imp = ctx.pkg.Import(path)
+		/* TODO(xsw):
+		if imp = ctx.pkg.TryImport(path); imp.Types == nil {
+			panic("compileDomainTextLit TODO: unknown domain: " + name)
+		}
+		*/
 	}
+	cb.Val(imp.Ref("New")).
+		Val(&goast.BasicLit{Kind: gotoken.STRING, Value: v.Value}, v).
+		CallWith(1, 0, v)
 }
 
 const (
@@ -1277,22 +1304,25 @@ func compileCompositeLitEx(ctx *blockCtx, v *ast.CompositeLit, expected types.Ty
 			return err
 		}
 		n := len(v.Elts)
-		if isMap(underlying) {
+		switch underlying.(type) {
+		case *types.Slice:
+			ctx.cb.SliceLitEx(typ, n<<kind, kind == compositeLitKeyVal, v)
+		case *types.Array:
+			ctx.cb.ArrayLitEx(typ, n<<kind, kind == compositeLitKeyVal, v)
+		case *types.Map:
 			if kind == compositeLitVal && n > 0 {
 				return ctx.newCodeError(v.Pos(), "missing key in map literal")
 			}
-			if err := ctx.cb.MapLitEx(typ, n<<1, v); err != nil {
+			if err := compileMapLitEx(ctx, typ, n, v); err != nil {
 				return err
 			}
-		} else {
-			switch underlying.(type) {
-			case *types.Slice:
-				ctx.cb.SliceLitEx(typ, n<<kind, kind == compositeLitKeyVal, v)
-			case *types.Array:
-				ctx.cb.ArrayLitEx(typ, n<<kind, kind == compositeLitKeyVal, v)
-			default:
+		default:
+			if kind == compositeLitVal && n > 0 {
 				return ctx.newCodeErrorf(v.Pos(), "invalid composite literal type %v", typ)
 			}
+			if err := compileMapLitEx(ctx, nil, n, v); err != nil {
+				return err
+			}
 		}
 	}
 	if hasPtr {
@@ -1305,12 +1335,14 @@ func compileCompositeLitEx(ctx *blockCtx, v *ast.CompositeLit, expected types.Ty
 	return nil
 }
 
-func isMap(tu types.Type) bool {
-	if tu == nil { // map can omit type
-		return true
-	}
-	_, ok := tu.(*types.Map)
-	return ok
+func compileMapLitEx(ctx *blockCtx, typ types.Type, n int, v *ast.CompositeLit) (err error) {
+	defer func() {
+		if e := recover(); e != nil {
+			err = ctx.newCodeError(v.Pos(), "invalid map literal")
+		}
+	}()
+	err = ctx.cb.MapLitEx(typ, n<<1, v)
+	return
 }
 
 func isMapOrStruct(tu types.Type) bool {
@@ -1335,10 +1367,10 @@ func compileSliceLit(ctx *blockCtx, v *ast.SliceLit, typ types.Type, noPanic ...
 	for _, elt := range v.Elts {
 		compileExpr(ctx, elt)
 	}
-	if sliceHasTypeParam(ctx, typ) {
-		ctx.cb.SliceLitEx(nil, n, false, v)
-	} else {
+	if isSpecificSliceType(ctx, typ) {
 		ctx.cb.SliceLitEx(typ, n, false, v)
+	} else {
+		ctx.cb.SliceLitEx(nil, n, false, v)
 	}
 	return
 }
@@ -1488,8 +1520,12 @@ func compileErrWrapExpr(ctx *blockCtx, v *ast.ErrWrapExpr, inFlags int) {
 	if !useClosure && (cb.Scope().Parent() == types.Universe) {
 		panic("TODO: can't use expr? in global")
 	}
-
-	compileExpr(ctx, v.X, inFlags)
+	expr := v.X
+	switch expr.(type) {
+	case *ast.Ident, *ast.SelectorExpr:
+		expr = &ast.CallExpr{Fun: expr}
+	}
+	compileExpr(ctx, expr, inFlags)
 	x := cb.InternalStack().Pop()
 	n := 0
 	results, ok := x.Type.(*types.Tuple)
diff --git a/cl/internal/spx/game.go b/cl/internal/spx/game.go
index e7672028..d645abaa 100644
--- a/cl/internal/spx/game.go
+++ b/cl/internal/spx/game.go
@@ -26,7 +26,7 @@ type Sound string
 type MyGame struct {
 }
 
-func Gopt_MyGame_Main(game interface{}) {
+func Gopt_MyGame_Main(game any) {
 }
 
 func (p *MyGame) Ls(n int) {}
@@ -51,20 +51,20 @@ func (p *MyGame) Broadcast__0(msg string) {
 func (p *MyGame) Broadcast__1(msg string, wait bool) {
 }
 
-func (p *MyGame) Broadcast__2(msg string, data interface{}, wait bool) {
+func (p *MyGame) Broadcast__2(msg string, data any, wait bool) {
 }
 
 func (p *MyGame) Play(media string, wait ...bool) {
 }
 
-func (p *MyGame) sendMessage(data interface{}) {
+func (p *MyGame) sendMessage(data any) {
 }
 
-func (p *MyGame) SendMessage(data interface{}) {
+func (p *MyGame) SendMessage(data any) {
 	p.sendMessage(data)
 }
 
-func Gopt_MyGame_Run(game interface{}, resource string) error {
+func Gopt_MyGame_Run(game any, resource string) error {
 	return nil
 }
 
diff --git a/cl/internal/spx/sprite.go b/cl/internal/spx/sprite.go
index bdcf7c22..a7d1eed9 100644
--- a/cl/internal/spx/sprite.go
+++ b/cl/internal/spx/sprite.go
@@ -33,7 +33,7 @@ func (p *Entry) Vector() *pkg.Vector {
 	return &p.vec
 }
 
-func (p *Sprite) SetCostume(costume interface{}) {
+func (p *Sprite) SetCostume(costume any) {
 }
 
 func (p *Sprite) Say(msg string, secs ...float64) {
@@ -47,38 +47,38 @@ type Mesher interface {
 	Name() string
 }
 
-func Gopt_Sprite_Clone__0(sprite interface{}) {
+func Gopt_Sprite_Clone__0(sprite any) {
 }
 
-func Gopt_Sprite_Clone__1(sprite interface{}, data interface{}) {
+func Gopt_Sprite_Clone__1(sprite any, data any) {
 }
 
-func Gopt_Sprite_OnKey__0(sprite interface{}, a string, fn func()) {
+func Gopt_Sprite_OnKey__0(sprite any, a string, fn func()) {
 }
 
-func Gopt_Sprite_OnKey__1(sprite interface{}, a string, fn func(key string)) {
+func Gopt_Sprite_OnKey__1(sprite any, a string, fn func(key string)) {
 }
 
-func Gopt_Sprite_OnKey__2(sprite interface{}, a []string, fn func()) {
+func Gopt_Sprite_OnKey__2(sprite any, a []string, fn func()) {
 }
 
-func Gopt_Sprite_OnKey__3(sprite interface{}, a []string, fn func(key string)) {
+func Gopt_Sprite_OnKey__3(sprite any, a []string, fn func(key string)) {
 }
 
-func Gopt_Sprite_OnKey__4(sprite interface{}, a []Mesher, fn func()) {
+func Gopt_Sprite_OnKey__4(sprite any, a []Mesher, fn func()) {
 }
 
-func Gopt_Sprite_OnKey__5(sprite interface{}, a []Mesher, fn func(key Mesher)) {
+func Gopt_Sprite_OnKey__5(sprite any, a []Mesher, fn func(key Mesher)) {
 }
 
-func Gopt_Sprite_OnKey__6(sprite interface{}, a []string, b []string, fn func(key string)) {
+func Gopt_Sprite_OnKey__6(sprite any, a []string, b []string, fn func(key string)) {
 }
 
-func Gopt_Sprite_OnKey__7(sprite interface{}, a []string, b []Mesher, fn func(key string)) {
+func Gopt_Sprite_OnKey__7(sprite any, a []string, b []Mesher, fn func(key string)) {
 }
 
-func Gopt_Sprite_OnKey__8(sprite interface{}, x int, y int) {
+func Gopt_Sprite_OnKey__8(sprite any, x int, y int) {
 }
 
-func Gopt_Sprite_OnKey2(sprite interface{}, a string, fn func(key string)) {
+func Gopt_Sprite_OnKey2(sprite any, a string, fn func(key string)) {
 }
diff --git a/cl/internal/spx3/spx3.go b/cl/internal/spx3/spx3.go
index 551fcdb1..3697a3a7 100644
--- a/cl/internal/spx3/spx3.go
+++ b/cl/internal/spx3/spx3.go
@@ -40,5 +40,11 @@ func (p *Sprite) Name() string {
 
 func (p *Sprite) Main(name string) {}
 
-func Gopt_Game_Main(game interface{ initGame() }, workers ...interface{ Main(name string) }) {
+type iHandler interface {
+	Main(name string)
+	Classfname() string
+	Classclone() any
+}
+
+func Gopt_Game_Main(game interface{ initGame() }, workers ...iHandler) {
 }
diff --git a/cl/stmt.go b/cl/stmt.go
index 48d92bb0..3f5f66e4 100644
--- a/cl/stmt.go
+++ b/cl/stmt.go
@@ -977,7 +977,7 @@ func compileType(ctx *blockCtx, t *ast.TypeSpec) {
 }
 
 type (
-	valueMap  map[interface{}][]valueType // underlying Go value -> valueType
+	valueMap  map[any][]valueType // underlying Go value -> valueType
 	valueType struct {
 		pos token.Pos
 		typ types.Type
@@ -985,7 +985,7 @@ type (
 )
 
 // goVal returns the Go value for val, or nil.
-func goVal(val constant.Value) interface{} {
+func goVal(val constant.Value) any {
 	// val should exist, but be conservative and check
 	if val == nil {
 		return nil
diff --git a/cl/typeparams.go b/cl/typeparams.go
index 95a3df90..ede7ff34 100644
--- a/cl/typeparams.go
+++ b/cl/typeparams.go
@@ -255,7 +255,7 @@ func isTypeParam(t types.Type) bool {
 	return ok
 }
 
-func sliceHasTypeParam(ctx *blockCtx, typ types.Type) bool {
+func isSpecificSliceType(ctx *blockCtx, typ types.Type) bool {
 	if typ == nil {
 		return false
 	}
@@ -265,9 +265,11 @@ func sliceHasTypeParam(ctx *blockCtx, typ types.Type) bool {
 		t = getUnderlying(ctx, tt).(*types.Slice)
 	case *types.Slice:
 		t = tt
+	default:
+		return false
 	}
 	_, ok := t.Elem().(*types.TypeParam)
-	return ok
+	return !ok
 }
 
 func boundTypeParam(ctx *blockCtx, x ast.Expr) types.Type {
diff --git a/cmd/internal/env/env.go b/cmd/internal/env/env.go
index d5afc1f2..b634b954 100644
--- a/cmd/internal/env/env.go
+++ b/cmd/internal/env/env.go
@@ -64,7 +64,7 @@ func runCmd(_ *base.Command, args []string) {
 		log.Fatalln("run go env failed:", err)
 	}
 
-	var gopEnv map[string]interface{}
+	var gopEnv map[string]any
 	if err := json.Unmarshal(stdout.Bytes(), &gopEnv); err != nil {
 		log.Fatal("decode json of go env failed:", err)
 	}
@@ -82,7 +82,7 @@ func runCmd(_ *base.Command, args []string) {
 	outputEnvVars(gopEnv, vars, *envJson)
 }
 
-func outputEnvVars(gopEnv map[string]interface{}, vars []string, outputJson bool) {
+func outputEnvVars(gopEnv map[string]any, vars []string, outputJson bool) {
 	onlyValues := true
 
 	if len(vars) == 0 {
@@ -94,7 +94,7 @@ func outputEnvVars(gopEnv map[string]interface{}, vars []string, outputJson bool
 		}
 		sort.Strings(vars)
 	} else {
-		newEnv := make(map[string]interface{})
+		newEnv := make(map[string]any)
 		for _, v := range vars {
 			if value, ok := gopEnv[v]; ok {
 				newEnv[v] = value
diff --git a/cmd/internal/help/help.go b/cmd/internal/help/help.go
index 77728cff..16ab23aa 100644
--- a/cmd/internal/help/help.go
+++ b/cmd/internal/help/help.go
@@ -89,7 +89,7 @@ func (w *errWriter) Write(b []byte) (int, error) {
 }
 
 // tmpl executes the given template text on data, writing the result to w.
-func tmpl(w io.Writer, text string, data interface{}) {
+func tmpl(w io.Writer, text string, data any) {
 	t := template.New("top")
 	t.Funcs(template.FuncMap{"trim": strings.TrimSpace, "capitalize": capitalize})
 	template.Must(t.Parse(text))
diff --git a/cmd/internal/mod/mod.go b/cmd/internal/mod/mod.go
index 296f7ecd..cc6fd3e8 100644
--- a/cmd/internal/mod/mod.go
+++ b/cmd/internal/mod/mod.go
@@ -40,7 +40,7 @@ func check(err error) {
 	}
 }
 
-func fatal(msg interface{}) {
+func fatal(msg any) {
 	fmt.Fprintln(os.Stderr, msg)
 	os.Exit(1)
 }
diff --git a/demo/gop-parser/natural-lang/nlang.gop b/demo/gop-parser/natural-lang/nlang.gop
new file mode 100644
index 00000000..f61708b7
--- /dev/null
+++ b/demo/gop-parser/natural-lang/nlang.gop
@@ -0,0 +1,18 @@
+import "os"
+
+cl := tpl`
+expr = subject verb object
+subject = "I" | "You" | "He" | "She" | "It" | "Dog" | "Cat"
+object = "me" | "you" | "him" | "her" | "it" | "fish" | "apple" | "banana" | "dog" | "cat"
+verb = "attack" | "love" | "eat" | "hate"
+`!
+
+print "> "
+for line <- os.Stdin {
+	e, err := cl.parseExpr(line, nil)
+	if err != nil {
+		print "${err}\n> "
+	} else {
+		print e, "\n > "
+	}
+}
diff --git a/demo/mixgo/a.go b/demo/mixgo/a.go
index 46c3612e..a31f737c 100644
--- a/demo/mixgo/a.go
+++ b/demo/mixgo/a.go
@@ -2,7 +2,7 @@ package main
 
 import "fmt"
 
-func p(a interface{}) {
+func p(a any) {
 	sayMix()
 	fmt.Println("Hello,", a)
 }
diff --git a/demo/tpl-demo/demo.gop b/demo/tpl-demo/demo.gop
new file mode 100644
index 00000000..f678244f
--- /dev/null
+++ b/demo/tpl-demo/demo.gop
@@ -0,0 +1,8 @@
+cl := tpl`expr = *INT`!
+echo cl.parseExpr("1 2 3", nil)!
+
+cl = tpl`expr = INT % ","`!
+echo cl.parseExpr("1, 2, 3", nil)!
+
+cl = tpl`expr = INT % ("+" | "-")`!
+echo cl.parseExpr("1 + 2 - 3", nil)!
diff --git a/format/format.go b/format/format.go
index 77d8075e..d090b3f9 100644
--- a/format/format.go
+++ b/format/format.go
@@ -50,7 +50,7 @@ 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 {
+func Node(dst io.Writer, fset *token.FileSet, node any) error {
 	// Determine if we have a complete source file (file != nil).
 	var file *ast.File
 	var cnode *printer.CommentedNode
diff --git a/go.mod b/go.mod
index e04806b3..4ad2fecf 100644
--- a/go.mod
+++ b/go.mod
@@ -4,8 +4,8 @@ go 1.18
 
 require (
 	github.com/fsnotify/fsnotify v1.8.0
-	github.com/goplus/gogen v1.16.6
-	github.com/goplus/llgo v0.9.9
+	github.com/goplus/gogen v1.16.7
+	github.com/goplus/llgo v0.10.0
 	github.com/goplus/mod v0.13.17
 	github.com/qiniu/x v1.13.12
 	golang.org/x/tools v0.19.0
diff --git a/go.sum b/go.sum
index 2a4c6ac4..657b96bc 100644
--- a/go.sum
+++ b/go.sum
@@ -1,16 +1,16 @@
 github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
 github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
-github.com/goplus/gogen v1.16.6 h1:Zwv18HoTbPDk8s2ajxgVeqZE5i4/GMV722KHl6GS8Yk=
-github.com/goplus/gogen v1.16.6/go.mod h1:6TQYbabXDF9LCdDkOOzHmfg1R4ENfXQ3XpHa9RhTSD8=
-github.com/goplus/llgo v0.9.9 h1:sCM8ehOtuAEY/NQbJ4xAMRZhxEdDgilouWZpoaLiYrM=
-github.com/goplus/llgo v0.9.9/go.mod h1:udcq+s6tGOdhJq7I8fXPTv4qT2j17/KrlvtcJrMZAoM=
+github.com/goplus/gogen v1.16.7 h1:OVN6byjWVtzs4plS7q80h747Jh8G7J+VUPN0L9YWQ68=
+github.com/goplus/gogen v1.16.7/go.mod h1:6TQYbabXDF9LCdDkOOzHmfg1R4ENfXQ3XpHa9RhTSD8=
+github.com/goplus/llgo v0.10.0 h1:s3U3cnO3cploF1xCCJleAb16NQFAmHxdUmdrNhRH3hY=
+github.com/goplus/llgo v0.10.0/go.mod h1:YfOHsT/g3lc9b4GclLj812YzdSsJr0kd3CCB830TqHE=
 github.com/goplus/mod v0.13.17 h1:aWp14xosENrh7t0/0qcIejDmQEiTgI3ou2+KoLDlSlE=
 github.com/goplus/mod v0.13.17/go.mod h1:XlHf8mnQ4QkRDX14Of2tpywuHDd+JVpPStvh3egog+0=
 github.com/qiniu/x v1.13.12 h1:UyAwja6dgKUOYWZMzzc02wLodwnZ7wmK/0XzRd0e78g=
 github.com/qiniu/x v1.13.12/go.mod h1:INZ2TSWSJVWO/RuELQROERcslBwVgFG7MkTfEdaQz9E=
 golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
 golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
-golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
+golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
 golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
 golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
diff --git a/parser/_nofmt/exists/exists.gop b/parser/_nofmt/exists/exists.gop
new file mode 100644
index 00000000..7ebe18fd
--- /dev/null
+++ b/parser/_nofmt/exists/exists.gop
@@ -0,0 +1,2 @@
+a := [1, 3, 5, 7, 8, 19]
+hasEven := {for x <- a if x%2 == 0}
diff --git a/parser/_nofmt/exists/parser.expect b/parser/_nofmt/exists/parser.expect
new file mode 100644
index 00000000..8f650520
--- /dev/null
+++ b/parser/_nofmt/exists/parser.expect
@@ -0,0 +1,74 @@
+package main
+
+file exists.gop
+noEntrypoint
+ast.FuncDecl:
+  Name:
+    ast.Ident:
+      Name: main
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+  Body:
+    ast.BlockStmt:
+      List:
+        ast.AssignStmt:
+          Lhs:
+            ast.Ident:
+              Name: a
+          Tok: :=
+          Rhs:
+            ast.SliceLit:
+              Elts:
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 1
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 3
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 5
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 7
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 8
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 19
+        ast.AssignStmt:
+          Lhs:
+            ast.Ident:
+              Name: hasEven
+          Tok: :=
+          Rhs:
+            ast.ComprehensionExpr:
+              Tok: {
+              Fors:
+                ast.ForPhrase:
+                  Value:
+                    ast.Ident:
+                      Name: x
+                  X:
+                    ast.Ident:
+                      Name: a
+                  Cond:
+                    ast.BinaryExpr:
+                      X:
+                        ast.BinaryExpr:
+                          X:
+                            ast.Ident:
+                              Name: x
+                          Op: %
+                          Y:
+                            ast.BasicLit:
+                              Kind: INT
+                              Value: 2
+                      Op: ==
+                      Y:
+                        ast.BasicLit:
+                          Kind: INT
+                          Value: 0
diff --git a/parser/_nofmt/forloop/forloop.gop b/parser/_nofmt/forloop/forloop.gop
new file mode 100644
index 00000000..e2cc6920
--- /dev/null
+++ b/parser/_nofmt/forloop/forloop.gop
@@ -0,0 +1,30 @@
+n := 0
+for range [1, 3, 5, 7, 11] {
+	n++
+}
+println("n:", n)
+
+for x := range [1] {
+}
+
+sum := 0
+for _, x := range [1, 3, 5, 7, 11] {
+	if x > 3 {
+		sum += x
+	}
+}
+println("sum(1,3,5,7,11):", sum)
+
+sum = 0
+for i := 1; i < 100; i++ {
+	sum += i
+}
+println("sum(1-100):", sum)
+
+for x <- [1] {
+	println(x)
+}
+
+for i, x <- [1] if i%2 == 0 {
+	println(i, x)
+}
diff --git a/parser/_nofmt/forloop/parser.expect b/parser/_nofmt/forloop/parser.expect
new file mode 100644
index 00000000..aff616e0
--- /dev/null
+++ b/parser/_nofmt/forloop/parser.expect
@@ -0,0 +1,278 @@
+package main
+
+file forloop.gop
+noEntrypoint
+ast.FuncDecl:
+  Name:
+    ast.Ident:
+      Name: main
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+  Body:
+    ast.BlockStmt:
+      List:
+        ast.AssignStmt:
+          Lhs:
+            ast.Ident:
+              Name: n
+          Tok: :=
+          Rhs:
+            ast.BasicLit:
+              Kind: INT
+              Value: 0
+        ast.RangeStmt:
+          Tok: ILLEGAL
+          X:
+            ast.SliceLit:
+              Elts:
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 1
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 3
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 5
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 7
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 11
+          Body:
+            ast.BlockStmt:
+              List:
+                ast.IncDecStmt:
+                  X:
+                    ast.Ident:
+                      Name: n
+                  Tok: ++
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: println
+              Args:
+                ast.BasicLit:
+                  Kind: STRING
+                  Value: "n:"
+                ast.Ident:
+                  Name: n
+        ast.RangeStmt:
+          Key:
+            ast.Ident:
+              Name: x
+          Tok: :=
+          X:
+            ast.SliceLit:
+              Elts:
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 1
+          Body:
+            ast.BlockStmt:
+        ast.AssignStmt:
+          Lhs:
+            ast.Ident:
+              Name: sum
+          Tok: :=
+          Rhs:
+            ast.BasicLit:
+              Kind: INT
+              Value: 0
+        ast.RangeStmt:
+          Key:
+            ast.Ident:
+              Name: _
+          Value:
+            ast.Ident:
+              Name: x
+          Tok: :=
+          X:
+            ast.SliceLit:
+              Elts:
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 1
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 3
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 5
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 7
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 11
+          Body:
+            ast.BlockStmt:
+              List:
+                ast.IfStmt:
+                  Cond:
+                    ast.BinaryExpr:
+                      X:
+                        ast.Ident:
+                          Name: x
+                      Op: >
+                      Y:
+                        ast.BasicLit:
+                          Kind: INT
+                          Value: 3
+                  Body:
+                    ast.BlockStmt:
+                      List:
+                        ast.AssignStmt:
+                          Lhs:
+                            ast.Ident:
+                              Name: sum
+                          Tok: +=
+                          Rhs:
+                            ast.Ident:
+                              Name: x
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: println
+              Args:
+                ast.BasicLit:
+                  Kind: STRING
+                  Value: "sum(1,3,5,7,11):"
+                ast.Ident:
+                  Name: sum
+        ast.AssignStmt:
+          Lhs:
+            ast.Ident:
+              Name: sum
+          Tok: =
+          Rhs:
+            ast.BasicLit:
+              Kind: INT
+              Value: 0
+        ast.ForStmt:
+          Init:
+            ast.AssignStmt:
+              Lhs:
+                ast.Ident:
+                  Name: i
+              Tok: :=
+              Rhs:
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 1
+          Cond:
+            ast.BinaryExpr:
+              X:
+                ast.Ident:
+                  Name: i
+              Op: <
+              Y:
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 100
+          Post:
+            ast.IncDecStmt:
+              X:
+                ast.Ident:
+                  Name: i
+              Tok: ++
+          Body:
+            ast.BlockStmt:
+              List:
+                ast.AssignStmt:
+                  Lhs:
+                    ast.Ident:
+                      Name: sum
+                  Tok: +=
+                  Rhs:
+                    ast.Ident:
+                      Name: i
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: println
+              Args:
+                ast.BasicLit:
+                  Kind: STRING
+                  Value: "sum(1-100):"
+                ast.Ident:
+                  Name: sum
+        ast.ForPhraseStmt:
+          ForPhrase:
+            ast.ForPhrase:
+              Value:
+                ast.Ident:
+                  Name: x
+              X:
+                ast.SliceLit:
+                  Elts:
+                    ast.BasicLit:
+                      Kind: INT
+                      Value: 1
+          Body:
+            ast.BlockStmt:
+              List:
+                ast.ExprStmt:
+                  X:
+                    ast.CallExpr:
+                      Fun:
+                        ast.Ident:
+                          Name: println
+                      Args:
+                        ast.Ident:
+                          Name: x
+        ast.ForPhraseStmt:
+          ForPhrase:
+            ast.ForPhrase:
+              Key:
+                ast.Ident:
+                  Name: i
+              Value:
+                ast.Ident:
+                  Name: x
+              X:
+                ast.SliceLit:
+                  Elts:
+                    ast.BasicLit:
+                      Kind: INT
+                      Value: 1
+              Cond:
+                ast.BinaryExpr:
+                  X:
+                    ast.BinaryExpr:
+                      X:
+                        ast.Ident:
+                          Name: i
+                      Op: %
+                      Y:
+                        ast.BasicLit:
+                          Kind: INT
+                          Value: 2
+                  Op: ==
+                  Y:
+                    ast.BasicLit:
+                      Kind: INT
+                      Value: 0
+          Body:
+            ast.BlockStmt:
+              List:
+                ast.ExprStmt:
+                  X:
+                    ast.CallExpr:
+                      Fun:
+                        ast.Ident:
+                          Name: println
+                      Args:
+                        ast.Ident:
+                          Name: i
+                        ast.Ident:
+                          Name: x
diff --git a/parser/_nofmt/listcompr/listcompr.gop b/parser/_nofmt/listcompr/listcompr.gop
new file mode 100644
index 00000000..e7f61460
--- /dev/null
+++ b/parser/_nofmt/listcompr/listcompr.gop
@@ -0,0 +1,14 @@
+y := [x*x for x <- [1]]
+println(y)
+
+y = [x*x for x <- [1, 3, 5, 7, 11] if x > 3]
+println(y)
+
+z := [i+v for i, v <- [1, 3, 5, 7, 11] if t := i % 2; t == 1]
+println(z)
+
+println([k+","+s for k, s <- {"Hello": "xsw", "Hi": "Go+"}])
+
+arr := [1, 2, 3, 4, 5, 6]
+x := [[a, b] for a <- arr if a < b for b <- arr if b > 2]
+println("x:", x)
diff --git a/parser/_nofmt/listcompr/parser.expect b/parser/_nofmt/listcompr/parser.expect
new file mode 100644
index 00000000..14b35e6b
--- /dev/null
+++ b/parser/_nofmt/listcompr/parser.expect
@@ -0,0 +1,330 @@
+package main
+
+file listcompr.gop
+noEntrypoint
+ast.FuncDecl:
+  Name:
+    ast.Ident:
+      Name: main
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+  Body:
+    ast.BlockStmt:
+      List:
+        ast.AssignStmt:
+          Lhs:
+            ast.Ident:
+              Name: y
+          Tok: :=
+          Rhs:
+            ast.ComprehensionExpr:
+              Tok: [
+              Elt:
+                ast.BinaryExpr:
+                  X:
+                    ast.Ident:
+                      Name: x
+                  Op: *
+                  Y:
+                    ast.Ident:
+                      Name: x
+              Fors:
+                ast.ForPhrase:
+                  Value:
+                    ast.Ident:
+                      Name: x
+                  X:
+                    ast.SliceLit:
+                      Elts:
+                        ast.BasicLit:
+                          Kind: INT
+                          Value: 1
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: println
+              Args:
+                ast.Ident:
+                  Name: y
+        ast.AssignStmt:
+          Lhs:
+            ast.Ident:
+              Name: y
+          Tok: =
+          Rhs:
+            ast.ComprehensionExpr:
+              Tok: [
+              Elt:
+                ast.BinaryExpr:
+                  X:
+                    ast.Ident:
+                      Name: x
+                  Op: *
+                  Y:
+                    ast.Ident:
+                      Name: x
+              Fors:
+                ast.ForPhrase:
+                  Value:
+                    ast.Ident:
+                      Name: x
+                  X:
+                    ast.SliceLit:
+                      Elts:
+                        ast.BasicLit:
+                          Kind: INT
+                          Value: 1
+                        ast.BasicLit:
+                          Kind: INT
+                          Value: 3
+                        ast.BasicLit:
+                          Kind: INT
+                          Value: 5
+                        ast.BasicLit:
+                          Kind: INT
+                          Value: 7
+                        ast.BasicLit:
+                          Kind: INT
+                          Value: 11
+                  Cond:
+                    ast.BinaryExpr:
+                      X:
+                        ast.Ident:
+                          Name: x
+                      Op: >
+                      Y:
+                        ast.BasicLit:
+                          Kind: INT
+                          Value: 3
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: println
+              Args:
+                ast.Ident:
+                  Name: y
+        ast.AssignStmt:
+          Lhs:
+            ast.Ident:
+              Name: z
+          Tok: :=
+          Rhs:
+            ast.ComprehensionExpr:
+              Tok: [
+              Elt:
+                ast.BinaryExpr:
+                  X:
+                    ast.Ident:
+                      Name: i
+                  Op: +
+                  Y:
+                    ast.Ident:
+                      Name: v
+              Fors:
+                ast.ForPhrase:
+                  Key:
+                    ast.Ident:
+                      Name: i
+                  Value:
+                    ast.Ident:
+                      Name: v
+                  X:
+                    ast.SliceLit:
+                      Elts:
+                        ast.BasicLit:
+                          Kind: INT
+                          Value: 1
+                        ast.BasicLit:
+                          Kind: INT
+                          Value: 3
+                        ast.BasicLit:
+                          Kind: INT
+                          Value: 5
+                        ast.BasicLit:
+                          Kind: INT
+                          Value: 7
+                        ast.BasicLit:
+                          Kind: INT
+                          Value: 11
+                  Init:
+                    ast.AssignStmt:
+                      Lhs:
+                        ast.Ident:
+                          Name: t
+                      Tok: :=
+                      Rhs:
+                        ast.BinaryExpr:
+                          X:
+                            ast.Ident:
+                              Name: i
+                          Op: %
+                          Y:
+                            ast.BasicLit:
+                              Kind: INT
+                              Value: 2
+                  Cond:
+                    ast.BinaryExpr:
+                      X:
+                        ast.Ident:
+                          Name: t
+                      Op: ==
+                      Y:
+                        ast.BasicLit:
+                          Kind: INT
+                          Value: 1
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: println
+              Args:
+                ast.Ident:
+                  Name: z
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: println
+              Args:
+                ast.ComprehensionExpr:
+                  Tok: [
+                  Elt:
+                    ast.BinaryExpr:
+                      X:
+                        ast.BinaryExpr:
+                          X:
+                            ast.Ident:
+                              Name: k
+                          Op: +
+                          Y:
+                            ast.BasicLit:
+                              Kind: STRING
+                              Value: ","
+                      Op: +
+                      Y:
+                        ast.Ident:
+                          Name: s
+                  Fors:
+                    ast.ForPhrase:
+                      Key:
+                        ast.Ident:
+                          Name: k
+                      Value:
+                        ast.Ident:
+                          Name: s
+                      X:
+                        ast.CompositeLit:
+                          Elts:
+                            ast.KeyValueExpr:
+                              Key:
+                                ast.BasicLit:
+                                  Kind: STRING
+                                  Value: "Hello"
+                              Value:
+                                ast.BasicLit:
+                                  Kind: STRING
+                                  Value: "xsw"
+                            ast.KeyValueExpr:
+                              Key:
+                                ast.BasicLit:
+                                  Kind: STRING
+                                  Value: "Hi"
+                              Value:
+                                ast.BasicLit:
+                                  Kind: STRING
+                                  Value: "Go+"
+        ast.AssignStmt:
+          Lhs:
+            ast.Ident:
+              Name: arr
+          Tok: :=
+          Rhs:
+            ast.SliceLit:
+              Elts:
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 1
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 2
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 3
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 4
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 5
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 6
+        ast.AssignStmt:
+          Lhs:
+            ast.Ident:
+              Name: x
+          Tok: :=
+          Rhs:
+            ast.ComprehensionExpr:
+              Tok: [
+              Elt:
+                ast.SliceLit:
+                  Elts:
+                    ast.Ident:
+                      Name: a
+                    ast.Ident:
+                      Name: b
+              Fors:
+                ast.ForPhrase:
+                  Value:
+                    ast.Ident:
+                      Name: a
+                  X:
+                    ast.Ident:
+                      Name: arr
+                  Cond:
+                    ast.BinaryExpr:
+                      X:
+                        ast.Ident:
+                          Name: a
+                      Op: <
+                      Y:
+                        ast.Ident:
+                          Name: b
+                ast.ForPhrase:
+                  Value:
+                    ast.Ident:
+                      Name: b
+                  X:
+                    ast.Ident:
+                      Name: arr
+                  Cond:
+                    ast.BinaryExpr:
+                      X:
+                        ast.Ident:
+                          Name: b
+                      Op: >
+                      Y:
+                        ast.BasicLit:
+                          Kind: INT
+                          Value: 2
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: println
+              Args:
+                ast.BasicLit:
+                  Kind: STRING
+                  Value: "x:"
+                ast.Ident:
+                  Name: x
diff --git a/parser/_nofmt/rangeexpr1/parser.expect b/parser/_nofmt/rangeexpr1/parser.expect
new file mode 100644
index 00000000..c0dd7734
--- /dev/null
+++ b/parser/_nofmt/rangeexpr1/parser.expect
@@ -0,0 +1,113 @@
+package main
+
+file rangeexpr.gop
+ast.FuncDecl:
+  Name:
+    ast.Ident:
+      Name: main
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+  Body:
+    ast.BlockStmt:
+      List:
+        ast.RangeStmt:
+          Key:
+            ast.Ident:
+              Name: i
+          Tok: :=
+          X:
+            ast.RangeExpr:
+              Last:
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 10
+          Body:
+            ast.BlockStmt:
+              List:
+                ast.ExprStmt:
+                  X:
+                    ast.CallExpr:
+                      Fun:
+                        ast.Ident:
+                          Name: println
+                      Args:
+                        ast.Ident:
+                          Name: i
+        ast.ForPhraseStmt:
+          ForPhrase:
+            ast.ForPhrase:
+              Value:
+                ast.Ident:
+                  Name: i
+              X:
+                ast.RangeExpr:
+                  First:
+                    ast.BasicLit:
+                      Kind: INT
+                      Value: 1
+                  Last:
+                    ast.BasicLit:
+                      Kind: INT
+                      Value: 10
+          Body:
+            ast.BlockStmt:
+              List:
+                ast.ExprStmt:
+                  X:
+                    ast.CallExpr:
+                      Fun:
+                        ast.Ident:
+                          Name: println
+                      Args:
+                        ast.Ident:
+                          Name: i
+        ast.RangeStmt:
+          Key:
+            ast.Ident:
+              Name: i
+          Tok: :=
+          X:
+            ast.RangeExpr:
+              Last:
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 10
+              Expr3:
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 2
+          Body:
+            ast.BlockStmt:
+              List:
+                ast.ExprStmt:
+                  X:
+                    ast.CallExpr:
+                      Fun:
+                        ast.Ident:
+                          Name: println
+                      Args:
+                        ast.Ident:
+                          Name: i
+        ast.RangeStmt:
+          Tok: ILLEGAL
+          X:
+            ast.RangeExpr:
+              Last:
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 10
+          Body:
+            ast.BlockStmt:
+              List:
+                ast.ExprStmt:
+                  X:
+                    ast.CallExpr:
+                      Fun:
+                        ast.Ident:
+                          Name: println
+                      Args:
+                        ast.BasicLit:
+                          Kind: STRING
+                          Value: "Range expression"
diff --git a/parser/_nofmt/rangeexpr1/rangeexpr.gop b/parser/_nofmt/rangeexpr1/rangeexpr.gop
new file mode 100644
index 00000000..d276df83
--- /dev/null
+++ b/parser/_nofmt/rangeexpr1/rangeexpr.gop
@@ -0,0 +1,19 @@
+package main
+
+func main() {
+	for i := range :10 {
+		println(i)
+	}
+
+	for i <- 1:10 {
+		println(i)
+	}
+
+	for i := range :10:2 {
+		println(i)
+	}
+
+	for range :10 {
+		println("Range expression")
+	}
+}
diff --git a/parser/_nofmt/selectdata/parser.expect b/parser/_nofmt/selectdata/parser.expect
new file mode 100644
index 00000000..383bdf56
--- /dev/null
+++ b/parser/_nofmt/selectdata/parser.expect
@@ -0,0 +1,77 @@
+package main
+
+file select.gop
+noEntrypoint
+ast.FuncDecl:
+  Name:
+    ast.Ident:
+      Name: main
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+  Body:
+    ast.BlockStmt:
+      List:
+        ast.AssignStmt:
+          Lhs:
+            ast.Ident:
+              Name: a
+          Tok: :=
+          Rhs:
+            ast.SliceLit:
+              Elts:
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 1
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 3
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 5
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 7
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 8
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 19
+        ast.AssignStmt:
+          Lhs:
+            ast.Ident:
+              Name: y
+          Tok: :=
+          Rhs:
+            ast.ComprehensionExpr:
+              Tok: {
+              Elt:
+                ast.Ident:
+                  Name: x
+              Fors:
+                ast.ForPhrase:
+                  Value:
+                    ast.Ident:
+                      Name: x
+                  X:
+                    ast.Ident:
+                      Name: a
+                  Cond:
+                    ast.BinaryExpr:
+                      X:
+                        ast.BinaryExpr:
+                          X:
+                            ast.Ident:
+                              Name: x
+                          Op: %
+                          Y:
+                            ast.BasicLit:
+                              Kind: INT
+                              Value: 2
+                      Op: ==
+                      Y:
+                        ast.BasicLit:
+                          Kind: INT
+                          Value: 0
diff --git a/parser/_nofmt/selectdata/select.gop b/parser/_nofmt/selectdata/select.gop
new file mode 100644
index 00000000..c5880fe6
--- /dev/null
+++ b/parser/_nofmt/selectdata/select.gop
@@ -0,0 +1,2 @@
+a := [1, 3, 5, 7, 8, 19]
+y := {x for x <- a if x%2 == 0}
diff --git a/parser/fsx/memfs/fs.go b/parser/fsx/memfs/fs.go
index 8a076fc1..3876b3d4 100644
--- a/parser/fsx/memfs/fs.go
+++ b/parser/fsx/memfs/fs.go
@@ -41,7 +41,7 @@ func (p *dirEntry) Info() (fs.FileInfo, error) {
 	return p.FileInfo, nil
 }
 
-func File(filename string, src interface{}) (f *FileFS, err error) {
+func File(filename string, src any) (f *FileFS, err error) {
 	var data []byte
 	var info fs.DirEntry
 	if src != nil {
@@ -84,7 +84,7 @@ func (p *FileFS) Abs(path string) (string, error) {
 	return path, nil
 }
 
-func readSource(src interface{}) ([]byte, error) {
+func readSource(src any) ([]byte, error) {
 	switch s := src.(type) {
 	case string:
 		return []byte(s), nil
diff --git a/parser/fsx/memfs/memfs.go b/parser/fsx/memfs/memfs.go
index 75de1f6c..a5c398f3 100644
--- a/parser/fsx/memfs/memfs.go
+++ b/parser/fsx/memfs/memfs.go
@@ -55,7 +55,7 @@ func (p *memFileInfo) IsDir() bool {
 	return false
 }
 
-func (p *memFileInfo) Sys() interface{} {
+func (p *memFileInfo) Sys() any {
 	return nil
 }
 
diff --git a/parser/interface.go b/parser/interface.go
index 831ac793..a798f60f 100644
--- a/parser/interface.go
+++ b/parser/interface.go
@@ -78,7 +78,7 @@ 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) {
+func parseFile(fset *token.FileSet, filename string, src any, 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 7c03fb01..3f25cfac 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -50,10 +50,9 @@ type parser struct {
 	scanner scanner.Scanner
 
 	// Tracing/debugging
-	mode        Mode          // parsing mode
-	trace       bool          // == (mode & Trace != 0)
-	shadowEntry *ast.FuncDecl // shadowEntry != nil: no entrypoint func
-	indent      int           // indentation used for tracing output
+	mode   Mode // parsing mode
+	trace  bool // == (mode & Trace != 0)
+	indent int  // indentation used for tracing output
 
 	// Comments
 	comments    []*ast.CommentGroup
@@ -150,7 +149,7 @@ func (p *parser) closeLabelScope() {
 	p.labelScope = p.labelScope.Outer
 }
 
-func (p *parser) declare(decl, data interface{}, scope *ast.Scope, kind ast.ObjKind, idents ...*ast.Ident) {
+func (p *parser) declare(decl, data any, scope *ast.Scope, kind ast.ObjKind, idents ...*ast.Ident) {
 	for _, ident := range idents {
 		assert(ident.Obj == nil, "identifier already declared or resolved")
 		obj := ast.NewObj(kind, ident.Name)
@@ -242,7 +241,7 @@ func (p *parser) resolve(x ast.Expr) {
 // ----------------------------------------------------------------------------
 // Parsing support
 
-func (p *parser) printTrace(a ...interface{}) {
+func (p *parser) printTrace(a ...any) {
 	const dots = ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . "
 	const n = len(dots)
 	pos := p.file.Position(p.pos)
@@ -455,6 +454,22 @@ func (p *parser) expect2(tok token.Token) (pos token.Pos) {
 	return
 }
 
+func (p *parser) expectIn() token.Pos {
+	pos := p.pos
+	switch p.tok {
+	case token.ARROW: // <- (backward compatibility)
+	case token.IDENT: // in
+		if p.lit == "in" {
+			break
+		}
+		fallthrough
+	default:
+		p.errorExpected(pos, "'in'", 3)
+	}
+	p.next() // make progress
+	return 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 {
@@ -827,7 +842,7 @@ func (p *parser) parseArrayTypeOrSliceLit(state int, slice ast.Expr) (expr ast.E
 				sliceLit := p.parseSliceOrMatrixLit(lbrack, len)
 				p.exprLev--
 				return sliceLit, resultSliceLit
-			case token.FOR: // [expr for k, v <- container if cond]
+			case token.FOR: // [expr for k, v in container if cond]
 				phrases := p.parseForPhrases()
 				p.exprLev--
 				rbrack := p.expect(token.RBRACK)
@@ -2593,14 +2608,22 @@ func (p *parser) parseLambdaExpr(allowTuple, allowCmd, allowRangeExpr bool) (x a
 			case *tupleExpr:
 				items := make([]*ast.Ident, len(v.items))
 				for i, item := range v.items {
-					items[i] = p.toIdent(item)
+					ident := p.toIdent(item)
+					if ident == nil {
+						return &ast.BadExpr{From: item.Pos(), To: p.safePos(item.End())}, false
+					}
+					items[i] = ident
 				}
 				lhs, lhsHasParen = items, true
 			case *ast.ParenExpr:
 				e, lhsHasParen = v.X, true
 				goto retry
 			default:
-				lhs = []*ast.Ident{p.toIdent(v)}
+				ident := p.toIdent(v)
+				if ident == nil {
+					return &ast.BadExpr{From: v.Pos(), To: p.safePos(v.End())}, false
+				}
+				lhs = []*ast.Ident{ident}
 			}
 		}
 		if debugParseOutput {
@@ -2737,7 +2760,12 @@ func (p *parser) parseSimpleStmtEx(mode int, allowCmd, allowRangeExpr bool) (ast
 			p.shortVarDecl(as, x)
 		}
 		return as, isRange
-	case token.ARROW:
+	case token.IDENT: // in
+		if mode != rangeOk || p.lit != "in" {
+			break
+		}
+		fallthrough
+	case token.ARROW: // <- (backward compatibility)
 		if mode == rangeOk {
 			return p.parseForPhraseStmtPart(x), true
 		}
@@ -3277,7 +3305,7 @@ func (p *parser) parseForPhrases() (phrases []*ast.ForPhrase) {
 }
 
 func (p *parser) parseForPhraseStmtPart(lhs []ast.Expr) *ast.ForPhraseStmt {
-	tokPos := p.expect(token.ARROW) // <-
+	tokPos := p.expectIn() // in
 	x := p.parseExpr(false, false, true)
 	var cond ast.Expr
 	var ifPos token.Pos
@@ -3313,7 +3341,7 @@ func (p *parser) toIdent(e ast.Expr) *ast.Ident {
 	return nil
 }
 
-func (p *parser) parseForPhrase() *ast.ForPhrase { // for k, v <- container if cond
+func (p *parser) parseForPhrase() *ast.ForPhrase { // for k, v in container if cond
 	if p.trace {
 		defer un(trace(p, "ForPhrase"))
 	}
@@ -3329,7 +3357,7 @@ func (p *parser) parseForPhrase() *ast.ForPhrase { // for k, v <- container if c
 		k, v = v, p.parseIdent()
 	}
 
-	tokPos := p.expect(token.ARROW) // <- container
+	tokPos := p.expectIn() // in container
 	x := p.parseExpr(false, false, true)
 	var init ast.Stmt
 	var cond ast.Expr
@@ -3975,13 +4003,13 @@ func (p *parser) parseGlobalStmts(sync map[token.Token]bool, pos token.Pos, stmt
 	if stmts != nil {
 		list = append(stmts, list...)
 	}
-	if p.errors.Len() != 0 { // TODO: error
+	if p.errors.Len() != 0 { // TODO(xsw): error
 		p.advance(sync)
 	}
 	if p.tok != token.EOF {
 		p.errorExpected(p.pos, "statement", 2)
 	}
-	f := &ast.FuncDecl{
+	return &ast.FuncDecl{
 		Name: &ast.Ident{NamePos: pos, Name: "main"},
 		Doc:  doc,
 		Type: &ast.FuncType{
@@ -3991,8 +4019,6 @@ func (p *parser) parseGlobalStmts(sync map[token.Token]bool, pos token.Pos, stmt
 		Body:   &ast.BlockStmt{List: list},
 		Shadow: true,
 	}
-	p.shadowEntry = f
-	return f
 }
 
 // ----------------------------------------------------------------------------
@@ -4038,6 +4064,7 @@ func (p *parser) parseFile() *ast.File {
 	p.openScope()
 	p.pkgScope = p.topScope
 	var decls []ast.Decl
+	var shadowEntry *ast.FuncDecl
 	if p.mode&PackageClauseOnly == 0 {
 		// import decls
 		for p.tok == token.IMPORT {
@@ -4049,6 +4076,11 @@ func (p *parser) parseFile() *ast.File {
 			for p.tok != token.EOF {
 				decls = append(decls, p.parseDecl(declStart))
 			}
+			if n := len(decls); n > 0 {
+				if f, ok := decls[n-1].(*ast.FuncDecl); ok && f.Shadow {
+					shadowEntry = f
+				}
+			}
 		}
 	}
 	p.closeScope()
@@ -4076,7 +4108,7 @@ func (p *parser) parseFile() *ast.File {
 		Imports:     p.imports,
 		Unresolved:  p.unresolved[0:i],
 		Comments:    p.comments,
-		ShadowEntry: p.shadowEntry,
+		ShadowEntry: shadowEntry,
 		NoPkgDecl:   noPkgDecl,
 	}
 }
diff --git a/parser/parser_gop.go b/parser/parser_gop.go
index 6309871b..d32cc55d 100644
--- a/parser/parser_gop.go
+++ b/parser/parser_gop.go
@@ -57,7 +57,7 @@ type FileSystem = fsx.FileSystem
 
 // Parse parses a single Go+ source file. The filename specifies the Go+ source file.
 // If the file couldn't be read, a nil map and the respective error are returned.
-func Parse(fset *token.FileSet, filename string, src interface{}, mode Mode) (pkgs map[string]*ast.Package, err error) {
+func Parse(fset *token.FileSet, filename string, src any, mode Mode) (pkgs map[string]*ast.Package, err error) {
 	file, err := ParseFile(fset, filename, src, mode)
 	if err != nil {
 		return
@@ -96,7 +96,7 @@ func ParseDirEx(fset *token.FileSet, path string, conf Config) (pkgs map[string]
 }
 
 // ParseEntry calls ParseFSEntry by passing a local filesystem.
-func ParseEntry(fset *token.FileSet, filename string, src interface{}, conf Config) (f *ast.File, err error) {
+func ParseEntry(fset *token.FileSet, filename string, src any, conf Config) (f *ast.File, err error) {
 	return ParseFSEntry(fset, fsx.Local, filename, src, conf)
 }
 
@@ -192,7 +192,7 @@ func ParseFSDir(fset *token.FileSet, fs FileSystem, dir string, conf Config) (pk
 
 // ParseFSEntry parses the source code of a single Go+ source file and returns the corresponding ast.File node.
 // Compared to ParseFSFile, ParseFSEntry detects fileKind by its filename.
-func ParseFSEntry(fset *token.FileSet, fs FileSystem, filename string, src interface{}, conf Config) (f *ast.File, err error) {
+func ParseFSEntry(fset *token.FileSet, fs FileSystem, filename string, src any, conf Config) (f *ast.File, err error) {
 	fname := fs.Base(filename)
 	ext := path.Ext(fname)
 	var isProj, isClass, isNormalGox bool
@@ -322,12 +322,12 @@ func ParseFSEntries(fset *token.FileSet, fs FileSystem, files []string, conf Con
 // -----------------------------------------------------------------------------
 
 // ParseFile parses the source code of a single Go+ source file and returns the corresponding ast.File node.
-func ParseFile(fset *token.FileSet, filename string, src interface{}, mode Mode) (f *ast.File, err error) {
+func ParseFile(fset *token.FileSet, filename string, src any, mode Mode) (f *ast.File, err error) {
 	return ParseFSFile(fset, fsx.Local, filename, src, mode)
 }
 
 // ParseFSFile parses the source code of a single Go+ source file and returns the corresponding ast.File node.
-func ParseFSFile(fset *token.FileSet, fs FileSystem, filename string, src interface{}, mode Mode) (f *ast.File, err error) {
+func ParseFSFile(fset *token.FileSet, fs FileSystem, filename string, src any, mode Mode) (f *ast.File, err error) {
 	code, err := readSourceFS(fs, filename, src)
 	if err != nil {
 		return
@@ -338,7 +338,7 @@ func ParseFSFile(fset *token.FileSet, fs FileSystem, filename string, src interf
 	return parseFile(fset, filename, code, mode)
 }
 
-func readSourceFS(fs FileSystem, filename string, src interface{}) ([]byte, error) {
+func readSourceFS(fs FileSystem, filename string, src any) ([]byte, error) {
 	if src != nil {
 		return iox.ReadSource(src)
 	}
diff --git a/printer/nodes.go b/printer/nodes.go
index 2f36c021..e123ad8b 100644
--- a/printer/nodes.go
+++ b/printer/nodes.go
@@ -1144,6 +1144,10 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int) {
 	}
 }
 
+var (
+	in = &ast.Ident{Name: "in"}
+)
+
 func (p *printer) listForPhrase(list []*ast.ForPhrase) {
 	for i, x := range list {
 		if i > 0 {
@@ -1155,7 +1159,7 @@ func (p *printer) listForPhrase(list []*ast.ForPhrase) {
 			p.print(token.COMMA, blank)
 		}
 		p.print(x.Value, blank)
-		p.print(x.TokPos, token.ARROW, blank)
+		p.print(x.TokPos, in, blank)
 		p.expr(x.X)
 		if x.Cond != nil {
 			p.print(blank, x.Cond.Pos(), token.IF, blank)
@@ -1567,7 +1571,7 @@ func (p *printer) stmt(stmt ast.Stmt, nextIsRBrace bool) {
 			p.print(token.COMMA, blank)
 		}
 		p.expr(s.Value)
-		p.print(blank, s.TokPos, token.ARROW, blank)
+		p.print(blank, s.TokPos, in, blank)
 		p.expr(s.X)
 		if s.Cond != nil {
 			p.print(blank, s.Cond.Pos(), token.IF, blank)
diff --git a/printer/printer.go b/printer/printer.go
index fbc3a266..cdae7273 100644
--- a/printer/printer.go
+++ b/printer/printer.go
@@ -116,7 +116,7 @@ func (p *printer) init(cfg *Config, fset *token.FileSet, nodeSizes map[ast.Node]
 	p.cachedPos = -1
 }
 
-func (p *printer) internalError(msg ...interface{}) {
+func (p *printer) internalError(msg ...any) {
 	if debug {
 		fmt.Print(p.pos.String() + ": ")
 		fmt.Println(msg...)
@@ -889,7 +889,7 @@ func (p *printer) setPos(pos token.Pos) {
 // taking into account the amount and structure of any pending white-
 // space for best comment placement. Then, any leftover whitespace is
 // printed, followed by the actual token.
-func (p *printer) print(args ...interface{}) {
+func (p *printer) print(args ...any) {
 	for _, arg := range args {
 		// information about the current arg
 		var data string
@@ -1085,7 +1085,7 @@ func getLastComment(n ast.Node) *ast.CommentGroup {
 	return nil
 }
 
-func (p *printer) printNode(node interface{}) error {
+func (p *printer) printNode(node any) error {
 	// unpack *CommentedNode, if any
 	var comments []*ast.CommentGroup
 	if cnode, ok := node.(*CommentedNode); ok {
@@ -1300,7 +1300,7 @@ type Config struct {
 }
 
 // fprint implements Fprint and takes a nodesSizes map for setting up the printer state.
-func (cfg *Config) fprint(output io.Writer, fset *token.FileSet, node interface{}, nodeSizes map[ast.Node]int) (err error) {
+func (cfg *Config) fprint(output io.Writer, fset *token.FileSet, node any, nodeSizes map[ast.Node]int) (err error) {
 	// print node
 	var p printer
 	p.init(cfg, fset, nodeSizes)
@@ -1351,7 +1351,7 @@ func (cfg *Config) fprint(output io.Writer, fset *token.FileSet, node interface{
 // A CommentedNode bundles an AST node and corresponding comments.
 // It may be provided as argument to any of the Fprint functions.
 type CommentedNode struct {
-	Node     interface{} // *ast.File, or ast.Expr, ast.Decl, ast.Spec, or ast.Stmt
+	Node     any // *ast.File, or ast.Expr, ast.Decl, ast.Spec, or ast.Stmt
 	Comments []*ast.CommentGroup
 }
 
@@ -1359,7 +1359,7 @@ type CommentedNode struct {
 // Position information is interpreted relative to the file set fset.
 // The node type must be *ast.File, *CommentedNode, []ast.Decl, []ast.Stmt,
 // or assignment-compatible to ast.Expr, ast.Decl, ast.Spec, or ast.Stmt.
-func (cfg *Config) Fprint(output io.Writer, fset *token.FileSet, node interface{}) error {
+func (cfg *Config) Fprint(output io.Writer, fset *token.FileSet, node any) error {
 	return cfg.fprint(output, fset, node, make(map[ast.Node]int))
 }
 
@@ -1367,6 +1367,6 @@ func (cfg *Config) Fprint(output io.Writer, fset *token.FileSet, node interface{
 // It calls Config.Fprint with default settings.
 // Note that gofmt uses tabs for indentation but spaces for alignment;
 // use format.Node (package go/format) for output that matches gofmt.
-func Fprint(output io.Writer, fset *token.FileSet, node interface{}) error {
+func Fprint(output io.Writer, fset *token.FileSet, node any) error {
 	return (&Config{Tabwidth: 8}).Fprint(output, fset, node)
 }
diff --git a/scanner/scanner.go b/scanner/scanner.go
index 1994eb8c..962e7150 100644
--- a/scanner/scanner.go
+++ b/scanner/scanner.go
@@ -170,7 +170,7 @@ func (s *Scanner) error(offs int, msg string) {
 	s.ErrorCount++
 }
 
-func (s *Scanner) errorf(offs int, format string, args ...interface{}) {
+func (s *Scanner) errorf(offs int, format string, args ...any) {
 	s.error(offs, fmt.Sprintf(format, args...))
 }
 
diff --git a/tpl/encoding/csv/csv.go b/tpl/encoding/csv/csv.go
new file mode 100644
index 00000000..2858379d
--- /dev/null
+++ b/tpl/encoding/csv/csv.go
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2025 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 csv
+
+import (
+	"encoding/csv"
+	"strings"
+)
+
+// New creates a new csv object from a string
+func New(text string) (records [][]string, err error) {
+	return csv.NewReader(strings.NewReader(text)).ReadAll()
+}
diff --git a/tpl/encoding/json/json.go b/tpl/encoding/json/json.go
new file mode 100644
index 00000000..c8a56131
--- /dev/null
+++ b/tpl/encoding/json/json.go
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2025 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 json
+
+import (
+	"encoding/json"
+	"strings"
+)
+
+// New creates a new json object from a string
+func New(text string) (ret any, err error) {
+	err = json.NewDecoder(strings.NewReader(text)).Decode(&ret)
+	return
+}
diff --git a/tpl/encoding/regexp/regexp.go b/tpl/encoding/regexp/regexp.go
new file mode 100644
index 00000000..c0dbf252
--- /dev/null
+++ b/tpl/encoding/regexp/regexp.go
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2025 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 regexp
+
+import (
+	"regexp"
+)
+
+// New creates a new regexp object from a string
+func New(text string) (*regexp.Regexp, error) {
+	return regexp.Compile(text)
+}
diff --git a/tpl/encoding/regexposix/regexp.go b/tpl/encoding/regexposix/regexp.go
new file mode 100644
index 00000000..46cb307b
--- /dev/null
+++ b/tpl/encoding/regexposix/regexp.go
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2025 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 regexposix
+
+import (
+	"regexp"
+)
+
+// New creates a new POSIX regexp object from a string
+func New(text string) (*regexp.Regexp, error) {
+	return regexp.CompilePOSIX(text)
+}
diff --git a/tpl/encoding/xml/xml.go b/tpl/encoding/xml/xml.go
new file mode 100644
index 00000000..20abed5c
--- /dev/null
+++ b/tpl/encoding/xml/xml.go
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2025 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 xml
+
+import (
+	"encoding/xml"
+	"strings"
+)
+
+// New creates a new xml object from a string
+func New(text string) (ret any, err error) {
+	err = xml.NewDecoder(strings.NewReader(text)).Decode(&ret)
+	return
+}
diff --git a/tpl/matcher/match.go b/tpl/matcher/match.go
index 40b9767a..15278cb9 100644
--- a/tpl/matcher/match.go
+++ b/tpl/matcher/match.go
@@ -24,6 +24,13 @@ import (
 	"github.com/goplus/gop/tpl/types"
 )
 
+var (
+	errMultiMismatch = errors.New("multiple mismatch")
+
+	// ErrVarAssigned error
+	ErrVarAssigned = errors.New("variable is already assigned")
+)
+
 // -----------------------------------------------------------------------------
 
 // Error represents a matching error.
@@ -114,30 +121,25 @@ type gChoice struct {
 }
 
 func (p *gChoice) Match(src []*types.Token, ctx *Context) (n int, result any, err error) {
-	var nMax int
+	var nMax = -1
 	var errMax error
-	// var multiErr = true
+	var multiErr = true
 
 	for _, g := range p.options {
 		if n, result, err = g.Match(src, ctx); err == nil {
 			return
 		}
 		if n >= nMax {
-			nMax, errMax = n, err
-			/*
-				if n == nMax {
-					multiErr = true
-				} else {
-					nMax, errMax, multiErr = n, err, false
-				}
-			*/
+			if n == nMax {
+				multiErr = true
+			} else {
+				nMax, errMax, multiErr = n, err, false
+			}
 		}
 	}
-	/*
-		if multiErr {
-			errMax = ctx.NewError(src[nMax].End(), "TODO: error msg") // TODO(xsw)
-		}
-	*/
+	if multiErr {
+		errMax = errMultiMismatch
+	}
 	return nMax, nil, errMax
 }
 
@@ -263,11 +265,6 @@ func List(a, b Matcher) Matcher {
 
 // -----------------------------------------------------------------------------
 
-var (
-	// ErrVarAssigned error
-	ErrVarAssigned = errors.New("variable is already assigned")
-)
-
 type Var struct {
 	Elem Matcher
 	Name string
@@ -277,9 +274,20 @@ type Var struct {
 func (p *Var) Match(src []*types.Token, ctx *Context) (n int, result any, err error) {
 	g := p.Elem
 	if g == nil {
-		return 0, nil, ctx.NewErrorf(p.Pos, "variable `%s` not found", p.Name)
+		return 0, nil, ctx.NewErrorf(p.Pos, "variable `%s` not assigned", p.Name)
+	}
+	n, result, err = g.Match(src, ctx)
+	if err == errMultiMismatch {
+		var posErr token.Pos
+		var tokErr any
+		if len(src) > 0 {
+			posErr, tokErr = src[0].Pos, src[0]
+		} else {
+			posErr, tokErr = ctx.FileEnd, "EOF"
+		}
+		err = ctx.NewErrorf(posErr, "expect `%s`, but got `%s`", p.Name, tokErr)
 	}
-	return g.Match(src, ctx)
+	return
 }
 
 // Assign assigns a value to this variable.
diff --git a/tpl/parser/parser.go b/tpl/parser/parser.go
index 23cf5ee0..611eae90 100644
--- a/tpl/parser/parser.go
+++ b/tpl/parser/parser.go
@@ -121,6 +121,9 @@ func (p *parser) parseFile() *ast.File {
 
 	for p.tok != token.EOF {
 		rule := p.parseRule()
+		if rule == nil {
+			break
+		}
 		file.Decls = append(file.Decls, rule)
 	}
 
@@ -212,6 +215,7 @@ func (p *parser) parseTerm() (ast.Expr, bool) {
 		y, ok := p.parseFactor()
 		if !ok {
 			p.error(p.pos, "expected factor")
+			return x, false
 		}
 		x = &ast.BinaryExpr{
 			X:     x,
diff --git a/tpl/tpl.go b/tpl/tpl.go
index a0bd5867..20da7f22 100644
--- a/tpl/tpl.go
+++ b/tpl/tpl.go
@@ -166,15 +166,17 @@ func isPlain(result []any) bool {
 
 // -----------------------------------------------------------------------------
 
-func Dump(result any) {
-	Fdump(os.Stdout, result, "", "  ")
+func Dump(result any, omitSemi ...bool) {
+	Fdump(os.Stdout, result, "", "  ", omitSemi != nil && omitSemi[0])
 }
 
-func Fdump(w io.Writer, result any, prefix, indent string) {
+func Fdump(w io.Writer, result any, prefix, indent string, omitSemi bool) {
 	switch result := result.(type) {
 	case *Token:
 		if result.Tok != token.SEMICOLON {
 			fmt.Fprint(w, prefix, result, "\n")
+		} else if !omitSemi {
+			fmt.Fprint(w, prefix, ";\n")
 		}
 	case []any:
 		if isPlain(result) {
@@ -189,7 +191,7 @@ func Fdump(w io.Writer, result any, prefix, indent string) {
 		} else {
 			fmt.Print(prefix, "[\n")
 			for _, v := range result {
-				Fdump(w, v, prefix+indent, indent)
+				Fdump(w, v, prefix+indent, indent, omitSemi)
 			}
 			fmt.Print(prefix, "]\n")
 		}
diff --git a/x/build/build.go b/x/build/build.go
index c5d5eead..5407d2cb 100644
--- a/x/build/build.go
+++ b/x/build/build.go
@@ -141,7 +141,7 @@ func (c *Context) ParseFSDir(fs parser.FileSystem, dir string) (*Package, error)
 	return c.loadPackage(dir, pkgs)
 }
 
-func (c *Context) ParseFile(file string, src interface{}) (*Package, error) {
+func (c *Context) ParseFile(file string, src any) (*Package, error) {
 	fs, err := memfs.File(file, src)
 	if err != nil {
 		return nil, err
@@ -173,7 +173,7 @@ func (c *Context) loadPackage(srcDir string, pkgs map[string]*ast.Package) (*Pac
 	return &Package{c.fset, out}, nil
 }
 
-func (ctx *Context) BuildFile(filename string, src interface{}) (data []byte, err error) {
+func (ctx *Context) BuildFile(filename string, src any) (data []byte, err error) {
 	defer func() {
 		r := recover()
 		if r != nil {
diff --git a/x/jsonrpc2/conn.go b/x/jsonrpc2/conn.go
index cf31d118..937479d3 100644
--- a/x/jsonrpc2/conn.go
+++ b/x/jsonrpc2/conn.go
@@ -275,7 +275,7 @@ func newConnection(bindCtx context.Context, rwc io.ReadWriteCloser, binder Binde
 // Notify invokes the target method but does not wait for a response.
 // The params will be marshaled to JSON before sending over the wire, and will
 // be handed to the method invoked.
-func (c *Connection) Notify(ctx context.Context, method string, params interface{}) (err error) {
+func (c *Connection) Notify(ctx context.Context, method string, params any) (err error) {
 	attempted := false
 
 	defer func() {
@@ -325,7 +325,7 @@ func (c *Connection) Notify(ctx context.Context, method string, params interface
 // be handed to the method invoked.
 // You do not have to wait for the response, it can just be ignored if not needed.
 // If sending the call failed, the response will be ready and have the error in it.
-func (c *Connection) Call(ctx context.Context, method string, params interface{}) *AsyncCall {
+func (c *Connection) Call(ctx context.Context, method string, params any) *AsyncCall {
 	if debugCall {
 		log.Println("Call", method, "params:", params)
 	}
@@ -417,7 +417,7 @@ func (ac *AsyncCall) retire(response *Response) {
 
 // Await waits for (and decodes) the results of a Call.
 // The response will be unmarshaled from JSON into the result.
-func (ac *AsyncCall) Await(ctx context.Context, result interface{}) error {
+func (ac *AsyncCall) Await(ctx context.Context, result any) error {
 	if Verbose {
 		log.Println("==> AsyncCall.Await", ac.id)
 	}
@@ -446,7 +446,7 @@ func (ac *AsyncCall) Await(ctx context.Context, result interface{}) error {
 //
 // Respond must be called exactly once for any message for which a handler
 // returns ErrAsyncResponse. It must not be called for any other message.
-func (c *Connection) Respond(id ID, result interface{}, err error) error {
+func (c *Connection) Respond(id ID, result any, err error) error {
 	var req *incomingRequest
 	c.updateInFlight(func(s *inFlightState) {
 		req = s.incomingByID[id]
@@ -705,7 +705,7 @@ func (c *Connection) handleAsync() {
 }
 
 // processResult processes the result of a request and, if appropriate, sends a response.
-func (c *Connection) processResult(from interface{}, req *incomingRequest, result interface{}, err error) error {
+func (c *Connection) processResult(from any, req *incomingRequest, result any, err error) error {
 	switch err {
 	case ErrAsyncResponse:
 		if !req.IsCall() {
@@ -794,7 +794,7 @@ func (c *Connection) write(ctx context.Context, msg Message) error {
 // internalErrorf reports an internal error. By default it panics, but if
 // c.onInternalError is non-nil it instead calls that and returns an error
 // wrapping ErrInternal.
-func (c *Connection) internalErrorf(format string, args ...interface{}) error {
+func (c *Connection) internalErrorf(format string, args ...any) error {
 	err := fmt.Errorf(format, args...)
 	c.onInternalError(err)
 
@@ -804,7 +804,7 @@ func (c *Connection) internalErrorf(format string, args ...interface{}) error {
 // notDone is a context.Context wrapper that returns a nil Done channel.
 type notDone struct{ ctx context.Context }
 
-func (ic notDone) Value(key interface{}) interface{} {
+func (ic notDone) Value(key any) any {
 	return ic.ctx.Value(key)
 }
 
diff --git a/x/jsonrpc2/jsonrpc2.go b/x/jsonrpc2/jsonrpc2.go
index 8c758389..ed3a09e6 100644
--- a/x/jsonrpc2/jsonrpc2.go
+++ b/x/jsonrpc2/jsonrpc2.go
@@ -79,13 +79,13 @@ type Preempter interface {
 	// Otherwise, the result and error are processed as if returned by Handle.
 	//
 	// Preempt must not block. (The Context passed to it is for Values only.)
-	Preempt(ctx context.Context, req *Request) (result interface{}, err error)
+	Preempt(ctx context.Context, req *Request) (result any, err error)
 }
 
 // A PreempterFunc implements the Preempter interface for a standalone Preempt function.
-type PreempterFunc func(ctx context.Context, req *Request) (interface{}, error)
+type PreempterFunc func(ctx context.Context, req *Request) (any, error)
 
-func (f PreempterFunc) Preempt(ctx context.Context, req *Request) (interface{}, error) {
+func (f PreempterFunc) Preempt(ctx context.Context, req *Request) (any, error) {
 	return f(ctx, req)
 }
 
@@ -106,23 +106,23 @@ type Handler interface {
 	// connection is broken or the request is canceled or completed.
 	// (If Handle returns ErrAsyncResponse, ctx will remain uncanceled
 	// until either Cancel or Respond is called for the request's ID.)
-	Handle(ctx context.Context, req *Request) (result interface{}, err error)
+	Handle(ctx context.Context, req *Request) (result any, err error)
 }
 
 type defaultHandler struct{}
 
-func (defaultHandler) Preempt(context.Context, *Request) (interface{}, error) {
+func (defaultHandler) Preempt(context.Context, *Request) (any, error) {
 	return nil, ErrNotHandled
 }
 
-func (defaultHandler) Handle(context.Context, *Request) (interface{}, error) {
+func (defaultHandler) Handle(context.Context, *Request) (any, error) {
 	return nil, ErrNotHandled
 }
 
 // A HandlerFunc implements the Handler interface for a standalone Handle function.
-type HandlerFunc func(ctx context.Context, req *Request) (interface{}, error)
+type HandlerFunc func(ctx context.Context, req *Request) (any, error)
 
-func (f HandlerFunc) Handle(ctx context.Context, req *Request) (interface{}, error) {
+func (f HandlerFunc) Handle(ctx context.Context, req *Request) (any, error) {
 	return f(ctx, req)
 }
 
diff --git a/x/jsonrpc2/messages.go b/x/jsonrpc2/messages.go
index 509f607b..1067544d 100644
--- a/x/jsonrpc2/messages.go
+++ b/x/jsonrpc2/messages.go
@@ -28,7 +28,7 @@ import (
 
 // ID is a Request identifier.
 type ID struct {
-	value interface{}
+	value any
 }
 
 // Message is the interface to all jsonrpc2 message types.
@@ -75,18 +75,18 @@ func Int64ID(i int64) ID { return ID{value: i} }
 func (id ID) IsValid() bool { return id.value != nil }
 
 // Raw returns the underlying value of the ID.
-func (id ID) Raw() interface{} { return id.value }
+func (id ID) Raw() any { return id.value }
 
 // NewNotification constructs a new Notification message for the supplied
 // method and parameters.
-func NewNotification(method string, params interface{}) (*Request, error) {
+func NewNotification(method string, params any) (*Request, error) {
 	p, merr := marshalToRaw(params)
 	return &Request{Method: method, Params: p}, merr
 }
 
 // NewCall constructs a new Call message for the supplied ID, method and
 // parameters.
-func NewCall(id ID, method string, params interface{}) (*Request, error) {
+func NewCall(id ID, method string, params any) (*Request, error) {
 	p, merr := marshalToRaw(params)
 	return &Request{ID: id, Method: method, Params: p}, merr
 }
@@ -101,7 +101,7 @@ func (msg *Request) marshal(to *wireCombined) {
 
 // NewResponse constructs a new Response message that is a reply to the
 // supplied. If err is set result may be ignored.
-func NewResponse(id ID, result interface{}, rerr error) (*Response, error) {
+func NewResponse(id ID, result any, rerr error) (*Response, error) {
 	r, merr := marshalToRaw(result)
 	return &Response{ID: id, Result: r, Error: rerr}, merr
 }
@@ -185,7 +185,7 @@ func DecodeMessage(data []byte) (Message, error) {
 	return resp, nil
 }
 
-func marshalToRaw(obj interface{}) (json.RawMessage, error) {
+func marshalToRaw(obj any) (json.RawMessage, error) {
 	if obj == nil {
 		return nil, nil
 	}
diff --git a/x/jsonrpc2/wire.go b/x/jsonrpc2/wire.go
index 317b6598..4a9d2967 100644
--- a/x/jsonrpc2/wire.go
+++ b/x/jsonrpc2/wire.go
@@ -61,7 +61,7 @@ const wireVersion = "2.0"
 // We can decode this and then work out which it is.
 type wireCombined struct {
 	VersionTag string          `json:"jsonrpc"`
-	ID         interface{}     `json:"id,omitempty"`
+	ID         any             `json:"id,omitempty"`
 	Method     string          `json:"method,omitempty"`
 	Params     json.RawMessage `json:"params,omitempty"`
 	Result     json.RawMessage `json:"result,omitempty"`
diff --git a/x/langserver/server.go b/x/langserver/server.go
index 47de37cb..d69dfc54 100644
--- a/x/langserver/server.go
+++ b/x/langserver/server.go
@@ -113,7 +113,7 @@ func (p *handler) Changed(files []string) {
 	}
 }
 
-func (p *handler) Handle(ctx context.Context, req *jsonrpc2.Request) (result interface{}, err error) {
+func (p *handler) Handle(ctx context.Context, req *jsonrpc2.Request) (result any, err error) {
 	switch req.Method {
 	case methodChanged:
 		var files []string
diff --git a/x/typesutil/check.go b/x/typesutil/check.go
index 256e9375..875488db 100644
--- a/x/typesutil/check.go
+++ b/x/typesutil/check.go
@@ -163,6 +163,8 @@ func (p *Checker) Files(goFiles []*goast.File, gopFiles []*ast.File) (err error)
 				}
 			} else if ce, ok := convErr(fset, err); ok {
 				onErr(ce)
+			} else {
+				onErr(err)
 			}
 		}
 		if debugPrintErr {
@@ -246,6 +248,9 @@ func convErr(fset *token.FileSet, e error) (ret types.Error, ok bool) {
 	case *gogen.ImportError:
 		ret.Pos, ret.Msg = v.Pos, v.Err.Error()
 		typesutil.SetErrorGo116(&ret, 0, v.Pos, v.Pos)
+	case *gogen.BoundTypeError:
+		ret.Pos, ret.Msg = v.Pos, v.Error()
+		typesutil.SetErrorGo116(&ret, 0, v.Pos, v.Pos)
 	default:
 		return
 	}


Test patch:
diff --git a/cl/_testgop/domaintext-html/go.mod b/cl/_testgop/domaintext-html/go.mod
new file mode 100644
index 00000000..fed7c47c
--- /dev/null
+++ b/cl/_testgop/domaintext-html/go.mod
@@ -0,0 +1,5 @@
+module example
+
+go 1.22
+
+require golang.org/x/net v0.34.0
diff --git a/cl/_testgop/domaintext-html/go.sum b/cl/_testgop/domaintext-html/go.sum
new file mode 100644
index 00000000..4db452ac
--- /dev/null
+++ b/cl/_testgop/domaintext-html/go.sum
@@ -0,0 +1,2 @@
+golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
+golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
diff --git a/cl/_testgop/domaintext-html/in.gop b/cl/_testgop/domaintext-html/in.gop
new file mode 100644
index 00000000..9f6b7f34
--- /dev/null
+++ b/cl/_testgop/domaintext-html/in.gop
@@ -0,0 +1,3 @@
+import "golang.org/x/net/html"
+
+echo html`<html><body><h1>hello</h1></body></html>`
diff --git a/cl/_testgop/domaintext-html/out.go b/cl/_testgop/domaintext-html/out.go
new file mode 100644
index 00000000..a77942be
--- /dev/null
+++ b/cl/_testgop/domaintext-html/out.go
@@ -0,0 +1,11 @@
+package main
+
+import (
+	"fmt"
+	"golang.org/x/net/html"
+	"strings"
+)
+
+func main() {
+	fmt.Println(html.Parse(strings.NewReader(`<html><body><h1>hello</h1></body></html>`)))
+}
diff --git a/cl/_testgop/domaintext-json/in.gop b/cl/_testgop/domaintext-json/in.gop
new file mode 100644
index 00000000..fc62c9b2
--- /dev/null
+++ b/cl/_testgop/domaintext-json/in.gop
@@ -0,0 +1,1 @@
+echo json`{"a":1, "b":2}`
diff --git a/cl/_testgop/domaintext-json/out.go b/cl/_testgop/domaintext-json/out.go
new file mode 100644
index 00000000..aeee0a22
--- /dev/null
+++ b/cl/_testgop/domaintext-json/out.go
@@ -0,0 +1,10 @@
+package main
+
+import (
+	"fmt"
+	"github.com/goplus/gop/tpl/encoding/json"
+)
+
+func main() {
+	fmt.Println(json.New(`{"a":1, "b":2}`))
+}
diff --git a/cl/_testgop/domaintext-md/go.mod b/cl/_testgop/domaintext-md/go.mod
new file mode 100644
index 00000000..2499a4c4
--- /dev/null
+++ b/cl/_testgop/domaintext-md/go.mod
@@ -0,0 +1,7 @@
+module example
+
+go 1.22
+
+require github.com/xushiwei/markdown v0.1.0
+
+require github.com/yuin/goldmark v1.7.8 // indirect
diff --git a/cl/_testgop/domaintext-md/go.sum b/cl/_testgop/domaintext-md/go.sum
new file mode 100644
index 00000000..a08a8799
--- /dev/null
+++ b/cl/_testgop/domaintext-md/go.sum
@@ -0,0 +1,4 @@
+github.com/xushiwei/markdown v0.1.0 h1:Vjw4MVcwSEJge9ObA/3C1lrmNYX60nJwuVKdLYrf0+o=
+github.com/xushiwei/markdown v0.1.0/go.mod h1:6hyvHMBrprwcCYXaw71W9WwBX1st9r+Rfsj+cKXW3EE=
+github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
+github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
diff --git a/cl/_testgop/domaintext-md/in.gop b/cl/_testgop/domaintext-md/in.gop
new file mode 100644
index 00000000..340b8940
--- /dev/null
+++ b/cl/_testgop/domaintext-md/in.gop
@@ -0,0 +1,13 @@
+import (
+    "os"
+
+    "github.com/xushiwei/markdown"
+)
+
+md := markdown`
+# Title
+
+Hello world
+`
+
+md.convert os.Stdout
diff --git a/cl/_testgop/domaintext-md/out.go b/cl/_testgop/domaintext-md/out.go
new file mode 100644
index 00000000..b795191f
--- /dev/null
+++ b/cl/_testgop/domaintext-md/out.go
@@ -0,0 +1,15 @@
+package main
+
+import (
+	"github.com/xushiwei/markdown"
+	"os"
+)
+
+func main() {
+	md := markdown.New(`
+# Title
+
+Hello world
+`)
+	md.Convert(os.Stdout)
+}
diff --git a/cl/_testgop/domaintext-regexp/in.gop b/cl/_testgop/domaintext-regexp/in.gop
new file mode 100644
index 00000000..c75f2576
--- /dev/null
+++ b/cl/_testgop/domaintext-regexp/in.gop
@@ -0,0 +1,3 @@
+re, err := regexp`^[a-z]+\[[0-9]+\]$`
+echo re.matchString("adam[23]")
+_ = err
diff --git a/cl/_testgop/domaintext-regexp/out.go b/cl/_testgop/domaintext-regexp/out.go
new file mode 100644
index 00000000..e5d05641
--- /dev/null
+++ b/cl/_testgop/domaintext-regexp/out.go
@@ -0,0 +1,12 @@
+package main
+
+import (
+	"fmt"
+	"github.com/goplus/gop/tpl/encoding/regexp"
+)
+
+func main() {
+	re, err := regexp.New(`^[a-z]+\[[0-9]+\]$`)
+	fmt.Println(re.MatchString("adam[23]"))
+	_ = err
+}
diff --git a/cl/_testgop/domaintext/in.gop b/cl/_testgop/domaintext-tpl/in.gop
similarity index 100%
rename from cl/_testgop/domaintext/in.gop
rename to cl/_testgop/domaintext-tpl/in.gop
diff --git a/cl/_testgop/domaintext/out.go b/cl/_testgop/domaintext-tpl/out.go
similarity index 100%
rename from cl/_testgop/domaintext/out.go
rename to cl/_testgop/domaintext-tpl/out.go
diff --git a/cl/builtin_test.go b/cl/builtin_test.go
index 88cd15e7..6c553c16 100644
--- a/cl/builtin_test.go
+++ b/cl/builtin_test.go
@@ -39,6 +39,13 @@ func getGoxConf() *gogen.Config {
 	return &gogen.Config{Fset: fset, Importer: imp}
 }
 
+func TestLoadExpr(t *testing.T) {
+	var ni nodeInterp
+	if v := ni.LoadExpr(&ast.Ident{Name: "x"}); v != "" {
+		t.Fatal("LoadExpr:", v)
+	}
+}
+
 func TestSimplifyPkgPath(t *testing.T) {
 	if simplifyPkgPath("c/lua") != "github.com/goplus/llgo/c/lua" {
 		t.Fatal("simplifyPkgPath: c/lua")
@@ -455,7 +462,7 @@ func TestSpxRef(t *testing.T) {
 	spxRef(pkg, "bar")
 }
 
-func isError(e interface{}, msg string) bool {
+func isError(e any, msg string) bool {
 	if e != nil {
 		if err, ok := e.(error); ok {
 			return err.Error() == msg
diff --git a/cl/cltest/cltest.go b/cl/cltest/cltest.go
index 013908ca..aff9a1b1 100644
--- a/cl/cltest/cltest.go
+++ b/cl/cltest/cltest.go
@@ -34,6 +34,7 @@ import (
 	"github.com/goplus/gop/token"
 	"github.com/goplus/gop/tool"
 	"github.com/goplus/mod/env"
+	"github.com/goplus/mod/gopmod"
 	"github.com/goplus/mod/modfile"
 )
 
@@ -192,7 +193,16 @@ func testFrom(t *testing.T, pkgDir, sel string) {
 	filter := func(fi fs.FileInfo) bool {
 		return fi.Name() == "in.gop"
 	}
-	DoFS(t, Conf, fsx.Local, pkgDir, filter, "main", expected)
+	conf := Conf
+	goMod := pkgDir + "/go.mod"
+	if _, err := os.Stat(goMod); err == nil {
+		if mod, err := gopmod.Load(pkgDir); err == nil {
+			confCopy := *Conf
+			confCopy.Importer = tool.NewImporter(mod, Gop, conf.Fset)
+			conf = &confCopy
+		}
+	}
+	DoFS(t, conf, fsx.Local, pkgDir, filter, "main", expected)
 }
 
 // -----------------------------------------------------------------------------
diff --git a/cl/compile_gop_test.go b/cl/compile_gop_test.go
index d95d39f6..d6fa13e0 100644
--- a/cl/compile_gop_test.go
+++ b/cl/compile_gop_test.go
@@ -1689,3 +1689,39 @@ func main() {
 }
 `)
 }
+
+func TestSliceType(t *testing.T) {
+	gopClTest(t, `
+a := [1, "a"]
+a[0] = [1, 2, 3]
+echo a
+`, `package main
+
+import "fmt"
+
+func main() {
+	a := []interface{}{1, "a"}
+	a[0] = []int{1, 2, 3}
+	fmt.Println(a)
+}
+`)
+}
+
+func TestMapLitType(t *testing.T) {
+	gopClTest(t, `
+var a any = {
+    "Monday": 1,
+    "Tuesday": 2,
+}
+echo a
+`, `package main
+
+import "fmt"
+
+var a interface{} = map[string]int{"Monday": 1, "Tuesday": 2}
+
+func main() {
+	fmt.Println(a)
+}
+`)
+}
diff --git a/cl/compile_spx_test.go b/cl/compile_spx_test.go
index e6a2539e..6f6a5952 100644
--- a/cl/compile_spx_test.go
+++ b/cl/compile_spx_test.go
@@ -57,9 +57,6 @@ type MyGame struct {
 	*spx.MyGame
 }
 
-func (this *Kai) Classfname() string {
-	return "Kai"
-}
 func (this *Kai) Main() {
 }
 func (this *MyGame) Main() {
@@ -142,9 +139,6 @@ func (this *Kai) onMsg(msg string) {
 		this.Say("Hi")
 	}
 }
-func (this *Kai) Classfname() string {
-	return "Kai"
-}
 func (this *Kai) Main() {
 }
 func main() {
@@ -199,9 +193,6 @@ func (this *index) MainEntry() {
 func (this *index) Main() {
 	spx.Gopt_MyGame_Main(this)
 }
-func (this *bar) Classfname() string {
-	return "bar"
-}
 func (this *bar) Main() {
 }
 func main() {
@@ -234,9 +225,6 @@ func (this *index) MainEntry() {
 func (this *index) Main() {
 	spx.Gopt_MyGame_Main(this)
 }
-func (this *bar) Classfname() string {
-	return "bar"
-}
 func (this *bar) Main() {
 }
 func main() {
@@ -270,9 +258,6 @@ func (this *index) MainEntry() {
 func (this *index) Main() {
 	spx.Gopt_MyGame_Main(this)
 }
-func (this *bar) Classfname() string {
-	return "bar"
-}
 func (this *bar) Main() {
 }
 func main() {
@@ -314,9 +299,6 @@ func (this *index) MainEntry() {
 func (this *index) Main() {
 	spx.Gopt_MyGame_Main(this)
 }
-func (this *bar) Classfname() string {
-	return "bar"
-}
 func (this *bar) Main() {
 }
 func main() {
@@ -372,9 +354,6 @@ func (this *bar) onInit() {
 	this.Say("Where do you come from?", 2)
 	this.Broadcast__0("msg2")
 }
-func (this *bar) Classfname() string {
-	return "bar"
-}
 func (this *bar) Main() {
 }
 func main() {
@@ -434,9 +413,6 @@ func (this *Kai) onInit() {
 func (this *Kai) onCloned() {
 	this.Say("Hi")
 }
-func (this *Kai) Classfname() string {
-	return "Kai"
-}
 func (this *Kai) Main() {
 }
 func main() {
@@ -485,9 +461,6 @@ func (this *index) Main() {
 func (this *Kai) Main() {
 	fmt.Println("Hi")
 }
-func (this *Kai) Classfname() string {
-	return "Kai"
-}
 func main() {
 	new(index).Main()
 }
@@ -533,6 +506,10 @@ func (this *Kai) Main(_gop_arg0 string) {
 func (this *Kai) Classfname() string {
 	return "Kai"
 }
+func (this *Kai) Classclone() interface{} {
+	_gop_ret := *this
+	return &_gop_ret
+}
 func main() {
 	new(Game).Main()
 }
@@ -572,6 +549,10 @@ func (this *Game) Main() {
 func (this *Kai) Classfname() string {
 	return "Kai"
 }
+func (this *Kai) Classclone() interface{} {
+	_gop_ret := *this
+	return &_gop_ret
+}
 func (this *Kai) Main(_gop_arg0 string) {
 	this.Sprite.Main(_gop_arg0)
 }
@@ -610,9 +591,6 @@ func (this *Game) Main() {
 }
 func (this *Kai) onMsg(msg string) {
 }
-func (this *Kai) Classfname() string {
-	return "Kai"
-}
 func (this *Kai) Main() {
 }
 func main() {
@@ -650,9 +628,6 @@ func (this *Game) Main() {
 }
 func (this *Kai) onMsg(msg string) {
 }
-func (this *Kai) Classfname() string {
-	return "Kai"
-}
 func (this *Kai) Main() {
 }
 func main() {
@@ -684,14 +659,8 @@ type Kai struct {
 func (this *Dog) Main() {
 	fmt.Println("Hi, Sprite")
 }
-func (this *Dog) Classfname() string {
-	return "Dog"
-}
 func (this *Kai) onMsg(msg string) {
 }
-func (this *Kai) Classfname() string {
-	return "Kai"
-}
 func (this *Kai) Main() {
 }
 `, "Dog_t3spx.gox", "Kai.t3spx2")
@@ -721,9 +690,6 @@ func (this *Game) MainEntry() {
 func (this *Game) Main() {
 	(*spx2.Game).Main(&this.Game)
 }
-func (this *Kai) Classfname() string {
-	return "Kai"
-}
 func (this *Kai) Main() {
 }
 func main() {
@@ -753,9 +719,6 @@ func (this *Game) MainEntry() {
 func (this *Game) Main() {
 	(*spx2.Game).Main(&this.Game)
 }
-func (this *Kai) Classfname() string {
-	return "Kai"
-}
 func (this *Kai) Main() {
 }
 func main() {
@@ -803,9 +766,6 @@ func (this *Kai) Main() {
 }
 func (this *Kai) onMsg(msg string) {
 }
-func (this *Kai) Classfname() string {
-	return "Kai"
-}
 func main() {
 	new(Game).Main()
 }
@@ -852,9 +812,6 @@ func (this *Kai) onMsg(msg string) {
 		this.Say("Hi")
 	}
 }
-func (this *Kai) Classfname() string {
-	return "Kai"
-}
 func (this *Kai) Main() {
 }
 func main() {
@@ -929,9 +886,6 @@ func (this *Kai) onInit() {
 func (this *Kai) onCloned() {
 	this.Say("Hi")
 }
-func (this *Kai) Classfname() string {
-	return "Kai"
-}
 func (this *Kai) Main() {
 }
 func main() {
@@ -974,9 +928,6 @@ func (this *Game) Main() {
 }
 func (this *Kai) onMsg(msg string) {
 }
-func (this *Kai) Classfname() string {
-	return "Kai"
-}
 func (this *Kai) Main() {
 }
 func main() {
@@ -1016,9 +967,6 @@ func (this *Game) Main() {
 func (this *Kai) onMsg(msg string) {
 	this.Position().Add__0(100, 200)
 }
-func (this *Kai) Classfname() string {
-	return "Kai"
-}
 func (this *Kai) Main() {
 }
 func main() {
@@ -1083,9 +1031,6 @@ func (this *Kai) onMsg(msg string) {
 	fmt.Println(this.Vector().X)
 	fmt.Println(this.Vector().Self().Self())
 }
-func (this *Kai) Classfname() string {
-	return "Kai"
-}
 func (this *Kai) Main() {
 }
 func main() {
@@ -1197,9 +1142,6 @@ func (this *Kai) Main() {
 	spx.Gopt_Sprite_OnKey2(this, "hello", func(key string) {
 	})
 }
-func (this *Kai) Classfname() string {
-	return "Kai"
-}
 func main() {
 	new(Game).Main()
 }
@@ -1239,9 +1181,6 @@ func (this *caseFoo) Main() {
 		t.Fatal("failed")
 	})
 }
-func (this *caseFoo) Classfname() string {
-	return "Foo"
-}
 func TestFoo(t *testing.T) {
 	test.Gopt_Case_TestMain(new(caseFoo), t)
 }
@@ -1270,9 +1209,6 @@ type case_foo struct {
 func (this *case_foo) Main() {
 	this.T().Log("Hi")
 }
-func (this *case_foo) Classfname() string {
-	return "foo"
-}
 func Test_foo(t *testing.T) {
 	test.Gopt_Case_TestMain(new(case_foo), t)
 }
diff --git a/cl/compile_test.go b/cl/compile_test.go
index b72c4ad3..88521d52 100644
--- a/cl/compile_test.go
+++ b/cl/compile_test.go
@@ -4251,3 +4251,69 @@ func main() {
 }
 `)
 }
+
+func TestErrWrapNoArgs(t *testing.T) {
+	gopClTest(t, `
+func foo(v ...int) (func(), error) {
+	return nil, nil
+}
+func Bar() (int, error) {
+	return 100, nil
+}
+foo!()
+foo(1)!()
+echo foo!
+echo bar!
+`, `package main
+
+import (
+	"fmt"
+	"github.com/qiniu/x/errors"
+)
+
+func foo(v ...int) (func(), error) {
+	return nil, nil
+}
+func Bar() (int, error) {
+	return 100, nil
+}
+func main() {
+	func() (_gop_ret func()) {
+		var _gop_err error
+		_gop_ret, _gop_err = foo()
+		if _gop_err != nil {
+			_gop_err = errors.NewFrame(_gop_err, "foo", "/foo/bar.gop", 8, "main.main")
+			panic(_gop_err)
+		}
+		return
+	}()()
+	func() (_gop_ret func()) {
+		var _gop_err error
+		_gop_ret, _gop_err = foo(1)
+		if _gop_err != nil {
+			_gop_err = errors.NewFrame(_gop_err, "foo(1)", "/foo/bar.gop", 9, "main.main")
+			panic(_gop_err)
+		}
+		return
+	}()()
+	fmt.Println(func() (_gop_ret func()) {
+		var _gop_err error
+		_gop_ret, _gop_err = foo()
+		if _gop_err != nil {
+			_gop_err = errors.NewFrame(_gop_err, "foo", "/foo/bar.gop", 10, "main.main")
+			panic(_gop_err)
+		}
+		return
+	}())
+	fmt.Println(func() (_gop_ret int) {
+		var _gop_err error
+		_gop_ret, _gop_err = Bar()
+		if _gop_err != nil {
+			_gop_err = errors.NewFrame(_gop_err, "bar", "/foo/bar.gop", 11, "main.main")
+			panic(_gop_err)
+		}
+		return
+	}())
+}
+`)
+}
diff --git a/cl/error_msg_test.go b/cl/error_msg_test.go
index a9553ee3..10cb6b76 100644
--- a/cl/error_msg_test.go
+++ b/cl/error_msg_test.go
@@ -603,6 +603,12 @@ bar.gop:3:27: cannot use "Go" + "+" (type untyped string) as type int in map val
 		`
 a := map[string]int{1+2: 2}
 b := map[string]int{"Hi": "Go" + "+"}
+`)
+	codeErrorTest(t, `bar.gop:2:13: invalid map literal`, `
+var v any = {1:2,1}
+`)
+	codeErrorTest(t, `bar.gop:2:21: invalid map literal`, `
+var v map[int]int = {1:2,1}
 `)
 }
 
diff --git a/parser/_testdata/exists/exists.gop b/parser/_testdata/exists/exists.gop
index 7ebe18fd..d1439096 100644
--- a/parser/_testdata/exists/exists.gop
+++ b/parser/_testdata/exists/exists.gop
@@ -1,2 +1,2 @@
 a := [1, 3, 5, 7, 8, 19]
-hasEven := {for x <- a if x%2 == 0}
+hasEven := {for x in a if x%2 == 0}
diff --git a/parser/_testdata/forloop/forloop.gop b/parser/_testdata/forloop/forloop.gop
index e2cc6920..0db180bf 100644
--- a/parser/_testdata/forloop/forloop.gop
+++ b/parser/_testdata/forloop/forloop.gop
@@ -21,10 +21,10 @@ for i := 1; i < 100; i++ {
 }
 println("sum(1-100):", sum)
 
-for x <- [1] {
+for x in [1] {
 	println(x)
 }
 
-for i, x <- [1] if i%2 == 0 {
+for i, x in [1] if i%2 == 0 {
 	println(i, x)
 }
diff --git a/parser/_testdata/listcompr/listcompr.gop b/parser/_testdata/listcompr/listcompr.gop
index e7f61460..8e7f91ab 100644
--- a/parser/_testdata/listcompr/listcompr.gop
+++ b/parser/_testdata/listcompr/listcompr.gop
@@ -1,14 +1,14 @@
-y := [x*x for x <- [1]]
+y := [x*x for x in [1]]
 println(y)
 
-y = [x*x for x <- [1, 3, 5, 7, 11] if x > 3]
+y = [x*x for x in [1, 3, 5, 7, 11] if x > 3]
 println(y)
 
-z := [i+v for i, v <- [1, 3, 5, 7, 11] if t := i % 2; t == 1]
+z := [i+v for i, v in [1, 3, 5, 7, 11] if t := i % 2; t == 1]
 println(z)
 
-println([k+","+s for k, s <- {"Hello": "xsw", "Hi": "Go+"}])
+println([k+","+s for k, s in {"Hello": "xsw", "Hi": "Go+"}])
 
 arr := [1, 2, 3, 4, 5, 6]
-x := [[a, b] for a <- arr if a < b for b <- arr if b > 2]
+x := [[a, b] for a in arr if a < b for b in arr if b > 2]
 println("x:", x)
diff --git a/parser/_testdata/rangeexpr1/rangeexpr.gop b/parser/_testdata/rangeexpr1/rangeexpr.gop
index d276df83..15cc6644 100644
--- a/parser/_testdata/rangeexpr1/rangeexpr.gop
+++ b/parser/_testdata/rangeexpr1/rangeexpr.gop
@@ -5,7 +5,7 @@ func main() {
 		println(i)
 	}
 
-	for i <- 1:10 {
+	for i in 1:10 {
 		println(i)
 	}
 
diff --git a/parser/_testdata/selectdata/select.gop b/parser/_testdata/selectdata/select.gop
index c5880fe6..235ad063 100644
--- a/parser/_testdata/selectdata/select.gop
+++ b/parser/_testdata/selectdata/select.gop
@@ -1,2 +1,2 @@
 a := [1, 3, 5, 7, 8, 19]
-y := {x for x <- a if x%2 == 0}
+y := {x for x in a if x%2 == 0}
diff --git a/parser/parser_test.go b/parser/parser_test.go
index 94e1dbab..9172c33d 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -64,7 +64,7 @@ func TestAssert(t *testing.T) {
 	assert(false, "panic msg")
 }
 
-func panicMsg(e interface{}) string {
+func panicMsg(e any) string {
 	switch v := e.(type) {
 	case string:
 		return v
@@ -164,6 +164,10 @@ test "hello", (x, "y") => {
 	println "lambda",x,y
 }
 `, `/foo/bar.gop:3:19: expected 'IDENT', found "y"`, ``)
+	testErrCode(t, `onTouchStart "someone" => {
+	say "touched by someone"
+}
+`, `/foo/bar.gop:1:14: expected 'IDENT', found "someone"`, ``)
 }
 
 func TestErrTooManyParseExpr(t *testing.T) {
@@ -339,6 +343,11 @@ func (a T) +{}
 `, `/foo/bar.gop:1:6: overload operator can only have one parameter`, ``)
 }
 
+func TestErrForIn(t *testing.T) {
+	testErrCode(t, `x := [a for a i b]
+`, `/foo/bar.gop:1:15: expected 'in', found i`, ``)
+}
+
 func TestNumberUnitLit(t *testing.T) {
 	var p parser
 	p.checkExpr(&ast.NumberUnitLit{})
diff --git a/parser/parserdir_go118_test.go b/parser/parserdir_go118_test.go
index d4e0bf05..829ee283 100644
--- a/parser/parserdir_go118_test.go
+++ b/parser/parserdir_go118_test.go
@@ -21,8 +21,277 @@ package parser
 
 import (
 	"testing"
+
+	"github.com/goplus/gop/parser/parsertest"
+	"github.com/goplus/gop/token"
 )
 
+var testStdCode = `package bar; import "io"
+	// comment
+	x := 0
+	if t := false; t {
+		x = 3
+	} else {
+		x = 5
+	}
+	println("x:", x)
+
+	// comment 1
+	// comment 2
+	x = 0
+	switch s := "Hello"; s {
+	default:
+		x = 7
+	case "world", "hi":
+		x = 5
+	case "xsw":
+		x = 3
+	}
+	println("x:", x)
+
+	c := make(chan bool, 100)
+	select {
+	case c <- true:
+	case v := <-c:
+	default:
+		panic("error")
+	}
+`
+
+func TestStd(t *testing.T) {
+	fset := token.NewFileSet()
+	pkgs, err := Parse(fset, "/foo/bar.gop", testStdCode, ParseComments)
+	if err != nil || len(pkgs) != 1 {
+		t.Fatal("Parse failed:", err, len(pkgs))
+	}
+	bar := pkgs["bar"]
+	parsertest.Expect(t, bar, `package bar
+
+file bar.gop
+noEntrypoint
+ast.GenDecl:
+  Tok: import
+  Specs:
+    ast.ImportSpec:
+      Path:
+        ast.BasicLit:
+          Kind: STRING
+          Value: "io"
+ast.FuncDecl:
+  Doc:
+    ast.CommentGroup:
+      List:
+        ast.Comment:
+          Text: // comment
+  Name:
+    ast.Ident:
+      Name: main
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+  Body:
+    ast.BlockStmt:
+      List:
+        ast.AssignStmt:
+          Lhs:
+            ast.Ident:
+              Name: x
+          Tok: :=
+          Rhs:
+            ast.BasicLit:
+              Kind: INT
+              Value: 0
+        ast.IfStmt:
+          Init:
+            ast.AssignStmt:
+              Lhs:
+                ast.Ident:
+                  Name: t
+              Tok: :=
+              Rhs:
+                ast.Ident:
+                  Name: false
+          Cond:
+            ast.Ident:
+              Name: t
+          Body:
+            ast.BlockStmt:
+              List:
+                ast.AssignStmt:
+                  Lhs:
+                    ast.Ident:
+                      Name: x
+                  Tok: =
+                  Rhs:
+                    ast.BasicLit:
+                      Kind: INT
+                      Value: 3
+          Else:
+            ast.BlockStmt:
+              List:
+                ast.AssignStmt:
+                  Lhs:
+                    ast.Ident:
+                      Name: x
+                  Tok: =
+                  Rhs:
+                    ast.BasicLit:
+                      Kind: INT
+                      Value: 5
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: println
+              Args:
+                ast.BasicLit:
+                  Kind: STRING
+                  Value: "x:"
+                ast.Ident:
+                  Name: x
+        ast.AssignStmt:
+          Lhs:
+            ast.Ident:
+              Name: x
+          Tok: =
+          Rhs:
+            ast.BasicLit:
+              Kind: INT
+              Value: 0
+        ast.SwitchStmt:
+          Init:
+            ast.AssignStmt:
+              Lhs:
+                ast.Ident:
+                  Name: s
+              Tok: :=
+              Rhs:
+                ast.BasicLit:
+                  Kind: STRING
+                  Value: "Hello"
+          Tag:
+            ast.Ident:
+              Name: s
+          Body:
+            ast.BlockStmt:
+              List:
+                ast.CaseClause:
+                  Body:
+                    ast.AssignStmt:
+                      Lhs:
+                        ast.Ident:
+                          Name: x
+                      Tok: =
+                      Rhs:
+                        ast.BasicLit:
+                          Kind: INT
+                          Value: 7
+                ast.CaseClause:
+                  List:
+                    ast.BasicLit:
+                      Kind: STRING
+                      Value: "world"
+                    ast.BasicLit:
+                      Kind: STRING
+                      Value: "hi"
+                  Body:
+                    ast.AssignStmt:
+                      Lhs:
+                        ast.Ident:
+                          Name: x
+                      Tok: =
+                      Rhs:
+                        ast.BasicLit:
+                          Kind: INT
+                          Value: 5
+                ast.CaseClause:
+                  List:
+                    ast.BasicLit:
+                      Kind: STRING
+                      Value: "xsw"
+                  Body:
+                    ast.AssignStmt:
+                      Lhs:
+                        ast.Ident:
+                          Name: x
+                      Tok: =
+                      Rhs:
+                        ast.BasicLit:
+                          Kind: INT
+                          Value: 3
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: println
+              Args:
+                ast.BasicLit:
+                  Kind: STRING
+                  Value: "x:"
+                ast.Ident:
+                  Name: x
+        ast.AssignStmt:
+          Lhs:
+            ast.Ident:
+              Name: c
+          Tok: :=
+          Rhs:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: make
+              Args:
+                ast.ChanType:
+                  Value:
+                    ast.Ident:
+                      Name: bool
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 100
+        ast.SelectStmt:
+          Body:
+            ast.BlockStmt:
+              List:
+                ast.CommClause:
+                  Comm:
+                    ast.SendStmt:
+                      Chan:
+                        ast.Ident:
+                          Name: c
+                      Values:
+                        ast.Ident:
+                          Name: true
+                ast.CommClause:
+                  Comm:
+                    ast.AssignStmt:
+                      Lhs:
+                        ast.Ident:
+                          Name: v
+                      Tok: :=
+                      Rhs:
+                        ast.UnaryExpr:
+                          Op: <-
+                          X:
+                            ast.Ident:
+                              Name: c
+                ast.CommClause:
+                  Body:
+                    ast.ExprStmt:
+                      X:
+                        ast.CallExpr:
+                          Fun:
+                            ast.Ident:
+                              Name: panic
+                          Args:
+                            ast.BasicLit:
+                              Kind: STRING
+                              Value: "error"
+`)
+}
+
 func TestFromInstance(t *testing.T) {
 	testFromDir(t, "", "./_instance")
 }
diff --git a/parser/parserdir_test.go b/parser/parserdir_test.go
index 8c4d29ed..a49f2487 100644
--- a/parser/parserdir_test.go
+++ b/parser/parserdir_test.go
@@ -42,272 +42,6 @@ func init() {
 	SetDebug(DbgFlagAll)
 }
 
-var testStdCode = `package bar; import "io"
-	// comment
-	x := 0
-	if t := false; t {
-		x = 3
-	} else {
-		x = 5
-	}
-	println("x:", x)
-
-	// comment 1
-	// comment 2
-	x = 0
-	switch s := "Hello"; s {
-	default:
-		x = 7
-	case "world", "hi":
-		x = 5
-	case "xsw":
-		x = 3
-	}
-	println("x:", x)
-
-	c := make(chan bool, 100)
-	select {
-	case c <- true:
-	case v := <-c:
-	default:
-		panic("error")
-	}
-`
-
-func TestStd(t *testing.T) {
-	fset := token.NewFileSet()
-	pkgs, err := Parse(fset, "/foo/bar.gop", testStdCode, ParseComments)
-	if err != nil || len(pkgs) != 1 {
-		t.Fatal("Parse failed:", err, len(pkgs))
-	}
-	bar := pkgs["bar"]
-	parsertest.Expect(t, bar, `package bar
-
-file bar.gop
-noEntrypoint
-ast.GenDecl:
-  Tok: import
-  Specs:
-    ast.ImportSpec:
-      Path:
-        ast.BasicLit:
-          Kind: STRING
-          Value: "io"
-ast.FuncDecl:
-  Doc:
-    ast.CommentGroup:
-      List:
-        ast.Comment:
-          Text: // comment
-  Name:
-    ast.Ident:
-      Name: main
-  Type:
-    ast.FuncType:
-      Params:
-        ast.FieldList:
-  Body:
-    ast.BlockStmt:
-      List:
-        ast.AssignStmt:
-          Lhs:
-            ast.Ident:
-              Name: x
-          Tok: :=
-          Rhs:
-            ast.BasicLit:
-              Kind: INT
-              Value: 0
-        ast.IfStmt:
-          Init:
-            ast.AssignStmt:
-              Lhs:
-                ast.Ident:
-                  Name: t
-              Tok: :=
-              Rhs:
-                ast.Ident:
-                  Name: false
-          Cond:
-            ast.Ident:
-              Name: t
-          Body:
-            ast.BlockStmt:
-              List:
-                ast.AssignStmt:
-                  Lhs:
-                    ast.Ident:
-                      Name: x
-                  Tok: =
-                  Rhs:
-                    ast.BasicLit:
-                      Kind: INT
-                      Value: 3
-          Else:
-            ast.BlockStmt:
-              List:
-                ast.AssignStmt:
-                  Lhs:
-                    ast.Ident:
-                      Name: x
-                  Tok: =
-                  Rhs:
-                    ast.BasicLit:
-                      Kind: INT
-                      Value: 5
-        ast.ExprStmt:
-          X:
-            ast.CallExpr:
-              Fun:
-                ast.Ident:
-                  Name: println
-              Args:
-                ast.BasicLit:
-                  Kind: STRING
-                  Value: "x:"
-                ast.Ident:
-                  Name: x
-        ast.AssignStmt:
-          Lhs:
-            ast.Ident:
-              Name: x
-          Tok: =
-          Rhs:
-            ast.BasicLit:
-              Kind: INT
-              Value: 0
-        ast.SwitchStmt:
-          Init:
-            ast.AssignStmt:
-              Lhs:
-                ast.Ident:
-                  Name: s
-              Tok: :=
-              Rhs:
-                ast.BasicLit:
-                  Kind: STRING
-                  Value: "Hello"
-          Tag:
-            ast.Ident:
-              Name: s
-          Body:
-            ast.BlockStmt:
-              List:
-                ast.CaseClause:
-                  Body:
-                    ast.AssignStmt:
-                      Lhs:
-                        ast.Ident:
-                          Name: x
-                      Tok: =
-                      Rhs:
-                        ast.BasicLit:
-                          Kind: INT
-                          Value: 7
-                ast.CaseClause:
-                  List:
-                    ast.BasicLit:
-                      Kind: STRING
-                      Value: "world"
-                    ast.BasicLit:
-                      Kind: STRING
-                      Value: "hi"
-                  Body:
-                    ast.AssignStmt:
-                      Lhs:
-                        ast.Ident:
-                          Name: x
-                      Tok: =
-                      Rhs:
-                        ast.BasicLit:
-                          Kind: INT
-                          Value: 5
-                ast.CaseClause:
-                  List:
-                    ast.BasicLit:
-                      Kind: STRING
-                      Value: "xsw"
-                  Body:
-                    ast.AssignStmt:
-                      Lhs:
-                        ast.Ident:
-                          Name: x
-                      Tok: =
-                      Rhs:
-                        ast.BasicLit:
-                          Kind: INT
-                          Value: 3
-        ast.ExprStmt:
-          X:
-            ast.CallExpr:
-              Fun:
-                ast.Ident:
-                  Name: println
-              Args:
-                ast.BasicLit:
-                  Kind: STRING
-                  Value: "x:"
-                ast.Ident:
-                  Name: x
-        ast.AssignStmt:
-          Lhs:
-            ast.Ident:
-              Name: c
-          Tok: :=
-          Rhs:
-            ast.CallExpr:
-              Fun:
-                ast.Ident:
-                  Name: make
-              Args:
-                ast.ChanType:
-                  Value:
-                    ast.Ident:
-                      Name: bool
-                ast.BasicLit:
-                  Kind: INT
-                  Value: 100
-        ast.SelectStmt:
-          Body:
-            ast.BlockStmt:
-              List:
-                ast.CommClause:
-                  Comm:
-                    ast.SendStmt:
-                      Chan:
-                        ast.Ident:
-                          Name: c
-                      Values:
-                        ast.Ident:
-                          Name: true
-                ast.CommClause:
-                  Comm:
-                    ast.AssignStmt:
-                      Lhs:
-                        ast.Ident:
-                          Name: v
-                      Tok: :=
-                      Rhs:
-                        ast.UnaryExpr:
-                          Op: <-
-                          X:
-                            ast.Ident:
-                              Name: c
-                ast.CommClause:
-                  Body:
-                    ast.ExprStmt:
-                      X:
-                        ast.CallExpr:
-                          Fun:
-                            ast.Ident:
-                              Name: panic
-                          Args:
-                            ast.BasicLit:
-                              Kind: STRING
-                              Value: "error"
-`)
-}
-
 func TestParseExprFrom(t *testing.T) {
 	fset := token.NewFileSet()
 	if _, err := ParseExprFrom(fset, "/foo/bar/not-exists", nil, 0); err == nil {
@@ -426,7 +160,7 @@ func TestParseEntries_SaveAbsFile(t *testing.T) {
 }
 
 func doTestParseEntries(t *testing.T, confReal Config) {
-	doTestParseEntry(t, func(fset *token.FileSet, filename string, src interface{}, conf Config) (f *ast.File, err error) {
+	doTestParseEntry(t, func(fset *token.FileSet, filename string, src any, conf Config) (f *ast.File, err error) {
 		fs, _ := memfs.File(filename, src)
 		pkgs, err := ParseFSEntries(fset, fs, []string{filename}, confReal)
 		if err != nil {
@@ -447,7 +181,7 @@ func TestParseEntry(t *testing.T) {
 	doTestParseEntry(t, ParseEntry)
 }
 
-func doTestParseEntry(t *testing.T, parseEntry func(fset *token.FileSet, filename string, src interface{}, conf Config) (f *ast.File, err error)) {
+func doTestParseEntry(t *testing.T, parseEntry func(fset *token.FileSet, filename string, src any, conf Config) (f *ast.File, err error)) {
 	fset := token.NewFileSet()
 	src, err := os.ReadFile("./_testdata/functype/functype.go")
 	if err != nil {
diff --git a/parser/parsertest/parsertest.go b/parser/parsertest/parsertest.go
index def8c133..ac94bb9f 100644
--- a/parser/parsertest/parsertest.go
+++ b/parser/parsertest/parsertest.go
@@ -31,7 +31,7 @@ import (
 	"github.com/goplus/gop/token"
 )
 
-func sortedKeys(m interface{}) []string {
+func sortedKeys(m any) []string {
 	iter := reflect.ValueOf(m).MapRange()
 	keys := make([]string, 0, 8)
 	for iter.Next() {
@@ -50,7 +50,7 @@ var (
 )
 
 // FprintNode prints a Go+ AST node.
-func FprintNode(w io.Writer, lead string, v interface{}, prefix, indent string) {
+func FprintNode(w io.Writer, lead string, v any, prefix, indent string) {
 	val := reflect.ValueOf(v)
 	switch val.Kind() {
 	case reflect.Slice:
diff --git a/printer/_testdata/10-List-comprehension/list_comprehens.gop b/printer/_testdata/10-List-comprehension/list_comprehens.gop
index 7f6e6724..1dba67ac 100644
--- a/printer/_testdata/10-List-comprehension/list_comprehens.gop
+++ b/printer/_testdata/10-List-comprehension/list_comprehens.gop
@@ -1,14 +1,14 @@
-y := [x*x for x <- [1, 3, 5, 7, 11]]
+y := [x*x for x in [1, 3, 5, 7, 11]]
 println(y)
 
-y = [x*x for x <- [1, 3, 5, 7, 11] if x > 3]
+y = [x*x for x in [1, 3, 5, 7, 11] if x > 3]
 println(y)
 
-z := [i+v for i, v <- [1, 3, 5, 7, 11] if i%2 == 1]
+z := [i+v for i, v in [1, 3, 5, 7, 11] if i%2 == 1]
 println(z)
 
-println([k+","+s for k, s <- {"Hello": "xsw", "Hi": "Go+"}])
+println([k+","+s for k, s in {"Hello": "xsw", "Hi": "Go+"}])
 
 arr := [1, 2, 3, 4, 5, 6]
-x := [[a, b] for a <- arr if a < b for b <- arr if b > 2]
+x := [[a, b] for a in arr if a < b for b in arr if b > 2]
 println("x:", x)
diff --git a/printer/_testdata/11-Map-comprehension/map_comprehens.gop b/printer/_testdata/11-Map-comprehension/map_comprehens.gop
index 23bd1f13..f8cd89bf 100644
--- a/printer/_testdata/11-Map-comprehension/map_comprehens.gop
+++ b/printer/_testdata/11-Map-comprehension/map_comprehens.gop
@@ -1,8 +1,8 @@
-y := {x: i for i, x <- [1, 3, 5, 7, 11]}
+y := {x: i for i, x in [1, 3, 5, 7, 11]}
 println(y)
 
-y = {x: i for i, x <- [1, 3, 5, 7, 11] if i%2 == 1}
+y = {x: i for i, x in [1, 3, 5, 7, 11] if i%2 == 1}
 println(y)
 
-z := {v: k for k, v <- {1: "Hello", 3: "Hi", 5: "xsw", 7: "Go+"} if k > 3}
+z := {v: k for k, v in {1: "Hello", 3: "Hi", 5: "xsw", 7: "Go+"} if k > 3}
 println(z)
diff --git a/printer/_testdata/12-Select-comprehension/select.gop b/printer/_testdata/12-Select-comprehension/select.gop
index cc9ee6ad..becf2ed2 100644
--- a/printer/_testdata/12-Select-comprehension/select.gop
+++ b/printer/_testdata/12-Select-comprehension/select.gop
@@ -1,9 +1,9 @@
 a := [2, 3, 5, 7, 9, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59]
-where := {i for i, v <- a if v == 19}
+where := {i for i, v in a if v == 19}
 println("19 is at index", where)
 
-if at, ok := {i for i, v <- a if v == 37}; ok {
+if at, ok := {i for i, v in a if v == 37}; ok {
 	println("37 is found! index =", at)
 }
 
-println("first element of a is:", {v for v <- a})
+println("first element of a is:", {v for v in a})
diff --git a/printer/_testdata/12-Select-comprehension2/findscore.gop b/printer/_testdata/12-Select-comprehension2/findscore.gop
index a7845a1d..b408f0d9 100644
--- a/printer/_testdata/12-Select-comprehension2/findscore.gop
+++ b/printer/_testdata/12-Select-comprehension2/findscore.gop
@@ -4,7 +4,7 @@ type student struct {
 }
 
 func findScore(a []student, name string) (score int, ok bool) {
-	return {x.score for x <- a if x.name == name}
+	return {x.score for x in a if x.name == name}
 }
 
 a := [student{"ken", 95}, student{"john", 90}, student{"tom", 58}]
diff --git a/printer/_testdata/13-Exists-comprehension/exists.gop b/printer/_testdata/13-Exists-comprehension/exists.gop
index 2e4cd1bb..5c0ca678 100644
--- a/printer/_testdata/13-Exists-comprehension/exists.gop
+++ b/printer/_testdata/13-Exists-comprehension/exists.gop
@@ -5,8 +5,8 @@ type student struct {
 
 a := [student{"du", 84}, student{"wang", 70}, student{"ken", 100}]
 
-hasFullMark := {for x <- a if x.score == 100}
+hasFullMark := {for x in a if x.score == 100}
 println("is any student full mark:", hasFullMark)
 
-hasFailed := {for x <- a if x.score < 60}
+hasFailed := {for x in a if x.score < 60}
 println("is any student failed:", hasFailed)
diff --git a/printer/_testdata/14-Using-goplus-in-Go/foo/foo.gop b/printer/_testdata/14-Using-goplus-in-Go/foo/foo.gop
index 6bda9ae9..379e0d9c 100644
--- a/printer/_testdata/14-Using-goplus-in-Go/foo/foo.gop
+++ b/printer/_testdata/14-Using-goplus-in-Go/foo/foo.gop
@@ -1,5 +1,5 @@
 package foo
 
 func ReverseMap(m map[string]int) map[int]string {
-	return {v: k for k, v <- m}
+	return {v: k for k, v in m}
 }
diff --git a/printer/_testdata/22-For-loop/for.gop b/printer/_testdata/22-For-loop/for.gop
index 4eb17153..f02a9004 100644
--- a/printer/_testdata/22-For-loop/for.gop
+++ b/printer/_testdata/22-For-loop/for.gop
@@ -1,14 +1,14 @@
 // -----------------------------------------------------------------------------
-// for with <-
+// for..in
 
 sum := 0
-for x <- [1, 3, 5, 7, 11, 13, 17] if x > 3 {
+for x in [1, 3, 5, 7, 11, 13, 17] if x > 3 {
 	sum += x
 }
 println("sum(5,7,11,13,17):", sum)
 
 fns := make([]func() int, 3)
-for i, x <- [3, 15, 777] {
+for i, x in [3, 15, 777] {
 	v := x
 	fns[i] = func() int {
 		return v
diff --git a/printer/_testdata/23-Defer/defer.gop b/printer/_testdata/23-Defer/defer.gop
index 7ea750ff..901fc0dd 100644
--- a/printer/_testdata/23-Defer/defer.gop
+++ b/printer/_testdata/23-Defer/defer.gop
@@ -14,7 +14,7 @@ func g() (x int) {
 }
 
 func h() (x int) {
-	for i <- [3, 2, 1] {
+	for i in [3, 2, 1] {
 		v := i
 		defer func() {
 			x = v
diff --git a/printer/_testdata/33-Interface/shape.gop b/printer/_testdata/33-Interface/shape.gop
index e458cb4f..05921740 100644
--- a/printer/_testdata/33-Interface/shape.gop
+++ b/printer/_testdata/33-Interface/shape.gop
@@ -22,7 +22,7 @@ func (p *Circle) Area() float64 {
 
 func Area(shapes ...Shape) float64 {
 	s := 0.0
-	for shape <- shapes {
+	for shape in shapes {
 		s += shape.Area()
 	}
 	return s
diff --git a/printer/_testdata/35-Chan-select/select.gop b/printer/_testdata/35-Chan-select/select.gop
index 792c09a6..2f865180 100644
--- a/printer/_testdata/35-Chan-select/select.gop
+++ b/printer/_testdata/35-Chan-select/select.gop
@@ -15,7 +15,7 @@ func consume(xchg chan int) {
 }
 
 func product(xchg chan int, from chan int) {
-	for x <- from {
+	for x in from {
 		xchg <- x
 	}
 	done <- true
diff --git a/printer/_testdata/39-Lambda-expression/lambda.gop b/printer/_testdata/39-Lambda-expression/lambda.gop
index 4fc2c77b..f43751a7 100644
--- a/printer/_testdata/39-Lambda-expression/lambda.gop
+++ b/printer/_testdata/39-Lambda-expression/lambda.gop
@@ -1,5 +1,5 @@
 func Map(c []float64, t func(float64) float64) []float64 {
-	return [t(x) for x <- c]
+	return [t(x) for x in c]
 }
 
 println(Map([1.2, 3.5, 6], x => x * x))
diff --git a/printer/_testdata/41-UDT-RangeForEach/udt_range.gop b/printer/_testdata/41-UDT-RangeForEach/udt_range.gop
index 2b6debf6..74264213 100644
--- a/printer/_testdata/41-UDT-RangeForEach/udt_range.gop
+++ b/printer/_testdata/41-UDT-RangeForEach/udt_range.gop
@@ -6,8 +6,8 @@ func (p *foo) Gop_Enum(proc func(key int, val string)) {
 	proc(7, "Go+")
 }
 
-for k, v <- new(foo) {
+for k, v in new(foo) {
 	println(k, v)
 }
 
-println({v: k for k, v <- new(foo)})
+println({v: k for k, v in new(foo)})
diff --git a/printer/_testdata/42-UDT-RangeIterator/udt_range_iter.gop b/printer/_testdata/42-UDT-RangeIterator/udt_range_iter.gop
index fa89e540..3d48131d 100644
--- a/printer/_testdata/42-UDT-RangeIterator/udt_range_iter.gop
+++ b/printer/_testdata/42-UDT-RangeIterator/udt_range_iter.gop
@@ -25,8 +25,8 @@ func (p *foo) Gop_Enum() *fooIter {
 }
 
 obj := newFoo()
-for k, v <- obj {
+for k, v in obj {
 	println(k, v)
 }
 
-println({v: k for k, v <- obj})
+println({v: k for k, v in obj})
diff --git a/printer/_testdata/43-RangeExpr/rangeexpr.gop b/printer/_testdata/43-RangeExpr/rangeexpr.gop
index 5aafe866..666df942 100644
--- a/printer/_testdata/43-RangeExpr/rangeexpr.gop
+++ b/printer/_testdata/43-RangeExpr/rangeexpr.gop
@@ -1,12 +1,12 @@
 println "---------------------------"
 
-for i <- :10 {
+for i in :10 {
 	println(i)
 }
 
 println "---------------------------"
 
-for i <- 9:-1:-1 {
+for i in 9:-1:-1 {
 	println(i)
 }
 
diff --git a/x/build/build_test.go b/x/build/build_test.go
index ea82939a..cdc10801 100644
--- a/x/build/build_test.go
+++ b/x/build/build_test.go
@@ -47,11 +47,11 @@ func init() {
 	build.RegisterClassFileType("_yap.gox", "App", nil, "github.com/goplus/yap")
 }
 
-func gopClTest(t *testing.T, gopcode interface{}, expected string) {
+func gopClTest(t *testing.T, gopcode any, expected string) {
 	gopClTestEx(t, "main.gop", gopcode, expected)
 }
 
-func gopClTestEx(t *testing.T, filename string, gopcode interface{}, expected string) {
+func gopClTestEx(t *testing.T, filename string, gopcode any, expected string) {
 	data, err := ctx.BuildFile(filename, gopcode)
 	if err != nil {
 		t.Fatalf("build gop error: %v", err)
@@ -410,9 +410,6 @@ type MyGame struct {
 func (this *Cat) Main() {
 	fmt.Println("hi")
 }
-func (this *Cat) Classfname() string {
-	return "Cat"
-}
 func (this *MyGame) Main() {
 	spx.Gopt_MyGame_Main(this)
 }
diff --git a/x/format/_testdata/gopsyntax/format.expect b/x/format/_testdata/gopsyntax/format.expect
index b783df28..372937a3 100644
--- a/x/format/_testdata/gopsyntax/format.expect
+++ b/x/format/_testdata/gopsyntax/format.expect
@@ -3,7 +3,7 @@ plot x => x * x
 onStart x => {
 	println x
 }
-for i <- :10 {
+for i in :10 {
 }
-println [x*x for x <- :10]
+println [x*x for x in :10]
 print f()?:10
diff --git a/x/jsonrpc2/jsonrpc2test/cases/testcase.go b/x/jsonrpc2/jsonrpc2test/cases/testcase.go
index 886d6537..ef277554 100644
--- a/x/jsonrpc2/jsonrpc2test/cases/testcase.go
+++ b/x/jsonrpc2/jsonrpc2test/cases/testcase.go
@@ -92,24 +92,24 @@ type invoker interface {
 
 type notify struct {
 	method string
-	params interface{}
+	params any
 }
 
 type call struct {
 	method string
-	params interface{}
-	expect interface{}
+	params any
+	expect any
 }
 
 type async struct {
 	name   string
 	method string
-	params interface{}
+	params any
 }
 
 type collect struct {
 	name   string
-	expect interface{}
+	expect any
 	fails  bool
 }
 
@@ -175,7 +175,7 @@ func (test call) Invoke(t *testing.T, ctx context.Context, h *handler) {
 
 func (test echo) Invoke(t *testing.T, ctx context.Context, h *handler) {
 	results := newResults(test.expect)
-	if err := h.conn.Call(ctx, "echo", []interface{}{test.method, test.params}).Await(ctx, results); err != nil {
+	if err := h.conn.Call(ctx, "echo", []any{test.method, test.params}).Await(ctx, results); err != nil {
 		t.Fatalf("%v:Echo failed: %v", test.method, err)
 	}
 	verifyResults(t, test.method, results, test.expect)
@@ -216,10 +216,10 @@ func (test sequence) Invoke(t *testing.T, ctx context.Context, h *handler) {
 }
 
 // newResults makes a new empty copy of the expected type to put the results into
-func newResults(expect interface{}) interface{} {
+func newResults(expect any) any {
 	switch e := expect.(type) {
-	case []interface{}:
-		var r []interface{}
+	case []any:
+		var r []any
 		for _, v := range e {
 			r = append(r, reflect.New(reflect.TypeOf(v)).Interface())
 		}
@@ -232,7 +232,7 @@ func newResults(expect interface{}) interface{} {
 }
 
 // verifyResults compares the results to the expected values
-func verifyResults(t *testing.T, method string, results interface{}, expect interface{}) {
+func verifyResults(t *testing.T, method string, results any, expect any) {
 	if expect == nil {
 		if results != nil {
 			t.Errorf("%v:Got results %+v where none expeted", method, expect)
@@ -288,7 +288,7 @@ func (h *handler) tryCloseWaiter(name string) (ok bool) {
 	return
 }
 
-func (h *handler) Preempt(ctx context.Context, req *jsonrpc2.Request) (interface{}, error) {
+func (h *handler) Preempt(ctx context.Context, req *jsonrpc2.Request) (any, error) {
 	switch req.Method {
 	case "unblock":
 		var name string
@@ -314,7 +314,7 @@ func (h *handler) Preempt(ctx context.Context, req *jsonrpc2.Request) (interface
 	}
 }
 
-func (h *handler) Handle(ctx context.Context, req *jsonrpc2.Request) (interface{}, error) {
+func (h *handler) Handle(ctx context.Context, req *jsonrpc2.Request) (any, error) {
 	switch req.Method {
 	case "no_args":
 		if len(req.Params) > 0 {
@@ -359,11 +359,11 @@ func (h *handler) Handle(ctx context.Context, req *jsonrpc2.Request) (interface{
 		}
 		return path.Join(v...), nil
 	case "echo":
-		var v []interface{}
+		var v []any
 		if err := json.Unmarshal(req.Params, &v); err != nil {
 			return nil, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err)
 		}
-		var result interface{}
+		var result any
 		err := h.conn.Call(ctx, v[0].(string), v[1]).Await(ctx, &result)
 		return result, err
 	case "wait":
diff --git a/x/typesutil/check_test.go b/x/typesutil/check_test.go
index 07da8446..656aef7a 100644
--- a/x/typesutil/check_test.go
+++ b/x/typesutil/check_test.go
@@ -25,7 +25,7 @@ func init() {
 	typesutil.SetDebug(typesutil.DbgFlagDefault)
 }
 
-func loadFiles(fset *token.FileSet, file string, src interface{}, goxfile string, goxsrc interface{}, gofile string, gosrc interface{}) ([]*ast.File, []*goast.File, error) {
+func loadFiles(fset *token.FileSet, file string, src any, goxfile string, goxsrc any, gofile string, gosrc any) ([]*ast.File, []*goast.File, error) {
 	var files []*ast.File
 	var gofiles []*goast.File
 	if file != "" {
@@ -52,7 +52,7 @@ func loadFiles(fset *token.FileSet, file string, src interface{}, goxfile string
 	return files, gofiles, nil
 }
 
-func checkFiles(fset *token.FileSet, file string, src interface{}, goxfile string, goxsrc interface{}, gofile string, gosrc interface{}) (*typesutil.Info, *types.Info, error) {
+func checkFiles(fset *token.FileSet, file string, src any, goxfile string, goxsrc any, gofile string, gosrc any) (*typesutil.Info, *types.Info, error) {
 	files, gofiles, err := loadFiles(fset, file, src, goxfile, goxsrc, gofile, gosrc)
 	if err != nil {
 		return nil, nil, err
@@ -181,6 +181,13 @@ println(pt)
 	}
 	_, _, err = checkFiles(fset, "main.gop", `
 var i int = "hello"
+`, "", "", "", "")
+	if err == nil {
+		t.Fatal("no error")
+	}
+	_, _, err = checkFiles(fset, "main.gop", `
+var nums []int
+nums = append(nums, "NaN")
 `, "", "", "", "")
 	if err == nil {
 		t.Fatal("no error")
diff --git a/x/typesutil/info_test.go b/x/typesutil/info_test.go
index 5f34a42b..1cede6fa 100644
--- a/x/typesutil/info_test.go
+++ b/x/typesutil/info_test.go
@@ -106,7 +106,7 @@ func parseMixedSource(mod *gopmod.Module, fset *token.FileSet, name, src string,
 	return chkOpts.Types, info, ginfo, err
 }
 
-func parseSource(fset *token.FileSet, filename string, src interface{}, mode parser.Mode) (*types.Package, *typesutil.Info, error) {
+func parseSource(fset *token.FileSet, filename string, src any, mode parser.Mode) (*types.Package, *typesutil.Info, error) {
 	f, err := parser.ParseEntry(fset, filename, src, parser.Config{
 		Mode: mode,
 	})
@@ -136,7 +136,7 @@ func parseSource(fset *token.FileSet, filename string, src interface{}, mode par
 	return pkg, info, err
 }
 
-func parseGoSource(fset *token.FileSet, filename string, src interface{}, mode goparser.Mode) (*types.Package, *types.Info, error) {
+func parseGoSource(fset *token.FileSet, filename string, src any, mode goparser.Mode) (*types.Package, *types.Info, error) {
 	f, err := goparser.ParseFile(fset, filename, src, mode)
 	if err != nil {
 		return nil, nil, err
@@ -190,7 +190,7 @@ func testGopInfoEx(t *testing.T, mod *gopmod.Module, name string, src string, go
 	}
 }
 
-func testInfo(t *testing.T, src interface{}) {
+func testInfo(t *testing.T, src any) {
 	fset := token.NewFileSet()
 	_, info, err := parseSource(fset, "main.gop", src, parser.ParseComments)
 	if err != nil {
@@ -1679,61 +1679,57 @@ func onCloned() {
 	say("Hi")
 }
 `, `== types ==
-000:  0: 0 | "Kai"               *ast.BasicLit                  | value   : untyped string = "Kai" | constant
-001:  0: 0 | *MyGame             *ast.StarExpr                  | type    : *main.MyGame | type
-002:  0: 0 | Kai                 *ast.Ident                     | type    : main.Kai | type
-003:  0: 0 | MyGame              *ast.Ident                     | type    : main.MyGame | type
-004:  0: 0 | string              *ast.Ident                     | type    : string | type
-005:  3: 4 | int                 *ast.Ident                     | type    : int | type
-006:  6:11 | struct {
+000:  0: 0 | *MyGame             *ast.StarExpr                  | type    : *main.MyGame | type
+001:  0: 0 | Kai                 *ast.Ident                     | type    : main.Kai | type
+002:  0: 0 | MyGame              *ast.Ident                     | type    : main.MyGame | type
+003:  3: 4 | int                 *ast.Ident                     | type    : int | type
+004:  6:11 | struct {
 	x int
 	y int
 } *ast.StructType                | type    : struct{x int; y int} | type
-007:  7: 4 | int                 *ast.Ident                     | type    : int | type
-008:  8: 4 | int                 *ast.Ident                     | type    : int | type
-009: 12: 2 | a                   *ast.Ident                     | var     : int | variable
-010: 12: 6 | 1                   *ast.BasicLit                  | value   : untyped int = 1 | constant
-011: 13: 2 | clone               *ast.Ident                     | value   : func(sprite interface{}) | value
-012: 14: 2 | clone               *ast.Ident                     | value   : func(sprite interface{}, data interface{}) | value
-013: 14: 2 | clone info{1, 2}    *ast.CallExpr                  | void    : () | no value
-014: 14: 8 | info                *ast.Ident                     | type    : main.info | type
-015: 14: 8 | info{1, 2}          *ast.CompositeLit              | value   : main.info | value
-016: 14:13 | 1                   *ast.BasicLit                  | value   : untyped int = 1 | constant
-017: 14:15 | 2                   *ast.BasicLit                  | value   : untyped int = 2 | constant
-018: 15: 2 | clone               *ast.Ident                     | value   : func(sprite interface{}, data interface{}) | value
-019: 15: 2 | clone &info{1, 2}   *ast.CallExpr                  | void    : () | no value
-020: 15: 8 | &info{1, 2}         *ast.UnaryExpr                 | value   : *main.info | value
-021: 15: 9 | info                *ast.Ident                     | type    : main.info | type
-022: 15: 9 | info{1, 2}          *ast.CompositeLit              | value   : main.info | value
-023: 15:14 | 1                   *ast.BasicLit                  | value   : untyped int = 1 | constant
-024: 15:16 | 2                   *ast.BasicLit                  | value   : untyped int = 2 | constant
-025: 19: 2 | say                 *ast.Ident                     | value   : func(msg string, secs ...float64) | value
-026: 19: 2 | say("Hi")           *ast.CallExpr                  | void    : () | no value
-027: 19: 6 | "Hi"                *ast.BasicLit                  | value   : untyped string = "Hi" | constant
+005:  7: 4 | int                 *ast.Ident                     | type    : int | type
+006:  8: 4 | int                 *ast.Ident                     | type    : int | type
+007: 12: 2 | a                   *ast.Ident                     | var     : int | variable
+008: 12: 6 | 1                   *ast.BasicLit                  | value   : untyped int = 1 | constant
+009: 13: 2 | clone               *ast.Ident                     | value   : func(sprite any) | value
+010: 14: 2 | clone               *ast.Ident                     | value   : func(sprite any, data any) | value
+011: 14: 2 | clone info{1, 2}    *ast.CallExpr                  | void    : () | no value
+012: 14: 8 | info                *ast.Ident                     | type    : main.info | type
+013: 14: 8 | info{1, 2}          *ast.CompositeLit              | value   : main.info | value
+014: 14:13 | 1                   *ast.BasicLit                  | value   : untyped int = 1 | constant
+015: 14:15 | 2                   *ast.BasicLit                  | value   : untyped int = 2 | constant
+016: 15: 2 | clone               *ast.Ident                     | value   : func(sprite any, data any) | value
+017: 15: 2 | clone &info{1, 2}   *ast.CallExpr                  | void    : () | no value
+018: 15: 8 | &info{1, 2}         *ast.UnaryExpr                 | value   : *main.info | value
+019: 15: 9 | info                *ast.Ident                     | type    : main.info | type
+020: 15: 9 | info{1, 2}          *ast.CompositeLit              | value   : main.info | value
+021: 15:14 | 1                   *ast.BasicLit                  | value   : untyped int = 1 | constant
+022: 15:16 | 2                   *ast.BasicLit                  | value   : untyped int = 2 | constant
+023: 19: 2 | say                 *ast.Ident                     | value   : func(msg string, secs ...float64) | value
+024: 19: 2 | say("Hi")           *ast.CallExpr                  | void    : () | no value
+025: 19: 6 | "Hi"                *ast.BasicLit                  | value   : untyped string = "Hi" | constant
 == defs ==
-000:  0: 0 | Classfname          | func (*main.Kai).Classfname() string
-001:  0: 0 | Main                | func (*main.Kai).Main()
-002:  0: 0 | this                | var this *main.Kai
-003:  3: 2 | a                   | field a int
-004:  6: 6 | info                | type main.info struct{x int; y int}
-005:  7: 2 | x                   | field x int
-006:  8: 2 | y                   | field y int
-007: 11: 6 | onInit              | func (*main.Kai).onInit()
-008: 18: 6 | onCloned            | func (*main.Kai).onCloned()
+000:  0: 0 | Main                | func (*main.Kai).Main()
+001:  0: 0 | this                | var this *main.Kai
+002:  3: 2 | a                   | field a int
+003:  6: 6 | info                | type main.info struct{x int; y int}
+004:  7: 2 | x                   | field x int
+005:  8: 2 | y                   | field y int
+006: 11: 6 | onInit              | func (*main.Kai).onInit()
+007: 18: 6 | onCloned            | func (*main.Kai).onCloned()
 == uses ==
 000:  0: 0 | Kai                 | type main.Kai struct{github.com/goplus/gop/cl/internal/spx.Sprite; *main.MyGame; a int}
 001:  0: 0 | MyGame              | type main.MyGame struct{*github.com/goplus/gop/cl/internal/spx.MyGame}
-002:  0: 0 | string              | type string
-003:  3: 4 | int                 | type int
-004:  7: 4 | int                 | type int
-005:  8: 4 | int                 | type int
-006: 12: 2 | a                   | field a int
-007: 13: 2 | clone               | func github.com/goplus/gop/cl/internal/spx.Gopt_Sprite_Clone__0(sprite interface{})
-008: 14: 2 | clone               | func github.com/goplus/gop/cl/internal/spx.Gopt_Sprite_Clone__1(sprite interface{}, data interface{})
-009: 14: 8 | info                | type main.info struct{x int; y int}
-010: 15: 2 | clone               | func github.com/goplus/gop/cl/internal/spx.Gopt_Sprite_Clone__1(sprite interface{}, data interface{})
-011: 15: 9 | info                | type main.info struct{x int; y int}
-012: 19: 2 | say                 | func (*github.com/goplus/gop/cl/internal/spx.Sprite).Say(msg string, secs ...float64)
+002:  3: 4 | int                 | type int
+003:  7: 4 | int                 | type int
+004:  8: 4 | int                 | type int
+005: 12: 2 | a                   | field a int
+006: 13: 2 | clone               | func github.com/goplus/gop/cl/internal/spx.Gopt_Sprite_Clone__0(sprite any)
+007: 14: 2 | clone               | func github.com/goplus/gop/cl/internal/spx.Gopt_Sprite_Clone__1(sprite any, data any)
+008: 14: 8 | info                | type main.info struct{x int; y int}
+009: 15: 2 | clone               | func github.com/goplus/gop/cl/internal/spx.Gopt_Sprite_Clone__1(sprite any, data any)
+010: 15: 9 | info                | type main.info struct{x int; y int}
+011: 19: 2 | say                 | func (*github.com/goplus/gop/cl/internal/spx.Sprite).Say(msg string, secs ...float64)
 == overloads ==
 000: 13: 2 | clone               | func (github.com/goplus/gop/cl/internal/spx.Sprite).Clone(__gop_overload_args__ interface{_()})
 001: 14: 2 | clone               | func (github.com/goplus/gop/cl/internal/spx.Sprite).Clone(__gop_overload_args__ interface{_()})