Skip to content

Commit d1fd3eb

Browse files
committed
cmd/compile/internal/types2: instantiate methods when instantiating Named types
This is a port of CL 349412 from go/types to types2 with minor adjustments for types2 names, plus CL 350143 (slightly simplified) to make sure we always get a new signature in instantiated methods, plus CL 350810 to take care of pointer receivers. It also contains adjustments to the compiler (provided by Dan Scales) make it work with the types2 changes. Change-Id: Ia683a3a8adba3c369701c411d786092f02e77efe Reviewed-on: https://go-review.googlesource.com/c/go/+/349998 Trust: Robert Griesemer <[email protected]> Run-TryBot: Robert Griesemer <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Robert Findley <[email protected]>
1 parent 2f87b9c commit d1fd3eb

File tree

10 files changed

+182
-90
lines changed

10 files changed

+182
-90
lines changed

src/cmd/compile/internal/noder/types.go

+8-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ func (g *irgen) typ(typ types2.Type) *types.Type {
4242
l := len(g.typesToFinalize)
4343
info := g.typesToFinalize[l-1]
4444
g.typesToFinalize = g.typesToFinalize[:l-1]
45+
types.DeferCheckSize()
4546
g.fillinMethods(info.typ, info.ntyp)
47+
types.ResumeCheckSize()
4648
}
4749
return res
4850
}
@@ -283,15 +285,20 @@ func (g *irgen) fillinMethods(typ *types2.Named, ntyp *types.Type) {
283285
m := typ.Method(i)
284286
recvType := deref2(types2.AsSignature(m.Type()).Recv().Type())
285287
var meth *ir.Name
288+
imported := false
286289
if m.Pkg() != g.self {
287290
// Imported methods cannot be loaded by name (what
288291
// g.obj() does) - they must be loaded via their
289292
// type.
290293
meth = g.obj(recvType.(*types2.Named).Obj()).Type().Methods().Index(i).Nname.(*ir.Name)
294+
// XXX Because Obj() returns the object of the base generic
295+
// type, we have to still do the method translation below.
296+
imported = true
291297
} else {
292298
meth = g.obj(m)
293299
}
294-
if recvType != types2.Type(typ) {
300+
assert(recvType == types2.Type(typ))
301+
if imported {
295302
// Unfortunately, meth is the type of the method of the
296303
// generic type, so we have to do a substitution to get
297304
// the name/type of the method of the instantiated type,

src/cmd/compile/internal/types2/call.go

-51
Original file line numberDiff line numberDiff line change
@@ -528,58 +528,7 @@ func (check *Checker) selector(x *operand, e *syntax.SelectorExpr) {
528528

529529
// methods may not have a fully set up signature yet
530530
if m, _ := obj.(*Func); m != nil {
531-
// check.dump("### found method %s", m)
532531
check.objDecl(m, nil)
533-
// If m has a parameterized receiver type, infer the type arguments from
534-
// the actual receiver provided and then substitute the type parameters in
535-
// the signature accordingly.
536-
// TODO(gri) factor this code out
537-
sig := m.typ.(*Signature)
538-
if sig.RecvTypeParams().Len() > 0 {
539-
// For inference to work, we must use the receiver type
540-
// matching the receiver in the actual method declaration.
541-
// If the method is embedded, the matching receiver is the
542-
// embedded struct or interface that declared the method.
543-
// Traverse the embedding to find that type (issue #44688).
544-
recv := x.typ
545-
for i := 0; i < len(index)-1; i++ {
546-
// The embedded type is either a struct or a pointer to
547-
// a struct except for the last one (which we don't need).
548-
recv = asStruct(derefStructPtr(recv)).Field(index[i]).typ
549-
}
550-
//check.dump("### recv = %s", recv)
551-
//check.dump("### method = %s rparams = %s tparams = %s", m, sig.rparams, sig.tparams)
552-
// The method may have a pointer receiver, but the actually provided receiver
553-
// may be a (hopefully addressable) non-pointer value, or vice versa. Here we
554-
// only care about inferring receiver type parameters; to make the inference
555-
// work, match up pointer-ness of receiver and argument.
556-
if ptrRecv := isPointer(sig.recv.typ); ptrRecv != isPointer(recv) {
557-
if ptrRecv {
558-
recv = NewPointer(recv)
559-
} else {
560-
recv = recv.(*Pointer).base
561-
}
562-
}
563-
// Disable reporting of errors during inference below. If we're unable to infer
564-
// the receiver type arguments here, the receiver must be be otherwise invalid
565-
// and an error has been reported elsewhere.
566-
arg := operand{mode: variable, expr: x.expr, typ: recv}
567-
targs := check.infer(m.pos, sig.RecvTypeParams().list(), nil, NewTuple(sig.recv), []*operand{&arg}, false /* no error reporting */)
568-
//check.dump("### inferred targs = %s", targs)
569-
if targs == nil {
570-
// We may reach here if there were other errors (see issue #40056).
571-
goto Error
572-
}
573-
// Don't modify m. Instead - for now - make a copy of m and use that instead.
574-
// (If we modify m, some tests will fail; possibly because the m is in use.)
575-
// TODO(gri) investigate and provide a correct explanation here
576-
copy := *m
577-
copy.typ = check.subst(e.Pos(), m.typ, makeSubstMap(sig.RecvTypeParams().list(), targs), nil)
578-
obj = &copy
579-
}
580-
// TODO(gri) we also need to do substitution for parameterized interface methods
581-
// (this breaks code in testdata/linalg.go2 at the moment)
582-
// 12/20/2019: Is this TODO still correct?
583532
}
584533

585534
if x.mode == typexpr {

src/cmd/compile/internal/types2/decl.go

+6
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ func (check *Checker) objDecl(obj Object, def *Named) {
6666
}()
6767
}
6868

69+
// Funcs with m.instRecv set have not yet be completed. Complete them now
70+
// so that they have a type when objDecl exits.
71+
if m, _ := obj.(*Func); m != nil && m.instRecv != nil {
72+
check.completeMethod(check.conf.Environment, m)
73+
}
74+
6975
// Checking the declaration of obj means inferring its type
7076
// (and possibly its value, for constants).
7177
// An object's type (and thus the object) may be in one of

src/cmd/compile/internal/types2/infer.go

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const useConstraintTypeInference = true
2929
//
3030
// Constraint type inference is used after each step to expand the set of type arguments.
3131
//
32+
// TODO(gri): remove the report parameter: is no longer needed.
3233
func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type, params *Tuple, args []*operand, report bool) (result []Type) {
3334
if debug {
3435
defer func() {

src/cmd/compile/internal/types2/instantiate_test.go

+82
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package types2_test
55

66
import (
77
. "cmd/compile/internal/types2"
8+
"strings"
89
"testing"
910
)
1011

@@ -60,3 +61,84 @@ func TestInstantiateNonEquality(t *testing.T) {
6061
t.Errorf("instance from pkg1 (%s) is identical to instance from pkg2 (%s)", res1, res2)
6162
}
6263
}
64+
65+
func TestMethodInstantiation(t *testing.T) {
66+
const prefix = genericPkg + `p
67+
68+
type T[P any] struct{}
69+
70+
var X T[int]
71+
72+
`
73+
tests := []struct {
74+
decl string
75+
want string
76+
}{
77+
{"func (r T[P]) m() P", "func (T[int]).m() int"},
78+
{"func (r T[P]) m(P)", "func (T[int]).m(int)"},
79+
{"func (r *T[P]) m(P)", "func (*T[int]).m(int)"},
80+
{"func (r T[P]) m() T[P]", "func (T[int]).m() T[int]"},
81+
{"func (r T[P]) m(T[P])", "func (T[int]).m(T[int])"},
82+
{"func (r T[P]) m(T[P], P, string)", "func (T[int]).m(T[int], int, string)"},
83+
{"func (r T[P]) m(T[P], T[string], T[int])", "func (T[int]).m(T[int], T[string], T[int])"},
84+
}
85+
86+
for _, test := range tests {
87+
src := prefix + test.decl
88+
pkg, err := pkgFor(".", src, nil)
89+
if err != nil {
90+
t.Fatal(err)
91+
}
92+
typ := NewPointer(pkg.Scope().Lookup("X").Type())
93+
obj, _, _ := LookupFieldOrMethod(typ, false, pkg, "m")
94+
m, _ := obj.(*Func)
95+
if m == nil {
96+
t.Fatalf(`LookupFieldOrMethod(%s, "m") = %v, want func m`, typ, obj)
97+
}
98+
if got := ObjectString(m, RelativeTo(pkg)); got != test.want {
99+
t.Errorf("instantiated %q, want %q", got, test.want)
100+
}
101+
}
102+
}
103+
104+
func TestImmutableSignatures(t *testing.T) {
105+
const src = genericPkg + `p
106+
107+
type T[P any] struct{}
108+
109+
func (T[P]) m() {}
110+
111+
var _ T[int]
112+
`
113+
pkg, err := pkgFor(".", src, nil)
114+
if err != nil {
115+
t.Fatal(err)
116+
}
117+
typ := pkg.Scope().Lookup("T").Type().(*Named)
118+
obj, _, _ := LookupFieldOrMethod(typ, false, pkg, "m")
119+
if obj == nil {
120+
t.Fatalf(`LookupFieldOrMethod(%s, "m") = %v, want func m`, typ, obj)
121+
}
122+
123+
// Verify that the original method is not mutated by instantiating T (this
124+
// bug manifested when subst did not return a new signature).
125+
want := "func (T[P]).m()"
126+
if got := stripAnnotations(ObjectString(obj, RelativeTo(pkg))); got != want {
127+
t.Errorf("instantiated %q, want %q", got, want)
128+
}
129+
}
130+
131+
// Copied from errors.go.
132+
func stripAnnotations(s string) string {
133+
var b strings.Builder
134+
for _, r := range s {
135+
// strip #'s and subscript digits
136+
if r < '₀' || '₀'+10 <= r { // '₀' == U+2080
137+
b.WriteRune(r)
138+
}
139+
}
140+
if b.Len() < len(s) {
141+
return b.String()
142+
}
143+
return s
144+
}

src/cmd/compile/internal/types2/lookup.go

-22
Original file line numberDiff line numberDiff line change
@@ -344,8 +344,6 @@ func (check *Checker) missingMethod(V Type, T *Interface, static bool) (method,
344344
}
345345

346346
// A concrete type implements T if it implements all methods of T.
347-
Vd, _ := deref(V)
348-
Vn := asNamed(Vd)
349347
for _, m := range T.typeSet().methods {
350348
// TODO(gri) should this be calling lookupFieldOrMethod instead (and why not)?
351349
obj, _, _ := lookupFieldOrMethod(V, false, m.pkg, m.name)
@@ -380,26 +378,6 @@ func (check *Checker) missingMethod(V Type, T *Interface, static bool) (method,
380378
panic("method with type parameters")
381379
}
382380

383-
// If V is a (instantiated) generic type, its methods are still
384-
// parameterized using the original (declaration) receiver type
385-
// parameters (subst simply copies the existing method list, it
386-
// does not instantiate the methods).
387-
// In order to compare the signatures, substitute the receiver
388-
// type parameters of ftyp with V's instantiation type arguments.
389-
// This lazily instantiates the signature of method f.
390-
if Vn != nil && Vn.TypeParams().Len() > 0 {
391-
// Be careful: The number of type arguments may not match
392-
// the number of receiver parameters. If so, an error was
393-
// reported earlier but the length discrepancy is still
394-
// here. Exit early in this case to prevent an assertion
395-
// failure in makeSubstMap.
396-
// TODO(gri) Can we avoid this check by fixing the lengths?
397-
if len(ftyp.RecvTypeParams().list()) != Vn.targs.Len() {
398-
return
399-
}
400-
ftyp = check.subst(nopos, ftyp, makeSubstMap(ftyp.RecvTypeParams().list(), Vn.targs.list()), nil).(*Signature)
401-
}
402-
403381
// If the methods have type parameters we don't care whether they
404382
// are the same or not, as long as they match up. Use unification
405383
// to see if they can be made to match.

src/cmd/compile/internal/types2/named.go

+80-11
Original file line numberDiff line numberDiff line change
@@ -221,16 +221,17 @@ func (n *Named) setUnderlying(typ Type) {
221221

222222
// expandNamed ensures that the underlying type of n is instantiated.
223223
// The underlying type will be Typ[Invalid] if there was an error.
224-
func expandNamed(env *Environment, n *Named, instPos syntax.Pos) (*TypeParamList, Type, []*Func) {
224+
func expandNamed(env *Environment, n *Named, instPos syntax.Pos) (tparams *TypeParamList, underlying Type, methods []*Func) {
225225
n.orig.resolve(env)
226226

227-
var u Type
228-
if n.check.validateTArgLen(instPos, n.orig.tparams.Len(), n.targs.Len()) {
227+
check := n.check
228+
229+
if check.validateTArgLen(instPos, n.orig.tparams.Len(), n.targs.Len()) {
229230
// TODO(rfindley): handling an optional Checker and Environment here (and
230231
// in subst) feels overly complicated. Can we simplify?
231232
if env == nil {
232-
if n.check != nil {
233-
env = n.check.conf.Environment
233+
if check != nil {
234+
env = check.conf.Environment
234235
} else {
235236
// If we're instantiating lazily, we might be outside the scope of a
236237
// type-checking pass. In that case we won't have a pre-existing
@@ -239,16 +240,84 @@ func expandNamed(env *Environment, n *Named, instPos syntax.Pos) (*TypeParamList
239240
env = NewEnvironment()
240241
}
241242
h := env.TypeHash(n.orig, n.targs.list())
242-
// add the instance to the environment to avoid infinite recursion.
243-
// addInstance may return a different, existing instance, but we
244-
// shouldn't return that instance from expand.
243+
// ensure that an instance is recorded for h to avoid infinite recursion.
245244
env.typeForHash(h, n)
246245
}
247-
u = n.check.subst(instPos, n.orig.underlying, makeSubstMap(n.orig.tparams.list(), n.targs.list()), env)
246+
smap := makeSubstMap(n.orig.tparams.list(), n.targs.list())
247+
underlying = n.check.subst(instPos, n.orig.underlying, smap, env)
248+
for i := 0; i < n.orig.NumMethods(); i++ {
249+
origm := n.orig.Method(i)
250+
251+
// During type checking origm may not have a fully set up type, so defer
252+
// instantiation of its signature until later.
253+
m := NewFunc(origm.pos, origm.pkg, origm.name, nil)
254+
m.hasPtrRecv = ptrRecv(origm)
255+
// Setting instRecv here allows us to complete later (we need the
256+
// instRecv to get targs and the original method).
257+
m.instRecv = n
258+
259+
methods = append(methods, m)
260+
}
261+
} else {
262+
underlying = Typ[Invalid]
263+
}
264+
265+
// Methods should not escape the type checker API without being completed. If
266+
// we're in the context of a type checking pass, we need to defer this until
267+
// later (not all methods may have types).
268+
completeMethods := func() {
269+
for _, m := range methods {
270+
if m.instRecv != nil {
271+
check.completeMethod(env, m)
272+
}
273+
}
274+
}
275+
if check != nil {
276+
check.later(completeMethods)
277+
} else {
278+
completeMethods()
279+
}
280+
281+
return n.orig.tparams, underlying, methods
282+
}
283+
284+
func (check *Checker) completeMethod(env *Environment, m *Func) {
285+
assert(m.instRecv != nil)
286+
rbase := m.instRecv
287+
m.instRecv = nil
288+
m.setColor(black)
289+
290+
assert(rbase.TypeArgs().Len() > 0)
291+
292+
// Look up the original method.
293+
_, orig := lookupMethod(rbase.orig.methods, rbase.obj.pkg, m.name)
294+
assert(orig != nil)
295+
if check != nil {
296+
check.objDecl(orig, nil)
297+
}
298+
origSig := orig.typ.(*Signature)
299+
if origSig.RecvTypeParams().Len() != rbase.targs.Len() {
300+
m.typ = origSig // or new(Signature), but we can't use Typ[Invalid]: Funcs must have Signature type
301+
return // error reported elsewhere
302+
}
303+
304+
smap := makeSubstMap(origSig.RecvTypeParams().list(), rbase.targs.list())
305+
sig := check.subst(orig.pos, origSig, smap, env).(*Signature)
306+
if sig == origSig {
307+
// No substitution occurred, but we still need to create a new signature to
308+
// hold the instantiated receiver.
309+
copy := *origSig
310+
sig = &copy
311+
}
312+
var rtyp Type
313+
if ptrRecv(m) {
314+
rtyp = NewPointer(rbase)
248315
} else {
249-
u = Typ[Invalid]
316+
rtyp = rbase
250317
}
251-
return n.orig.tparams, u, n.orig.methods
318+
sig.recv = NewParam(origSig.recv.pos, origSig.recv.pkg, origSig.recv.name, rtyp)
319+
320+
m.typ = sig
252321
}
253322

254323
// safeUnderlying returns the underlying of typ without expanding instances, to

src/cmd/compile/internal/types2/object.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,8 @@ func (*Var) isDependency() {} // a variable may be a dependency of an initializa
363363
// An abstract method may belong to many interfaces due to embedding.
364364
type Func struct {
365365
object
366-
hasPtrRecv bool // only valid for methods that don't have a type yet
366+
instRecv *Named // if non-nil, the receiver type for an incomplete instance method
367+
hasPtrRecv bool // only valid for methods that don't have a type yet
367368
}
368369

369370
// NewFunc returns a new function with the given signature, representing
@@ -374,7 +375,7 @@ func NewFunc(pos syntax.Pos, pkg *Package, name string, sig *Signature) *Func {
374375
if sig != nil {
375376
typ = sig
376377
}
377-
return &Func{object{nil, pos, pkg, name, typ, 0, colorFor(typ), nopos}, false}
378+
return &Func{object{nil, pos, pkg, name, typ, 0, colorFor(typ), nopos}, nil, false}
378379
}
379380

380381
// FullName returns the package- or receiver-type-qualified name of

src/cmd/compile/internal/types2/sizeof_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func TestSizeof(t *testing.T) {
4141
{Const{}, 64, 104},
4242
{TypeName{}, 56, 88},
4343
{Var{}, 60, 96},
44-
{Func{}, 60, 96},
44+
{Func{}, 64, 104},
4545
{Label{}, 60, 96},
4646
{Builtin{}, 60, 96},
4747
{Nil{}, 56, 88},

src/cmd/compile/internal/types2/subst.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,7 @@ func (subst *subster) typ(typ Type) Type {
125125
if recv != t.recv || params != t.params || results != t.results {
126126
return &Signature{
127127
rparams: t.rparams,
128-
// TODO(gri) Why can't we nil out tparams here, rather than in
129-
// instantiate above?
128+
// TODO(gri) why can't we nil out tparams here, rather than in instantiate?
130129
tparams: t.tparams,
131130
scope: t.scope,
132131
recv: recv,

0 commit comments

Comments
 (0)