Repository: goplus/gop
Pull request #1859 (2024-04-12 18:51:21)

v1.2.6


Patch:
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index 76788ece..f3e4bc2b 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -2,9 +2,11 @@ name: Go+ CI
 
 on:
   push:
-    branches: [ main ]
+    branches:
+    - "*"
   pull_request:
-    branches: [ main ]
+    branches:
+    - "*"
 
 jobs:
   Check:
@@ -19,8 +21,16 @@ jobs:
   Test:
     strategy:
       matrix:
-        go-version: [1.18.x, 1.19.x, 1.20.x, 1.21.x]
-        os: [ubuntu-latest, windows-latest, macos-11]
+        os:
+        - ubuntu-latest
+        - windows-latest
+        - macos-latest
+        go:
+        - 1.18.x
+        - 1.19.x
+        - 1.20.x
+        - 1.21.x
+        - 1.22.x
     runs-on: ${{ matrix.os }}
     steps:
     - uses: actions/checkout@v4
@@ -28,7 +38,7 @@ jobs:
     - name: Set up Go
       uses: actions/setup-go@v5
       with:
-        go-version: ${{ matrix.go-version }}
+        go: ${{ matrix.go }}
 
     - name: Test Go+ installer
       run: |
@@ -41,3 +51,5 @@ jobs:
 
     - name: Codecov
       uses: codecov/codecov-action@v4
+      with:
+        token: ${{ secrets.CODECOV_TOKEN }}
diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml
index 1b88a592..0c966198 100644
--- a/.github/workflows/release-build.yml
+++ b/.github/workflows/release-build.yml
@@ -12,7 +12,7 @@ jobs:
     - uses: actions/checkout@v4
       with:
         fetch-depth: 0
-    
+
     - name: Check goreleaser files
       run: |
         pip install --no-input pyyaml
@@ -28,7 +28,7 @@ jobs:
         echo Current tag: $tag_name
         git checkout $tag_name
         echo "TAG_NAME=${tag_name}" >> $GITHUB_ENV
-    
+
     - name: Check VERSION consistency
       run: |
         tag=$(git describe --tags --dirty)
@@ -44,7 +44,7 @@ jobs:
           echo "TAG: $tag"
           exit 1
         fi
-    
+
     - name: Check pre-release
       run: |
         version=$(cat VERSION)
@@ -57,7 +57,17 @@ jobs:
     - name: Set up Go
       uses: actions/setup-go@v5
       with:
-        go-version: "1.21"
+        go-version: 1.21.x
+
+    - name: Set up QEMU
+      uses: docker/setup-qemu-action@v3
+
+    - name: Log in to GitHub Container Registry
+      uses: docker/login-action@v3
+      with:
+        registry: ghcr.io
+        username: ${{ github.actor }}
+        password: ${{ github.token }}
 
     - name: Release with goreleaser
       uses: goreleaser/goreleaser-action@v5
@@ -67,8 +77,7 @@ jobs:
         version: latest
         args: release --clean -p 4
       env:
-        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-        GITHUB_REPOSITORY_OWNER: ${{ github.repository_owner }}
+        GITHUB_TOKEN: ${{ github.token }}
         WINGET_PKGS_PRIVATE_KEY: ${{ secrets.WINGET_PKGS_PRIVATE_KEY }}
 
     - name: Upload deb/rpm to Fury.io
@@ -85,4 +94,3 @@ jobs:
         done
       env:
         FURY_TOKEN: ${{ secrets.FURY_TOKEN }}
-        GITHUB_REPOSITORY_OWNER: ${{ github.repository_owner }}
diff --git a/.goreleaser.yaml b/.goreleaser.yaml
index 056f1ef7..e5da561d 100644
--- a/.goreleaser.yaml
+++ b/.goreleaser.yaml
@@ -43,11 +43,7 @@ archives:
   - format: tar.gz
     # this name template makes the OS and Arch compatible with the results of `uname`.
     name_template: >-
-      {{ .ProjectName }}_v{{.Version}}_
-      {{- title .Os }}_
-      {{- if eq .Arch "amd64" }}x86_64
-      {{- else if eq .Arch "386" }}i386
-      {{- else }}{{ .Arch }}{{ end }}
+      {{ .ProjectName }}{{.Version}}.{{ .Os }}-{{ .Arch }}
       {{- if .Arm }}v{{ .Arm }}{{ end }}
     # use zip for windows archives
     format_overrides:
@@ -68,6 +64,60 @@ changelog:
       - "^docs:"
       - "^test:"
 
+dockers:
+  - goarch: "386"
+    image_templates:
+      - ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:{{ .Version }}-386
+    use: buildx
+    build_flag_templates:
+      - --platform=linux/386
+      - --build-arg=USE_GORELEASER_ARTIFACTS=1
+    extra_files:
+      - ./
+  - goarch: amd64
+    image_templates:
+      - ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:{{ .Version }}-amd64
+    use: buildx
+    build_flag_templates:
+      - --platform=linux/amd64
+      - --build-arg=USE_GORELEASER_ARTIFACTS=1
+    extra_files:
+      - ./
+  - goarch: arm64
+    image_templates:
+      - ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:{{ .Version }}-arm64
+    use: buildx
+    build_flag_templates:
+      - --platform=linux/arm64
+      - --build-arg=USE_GORELEASER_ARTIFACTS=1
+    extra_files:
+      - ./
+
+docker_manifests:
+  - name_template: ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:{{ .Version }}
+    image_templates:
+      - ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:{{ .Version }}-386
+      - ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:{{ .Version }}-amd64
+      - ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:{{ .Version }}-arm64
+  - name_template: ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:{{ .Major }}.{{ .Minor }}
+    image_templates:
+      - ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:{{ .Version }}-386
+      - ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:{{ .Version }}-amd64
+      - ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:{{ .Version }}-arm64
+    skip_push: auto
+  - name_template: ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:{{ .Major }}
+    image_templates:
+      - ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:{{ .Version }}-386
+      - ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:{{ .Version }}-amd64
+      - ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:{{ .Version }}-arm64
+    skip_push: auto
+  - name_template: ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:latest
+    image_templates:
+      - ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:{{ .Version }}-386
+      - ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:{{ .Version }}-amd64
+      - ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:{{ .Version }}-arm64
+    skip_push: auto
+
 winget:
   - name: goplus
     homepage: "https://goplus.org/"
@@ -138,15 +188,15 @@ nfpms:
         dependencies:
           - "golang-bin >= 1.18.0"
     file_name_template: >-
-      {{ .ProjectName }}_v{{.Version}}_
-      {{- title .Os }}_
-      {{- if eq .Arch "amd64" }}x86_64
-      {{- else if eq .Arch "386" }}i386
-      {{- else }}{{ .Arch }}{{ end }}
+      {{ .ProjectName }}{{.Version}}.{{ .Os }}-{{ .Arch }}
       {{- if .Arm }}v{{ .Arm }}{{ end }}
     bindir: /usr/lib/{{ .ProjectName }}
     contents:
       # source folder
+      - src: "CODE_OF_CONDUCT.md"
+        dst: "/usr/lib/{{ .ProjectName }}/CODE_OF_CONDUCT.md"
+      - src: "Dockerfile"
+        dst: "/usr/lib/{{ .ProjectName }}/Dockerfile"
       - src: "LICENSE"
         dst: "/usr/lib/{{ .ProjectName }}/LICENSE"
       - src: "Makefile"
@@ -228,14 +278,14 @@ snapcrafts:
     confinement: classic
     license: Apache-2.0
     name_template: >-
-      {{ .ProjectName }}_v{{.Version}}_
-      {{- title .Os }}_
-      {{- if eq .Arch "amd64" }}x86_64
-      {{- else if eq .Arch "386" }}i386
-      {{- else }}{{ .Arch }}{{ end }}
+      {{ .ProjectName }}{{.Version}}.{{ .Os }}-{{ .Arch }}
       {{- if .Arm }}v{{ .Arm }}{{ end }}
     extra_files:
       # source folder
+      - source: "CODE_OF_CONDUCT.md"
+        destination: "CODE_OF_CONDUCT.md"
+      - source: "Dockerfile"
+        destination: "Dockerfile"
       - source: "LICENSE"
         destination: "LICENSE"
       - source: "Makefile"
@@ -309,4 +359,7 @@ snapcrafts:
           GOPROOT: "$SNAP"
 
 checksum:
-  name_template: "{{ .ProjectName }}_v{{ .Version }}_checksums.txt"
+  name_template: "{{ .ProjectName }}{{ .Version }}.checksums.txt"
+
+release:
+  prerelease: auto
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 00000000..91964024
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,128 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+We as members, contributors, and leaders pledge to make participation in our
+community a harassment-free experience for everyone, regardless of age, body
+size, visible or invisible disability, ethnicity, sex characteristics, gender
+identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, religion, or sexual identity
+and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming,
+diverse, inclusive, and healthy community.
+
+## Our Standards
+
+Examples of behavior that contributes to a positive environment for our
+community include:
+
+* Demonstrating empathy and kindness toward other people
+* Being respectful of differing opinions, viewpoints, and experiences
+* Giving and gracefully accepting constructive feedback
+* Accepting responsibility and apologizing to those affected by our mistakes,
+  and learning from the experience
+* Focusing on what is best not just for us as individuals, but for the
+  overall community
+
+Examples of unacceptable behavior include:
+
+* The use of sexualized language or imagery, and sexual attention or
+  advances of any kind
+* Trolling, insulting or derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or email
+  address, without their explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+  professional setting
+
+## Enforcement Responsibilities
+
+Community leaders are responsible for clarifying and enforcing our standards of
+acceptable behavior and will take appropriate and fair corrective action in
+response to any behavior that they deem inappropriate, threatening, offensive,
+or harmful.
+
+Community leaders have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, and will communicate reasons for moderation
+decisions when appropriate.
+
+## Scope
+
+This Code of Conduct applies within all community spaces, and also applies when
+an individual is officially representing the community in public spaces.
+Examples of representing our community include using an official e-mail address,
+posting via an official social media account, or acting as an appointed
+representative at an online or offline event.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported to the community leaders responsible for enforcement at
+conduct@goplus.org.
+All complaints will be reviewed and investigated promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the
+reporter of any incident.
+
+## Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining
+the consequences for any action they deem in violation of this Code of Conduct:
+
+### 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed
+unprofessional or unwelcome in the community.
+
+**Consequence**: A private, written warning from community leaders, providing
+clarity around the nature of the violation and an explanation of why the
+behavior was inappropriate. A public apology may be requested.
+
+### 2. Warning
+
+**Community Impact**: A violation through a single incident or series
+of actions.
+
+**Consequence**: A warning with consequences for continued behavior. No
+interaction with the people involved, including unsolicited interaction with
+those enforcing the Code of Conduct, for a specified period of time. This
+includes avoiding interactions in community spaces as well as external channels
+like social media. Violating these terms may lead to a temporary or
+permanent ban.
+
+### 3. Temporary Ban
+
+**Community Impact**: A serious violation of community standards, including
+sustained inappropriate behavior.
+
+**Consequence**: A temporary ban from any sort of interaction or public
+communication with the community for a specified period of time. No public or
+private interaction with the people involved, including unsolicited interaction
+with those enforcing the Code of Conduct, is allowed during this period.
+Violating these terms may lead to a permanent ban.
+
+### 4. Permanent Ban
+
+**Community Impact**: Demonstrating a pattern of violation of community
+standards, including sustained inappropriate behavior,  harassment of an
+individual, or aggression toward or disparagement of classes of individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within
+the community.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 2.0, available at
+https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
+
+Community Impact Guidelines were inspired by [Mozilla's code of conduct
+enforcement ladder](https://github.com/mozilla/diversity).
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see the FAQ at
+https://www.contributor-covenant.org/faq. Translations are available at
+https://www.contributor-covenant.org/translations.
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 00000000..740cab6d
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,26 @@
+ARG BASE_IMAGE=golang:1.22-bookworm
+
+FROM $BASE_IMAGE AS build
+ARG USE_GORELEASER_ARTIFACTS=0
+WORKDIR /usr/local/src/gop
+COPY . .
+ENV GOPROOT=/usr/local/gop
+RUN set -eux; \
+	mkdir -p $GOPROOT/bin; \
+	git ls-tree --full-tree --name-only -r HEAD | grep -vE "^\." | xargs -I {} cp --parents {} $GOPROOT/; \
+	if [ $USE_GORELEASER_ARTIFACTS -eq 1 ]; then \
+		GOARCH=$(go env GOARCH); \
+		BIN_DIR_SUFFIX=linux_$GOARCH; \
+		[ $GOARCH = "amd64" ] && BIN_DIR_SUFFIX=${BIN_DIR_SUFFIX}_v1; \
+		[ $GOARCH = "arm" ] && BIN_DIR_SUFFIX=${BIN_DIR_SUFFIX}_$(go env GOARM | cut -d , -f 1); \
+		cp .dist/gop_$BIN_DIR_SUFFIX/bin/gop .dist/gopfmt_$BIN_DIR_SUFFIX/bin/gopfmt $GOPROOT/bin/; \
+	else \
+		./all.bash; \
+		cp bin/gop bin/gopfmt $GOPROOT/bin/; \
+	fi
+
+FROM $BASE_IMAGE
+ENV GOPROOT=/usr/local/gop
+COPY --from=build $GOPROOT/ $GOPROOT/
+ENV PATH=$GOPROOT/bin:$PATH
+WORKDIR /gop
diff --git a/README.md b/README.md
index 44087027..5833aceb 100644
--- a/README.md
+++ b/README.md
@@ -87,8 +87,8 @@ Sound a bit abstract? Let's see some Go+ classfiles.
 * Unit Test: [classfile: Unit Test](https://github.com/goplus/gop/blob/main/doc/classfile.md#classfile-unit-test)
 * DevOps: [gsh: Go+ DevOps Tools](https://github.com/qiniu/x/tree/main/gsh)
 * Web Programming: [yap: Yet Another HTTP Web Framework](https://github.com/goplus/yap)
-* Web Programming: [yaptest: HTTP Test Framework](https://github.com/goplus/yap#yaptest-http-test-framework)
-* Web Programming: [ydb: Database Framework](https://github.com/goplus/yap#ydb-database-framework)
+* Web Programming: [yaptest: HTTP Test Framework](https://github.com/goplus/yap/tree/main/ytest)
+* Web Programming: [ydb: Database Framework](https://github.com/goplus/yap/tree/main/ydb)
 * STEM Education: [spx: A Go+ 2D Game Engine](https://github.com/goplus/spx)
 
 
diff --git a/VERSION b/VERSION
deleted file mode 100644
index 212c3bf8..00000000
--- a/VERSION
+++ /dev/null
@@ -1,1 +0,0 @@
-v1.2.5
diff --git a/ast/ast.go b/ast/ast.go
index 17ef0e3e..9f3753a5 100644
--- a/ast/ast.go
+++ b/ast/ast.go
@@ -726,6 +726,7 @@ type (
 		Tok        token.Token // ILLEGAL if Key == nil, ASSIGN, DEFINE
 		X          Expr        // value to range over
 		Body       *BlockStmt
+		NoRangeOp  bool
 	}
 )
 
@@ -1054,6 +1055,7 @@ type (
 		Operator bool          // is operator or not
 		Shadow   bool          // is a shadow entry
 		IsClass  bool          // recv set by class
+		Static   bool          // recv is static (class method)
 	}
 )
 
diff --git a/ast/ast_gop.go b/ast/ast_gop.go
index 251716a4..da6227a5 100644
--- a/ast/ast_gop.go
+++ b/ast/ast_gop.go
@@ -118,7 +118,7 @@ func (*EnvExpr) exprNode() {}
 // A SliceLit node represents a slice literal.
 type SliceLit struct {
 	Lbrack     token.Pos // position of "["
-	Elts       []Expr    // list of composite elements; or nil
+	Elts       []Expr    // list of slice elements; or nil
 	Rbrack     token.Pos // position of "]"
 	Incomplete bool      // true if (source) expressions are missing in the Elts list
 }
@@ -137,6 +137,48 @@ func (*SliceLit) exprNode() {}
 
 // -----------------------------------------------------------------------------
 
+// A MatrixLit node represents a matrix literal.
+type MatrixLit struct {
+	Lbrack     token.Pos // position of "["
+	Elts       [][]Expr  // list of matrix elements
+	Rbrack     token.Pos // position of "]"
+	Incomplete bool      // true if (source) expressions are missing in the Elts list
+}
+
+// Pos - position of first character belonging to the node.
+func (p *MatrixLit) Pos() token.Pos {
+	return p.Lbrack
+}
+
+// End - position of first character immediately after the node.
+func (p *MatrixLit) End() token.Pos {
+	return p.Rbrack + 1
+}
+
+func (*MatrixLit) exprNode() {}
+
+// -----------------------------------------------------------------------------
+
+// A ElemEllipsis node represents a matrix row elements.
+type ElemEllipsis struct {
+	Elt      Expr      // ellipsis element
+	Ellipsis token.Pos // position of "..."
+}
+
+// Pos - position of first character belonging to the node.
+func (p *ElemEllipsis) Pos() token.Pos {
+	return p.Elt.Pos()
+}
+
+// End - position of first character immediately after the node.
+func (p *ElemEllipsis) End() token.Pos {
+	return p.Ellipsis + 3
+}
+
+func (*ElemEllipsis) exprNode() {}
+
+// -----------------------------------------------------------------------------
+
 // ErrWrapExpr represents `expr!`, `expr?` or `expr?: defaultValue`.
 type ErrWrapExpr struct {
 	X       Expr
diff --git a/build_install_run.go b/build_install_run.go
index 5102ded2..b0c6d138 100644
--- a/build_install_run.go
+++ b/build_install_run.go
@@ -24,11 +24,18 @@ import (
 	"github.com/qiniu/x/errors"
 )
 
+func genFlags(flags []GenFlags) GenFlags {
+	if flags != nil {
+		return flags[0]
+	}
+	return 0
+}
+
 // -----------------------------------------------------------------------------
 
 // InstallDir installs a Go+ package directory.
-func InstallDir(dir string, conf *Config, install *gocmd.InstallConfig) (err error) {
-	_, _, err = GenGo(dir, conf, false)
+func InstallDir(dir string, conf *Config, install *gocmd.InstallConfig, flags ...GenFlags) (err error) {
+	_, _, err = GenGoEx(dir, conf, false, genFlags(flags))
 	if err != nil {
 		return errors.NewWith(err, `GenGo(dir, conf, false)`, -2, "gop.GenGo", dir, conf, false)
 	}
@@ -36,8 +43,8 @@ func InstallDir(dir string, conf *Config, install *gocmd.InstallConfig) (err err
 }
 
 // InstallPkgPath installs a Go+ package.
-func InstallPkgPath(workDir, pkgPath string, conf *Config, install *gocmd.InstallConfig) (err error) {
-	localDir, recursively, err := GenGoPkgPath(workDir, pkgPath, conf, true)
+func InstallPkgPath(workDir, pkgPath string, conf *Config, install *gocmd.InstallConfig, flags ...GenFlags) (err error) {
+	localDir, recursively, err := GenGoPkgPathEx(workDir, pkgPath, conf, true, genFlags(flags))
 	if err != nil {
 		return errors.NewWith(err, `GenGoPkgPath(workDir, pkgPath, conf, true)`, -2, "gop.GenGoPkgPath", workDir, pkgPath, conf, true)
 	}
@@ -77,8 +84,8 @@ func chdir(dir string) string {
 // -----------------------------------------------------------------------------
 
 // BuildDir builds a Go+ package directory.
-func BuildDir(dir string, conf *Config, build *gocmd.BuildConfig) (err error) {
-	_, _, err = GenGo(dir, conf, false)
+func BuildDir(dir string, conf *Config, build *gocmd.BuildConfig, flags ...GenFlags) (err error) {
+	_, _, err = GenGoEx(dir, conf, false, genFlags(flags))
 	if err != nil {
 		return errors.NewWith(err, `GenGo(dir, conf, false)`, -2, "gop.GenGo", dir, conf, false)
 	}
@@ -86,8 +93,8 @@ func BuildDir(dir string, conf *Config, build *gocmd.BuildConfig) (err error) {
 }
 
 // BuildPkgPath builds a Go+ package.
-func BuildPkgPath(workDir, pkgPath string, conf *Config, build *gocmd.BuildConfig) (err error) {
-	localDir, recursively, err := GenGoPkgPath(workDir, pkgPath, conf, false)
+func BuildPkgPath(workDir, pkgPath string, conf *Config, build *gocmd.BuildConfig, flags ...GenFlags) (err error) {
+	localDir, recursively, err := GenGoPkgPathEx(workDir, pkgPath, conf, false, genFlags(flags))
 	if err != nil {
 		return errors.NewWith(err, `GenGoPkgPath(workDir, pkgPath, conf, false)`, -2, "gop.GenGoPkgPath", workDir, pkgPath, conf, false)
 	}
@@ -131,8 +138,8 @@ func getBuildDir(conf *Config) string {
 }
 
 // RunDir runs an application from a Go+ package directory.
-func RunDir(dir string, args []string, conf *Config, run *gocmd.RunConfig) (err error) {
-	_, _, err = GenGo(dir, conf, false)
+func RunDir(dir string, args []string, conf *Config, run *gocmd.RunConfig, flags ...GenFlags) (err error) {
+	_, _, err = GenGoEx(dir, conf, false, genFlags(flags))
 	if err != nil {
 		return errors.NewWith(err, `GenGo(dir, conf, false)`, -2, "gop.GenGo", dir, conf, false)
 	}
@@ -140,8 +147,8 @@ func RunDir(dir string, args []string, conf *Config, run *gocmd.RunConfig) (err
 }
 
 // RunPkgPath runs an application from a Go+ package.
-func RunPkgPath(pkgPath string, args []string, chDir bool, conf *Config, run *gocmd.RunConfig) (err error) {
-	localDir, recursively, err := GenGoPkgPath("", pkgPath, conf, true)
+func RunPkgPath(pkgPath string, args []string, chDir bool, conf *Config, run *gocmd.RunConfig, flags ...GenFlags) (err error) {
+	localDir, recursively, err := GenGoPkgPathEx("", pkgPath, conf, true, genFlags(flags))
 	if err != nil {
 		return errors.NewWith(err, `GenGoPkgPath("", pkgPath, conf, true)`, -2, "gop.GenGoPkgPath", "", pkgPath, conf, true)
 	}
@@ -168,8 +175,8 @@ func RunFiles(autogen string, files []string, args []string, conf *Config, run *
 // -----------------------------------------------------------------------------
 
 // TestDir tests a Go+ package directory.
-func TestDir(dir string, conf *Config, test *gocmd.TestConfig) (err error) {
-	_, _, err = GenGo(dir, conf, true)
+func TestDir(dir string, conf *Config, test *gocmd.TestConfig, flags ...GenFlags) (err error) {
+	_, _, err = GenGoEx(dir, conf, true, genFlags(flags))
 	if err != nil {
 		return errors.NewWith(err, `GenGo(dir, conf, true)`, -2, "gop.GenGo", dir, conf, true)
 	}
@@ -177,8 +184,8 @@ func TestDir(dir string, conf *Config, test *gocmd.TestConfig) (err error) {
 }
 
 // TestPkgPath tests a Go+ package.
-func TestPkgPath(workDir, pkgPath string, conf *Config, test *gocmd.TestConfig) (err error) {
-	localDir, recursively, err := GenGoPkgPath(workDir, pkgPath, conf, false)
+func TestPkgPath(workDir, pkgPath string, conf *Config, test *gocmd.TestConfig, flags ...GenFlags) (err error) {
+	localDir, recursively, err := GenGoPkgPathEx(workDir, pkgPath, conf, false, genFlags(flags))
 	if err != nil {
 		return errors.NewWith(err, `GenGoPkgPath(workDir, pkgPath, conf, false)`, -2, "gop.GenGoPkgPath", workDir, pkgPath, conf, false)
 	}
diff --git a/cl/classfile.go b/cl/classfile.go
index 2eecbcd5..06572f0c 100644
--- a/cl/classfile.go
+++ b/cl/classfile.go
@@ -54,6 +54,15 @@ type gmxProject struct {
 	hasScheds  bool
 	gameIsPtr  bool
 	isTest     bool
+	hasMain_   bool
+}
+
+func (p *gmxProject) hasMain() bool {
+	if !p.hasMain_ {
+		imps := p.pkgImps
+		p.hasMain_ = len(imps) > 0 && imps[0].TryRef("GopTestClass") != nil
+	}
+	return p.hasMain_
 }
 
 func (p *gmxProject) getScheds(cb *gogen.CodeBuilder) []goast.Stmt {
@@ -87,6 +96,33 @@ func ClassNameAndExt(file string) (name, clsfile, ext string) {
 	return
 }
 
+// GetFileClassType get ast.File classType
+func GetFileClassType(file *ast.File, filename string, lookupClass func(ext string) (c *Project, ok bool)) (classType string, isTest bool, ok bool) {
+	if file.IsClass {
+		var ext string
+		classType, _, ext = ClassNameAndExt(filename)
+		ok = true
+		if file.IsNormalGox {
+			isTest = strings.HasSuffix(ext, "_test.gox")
+			if !isTest && classType == "main" {
+				classType = "_main"
+			}
+		} else {
+			isTest = strings.HasSuffix(ext, "test.gox")
+		}
+		if file.IsProj {
+			if gt, ok := lookupClass(ext); ok {
+				classType = gt.Class
+			}
+		} else if isTest {
+			classType = casePrefix + testNameSuffix(classType)
+		}
+	} else if strings.HasSuffix(filename, "_test.gop") {
+		isTest = true
+	}
+	return
+}
+
 func isGoxTestFile(ext string) bool {
 	return strings.HasSuffix(ext, "test.gox")
 }
@@ -143,6 +179,7 @@ func loadClass(ctx *pkgCtx, pkg *gogen.Package, file string, f *ast.File, conf *
 			log.Panicln("multiple project files found:", tname, p.gameClass)
 		}
 		p.gameClass = tname
+		p.hasMain_ = f.HasShadowEntry()
 	} else {
 		p.sptypes = append(p.sptypes, tname)
 	}
@@ -173,7 +210,7 @@ func spxTryRef(spx gogen.PkgRef, typ string) (obj types.Object, isPtr bool) {
 func spxRef(spx gogen.PkgRef, typ string) (obj gogen.Ref, isPtr bool) {
 	obj, isPtr = spxTryRef(spx, typ)
 	if obj == nil {
-		panic(spx.Path() + "." + typ + " not found")
+		panic(spx.Types.Name() + "." + typ + " not found")
 	}
 	return
 }
@@ -246,20 +283,34 @@ func genTestFunc(pkg *gogen.Package, name, testType, param, paramType string) {
 		End()
 }
 
-func gmxCheckProjs(pkg *gogen.Package, ctx *pkgCtx) (proj *gmxProject, multi bool) {
+func gmxCheckProjs(pkg *gogen.Package, ctx *pkgCtx) (*gmxProject, bool) {
+	var projMain, projNoMain *gmxProject
+	var multiMain, multiNoMain bool
 	for _, v := range ctx.projs {
 		if v.isTest {
 			continue
-		} else if proj != nil {
-			multi = true
+		}
+		if v.hasMain() {
+			if projMain != nil {
+				multiMain = true
+			} else {
+				projMain = v
+			}
 		} else {
-			proj = v
+			if projNoMain != nil {
+				multiNoMain = true
+			} else {
+				projNoMain = v
+			}
 		}
 		if v.game != nil { // just to make testcase happy
 			gmxProjMain(pkg, ctx, v)
 		}
 	}
-	return
+	if projMain != nil {
+		return projMain, multiMain
+	}
+	return projNoMain, multiNoMain
 }
 
 func gmxProjMain(pkg *gogen.Package, parent *pkgCtx, proj *gmxProject) {
@@ -403,6 +454,7 @@ func astFnClassfname(c *gmxClass) *ast.FuncDecl {
 				},
 			},
 		},
+		Shadow: true,
 	}
 }
 
diff --git a/cl/compile.go b/cl/compile.go
index c5948cfd..8e11e163 100644
--- a/cl/compile.go
+++ b/cl/compile.go
@@ -512,6 +512,9 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gogen.Packag
 	if conf.Recorder != nil {
 		rec = newRecorder(conf.Recorder)
 		confGox.Recorder = rec
+		defer func() {
+			rec.Complete(p.Types.Scope())
+		}()
 	}
 	if enableRecover {
 		defer func() {
@@ -888,19 +891,19 @@ func preloadFile(p *gogen.Package, ctx *blockCtx, f *ast.File, goFile string, ge
 
 	preloadFuncDecl := func(d *ast.FuncDecl) {
 		if ctx.classRecv != nil { // in class file (.spx/.gmx)
-			if d.Recv == nil {
+			if recv := d.Recv; recv == nil || len(recv.List) == 0 {
 				d.Recv = ctx.classRecv
 				d.IsClass = true
 			}
 		}
+		name := d.Name
+		fname := name.Name
 		if d.Recv == nil {
-			name := d.Name
 			fn := func() {
 				old, _ := p.SetCurFile(goFile, true)
 				defer p.RestoreCurFile(old)
-				loadFunc(ctx, nil, d, genFnBody)
+				loadFunc(ctx, nil, fname, d, genFnBody)
 			}
-			fname := name.Name
 			if fname == "init" {
 				if genFnBody {
 					if debugLoad {
@@ -918,18 +921,30 @@ func preloadFile(p *gogen.Package, ctx *blockCtx, f *ast.File, goFile string, ge
 					}
 				}
 			}
-		} else {
-			if name, ok := getRecvTypeName(parent, d.Recv, true); ok {
+		} else if tname, ok := getRecvTypeName(parent, d.Recv, true); ok {
+			if d.Static {
+				if debugLoad {
+					log.Printf("==> Preload static method %s.%s\n", tname, fname)
+				}
+				fname = staticMethod(tname, fname)
+				fn := func() {
+					old, _ := p.SetCurFile(goFile, true)
+					defer p.RestoreCurFile(old)
+					loadFunc(ctx, nil, fname, d, genFnBody)
+				}
+				initLoader(parent, syms, name.Pos(), fname, fn, genFnBody)
+				ctx.lbinames = append(ctx.lbinames, fname)
+			} else {
 				if debugLoad {
-					log.Printf("==> Preload method %s.%s\n", name, d.Name.Name)
+					log.Printf("==> Preload method %s.%s\n", tname, fname)
 				}
-				ld := getTypeLoader(parent, syms, token.NoPos, name)
+				ld := getTypeLoader(parent, syms, token.NoPos, tname)
 				fn := func() {
 					old, _ := p.SetCurFile(goFile, true)
 					defer p.RestoreCurFile(old)
 					doInitType(ld)
 					recv := toRecv(ctx, d.Recv)
-					loadFunc(ctx, recv, d, genFnBody)
+					loadFunc(ctx, recv, fname, d, genFnBody)
 				}
 				ld.methods = append(ld.methods, fn)
 			}
@@ -1068,6 +1083,9 @@ func preloadFile(p *gogen.Package, ctx *blockCtx, f *ast.File, goFile string, ge
 					log.Panicln("TODO - cl.preloadFile OverloadFuncDecl: invalid recv")
 				}
 				recv = otyp
+				if ctx.rec != nil {
+					ctx.rec.Refer(recv, recv.Name)
+				}
 			}
 			onames := make([]string, 0, 4)
 			exov := false
@@ -1079,19 +1097,27 @@ func preloadFile(p *gogen.Package, ctx *blockCtx, f *ast.File, goFile string, ge
 					onames = append(onames, expr.Name)
 					ctx.lbinames = append(ctx.lbinames, expr.Name)
 					exov = true
+					if ctx.rec != nil {
+						ctx.rec.Refer(expr, expr.Name)
+					}
 				case *ast.SelectorExpr:
 					checkOverloadMethod(d)
-					checkOverloadMethodRecvType(recv, expr.X)
+					rtyp := checkOverloadMethodRecvType(recv, expr.X)
 					onames = append(onames, "."+expr.Sel.Name)
 					ctx.lbinames = append(ctx.lbinames, recv)
 					exov = true
+					if ctx.rec != nil {
+						ctx.rec.Refer(rtyp, rtyp.Name)
+						ctx.rec.Refer(expr.Sel, rtyp.Name+"."+expr.Sel.Name)
+					}
 				case *ast.FuncLit:
 					checkOverloadFunc(d)
 					name1 := overloadFuncName(name.Name, idx)
+					onames = append(onames, "") // const Gopo_xxx = "xxxInt,,xxxFloat"
 					ctx.lbinames = append(ctx.lbinames, name1)
 					preloadFuncDecl(&ast.FuncDecl{
 						Doc:  d.Doc,
-						Name: &ast.Ident{NamePos: name.NamePos, Name: name1},
+						Name: &ast.Ident{NamePos: expr.Pos(), Name: name1},
 						Type: expr.Type,
 						Body: expr.Body,
 					})
@@ -1133,12 +1159,13 @@ func checkOverloadMethod(d *ast.OverloadFuncDecl) {
 	}
 }
 
-func checkOverloadMethodRecvType(ot *ast.Ident, recv ast.Expr) {
+func checkOverloadMethodRecvType(ot *ast.Ident, recv ast.Expr) *ast.Ident {
 	rtyp, _ := getRecvType(recv)
 	rt, ok := rtyp.(*ast.Ident)
 	if !ok || ot.Name != rt.Name {
 		log.Panicln("TODO - checkOverloadMethodRecvType:", recv)
 	}
+	return rt
 }
 
 const (
@@ -1168,6 +1195,14 @@ func overloadName(recv *ast.Ident, name string, isOp bool) string {
 	return "Gopo" + sep + typ + name
 }
 
+func staticMethod(tname, name string) string {
+	sep := "_"
+	if strings.ContainsRune(name, '_') || strings.ContainsRune(tname, '_') {
+		sep = "__"
+	}
+	return "Gops" + sep + tname + sep + name
+}
+
 func stringLit(val string) *ast.BasicLit {
 	return &ast.BasicLit{Kind: token.STRING, Value: strconv.Quote(val)}
 }
@@ -1185,8 +1220,7 @@ func aliasType(pkg *types.Package, pos token.Pos, name string, typ types.Type) {
 	pkg.Scope().Insert(o)
 }
 
-func loadFunc(ctx *blockCtx, recv *types.Var, d *ast.FuncDecl, genBody bool) {
-	name := d.Name.Name
+func loadFunc(ctx *blockCtx, recv *types.Var, name string, d *ast.FuncDecl, genBody bool) {
 	if debugLoad {
 		if recv == nil {
 			log.Println("==> Load func", name)
@@ -1234,7 +1268,7 @@ func loadFunc(ctx *blockCtx, recv *types.Var, d *ast.FuncDecl, genBody bool) {
 	commentFunc(ctx, fn, d)
 	if rec := ctx.recorder(); rec != nil {
 		rec.Def(d.Name, fn.Func)
-		if recv == nil && d.Name.Name != "_" {
+		if recv == nil && name != "_" {
 			ctx.fileScope.Insert(fn.Func)
 		}
 	}
diff --git a/cl/expr.go b/cl/expr.go
index b261dcf7..aaf7eb13 100644
--- a/cl/expr.go
+++ b/cl/expr.go
@@ -18,12 +18,12 @@ package cl
 
 import (
 	"bytes"
+	"errors"
 	goast "go/ast"
 	gotoken "go/token"
 	"go/types"
 	"log"
 	"math/big"
-	"reflect"
 	"strconv"
 	"strings"
 	"syscall"
@@ -188,6 +188,34 @@ find:
 	return
 }
 
+/*
+func compileMatrixLit(ctx *blockCtx, v *ast.MatrixLit) {
+	cb := ctx.cb
+	ncol := -1
+	for _, elts := range v.Elts {
+		switch n := len(elts); n {
+		case 1:
+			elt := elts[0]
+			if e, ok := elt.(*ast.Ellipsis); ok {
+				compileExpr(ctx, e.Elt)
+				panic("TODO") // TODO(xsw): matrixLit with ellipsis
+			}
+			fallthrough
+		default:
+			if ncol < 0 {
+				ncol = n
+			} else if ncol != n {
+				ctx.handleErrorf(elts[0].Pos(), "inconsistent matrix column count: got %v, want %v", n, ncol)
+			}
+			for _, elt := range elts {
+				compileExpr(ctx, elt)
+			}
+			cb.SliceLitEx(...)
+		}
+	}
+}
+*/
+
 func compileEnvExpr(ctx *blockCtx, v *ast.EnvExpr) {
 	cb := ctx.cb
 	if ctx.isClass { // in a Go+ class file
@@ -248,7 +276,7 @@ func compileExprLHS(ctx *blockCtx, expr ast.Expr) {
 	case *ast.StarExpr:
 		compileStarExprLHS(ctx, v)
 	default:
-		log.Panicln("compileExpr failed: unknown -", reflect.TypeOf(v))
+		panic(ctx.newCodeErrorf(v.Pos(), "compileExprLHS failed: unknown - %T", expr))
 	}
 	if rec := ctx.recorder(); rec != nil {
 		rec.recordExpr(ctx, expr, true)
@@ -363,8 +391,10 @@ func compileExpr(ctx *blockCtx, expr ast.Expr, inFlags ...int) {
 		ctx.cb.Typ(toFuncType(ctx, v, nil, nil), v)
 	case *ast.EnvExpr:
 		compileEnvExpr(ctx, v)
+	/* case *ast.MatrixLit:
+	compileMatrixLit(ctx, v) */
 	default:
-		log.Panicf("compileExpr failed: unknown - %T\n", v)
+		panic(ctx.newCodeErrorf(v.Pos(), "compileExpr failed: unknown - %T", v))
 	}
 	if rec := ctx.recorder(); rec != nil {
 		rec.recordExpr(ctx, expr, false)
@@ -566,12 +596,14 @@ func identVal(ctx *blockCtx, x *ast.Ident, flags int, v types.Object, alias bool
 }
 
 type fnType struct {
-	next     *fnType
-	params   *types.Tuple
-	base     int
-	size     int
-	variadic bool
-	typetype bool
+	next      *fnType
+	params    *types.Tuple
+	sig       *types.Signature
+	base      int
+	size      int
+	variadic  bool
+	typetype  bool
+	typeparam bool
 }
 
 func (p *fnType) arg(i int, ellipsis bool) types.Type {
@@ -590,7 +622,8 @@ func (p *fnType) arg(i int, ellipsis bool) types.Type {
 
 func (p *fnType) init(base int, t *types.Signature) {
 	p.base = base
-	p.params, p.variadic = t.Params(), t.Variadic()
+	p.sig = t
+	p.params, p.variadic, p.typeparam = t.Params(), t.Variadic(), t.TypeParams() != nil
 	p.size = p.params.Len()
 	if p.variadic {
 		p.size--
@@ -669,7 +702,6 @@ func compileCallExpr(ctx *blockCtx, v *ast.CallExpr, inFlags int) {
 	var err error
 	var stk = ctx.cb.InternalStack()
 	var base = stk.Len()
-	var fnt = stk.Get(-1).Type
 	var flags gogen.InstrFlags
 	var ellipsis = v.Ellipsis != gotoken.NoPos
 	if ellipsis {
@@ -678,10 +710,12 @@ func compileCallExpr(ctx *blockCtx, v *ast.CallExpr, inFlags int) {
 	if (inFlags & clCallWithTwoValue) != 0 {
 		flags |= gogen.InstrFlagTwoValue
 	}
+	pfn := stk.Get(-1)
+	fnt := pfn.Type
 	fn := &fnType{}
 	fn.load(fnt)
 	for fn != nil {
-		if err = compileCallArgs(fn, ctx, v, ellipsis, flags); err == nil {
+		if err = compileCallArgs(ctx, pfn, fn, v, ellipsis, flags); err == nil {
 			if rec := ctx.recorder(); rec != nil {
 				rec.recordCallExpr(ctx, v, fnt)
 			}
@@ -736,10 +770,16 @@ func fnCall(ctx *blockCtx, v *ast.CallExpr, flags gogen.InstrFlags, extra int) e
 	return ctx.cb.CallWithEx(len(v.Args)+extra, flags, v)
 }
 
-func compileCallArgs(fn *fnType, ctx *blockCtx, v *ast.CallExpr, ellipsis bool, flags gogen.InstrFlags) (err error) {
+func compileCallArgs(ctx *blockCtx, pfn *gogen.Element, fn *fnType, v *ast.CallExpr, ellipsis bool, flags gogen.InstrFlags) (err error) {
+	var needInferFunc bool
 	for i, arg := range v.Args {
 		switch expr := arg.(type) {
 		case *ast.LambdaExpr:
+			if fn.typeparam {
+				needInferFunc = true
+				compileIdent(ctx, ast.NewIdent("nil"), 0)
+				continue
+			}
 			sig, e := checkLambdaFuncType(ctx, expr, fn.arg(i, ellipsis), clLambaArgument, v.Fun)
 			if e != nil {
 				return e
@@ -748,6 +788,11 @@ func compileCallArgs(fn *fnType, ctx *blockCtx, v *ast.CallExpr, ellipsis bool,
 				return
 			}
 		case *ast.LambdaExpr2:
+			if fn.typeparam {
+				needInferFunc = true
+				compileIdent(ctx, ast.NewIdent("nil"), 0)
+				continue
+			}
 			sig, e := checkLambdaFuncType(ctx, expr, fn.arg(i, ellipsis), clLambaArgument, v.Fun)
 			if e != nil {
 				return e
@@ -784,9 +829,24 @@ func compileCallArgs(fn *fnType, ctx *blockCtx, v *ast.CallExpr, ellipsis bool,
 			compileExpr(ctx, arg)
 		}
 	}
+	if needInferFunc {
+		typ, err := gogen.InferFunc(ctx.pkg, pfn, fn.sig, nil, ctx.cb.InternalStack().GetArgs(len(v.Args)), flags)
+		if err != nil {
+			return err
+		}
+		next := &fnType{}
+		next.init(fn.base, typ.(*types.Signature))
+		next.next = fn.next
+		fn.next = next
+		return errCallNext
+	}
 	return ctx.cb.CallWithEx(len(v.Args), flags, v)
 }
 
+var (
+	errCallNext = errors.New("call next")
+)
+
 type clLambaFlag string
 
 const (
diff --git a/cl/recorder.go b/cl/recorder.go
index beccc1b9..4ac4c627 100644
--- a/cl/recorder.go
+++ b/cl/recorder.go
@@ -18,6 +18,7 @@ package cl
 
 import (
 	"go/types"
+	"strings"
 
 	"github.com/goplus/gogen"
 	"github.com/goplus/gop/ast"
@@ -28,12 +29,49 @@ import (
 
 type goxRecorder struct {
 	Recorder
-	types map[ast.Expr]types.TypeAndValue
+	types  map[ast.Expr]types.TypeAndValue
+	refers map[string][]*ast.Ident
 }
 
 func newRecorder(rec Recorder) *goxRecorder {
 	types := make(map[ast.Expr]types.TypeAndValue)
-	return &goxRecorder{rec, types}
+	refers := make(map[string][]*ast.Ident)
+	return &goxRecorder{rec, types, refers}
+}
+
+// Refer maps identifiers to name for ast.OverloadFuncDecl.
+func (p *goxRecorder) Refer(ident *ast.Ident, name string) {
+	p.refers[name] = append(p.refers[name], ident)
+}
+
+// Complete computes the types record.
+func (p *goxRecorder) Complete(scope *types.Scope) {
+	for name, idents := range p.refers {
+		pos := strings.Index(name, ".")
+		if pos == -1 {
+			if obj := scope.Lookup(name); obj != nil {
+				for _, id := range idents {
+					p.Use(id, obj)
+				}
+			}
+			continue
+		}
+		if obj := scope.Lookup(name[:pos]); obj != nil {
+			if named, ok := obj.Type().(*types.Named); ok {
+				n := named.NumMethods()
+				for i := 0; i < n; i++ {
+					if m := named.Method(i); m.Name() == name[pos+1:] {
+						for _, id := range idents {
+							p.Use(id, m)
+						}
+						break
+					}
+				}
+			}
+		}
+	}
+	p.types = nil
+	p.refers = nil
 }
 
 // Member maps identifiers to the objects they denote.
diff --git a/cmd/internal/build/build.go b/cmd/internal/build/build.go
index fdb2509b..5fe447fb 100644
--- a/cmd/internal/build/build.go
+++ b/cmd/internal/build/build.go
@@ -74,10 +74,12 @@ func runCmd(cmd *base.Command, args []string) {
 		log.Panicln("too many arguments:", args)
 	}
 
-	conf, err := gop.NewDefaultConf(".", true)
+	conf, err := gop.NewDefaultConf(".", gop.ConfFlagNoTestFiles)
 	if err != nil {
 		log.Panicln("gop.NewDefaultConf:", err)
 	}
+	defer conf.UpdateCache()
+
 	confCmd := &gocmd.BuildConfig{Gop: conf.Gop}
 	if *flagOutput != "" {
 		output, err := filepath.Abs(*flagOutput)
@@ -91,15 +93,16 @@ func runCmd(cmd *base.Command, args []string) {
 }
 
 func build(proj gopprojs.Proj, conf *gop.Config, build *gocmd.BuildConfig) {
+	const flags = gop.GenFlagPrompt
 	var obj string
 	var err error
 	switch v := proj.(type) {
 	case *gopprojs.DirProj:
 		obj = v.Dir
-		err = gop.BuildDir(obj, conf, build)
+		err = gop.BuildDir(obj, conf, build, flags)
 	case *gopprojs.PkgPathProj:
 		obj = v.Path
-		err = gop.BuildPkgPath("", v.Path, conf, build)
+		err = gop.BuildPkgPath("", v.Path, conf, build, flags)
 	case *gopprojs.FilesProj:
 		err = gop.BuildFiles(v.Files, conf, build)
 	default:
diff --git a/cmd/internal/gengo/go.go b/cmd/internal/gengo/go.go
index ac51e1c6..d66682b9 100644
--- a/cmd/internal/gengo/go.go
+++ b/cmd/internal/gengo/go.go
@@ -71,10 +71,11 @@ func runCmd(cmd *base.Command, args []string) {
 		cl.SetDisableRecover(true)
 	}
 
-	conf, err := gop.NewDefaultConf(".", false)
+	conf, err := gop.NewDefaultConf(".", 0)
 	if err != nil {
 		log.Panicln("gop.NewDefaultConf:", err)
 	}
+	defer conf.UpdateCache()
 
 	flags := gop.GenFlagPrintError | gop.GenFlagPrompt
 	if *flagCheckMode {
diff --git a/cmd/internal/install/install.go b/cmd/internal/install/install.go
index b469c1be..b343885a 100644
--- a/cmd/internal/install/install.go
+++ b/cmd/internal/install/install.go
@@ -71,10 +71,12 @@ func runCmd(cmd *base.Command, args []string) {
 		cl.SetDisableRecover(true)
 	}
 
-	conf, err := gop.NewDefaultConf(".", true)
+	conf, err := gop.NewDefaultConf(".", gop.ConfFlagNoTestFiles)
 	if err != nil {
 		log.Panicln("gop.NewDefaultConf:", err)
 	}
+	defer conf.UpdateCache()
+
 	confCmd := &gocmd.Config{Gop: conf.Gop}
 	confCmd.Flags = pass.Args
 	for _, proj := range projs {
@@ -83,15 +85,16 @@ func runCmd(cmd *base.Command, args []string) {
 }
 
 func install(proj gopprojs.Proj, conf *gop.Config, install *gocmd.InstallConfig) {
+	const flags = gop.GenFlagPrompt
 	var obj string
 	var err error
 	switch v := proj.(type) {
 	case *gopprojs.DirProj:
 		obj = v.Dir
-		err = gop.InstallDir(obj, conf, install)
+		err = gop.InstallDir(obj, conf, install, flags)
 	case *gopprojs.PkgPathProj:
 		obj = v.Path
-		err = gop.InstallPkgPath("", v.Path, conf, install)
+		err = gop.InstallPkgPath("", v.Path, conf, install, flags)
 	case *gopprojs.FilesProj:
 		err = gop.InstallFiles(v.Files, conf, install)
 	default:
diff --git a/cmd/internal/run/run.go b/cmd/internal/run/run.go
index b7d1c57f..9bcb3925 100644
--- a/cmd/internal/run/run.go
+++ b/cmd/internal/run/run.go
@@ -80,10 +80,12 @@ func runCmd(cmd *base.Command, args []string) {
 	}
 
 	noChdir := *flagNoChdir
-	conf, err := gop.NewDefaultConf(".", true)
+	conf, err := gop.NewDefaultConf(".", gop.ConfFlagNoTestFiles)
 	if err != nil {
 		log.Panicln("gop.NewDefaultConf:", err)
 	}
+	defer conf.UpdateCache()
+
 	if !conf.Mod.HasModfile() { // if no go.mod, check GopDeps
 		conf.GopDeps = new(int)
 	}
@@ -93,15 +95,16 @@ func runCmd(cmd *base.Command, args []string) {
 }
 
 func run(proj gopprojs.Proj, args []string, chDir bool, conf *gop.Config, run *gocmd.RunConfig) {
+	const flags = 0
 	var obj string
 	var err error
 	switch v := proj.(type) {
 	case *gopprojs.DirProj:
 		obj = v.Dir
-		err = gop.RunDir(obj, args, conf, run)
+		err = gop.RunDir(obj, args, conf, run, flags)
 	case *gopprojs.PkgPathProj:
 		obj = v.Path
-		err = gop.RunPkgPath(v.Path, args, chDir, conf, run)
+		err = gop.RunPkgPath(v.Path, args, chDir, conf, run, flags)
 	case *gopprojs.FilesProj:
 		err = gop.RunFiles("", v.Files, args, conf, run)
 	default:
diff --git a/doc/contributing.md b/doc/contributing.md
index 0ff69387..85d80501 100644
--- a/doc/contributing.md
+++ b/doc/contributing.md
@@ -1,29 +1,219 @@
-## Code Structure
+# Contributing Guidelines
 
-TODO
+Thank you for your interest in contributing to the Go+ project! Your efforts
+help us build and improve this powerful language.
 
-## Contributing
+Please be aware that participation in the Go+ project requires adherence to our
+established [code of conduct](https://github.com/goplus/gop/blob/main/CODE_OF_CONDUCT.md). Your involvement signifies your agreement to comply
+with the guidelines set forth within these terms. We appreciate your cooperation
+in fostering a respectful and inclusive environment for all contributors.
 
-The Go+ project welcomes all contributors. We appreciate your help!
+To ensure a smooth contribution process, this guide outlines the steps and best
+practices for contributing to Go+. It's assumed that you have a foundational
+knowledge of Go+ and are comfortable using Git and GitHub for version control
+and collaboration.
 
-Here are [list of Go+ Contributors](https://github.com/goplus/gop/wiki/Goplus-Contributors). We award an email account (XXX@goplus.org) for every contributor. And we suggest you commit code by using this email account:
+## Before contributing code
+
+The Go+ project welcomes code contributions. However, to ensure coordination,
+please discuss any significant changes beforehand. It's recommended to signal
+your intent to contribute via the issue tracker, either by filing a new issue or
+claiming an existing one.
+
+The issue tracker is your go-to, whether you've got a contribution in mind or
+are looking for inspiration. Issues are organized to streamline the workflow.
+
+Except for very trivial changes, all contributions should relate to an existing
+issue. Feel free to open one and discuss your plans. This process allows
+validation of design, prevents duplicate efforts, ensures alignment with
+language and tool goals, and confirms design validity before coding.
+
+## Making a code contribution
+
+Let's say you want to fix a typo in the `README.md` file of the
+`https://github.com/goplus/gop.git` repository, changing "Github" to "GitHub",
+and you wish to merge this contribution into the `main` branch of the upstream
+repository.
+
+### Step 1: Fork the upstream repository
+
+First, you need to fork the upstream repository to your own username (or an
+organization you own) by following [this guide](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo). Then you can start making your
+contributions.
+
+Let's say you've forked `https://github.com/goplus/gop.git` to
+`https://github.com/YOUR_USERNAME/gop.git`.
+
+### Step 2: Clone the forked repository
+
+Open your terminal, clone the repository you just forked, and change into the
+cloned directory:
+
+```
+git clone https://github.com/YOUR_USERNAME/gop.git
+cd gop
+```
+
+This directory will serve as the working directory in the following steps.
+
+### Step 3: Create a new branch
+
+We recommend that every contribution should have its own branch, and this branch
+should be deleted after the contribution is merged into the target branch of the
+upstream repository.
+
+First, you need to sync your fork's `main` branch to keep it up-to-date with the
+upstream's `main` branch by following [this guide](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork).
+
+Then, you can create and switch to a branch for your contribution:
+
+```
+git checkout -b typo main
+```
+
+### Step 4: Make the changes
+
+This step is where you'll spend most of your effort when contributing. It
+typically takes up the majority of the time needed to make a contribution.
+
+However, in our case, we just want to correct a typo in the `README.md` file,
+changing "Github" to "GitHub". So, go ahead and make that change.
+
+### Step 5: Commit and push
+
+After making your changes, you need to commit them:
 
-```bash
-git config --global user.email XXX@goplus.org
 ```
+git add README.md
+git commit -s -m 'README.md: correct typo "Github" to "GitHub"'
+```
+
+The commit message here is very straightforward, but it's recommended to read
+the [Commit Messages](#commit-messages) section for crafting better commit messages.
+
+Once you've committed your changes, you also need to push your commit to your
+fork on GitHub:
+
+```
+git push --set-upstream origin typo
+```
+
+### Step 6: Open a pull request
+
+Head to the upstream repository and open a pull request by following
+[this guide](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request). Your pull request should clearly describe the change you're
+proposing and why it's beneficial.
+
+Typically, the title and description of the pull request you open will come from
+the first commit message in the branch of your fork. You can modify them if
+necessary.
+
+### Step 7: Engage with feedback
+
+Be open to feedback from upstream repository maintainers. They might request
+further changes or provide insights that could enhance your contribution.
+
+After making any further changes, simply repeat [Step 5](#step-5-commit-and-push). However, when
+committing, it's recommended to use the `--amend` option. This ensures that all
+your changes are contained within a single commit, maintaining a linear commit
+history. For example:
+
+```
+git add README.md
+git commit --amend
+```
+
+This isn't a strict requirement, though. You can create as many commits as you
+like.
+
+### Step 8: Do post-merge cleanup
+
+As mentioned in [Step 3](#step-3-create-a-new-branch), we recommend dedicating a branch to each contribution.
+Once a contribution's pull request is [squash and merged](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/about-pull-request-merges#squash-and-merge-your-commits), the branch should be
+deleted:
+
+```
+git checkout main
+git branch -d typo
+git push origin -d typo
+```
+
+## Commit messages
+
+Commit messages in Go+ adhere to specific conventions outlined in this section.
+
+A well-crafted commit message looks like this:
+
+```
+docs: revise contribution guidelines for clarity and inclusivity
+
+As our project grows, we want to ensure that our community understands
+how to contribute effectively and feels welcomed to do so. This update
+to our contribution guidelines aims to clarify the process and encourage
+more developers to get involved.
+
+Key changes include:
+  - Simplified the language to make the guidelines more accessible to
+    non-native English speakers and newcomers to open source.
+  - Included a step-by-step guide on setting up the development
+    environment, submitting a pull request, and what to expect during
+    the review process.
+  - Added a new section on community standards and code of conduct to
+    foster a respectful and inclusive community atmosphere.
+  - Provided clear examples of desirable contributions, such as bug
+    fixes, feature proposals, and documentation improvements.
+
+Fixes #123
+
+Signed-off-by: John Doe <john@example.com>
+```
+
+### First line
+
+The first line of a commit message typically serves as a concise one-line
+summary, beginning with the primary component it impacts (e.g., a specific
+package). This summary should ideally be brief, with many Git interfaces
+favoring a limit of approximately 72 characters. While Go+ is not strict on the
+length limit, it's a good guideline to follow.
+
+Think of the summary as finishing the sentence "This commit modifies Go+ to
+_____." As such, it does not begin with a capital letter, form a complete
+sentence, or diverge from succinctly stating the commit's effect.
+
+After the summary, ensure to separate the main content (if any) with a blank
+line.
+
+### Main content
+
+The rest of the commit message should elaborate, offering context and a clear
+explanation of the commit. Employ complete sentences and proper punctuation.
+Refrain from using HTML, Markdown, or any form of markup language.
+
+Include relevant information, like benchmark data, if the commit impacts
+performance.
+
+Adhere to a line width of approximately 72 characters to accommodate Git viewing
+tools, except when longer lines are necessary (such as for ASCII art, tables, or
+extended URLs).
+
+### Referencing issues
+
+The special notation `Fixes #123` links the commit directly to issue 123 in the
+issue tracker. Upon merging this commit, the issue tracker will automatically
+mark the issue as resolved.
 
-If you did this, remember to add your `XXX@goplus.org` email to https://github.com/settings/emails.
+For commits that partially address an issue, use `Updates #123`. This notation
+will not close the issue upon the commit's integration.
 
-What does `a contributor to Go+` mean? You must meet one of the following conditions:
+To reference multiple issues within a single commit, list them by appending each
+issue number with a comma and a space, such as `Fixes #123, #456`. This indicates
+that the commit resolves both referenced issues.
 
-* At least one pull request of a full-featured implemention.
-* At least three pull requests of feature enhancements.
-* At least ten pull requests of any kind issues.
+For cross-repository references, use the full `user/repo#number` syntax, such as
+`Fixes goplus/gop#123`.
 
-Where can you start?
+### Signed-off-by
 
-* [![Issues](https://img.shields.io/badge/ISSUEs-Go+-blue.svg)](https://github.com/goplus/gop/issues)
-* [![Issues](https://img.shields.io/badge/ISSUEs-NumGo+-blue.svg)](https://github.com/numgoplus/ng/issues)
-* [![Issues](https://img.shields.io/badge/ISSUEs-PandasGo+-blue.svg)](https://github.com/goplus/pandas/issues)
-* [![Issues](https://img.shields.io/badge/ISSUEs-vscode%20Go+-blue.svg)](https://github.com/gopcode/vscode-goplus/issues)
-* [![TODOs](https://badgen.net/https/api.tickgit.com/badgen/github.com/goplus/gop)](https://www.tickgit.com/browse?repo=github.com/goplus/gop)
+For contributions to be accepted, we mandate that a `Signed-off-by` line, in
+accordance with the [Developer Certificate of Origin](https://developercertificate.org/), be included at the
+conclusion of each commit message.
diff --git a/doc/docs.md b/doc/docs.md
index 1a09f4d9..daf3d879 100644
--- a/doc/docs.md
+++ b/doc/docs.md
@@ -126,6 +126,8 @@ Here is my `Hello world` program:
 
 ## Hello World
 
+Different from the function call style of most languages, Go+ recommends command style code:
+
 ```go
 println "Hello world"
 ```
@@ -140,6 +142,12 @@ See `gop help` for all supported commands.
 [`println`](#println) is one of the few [built-in functions](#builtin-functions).
 It prints the value passed to it to standard output.
 
+To emphasize our preference for command style, we introduce `echo` as an alias for `println`:
+
+```coffee
+echo "Hello world"
+```
+
 See https://tutorial.goplus.org/hello-world for more details.
 
 <h5 align="right"><a href="#table-of-contents">⬆ back to toc</a></h5>
diff --git a/doc/z_gop.go b/doc/z_gop.go
new file mode 100644
index 00000000..1dfc990f
--- /dev/null
+++ b/doc/z_gop.go
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2024 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 doc
+
+import (
+	"go/doc"
+	"strings"
+)
+
+const (
+	goptPrefix = "Gopt_" // template method
+	gopoPrefix = "Gopo_" // overload function/method
+	gopxPrefix = "Gopx_" // type as parameters function/method
+	gopPackage = "GopPackage"
+)
+
+func isGopPackage(in *doc.Package) bool {
+	for _, v := range in.Consts {
+		for _, name := range v.Names {
+			if name == gopPackage {
+				return true
+			}
+		}
+	}
+	return false
+}
+
+func isGopoConst(name string) bool {
+	return strings.HasPrefix(name, gopoPrefix)
+}
+
+func hasGopoConst(in *doc.Value) bool {
+	for _, name := range in.Names {
+		if isGopoConst(name) {
+			return true
+		}
+	}
+	return false
+}
+
+func isOverload(name string) bool {
+	n := len(name)
+	return n > 3 && name[n-3:n-1] == "__"
+}
+
+// Func (no _ func name)
+// _Func (with _ func name)
+// TypeName_Method (no _ method name)
+// _TypeName__Method (with _ method name)
+func checkTypeMethod(name string) mthd {
+	if pos := strings.IndexByte(name, '_'); pos >= 0 {
+		if pos == 0 {
+			t := name[1:]
+			if pos = strings.Index(t, "__"); pos <= 0 {
+				return mthd{"", t} // _Func
+			}
+			return mthd{t[:pos], t[pos+2:]} // _TypeName__Method
+		}
+		return mthd{name[:pos], name[pos+1:]} // TypeName_Method
+	}
+	return mthd{"", name} // Func
+}
diff --git a/doc/z_transform.go b/doc/z_transform.go
new file mode 100644
index 00000000..507a7f92
--- /dev/null
+++ b/doc/z_transform.go
@@ -0,0 +1,259 @@
+/*
+ * Copyright (c) 2024 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 doc
+
+import (
+	"go/ast"
+	"go/doc"
+	"go/token"
+	"sort"
+	"strconv"
+	"strings"
+)
+
+type mthd struct {
+	typ  string
+	name string
+}
+
+type omthd struct {
+	mthd
+	idx int
+}
+
+type typExtra struct {
+	t       *doc.Type
+	funcs   []*doc.Func
+	methods []*doc.Func
+}
+
+type transformCtx struct {
+	overloadFuncs map[mthd][]omthd // realName => []overloadName
+	typs          map[string]*typExtra
+	orders        map[*doc.Func]int
+}
+
+func (p *transformCtx) finish(in *doc.Package) {
+	for _, ex := range p.typs {
+		if t := ex.t; t != nil {
+			t.Funcs = p.mergeFuncs(t.Funcs, ex.funcs)
+			t.Methods = p.mergeFuncs(t.Methods, ex.methods)
+		} else {
+			in.Funcs = p.mergeFuncs(in.Funcs, ex.funcs)
+		}
+	}
+}
+
+func (p *transformCtx) mergeFuncs(a, b []*doc.Func) []*doc.Func {
+	if len(b) == 0 {
+		return a
+	}
+	a = append(a, b...)
+	sort.Slice(a, func(i, j int) bool {
+		fa, fb := a[i], a[j]
+		aName, bName := fa.Name, fb.Name
+		if aName == bName {
+			return p.orders[fa] < p.orders[fb]
+		}
+		return aName < bName
+	})
+	return a
+}
+
+func newCtx(in *doc.Package) *transformCtx {
+	typs := make(map[string]*typExtra, len(in.Types)+1)
+	typs[""] = &typExtra{} // global functions
+	for _, t := range in.Types {
+		typs[t.Name] = &typExtra{t: t}
+	}
+	return &transformCtx{
+		overloadFuncs: make(map[mthd][]omthd),
+		typs:          typs,
+		orders:        make(map[*doc.Func]int),
+	}
+}
+
+func newIdent(name string, in *ast.Ident) *ast.Ident {
+	ret := *in
+	ret.Name = name
+	return &ret
+}
+
+func newFuncDecl(name string, in *ast.FuncDecl) *ast.FuncDecl {
+	ret := *in
+	ret.Name = newIdent(name, ret.Name)
+	return &ret
+}
+
+func newMethodDecl(name string, in *ast.FuncDecl) *ast.FuncDecl {
+	ret := *in
+	if ret.Recv == nil {
+		ft := *ret.Type
+		params := *ft.Params
+		ret.Recv = &ast.FieldList{List: params.List[:1]}
+		params.List = params.List[1:]
+		ft.Params = &params
+		ret.Type = &ft
+	}
+	ret.Name = newIdent(name, ret.Name)
+	return &ret
+}
+
+func docRecv(recv *ast.Field) (_ string, ok bool) {
+	switch v := recv.Type.(type) {
+	case *ast.Ident:
+		return v.Name, true
+	case *ast.StarExpr:
+		if t, ok := v.X.(*ast.Ident); ok {
+			return "*" + t.Name, true
+		}
+	}
+	return
+}
+
+func newMethod(name string, in *doc.Func) *doc.Func {
+	ret := *in
+	ret.Name = name
+	ret.Decl = newMethodDecl(name, in.Decl)
+	if recv, ok := docRecv(ret.Decl.Recv.List[0]); ok {
+		ret.Recv = recv
+	}
+	// TODO(xsw): alias doc - ret.Doc
+	return &ret
+}
+
+func newFunc(name string, in *doc.Func) *doc.Func {
+	ret := *in
+	ret.Name = name
+	ret.Decl = newFuncDecl(name, in.Decl)
+	// TODO(xsw): alias doc - ret.Doc
+	return &ret
+}
+
+func setOrder(ctx *transformCtx, in *doc.Func, order int) *doc.Func {
+	ctx.orders[in] = order
+	return in
+}
+
+func buildFunc(ctx *transformCtx, overload omthd, in *doc.Func) {
+	if ex, ok := ctx.typs[overload.typ]; ok {
+		if ex.t != nil { // method
+			ex.methods = append(ex.methods, setOrder(ctx, newMethod(overload.name, in), overload.idx))
+		} else {
+			ex.funcs = append(ex.funcs, setOrder(ctx, newFunc(overload.name, in), overload.idx))
+		}
+	}
+}
+
+func toIndex(c byte) int {
+	if c >= '0' && c <= '9' {
+		return int(c - '0')
+	}
+	if c >= 'a' && c <= 'z' {
+		return int(c - ('a' - 10))
+	}
+	panic("invalid character out of [0-9,a-z]")
+}
+
+func transformFunc(ctx *transformCtx, t *doc.Type, in *doc.Func, method bool) {
+	var m mthd
+	if method {
+		m.typ = t.Name
+	}
+	m.name = in.Name
+	if overloads, ok := ctx.overloadFuncs[m]; ok {
+		for _, overload := range overloads {
+			buildFunc(ctx, overload, in)
+		}
+	}
+	if isOverload(in.Name) {
+		order := toIndex(in.Name[len(in.Name)-1])
+		in.Name = in.Name[:len(in.Name)-3]
+		in.Decl.Name.Name = in.Name
+		ctx.orders[in] = order
+	}
+}
+
+func transformFuncs(ctx *transformCtx, t *doc.Type, in []*doc.Func, method bool) {
+	for _, f := range in {
+		transformFunc(ctx, t, f, method)
+	}
+}
+
+func transformTypes(ctx *transformCtx, in []*doc.Type) {
+	for _, t := range in {
+		transformFuncs(ctx, t, t.Funcs, false)
+		transformFuncs(ctx, t, t.Methods, true)
+	}
+}
+
+func transformGopo(ctx *transformCtx, name, val string) {
+	overload := checkTypeMethod(name[len(gopoPrefix):])
+	parts := strings.Split(val, ",")
+	for idx, part := range parts {
+		if part == "" {
+			continue
+		}
+		var real mthd
+		if part[0] == '.' {
+			real = mthd{overload.typ, part[1:]}
+		} else {
+			real = mthd{"", part}
+		}
+		ctx.overloadFuncs[real] = append(ctx.overloadFuncs[real], omthd{overload, idx})
+	}
+}
+
+func transformConstSpec(ctx *transformCtx, vspec *ast.ValueSpec) {
+	name := vspec.Names[0].Name
+	if isGopoConst(name) {
+		if lit, ok := vspec.Values[0].(*ast.BasicLit); ok {
+			if lit.Kind == token.STRING {
+				if val, e := strconv.Unquote(lit.Value); e == nil {
+					transformGopo(ctx, name, val)
+				}
+			}
+		}
+	}
+}
+
+func transformConst(ctx *transformCtx, in *doc.Value) {
+	if hasGopoConst(in) {
+		for _, spec := range in.Decl.Specs {
+			vspec := spec.(*ast.ValueSpec)
+			transformConstSpec(ctx, vspec)
+		}
+	}
+}
+
+func transformConsts(ctx *transformCtx, in []*doc.Value) {
+	for _, v := range in {
+		transformConst(ctx, v)
+	}
+}
+
+// Transform converts a Go doc package to a Go+ doc package.
+func Transform(in *doc.Package) *doc.Package {
+	if isGopPackage(in) {
+		ctx := newCtx(in)
+		transformConsts(ctx, in.Consts)
+		transformFuncs(ctx, nil, in.Funcs, false)
+		transformTypes(ctx, in.Types)
+		ctx.finish(in)
+	}
+	return in
+}
diff --git a/go.mod b/go.mod
index 4da48795..cfc58966 100644
--- a/go.mod
+++ b/go.mod
@@ -4,10 +4,10 @@ go 1.18
 
 require (
 	github.com/fsnotify/fsnotify v1.7.0
-	github.com/goplus/c2go v0.7.25
-	github.com/goplus/gogen v1.15.1
-	github.com/goplus/mod v0.13.9
-	github.com/qiniu/x v1.13.9
+	github.com/goplus/c2go v0.7.26
+	github.com/goplus/gogen v1.15.2
+	github.com/goplus/mod v0.13.10
+	github.com/qiniu/x v1.13.10
 	golang.org/x/tools v0.19.0
 )
 
@@ -15,7 +15,7 @@ require (
 	github.com/json-iterator/go v1.1.12 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
-	golang.org/x/mod v0.16.0 // indirect
+	golang.org/x/mod v0.17.0 // indirect
 	golang.org/x/sys v0.18.0 // indirect
 )
 
diff --git a/go.sum b/go.sum
index 2f87c7a3..d36cd4cc 100644
--- a/go.sum
+++ b/go.sum
@@ -4,12 +4,12 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
 github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
 github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/goplus/c2go v0.7.25 h1:QvQfOwGVGKFYOTLry8i4p9U55jnIOQWeLsnSyFg0hhc=
-github.com/goplus/c2go v0.7.25/go.mod h1:e9oe4jDVhGFMJLEGmPSrVkLuXbLZAEmAu0/uD6fSz5E=
-github.com/goplus/gogen v1.15.1 h1:iz/fFpOeldjwmnjLzEdNsZF2mCf+sOHJavbAvV3o7sY=
-github.com/goplus/gogen v1.15.1/go.mod h1:92qEzVgv7y8JEFICWG9GvYI5IzfEkxYdsA1DbmnTkqk=
-github.com/goplus/mod v0.13.9 h1:B9zZoHi2AzMltTSOFqZNVjqGlSMlhhNTWwEzVqhTQzg=
-github.com/goplus/mod v0.13.9/go.mod h1:MibsLSftGmxaQq78YzUzNviyFwB9RtpMaoscufvEKH4=
+github.com/goplus/c2go v0.7.26 h1:YmihhrbUC6+5RmdRqYzul4igfsyofwj4mM2iwkTTOuc=
+github.com/goplus/c2go v0.7.26/go.mod h1:ePAStubV/ls8mmdPGQo6VfADTVd46rKuBemE4zzBDnA=
+github.com/goplus/gogen v1.15.2 h1:Q6XaSx/Zi5tWnjfAziYsQI6Jv6MgODRpFtOYqNkiiqM=
+github.com/goplus/gogen v1.15.2/go.mod h1:92qEzVgv7y8JEFICWG9GvYI5IzfEkxYdsA1DbmnTkqk=
+github.com/goplus/mod v0.13.10 h1:5Om6KOvo31daN7N30kWU1vC5zhsJPM+uPbcEN/FnlzE=
+github.com/goplus/mod v0.13.10/go.mod h1:HDuPZgpWiaTp3PUolFgsiX+Q77cbUWB/mikVHfYND3c=
 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
 github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
@@ -18,8 +18,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/qiniu/x v1.13.9 h1:OUcQZSze1oTO5pzrhsepTUvEb9K9WtOiqZomWffFGWE=
-github.com/qiniu/x v1.13.9/go.mod h1:INZ2TSWSJVWO/RuELQROERcslBwVgFG7MkTfEdaQz9E=
+github.com/qiniu/x v1.13.10 h1:J4Z3XugYzAq85SlyAfqlKVrbf05glMbAOh+QncsDQpE=
+github.com/qiniu/x v1.13.10/go.mod h1:INZ2TSWSJVWO/RuELQROERcslBwVgFG7MkTfEdaQz9E=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
@@ -30,8 +30,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
-golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
+golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
diff --git a/imp.go b/imp.go
index 5e6f8edf..6a1d01f8 100644
--- a/imp.go
+++ b/imp.go
@@ -17,21 +17,31 @@
 package gop
 
 import (
+	"crypto/sha256"
+	"encoding/base64"
+	"fmt"
 	"go/token"
 	"go/types"
+	"io"
+	"log"
 	"os"
 	"os/exec"
+	"path"
 	"path/filepath"
+	"runtime"
 	"strings"
 
 	"github.com/goplus/gogen/packages"
+	"github.com/goplus/gogen/packages/cache"
 	"github.com/goplus/mod/env"
 	"github.com/goplus/mod/gopmod"
 	"github.com/goplus/mod/modfetch"
+	"github.com/goplus/mod/modfile"
 )
 
 // -----------------------------------------------------------------------------
 
+// Importer represents a Go+ importer.
 type Importer struct {
 	impFrom *packages.Importer
 	mod     *gopmod.Module
@@ -41,19 +51,68 @@ type Importer struct {
 	Flags GenFlags // can change this for loading Go+ modules
 }
 
+// NewImporter creates a Go+ Importer.
 func NewImporter(mod *gopmod.Module, gop *env.Gop, fset *token.FileSet) *Importer {
 	const (
 		defaultFlags = GenFlagPrompt | GenFlagPrintError
 	)
-	if mod == nil {
-		mod = gopmod.Default
-	}
-	dir := ""
-	if mod.HasModfile() {
-		dir = mod.Root()
+	if mod == nil || !mod.HasModfile() {
+		if modGop, e := gopmod.LoadFrom(filepath.Join(gop.Root, "go.mod"), ""); e == nil {
+			modGop.ImportClasses()
+			mod = modGop
+		} else {
+			mod = gopmod.Default
+		}
 	}
+	dir := mod.Root()
 	impFrom := packages.NewImporter(fset, dir)
-	return &Importer{mod: mod, gop: gop, impFrom: impFrom, fset: fset, Flags: defaultFlags}
+	ret := &Importer{mod: mod, gop: gop, impFrom: impFrom, fset: fset, Flags: defaultFlags}
+	impFrom.SetCache(cache.New(ret.PkgHash))
+	return ret
+}
+
+// CacheFile returns file path of the cache.
+func (p *Importer) CacheFile() string {
+	cacheDir, _ := os.UserCacheDir()
+	cacheDir += "/gop-build/"
+	os.MkdirAll(cacheDir, 0755)
+
+	fname := ""
+	h := sha256.New()
+	if root := p.mod.Root(); root != "" {
+		io.WriteString(h, root)
+		fname = filepath.Base(root)
+	}
+	hash := base64.RawURLEncoding.EncodeToString(h.Sum(nil))
+	return cacheDir + hash + fname
+}
+
+// Cache returns the cache object.
+func (p *Importer) Cache() *cache.Impl {
+	return p.impFrom.Cache().(*cache.Impl)
+}
+
+// PkgHash calculates hash value for a package.
+// It is required by cache.New func.
+func (p *Importer) PkgHash(pkgPath string, self bool) string {
+	if pkg, e := p.mod.Lookup(pkgPath); e == nil {
+		switch pkg.Type {
+		case gopmod.PkgtStandard:
+			return cache.HashSkip
+		case gopmod.PkgtExtern:
+			if pkg.Real.Version != "" {
+				return pkg.Real.String()
+			}
+			fallthrough
+		case gopmod.PkgtModule:
+			return dirHash(p.mod, p.gop, pkg.Dir, self)
+		}
+	}
+	if isPkgInMod(pkgPath, gopMod) {
+		return cache.HashSkip
+	}
+	log.Println("PkgHash: unexpected package -", pkgPath)
+	return cache.HashInvalid
 }
 
 const (
@@ -146,4 +205,37 @@ go 1.16
 `)
 }
 
+func dirHash(mod *gopmod.Module, gop *env.Gop, dir string, self bool) string {
+	h := sha256.New()
+	if self {
+		fmt.Fprintf(h, "go\t%s\n", runtime.Version())
+		fmt.Fprintf(h, "gop\t%s\n", gop.Version)
+	}
+	if fis, err := os.ReadDir(dir); err == nil {
+		for _, fi := range fis {
+			if fi.IsDir() {
+				continue
+			}
+			fname := fi.Name()
+			if strings.HasPrefix(fname, "_") || !canCl(mod, fname) {
+				continue
+			}
+			if v, e := fi.Info(); e == nil {
+				fmt.Fprintf(h, "file\t%s\t%x\t%x\n", fname, v.Size(), v.ModTime().UnixNano())
+			}
+		}
+	}
+	return base64.RawStdEncoding.EncodeToString(h.Sum(nil))
+}
+
+func canCl(mod *gopmod.Module, fname string) bool {
+	switch path.Ext(fname) {
+	case ".go", ".gop", ".gox":
+		return true
+	default:
+		ext := modfile.ClassExt(fname)
+		return mod.IsClass(ext)
+	}
+}
+
 // -----------------------------------------------------------------------------
diff --git a/load.go b/load.go
index bc958bef..127d3397 100644
--- a/load.go
+++ b/load.go
@@ -18,7 +18,6 @@ package gop
 
 import (
 	"fmt"
-	"go/types"
 	"io/fs"
 	"os"
 	"path"
@@ -115,11 +114,12 @@ func isNotatedErr(err error, pkg *ast.Package, fset *token.FileSet) (notatedErr
 
 // -----------------------------------------------------------------------------
 
+// Config represents a configuration for loading Go+ packages.
 type Config struct {
 	Gop      *env.Gop
 	Fset     *token.FileSet
 	Mod      *gopmod.Module
-	Importer types.Importer
+	Importer *Importer
 
 	Filter func(fs.FileInfo) bool
 
@@ -127,12 +127,25 @@ type Config struct {
 	// see https://pkg.go.dev/github.com/goplus/gogen#File.CheckGopDeps
 	GopDeps *int
 
+	// CacheFile specifies the file path of the cache.
+	CacheFile string
+
 	IgnoreNotatedError bool
 	DontUpdateGoMod    bool
 }
 
+// ConfFlags represents configuration flags.
+type ConfFlags int
+
+const (
+	ConfFlagIgnoreNotatedError ConfFlags = 1 << iota
+	ConfFlagDontUpdateGoMod
+	ConfFlagNoTestFiles
+	ConfFlagNoCacheFile
+)
+
 // NewDefaultConf creates a dfault configuration for common cases.
-func NewDefaultConf(dir string, noTestFile bool) (conf *Config, err error) {
+func NewDefaultConf(dir string, flags ConfFlags) (conf *Config, err error) {
 	mod, err := LoadMod(dir)
 	if err != nil {
 		return
@@ -140,13 +153,32 @@ func NewDefaultConf(dir string, noTestFile bool) (conf *Config, err error) {
 	gop := gopenv.Get()
 	fset := token.NewFileSet()
 	imp := NewImporter(mod, gop, fset)
-	conf = &Config{Gop: gop, Fset: fset, Mod: mod, Importer: imp}
-	if noTestFile {
+	conf = &Config{
+		Gop: gop, Fset: fset, Mod: mod, Importer: imp,
+		IgnoreNotatedError: flags&ConfFlagIgnoreNotatedError != 0,
+		DontUpdateGoMod:    flags&ConfFlagDontUpdateGoMod != 0,
+	}
+	if flags&ConfFlagNoCacheFile == 0 {
+		conf.CacheFile = imp.CacheFile()
+		imp.Cache().Load(conf.CacheFile)
+	}
+	if flags&ConfFlagNoTestFiles != 0 {
 		conf.Filter = FilterNoTestFiles
 	}
 	return
 }
 
+// UpdateCache updates the cache.
+func (conf *Config) UpdateCache(verbose ...bool) {
+	if conf.CacheFile != "" {
+		c := conf.Importer.Cache()
+		c.Save(conf.CacheFile)
+		if verbose != nil && verbose[0] {
+			fmt.Println("Times of calling go list:", c.ListTimes())
+		}
+	}
+}
+
 // LoadMod loads a Go+ module from a specified directory.
 func LoadMod(dir string) (mod *gopmod.Module, err error) {
 	mod, err = gopmod.Load(dir)
@@ -371,3 +403,10 @@ var (
 )
 
 // -----------------------------------------------------------------------------
+
+// GetFileClassType get gop module file classType.
+func GetFileClassType(mod *gopmod.Module, file *ast.File, filename string) (classType string, isTest bool, ok bool) {
+	return cl.GetFileClassType(file, filename, mod.LookupClass)
+}
+
+// -----------------------------------------------------------------------------
diff --git a/parser/_nofmt/matrix1/matrix.gop b/parser/_nofmt/matrix1/matrix.gop
new file mode 100644
index 00000000..3b393bd1
--- /dev/null
+++ b/parser/_nofmt/matrix1/matrix.gop
@@ -0,0 +1,3 @@
+echo [
+	1, 2, 3
+	4, 5, 6 ]
diff --git a/parser/_nofmt/matrix1/parser.expect b/parser/_nofmt/matrix1/parser.expect
new file mode 100644
index 00000000..ac04e453
--- /dev/null
+++ b/parser/_nofmt/matrix1/parser.expect
@@ -0,0 +1,43 @@
+package main
+
+file matrix.gop
+noEntrypoint
+ast.FuncDecl:
+  Name:
+    ast.Ident:
+      Name: main
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+  Body:
+    ast.BlockStmt:
+      List:
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: echo
+              Args:
+                ast.MatrixLit:
+                  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
+                  NElt: 2
diff --git a/parser/parser.go b/parser/parser.go
index 75b92bdf..040b3b51 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -824,7 +824,7 @@ func (p *parser) parseArrayTypeOrSliceLit(state int, slice ast.Expr) (expr ast.E
 		case stateArrayTypeOrSliceLit:
 			switch p.tok {
 			case token.COMMA: // [a, b, c, d ...]
-				sliceLit := p.parseSliceLit(lbrack, len)
+				sliceLit := p.parseSliceOrMatrixLit(lbrack, len)
 				p.exprLev--
 				return sliceLit, resultSliceLit
 			case token.FOR: // [expr for k, v <- container if cond]
@@ -887,20 +887,40 @@ func (p *parser) parseArrayTypeOrSliceLit(state int, slice ast.Expr) (expr ast.E
 	return &ast.ArrayType{Lbrack: lbrack, Len: len, Elt: elt}, resultArrayType
 }
 
-func (p *parser) parseSliceLit(lbrack token.Pos, len ast.Expr) ast.Expr {
+// [first, ...]
+// [first, a2, a2, ...; b1, b2, b3, ...]
+func (p *parser) parseSliceOrMatrixLit(lbrack token.Pos, first ast.Expr) ast.Expr {
+	var mat [][]ast.Expr
 	elts := make([]ast.Expr, 1, 8)
-	elts[0] = len
-	for p.tok == token.COMMA {
+	elts[0] = first
+	for {
+		switch p.tok {
+		case token.COMMA:
+		case token.SEMICOLON:
+			mat = append(mat, elts)
+			elts = make([]ast.Expr, 0, len(elts))
+		case token.ELLIPSIS:
+			n := len(elts)
+			elts[n-1] = &ast.ElemEllipsis{Ellipsis: p.pos, Elt: elts[n-1]}
+			p.next()
+			continue
+		default:
+			goto done
+		}
 		p.next()
 		if p.tok != token.RBRACK {
 			elt := p.parseRHS()
 			elts = append(elts, elt)
 		}
 	}
+done:
 	rbrack := p.expect(token.RBRACK)
 
-	if debugParseOutput {
-		log.Printf("ast.SliceLit{Elts: %v}\n", elts)
+	if mat != nil {
+		if len(elts) > 0 {
+			mat = append(mat, elts)
+		}
+		return &ast.MatrixLit{Lbrack: lbrack, Elts: mat, Rbrack: rbrack}
 	}
 	return &ast.SliceLit{Lbrack: lbrack, Elts: elts, Rbrack: rbrack}
 }
@@ -2180,6 +2200,7 @@ func (p *parser) checkExpr(x ast.Expr) ast.Expr {
 		p.error(v.opening, msgTupleNotSupported)
 		x = &ast.BadExpr{From: v.opening, To: v.closing}
 	case *ast.EnvExpr:
+	case *ast.ElemEllipsis:
 	default:
 		// all other nodes are not proper expressions
 		p.errorExpected(x.Pos(), "expression", 3)
@@ -2461,12 +2482,17 @@ func (p *parser) parseBinaryExpr(lhs bool, prec1 int, allowTuple, allowCmd bool)
 	}
 }
 
-func (p *parser) parseRangeExpr(allowTuple, allowCmd bool) (x ast.Expr, isTuple bool) {
+func (p *parser) parseRangeExpr(first ast.Expr, allowTuple, allowCmd bool) (x ast.Expr, isTuple bool) {
+	if p.trace {
+		defer un(trace(p, "RangeExpr"))
+	}
 	if p.tok != token.COLON {
 		x, isTuple = p.parseBinaryExpr(false, token.LowestPrec+1, allowTuple, allowCmd)
 		if isTuple || p.tok != token.COLON { // not RangeExpr
 			return
 		}
+	} else {
+		x = first
 	}
 	to := p.pos
 	p.next()
@@ -2496,7 +2522,7 @@ func (p *parser) parseLambdaExpr(allowTuple, allowCmd, allowRangeExpr bool) (x a
 	var first = p.pos
 	if p.tok != token.DRARROW {
 		if allowRangeExpr {
-			x, isTuple = p.parseRangeExpr(true, allowCmd)
+			x, isTuple = p.parseRangeExpr(nil, true, allowCmd)
 		} else {
 			x, isTuple = p.parseBinaryExpr(false, token.LowestPrec+1, true, allowCmd)
 		}
@@ -2633,10 +2659,18 @@ const (
 // assignment with a right-hand side that is a single unary expression of
 // the form "range x". No guarantees are given for the left-hand side.
 func (p *parser) parseSimpleStmt(mode int, allowCmd bool) (ast.Stmt, bool) {
+	return p.parseSimpleStmtEx(mode, allowCmd, false)
+}
+
+func (p *parser) parseSimpleStmtEx(mode int, allowCmd, allowRangeExpr bool) (ast.Stmt, bool) {
 	if p.trace {
 		defer un(trace(p, "SimpleStmt"))
 	}
 
+	if allowRangeExpr && p.tok == token.COLON { // rangeExpr
+		re, _ := p.parseRangeExpr(nil, false, false)
+		return &ast.ExprStmt{X: re}, true
+	}
 	x := p.parseLHSList(allowCmd)
 
 	switch p.tok {
@@ -2676,6 +2710,10 @@ func (p *parser) parseSimpleStmt(mode int, allowCmd bool) (ast.Stmt, bool) {
 
 	switch p.tok {
 	case token.COLON:
+		if allowRangeExpr {
+			re, _ := p.parseRangeExpr(x[0], false, false)
+			return &ast.ExprStmt{X: re}, true
+		}
 		// labeled statement
 		colon := p.pos
 		p.next()
@@ -3265,7 +3303,7 @@ func (p *parser) parseForStmt() ast.Stmt {
 				s2 = &ast.AssignStmt{Rhs: y}
 				isRange = true
 			} else {
-				s2, isRange = p.parseSimpleStmt(rangeOk, false)
+				s2, isRange = p.parseSimpleStmtEx(rangeOk, false, true)
 			}
 		}
 		if !isRange && p.tok == token.SEMICOLON {
@@ -3287,10 +3325,18 @@ func (p *parser) parseForStmt() ast.Stmt {
 	p.expectSemi()
 
 	if isRange {
-		if stmt, ok := s2.(*ast.ForPhraseStmt); ok {
+		switch stmt := s2.(type) {
+		case *ast.ForPhraseStmt:
 			stmt.For = pos
 			stmt.Body = body
 			return stmt
+		case *ast.ExprStmt:
+			return &ast.RangeStmt{
+				For:       pos,
+				X:         stmt.X,
+				Body:      body,
+				NoRangeOp: true,
+			}
 		}
 		as := s2.(*ast.AssignStmt)
 		// check lhs
@@ -3449,6 +3495,10 @@ func (p *parser) parseImportSpec(doc *ast.CommentGroup, _ token.Token, _ int) as
 	return spec
 }
 
+func (p *parser) inClassFile() bool {
+	return p.mode&ParseGoPlusClass != 0
+}
+
 func (p *parser) parseValueSpec(doc *ast.CommentGroup, keyword token.Token, iota int) ast.Spec {
 	if p.trace {
 		defer un(trace(p, keyword.String()+"Spec"))
@@ -3459,7 +3509,7 @@ func (p *parser) parseValueSpec(doc *ast.CommentGroup, keyword token.Token, iota
 	var typ ast.Expr
 	var tag *ast.BasicLit
 	var values []ast.Expr
-	if p.mode&ParseGoPlusClass != 0 && p.topScope == p.pkgScope && keyword == token.VAR && p.varDeclCnt == 1 {
+	if p.inClassFile() && p.topScope == p.pkgScope && keyword == token.VAR && p.varDeclCnt == 1 {
 		var starPos token.Pos
 		if p.tok == token.MUL {
 			starPos = p.pos
@@ -3602,7 +3652,7 @@ func (p *parser) parseGenDecl(keyword token.Token, f parseSpecFunction) *ast.Gen
 	}
 }
 
-func isOverloadOps(tok token.Token) bool {
+func isOverloadOp(tok token.Token) bool {
 	return int(tok) < len(overloadOps) && overloadOps[tok] != 0
 }
 
@@ -3659,6 +3709,9 @@ func (p *parser) parseOverloadDecl(decl *ast.OverloadFuncDecl) *ast.OverloadFunc
 // `func identOrOp(params) results {...}`
 // `func identOrOp = (overloadFuncs)`
 //
+// `func T.ident(params) results { ... }`
+// `func .ident(params) results { ... }` (only in classfile)
+//
 // `func (recv) identOrOp(params) results { ... }`
 // `func (T).identOrOp = (overloadFuncs)`
 // `func (params) results { ... }()`
@@ -3673,23 +3726,10 @@ func (p *parser) parseFuncDeclOrCall() (ast.Decl, *ast.CallExpr) {
 
 	var recv, params, results *ast.FieldList
 	var ident *ast.Ident
-	var isOp, isFunLit, ok bool
+	var isOp, isStatic, isFunLit, ok bool
 
-	if p.tok != token.LPAREN {
-		// func: `func identOrOp(...) results`
-		// overload: `func identOrOp = (overloadFuncs)`
-		ident, isOp = p.parseIdentOrOp()
-		if p.tok == token.ASSIGN {
-			// func identOrOp = (overloadFuncs)
-			return p.parseOverloadDecl(&ast.OverloadFuncDecl{
-				Doc:      doc,
-				Func:     pos,
-				Name:     ident,
-				Operator: isOp,
-			}), nil
-		}
-		params, results = p.parseSignature(scope)
-	} else {
+	switch p.tok {
+	case token.LPAREN:
 		// method: `func (recv) identOrOp(params) results { ... }`
 		// overload: `func (T).identOrOp = (overloadFuncs)`
 		// funlit: `func (params) results { ... }()`
@@ -3708,8 +3748,8 @@ func (p *parser) parseFuncDeclOrCall() (ast.Decl, *ast.CallExpr) {
 				Name:     ident,
 				Operator: isOp,
 			}), nil
-		} else if isOp = isOverloadOps(p.tok); isOp {
-			oldtok, oldpos := p.tok, p.pos
+		} else if isOp = isOverloadOp(p.tok); isOp {
+			oldtok, oldpos, oldlit := p.tok, p.pos, p.lit
 			p.next()
 			if p.tok == token.LPAREN {
 				// func (recv) op(params) results { ... }
@@ -3717,10 +3757,12 @@ func (p *parser) parseFuncDeclOrCall() (ast.Decl, *ast.CallExpr) {
 				params, results = p.parseSignature(scope)
 			} else {
 				// func (params) typ { ... }()
-				p.unget(oldpos, oldtok, "")
+				p.unget(oldpos, oldtok, oldlit)
 				typ := p.tryType()
 				if typ == nil {
-					panic("TODO: invalid result type")
+					p.errorExpected(oldpos, "type", 1)
+					p.next()
+					typ = &ast.BadExpr{From: oldpos, To: p.pos}
 				}
 				isFunLit, results = true, &ast.FieldList{List: []*ast.Field{{Type: typ}}}
 			}
@@ -3749,11 +3791,44 @@ func (p *parser) parseFuncDeclOrCall() (ast.Decl, *ast.CallExpr) {
 			p.expectSemi()
 			return nil, call
 		}
+	case token.PERIOD:
+		// func .ident(...) results (only in classfile)
+		if p.inClassFile() {
+			p.next()
+			recv = &ast.FieldList{}
+			isStatic = true
+		}
+		ident = p.parseIdent()
+		params, results = p.parseSignature(scope)
+	default:
+		// func: `func identOrOp(...) results`
+		// overload: `func identOrOp = (overloadFuncs)`
+		// static method: `func T.ident(...) results`
+		ident, isOp = p.parseIdentOrOp()
+		switch p.tok {
+		case token.ASSIGN:
+			// func identOrOp = (overloadFuncs)
+			return p.parseOverloadDecl(&ast.OverloadFuncDecl{
+				Doc:      doc,
+				Func:     pos,
+				Name:     ident,
+				Operator: isOp,
+			}), nil
+		case token.PERIOD:
+			// func T.ident(...) results
+			if !isOp {
+				p.next()
+				recv = &ast.FieldList{List: []*ast.Field{{Type: ident}}}
+				ident = p.parseIdent()
+				isStatic = true
+			}
+		}
+		params, results = p.parseSignature(scope)
 	}
 
 	if isOp {
 		if params == nil || len(params.List) != 1 {
-			log.Panicln("TODO: overload operator can only have one parameter")
+			p.error(ident.Pos(), "overload operator can only have one parameter")
 		}
 	}
 	var body *ast.BlockStmt
@@ -3783,6 +3858,7 @@ func (p *parser) parseFuncDeclOrCall() (ast.Decl, *ast.CallExpr) {
 		},
 		Body:     body,
 		Operator: isOp,
+		Static:   isStatic,
 	}
 	if recv == nil {
 		// Go spec: The scope of an identifier denoting a constant, type,
diff --git a/printer/nodes.go b/printer/nodes.go
index c1628863..2cf61f61 100644
--- a/printer/nodes.go
+++ b/printer/nodes.go
@@ -968,6 +968,24 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int) {
 		p.print(indent, unindent, mode, x.Rbrace, token.RBRACE, mode)
 		p.level--
 
+	case *ast.MatrixLit:
+		p.level++
+		p.print(x.Lbrack, token.LBRACK, newline, indent)
+		var last = len(x.Elts) - 1
+		var incomplete bool
+		for i, elts := range x.Elts {
+			if i == last {
+				incomplete = x.Incomplete
+			}
+			p.exprList(elts[0].Pos(), elts, 1, 0, elts[len(elts)-1].End(), incomplete)
+			p.print(newline)
+		}
+		mode := noExtraLinebreak | noExtraBlank
+		// need the initial indent to print lone comments with
+		// the proper level of indentation
+		p.print(unindent, mode, x.Rbrack, token.RBRACK, mode)
+		p.level--
+
 	case *ast.Ellipsis:
 		p.print(token.ELLIPSIS)
 		if x.Elt != nil {
@@ -1034,7 +1052,7 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int) {
 			p.print(token.LBRACK)
 			p.expr0(x.Elt, depth+1)
 			p.print(blank)
-			p.listForPhrase(x.Lpos, x.Fors, depth, x.Rpos)
+			p.listForPhrase(x.Fors)
 			p.print(token.RBRACK)
 		default: // {...}
 			p.print(token.LBRACE)
@@ -1048,7 +1066,7 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int) {
 				}
 				p.print(blank)
 			}
-			p.listForPhrase(x.Lpos, x.Fors, depth, x.Rpos)
+			p.listForPhrase(x.Fors)
 			p.print(token.RBRACE)
 		}
 	case *ast.ErrWrapExpr:
@@ -1109,12 +1127,16 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int) {
 			p.print(x.Name)
 		}
 
+	case *ast.ElemEllipsis:
+		p.expr(x.Elt)
+		p.print(token.ELLIPSIS)
+
 	default:
 		log.Fatalf("unreachable %T\n", x)
 	}
 }
 
-func (p *printer) listForPhrase(prev0 token.Pos, list []*ast.ForPhrase, depth int, next0 token.Pos) {
+func (p *printer) listForPhrase(list []*ast.ForPhrase) {
 	for i, x := range list {
 		if i > 0 {
 			p.print(blank)
@@ -1516,7 +1538,9 @@ func (p *printer) stmt(stmt ast.Stmt, nextIsRBrace bool) {
 			}
 			p.print(blank, s.TokPos, s.Tok, blank)
 		}
-		p.print(token.RANGE, blank)
+		if !s.NoRangeOp {
+			p.print(token.RANGE, blank)
+		}
 		p.expr(stripParens(s.X))
 		p.print(blank)
 		p.block(s.Body, 1)
@@ -1904,6 +1928,7 @@ func (p *printer) funcBody(headerSize int, sep whiteSpace, b *ast.BlockStmt) {
 // by sep. Otherwise the block's opening "{" is printed on the current line, followed by
 // lines for the block's statements and its closing "}".
 func (p *printer) funcBodyUnnamed(headerSize int, sep whiteSpace, b *ast.BlockStmt) {
+	_, _ = headerSize, sep
 	if b == nil {
 		return
 	}
@@ -1990,9 +2015,18 @@ func (p *printer) funcDecl(d *ast.FuncDecl) {
 	// different line (all whitespace preceding the FUNC is emitted only when the
 	// FUNC is emitted).
 	startCol := p.out.Column - len("func ")
-	if d.Recv != nil && !d.IsClass {
-		p.parameters(d.Recv) // method: print receiver
-		p.print(blank)
+	if d.Recv != nil {
+		if d.Static { // static method
+			if !d.IsClass {
+				if list := d.Recv.List; len(list) > 0 {
+					p.expr(list[0].Type)
+				}
+			}
+			p.print(token.PERIOD)
+		} else if !d.IsClass {
+			p.parameters(d.Recv) // method: print receiver
+			p.print(blank)
+		}
 	}
 	p.expr(d.Name)
 	if d.Operator && d.Recv != nil {
@@ -2056,6 +2090,10 @@ func declToken(decl ast.Decl) (tok token.Token) {
 func (p *printer) declList(list []ast.Decl) {
 	tok := token.ILLEGAL
 	for _, d := range list {
+		// skip no entry shadow
+		if decl, ok := d.(*ast.FuncDecl); ok && decl.Shadow && decl != p.shadowEntry {
+			continue
+		}
 		prev := tok
 		tok = declToken(d)
 		// If the declaration token changed (e.g., from CONST to TYPE)


Test patch:
diff --git a/cl/builtin_test.go b/cl/builtin_test.go
index 5837b440..ce7133df 100644
--- a/cl/builtin_test.go
+++ b/cl/builtin_test.go
@@ -92,11 +92,14 @@ func TestCompileLambda2(t *testing.T) {
 
 func TestCompileExpr(t *testing.T) {
 	defer func() {
-		if e := recover(); e != "compileExpr failed: unknown - *ast.Ellipsis\n" {
-			t.Fatal("compileExpr:", e)
+		if e := recover(); e != nil {
+			if ce := e.(*gogen.CodeError); ce.Msg != "compileExpr failed: unknown - *ast.Ellipsis" {
+				t.Fatal("compileExpr:", ce.Msg)
+			}
 		}
 	}()
-	compileExpr(nil, &ast.Ellipsis{})
+	ctx := &blockCtx{pkgCtx: &pkgCtx{}}
+	compileExpr(ctx, &ast.Ellipsis{})
 }
 
 func TestCompileStmt(t *testing.T) {
@@ -253,6 +256,17 @@ func TestErrParseTypeEmbedName(t *testing.T) {
 }
 
 func TestGmxCheckProjs(t *testing.T) {
+	_, multi := gmxCheckProjs(nil, &pkgCtx{
+		projs: map[string]*gmxProject{
+			".a": {hasMain_: true}, ".b": {hasMain_: true},
+		},
+	})
+	if !multi {
+		t.Fatal("gmxCheckProjs: not multi?")
+	}
+}
+
+func TestGmxCheckProjs2(t *testing.T) {
 	_, multi := gmxCheckProjs(nil, &pkgCtx{
 		projs: map[string]*gmxProject{
 			".a": {}, ".b": {},
@@ -300,6 +314,80 @@ func TestClassNameAndExt(t *testing.T) {
 	}
 }
 
+func TestFileClassType(t *testing.T) {
+	type testData struct {
+		isClass     bool
+		isNormalGox bool
+		isProj      bool
+		fileName    string
+		classType   string
+		isTest      bool
+		found       bool
+	}
+	tests := []*testData{
+		{false, false, false, "abc.gop", "", false, false},
+		{false, false, false, "abc_test.gop", "", true, false},
+
+		{true, true, false, "abc.gox", "abc", false, true},
+		{true, true, false, "Abc.gox", "Abc", false, true},
+		{true, true, false, "abc_demo.gox", "abc", false, true},
+		{true, true, false, "Abc_demo.gox", "Abc", false, true},
+
+		{true, true, false, "main.gox", "_main", false, true},
+		{true, true, false, "main_demo.gox", "_main", false, true},
+		{true, true, false, "abc_xtest.gox", "abc", false, true},
+		{true, true, false, "main_xtest.gox", "_main", false, true},
+
+		{true, true, false, "abc_test.gox", "case_abc", true, true},
+		{true, true, false, "Abc_test.gox", "caseAbc", true, true},
+		{true, true, false, "main_test.gox", "case_main", true, true},
+
+		{true, false, false, "get.yap", "get", false, true},
+		{true, false, false, "get_p_#id.yap", "get_p_id", false, true},
+		{true, false, true, "main.yap", "AppV2", false, true},
+
+		{true, false, false, "abc_yap.gox", "abc", false, true},
+		{true, false, false, "Abc_yap.gox", "Abc", false, true},
+		{true, false, true, "main_yap.gox", "App", false, true},
+
+		{true, false, false, "abc_ytest.gox", "case_abc", true, true},
+		{true, false, false, "Abc_ytest.gox", "caseAbc", true, true},
+		{true, false, true, "main_ytest.gox", "App", true, true},
+	}
+	lookupClass := func(ext string) (c *Project, ok bool) {
+		switch ext {
+		case ".yap":
+			return &modfile.Project{
+				Ext: ".yap", Class: "AppV2",
+				Works:    []*modfile.Class{{Ext: ".yap", Class: "Handler"}},
+				PkgPaths: []string{"github.com/goplus/yap"}}, true
+		case "_yap.gox":
+			return &modfile.Project{
+				Ext: "_yap.gox", Class: "App",
+				PkgPaths: []string{"github.com/goplus/yap"}}, true
+		case "_ytest.gox":
+			return &modfile.Project{
+				Ext: "_ytest.gox", Class: "App",
+				Works:    []*modfile.Class{{Ext: "_ytest.gox", Class: "Case"}},
+				PkgPaths: []string{"github.com/goplus/yap/ytest", "testing"}}, true
+		}
+		return
+	}
+	for _, test := range tests {
+		f := &ast.File{IsClass: test.isClass, IsNormalGox: test.isNormalGox, IsProj: test.isProj}
+		classType, isTest, found := GetFileClassType(f, test.fileName, lookupClass)
+		if found != test.found {
+			t.Fatalf("%v found classType want %v, got %v.", test.fileName, test.found, found)
+		}
+		if isTest != test.isTest {
+			t.Fatalf("%v check classType isTest want %v, got %v.", test.fileName, test.isTest, isTest)
+		}
+		if classType != test.classType {
+			t.Fatalf("%v getClassType want %v, got %v.", test.fileName, test.classType, classType)
+		}
+	}
+}
+
 func TestErrMultiStarRecv(t *testing.T) {
 	defer func() {
 		if e := recover(); e == nil {
diff --git a/cl/compile_gop_test.go b/cl/compile_gop_test.go
index 7e7c2b63..73874dfe 100644
--- a/cl/compile_gop_test.go
+++ b/cl/compile_gop_test.go
@@ -17,6 +17,7 @@
 package cl_test
 
 import (
+	"strings"
 	"testing"
 )
 
@@ -376,6 +377,46 @@ func main() {
 `)
 }
 
+func TestOverloadFunc3(t *testing.T) {
+	gopClTest(t, `
+func addInt(a, b int) int {
+	return a + b
+}
+
+func addFloat(a, b float64) float64 {
+	return a * b
+}
+
+func add = (
+	func (a,b string) string { return a + b }
+	addInt
+	addFloat
+)
+
+println add(100, 7)
+println add(1.2, 3.14)
+`, `package main
+
+import "fmt"
+
+const Gopo_add = ",addInt,addFloat"
+
+func add__0(a string, b string) string {
+	return a + b
+}
+func addInt(a int, b int) int {
+	return a + b
+}
+func addFloat(a float64, b float64) float64 {
+	return a * b
+}
+func main() {
+	fmt.Println(addInt(100, 7))
+	fmt.Println(addFloat(1.2, 3.14))
+}
+`)
+}
+
 func TestOverload(t *testing.T) {
 	gopClTest(t, `
 import "github.com/goplus/gop/cl/internal/overload/foo"
@@ -1225,6 +1266,264 @@ func main() {
 `)
 }
 
+func testRangeExpr(t *testing.T, codeTpl, expect string) {
+	for k, s := range []string{" <- ", " := range ", " = range "} {
+		if k == 2 {
+			codeTpl = "i:=0\n" + codeTpl
+			expect = strings.Replace(expect, "for i := ", "i := 0\n\tfor i = ", -1)
+		}
+		gopClTest(t, strings.Replace(codeTpl, "$", s, -1), expect)
+	}
+}
+
+func TestRangeExpr(t *testing.T) {
+	testRangeExpr(t, `
+for i $ :10 {
+	println(i)
+}`, `package main
+
+import "fmt"
+
+func main() {
+	for i := 0; i < 10; i += 1 {
+		fmt.Println(i)
+	}
+}
+`)
+	testRangeExpr(t, `
+for i $ 1:10:3 {
+	println(i)
+}`, `package main
+
+import "fmt"
+
+func main() {
+	for i := 1; i < 10; i += 3 {
+		fmt.Println(i)
+	}
+}
+`)
+}
+
+func TestRangeExpr2(t *testing.T) {
+	testRangeExpr(t, `
+for i $ 1:10:2 {
+	println(i)
+}`, `package main
+
+import "fmt"
+
+func main() {
+	for i := 1; i < 10; i += 2 {
+		fmt.Println(i)
+	}
+}
+`)
+}
+
+func TestRangeExpr3(t *testing.T) {
+	testRangeExpr(t, `
+for i $ 1:10 {
+	println(i)
+}`, `package main
+
+import "fmt"
+
+func main() {
+	for i := 1; i < 10; i += 1 {
+		fmt.Println(i)
+	}
+}
+`)
+}
+
+func TestRangeExpr4(t *testing.T) {
+	testRangeExpr(t, `
+for i $ :10:2 {
+	println(i)
+}`, `package main
+
+import "fmt"
+
+func main() {
+	for i := 0; i < 10; i += 2 {
+		fmt.Println(i)
+	}
+}
+`)
+}
+
+func TestRangeExpr5(t *testing.T) {
+	gopClTest(t, `
+for range :10 {
+	println("Hi")
+}`, `package main
+
+import "fmt"
+
+func main() {
+	for _gop_k := 0; _gop_k < 10; _gop_k += 1 {
+		fmt.Println("Hi")
+	}
+}
+`)
+}
+
+func TestRangeExpr6(t *testing.T) {
+	gopClTest(t, `
+for _ <- :10 {
+	println("Hi")
+}`, `package main
+
+import "fmt"
+
+func main() {
+	for _gop_k := 0; _gop_k < 10; _gop_k += 1 {
+		fmt.Println("Hi")
+	}
+}
+`)
+}
+
+func TestRangeExpr7(t *testing.T) {
+	gopClTest(t, `
+println [x for x <- 0:3:1]
+`, `package main
+
+import (
+	"fmt"
+	"github.com/goplus/gop/builtin"
+)
+
+func main() {
+	fmt.Println(func() (_gop_ret []int) {
+		for _gop_it := builtin.NewRange__0(0, 3, 1).Gop_Enum(); ; {
+			var _gop_ok bool
+			x, _gop_ok := _gop_it.Next()
+			if !_gop_ok {
+				break
+			}
+			_gop_ret = append(_gop_ret, x)
+		}
+		return
+	}())
+}
+`)
+}
+
+func testRangeExpr8(t *testing.T, codeTpl, expect string) {
+	for _, s := range []string{" <- ", " := range "} {
+		gopClTest(t, strings.Replace(codeTpl, "$", s, -1), expect)
+	}
+}
+
+func TestRangeExpr8(t *testing.T) {
+	testRangeExpr8(t, `
+type T struct{}
+
+func (t T) start() int {
+	return 0
+}
+func (t T) end() int{
+	return 3
+}
+func (t T) step() int{
+	return 1
+}
+
+t:=T{}
+
+for i <- t.start():t.end():t.step(){
+	println i
+}
+`, `package main
+
+import "fmt"
+
+type T struct {
+}
+
+func (t T) start() int {
+	return 0
+}
+func (t T) end() int {
+	return 3
+}
+func (t T) step() int {
+	return 1
+}
+func main() {
+	t := T{}
+	for i, _gop_end, _gop_step := t.start(), t.end(), t.step(); i < _gop_end; i += _gop_step {
+		fmt.Println(i)
+	}
+}
+`)
+}
+
+func TestRangeExpr9(t *testing.T) {
+	testRangeExpr8(t, `
+type T struct{}
+
+func (t T) start() int {
+	return 0
+}
+func (t T) end() int{
+	return 3
+}
+func (t T) step() int{
+	return 1
+}
+
+t:=T{}
+i:=0
+for i =range t.start():t.end():t.step(){
+	println i
+}
+`, `package main
+
+import "fmt"
+
+type T struct {
+}
+
+func (t T) start() int {
+	return 0
+}
+func (t T) end() int {
+	return 3
+}
+func (t T) step() int {
+	return 1
+}
+func main() {
+	t := T{}
+	i := 0
+	for _gop_k, _gop_end, _gop_step := t.start(), t.end(), t.step(); _gop_k < _gop_end; _gop_k += _gop_step {
+		i = _gop_k
+		fmt.Println(i)
+	}
+}
+`)
+}
+
+func TestRangeExpr10(t *testing.T) {
+	gopClTest(t, `
+for :10 {
+	echo "Hi"
+}
+`, `package main
+
+import "fmt"
+
+func main() {
+	for _gop_k := 0; _gop_k < 10; _gop_k += 1 {
+		fmt.Println("Hi")
+	}
+}
+`)
+}
+
 func Test_RangeExpressionIf_Issue1243(t *testing.T) {
 	gopClTest(t, `
 for i <- :10, i%3 == 0 {
@@ -1242,3 +1541,32 @@ func main() {
 }
 `)
 }
+
+func TestStaticMethod(t *testing.T) {
+	gopClTest(t, `
+type foo int
+
+func foo.New(a int) *foo {
+	return new(foo)
+}
+
+func foo._add() *foo {
+	return new(foo)
+}
+
+a := foo.new(100)
+`, `package main
+
+type foo int
+
+func Gops_foo_New(a int) *foo {
+	return new(foo)
+}
+func Gops__foo___add() *foo {
+	return new(foo)
+}
+func main() {
+	a := Gops_foo_New(100)
+}
+`)
+}
diff --git a/cl/compile_spx_test.go b/cl/compile_spx_test.go
index a51deb70..d205537b 100644
--- a/cl/compile_spx_test.go
+++ b/cl/compile_spx_test.go
@@ -305,6 +305,16 @@ func main() {
 `)
 }
 
+func _TestMatrix1(t *testing.T) {
+	gopSpxTest(t, `
+echo [
+	1, 2, 3
+	4, 5, 6
+]
+`, ``, `
+`)
+}
+
 func TestEnvOp(t *testing.T) {
 	gopSpxTest(t, `
 echo ${PATH}, $id
diff --git a/cl/compile_test.go b/cl/compile_test.go
index 7c026ef6..d709cf44 100644
--- a/cl/compile_test.go
+++ b/cl/compile_test.go
@@ -19,7 +19,6 @@ package cl_test
 import (
 	"bytes"
 	"os"
-	"strings"
 	"sync"
 	"testing"
 
@@ -3634,247 +3633,6 @@ func newRepo() Repo {
 	}
 }
 
-func testRangeExpr(t *testing.T, codeTpl, expect string) {
-	for k, s := range []string{" <- ", " := range ", " = range "} {
-		if k == 2 {
-			codeTpl = "i:=0\n" + codeTpl
-			expect = strings.Replace(expect, "for i := ", "i := 0\n\tfor i = ", -1)
-		}
-		gopClTest(t, strings.Replace(codeTpl, "$", s, -1), expect)
-	}
-}
-
-func TestRangeExpr(t *testing.T) {
-	testRangeExpr(t, `
-for i $ :10 {
-	println(i)
-}`, `package main
-
-import "fmt"
-
-func main() {
-	for i := 0; i < 10; i += 1 {
-		fmt.Println(i)
-	}
-}
-`)
-	testRangeExpr(t, `
-for i $ 1:10:3 {
-	println(i)
-}`, `package main
-
-import "fmt"
-
-func main() {
-	for i := 1; i < 10; i += 3 {
-		fmt.Println(i)
-	}
-}
-`)
-}
-
-func TestRangeExpr2(t *testing.T) {
-	testRangeExpr(t, `
-for i $ 1:10:2 {
-	println(i)
-}`, `package main
-
-import "fmt"
-
-func main() {
-	for i := 1; i < 10; i += 2 {
-		fmt.Println(i)
-	}
-}
-`)
-}
-
-func TestRangeExpr3(t *testing.T) {
-	testRangeExpr(t, `
-for i $ 1:10 {
-	println(i)
-}`, `package main
-
-import "fmt"
-
-func main() {
-	for i := 1; i < 10; i += 1 {
-		fmt.Println(i)
-	}
-}
-`)
-}
-
-func TestRangeExpr4(t *testing.T) {
-	testRangeExpr(t, `
-for i $ :10:2 {
-	println(i)
-}`, `package main
-
-import "fmt"
-
-func main() {
-	for i := 0; i < 10; i += 2 {
-		fmt.Println(i)
-	}
-}
-`)
-}
-
-func TestRangeExpr5(t *testing.T) {
-	gopClTest(t, `
-for range :10 {
-	println("Hi")
-}`, `package main
-
-import "fmt"
-
-func main() {
-	for _gop_k := 0; _gop_k < 10; _gop_k += 1 {
-		fmt.Println("Hi")
-	}
-}
-`)
-}
-
-func TestRangeExpr6(t *testing.T) {
-	gopClTest(t, `
-for _ <- :10 {
-	println("Hi")
-}`, `package main
-
-import "fmt"
-
-func main() {
-	for _gop_k := 0; _gop_k < 10; _gop_k += 1 {
-		fmt.Println("Hi")
-	}
-}
-`)
-}
-
-func TestRangeExpr7(t *testing.T) {
-	gopClTest(t, `
-println [x for x <- 0:3:1]
-`, `package main
-
-import (
-	"fmt"
-	"github.com/goplus/gop/builtin"
-)
-
-func main() {
-	fmt.Println(func() (_gop_ret []int) {
-		for _gop_it := builtin.NewRange__0(0, 3, 1).Gop_Enum(); ; {
-			var _gop_ok bool
-			x, _gop_ok := _gop_it.Next()
-			if !_gop_ok {
-				break
-			}
-			_gop_ret = append(_gop_ret, x)
-		}
-		return
-	}())
-}
-`)
-}
-
-func testRangeExpr8(t *testing.T, codeTpl, expect string) {
-	for _, s := range []string{" <- ", " := range "} {
-		gopClTest(t, strings.Replace(codeTpl, "$", s, -1), expect)
-	}
-}
-
-func TestRangeExpr8(t *testing.T) {
-	testRangeExpr8(t, `
-	type T struct{}
-
-	func (t T)start()int{
-		return 0
-	}
-	func (t T)end()int{
-		return 3 
-	}
-	func (t T)step()int{
-		return 1 
-	}
-	
-	t:=T{}
-	
-	for i <- t.start():t.end():t.step(){
-		println i
-	}
-	`, `package main
-
-import "fmt"
-
-type T struct {
-}
-
-func (t T) start() int {
-	return 0
-}
-func (t T) end() int {
-	return 3
-}
-func (t T) step() int {
-	return 1
-}
-func main() {
-	t := T{}
-	for i, _gop_end, _gop_step := t.start(), t.end(), t.step(); i < _gop_end; i += _gop_step {
-		fmt.Println(i)
-	}
-}
-`)
-}
-
-func TestRangeExpr9(t *testing.T) {
-	testRangeExpr8(t, `
-	type T struct{}
-
-	func (t T)start()int{
-		return 0
-	}
-	func (t T)end()int{
-		return 3 
-	}
-	func (t T)step()int{
-		return 1 
-	}
-	
-	t:=T{}
-	i:=0
-	for i =range t.start():t.end():t.step(){
-		println i
-	}
-	`, `package main
-
-import "fmt"
-
-type T struct {
-}
-
-func (t T) start() int {
-	return 0
-}
-func (t T) end() int {
-	return 3
-}
-func (t T) step() int {
-	return 1
-}
-func main() {
-	t := T{}
-	i := 0
-	for _gop_k, _gop_end, _gop_step := t.start(), t.end(), t.step(); _gop_k < _gop_end; _gop_k += _gop_step {
-		i = _gop_k
-		fmt.Println(i)
-	}
-}
-`)
-}
-
 func TestGoFuncInstr(t *testing.T) {
 	gopClTest(t, `package main
 
diff --git a/cl/error_msg_test.go b/cl/error_msg_test.go
index 5c268cd9..ddc65610 100644
--- a/cl/error_msg_test.go
+++ b/cl/error_msg_test.go
@@ -1049,3 +1049,17 @@ type
 a := 1
 `)
 }
+
+func TestCompileExprError(t *testing.T) {
+	codeErrorTestAst(t, "main", "bar.go", `bar.go:5:1: compileExpr failed: unknown - *ast.BadExpr`, `
+func Foo(){}
+func _() {
+	Foo(
+}
+`)
+	codeErrorTestAst(t, "main", "bar.go", `bar.go:3:2: compileExprLHS failed: unknown - *ast.StructType`, `
+func _() {
+	struct() = nil
+}
+`)
+}
diff --git a/cl/typeparams_test.go b/cl/typeparams_test.go
index c4b53b2d..4d35387f 100644
--- a/cl/typeparams_test.go
+++ b/cl/typeparams_test.go
@@ -622,3 +622,74 @@ func main() {
 }
 `)
 }
+
+func TestInferFuncLambda(t *testing.T) {
+	gopMixedClTest(t, "main", `package main
+func ListMap[T any](ar []T, fn func(v T) T)[]T {
+	for i, v := range ar {
+		ar[i] = fn(v)
+	}
+	return ar
+}
+`, `
+println ListMap([1,2,3,4], x => x*x)
+ListMap [1,2,3,4], x => {
+	println x
+	return x
+}
+`, `package main
+
+import "fmt"
+
+func main() {
+	fmt.Println(ListMap([]int{1, 2, 3, 4}, func(x int) int {
+		return x * x
+	}))
+	ListMap([]int{1, 2, 3, 4}, func(x int) int {
+		fmt.Println(x)
+		return x
+	})
+}
+`)
+}
+
+func TestInferOverloadFuncLambda(t *testing.T) {
+	gopMixedClTest(t, "main", `package main
+func ListMap__0[T any](ar []T, fn func(v T) T)[]T {
+	for i, v := range ar {
+		ar[i] = fn(v)
+	}
+	return ar
+}
+func ListMap__1(a string, fn func(s string)) {
+	for _, c := range a {
+		fn(string(c))
+	}
+}
+`, `
+println ListMap([1,2,3,4], x => x*x)
+ListMap [1,2,3,4], x => {
+	println x
+	return x
+}
+ListMap "hello", x => {
+	println x
+}
+`, `package main
+
+import "fmt"
+
+func main() {
+	fmt.Println(ListMap__0([]int{1, 2, 3, 4}, func(x int) int {
+		return x * x
+	}))
+	ListMap__0([]int{1, 2, 3, 4}, func(x int) int {
+		fmt.Println(x)
+		return x
+	})
+	ListMap__1("hello", func(x string) {
+		fmt.Println(x)
+	})
+}
+`)
+}
diff --git a/cmd/internal/test/test.go b/cmd/internal/test/test.go
index d578ec0e..7a070f3c 100644
--- a/cmd/internal/test/test.go
+++ b/cmd/internal/test/test.go
@@ -69,10 +69,12 @@ func runCmd(cmd *base.Command, args []string) {
 		cl.SetDisableRecover(true)
 	}
 
-	conf, err := gop.NewDefaultConf(".", false)
+	conf, err := gop.NewDefaultConf(".", 0)
 	if err != nil {
 		log.Panicln("gop.NewDefaultConf:", err)
 	}
+	defer conf.UpdateCache()
+
 	confCmd := &gocmd.Config{Gop: conf.Gop}
 	confCmd.Flags = pass.Args
 	for _, proj := range projs {
@@ -81,15 +83,16 @@ func runCmd(cmd *base.Command, args []string) {
 }
 
 func test(proj gopprojs.Proj, conf *gop.Config, test *gocmd.TestConfig) {
+	const flags = gop.GenFlagPrompt
 	var obj string
 	var err error
 	switch v := proj.(type) {
 	case *gopprojs.DirProj:
 		obj = v.Dir
-		err = gop.TestDir(obj, conf, test)
+		err = gop.TestDir(obj, conf, test, flags)
 	case *gopprojs.PkgPathProj:
 		obj = v.Path
-		err = gop.TestPkgPath("", v.Path, conf, test)
+		err = gop.TestPkgPath("", v.Path, conf, test, flags)
 	case *gopprojs.FilesProj:
 		err = gop.TestFiles(v.Files, conf, test)
 	default:
diff --git a/doc/_testdata/gopoFn/in.go b/doc/_testdata/gopoFn/in.go
new file mode 100644
index 00000000..48f281c4
--- /dev/null
+++ b/doc/_testdata/gopoFn/in.go
@@ -0,0 +1,20 @@
+package foo
+
+const GopPackage = true
+
+const Gopo_Add = "AddInt,,AddString"
+
+// Add doc
+func Add__1(a, b float64) float64 {
+	return 0
+}
+
+// AddInt doc
+func AddInt(a, b int) int {
+	return 0
+}
+
+// AddString doc
+func AddString(a, b string) string {
+	return ""
+}
diff --git a/doc/_testdata/gopoFn/out.expect b/doc/_testdata/gopoFn/out.expect
new file mode 100644
index 00000000..6efcfd4d
--- /dev/null
+++ b/doc/_testdata/gopoFn/out.expect
@@ -0,0 +1,20 @@
+== Func Add ==
+Doc: AddInt doc
+
+func Add(a, b int) int
+== Func Add ==
+Doc: Add doc
+
+func Add(a, b float64) float64
+== Func Add ==
+Doc: AddString doc
+
+func Add(a, b string) string
+== Func AddInt ==
+Doc: AddInt doc
+
+func AddInt(a, b int) int
+== Func AddString ==
+Doc: AddString doc
+
+func AddString(a, b string) string
diff --git a/doc/_testdata/gopoMethod/in.go b/doc/_testdata/gopoMethod/in.go
new file mode 100644
index 00000000..f4646a05
--- /dev/null
+++ b/doc/_testdata/gopoMethod/in.go
@@ -0,0 +1,17 @@
+package foo
+
+const GopPackage = true
+
+const Gopo__T__Gop_Add = ".AddInt,AddString"
+
+type T int
+
+// AddInt doc
+func (p T) AddInt(b int) *T {
+	return nil
+}
+
+// AddString doc
+func AddString(this *T, b string) *T {
+	return nil
+}
diff --git a/doc/_testdata/gopoMethod/out.expect b/doc/_testdata/gopoMethod/out.expect
new file mode 100644
index 00000000..bb88970b
--- /dev/null
+++ b/doc/_testdata/gopoMethod/out.expect
@@ -0,0 +1,20 @@
+== Type T ==
+- Func AddString -
+Doc: AddString doc
+
+func AddString(this *T, b string) *T
+- Method AddInt -
+Recv: T
+Doc: AddInt doc
+
+func (p T) AddInt(b int) *T
+- Method Gop_Add -
+Recv: T
+Doc: AddInt doc
+
+func (p T) Gop_Add(b int) *T
+- Method Gop_Add -
+Recv: *T
+Doc: AddString doc
+
+func (this *T) Gop_Add(b string) *T
diff --git a/doc/_testdata/overloadFn/in.go b/doc/_testdata/overloadFn/in.go
new file mode 100644
index 00000000..b3c139e3
--- /dev/null
+++ b/doc/_testdata/overloadFn/in.go
@@ -0,0 +1,7 @@
+package foo
+
+const GopPackage = true
+
+// Bar doc
+func Bar__0() {
+}
diff --git a/doc/_testdata/overloadFn/out.expect b/doc/_testdata/overloadFn/out.expect
new file mode 100644
index 00000000..f3141a2c
--- /dev/null
+++ b/doc/_testdata/overloadFn/out.expect
@@ -0,0 +1,4 @@
+== Func Bar ==
+Doc: Bar doc
+
+func Bar()
diff --git a/doc/_testdata/overloadMethod/in.go b/doc/_testdata/overloadMethod/in.go
new file mode 100644
index 00000000..0b9a4246
--- /dev/null
+++ b/doc/_testdata/overloadMethod/in.go
@@ -0,0 +1,13 @@
+package foo
+
+const GopPackage = true
+
+type T int
+
+// Bar doc 1
+func (p *T) Bar__0() {
+}
+
+// Bar doc 2
+func (t T) Bar__1() {
+}
diff --git a/doc/_testdata/overloadMethod/out.expect b/doc/_testdata/overloadMethod/out.expect
new file mode 100644
index 00000000..e1e3fbdb
--- /dev/null
+++ b/doc/_testdata/overloadMethod/out.expect
@@ -0,0 +1,11 @@
+== Type T ==
+- Method Bar -
+Recv: *T
+Doc: Bar doc 1
+
+func (p *T) Bar()
+- Method Bar -
+Recv: T
+Doc: Bar doc 2
+
+func (t T) Bar()
diff --git a/doc/z_test.go b/doc/z_test.go
new file mode 100644
index 00000000..4e0d18b3
--- /dev/null
+++ b/doc/z_test.go
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2024 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 doc
+
+import (
+	"bytes"
+	"fmt"
+	"go/ast"
+	"go/doc"
+	"go/format"
+	"go/parser"
+	"go/token"
+	"os"
+	"path"
+	"strings"
+	"testing"
+)
+
+func TestToIndex(t *testing.T) {
+	if ret := toIndex('a'); ret != 10 {
+		t.Fatal("toIndex:", ret)
+	}
+	defer func() {
+		if e := recover(); e != "invalid character out of [0-9,a-z]" {
+			t.Fatal("panic:", e)
+		}
+	}()
+	toIndex('A')
+}
+
+func TestCheckTypeMethod(t *testing.T) {
+	if ret := checkTypeMethod("_Foo_a"); ret.typ != "" || ret.name != "Foo_a" {
+		t.Fatal("checkTypeMethod:", ret)
+	}
+	if ret := checkTypeMethod("Foo_a"); ret.typ != "Foo" || ret.name != "a" {
+		t.Fatal("checkTypeMethod:", ret)
+	}
+}
+
+func TestIsGopPackage(t *testing.T) {
+	if isGopPackage(&doc.Package{}) {
+		t.Fatal("isGopPackage: true?")
+	}
+}
+
+func TestDocRecv(t *testing.T) {
+	if _, ok := docRecv(&ast.Field{}); ok {
+		t.Fatal("docRecv: ok?")
+	}
+}
+
+func printVal(parts []string, format string, val any) []string {
+	return append(parts, fmt.Sprintf(format, val))
+}
+
+func printFuncDecl(parts []string, fset *token.FileSet, decl *ast.FuncDecl) []string {
+	var b bytes.Buffer
+	if e := format.Node(&b, fset, decl); e != nil {
+		panic(e)
+	}
+	return append(parts, b.String())
+}
+
+func printFunc(parts []string, fset *token.FileSet, format string, fn *doc.Func) []string {
+	parts = printVal(parts, format, fn.Name)
+	if fn.Recv != "" {
+		parts = printVal(parts, "Recv: %s", fn.Recv)
+	}
+	parts = printVal(parts, "Doc: %s", fn.Doc)
+	parts = printFuncDecl(parts, fset, fn.Decl)
+	return parts
+}
+
+func printFuncs(parts []string, fset *token.FileSet, fns []*doc.Func) []string {
+	for _, fn := range fns {
+		parts = printFunc(parts, fset, "== Func %s ==", fn)
+	}
+	return parts
+}
+
+func printType(parts []string, fset *token.FileSet, typ *doc.Type) []string {
+	parts = append(parts, fmt.Sprintf("== Type %s ==", typ.Name))
+	for _, fn := range typ.Funcs {
+		parts = printFunc(parts, fset, "- Func %s -", fn)
+	}
+	for _, fn := range typ.Methods {
+		parts = printFunc(parts, fset, "- Method %s -", fn)
+	}
+	return parts
+}
+
+func printTypes(parts []string, fset *token.FileSet, types []*doc.Type) []string {
+	for _, typ := range types {
+		parts = printType(parts, fset, typ)
+	}
+	return parts
+}
+
+func printPkg(fset *token.FileSet, in *doc.Package) string {
+	var parts []string
+	parts = printFuncs(parts, fset, in.Funcs)
+	parts = printTypes(parts, fset, in.Types)
+	return strings.Join(append(parts, ""), "\n")
+}
+
+func testPkg(t *testing.T, filename string, src any, expected string) {
+	t.Helper()
+	fset := token.NewFileSet()
+	f, err := parser.ParseFile(fset, filename, src, parser.ParseComments)
+	if err != nil {
+		t.Fatal(err)
+	}
+	pkg, err := doc.NewFromFiles(fset, []*ast.File{f}, "foo")
+	if err != nil {
+		t.Fatal(err)
+	}
+	pkg = Transform(pkg)
+	if ret := printPkg(fset, pkg); ret != expected {
+		t.Fatalf("got:\n%s\nexpected:\n%s\n", ret, expected)
+	}
+}
+
+func testFromDir(t *testing.T, sel, relDir string) {
+	dir, err := os.Getwd()
+	if err != nil {
+		t.Fatal("Getwd failed:", err)
+	}
+	dir = path.Join(dir, relDir)
+	fis, err := os.ReadDir(dir)
+	if err != nil {
+		t.Fatal("ReadDir failed:", err)
+	}
+	for _, fi := range fis {
+		name := fi.Name()
+		if strings.HasPrefix(name, "_") || !fi.IsDir() || (sel != "" && !strings.Contains(name, sel)) {
+			continue
+		}
+		t.Run(name, func(t *testing.T) {
+			testDir := dir + "/" + name
+			out, e := os.ReadFile(testDir + "/out.expect")
+			if e != nil {
+				t.Fatal(e)
+			}
+			testPkg(t, testDir+"/in.go", nil, string(out))
+		})
+	}
+}
+
+func TestFromTestdata(t *testing.T) {
+	testFromDir(t, "", "./_testdata")
+}
diff --git a/parser/_testdata/embedded1/embtype.gop b/parser/_testdata/embedded1/embtype.gop
new file mode 100644
index 00000000..f34f7c63
--- /dev/null
+++ b/parser/_testdata/embedded1/embtype.gop
@@ -0,0 +1,3 @@
+type T struct {
+	abc.E
+}
diff --git a/parser/_testdata/embedded1/parser.expect b/parser/_testdata/embedded1/parser.expect
new file mode 100644
index 00000000..8786d354
--- /dev/null
+++ b/parser/_testdata/embedded1/parser.expect
@@ -0,0 +1,24 @@
+package main
+
+file embtype.gop
+ast.GenDecl:
+  Tok: type
+  Specs:
+    ast.TypeSpec:
+      Name:
+        ast.Ident:
+          Name: T
+      Type:
+        ast.StructType:
+          Fields:
+            ast.FieldList:
+              List:
+                ast.Field:
+                  Type:
+                    ast.SelectorExpr:
+                      X:
+                        ast.Ident:
+                          Name: abc
+                      Sel:
+                        ast.Ident:
+                          Name: E
diff --git a/parser/_testdata/matrix1/matrix.gop b/parser/_testdata/matrix1/matrix.gop
new file mode 100644
index 00000000..b46adba7
--- /dev/null
+++ b/parser/_testdata/matrix1/matrix.gop
@@ -0,0 +1,4 @@
+echo [
+	1, 2, 3
+	4, 5, 6
+]
diff --git a/parser/_testdata/matrix1/parser.expect b/parser/_testdata/matrix1/parser.expect
new file mode 100644
index 00000000..ac04e453
--- /dev/null
+++ b/parser/_testdata/matrix1/parser.expect
@@ -0,0 +1,43 @@
+package main
+
+file matrix.gop
+noEntrypoint
+ast.FuncDecl:
+  Name:
+    ast.Ident:
+      Name: main
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+  Body:
+    ast.BlockStmt:
+      List:
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: echo
+              Args:
+                ast.MatrixLit:
+                  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
+                  NElt: 2
diff --git a/parser/_testdata/matrix2/matrix.gop b/parser/_testdata/matrix2/matrix.gop
new file mode 100644
index 00000000..1682ebec
--- /dev/null
+++ b/parser/_testdata/matrix2/matrix.gop
@@ -0,0 +1,5 @@
+echo [
+	1, 2, 3
+	row...
+	7, 8, 9
+]
diff --git a/parser/_testdata/matrix2/parser.expect b/parser/_testdata/matrix2/parser.expect
new file mode 100644
index 00000000..9e62b45b
--- /dev/null
+++ b/parser/_testdata/matrix2/parser.expect
@@ -0,0 +1,47 @@
+package main
+
+file matrix.gop
+noEntrypoint
+ast.FuncDecl:
+  Name:
+    ast.Ident:
+      Name: main
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+  Body:
+    ast.BlockStmt:
+      List:
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: echo
+              Args:
+                ast.MatrixLit:
+                  Elts:
+                    ast.BasicLit:
+                      Kind: INT
+                      Value: 1
+                    ast.BasicLit:
+                      Kind: INT
+                      Value: 2
+                    ast.BasicLit:
+                      Kind: INT
+                      Value: 3
+                    ast.ElemEllipsis:
+                      Elt:
+                        ast.Ident:
+                          Name: row
+                    ast.BasicLit:
+                      Kind: INT
+                      Value: 7
+                    ast.BasicLit:
+                      Kind: INT
+                      Value: 8
+                    ast.BasicLit:
+                      Kind: INT
+                      Value: 9
+                  NElt: 3
diff --git a/parser/_testdata/rangeexpr/parser.expect b/parser/_testdata/rangeexpr1/parser.expect
similarity index 100%
rename from parser/_testdata/rangeexpr/parser.expect
rename to parser/_testdata/rangeexpr1/parser.expect
diff --git a/parser/_testdata/rangeexpr/rangeexpr.gop b/parser/_testdata/rangeexpr1/rangeexpr.gop
similarity index 100%
rename from parser/_testdata/rangeexpr/rangeexpr.gop
rename to parser/_testdata/rangeexpr1/rangeexpr.gop
diff --git a/parser/_testdata/rangeexpr2/parser.expect b/parser/_testdata/rangeexpr2/parser.expect
new file mode 100644
index 00000000..98e6de3c
--- /dev/null
+++ b/parser/_testdata/rangeexpr2/parser.expect
@@ -0,0 +1,25 @@
+package main
+
+file rangeexpr.gop
+noEntrypoint
+ast.FuncDecl:
+  Name:
+    ast.Ident:
+      Name: main
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+  Body:
+    ast.BlockStmt:
+      List:
+        ast.RangeStmt:
+          Tok: ILLEGAL
+          X:
+            ast.RangeExpr:
+              Last:
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 10
+          Body:
+            ast.BlockStmt:
diff --git a/parser/_testdata/rangeexpr2/rangeexpr.gop b/parser/_testdata/rangeexpr2/rangeexpr.gop
new file mode 100644
index 00000000..a62f7642
--- /dev/null
+++ b/parser/_testdata/rangeexpr2/rangeexpr.gop
@@ -0,0 +1,2 @@
+for :10 {
+}
diff --git a/parser/_testdata/rangeexpr3/parser.expect b/parser/_testdata/rangeexpr3/parser.expect
new file mode 100644
index 00000000..711c6ef3
--- /dev/null
+++ b/parser/_testdata/rangeexpr3/parser.expect
@@ -0,0 +1,29 @@
+package main
+
+file rangeexpr.gop
+noEntrypoint
+ast.FuncDecl:
+  Name:
+    ast.Ident:
+      Name: main
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+  Body:
+    ast.BlockStmt:
+      List:
+        ast.RangeStmt:
+          Tok: ILLEGAL
+          X:
+            ast.RangeExpr:
+              First:
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 1
+              Last:
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 10
+          Body:
+            ast.BlockStmt:
diff --git a/parser/_testdata/rangeexpr3/rangeexpr.gop b/parser/_testdata/rangeexpr3/rangeexpr.gop
new file mode 100644
index 00000000..c744aef8
--- /dev/null
+++ b/parser/_testdata/rangeexpr3/rangeexpr.gop
@@ -0,0 +1,2 @@
+for 1:10 {
+}
diff --git a/parser/_testdata/staticmthd1/parser.expect b/parser/_testdata/staticmthd1/parser.expect
new file mode 100644
index 00000000..2fc5405f
--- /dev/null
+++ b/parser/_testdata/staticmthd1/parser.expect
@@ -0,0 +1,37 @@
+package main
+
+file static_method.gop
+ast.FuncDecl:
+  Recv:
+    ast.FieldList:
+      List:
+        ast.Field:
+          Type:
+            ast.Ident:
+              Name: T
+  Name:
+    ast.Ident:
+      Name: foo
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+          List:
+            ast.Field:
+              Names:
+                ast.Ident:
+                  Name: a
+                ast.Ident:
+                  Name: b
+              Type:
+                ast.Ident:
+                  Name: int
+      Results:
+        ast.FieldList:
+          List:
+            ast.Field:
+              Type:
+                ast.Ident:
+                  Name: string
+  Body:
+    ast.BlockStmt:
diff --git a/parser/_testdata/staticmthd1/static_method.gop b/parser/_testdata/staticmthd1/static_method.gop
new file mode 100644
index 00000000..b5abce4b
--- /dev/null
+++ b/parser/_testdata/staticmthd1/static_method.gop
@@ -0,0 +1,2 @@
+func T.foo(a, b int) string {
+}
diff --git a/parser/_testdata/staticmthd2/a.gox b/parser/_testdata/staticmthd2/a.gox
new file mode 100644
index 00000000..ab011d51
--- /dev/null
+++ b/parser/_testdata/staticmthd2/a.gox
@@ -0,0 +1,2 @@
+func .New() *T {
+}
diff --git a/parser/_testdata/staticmthd2/parser.expect b/parser/_testdata/staticmthd2/parser.expect
new file mode 100644
index 00000000..ddbef83d
--- /dev/null
+++ b/parser/_testdata/staticmthd2/parser.expect
@@ -0,0 +1,24 @@
+package main
+
+file a.gox
+ast.FuncDecl:
+  Recv:
+    ast.FieldList:
+  Name:
+    ast.Ident:
+      Name: New
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+      Results:
+        ast.FieldList:
+          List:
+            ast.Field:
+              Type:
+                ast.StarExpr:
+                  X:
+                    ast.Ident:
+                      Name: T
+  Body:
+    ast.BlockStmt:
diff --git a/parser/parser_test.go b/parser/parser_test.go
index c1edf70c..e0815f3b 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -20,7 +20,7 @@ import (
 	"io/fs"
 	"testing"
 
-	"github.com/goplus/gop/parser/parsertest"
+	"github.com/goplus/gop/ast"
 	"github.com/goplus/gop/token"
 	fsx "github.com/qiniu/x/http/fs"
 )
@@ -121,6 +121,10 @@ func testClassErrCode(t *testing.T, code string, errExp, panicExp string) {
 	}
 }
 
+func TestErrLabel(t *testing.T) {
+	testErrCode(t, `a.x:`, `/foo/bar.gop:1:4: illegal label declaration`, ``)
+}
+
 func TestErrTuple(t *testing.T) {
 	testErrCode(t, `println (1,2)*2`, `/foo/bar.gop:1:9: tuple is not supported`, ``)
 	testErrCode(t, `println 2*(1,2)`, `/foo/bar.gop:1:13: expected ')', found ','`, ``)
@@ -213,272 +217,6 @@ func TestErrInFunc(t *testing.T) {
 
 // -----------------------------------------------------------------------------
 
-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
-                      Value:
-                        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 TestClassErrCode(t *testing.T) {
 	testClassErrCode(t, `var (
 	A,B
@@ -550,4 +288,55 @@ func TestErrStringLiteral(t *testing.T) {
 `, `/foo/bar.gop:1:5: string literal not terminated`, ``)
 }
 
+func TestErrFieldDecl(t *testing.T) {
+	testErrCode(t, `
+type T struct {
+	*(Foo)
+}
+`, `/foo/bar.gop:3:3: cannot parenthesize embedded type`, ``)
+	testErrCode(t, `
+type T struct {
+	(Foo)
+}
+`, `/foo/bar.gop:3:2: cannot parenthesize embedded type`, ``)
+	testErrCode(t, `
+type T struct {
+	(*Foo)
+}
+`, `/foo/bar.gop:3:2: cannot parenthesize embedded type`, ``)
+}
+
+func TestParseFieldDecl(t *testing.T) {
+	var p parser
+	p.init(token.NewFileSet(), "/foo/bar.gop", []byte(`type T struct {
+}
+`), 0)
+	p.parseFieldDecl(nil)
+}
+
+func TestCheckExpr(t *testing.T) {
+	var p parser
+	p.init(token.NewFileSet(), "/foo/bar.gop", []byte(``), 0)
+	p.checkExpr(&ast.Ellipsis{})
+	p.checkExpr(&ast.ElemEllipsis{})
+	p.checkExpr(&ast.StarExpr{})
+	p.checkExpr(&ast.IndexListExpr{})
+	p.checkExpr(&ast.FuncType{})
+	p.checkExpr(&ast.FuncLit{})
+}
+
+func TestErrFuncDecl(t *testing.T) {
+	testErrCode(t, `func test()
+{
+}
+`, `/foo/bar.gop:2:1: unexpected semicolon or newline before {`, ``)
+	testErrCode(t, `func test() +1
+`, `/foo/bar.gop:1:13: expected ';', found '+'`, ``)
+	testErrCode(t, `
+func (a T) +{}
+`, `/foo/bar.gop:2:12: expected type, found '+'`, ``)
+	testErrCode(t, `func +(a T, b T) {}
+`, `/foo/bar.gop:1:6: overload operator can only have one parameter`, ``)
+}
+
 // -----------------------------------------------------------------------------
diff --git a/parser/parserdir_test.go b/parser/parserdir_test.go
index 0831a73b..2277ec8d 100644
--- a/parser/parserdir_test.go
+++ b/parser/parserdir_test.go
@@ -41,6 +41,272 @@ 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
+                      Value:
+                        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 {
diff --git a/parser/parsertest/parsertest.go b/parser/parsertest/parsertest.go
index 081d233a..def8c133 100644
--- a/parser/parsertest/parsertest.go
+++ b/parser/parsertest/parsertest.go
@@ -84,6 +84,9 @@ func FprintNode(w io.Writer, lead string, v interface{}, prefix, indent string)
 					FprintNode(w, fmt.Sprintf("%s%v:\n", prefix, sf.Name), sfv, prefix+indent, indent)
 				}
 			}
+			if m, ok := v.(*ast.MatrixLit); ok {
+				fmt.Fprintf(w, "%sNElt: %d\n", prefix, len(m.Elts))
+			}
 		} else if lit, ok := v.(*ast.StringLitEx); ok {
 			fmt.Fprintf(w, "%sExtra:\n", prefix)
 			prefix += indent
diff --git a/printer/gop_test.go b/printer/gop_test.go
index 8aadd297..83fae961 100644
--- a/printer/gop_test.go
+++ b/printer/gop_test.go
@@ -67,6 +67,12 @@ func TestFuncs(t *testing.T) {
 				Name: &ast.Ident{Name: "bar"},
 				Body: &ast.BlockStmt{},
 			},
+			&ast.FuncDecl{
+				Type:   &ast.FuncType{Params: &ast.FieldList{}},
+				Name:   &ast.Ident{Name: "Classname"},
+				Body:   &ast.BlockStmt{},
+				Shadow: true,
+			},
 		},
 		NoPkgDecl: true,
 	}); err != nil {
diff --git a/x/typesutil/info_test.go b/x/typesutil/info_test.go
index 07b8dfdd..d70fbe67 100644
--- a/x/typesutil/info_test.go
+++ b/x/typesutil/info_test.go
@@ -1985,3 +1985,163 @@ func (p *N) Test__1(n int) {
 		t.Fatal("check TyOverloadMethod failed")
 	}
 }
+
+func TestGopOverloadUses(t *testing.T) {
+	testGopInfo(t, `
+func MulInt(a, b int) int {
+	return a * b
+}
+
+func MulFloat(a, b float64) float64 {
+	return a * b
+}
+
+func Mul = (
+	MulInt
+	MulFloat
+	func(x, y, z int) int {
+		return x * y * z
+	}
+)
+
+Mul 100,200
+Mul 100,200,300
+`, ``, `== types ==
+000:  0: 0 | "MulInt,MulFloat,"  *ast.BasicLit                  | value   : untyped string = "MulInt,MulFloat," | constant
+001:  2:18 | int                 *ast.Ident                     | type    : int | type
+002:  2:23 | int                 *ast.Ident                     | type    : int | type
+003:  3: 9 | a                   *ast.Ident                     | var     : int | variable
+004:  3: 9 | a * b               *ast.BinaryExpr                | value   : int | value
+005:  3:13 | b                   *ast.Ident                     | var     : int | variable
+006:  6:20 | float64             *ast.Ident                     | type    : float64 | type
+007:  6:29 | float64             *ast.Ident                     | type    : float64 | type
+008:  7: 9 | a                   *ast.Ident                     | var     : float64 | variable
+009:  7: 9 | a * b               *ast.BinaryExpr                | value   : float64 | value
+010:  7:13 | b                   *ast.Ident                     | var     : float64 | variable
+011: 13:15 | int                 *ast.Ident                     | type    : int | type
+012: 13:20 | int                 *ast.Ident                     | type    : int | type
+013: 14:10 | x                   *ast.Ident                     | var     : int | variable
+014: 14:10 | x * y               *ast.BinaryExpr                | value   : int | value
+015: 14:10 | x * y * z           *ast.BinaryExpr                | value   : int | value
+016: 14:14 | y                   *ast.Ident                     | var     : int | variable
+017: 14:18 | z                   *ast.Ident                     | var     : int | variable
+018: 18: 1 | Mul                 *ast.Ident                     | value   : func(a int, b int) int | value
+019: 18: 1 | Mul 100, 200        *ast.CallExpr                  | value   : int | value
+020: 18: 5 | 100                 *ast.BasicLit                  | value   : untyped int = 100 | constant
+021: 18: 9 | 200                 *ast.BasicLit                  | value   : untyped int = 200 | constant
+022: 19: 1 | Mul                 *ast.Ident                     | value   : func(x int, y int, z int) int | value
+023: 19: 1 | Mul 100, 200, 300   *ast.CallExpr                  | value   : int | value
+024: 19: 5 | 100                 *ast.BasicLit                  | value   : untyped int = 100 | constant
+025: 19: 9 | 200                 *ast.BasicLit                  | value   : untyped int = 200 | constant
+026: 19:13 | 300                 *ast.BasicLit                  | value   : untyped int = 300 | constant
+== defs ==
+000:  0: 0 | Gopo_Mul            | const main.Gopo_Mul untyped string
+001:  2: 6 | MulInt              | func main.MulInt(a int, b int) int
+002:  2:13 | a                   | var a int
+003:  2:16 | b                   | var b int
+004:  6: 6 | MulFloat            | func main.MulFloat(a float64, b float64) float64
+005:  6:15 | a                   | var a float64
+006:  6:18 | b                   | var b float64
+007: 13: 2 | Mul__2              | func main.Mul__2(x int, y int, z int) int
+008: 13: 7 | x                   | var x int
+009: 13:10 | y                   | var y int
+010: 13:13 | z                   | var z int
+011: 18: 1 | main                | func main.main()
+== uses ==
+000:  2:18 | int                 | type int
+001:  2:23 | int                 | type int
+002:  3: 9 | a                   | var a int
+003:  3:13 | b                   | var b int
+004:  6:20 | float64             | type float64
+005:  6:29 | float64             | type float64
+006:  7: 9 | a                   | var a float64
+007:  7:13 | b                   | var b float64
+008: 11: 2 | MulInt              | func main.MulInt(a int, b int) int
+009: 12: 2 | MulFloat            | func main.MulFloat(a float64, b float64) float64
+010: 13:15 | int                 | type int
+011: 13:20 | int                 | type int
+012: 14:10 | x                   | var x int
+013: 14:14 | y                   | var y int
+014: 14:18 | z                   | var z int
+015: 18: 1 | Mul                 | func main.MulInt(a int, b int) int
+016: 19: 1 | Mul                 | func main.Mul__2(x int, y int, z int) int`)
+
+	testGopInfo(t, `
+type foo struct {
+}
+
+func (a *foo) mulInt(b int) *foo {
+	return a
+}
+
+func (a *foo) mulFoo(b *foo) *foo {
+	return a
+}
+
+func (foo).mul = (
+	(foo).mulInt
+	(foo).mulFoo
+)
+
+var a, b *foo
+var c = a.mul(100)
+var d = a.mul(c)
+`, ``, `== types ==
+000:  0: 0 | ".mulInt,.mulFoo"   *ast.BasicLit                  | value   : untyped string = ".mulInt,.mulFoo" | constant
+001:  2:10 | struct {
+}          *ast.StructType                | type    : struct{} | type
+002:  5:10 | foo                 *ast.Ident                     | type    : main.foo | type
+003:  5:24 | int                 *ast.Ident                     | type    : int | type
+004:  5:29 | *foo                *ast.StarExpr                  | type    : *main.foo | type
+005:  5:30 | foo                 *ast.Ident                     | type    : main.foo | type
+006:  6: 9 | a                   *ast.Ident                     | var     : *main.foo | variable
+007:  9:10 | foo                 *ast.Ident                     | type    : main.foo | type
+008:  9:24 | *foo                *ast.StarExpr                  | type    : *main.foo | type
+009:  9:25 | foo                 *ast.Ident                     | type    : main.foo | type
+010:  9:30 | *foo                *ast.StarExpr                  | type    : *main.foo | type
+011:  9:31 | foo                 *ast.Ident                     | type    : main.foo | type
+012: 10: 9 | a                   *ast.Ident                     | var     : *main.foo | variable
+013: 18:10 | *foo                *ast.StarExpr                  | type    : *main.foo | type
+014: 18:11 | foo                 *ast.Ident                     | type    : main.foo | type
+015: 19: 9 | a                   *ast.Ident                     | var     : *main.foo | variable
+016: 19: 9 | a.mul               *ast.SelectorExpr              | value   : func(b int) *main.foo | value
+017: 19: 9 | a.mul(100)          *ast.CallExpr                  | value   : *main.foo | value
+018: 19:15 | 100                 *ast.BasicLit                  | value   : untyped int = 100 | constant
+019: 20: 9 | a                   *ast.Ident                     | var     : *main.foo | variable
+020: 20: 9 | a.mul               *ast.SelectorExpr              | value   : func(b *main.foo) *main.foo | value
+021: 20: 9 | a.mul(c)            *ast.CallExpr                  | value   : *main.foo | value
+022: 20:15 | c                   *ast.Ident                     | var     : *main.foo | variable
+== defs ==
+000:  0: 0 | Gopo_foo_mul        | const main.Gopo_foo_mul untyped string
+001:  2: 6 | foo                 | type main.foo struct{}
+002:  5: 7 | a                   | var a *main.foo
+003:  5:15 | mulInt              | func (*main.foo).mulInt(b int) *main.foo
+004:  5:22 | b                   | var b int
+005:  9: 7 | a                   | var a *main.foo
+006:  9:15 | mulFoo              | func (*main.foo).mulFoo(b *main.foo) *main.foo
+007:  9:22 | b                   | var b *main.foo
+008: 18: 5 | a                   | var main.a *main.foo
+009: 18: 8 | b                   | var main.b *main.foo
+010: 19: 5 | c                   | var main.c *main.foo
+011: 20: 5 | d                   | var main.d *main.foo
+== uses ==
+000:  5:10 | foo                 | type main.foo struct{}
+001:  5:24 | int                 | type int
+002:  5:30 | foo                 | type main.foo struct{}
+003:  6: 9 | a                   | var a *main.foo
+004:  9:10 | foo                 | type main.foo struct{}
+005:  9:25 | foo                 | type main.foo struct{}
+006:  9:31 | foo                 | type main.foo struct{}
+007: 10: 9 | a                   | var a *main.foo
+008: 13: 7 | foo                 | type main.foo struct{}
+009: 14: 3 | foo                 | type main.foo struct{}
+010: 14: 8 | mulInt              | func (*main.foo).mulInt(b int) *main.foo
+011: 15: 3 | foo                 | type main.foo struct{}
+012: 15: 8 | mulFoo              | func (*main.foo).mulFoo(b *main.foo) *main.foo
+013: 18:11 | foo                 | type main.foo struct{}
+014: 19: 9 | a                   | var main.a *main.foo
+015: 19:11 | mul                 | func (*main.foo).mulInt(b int) *main.foo
+016: 20: 9 | a                   | var main.a *main.foo
+017: 20:11 | mul                 | func (*main.foo).mulFoo(b *main.foo) *main.foo
+018: 20:15 | c                   | var main.c *main.foo`)
+}