go/types: implement substMap and use it for substitutions

Instead of passing around a type parameter list and type arguments,
create a substitution map from them and use that map instead.

Change-Id: Ia4a041d95bfaa98888c9c06812d33b3d2a79227d
This commit is contained in:
Robert Griesemer 2020-02-05 15:19:56 -08:00
parent 72d320ec6e
commit 38c2ef660d
4 changed files with 51 additions and 26 deletions

View File

@ -329,7 +329,7 @@ func (check *Checker) arguments(call *ast.CallExpr, sig *Signature, args []*oper
// need to compute it from the adjusted list; otherwise we can
// simply use the result signature's parameter list.
if adjusted {
sig_params = check.subst(call.Pos(), sig_params, sig.tparams, targs).(*Tuple)
sig_params = check.subst(call.Pos(), sig_params, makeSubstMap(sig.tparams, targs)).(*Tuple)
} else {
sig_params = rsig.params
}
@ -523,7 +523,7 @@ func (check *Checker) selector(x *operand, e *ast.SelectorExpr) {
// (If we modify m, some tests will fail; possibly because the m is in use.)
// TODO(gri) investigate and provide a correct explanation here
copy := *m
copy.typ = check.subst(e.Pos(), m.typ, sig.rparams, targs)
copy.typ = check.subst(e.Pos(), m.typ, makeSubstMap(sig.rparams, targs))
obj = &copy
}
// TODO(gri) we also need to do substitution for parameterized interface methods

View File

@ -364,7 +364,7 @@ func (check *Checker) missingMethod(V Type, T *Interface, static bool) (method,
// type parameters of ftyp with V's instantiation type arguments.
// This lazily instantiates the signature of method f.
if Vn != nil && len(Vn.targs) > 0 {
ftyp = check.subst(token.NoPos, ftyp, ftyp.rparams, Vn.targs).(*Signature)
ftyp = check.subst(token.NoPos, ftyp, makeSubstMap(ftyp.rparams, Vn.targs)).(*Signature)
}
// If the methods have type parameters we don't care whether they

View File

@ -10,10 +10,45 @@ package types
import (
"bytes"
"fmt"
"go/token"
"strings"
)
type substMap struct {
// The targs field is currently needed for *Named type substitution.
// TODO(gri) rewrite that code, get rid of this field, and make this
// struct just the map (proj)
targs []Type
proj map[*TypeParam]Type
}
func makeSubstMap(tpars []*TypeName, targs []Type) *substMap {
assert(len(tpars) == len(targs))
proj := make(map[*TypeParam]Type, len(tpars))
for i, tpar := range tpars {
targ := targs[i]
assert(targ != nil)
proj[tpar.typ.(*TypeParam)] = targs[i]
}
return &substMap{targs, proj}
}
func (m *substMap) String() string {
return fmt.Sprintf("%s", m.proj)
}
func (m *substMap) empty() bool {
return len(m.proj) == 0
}
func (m *substMap) lookup(typ *TypeParam) Type {
if t := m.proj[typ]; t != nil {
return t
}
return typ
}
func (check *Checker) instantiate(pos token.Pos, typ Type, targs []Type, poslist []token.Pos) (res Type) {
if check.conf.Trace {
check.trace(pos, "-- instantiating %s with %s", typ, typeListString(targs))
@ -67,6 +102,8 @@ func (check *Checker) instantiate(pos token.Pos, typ Type, targs []Type, poslist
return typ // nothing to do (minor optimization)
}
smap := makeSubstMap(tparams, targs)
// check bounds
for i, tname := range tparams {
tpar := tname.typ.(*TypeParam)
@ -87,7 +124,7 @@ func (check *Checker) instantiate(pos token.Pos, typ Type, targs []Type, poslist
// 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)
iface = check.subst(pos, iface, smap).(*Interface)
// targ must implement iface (methods)
if m, _ := check.missingMethod(targ, iface, true); m != nil {
@ -125,14 +162,13 @@ func (check *Checker) instantiate(pos token.Pos, typ Type, targs []Type, poslist
}
}
return check.subst(pos, typ, tparams, targs)
return check.subst(pos, typ, smap)
}
// 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))
if len(tpars) == 0 {
func (check *Checker) subst(pos token.Pos, typ Type, smap *substMap) Type {
if smap.empty() {
return typ
}
@ -141,16 +177,11 @@ func (check *Checker) subst(pos token.Pos, typ Type, tpars []*TypeName, targs []
case *Basic:
return typ // nothing to do
case *TypeParam:
for i, tpar := range tpars {
if tpar.typ == t {
return targs[i]
}
}
return typ
return smap.lookup(t)
}
// general case
subst := subster{check, pos, make(map[Type]Type), tpars, targs}
subst := subster{check, pos, make(map[Type]Type), smap}
return subst.typ(typ)
}
@ -158,8 +189,7 @@ type subster struct {
check *Checker
pos token.Pos
cache map[Type]Type
tpars []*TypeName
targs []Type
smap *substMap
}
func (subst *subster) typ(typ Type) Type {
@ -278,7 +308,7 @@ func (subst *subster) typ(typ Type) Type {
// not yet instantiated
dump(">>> first instantiation of %s", t)
new_targs = subst.targs
new_targs = subst.smap.targs
}
@ -300,19 +330,14 @@ func (subst *subster) typ(typ Type) Type {
named.tparams = t.tparams // new type is still parameterized
named.targs = new_targs
subst.cache[t] = named
dump(">>> subst %s(%s) with %s (new: %s)", t.underlying, subst.tpars, subst.targs, new_targs)
dump(">>> subst %s with %s (new: %s)", t.underlying, subst.smap, new_targs)
named.underlying = subst.typ(t.underlying)
named.methods = t.methods // method signatures are updated lazily
return named
case *TypeParam:
// TODO(gri) Can we do this with direct indexing somehow? Or use a map instead?
for i, tpar := range subst.tpars {
if tpar.typ == t {
return subst.targs[i]
}
}
return subst.smap.lookup(t)
default:
panic("unimplemented")

View File

@ -207,7 +207,7 @@ func (check *Checker) funcType(sig *Signature, recvPar *ast.FieldList, ftyp *ast
// TODO(gri) should we assume now that bounds always exist?
// (no bound == empty interface)
if bound != nil {
bound = check.subst(tname.pos, bound, recvTParams, list)
bound = check.subst(tname.pos, bound, makeSubstMap(recvTParams, list))
tname.typ.(*TypeParam).bound = bound
}
}