Repository: goplus/gop
Pull request #1676 (2024-01-28 09:58:28)

overload operator: T.op = (...)


Patch:
diff --git a/cl/compile.go b/cl/compile.go
index f009dead..f95aa1d3 100644
--- a/cl/compile.go
+++ b/cl/compile.go
@@ -340,15 +340,15 @@ func doInitMethods(ld *typeLoader) {
 
 type pkgCtx struct {
 	*nodeInterp
-	projs   map[string]*gmxProject // .gmx => project
-	classes map[*ast.File]gmxClass
-	fset    *token.FileSet
-	cpkgs   *cpackages.Importer
-	syms    map[string]loader
-	ovnames []string // names should load before initGopPkg
-	inits   []func()
-	tylds   []*typeLoader
-	errs    errors.List
+	projs    map[string]*gmxProject // .gmx => project
+	classes  map[*ast.File]gmxClass
+	fset     *token.FileSet
+	cpkgs    *cpackages.Importer
+	syms     map[string]loader
+	lbinames []any // names that should load before initGopPkg (can be string/func or *ast.Ident/type)
+	inits    []func()
+	tylds    []*typeLoader
+	errs     errors.List
 
 	generics map[string]bool // generic type record
 	idents   []*ast.Ident    // toType ident recored
@@ -644,8 +644,12 @@ func initGopPkg(ctx *pkgCtx, pkg *gox.Package, gopSyms map[string]bool) {
 			ctx.loadSymbol(name)
 		}
 	}
-	for _, name := range ctx.ovnames {
-		ctx.loadSymbol(name)
+	for _, lbi := range ctx.lbinames {
+		if name, ok := lbi.(string); ok {
+			ctx.loadSymbol(name)
+		} else {
+			ctx.loadType(lbi.(*ast.Ident).Name)
+		}
 	}
 	if pkg.Types.Scope().Lookup(gopPackage) == nil {
 		pkg.Types.Scope().Insert(types.NewConst(token.NoPos, pkg.Types, gopPackage, types.Typ[types.UntypedBool], constant.MakeBool(true)))
@@ -693,16 +697,6 @@ func loadFile(ctx *pkgCtx, f *ast.File) {
 					getTypeLoader(ctx, ctx.syms, token.NoPos, name).load()
 				}
 			}
-		case *ast.OverloadFuncDecl:
-			/* name := d.Name
-			for idx, fn := range d.Funcs {
-				switch expr := fn.(type) {
-				case *ast.FuncLit:
-					// ctx.loadSymbol(overloadFuncName(name.Name, idx))
-				default:
-					log.Panicf("TODO - cl.loadFile OverloadFuncDecl: unknown func - %T\n", expr)
-				}
-			} */
 		}
 	}
 }
@@ -1082,21 +1076,33 @@ func preloadFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, goFile
 				markSelector
 				markFuncLit
 			)
+			var recv *ast.Ident
+			if d.Recv != nil {
+				otyp, ok := d.Recv.List[0].Type.(*ast.Ident)
+				if !ok {
+					log.Panicln("TODO - cl.preloadFile OverloadFuncDecl: invalid recv")
+				}
+				recv = otyp
+			}
 			onames := make([]string, 0, 4)
 			mark := 0
 			name := d.Name
 			for idx, fn := range d.Funcs {
 				switch expr := fn.(type) {
 				case *ast.Ident:
+					checkOverloadFunc(d)
 					onames = append(onames, expr.Name)
-					ctx.ovnames = append(ctx.ovnames, expr.Name)
+					ctx.lbinames = append(ctx.lbinames, expr.Name)
 					mark |= markIdent
+				case *ast.SelectorExpr:
+					checkOverloadMethod(d)
+					checkOverloadMethodRecvType(recv, expr.X)
+					onames = append(onames, "."+expr.Sel.Name)
+					ctx.lbinames = append(ctx.lbinames, recv)
 				case *ast.FuncLit:
-					if d.Recv != nil || d.Operator {
-						log.Panicln("TODO - OverloadFuncDecl")
-					}
+					checkOverloadFunc(d)
 					name1 := overloadFuncName(name.Name, idx)
-					ctx.ovnames = append(ctx.ovnames, name1)
+					ctx.lbinames = append(ctx.lbinames, name1)
 					preloadFuncDecl(&ast.FuncDecl{
 						Doc:  d.Doc,
 						Name: &ast.Ident{NamePos: name.NamePos, Name: name1},
@@ -1109,7 +1115,7 @@ func preloadFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, goFile
 				}
 			}
 			if (mark & (markIdent | markSelector)) != 0 {
-				oname := overloadName(name.Name)
+				oname := overloadName(recv, name.Name, d.Operator)
 				oval := strings.Join(onames, ",")
 				preloadConst(&ast.GenDecl{
 					Doc: d.Doc,
@@ -1121,7 +1127,7 @@ func preloadFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, goFile
 						},
 					},
 				})
-				ctx.ovnames = append(ctx.ovnames, oname)
+				ctx.lbinames = append(ctx.lbinames, oname)
 			}
 
 		default:
@@ -1130,6 +1136,26 @@ func preloadFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, goFile
 	}
 }
 
+func checkOverloadFunc(d *ast.OverloadFuncDecl) {
+	if d.Recv != nil && !d.Operator {
+		log.Panicln("TODO - cl.preloadFile OverloadFuncDecl: checkOverloadFunc")
+	}
+}
+
+func checkOverloadMethod(d *ast.OverloadFuncDecl) {
+	if d.Recv == nil {
+		log.Panicln("TODO - cl.preloadFile OverloadFuncDecl: checkOverloadMethod")
+	}
+}
+
+func checkOverloadMethodRecvType(ot *ast.Ident, recv ast.Expr) {
+	rtyp, _ := getRecvType(recv)
+	rt, ok := rtyp.(*ast.Ident)
+	if !ok || ot.Name != rt.Name {
+		log.Panicln("TODO - checkOverloadMethodRecvType:", recv)
+	}
+}
+
 const (
 	indexTable = "0123456789abcdefghijklmnopqrstuvwxyz"
 )
@@ -1138,12 +1164,23 @@ func overloadFuncName(name string, idx int) string {
 	return name + "__" + indexTable[idx:idx+1]
 }
 
-func overloadName(name string) string {
-	prefix := "Gopo_"
-	if strings.ContainsRune(name, '_') {
-		prefix = "Gopo__"
+func overloadName(recv *ast.Ident, name string, isOp bool) string {
+	if isOp {
+		if oname, ok := binaryGopNames[name]; ok {
+			name = oname
+		} else {
+			log.Panicln("TODO - can't overload operator", name)
+		}
+	}
+	sep := "_"
+	if strings.ContainsRune(name, '_') || (recv != nil && strings.ContainsRune(recv.Name, '_')) {
+		sep = "__"
+	}
+	typ := ""
+	if recv != nil {
+		typ = recv.Name + sep
 	}
-	return prefix + name
+	return "Gopo" + sep + typ + name
 }
 
 func stringLit(val string) *ast.BasicLit {
diff --git a/go.mod b/go.mod
index a8f981af..6d58cd5b 100644
--- a/go.mod
+++ b/go.mod
@@ -5,7 +5,7 @@ go 1.18
 require (
 	github.com/fsnotify/fsnotify v1.7.0
 	github.com/goplus/c2go v0.7.20
-	github.com/goplus/gox v1.14.0
+	github.com/goplus/gox v1.14.1-0.20240128095250-49986c435532
 	github.com/goplus/mod v0.12.2
 	github.com/qiniu/x v1.13.2
 	golang.org/x/tools v0.17.0
diff --git a/go.sum b/go.sum
index 57ed71fb..941df093 100644
--- a/go.sum
+++ b/go.sum
@@ -6,8 +6,9 @@ github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyT
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/goplus/c2go v0.7.20 h1:90HiO7tuX8hHfEnl7HZRmLsAPH9q2wsYIvHiJMpwtU8=
 github.com/goplus/c2go v0.7.20/go.mod h1:hToieb5V4BMY3c431DQ7DEg13JQ+q9qGmls4+GgymrA=
-github.com/goplus/gox v1.14.0 h1:QYhgtBUyFvByaD8FbHx/5r2RsDw/tgT6tKEqIwtcJFk=
 github.com/goplus/gox v1.14.0/go.mod h1:G7Hz+cAOUyJyN9pPHrpqhfQPDUtiJNmoRVTwoaQ9nw0=
+github.com/goplus/gox v1.14.1-0.20240128095250-49986c435532 h1:uiTaxyc0x5xifrD+LAFk6V9F/lh7u4sGfK45vC6qH8U=
+github.com/goplus/gox v1.14.1-0.20240128095250-49986c435532/go.mod h1:G7Hz+cAOUyJyN9pPHrpqhfQPDUtiJNmoRVTwoaQ9nw0=
 github.com/goplus/mod v0.12.2 h1:5RHUdmXT5jlzOFUAfrx/5uSk4RTphFPFcCbRUt/AIBo=
 github.com/goplus/mod v0.12.2/go.mod h1:ZtlS9wHOcAVxZ/zq7WLdKVes1HG/8Yn3KNuWZGcpeTs=
 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=


Test patch:
diff --git a/cl/builtin_test.go b/cl/builtin_test.go
index e70d7b67..3d03c7ad 100644
--- a/cl/builtin_test.go
+++ b/cl/builtin_test.go
@@ -56,9 +56,52 @@ func TestErrStringLit(t *testing.T) {
 func TestErrPreloadFile(t *testing.T) {
 	pkg := gox.NewPackage("", "foo", goxConf)
 	ctx := &blockCtx{pkgCtx: &pkgCtx{}}
-	func() {
+	t.Run("overloadName", func(t *testing.T) {
+		defer func() {
+			if e := recover(); e == nil || e != "TODO - can't overload operator ++\n" {
+				t.Fatal("TestErrPreloadFile:", e)
+			}
+		}()
+		overloadName(&ast.Ident{}, "++", true)
+	})
+	t.Run("checkOverloadFunc", func(t *testing.T) {
+		defer func() {
+			if e := recover(); e == nil || e != "TODO - cl.preloadFile OverloadFuncDecl: checkOverloadFunc\n" {
+				t.Fatal("TestErrPreloadFile:", e)
+			}
+		}()
+		checkOverloadFunc(&ast.OverloadFuncDecl{
+			Recv: &ast.FieldList{},
+		})
+	})
+	t.Run("checkOverloadMethod", func(t *testing.T) {
+		defer func() {
+			if e := recover(); e == nil || e != "TODO - cl.preloadFile OverloadFuncDecl: checkOverloadMethod\n" {
+				t.Fatal("TestErrPreloadFile:", e)
+			}
+		}()
+		checkOverloadMethod(&ast.OverloadFuncDecl{})
+	})
+	t.Run("checkOverloadMethodRecvType1", func(t *testing.T) {
+		defer func() {
+			if e := recover(); e == nil || e != "TODO - checkOverloadMethodRecvType: bar\n" {
+				t.Fatal("TestErrPreloadFile:", e)
+			}
+		}()
+		checkOverloadMethodRecvType(&ast.Ident{Name: "foo"}, &ast.Ident{Name: "bar"})
+	})
+	t.Run("checkOverloadMethodRecvType2", func(t *testing.T) {
+		defer func() {
+			if e := recover(); e == nil || e != "TODO - checkOverloadMethodRecvType: &{0 INT 123 <nil>}\n" {
+				t.Fatal("TestErrPreloadFile:", e)
+			}
+		}()
+		expr := &ast.BasicLit{Kind: token.INT, Value: "123"}
+		checkOverloadMethodRecvType(&ast.Ident{Name: "foo"}, expr)
+	})
+	t.Run("OverloadFuncDecl: invalid recv", func(t *testing.T) {
 		defer func() {
-			if e := recover(); e == nil || e != "TODO - OverloadFuncDecl\n" {
+			if e := recover(); e == nil || e != "TODO - cl.preloadFile OverloadFuncDecl: invalid recv\n" {
 				t.Fatal("TestErrPreloadFile:", e)
 			}
 		}()
@@ -68,12 +111,14 @@ func TestErrPreloadFile(t *testing.T) {
 				Funcs: []ast.Expr{
 					&ast.FuncLit{},
 				},
-				Operator: true,
+				Recv: &ast.FieldList{List: []*ast.Field{
+					{Type: &ast.StarExpr{}},
+				}},
 			},
 		}
 		preloadFile(pkg, ctx, "foo.gop", &ast.File{Decls: decls}, "", true)
-	}()
-	func() {
+	})
+	t.Run("OverloadFuncDecl: unknown func", func(t *testing.T) {
 		defer func() {
 			if e := recover(); e == nil || e != "TODO - cl.preloadFile OverloadFuncDecl: unknown func - *ast.BasicLit\n" {
 				t.Fatal("TestErrPreloadFile:", e)
@@ -89,8 +134,8 @@ func TestErrPreloadFile(t *testing.T) {
 			},
 		}
 		preloadFile(pkg, ctx, "foo.gop", &ast.File{Decls: decls}, "", true)
-	}()
-	func() {
+	})
+	t.Run("unknown decl", func(t *testing.T) {
 		defer func() {
 			if e := recover(); e == nil || e != "TODO - cl.preloadFile: unknown decl - *ast.BadDecl\n" {
 				t.Fatal("TestErrPreloadFile:", e)
@@ -100,7 +145,7 @@ func TestErrPreloadFile(t *testing.T) {
 			&ast.BadDecl{},
 		}
 		preloadFile(pkg, ctx, "foo.gop", &ast.File{Decls: decls}, "", true)
-	}()
+	})
 }
 
 func TestErrParseTypeEmbedName(t *testing.T) {
diff --git a/cl/compile_gop_test.go b/cl/compile_gop_test.go
index c72f41a5..0784fd27 100644
--- a/cl/compile_gop_test.go
+++ b/cl/compile_gop_test.go
@@ -20,6 +20,128 @@ import (
 	"testing"
 )
 
+func TestOverloadOp(t *testing.T) {
+	gopClTest(t, `
+type foo struct {
+}
+
+func (a *foo) + (b *foo) *foo {
+	println("a + b")
+	return &foo{}
+}
+
+func (a foo) - (b foo) foo {
+	println("a - b")
+	return foo{}
+}
+
+func -(a foo) {
+	println("-a")
+}
+
+func ++(a foo) {
+	println("a++")
+}
+
+func (a foo) != (b foo) bool{
+	println("a!=b")
+	return true
+}
+
+var a, b foo
+var c = a - b
+var d = -a       // TODO: -a have no return value!
+var e = a!=b
+`, `package main
+
+import "fmt"
+
+type foo struct {
+}
+
+func (a *foo) Gop_Add(b *foo) *foo {
+	fmt.Println("a + b")
+	return &foo{}
+}
+func (a foo) Gop_Sub(b foo) foo {
+	fmt.Println("a - b")
+	return foo{}
+}
+func (a foo) Gop_NE(b foo) bool {
+	fmt.Println("a!=b")
+	return true
+}
+func (a foo) Gop_Neg() {
+	fmt.Println("-a")
+}
+func (a foo) Gop_Inc() {
+	fmt.Println("a++")
+}
+
+var a, b foo
+var c = (foo).Gop_Sub(a, b)
+var d = a.Gop_Neg()
+var e = (foo).Gop_NE(a, b)
+`)
+}
+
+func TestOverloadOp2(t *testing.T) {
+	gopClTest(t, `
+type foo struct {
+}
+
+func (a foo) mulInt(b int) (ret foo) {
+	return
+}
+
+func (a foo) mulFoo(b foo) (ret foo) {
+	return
+}
+
+func intMulFoo(a int, b foo) (ret foo) {
+	return
+}
+
+func (foo).* = (
+	(foo).mulInt
+	(foo).mulFoo
+	intMulFoo
+)
+
+var a, b foo
+
+println a * 10
+println a * b
+println 10 * a
+`, `package main
+
+import "fmt"
+
+type foo struct {
+}
+
+const Gopo__foo__Gop_Mul = ".mulInt,.mulFoo,intMulFoo"
+
+func (a foo) mulInt(b int) (ret foo) {
+	return
+}
+func (a foo) mulFoo(b foo) (ret foo) {
+	return
+}
+func intMulFoo(a int, b foo) (ret foo) {
+	return
+}
+
+var a, b foo
+
+func main() {
+	fmt.Println((foo).mulInt(a, 10))
+	fmt.Println((foo).mulFoo(a, b))
+	fmt.Println(intMulFoo(10, a))
+}
+`)
+}
+
 func TestOverloadFunc(t *testing.T) {
 	gopClTest(t, `
 func add = (
diff --git a/cl/compile_test.go b/cl/compile_test.go
index 125a5846..8ddf572a 100644
--- a/cl/compile_test.go
+++ b/cl/compile_test.go
@@ -1535,7 +1535,7 @@ var z uint128 = x + y
 import "github.com/goplus/gop/builtin/ng"
 
 var x, y ng.Uint128
-var z ng.Uint128 = x.Gop_Add__1(y)
+var z ng.Uint128 = (ng.Uint128).Gop_Add__1(x, y)
 `)
 }
 
@@ -1548,7 +1548,7 @@ var z int128 = x + y
 import "github.com/goplus/gop/builtin/ng"
 
 var x, y ng.Int128
-var z ng.Int128 = x.Gop_Add__1(y)
+var z ng.Int128 = (ng.Int128).Gop_Add__1(x, y)
 `)
 }
 
@@ -1561,7 +1561,7 @@ var z bigint = x + y
 import "github.com/goplus/gop/builtin/ng"
 
 var x, y ng.Bigint
-var z ng.Bigint = x.Gop_Add(y)
+var z ng.Bigint = (ng.Bigint).Gop_Add(x, y)
 `)
 }
 
@@ -1643,8 +1643,8 @@ import (
 )
 
 var x = ng.Bigrat_Init__2(big.NewRat(7, 2))
-var y = x.Gop_Add(ng.Bigrat_Init__0(100))
-var z = ng.Bigrat_Init__0(100) + y
+var y = (ng.Bigrat).Gop_Add(x, ng.Bigrat_Init__0(100))
+var z = (ng.Bigrat).Gop_Add(ng.Bigrat_Init__0(100), y)
 `)
 }
 
@@ -2758,71 +2758,6 @@ func (M) Bar() {
 `)
 }
 
-func TestOverloadOp(t *testing.T) {
-	gopClTest(t, `
-type foo struct {
-}
-
-func (a *foo) + (b *foo) *foo {
-	println("a + b")
-	return &foo{}
-}
-
-func (a foo) - (b foo) foo {
-	println("a - b")
-	return foo{}
-}
-
-func -(a foo) {
-	println("-a")
-}
-
-func ++(a foo) {
-	println("a++")
-}
-
-func (a foo) != (b foo) bool{
-	println("a!=b")	
-	return true
-}
-
-var a, b foo
-var c = a - b
-var d = -a       // TODO: -a have no return value!
-var e = a!=b
-`, `package main
-
-import "fmt"
-
-type foo struct {
-}
-
-func (a *foo) Gop_Add(b *foo) *foo {
-	fmt.Println("a + b")
-	return &foo{}
-}
-func (a foo) Gop_Sub(b foo) foo {
-	fmt.Println("a - b")
-	return foo{}
-}
-func (a foo) Gop_NE(b foo) bool {
-	fmt.Println("a!=b")
-	return true
-}
-func (a foo) Gop_Neg() {
-	fmt.Println("-a")
-}
-func (a foo) Gop_Inc() {
-	fmt.Println("a++")
-}
-
-var a, b foo
-var c = a.Gop_Sub(b)
-var d = a.Gop_Neg()
-var e = a.Gop_NE(b)
-`)
-}
-
 func TestCmdlineNoEOL(t *testing.T) {
 	gopClTest(t, `println "Hi"`, `package main
 
@@ -4761,9 +4696,9 @@ var e = a!=b
 `, `package main
 
 var a, b foo
-var c = a.Gop_Sub(b)
+var c = (foo).Gop_Sub(a, b)
 var d = a.Gop_Neg()
-var e = a.Gop_NE(b)
+var e = (foo).Gop_NE(a, b)
 `)
 }
 
@@ -4822,11 +4757,11 @@ var b int
 var c float64
 
 func main() {
-	_ = a.Gop_Add__0(b)
-	_ = a.Gop_Add__0(100)
-	_ = a.Gop_Add__1(c)
-	_ = Vector3_Init__0(100) + a
-	_ = Vector3_Cast__0(b).Gop_Add__2(a)
+	_ = (Vector3).Gop_Add__0(a, b)
+	_ = (Vector3).Gop_Add__0(a, 100)
+	_ = (Vector3).Gop_Add__1(a, c)
+	_ = (Vector3).Gop_Add__2(Vector3_Init__0(100), a)
+	_ = (Vector3).Gop_Add__2(Vector3_Cast__0(b), a)
 	_ = b + a.Gop_Rcast__0()
 	a.Gop_AddAssign(Vector3_Init__0(b))
 	a.Gop_AddAssign(Vector3_Init__1(c))
diff --git a/testdata/overloadop1/overloadop.gop b/testdata/overloadop1/overloadop.gop
new file mode 100644
index 00000000..d7b489b6
--- /dev/null
+++ b/testdata/overloadop1/overloadop.gop
@@ -0,0 +1,34 @@
+type foo struct {
+}
+
+func (a foo) + (b foo) (ret foo) {
+	println "a + b"
+	return
+}
+
+func (a foo) - (b foo) (ret foo) {
+	println "a - b"
+	return
+}
+
+func -(a foo) (ret foo) {
+	println "-a"
+	return
+}
+
+func ++(a foo) {
+	println "a++"
+}
+
+func (a foo) != (b foo) bool {
+	println "a != b"
+	return true
+}
+
+var a, b foo
+var c = a + b
+var d = a - b
+var e = -a       // TODO: -a have no return value!
+var f = a != b
+println f
+a++
diff --git a/testdata/overloadop2/overloadop.gop b/testdata/overloadop2/overloadop.gop
new file mode 100644
index 00000000..516947f2
--- /dev/null
+++ b/testdata/overloadop2/overloadop.gop
@@ -0,0 +1,28 @@
+type foo struct {
+}
+
+func (a foo) mulInt(b int) (ret foo) {
+	println "a * int"
+	return
+}
+
+func (a foo) mulFoo(b foo) (ret foo) {
+	println "a * b"
+	return
+}
+
+func intMulFoo(a int, b foo) (ret foo) {
+	println "int * b"
+	return
+}
+
+func (foo).* = (
+	(foo).mulInt
+	(foo).mulFoo
+	intMulFoo
+)
+
+var a, b foo
+var c = a * 10
+var d = a * b
+var e = 10 * a
diff --git a/x/build/build_test.go b/x/build/build_test.go
index b09985c5..b43a127f 100644
--- a/x/build/build_test.go
+++ b/x/build/build_test.go
@@ -241,7 +241,7 @@ import (
 
 func main() {
 	a := ng.Bigrat_Init__2(big.NewRat(1, 2))
-	fmt.Println(a.Gop_Add(ng.Bigrat_Init__2(big.NewRat(1, 2))))
+	fmt.Println((ng.Bigrat).Gop_Add(a, ng.Bigrat_Init__2(big.NewRat(1, 2))))
 }
 `)
 }