1
0
mirror of https://github.com/golang/go synced 2024-11-11 19:21:37 -07:00

go/types: update interface receivers after substituting

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>
This commit is contained in:
Robert Findley 2022-01-26 20:50:51 -05:00
parent 1a2435c95f
commit f5fe5a4524
6 changed files with 188 additions and 16 deletions

View 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 {

View 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

View 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
}

View 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 {

View 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

View 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
}