go/types: various attempts at lazy substitution of methods for lookups (snapshot)

When instantiating a type, we should also instantiate its methods (at least their
signatures) so that they have the correct type when being looked up. Unfortunately,
methods may not yet be type-checked; and worse, when we type-check them, typing
their receiver means instantiating that type again which would require updating
(= instantiating) the methods...

Instead, we keep the original list of methods (*Funcs) with each (named) type
and only update the method signatures when looking them up. The problem with this
approach is that we need to know with which values to substitute the method (i.e.,
receiver) type parameters, and this is currently not working.

Change-Id: Ie1835919dc8bfb8161a6a9e3d784c3bbbeb958c4
This commit is contained in:
Robert Griesemer 2019-12-18 17:23:46 -08:00
parent cef8f5c6a4
commit a28b69c5d0
5 changed files with 122 additions and 35 deletions

View File

@ -798,13 +798,17 @@ func (check *Checker) funcDecl(obj *Func, decl *declInfo) {
// since the method is not a type, we get an error. If we have a parameterized
// receiver type, instantiating the receiver type leads to the instantiation of
// its methods, and we don't want a cycle error in that case.
// TODO(gri) review if this is correct for the latter case
// TODO(gri) review if this is correct and/or whether we still need this?
saved := obj.color_
obj.color_ = black
check.funcType(sig, fdecl.Recv, fdecl.Type)
obj.color_ = saved
if !fdecl.IsMethod() {
// TODO(gri) we should just use one tparam list - clean up
if fdecl.IsMethod() {
// needed for updating methods during instantiation bounds checking
sig.mtparams = obj.tparams
} else {
// only functions can have type parameters that need to be passed
// (the obj.tparams for methods are the receiver parameters)
// TODO(gri) remove the need for storing tparams in signatures

View File

@ -281,7 +281,7 @@ func MissingMethod(V Type, T *Interface, static bool) (method *Func, wrongType b
// signature, the existing method is returned as well.
// To improve error messages, also report the wrong signature
// when the method exists on *V instead of V.
func (check *Checker) missingMethod(V Type, T *Interface, static bool, update func(Type) Type) (method, wrongType *Func) {
func (check *Checker) missingMethod(V Type, T *Interface, static bool, update func(V Type, sig *Signature) *Signature) (method, wrongType *Func) {
check.completeInterface(token.NoPos, T)
// fast path for common case
@ -302,13 +302,7 @@ func (check *Checker) missingMethod(V Type, T *Interface, static bool, update fu
continue
}
// update (generic) method signatures before comparing them
ftyp := f.Type()
if update != nil {
ftyp = update(ftyp)
}
if !check.identical(ftyp, m.typ) {
if !check.identical(f.typ, m.typ) {
return m, f
}
}
@ -341,9 +335,9 @@ func (check *Checker) missingMethod(V Type, T *Interface, static bool, update fu
}
// update (generic) method signatures before comparing them
ftyp := f.typ
ftyp := f.typ.(*Signature)
if update != nil {
ftyp = update(ftyp)
ftyp = update(V, ftyp)
}
if !check.identical(ftyp, m.typ) {

View File

@ -76,22 +76,36 @@ func (check *Checker) instantiate(pos token.Pos, typ Type, targs []Type, poslist
// determine type parameter bound
iface := tpar.Interface()
// TODO(gri) document/explain why the substitution below is correct
//check.dump(">>> %s: iface before: %s", pos, iface)
// The type parameter bound is parameterized with the same type parameters
// as the instantiated type; before we can use it for bounds checking we
// need to instantiate it with the type arguments with which we instantiate
// the parameterized type.
iface = check.subst(pos, iface, tparams, targs).(*Interface)
//check.dump(">>> %s: iface after : %s", pos, iface)
// update targ method signatures
update := func(typ Type) Type {
// check.dump(">>> %s: sig before: %s (tparams = %s)", pos, typ, typ.(*Signature).tparams)
typ = check.subst(pos, typ, tparams, targs)
// check.dump(">>> %s: sig after : %s", pos, typ)
return typ
update := func(V Type, sig *Signature) *Signature {
V, _ = deref(V)
// check.dump(">>> %s: V = %s", pos, V)
var targs []Type
if vnamed, _ := V.(*Named); vnamed != nil {
targs = vnamed.targs
} else {
// nothing to do
return sig
}
// check.dump(">>> %s: targs = %s", pos, targs)
// check.dump(">>> %s: sig.mtparams = %s", pos, sig.mtparams)
// check.dump(">>> %s: sig before: %s, tparams = %s, targs = %s", pos, sig, sig.mtparams, targs)
sig = check.subst(pos, sig, sig.mtparams, targs).(*Signature)
// check.dump(">>> %s: sig after : %s", pos, sig)
return sig
}
// targ must implement iface (methods)
if m, _ := check.missingMethod(targ, iface, true, update); m != nil {
check.softErrorf(pos, "%s does not satisfy %s (missing method %s)", targ, tpar.bound, m)
// TODO(gri) needs to print updated name to avoid major confusion in error message!
// (print warning for now)
check.softErrorf(pos, "%s does not satisfy %s (warning: name not updated) = %s (missing method %s)", targ, tpar.bound, iface, m)
break
}
@ -126,7 +140,7 @@ func (check *Checker) instantiate(pos token.Pos, typ Type, targs []Type, poslist
return check.subst(pos, typ, tparams, targs)
}
// subst returns the type typ with its type parameters tparams replaced by
// subst returns the type typ with its type parameters tpars replaced by
// the corresponding type arguments targs, recursively.
func (check *Checker) subst(pos token.Pos, typ Type, tpars []*TypeName, targs []Type) Type {
assert(len(tpars) == len(targs))
@ -134,6 +148,19 @@ func (check *Checker) subst(pos token.Pos, typ Type, tpars []*TypeName, targs []
return typ
}
if debug {
for i, tpar := range tpars {
if tpar == nil {
check.dump("%s: tpars[%d] == nil", pos, i)
panic("nil type parameter")
}
if targs[i] == nil {
check.dump("%s: targ[%d] == nil", pos, i)
panic("nil type argument")
}
}
}
// common cases
switch t := typ.(type) {
case *Basic:
@ -162,6 +189,9 @@ type subster struct {
func (subst *subster) typ(typ Type) Type {
switch t := typ.(type) {
case nil:
panic("nil typ")
case *Basic:
// nothing to do
@ -253,7 +283,7 @@ func (subst *subster) typ(typ Type) Type {
// already instantiated
dump(">>> %s already instantiated", t)
assert(len(t.targs) == len(t.obj.tparams))
assert(len(t.targs) == len(t.targs))
// For each (existing) type argument targ, determine if it needs
// to be substituted; i.e., if it is or contains a type parameter
// that has a type argument for it.
@ -301,7 +331,25 @@ func (subst *subster) typ(typ Type) Type {
subst.cache[t] = named
dump(">>> subst %s(%s) with %s (new: %s)", t.underlying, subst.tpars, subst.targs, new_targs)
named.underlying = subst.typ(t.underlying)
named.methods = t.methods // methods will be customized when used
// update all method signatures
named.methods = t.methods
/*
if len(t.methods) > 0 {
dump(">>> update method signatures:")
for _, m := range t.methods {
subst.check.objDecl(m, nil)
copy := *m
copy.typ = subst.typ(copy.typ)
dump(">>> - %s: %s -> %s", m, m.typ, copy.typ)
named.methods = append(named.methods, &copy)
}
dump(">>> done with methods")
} else {
dump(">>> no methods to update")
}
*/
return named
case *TypeParam:

View File

@ -4,19 +4,14 @@
package p
type NumericAbs(type T) interface {
Abs() T
}
func f(type P interface{ m() P })(x P)
func AbsDifference(type T NumericAbs(T))(x T)
type T(type P) P
type OrderedAbs(type T) T
func (_ T(P)) m() P
func (a OrderedAbs(T)) Abs() T
func OrderedAbsDifference(type T)(x T) {
// TODO(gri) this should work
AbsDifference /* ERROR does not satisfy */ (OrderedAbs(T)(x))
func _(type Q)(x Q) {
f(T /* ERROR does not satisfy */ (Q))(T(Q)(x))
}
// TODO(gri) Once the code above works, check the code below.
@ -31,6 +26,51 @@ type OrderedAbs(type T) T
func (a OrderedAbs(T)) Abs() T
func OrderedAbsDifference(type T)() {
AbsDifference(OrderedAbs(T))()
}
*/
/*
package p
type T(type _ interface { a() }, _ interface{}) struct{}
type A(type P) struct{ x P }
func (_ A(P)) a() {}
var _ T(A(int), int)
*/
/*
type NumericAbs(type T) interface {
Abs() T
}
func AbsDifference(type T NumericAbs(T))(x T)
type OrderedAbs(type T) T
func (a OrderedAbs(T)) Abs() T
func OrderedAbsDifference(type T)(x T) {
AbsDifference(OrderedAbs(T)(x))
}
*/
// TODO(gri) Once the code above works, check the code below.
/*
type NumericAbs(type T) interface {
Abs() T
}
func AbsDifference(type T NumericAbs(T))()
type OrderedAbs(type T) T
func (a OrderedAbs(T)) Abs() T
func OrderedAbsDifference(type T)() {
AbsDifference(OrderedAbs(T))()
}

View File

@ -204,6 +204,7 @@ type Signature struct {
// TODO(gri) do we need to keep tparams in the Signature, rather than the Func object?
// (how are they different from type parameters which we keep with the TypeName?)
tparams []*TypeName // type parameters from left to right; or nil
mtparams []*TypeName // method type parameters
params *Tuple // (incoming) parameters from left to right; or nil
results *Tuple // (outgoing) results from left to right; or nil
variadic bool // true if the last parameter's type is of the form ...T (or string, for append built-in only)
@ -223,7 +224,7 @@ func NewSignature(recv *Var, params, results *Tuple, variadic bool) *Signature {
panic("types.NewSignature: variadic parameter must be of unnamed slice type")
}
}
return &Signature{nil, recv, nil, params, results, variadic}
return &Signature{nil, recv, nil, nil, params, results, variadic}
}
// Recv returns the receiver of signature s (if a method), or nil if a