Repository: goplus/gop
Pull request #1848 (2024-04-09 09:28:45)

parser/printer: Static methods


Patch:
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index f3e4bc2b..234cf129 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -21,10 +21,7 @@ jobs:
   Test:
     strategy:
       matrix:
-        os:
-        - ubuntu-latest
-        - windows-latest
-        - macos-latest
+        os: [ubuntu-latest, windows-latest, macos-11]
         go:
         - 1.18.x
         - 1.19.x
diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml
index 0c966198..92e8181d 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,7 @@ jobs:
     - name: Set up Go
       uses: actions/setup-go@v5
       with:
-        go-version: 1.21.x
+        go-version: "1.21"
 
     - name: Set up QEMU
       uses: docker/setup-qemu-action@v3
@@ -77,7 +77,8 @@ jobs:
         version: latest
         args: release --clean -p 4
       env:
-        GITHUB_TOKEN: ${{ github.token }}
+        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        GITHUB_REPOSITORY_OWNER: ${{ github.repository_owner }}
         WINGET_PKGS_PRIVATE_KEY: ${{ secrets.WINGET_PKGS_PRIVATE_KEY }}
 
     - name: Upload deb/rpm to Fury.io
@@ -94,3 +95,4 @@ jobs:
         done
       env:
         FURY_TOKEN: ${{ secrets.FURY_TOKEN }}
+        GITHUB_REPOSITORY_OWNER: ${{ github.repository_owner }}
diff --git a/.goreleaser.yaml b/.goreleaser.yaml
index e5da561d..04d4cb28 100644
--- a/.goreleaser.yaml
+++ b/.goreleaser.yaml
@@ -67,7 +67,7 @@ changelog:
 dockers:
   - goarch: "386"
     image_templates:
-      - ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:{{ .Version }}-386
+      - ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:v{{ .Version }}-i386
     use: buildx
     build_flag_templates:
       - --platform=linux/386
@@ -76,7 +76,7 @@ dockers:
       - ./
   - goarch: amd64
     image_templates:
-      - ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:{{ .Version }}-amd64
+      - ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:v{{ .Version }}-x86_64
     use: buildx
     build_flag_templates:
       - --platform=linux/amd64
@@ -85,7 +85,7 @@ dockers:
       - ./
   - goarch: arm64
     image_templates:
-      - ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:{{ .Version }}-arm64
+      - ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:v{{ .Version }}-arm64
     use: buildx
     build_flag_templates:
       - --platform=linux/arm64
@@ -94,28 +94,28 @@ dockers:
       - ./
 
 docker_manifests:
-  - name_template: ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:{{ .Version }}
+  - name_template: ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:v{{ .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 }}
+      - ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:v{{ .Version }}-i386
+      - ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:v{{ .Version }}-x86_64
+      - ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:v{{ .Version }}-arm64
+  - name_template: ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:v{{ .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
+      - ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:v{{ .Version }}-i386
+      - ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:v{{ .Version }}-x86_64
+      - ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:v{{ .Version }}-arm64
     skip_push: auto
-  - name_template: ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:{{ .Major }}
+  - name_template: ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:v{{ .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
+      - ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:v{{ .Version }}-i386
+      - ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:v{{ .Version }}-x86_64
+      - ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:v{{ .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
+      - ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:v{{ .Version }}-i386
+      - ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:v{{ .Version }}-x86_64
+      - ghcr.io/{{ envOrDefault "GITHUB_REPOSITORY" "goplus/gop" }}:v{{ .Version }}-arm64
     skip_push: auto
 
 winget:
diff --git a/ast/ast.go b/ast/ast.go
index 2ddec568..9f3753a5 100644
--- a/ast/ast.go
+++ b/ast/ast.go
@@ -1055,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/cl/expr.go b/cl/expr.go
index e5e02047..aaf7eb13 100644
--- a/cl/expr.go
+++ b/cl/expr.go
@@ -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
@@ -363,6 +391,8 @@ 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:
 		panic(ctx.newCodeErrorf(v.Pos(), "compileExpr failed: unknown - %T", v))
 	}
diff --git a/parser/parser.go b/parser/parser.go
index f2d2fc42..3ad03759 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -3648,7 +3648,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
 }
 
@@ -3705,6 +3705,8 @@ func (p *parser) parseOverloadDecl(decl *ast.OverloadFuncDecl) *ast.OverloadFunc
 // `func identOrOp(params) results {...}`
 // `func identOrOp = (overloadFuncs)`
 //
+// `func T.ident(params) results { ... }`
+//
 // `func (recv) identOrOp(params) results { ... }`
 // `func (T).identOrOp = (overloadFuncs)`
 // `func (params) results { ... }()`
@@ -3719,13 +3721,15 @@ 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)`
+		// static method: `func T.ident(...) results`
 		ident, isOp = p.parseIdentOrOp()
-		if p.tok == token.ASSIGN {
+		switch p.tok {
+		case token.ASSIGN:
 			// func identOrOp = (overloadFuncs)
 			return p.parseOverloadDecl(&ast.OverloadFuncDecl{
 				Doc:      doc,
@@ -3733,6 +3737,14 @@ func (p *parser) parseFuncDeclOrCall() (ast.Decl, *ast.CallExpr) {
 				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)
 	} else {
@@ -3754,8 +3766,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 { ... }
@@ -3763,10 +3775,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}}}
 			}
@@ -3799,7 +3813,7 @@ func (p *parser) parseFuncDeclOrCall() (ast.Decl, *ast.CallExpr) {
 
 	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
@@ -3829,6 +3843,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 5b831449..876ec7a8 100644
--- a/printer/nodes.go
+++ b/printer/nodes.go
@@ -2016,8 +2016,13 @@ func (p *printer) funcDecl(d *ast.FuncDecl) {
 	// 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.Static { // static method
+			p.expr(d.Recv.List[0].Type)
+			p.print(token.PERIOD)
+		} else {
+			p.parameters(d.Recv) // method: print receiver
+			p.print(blank)
+		}
 	}
 	p.expr(d.Name)
 	if d.Operator && d.Recv != nil {


Test patch:
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/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/parser_test.go b/parser/parser_test.go
index c7cc3450..e0815f3b 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -325,4 +325,18 @@ func TestCheckExpr(t *testing.T) {
 	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`, ``)
+}
+
 // -----------------------------------------------------------------------------