[dev.typeparams] Add optional sub-dict entry for typeparam bound calls

In the case that a generic function/method f does a method call on a
type param allowed by its bound, an instantiation of f may do a direct
method call of a concrete type or a method call defined on a generic
type, depending on whether the passed type in a concrete type or an
instantiated type with the appropriate method defined. See the test case
boundmethod.go added to this change.

In order to keep the dictionary format the same for all instantiations
of a generic function/method, I decided to have an optional
sub-dictionary entry for "bounds" calls. At the point that we are
creating the actual dictionary, we can then fill in the needed
sub-dictionary, if the type arg is an instantiated type, or a zeroed
dictionary entry, if type arg is not instantiated and the method will be
on a concrete type.

In order to implement this, I now fill in n.Selection for "bounds"
method calls in generic functions as well. Also, I need to calculate
n.Selection correctly during import for the case where it is now set -
method calls on generic types, and bounds calls on typeparams.

With this change, the dictionaries/sub-dictionaries are correct for
absdiff.go. The new test boundmethod.go illustrates the case where the
bound sub-dict entry is not used for a dictionary for stringify[myint],
but is used for a dictionary for stringify[StringInt[myint]].

Change-Id: Ie2bcb971b7019a9f1da68c97eb03da2333327457
Reviewed-on: https://go-review.googlesource.com/c/go/+/333456
Run-TryBot: Dan Scales <danscales@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
Trust: Dan Scales <danscales@google.com>
This commit is contained in:
Dan Scales 2021-07-02 17:51:20 -07:00
parent 0dcab98fd8
commit 1c783dc148
5 changed files with 172 additions and 27 deletions

View file

@ -158,12 +158,9 @@ func (g *irgen) stencil() {
st := g.getInstantiation(gf, targs, true)
dictValue, usingSubdict := g.getDictOrSubdict(declInfo, n, gf, targs, true)
_ = usingSubdict
// TODO: We should do assert(usingSubdict) here, but
// not creating sub-dictionary entry for
// absDifference in absdiff.go yet. Unusual case,
// where there are different generic method
// implementations of Abs in absDifference.
// We have to be using a subdictionary, since this is
// a generic method call.
assert(usingSubdict)
call.SetOp(ir.OCALL)
call.X = st.Nname
@ -741,10 +738,9 @@ func gcshapeType(t *types.Type) (*types.Type, string) {
return gcshape, buf.String()
}
// getInstantiation gets the instantiantion and dictionary of the function or method nameNode
// with the type arguments targs. If the instantiated function is not already
// cached, then it calls genericSubst to create the new instantiation.
func (g *irgen) getInstantiation(nameNode *ir.Name, targs []*types.Type, isMeth bool) *ir.Func {
// checkFetchBody checks if a generic body can be fetched, but hasn't been loaded
// yet. If so, it imports the body.
func checkFetchBody(nameNode *ir.Name) {
if nameNode.Func.Body == nil && nameNode.Func.Inl != nil {
// If there is no body yet but Func.Inl exists, then we can can
// import the whole generic body.
@ -754,6 +750,13 @@ func (g *irgen) getInstantiation(nameNode *ir.Name, targs []*types.Type, isMeth
nameNode.Func.Body = nameNode.Func.Inl.Body
nameNode.Func.Dcl = nameNode.Func.Inl.Dcl
}
}
// getInstantiation gets the instantiantion and dictionary of the function or method nameNode
// with the type arguments targs. If the instantiated function is not already
// cached, then it calls genericSubst to create the new instantiation.
func (g *irgen) getInstantiation(nameNode *ir.Name, targs []*types.Type, isMeth bool) *ir.Func {
checkFetchBody(nameNode)
sym := typecheck.MakeInstName(nameNode.Sym(), targs, isMeth)
info := g.instInfoMap[sym]
if info == nil {
@ -1405,13 +1408,41 @@ func (g *irgen) getDictionarySym(gf *ir.Name, targs []*types.Type, isMeth bool)
case ir.OCALL:
call := n.(*ir.CallExpr)
if call.X.Op() == ir.OXDOT {
subtargs := deref(call.X.(*ir.SelectorExpr).X.Type()).RParams()
s2targs := make([]*types.Type, len(subtargs))
for i, t := range subtargs {
s2targs[i] = subst.Typ(t)
var nameNode *ir.Name
se := call.X.(*ir.SelectorExpr)
if types.IsInterfaceMethod(se.Selection.Type) {
// This is a method call enabled by a type bound.
tmpse := ir.NewSelectorExpr(base.Pos, ir.OXDOT, se.X, se.Sel)
tmpse = typecheck.AddImplicitDots(tmpse)
tparam := tmpse.X.Type()
assert(tparam.IsTypeParam())
recvType := targs[tparam.Index()]
if len(recvType.RParams()) == 0 {
// No sub-dictionary entry is
// actually needed, since the
// typeparam is not an
// instantiated type that
// will have generic methods.
break
}
// This is a method call for an
// instantiated type, so we need a
// sub-dictionary.
targs := recvType.RParams()
genRecvType := recvType.OrigSym.Def.Type()
nameNode = typecheck.Lookdot1(call.X, se.Sel, genRecvType, genRecvType.Methods(), 1).Nname.(*ir.Name)
sym = g.getDictionarySym(nameNode, targs, true)
} else {
// This is the case of a normal
// method call on a generic type.
nameNode = call.X.(*ir.SelectorExpr).Selection.Nname.(*ir.Name)
subtargs := deref(call.X.(*ir.SelectorExpr).X.Type()).RParams()
s2targs := make([]*types.Type, len(subtargs))
for i, t := range subtargs {
s2targs[i] = subst.Typ(t)
}
sym = g.getDictionarySym(nameNode, s2targs, true)
}
nameNode := call.X.(*ir.SelectorExpr).Selection.Nname.(*ir.Name)
sym = g.getDictionarySym(nameNode, s2targs, true)
} else {
inst := call.X.(*ir.InstExpr)
var nameNode *ir.Name
@ -1452,8 +1483,14 @@ func (g *irgen) getDictionarySym(gf *ir.Name, targs []*types.Type, isMeth bool)
assert(false)
}
off = objw.SymPtr(lsym, off, sym.Linksym(), 0)
infoPrint(" - Subdict %v\n", sym.Name)
if sym == nil {
// Unused sub-dictionary entry, just emit 0.
off = objw.Uintptr(lsym, off, 0)
infoPrint(" - Unused subdict entry\n")
} else {
off = objw.SymPtr(lsym, off, sym.Linksym(), 0)
infoPrint(" - Subdict %v\n", sym.Name)
}
}
objw.Global(lsym, int32(off), obj.DUPOK|obj.RODATA)
infoPrint("=== Done dictionary\n")
@ -1512,6 +1549,7 @@ func (g *irgen) getGfInfo(gn *ir.Name) *gfInfo {
return infop
}
checkFetchBody(gn)
var info gfInfo
gf := gn.Func
recv := gf.Type().Recv()
@ -1575,6 +1613,13 @@ func (g *irgen) getGfInfo(gn *ir.Name) *gfInfo {
info.subDictCalls = append(info.subDictCalls, n)
}
}
if n.Op() == ir.OCALL && n.(*ir.CallExpr).X.Op() == ir.OXDOT &&
n.(*ir.CallExpr).X.(*ir.SelectorExpr).Selection != nil &&
deref(n.(*ir.CallExpr).X.(*ir.SelectorExpr).X.Type()).IsTypeParam() {
n.(*ir.CallExpr).X.(*ir.SelectorExpr).SetImplicit(true)
infoPrint(" Optional subdictionary at generic bound call: %v\n", n)
info.subDictCalls = append(info.subDictCalls, n)
}
if n.Op() == ir.OCLOSURE {
// Visit the closure body and add all relevant entries to the
// dictionary of the outer function (closure will just use