]> Cypherpunks repositories - gostls13.git/commitdiff
[dev.typeparams] cmd/compile: fixing case where type arg is an interface
authorDan Scales <danscales@google.com>
Wed, 4 Aug 2021 21:25:01 +0000 (14:25 -0700)
committerDan Scales <danscales@google.com>
Thu, 5 Aug 2021 17:33:41 +0000 (17:33 +0000)
In this case, we can't use an itab for doing a bound call, since we're
converting from an interface to an interface. We do a static or dynamic
type assert in new function assertToBound().

The dynamic type assert in assertToBound() is only needed if a bound is
parameterized. In that case, we must do a dynamic type assert, and
therefore need a dictionary entry for the type bound (see change in
getGfInfo). I'm not sure if we can somehow limit this case, since using
an interface as a type arg AND having the type bound of the type
arg be parameterized is a very unlikely case.

Had to add the TUNION case to parameterizedBy1() (which is only used for
extra checking).

Added a bunch of these test cases to 13.go, which now passes.

Change-Id: Ic22eed637fa879b5bbb46d36b40aaad6f90b9d01
Reviewed-on: https://go-review.googlesource.com/c/go/+/339898
Trust: Dan Scales <danscales@google.com>
Reviewed-by: Keith Randall <khr@golang.org>
src/cmd/compile/internal/noder/stencil.go
test/run.go
test/typeparam/mdempsky/13.go

index c006c4af446ebadb0bd3082d16f5878942c6114e..b2677d5a775e3d835c0bbc9b722e3ec75007a00a 100644 (file)
@@ -1181,7 +1181,7 @@ func (subst *subster) node(n ir.Node) ir.Node {
                                // The only dot on a shape type value are methods.
                                if mse.X.Op() == ir.OTYPE {
                                        // Method expression T.M
-                                       m = subst.g.buildClosure2(subst.newf, subst.info, m, x)
+                                       m = subst.g.buildClosure2(subst, m, x)
                                        // No need for transformDot - buildClosure2 has already
                                        // transformed to OCALLINTER/ODOTINTER.
                                } else {
@@ -1189,11 +1189,18 @@ func (subst *subster) node(n ir.Node) ir.Node {
                                        //  1) convert x to the bound interface
                                        //  2) call M on that interface
                                        gsrc := x.(*ir.SelectorExpr).X.Type()
-                                       dst := gsrc.Bound()
+                                       bound := gsrc.Bound()
+                                       dst := bound
                                        if dst.HasTParam() {
                                                dst = subst.ts.Typ(dst)
                                        }
-                                       mse.X = convertUsingDictionary(subst.info, subst.info.dictParam, m.Pos(), mse.X, x, dst, gsrc)
+                                       if src.IsInterface() {
+                                               // If type arg is an interface (unusual case),
+                                               // we do a type assert to the type bound.
+                                               mse.X = assertToBound(subst.info, subst.info.dictParam, m.Pos(), mse.X, bound, dst)
+                                       } else {
+                                               mse.X = convertUsingDictionary(subst.info, subst.info.dictParam, m.Pos(), mse.X, x, dst, gsrc)
+                                       }
                                        transformDot(mse, false)
                                }
                        } else {
@@ -1554,10 +1561,10 @@ func (g *irgen) getDictionarySym(gf *ir.Name, targs []*types.Type, isMeth bool)
                                                tparam := tmpse.X.Type()
                                                assert(tparam.IsTypeParam())
                                                recvType := targs[tparam.Index()]
-                                               if len(recvType.RParams()) == 0 {
+                                               if recvType.IsInterface() || len(recvType.RParams()) == 0 {
                                                        // No sub-dictionary entry is
                                                        // actually needed, since the
-                                                       // typeparam is not an
+                                                       // type arg is not an
                                                        // instantiated type that
                                                        // will have generic methods.
                                                        break
@@ -1686,8 +1693,14 @@ func (g *irgen) finalizeSyms() {
                        default:
                                base.Fatalf("itab entry with unknown op %s", n.Op())
                        }
-                       itabLsym := reflectdata.ITabLsym(srctype, dsttype)
-                       d.off = objw.SymPtr(lsym, d.off, itabLsym, 0)
+                       if srctype.IsInterface() {
+                               // No itab is wanted if src type is an interface. We
+                               // will use a type assert instead.
+                               d.off = objw.Uintptr(lsym, d.off, 0)
+                       } else {
+                               itabLsym := reflectdata.ITabLsym(srctype, dsttype)
+                               d.off = objw.SymPtr(lsym, d.off, itabLsym, 0)
+                       }
                }
 
                objw.Global(lsym, int32(d.off), obj.DUPOK|obj.RODATA)
@@ -1760,6 +1773,17 @@ func (g *irgen) getGfInfo(gn *ir.Name) *gfInfo {
                        info.tparams[i] = f.Type
                }
        }
+
+       for _, t := range info.tparams {
+               b := t.Bound()
+               if b.HasTParam() {
+                       // If a type bound is parameterized (unusual case), then we
+                       // may need its derived type to do a type assert when doing a
+                       // bound call for a type arg that is an interface.
+                       addType(&info, nil, b)
+               }
+       }
+
        for _, n := range gf.Dcl {
                addType(&info, n, n.Type())
        }
@@ -1950,6 +1974,15 @@ func parameterizedBy1(t *types.Type, params []*types.Type, visited map[*types.Ty
                types.TUINTPTR, types.TBOOL, types.TSTRING, types.TFLOAT32, types.TFLOAT64, types.TCOMPLEX64, types.TCOMPLEX128:
                return true
 
+       case types.TUNION:
+               for i := 0; i < t.NumTerms(); i++ {
+                       tt, _ := t.Term(i)
+                       if !parameterizedBy1(tt, params, visited) {
+                               return false
+                       }
+               }
+               return true
+
        default:
                base.Fatalf("bad type kind %+v", t)
                return true
@@ -2000,15 +2033,32 @@ func startClosure(pos src.XPos, outer *ir.Func, typ *types.Type) (*ir.Func, []*t
 
 }
 
+// assertToBound returns a new node that converts a node rcvr with interface type to
+// the 'dst' interface type.  bound is the unsubstituted form of dst.
+func assertToBound(info *instInfo, dictVar *ir.Name, pos src.XPos, rcvr ir.Node, bound, dst *types.Type) ir.Node {
+       if bound.HasTParam() {
+               ix := findDictType(info, bound)
+               assert(ix >= 0)
+               rt := getDictionaryType(info, dictVar, pos, ix)
+               rcvr = ir.NewDynamicTypeAssertExpr(pos, ir.ODYNAMICDOTTYPE, rcvr, rt)
+               typed(dst, rcvr)
+       } else {
+               rcvr = ir.NewTypeAssertExpr(pos, rcvr, nil)
+               typed(bound, rcvr)
+       }
+       return rcvr
+}
+
 // buildClosure2 makes a closure to implement a method expression m (generic form x)
 // which has a shape type as receiver. If the receiver is exactly a shape (i.e. from
-// a typeparam), then the body of the closure converts the first argument (the
-// receiver) to the interface bound type, and makes an interface call with the
-// remaining arguments.
+// a typeparam), then the body of the closure converts m.X (the receiver) to the
+// interface bound type, and makes an interface call with the remaining arguments.
 //
-// The returned closure is fully substituted and has already has any needed
+// The returned closure is fully substituted and has already had any needed
 // transformations done.
-func (g *irgen) buildClosure2(outer *ir.Func, info *instInfo, m, x ir.Node) ir.Node {
+func (g *irgen) buildClosure2(subst *subster, m, x ir.Node) ir.Node {
+       outer := subst.newf
+       info := subst.info
        pos := m.Pos()
        typ := m.Type() // type of the closure
 
@@ -2031,11 +2081,24 @@ func (g *irgen) buildClosure2(outer *ir.Func, info *instInfo, m, x ir.Node) ir.N
        rcvr := args[0]
        args = args[1:]
        assert(m.(*ir.SelectorExpr).X.Type().IsShape())
-       rcvr = convertUsingDictionary(info, dictVar, pos, rcvr, x, x.(*ir.SelectorExpr).X.Type().Bound(), x.(*ir.SelectorExpr).X.Type())
+       gsrc := x.(*ir.SelectorExpr).X.Type()
+       bound := gsrc.Bound()
+       dst := bound
+       if dst.HasTParam() {
+               dst = subst.ts.Typ(bound)
+       }
+       if m.(*ir.SelectorExpr).X.Type().IsInterface() {
+               // If type arg is an interface (unusual case), we do a type assert to
+               // the type bound.
+               rcvr = assertToBound(info, dictVar, pos, rcvr, bound, dst)
+       } else {
+               rcvr = convertUsingDictionary(info, dictVar, pos, rcvr, x, dst, gsrc)
+       }
        dot := ir.NewSelectorExpr(pos, ir.ODOTINTER, rcvr, x.(*ir.SelectorExpr).Sel)
        dot.Selection = typecheck.Lookdot1(dot, dot.Sel, dot.X.Type(), dot.X.Type().AllMethods(), 1)
 
-       typed(x.(*ir.SelectorExpr).Selection.Type, dot)
+       // Do a type substitution on the generic bound, in case it is parameterized.
+       typed(subst.ts.Typ(x.(*ir.SelectorExpr).Selection.Type), dot)
        innerCall = ir.NewCallExpr(pos, ir.OCALLINTER, dot, args)
        t := m.Type()
        if t.NumResults() == 0 {
index 4971043ab646be59466064fd6a1aec41dd3d17be..6296234d56588ff93a1d153414962c6c6d0a96cd 100644 (file)
@@ -2184,7 +2184,6 @@ var g3Failures = setOf(
        "typeparam/nested.go", // -G=3 doesn't support function-local types with generics
 
        "typeparam/mdempsky/4.go",  // -G=3 can't export functions with labeled breaks in loops
-       "typeparam/mdempsky/13.go", // problem with interface as as a type arg.
        "typeparam/mdempsky/15.go", // ICE in (*irgen).buildClosure
 )
 
index dc1d29bce1cb75e824810c478fd0f86d7fad03a7..b492774d3d849a285e74202d9f03a128733eadc5 100644 (file)
@@ -6,33 +6,79 @@
 
 package main
 
-type Mer interface{ M() }
+// Interface which will be used as a regular interface type and as a type bound.
+type Mer interface{
+       M()
+}
 
-func F[T Mer](expectPanic bool) {
-       defer func() {
-               err := recover()
-               if (err != nil) != expectPanic {
-                       print("FAIL: (", err, " != nil) != ", expectPanic, "\n")
-               }
-       }()
+// Interface that is a superset of Mer.
+type Mer2 interface {
+       M()
+       String() string
+}
 
-       var t T
+func F[T Mer](t T) {
        T.M(t)
+       t.M()
 }
 
 type MyMer int
 
 func (MyMer) M() {}
+func (MyMer) String() string {
+       return "aa"
+}
+
+// Parameterized interface
+type Abs[T any] interface {
+       Abs() T
+}
+
+func G[T Abs[U], U any](t T) {
+       T.Abs(t)
+       t.Abs()
+}
+
+type MyInt int
+func (m MyInt) Abs() MyInt {
+       if m < 0 {
+               return -m
+       }
+       return m
+}
+
+type Abs2 interface {
+       Abs() MyInt
+}
+
 
 func main() {
-       F[Mer](true)
-       F[struct{ Mer }](true)
-       F[*struct{ Mer }](true)
-
-       F[MyMer](false)
-       F[*MyMer](true)
-       F[struct{ MyMer }](false)
-       F[struct{ *MyMer }](true)
-       F[*struct{ MyMer }](true)
-       F[*struct{ *MyMer }](true)
+       mm := MyMer(3)
+       ms := struct{ Mer }{Mer: mm }
+
+       // Testing F with an interface type arg: Mer and Mer2
+       F[Mer](mm)
+       F[Mer2](mm)
+       F[struct{ Mer }](ms)
+       F[*struct{ Mer }](&ms)
+
+       ms2 := struct { MyMer }{MyMer: mm}
+       ms3 := struct { *MyMer }{MyMer: &mm}
+
+       // Testing F with a concrete type arg
+       F[MyMer](mm)
+       F[*MyMer](&mm)
+       F[struct{ MyMer }](ms2)
+       F[struct{ *MyMer }](ms3)
+       F[*struct{ MyMer }](&ms2)
+       F[*struct{ *MyMer }](&ms3)
+
+       // Testing G with a concrete type args
+       mi := MyInt(-3)
+       G[MyInt,MyInt](mi)
+
+       // Interface Abs[MyInt] holding an mi.
+       intMi := Abs[MyInt](mi)
+       // First type arg here is Abs[MyInt], an interface type.
+       G[Abs[MyInt],MyInt](intMi)
 }