Repository: goplus/xgo

Issue:
Parameter *T Automatically Converted to *Base
### Proposal

In Go, a `recv *T` pointer can be automatically converted to `*Base`. For example:

```go
type Base struct {
    ...
}

func (p *Base) F() { ... }

type T struct {
    Base
}
```

A function call like `p.F()` is essentially equivalent to a call like `(*Base).F(&p.Base)`, where `&p.Base` automatically converts the `p *T` into a `*Base` pointer.

We want the parameter behaviour in [XGo](https://github.com/goplus/xgo) to be consistent with the receiver pointer behaviour. That is, even if `F` is not a method but a function, this should still work:

```go
func F(p *Base) { ... }
```

For a `p *T`, when we call `F(p)`, no type error will be raised. Instead, it will be automatically converted to a function call like `F(&p.Base)`.


Discussion:

Solution patch:
diff --git a/cl/_testgop/autoconv-2439/in.xgo b/cl/_testgop/autoconv-2439/in.xgo
new file mode 100644
index 00000000..8202ed85
--- /dev/null
+++ b/cl/_testgop/autoconv-2439/in.xgo
@@ -0,0 +1,20 @@
+type BasePtr struct {
+}
+
+type Base struct {
+    *BasePtr
+}
+
+func Walk(p *Base) {}
+func WalkPtr(p *BasePtr) {}
+
+type T struct {
+    Base
+}
+
+func f() *T {
+    return {}
+}
+
+walk new(T)
+walkPtr f()
diff --git a/cl/compile.go b/cl/compile.go
index 6784ea3a..f0a0404b 100644
--- a/cl/compile.go
+++ b/cl/compile.go
@@ -19,6 +19,8 @@ package cl
 
 import (
 	"fmt"
+	goast "go/ast"
+	gotoken "go/token"
 	"go/types"
 	"log"
 	"reflect"
@@ -497,6 +499,66 @@ func (p *pkgCtx) lookupClassNode(name string) ast.Node {
 	return nil
 }
 
+type visitedT = map[*types.Struct]none
+
+// see https://github.com/goplus/xgo/issues/2439
+func embeddedFieldCast(o *types.Struct, tn *types.Named, pv *gogen.Element, visited visitedT) bool {
+	if _, ok := visited[o]; ok {
+		return false
+	}
+	visited[o] = none{}
+	for i, n := 0, o.NumFields(); i < n; i++ {
+		if fld := o.Field(i); fld.Embedded() {
+			var ptr bool
+			var ftn *types.Named
+			switch ft := fld.Type().(type) {
+			case *types.Named:
+				ftn = ft
+			case *types.Pointer:
+				if ft, ok := ft.Elem().(*types.Named); ok {
+					ftn, ptr = ft, true
+				}
+			}
+			if ftn != nil {
+				if ftn == tn {
+					pv.Val = &goast.SelectorExpr{
+						X:   pv.Val,
+						Sel: goast.NewIdent(fld.Name()),
+					}
+					if !ptr {
+						pv.Val = &goast.UnaryExpr{
+							Op: gotoken.AND,
+							X:  pv.Val,
+						}
+					}
+					return true
+				}
+				if fldStruct, ok := ftn.Underlying().(*types.Struct); ok {
+					if embeddedFieldCast(fldStruct, tn, pv, visited) {
+						return true
+					}
+				}
+			}
+		}
+	}
+	return false
+}
+
+func implicitCast(pkg *gogen.Package, V, T types.Type, pv *gogen.Element) bool {
+	if pv != nil {
+		if t, ok := T.(*types.Pointer); ok {
+			if tn, ok := t.Elem().(*types.Named); ok {
+				if v, ok := V.(*types.Pointer); ok {
+					if o, ok := v.Elem().Underlying().(*types.Struct); ok {
+						return embeddedFieldCast(o, tn, pv, visitedT{})
+					}
+				}
+			}
+		}
+	}
+	return false
+}
+
 const (
 	defaultGoFile  = ""
 	skippingGoFile = "_skip"
@@ -532,6 +594,7 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gogen.Packag
 		NoSkipConstant:  conf.NoSkipConstant,
 		PkgPathIox:      osxPkgPath,
 		DbgPositioner:   interp,
+		CanImplicitCast: implicitCast,
 	}
 	var rec *goxRecorder
 	if conf.Recorder != nil {



Test patch:
diff --git a/cl/_testgop/autoconv-2439/out.go b/cl/_testgop/autoconv-2439/out.go
new file mode 100644
index 00000000..9d01116b
--- /dev/null
+++ b/cl/_testgop/autoconv-2439/out.go
@@ -0,0 +1,22 @@
+package main
+
+type BasePtr struct {
+}
+type Base struct {
+	*BasePtr
+}
+type T struct {
+	Base
+}
+
+func Walk(p *Base) {
+}
+func WalkPtr(p *BasePtr) {
+}
+func f() *T {
+	return &T{}
+}
+func main() {
+	Walk(&new(T).Base)
+	WalkPtr(f().BasePtr)
+}
diff --git a/cl/builtin_test.go b/cl/builtin_test.go
index 71bb9200..cc197d2d 100644
--- a/cl/builtin_test.go
+++ b/cl/builtin_test.go
@@ -39,6 +39,12 @@ func getGoxConf() *gogen.Config {
 	return &gogen.Config{Fset: fset, Importer: imp}
 }
 
+func TestEmbeddedFieldCast(t *testing.T) {
+	var o = new(types.Struct)
+	embeddedFieldCast(o, nil, nil, visitedT{o: none{}})
+	embeddedFieldCast(o, nil, nil, visitedT{})
+}
+
 func TestNonClosure(t *testing.T) {
 	tn := types.NewTypeName(0, nil, "a", nil)
 	if !nonClosure(types.NewNamed(tn, types.Typ[types.Int], nil)) {