1
0
mirror of https://github.com/golang/go synced 2024-09-24 07:20:14 -06:00

cmd/compile/internal/types2: merge Instantiate and InstantiateLazy

Instantiate and InstantiateLazy have the same signature; on first
principles, if Instantiate should work for importers it should be
possible to consolidate these APIs.

This CL does this. In order to make it work, a typMap needs to be
threaded through type expansion to prevent infinite recursion in the
case that the Checker is nil.

Notably, Named types now must be expanded before returning from
Underlying(). This makes Underlying generally unsafe to call while type
checking a package, so a helper function safeUnderlying is added to
provide the previous behavior. This is probably overly conservative at
most call sites, but cleanup is deferred to a later CL.

Change-Id: I03cfb75bea0750862cd6eea4e3cdc875a7daa989
Reviewed-on: https://go-review.googlesource.com/c/go/+/341855
Trust: Robert Findley <rfindley@google.com>
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Robert Griesemer <gri@golang.org>
This commit is contained in:
Rob Findley 2021-08-10 14:00:56 -04:00 committed by Robert Findley
parent 7eaabae84d
commit fc27eb50ff
15 changed files with 87 additions and 56 deletions

View File

@ -229,7 +229,7 @@ func (r *reader2) doTyp() (res types2.Type) {
obj, targs := r.obj()
name := obj.(*types2.TypeName)
if len(targs) != 0 {
return r.p.check.InstantiateLazy(syntax.Pos{}, name.Type(), targs, nil, false)
return r.p.check.Instantiate(syntax.Pos{}, name.Type(), targs, nil, false)
}
return name.Type()

View File

@ -1875,7 +1875,7 @@ func TestInstantiate(t *testing.T) {
res := check.Instantiate(nopos, T, []Type{Typ[Int]}, nil, false)
// instantiated type should point to itself
if res.Underlying().(*Pointer).Elem() != res {
t.Fatalf("unexpected result type: %s", res)
if p := res.Underlying().(*Pointer).Elem(); p != res {
t.Fatalf("unexpected result type: %s points to %s", res, p)
}
}

View File

@ -334,7 +334,7 @@ func (check *Checker) arguments(call *syntax.CallExpr, sig *Signature, targs []T
// need to compute it from the adjusted list; otherwise we can
// simply use the result signature's parameter list.
if adjusted {
sigParams = check.subst(call.Pos(), sigParams, makeSubstMap(sig.TParams().list(), targs)).(*Tuple)
sigParams = check.subst(call.Pos(), sigParams, makeSubstMap(sig.TParams().list(), targs), nil).(*Tuple)
} else {
sigParams = rsig.params
}
@ -555,7 +555,7 @@ func (check *Checker) selector(x *operand, e *syntax.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, makeSubstMap(sig.RParams().list(), targs))
copy.typ = check.subst(e.Pos(), m.typ, makeSubstMap(sig.RParams().list(), targs), nil)
obj = &copy
}
// TODO(gri) we also need to do substitution for parameterized interface methods

View File

@ -317,7 +317,7 @@ func (check *Checker) validType(typ Type, path []Object) typeInfo {
}
case *Named:
t.expand()
t.expand(check.typMap)
// don't touch the type if it is from a different package or the Universe scope
// (doing so would lead to a race condition - was issue #35049)
@ -650,7 +650,8 @@ func (check *Checker) collectMethods(obj *TypeName) {
// and field names must be distinct."
base := asNamed(obj.typ) // shouldn't fail but be conservative
if base != nil {
if t, _ := base.Underlying().(*Struct); t != nil {
u := safeUnderlying(base) // base should be expanded, but use safeUnderlying to be conservative
if t, _ := u.(*Struct); t != nil {
for _, fld := range t.fields {
if fld.name != "_" {
assert(mset.insert(fld) == nil)

View File

@ -661,7 +661,7 @@ func (check *Checker) updateExprVal(x syntax.Expr, val constant.Value) {
func (check *Checker) convertUntyped(x *operand, target Type) {
newType, val, code := check.implicitTypeAndValue(x, target)
if code != 0 {
check.invalidConversion(code, x, target.Underlying())
check.invalidConversion(code, x, safeUnderlying(target))
x.mode = invalid
return
}

View File

@ -87,7 +87,7 @@ func (check *Checker) infer(pos syntax.Pos, tparams []*TypeName, targs []Type, p
// but that doesn't impact the isParameterized check for now).
if params.Len() > 0 {
smap := makeSubstMap(tparams, targs)
params = check.subst(nopos, params, smap).(*Tuple)
params = check.subst(nopos, params, smap, nil).(*Tuple)
}
// --- 2 ---
@ -127,7 +127,7 @@ func (check *Checker) infer(pos syntax.Pos, tparams []*TypeName, targs []Type, p
}
}
smap := makeSubstMap(tparams, targs)
inferred := check.subst(arg.Pos(), tpar, smap)
inferred := check.subst(arg.Pos(), tpar, smap, nil)
if inferred != tpar {
check.errorf(arg, "%s %s of %s does not match inferred type %s for %s", kind, targ, arg.expr, inferred, tpar)
} else {
@ -427,7 +427,7 @@ func (check *Checker) inferB(tparams []*TypeName, targs []Type, report bool) (ty
n := 0
for _, index := range dirty {
t0 := types[index]
if t1 := check.subst(nopos, t0, smap); t1 != t0 {
if t1 := check.subst(nopos, t0, smap, nil); t1 != t0 {
types[index] = t1
dirty[n] = index
n++

View File

@ -29,7 +29,7 @@ func (check *Checker) Instantiate(pos syntax.Pos, typ Type, targs []Type, posLis
var tparams []*TypeName
switch t := typ.(type) {
case *Named:
tparams = t.TParams().list()
return check.instantiateLazy(pos, t, targs, posList, verify)
case *Signature:
tparams = t.TParams().list()
defer func() {
@ -55,14 +55,14 @@ func (check *Checker) Instantiate(pos syntax.Pos, typ Type, targs []Type, posLis
panic(fmt.Sprintf("%v: cannot instantiate %v", pos, typ))
}
inst := check.instantiate(pos, typ, tparams, targs, posList)
inst := check.instantiate(pos, typ, tparams, targs, posList, nil)
if verify && len(tparams) == len(targs) {
check.verify(pos, tparams, targs, posList)
}
return inst
}
func (check *Checker) instantiate(pos syntax.Pos, typ Type, tparams []*TypeName, targs []Type, posList []syntax.Pos) (res Type) {
func (check *Checker) instantiate(pos syntax.Pos, typ Type, tparams []*TypeName, targs []Type, posList []syntax.Pos, typMap map[string]*Named) (res Type) {
// the number of supplied types must match the number of type parameters
if len(targs) != len(tparams) {
// TODO(gri) provide better error message
@ -83,7 +83,7 @@ func (check *Checker) instantiate(pos syntax.Pos, typ Type, tparams []*TypeName,
// Calling under() here may lead to endless instantiations.
// Test case: type T[P any] T[P]
// TODO(gri) investigate if that's a bug or to be expected.
under = res.Underlying()
under = safeUnderlying(res)
}
check.trace(pos, "=> %s (under = %s)", res, under)
}()
@ -95,21 +95,14 @@ func (check *Checker) instantiate(pos syntax.Pos, typ Type, tparams []*TypeName,
return typ // nothing to do (minor optimization)
}
return check.subst(pos, typ, makeSubstMap(tparams, targs))
return check.subst(pos, typ, makeSubstMap(tparams, targs), typMap)
}
// InstantiateLazy is like Instantiate, but avoids actually
// instantiating the type until needed. typ must be a *Named
// type.
func (check *Checker) InstantiateLazy(pos syntax.Pos, typ Type, targs []Type, posList []syntax.Pos, verify bool) Type {
// Don't use asNamed here: we don't want to expand the base during lazy
// instantiation.
base := typ.(*Named)
if base == nil {
panic(fmt.Sprintf("%v: cannot instantiate %v", pos, typ))
}
// instantiateLazy avoids actually instantiating the type until needed. typ
// must be a *Named type.
func (check *Checker) instantiateLazy(pos syntax.Pos, base *Named, targs []Type, posList []syntax.Pos, verify bool) Type {
if verify && base.TParams().Len() == len(targs) {
// TODO: lift the nil check in verify to here.
check.later(func() {
check.verify(pos, base.tparams.list(), targs, posList)
})
@ -169,7 +162,7 @@ func (check *Checker) satisfies(pos syntax.Pos, targ Type, tpar *TypeParam, smap
// 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, smap).(*Interface)
iface = check.subst(pos, iface, smap, nil).(*Interface)
// if iface is comparable, targ must be comparable
// TODO(gri) the error messages needs to be better, here

View File

@ -46,7 +46,7 @@ func LookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (o
// pointer type but discard the result if it is a method since we would
// not have found it for T (see also issue 8590).
if t := asNamed(T); t != nil {
if p, _ := t.Underlying().(*Pointer); p != nil {
if p, _ := safeUnderlying(t).(*Pointer); p != nil {
obj, index, indirect = lookupFieldOrMethod(p, false, pkg, name)
if _, ok := obj.(*Func); ok {
return nil, nil, false
@ -394,7 +394,7 @@ func (check *Checker) missingMethod(V Type, T *Interface, static bool) (method,
if len(ftyp.RParams().list()) != len(Vn.targs) {
return
}
ftyp = check.subst(nopos, ftyp, makeSubstMap(ftyp.RParams().list(), Vn.targs)).(*Signature)
ftyp = check.subst(nopos, ftyp, makeSubstMap(ftyp.RParams().list(), Vn.targs), nil).(*Signature)
}
// If the methods have type parameters we don't care whether they

View File

@ -153,7 +153,7 @@ func (t *Named) AddMethod(m *Func) {
}
}
func (t *Named) Underlying() Type { return t.load().underlying }
func (t *Named) Underlying() Type { return t.load().expand(nil).underlying }
func (t *Named) String() string { return TypeString(t, nil) }
// ----------------------------------------------------------------------------
@ -166,9 +166,9 @@ func (t *Named) String() string { return TypeString(t, nil) }
// is detected, the result is Typ[Invalid]. If a cycle is detected and
// n0.check != nil, the cycle is reported.
func (n0 *Named) under() Type {
n0.expand()
n0.expand(nil)
u := n0.Underlying()
u := n0.load().underlying
if u == Typ[Invalid] {
return u
@ -206,7 +206,7 @@ func (n0 *Named) under() Type {
seen := map[*Named]int{n0: 0}
path := []Object{n0.obj}
for {
u = n.Underlying()
u = n.load().underlying
if u == nil {
u = Typ[Invalid]
break
@ -214,7 +214,7 @@ func (n0 *Named) under() Type {
var n1 *Named
switch u1 := u.(type) {
case *Named:
u1.expand()
u1.expand(nil)
n1 = u1
}
if n1 == nil {
@ -264,15 +264,40 @@ type instance struct {
// expand ensures that the underlying type of n is instantiated.
// The underlying type will be Typ[Invalid] if there was an error.
func (n *Named) expand() {
func (n *Named) expand(typMap map[string]*Named) *Named {
if n.instance != nil {
// n must be loaded before instantiation, in order to have accurate
// tparams. This is done implicitly by the call to n.TParams, but making it
// explicit is harmless: load is idempotent.
n.load()
inst := n.check.instantiate(n.instance.pos, n.orig.underlying, n.TParams().list(), n.targs, n.instance.posList)
if typMap == nil {
if n.check != nil {
typMap = n.check.typMap
} else {
// If we're instantiating lazily, we might be outside the scope of a
// type-checking pass. In that case we won't have a pre-existing
// typMap, but don't want to create a duplicate of the current instance
// in the process of expansion.
h := instantiatedHash(n.orig, n.targs)
typMap = map[string]*Named{h: n}
}
}
inst := n.check.instantiate(n.instance.pos, n.orig.underlying, n.TParams().list(), n.targs, n.instance.posList, typMap)
n.underlying = inst
n.fromRHS = inst
n.instance = nil
}
return n
}
// safeUnderlying returns the underlying of typ without expanding instances, to
// avoid infinite recursion.
//
// TODO(rfindley): eliminate this function or give it a better name.
func safeUnderlying(typ Type) Type {
if t, _ := typ.(*Named); t != nil {
return t.load().underlying
}
return typ.Underlying()
}

View File

@ -302,8 +302,8 @@ func identical(x, y Type, cmpTags bool, p *ifacePair) bool {
// Two named types are identical if their type names originate
// in the same type declaration.
if y, ok := y.(*Named); ok {
x.expand()
y.expand()
x.expand(nil)
y.expand(nil)
// TODO(gri) Why is x == y not sufficient? And if it is,
// we can just return false here because x == y
// is caught in the very beginning of this function.

View File

@ -153,7 +153,7 @@ func (check *Checker) funcType(sig *Signature, recvPar *syntax.Field, tparams []
// TODO(gri) should we assume now that bounds always exist?
// (no bound == empty interface)
if bound != nil {
bound = check.subst(tname.pos, bound, smap)
bound = check.subst(tname.pos, bound, smap, nil)
tname.typ.(*TypeParam).bound = bound
}
}
@ -215,7 +215,7 @@ func (check *Checker) funcType(sig *Signature, recvPar *syntax.Field, tparams []
var err string
switch T := rtyp.(type) {
case *Named:
T.expand()
T.expand(nil)
// spec: "The type denoted by T is called the receiver base type; it must not
// be a pointer or interface type and it must be declared in the same package
// as the method."

View File

@ -51,7 +51,9 @@ func (m *substMap) lookup(tpar *TypeParam) Type {
// subst is functional in the sense that it doesn't modify the incoming
// type. If a substitution took place, the result type is different from
// from the incoming type.
func (check *Checker) subst(pos syntax.Pos, typ Type, smap *substMap) Type {
//
// If the given typMap is nil and check is non-nil, check.typMap is used.
func (check *Checker) subst(pos syntax.Pos, typ Type, smap *substMap, typMap map[string]*Named) Type {
if smap.empty() {
return typ
}
@ -68,16 +70,21 @@ func (check *Checker) subst(pos syntax.Pos, typ Type, smap *substMap) Type {
var subst subster
subst.pos = pos
subst.smap = smap
if check != nil {
subst.check = check
subst.typMap = check.typMap
} else {
if typMap == nil {
typMap = check.typMap
}
}
if typMap == nil {
// If we don't have a *Checker and its global type map,
// use a local version. Besides avoiding duplicate work,
// the type map prevents infinite recursive substitution
// for recursive types (example: type T[P any] *T[P]).
subst.typMap = make(map[string]*Named)
typMap = make(map[string]*Named)
}
subst.typMap = typMap
return subst.typ(typ)
}
@ -234,14 +241,15 @@ func (subst *subster) typ(typ Type) Type {
// create a new named type and populate typMap to avoid endless recursion
tname := NewTypeName(subst.pos, t.obj.pkg, t.obj.name, nil)
named := subst.check.newNamed(tname, t, t.Underlying(), t.TParams(), t.methods) // method signatures are updated lazily
t.load()
named := subst.check.newNamed(tname, t.orig, t.underlying, t.TParams(), t.methods) // method signatures are updated lazily
named.targs = new_targs
subst.typMap[h] = named
t.expand() // must happen after typMap update to avoid infinite recursion
t.expand(subst.typMap) // must happen after typMap update to avoid infinite recursion
// do the substitution
dump(">>> subst %s with %s (new: %s)", t.underlying, subst.smap, new_targs)
named.underlying = subst.typOrNil(t.Underlying())
named.underlying = subst.typOrNil(t.underlying)
dump(">>> underlying: %v", named.underlying)
assert(named.underlying != nil)
named.fromRHS = named.underlying // for cycle detection (Checker.validType)

View File

@ -116,7 +116,7 @@ func asInterface(t Type) *Interface {
func asNamed(t Type) *Named {
e, _ := t.(*Named)
if e != nil {
e.expand()
e.expand(nil)
}
return e
}

View File

@ -225,7 +225,7 @@ func (check *Checker) typInternal(e0 syntax.Expr, def *Named) (T Type) {
// Test case: type T[P any] *T[P]
// TODO(gri) investigate if that's a bug or to be expected
// (see also analogous comment in Checker.instantiate).
under = T.Underlying()
under = safeUnderlying(T)
}
if T == under {
check.trace(e0.Pos(), "=> %s // %s", T, goTypeName(T))
@ -422,9 +422,13 @@ func (check *Checker) typOrNil(e syntax.Expr) Type {
}
func (check *Checker) instantiatedType(x syntax.Expr, targsx []syntax.Expr, def *Named) Type {
base := check.genericType(x, true)
if base == Typ[Invalid] {
return base // error already reported
gtyp := check.genericType(x, true)
if gtyp == Typ[Invalid] {
return gtyp // error already reported
}
base, _ := gtyp.(*Named)
if base == nil {
panic(fmt.Sprintf("%v: cannot instantiate %v", x.Pos(), gtyp))
}
// evaluate arguments
@ -440,7 +444,7 @@ func (check *Checker) instantiatedType(x syntax.Expr, targsx []syntax.Expr, def
posList[i] = syntax.StartPos(arg)
}
typ := check.InstantiateLazy(x.Pos(), base, targs, posList, true)
typ := check.instantiateLazy(x.Pos(), base, targs, posList, true)
def.setUnderlying(typ)
// make sure we check instantiation works at least once

View File

@ -432,8 +432,8 @@ func (u *unifier) nify(x, y Type, p *ifacePair) bool {
// return x.obj == y.obj
// }
if y, ok := y.(*Named); ok {
x.expand()
y.expand()
x.expand(nil)
y.expand(nil)
// TODO(gri) This is not always correct: two types may have the same names
// in the same package if one of them is nested in a function.
// Extremely unlikely but we need an always correct solution.