mirror of
https://github.com/golang/go.git
synced 2024-09-21 10:28:27 +00:00
go/types: merge Instantiate and InstantiateLazy
This is a straightforward port of CL 341855 to go/types. Change-Id: I42a74df7a54f5d03aab31ad75dfeb3d1ba775354 Reviewed-on: https://go-review.googlesource.com/c/go/+/342477 Trust: Robert Findley <rfindley@google.com> Run-TryBot: Robert Findley <rfindley@google.com> Reviewed-by: Robert Griesemer <gri@golang.org> TryBot-Result: Go Bot <gobot@golang.org>
This commit is contained in:
parent
2460cf8602
commit
ff36d11470
@ -1857,7 +1857,7 @@ func TestInstantiate(t *testing.T) {
|
||||
res := check.Instantiate(token.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)
|
||||
}
|
||||
}
|
||||
|
@ -341,7 +341,7 @@ func (check *Checker) arguments(call *ast.CallExpr, sig *Signature, targs []Type
|
||||
// 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
|
||||
}
|
||||
@ -554,7 +554,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, makeSubstMap(sig.RParams().list(), targs))
|
||||
copy.typ = check.subst(e.Pos(), m.typ, makeSubstMap(sig.RParams().list(), targs), nil)
|
||||
obj = ©
|
||||
}
|
||||
// TODO(gri) we also need to do substitution for parameterized interface methods
|
||||
|
@ -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)
|
||||
if t.obj.pkg != check.pkg {
|
||||
@ -711,7 +711,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)
|
||||
|
@ -600,7 +600,7 @@ func (check *Checker) updateExprVal(x ast.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
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ func (check *Checker) infer(posn positioner, tparams []*TypeName, targs []Type,
|
||||
// but that doesn't impact the isParameterized check for now).
|
||||
if params.Len() > 0 {
|
||||
smap := makeSubstMap(tparams, targs)
|
||||
params = check.subst(token.NoPos, params, smap).(*Tuple)
|
||||
params = check.subst(token.NoPos, params, smap, nil).(*Tuple)
|
||||
}
|
||||
|
||||
// --- 2 ---
|
||||
@ -127,7 +127,7 @@ func (check *Checker) infer(posn positioner, tparams []*TypeName, targs []Type,
|
||||
}
|
||||
smap := makeSubstMap(tparams, targs)
|
||||
// TODO(rFindley): pass a positioner here, rather than arg.Pos().
|
||||
inferred := check.subst(arg.Pos(), tpar, smap)
|
||||
inferred := check.subst(arg.Pos(), tpar, smap, nil)
|
||||
if inferred != tpar {
|
||||
check.errorf(arg, _Todo, "%s %s of %s does not match inferred type %s for %s", kind, targ, arg.expr, inferred, tpar)
|
||||
} else {
|
||||
@ -422,7 +422,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(token.NoPos, t0, smap); t1 != t0 {
|
||||
if t1 := check.subst(token.NoPos, t0, smap, nil); t1 != t0 {
|
||||
types[index] = t1
|
||||
dirty[n] = index
|
||||
n++
|
||||
|
@ -28,7 +28,7 @@ func (check *Checker) Instantiate(pos token.Pos, typ Type, targs []Type, posList
|
||||
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() {
|
||||
@ -54,14 +54,14 @@ func (check *Checker) Instantiate(pos token.Pos, typ Type, targs []Type, posList
|
||||
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 token.Pos, typ Type, tparams []*TypeName, targs []Type, posList []token.Pos) (res Type) {
|
||||
func (check *Checker) instantiate(pos token.Pos, typ Type, tparams []*TypeName, targs []Type, posList []token.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
|
||||
@ -82,7 +82,7 @@ func (check *Checker) instantiate(pos token.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)
|
||||
}()
|
||||
@ -96,22 +96,14 @@ func (check *Checker) instantiate(pos token.Pos, typ Type, tparams []*TypeName,
|
||||
return typ // nothing to do (minor optimization)
|
||||
}
|
||||
|
||||
smap := makeSubstMap(tparams, targs)
|
||||
|
||||
return check.subst(pos, typ, smap)
|
||||
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 token.Pos, typ Type, targs []Type, posList []token.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 token.Pos, base *Named, targs []Type, posList []token.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)
|
||||
})
|
||||
@ -171,7 +163,7 @@ func (check *Checker) satisfies(pos token.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
|
||||
|
@ -48,7 +48,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
|
||||
@ -392,7 +392,7 @@ func (check *Checker) missingMethod(V Type, T *Interface, static bool) (method,
|
||||
if len(ftyp.RParams().list()) != len(Vn.targs) {
|
||||
return
|
||||
}
|
||||
ftyp = check.subst(token.NoPos, ftyp, makeSubstMap(ftyp.RParams().list(), Vn.targs)).(*Signature)
|
||||
ftyp = check.subst(token.NoPos, ftyp, makeSubstMap(ftyp.RParams().list(), Vn.targs), nil).(*Signature)
|
||||
}
|
||||
|
||||
// If the methods have type parameters we don't care whether they
|
||||
|
@ -157,7 +157,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) }
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
@ -170,9 +170,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
|
||||
@ -210,7 +210,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
|
||||
@ -218,7 +218,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 {
|
||||
@ -268,17 +268,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.
|
||||
// TODO(rfindley): expand would be a better name for this method, but conflicts
|
||||
// with the existing concept of lazy expansion. Need to reconcile this.
|
||||
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()
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -148,7 +148,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, smap)
|
||||
bound = check.subst(tname.pos, bound, smap, nil)
|
||||
tname.typ.(*TypeParam).bound = bound
|
||||
}
|
||||
}
|
||||
@ -205,7 +205,7 @@ func (check *Checker) funcType(sig *Signature, recvPar *ast.FieldList, ftyp *ast
|
||||
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."
|
||||
|
@ -54,7 +54,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 token.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 token.Pos, typ Type, smap *substMap, typMap map[string]*Named) Type {
|
||||
if smap.empty() {
|
||||
return typ
|
||||
}
|
||||
@ -71,16 +73,21 @@ func (check *Checker) subst(pos token.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)
|
||||
}
|
||||
@ -241,14 +248,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 = newTargs
|
||||
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, newTargs)
|
||||
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)
|
||||
|
@ -115,7 +115,7 @@ func asInterface(t Type) *Interface {
|
||||
func asNamed(t Type) *Named {
|
||||
e, _ := t.(*Named)
|
||||
if e != nil {
|
||||
e.expand()
|
||||
e.expand(nil)
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
@ -223,7 +223,7 @@ func (check *Checker) typInternal(e0 ast.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))
|
||||
@ -411,9 +411,13 @@ func (check *Checker) typeOrNil(e ast.Expr) Type {
|
||||
}
|
||||
|
||||
func (check *Checker) instantiatedType(x ast.Expr, targsx []ast.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
|
||||
@ -429,7 +433,7 @@ func (check *Checker) instantiatedType(x ast.Expr, targsx []ast.Expr, def *Named
|
||||
posList[i] = arg.Pos()
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -429,8 +429,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.
|
||||
|
Loading…
Reference in New Issue
Block a user