]> Cypherpunks repositories - gostls13.git/commitdiff
go/types: update interface receivers after substituting
authorRobert Findley <rfindley@google.com>
Thu, 27 Jan 2022 01:50:51 +0000 (20:50 -0500)
committerRobert Findley <rfindley@google.com>
Thu, 27 Jan 2022 21:11:21 +0000 (21:11 +0000)
Interface method receivers are synthetic: they record either the
interface type or the the defined type for which they are the RHS of the
type declaration. When instantiating, we need to update these receivers
accordingly.

Fixes #50839

Change-Id: Icd8e1a2817b0135059d25d034b01b0ff5207641f
Reviewed-on: https://go-review.googlesource.com/c/go/+/381174
Trust: Robert Findley <rfindley@google.com>
Run-TryBot: Robert Findley <rfindley@google.com>
Reviewed-by: Robert Griesemer <gri@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>

src/cmd/compile/internal/types2/api_test.go
src/cmd/compile/internal/types2/named.go
src/cmd/compile/internal/types2/subst.go
src/go/types/api_test.go
src/go/types/named.go
src/go/types/subst.go

index b54f84dde0b75dbf0c19cddb42a1dcd38c6439f1..80e998ebeea3a2ec4f26b6a56f8ae39171e2cbc9 100644 (file)
@@ -697,6 +697,19 @@ func TestUsesInfo(t *testing.T) {
                // Uses of methods are uses of the instantiated method.
                {`package m0; type N[A any] int; func (r N[B]) m() { r.n() }; func (N[C]) n() {}`, `n`, `func (m0.N[B]).n()`},
                {`package m1; type N[A any] int; func (r N[B]) m() { }; var f = N[int].m`, `m`, `func (m1.N[int]).m()`},
+               {`package m2; func _[A any](v interface{ m() A }) { v.m() }`, `m`, `func (interface).m() A`},
+               {`package m3; func f[A any]() interface{ m() A } { return nil }; var _ = f[int]().m()`, `m`, `func (interface).m() int`},
+               {`package m4; type T[A any] func() interface{ m() A }; var x T[int]; var y = x().m`, `m`, `func (interface).m() int`},
+               {`package m5; type T[A any] interface{ m() A }; func _[B any](t T[B]) { t.m() }`, `m`, `func (m5.T[B]).m() B`},
+               {`package m6; type T[A any] interface{ m() }; func _[B any](t T[B]) { t.m() }`, `m`, `func (m6.T[B]).m()`},
+               {`package m7; type T[A any] interface{ m() A }; func _(t T[int]) { t.m() }`, `m`, `func (m7.T[int]).m() int`},
+               {`package m8; type T[A any] interface{ m() }; func _(t T[int]) { t.m() }`, `m`, `func (m8.T[int]).m()`},
+               {`package m9; type T[A any] interface{ m() }; func _(t T[int]) { _ = t.m }`, `m`, `func (m9.T[int]).m()`},
+               {
+                       `package m10; type E[A any] interface{ m() }; type T[B any] interface{ E[B]; n() }; func _(t T[int]) { t.m() }`,
+                       `m`,
+                       `func (m10.E[int]).m()`,
+               },
        }
 
        for _, test := range tests {
@@ -709,8 +722,10 @@ func TestUsesInfo(t *testing.T) {
                var use Object
                for id, obj := range info.Uses {
                        if id.Value == test.obj {
+                               if use != nil {
+                                       panic(fmt.Sprintf("multiple uses of %q", id.Value))
+                               }
                                use = obj
-                               break
                        }
                }
                if use == nil {
index ed33a9ddf7a73d75799b3edc02082ea6070d460e..5248893a4a4149b5bf593e2d1aec16a8425fed9e 100644 (file)
@@ -351,13 +351,30 @@ func expandNamed(ctxt *Context, n *Named, instPos syntax.Pos) (tparams *TypePara
 
                smap := makeSubstMap(n.orig.tparams.list(), n.targs.list())
                underlying = n.check.subst(instPos, n.orig.underlying, smap, ctxt)
+               // If the underlying of n is an interface, we need to set the receiver of
+               // its methods accurately -- we set the receiver of interface methods on
+               // the RHS of a type declaration to the defined type.
+               if iface, _ := underlying.(*Interface); iface != nil {
+                       if methods, copied := replaceRecvType(iface.methods, n.orig, n); copied {
+                               // If the underlying doesn't actually use type parameters, it's possible
+                               // that it wasn't substituted. In this case we need to create a new
+                               // *Interface before modifying receivers.
+                               if iface == n.orig.underlying {
+                                       iface = &Interface{
+                                               embeddeds: iface.embeddeds,
+                                               complete:  iface.complete,
+                                               implicit:  iface.implicit, // should be false but be conservative
+                                       }
+                                       underlying = iface
+                               }
+                               iface.methods = methods
+                       }
+               }
        } else {
                underlying = Typ[Invalid]
        }
 
-       mlist := newLazyMethodList(n.orig.methods.Len())
-
-       return n.orig.tparams, underlying, mlist
+       return n.orig.tparams, underlying, newLazyMethodList(n.orig.methods.Len())
 }
 
 // safeUnderlying returns the underlying of typ without expanding instances, to
index 4108f6aa8542a9ea08a6de53e444a34b16c73b33..f2e8fecc05bbf49aae74a5316fe8a7ab44f0f8a4 100644 (file)
@@ -106,12 +106,24 @@ func (subst *subster) typ(typ Type) Type {
                return subst.tuple(t)
 
        case *Signature:
-               // TODO(gri) rethink the recv situation with respect to methods on parameterized types
-               // recv := subst.var_(t.recv) // TODO(gri) this causes a stack overflow - explain
+               // Preserve the receiver: it is handled during *Interface and *Named type
+               // substitution.
+               //
+               // Naively doing the substitution here can lead to an infinite recursion in
+               // the case where the receiver is an interface. For example, consider the
+               // following declaration:
+               //
+               //  type T[A any] struct { f interface{ m() } }
+               //
+               // In this case, the type of f is an interface that is itself the receiver
+               // type of all of its methods. Because we have no type name to break
+               // cycles, substituting in the recv results in an infinite loop of
+               // recv->interface->recv->interface->...
                recv := t.recv
+
                params := subst.tuple(t.params)
                results := subst.tuple(t.results)
-               if recv != t.recv || params != t.params || results != t.results {
+               if params != t.params || results != t.results {
                        return &Signature{
                                rparams: t.rparams,
                                // TODO(gri) why can't we nil out tparams here, rather than in instantiate?
@@ -137,7 +149,21 @@ func (subst *subster) typ(typ Type) Type {
                methods, mcopied := subst.funcList(t.methods)
                embeddeds, ecopied := subst.typeList(t.embeddeds)
                if mcopied || ecopied {
-                       iface := &Interface{methods: methods, embeddeds: embeddeds, implicit: t.implicit, complete: t.complete}
+                       iface := &Interface{embeddeds: embeddeds, implicit: t.implicit, complete: t.complete}
+                       // If we've changed the interface type, we may need to replace its
+                       // receiver if the receiver type is the original interface. Receivers of
+                       // *Named type are replaced during named type expansion.
+                       //
+                       // Notably, it's possible to reach here and not create a new *Interface,
+                       // even though the receiver type may be parameterized. For example:
+                       //
+                       //  type T[P any] interface{ m() }
+                       //
+                       // In this case the interface will not be substituted here, because its
+                       // method signatures do not depend on the type parameter P, but we still
+                       // need to create new interface methods to hold the instantiated
+                       // receiver. This is handled by expandNamed.
+                       iface.methods, _ = replaceRecvType(methods, t, iface)
                        return iface
                }
 
@@ -349,3 +375,31 @@ func (subst *subster) termlist(in []*Term) (out []*Term, copied bool) {
        }
        return
 }
+
+// replaceRecvType updates any function receivers that have type old to have
+// type new. It does not modify the input slice; if modifications are required,
+// the input slice and any affected signatures will be copied before mutating.
+//
+// The resulting out slice contains the updated functions, and copied reports
+// if anything was modified.
+func replaceRecvType(in []*Func, old, new Type) (out []*Func, copied bool) {
+       out = in
+       for i, method := range in {
+               sig := method.Type().(*Signature)
+               if sig.recv != nil && sig.recv.Type() == old {
+                       if !copied {
+                               // Allocate a new methods slice before mutating for the first time.
+                               // This is defensive, as we may share methods across instantiations of
+                               // a given interface type if they do not get substituted.
+                               out = make([]*Func, len(in))
+                               copy(out, in)
+                               copied = true
+                       }
+                       newsig := *sig
+                       sig = &newsig
+                       sig.recv = NewVar(sig.recv.pos, sig.recv.pkg, "", new)
+                       out[i] = NewFunc(method.pos, method.pkg, method.name, sig)
+               }
+       }
+       return
+}
index 5c61e54360f1bd1f68515f76d175b9da784fd309..5f4d48472cec39c9fb5be92c4547e73dc1a83319 100644 (file)
@@ -689,6 +689,19 @@ func TestUsesInfo(t *testing.T) {
                // Uses of methods are uses of the instantiated method.
                {`package generic_m0; type N[A any] int; func (r N[B]) m() { r.n() }; func (N[C]) n() {}`, `n`, `func (generic_m0.N[B]).n()`},
                {`package generic_m1; type N[A any] int; func (r N[B]) m() { }; var f = N[int].m`, `m`, `func (generic_m1.N[int]).m()`},
+               {`package generic_m2; func _[A any](v interface{ m() A }) { v.m() }`, `m`, `func (interface).m() A`},
+               {`package generic_m3; func f[A any]() interface{ m() A } { return nil }; var _ = f[int]().m()`, `m`, `func (interface).m() int`},
+               {`package generic_m4; type T[A any] func() interface{ m() A }; var x T[int]; var y = x().m`, `m`, `func (interface).m() int`},
+               {`package generic_m5; type T[A any] interface{ m() A }; func _[B any](t T[B]) { t.m() }`, `m`, `func (generic_m5.T[B]).m() B`},
+               {`package generic_m6; type T[A any] interface{ m() }; func _[B any](t T[B]) { t.m() }`, `m`, `func (generic_m6.T[B]).m()`},
+               {`package generic_m7; type T[A any] interface{ m() A }; func _(t T[int]) { t.m() }`, `m`, `func (generic_m7.T[int]).m() int`},
+               {`package generic_m8; type T[A any] interface{ m() }; func _(t T[int]) { t.m() }`, `m`, `func (generic_m8.T[int]).m()`},
+               {`package generic_m9; type T[A any] interface{ m() }; func _(t T[int]) { _ = t.m }`, `m`, `func (generic_m9.T[int]).m()`},
+               {
+                       `package generic_m10; type E[A any] interface{ m() }; type T[B any] interface{ E[B]; n() }; func _(t T[int]) { t.m() }`,
+                       `m`,
+                       `func (generic_m10.E[int]).m()`,
+               },
        }
 
        for _, test := range tests {
@@ -701,8 +714,10 @@ func TestUsesInfo(t *testing.T) {
                var use Object
                for id, obj := range info.Uses {
                        if id.Name == test.obj {
+                               if use != nil {
+                                       panic(fmt.Sprintf("multiple uses of %q", id.Name))
+                               }
                                use = obj
-                               break
                        }
                }
                if use == nil {
index a9d1eab24b49c7593daab8067c67fe0ec4273c41..28db26014f34de3b651577d555d8979686387003 100644 (file)
@@ -353,13 +353,30 @@ func expandNamed(ctxt *Context, n *Named, instPos token.Pos) (tparams *TypeParam
 
                smap := makeSubstMap(n.orig.tparams.list(), n.targs.list())
                underlying = n.check.subst(instPos, n.orig.underlying, smap, ctxt)
+               // If the underlying of n is an interface, we need to set the receiver of
+               // its methods accurately -- we set the receiver of interface methods on
+               // the RHS of a type declaration to the defined type.
+               if iface, _ := underlying.(*Interface); iface != nil {
+                       if methods, copied := replaceRecvType(iface.methods, n.orig, n); copied {
+                               // If the underlying doesn't actually use type parameters, it's possible
+                               // that it wasn't substituted. In this case we need to create a new
+                               // *Interface before modifying receivers.
+                               if iface == n.orig.underlying {
+                                       iface = &Interface{
+                                               embeddeds: iface.embeddeds,
+                                               complete:  iface.complete,
+                                               implicit:  iface.implicit, // should be false but be conservative
+                                       }
+                                       underlying = iface
+                               }
+                               iface.methods = methods
+                       }
+               }
        } else {
                underlying = Typ[Invalid]
        }
 
-       mlist := newLazyMethodList(n.orig.methods.Len())
-
-       return n.orig.tparams, underlying, mlist
+       return n.orig.tparams, underlying, newLazyMethodList(n.orig.methods.Len())
 }
 
 // safeUnderlying returns the underlying of typ without expanding instances, to
index b7e3b127798b4fbc78918c9a6c152462695345c3..0cce46ac46f0e7e66d7d1aac973057a9db06e387 100644 (file)
@@ -106,12 +106,24 @@ func (subst *subster) typ(typ Type) Type {
                return subst.tuple(t)
 
        case *Signature:
-               // TODO(gri) rethink the recv situation with respect to methods on parameterized types
-               // recv := subst.var_(t.recv) // TODO(gri) this causes a stack overflow - explain
+               // Preserve the receiver: it is handled during *Interface and *Named type
+               // substitution.
+               //
+               // Naively doing the substitution here can lead to an infinite recursion in
+               // the case where the receiver is an interface. For example, consider the
+               // following declaration:
+               //
+               //  type T[A any] struct { f interface{ m() } }
+               //
+               // In this case, the type of f is an interface that is itself the receiver
+               // type of all of its methods. Because we have no type name to break
+               // cycles, substituting in the recv results in an infinite loop of
+               // recv->interface->recv->interface->...
                recv := t.recv
+
                params := subst.tuple(t.params)
                results := subst.tuple(t.results)
-               if recv != t.recv || params != t.params || results != t.results {
+               if params != t.params || results != t.results {
                        return &Signature{
                                rparams: t.rparams,
                                // TODO(rFindley) why can't we nil out tparams here, rather than in instantiate?
@@ -137,7 +149,21 @@ func (subst *subster) typ(typ Type) Type {
                methods, mcopied := subst.funcList(t.methods)
                embeddeds, ecopied := subst.typeList(t.embeddeds)
                if mcopied || ecopied {
-                       iface := &Interface{methods: methods, embeddeds: embeddeds, implicit: t.implicit, complete: t.complete}
+                       iface := &Interface{embeddeds: embeddeds, implicit: t.implicit, complete: t.complete}
+                       // If we've changed the interface type, we may need to replace its
+                       // receiver if the receiver type is the original interface. Receivers of
+                       // *Named type are replaced during named type expansion.
+                       //
+                       // Notably, it's possible to reach here and not create a new *Interface,
+                       // even though the receiver type may be parameterized. For example:
+                       //
+                       //  type T[P any] interface{ m() }
+                       //
+                       // In this case the interface will not be substituted here, because its
+                       // method signatures do not depend on the type parameter P, but we still
+                       // need to create new interface methods to hold the instantiated
+                       // receiver. This is handled by expandNamed.
+                       iface.methods, _ = replaceRecvType(methods, t, iface)
                        return iface
                }
 
@@ -349,3 +375,31 @@ func (subst *subster) termlist(in []*Term) (out []*Term, copied bool) {
        }
        return
 }
+
+// replaceRecvType updates any function receivers that have type old to have
+// type new. It does not modify the input slice; if modifications are required,
+// the input slice and any affected signatures will be copied before mutating.
+//
+// The resulting out slice contains the updated functions, and copied reports
+// if anything was modified.
+func replaceRecvType(in []*Func, old, new Type) (out []*Func, copied bool) {
+       out = in
+       for i, method := range in {
+               sig := method.Type().(*Signature)
+               if sig.recv != nil && sig.recv.Type() == old {
+                       if !copied {
+                               // Allocate a new methods slice before mutating for the first time.
+                               // This is defensive, as we may share methods across instantiations of
+                               // a given interface type if they do not get substituted.
+                               out = make([]*Func, len(in))
+                               copy(out, in)
+                               copied = true
+                       }
+                       newsig := *sig
+                       sig = &newsig
+                       sig.recv = NewVar(sig.recv.pos, sig.recv.pkg, "", new)
+                       out[i] = NewFunc(method.pos, method.pkg, method.name, sig)
+               }
+       }
+       return
+}