check.recordUntyped()
- if check.Info != nil {
- sanitizeInfo(check.Info)
- }
-
check.pkg.complete = true
// no longer needed - release memory
}
case *Named:
+ t.complete()
// 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 {
panic("internal error: cycle start not found")
}
return t.info
-
- case *instance:
- return check.validType(t.expand(), path)
}
return valid
// determine underlying type of named
named.fromRHS = check.definedType(tdecl.Type, named)
+ assert(named.fromRHS != nil)
// The underlying type of named may be itself a named type that is
// incomplete:
bound := check.typ(e)
check.later(func() {
- if _, ok := under(bound).(*Interface); !ok && bound != Typ[Invalid] {
+ u := under(bound)
+ if _, ok := u.(*Interface); !ok && u != Typ[Invalid] {
check.errorf(e, _Todo, "%s is not an interface", bound)
}
})
// t must be one of w.tparams
return t.index < len(w.tparams) && w.tparams[t.index].typ == t
- case *instance:
- return w.isParameterizedList(t.targs)
-
default:
unreachable()
}
package types
+// TODO(rfindley): move this code to named.go.
+
import "go/token"
-// An instance represents an instantiated generic type syntactically
-// (without expanding the instantiation). Type instances appear only
-// during type-checking and are replaced by their fully instantiated
-// (expanded) types before the end of type-checking.
+// instance holds a Checker along with syntactic information
+// information, for use in lazy instantiation.
type instance struct {
- check *Checker // for lazy instantiation
+ check *Checker
pos token.Pos // position of type instantiation; for error reporting only
- base *Named // parameterized type to be instantiated
- targs []Type // type arguments
posList []token.Pos // position of each targ; for error reporting only
verify bool // if set, constraint satisfaction is verified
- value Type // base[targs...] after instantiation or Typ[Invalid]; nil if not yet set
}
-// expand returns the instantiated (= expanded) type of t.
-// The result is either an instantiated *Named type, or
-// Typ[Invalid] if there was an error.
-func (t *instance) expand() Type {
- v := t.value
- if v == nil {
- v = t.check.Instantiate(t.pos, t.base, t.targs, t.posList, t.verify)
- if v == nil {
- v = Typ[Invalid]
- }
- t.value = v
- }
- // After instantiation we must have an invalid or a *Named type.
- if debug && v != Typ[Invalid] {
- _ = v.(*Named)
+// complete 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) complete() {
+ if n.instance != nil && len(n.targs) > 0 && n.underlying == nil {
+ check := n.instance.check
+ inst := check.instantiate(n.instance.pos, n.orig.underlying, n.tparams, n.targs, n.instance.posList, n.instance.verify)
+ n.underlying = inst
+ n.fromRHS = inst
+ n.methods = n.orig.methods
}
- return v
}
// expand expands a type instance into its instantiated
// type and leaves all other types alone. expand does
// not recurse.
func expand(typ Type) Type {
- if t, _ := typ.(*instance); t != nil {
- return t.expand()
+ if t, _ := typ.(*Named); t != nil {
+ t.complete()
}
return typ
}
-
-// expandf is set to expand.
-// Call expandf when calling expand causes compile-time cycle error.
-var expandf func(Type) Type
-
-func init() { expandf = expand }
-
-func (t *instance) Underlying() Type { return t }
-func (t *instance) String() string { return TypeString(t, nil) }
// Any methods attached to a *Named are simply copied; they are not
// instantiated.
func (check *Checker) Instantiate(pos token.Pos, typ Type, targs []Type, posList []token.Pos, verify bool) (res Type) {
- if verify && check == nil {
- panic("cannot have nil receiver if verify is set")
- }
-
- if check != nil && trace {
- check.trace(pos, "-- instantiating %s with %s", typ, typeListString(targs))
- check.indent++
- defer func() {
- check.indent--
- var under Type
- if res != nil {
- // 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()
- }
- check.trace(pos, "=> %s (under = %s)", res, under)
- }()
- }
-
- assert(len(posList) <= len(targs))
-
- // TODO(gri) What is better here: work with TypeParams, or work with TypeNames?
var tparams []*TypeName
switch t := typ.(type) {
case *Named:
panic(fmt.Sprintf("%v: cannot instantiate %v", pos, typ))
}
+ return check.instantiate(pos, typ, tparams, targs, posList, verify)
+}
+
+func (check *Checker) instantiate(pos token.Pos, typ Type, tparams []*TypeName, targs []Type, posList []token.Pos, verify bool) (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
}
panic(fmt.Sprintf("%v: got %d arguments but %d type parameters", pos, len(targs), len(tparams)))
}
+ if verify && check == nil {
+ panic("cannot have nil receiver if verify is set")
+ }
+
+ if check != nil && trace {
+ check.trace(pos, "-- instantiating %s with %s", typ, typeListString(targs))
+ check.indent++
+ defer func() {
+ check.indent--
+ var under Type
+ if res != nil {
+ // 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()
+ }
+ check.trace(pos, "=> %s (under = %s)", res, under)
+ }()
+ }
+
+ assert(len(posList) <= len(targs))
+
+ // TODO(gri) What is better here: work with TypeParams, or work with TypeNames?
if len(tparams) == 0 {
return typ // nothing to do (minor optimization)
if base == nil {
panic(fmt.Sprintf("%v: cannot instantiate %v", pos, typ))
}
+ h := instantiatedHash(base, targs)
+ if check != nil {
+ if named := check.typMap[h]; named != nil {
+ return named
+ }
+ }
- return &instance{
+ tname := NewTypeName(pos, base.obj.pkg, base.obj.name, nil)
+ named := check.newNamed(tname, base, nil, base.tparams, base.methods) // methods are instantiated lazily
+ named.targs = targs
+ named.instance = &instance{
check: check,
pos: pos,
- base: base,
- targs: targs,
posList: posList,
verify: verify,
}
+ if check != nil {
+ check.typMap[h] = named
+ }
+ return named
}
// satisfies reports whether the type argument targ satisfies the constraint of type parameter
// A Named represents a named (defined) type.
type Named struct {
- check *Checker // for Named.under implementation; nilled once under has been called
+ instance *instance // syntactic information for lazy instantiation
info typeInfo // for cycle detection
obj *TypeName // corresponding declared object
orig *Named // original, uninstantiated type
// newNamed is like NewNamed but with a *Checker receiver and additional orig argument.
func (check *Checker) newNamed(obj *TypeName, orig *Named, underlying Type, tparams []*TypeName, methods []*Func) *Named {
- typ := &Named{check: check, obj: obj, orig: orig, fromRHS: underlying, underlying: underlying, tparams: tparams, methods: methods}
+ var inst *instance
+ if check != nil {
+ inst = &instance{
+ check: check,
+ }
+ }
+ typ := &Named{instance: inst, obj: obj, orig: orig, fromRHS: underlying, underlying: underlying, tparams: tparams, methods: methods}
if typ.orig == nil {
typ.orig = typ
}
if check != nil {
check.later(func() {
switch typ.under().(type) {
- case *Named, *instance:
+ case *Named:
panic("internal error: unexpanded underlying type")
}
- typ.check = nil
+ typ.instance = nil
})
}
return typ
// 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.complete()
+
u := n0.Underlying()
if u == Typ[Invalid] {
default:
// common case
return u
- case *Named, *instance:
+ case *Named:
// handled below
}
- if n0.check == nil {
+ if n0.instance == nil || n0.instance.check == nil {
panic("internal error: Named.check == nil but type is incomplete")
}
// Invariant: after this point n0 as well as any named types in its
// underlying chain should be set up when this function exits.
- check := n0.check
+ check := n0.instance.check
// If we can't expand u at this point, it is invalid.
n := asNamed(u)
var n1 *Named
switch u1 := u.(type) {
case *Named:
+ u1.complete()
n1 = u1
- case *instance:
- n1, _ = u1.expand().(*Named)
- if n1 == nil {
- u = Typ[Invalid]
- }
}
if n1 == nil {
break // end of chain
if _, ok := typ.(*Basic); ok {
return
}
+ if named, _ := typ.(*Named); named != nil && len(named.tparams) > 0 {
+ writeTParamList(buf, named.tparams, qf, nil)
+ }
if tname.IsAlias() {
buf.WriteString(" =")
} else {
// isNamed may be called with types that are not fully set up.
func isNamed(typ Type) bool {
switch typ.(type) {
- case *Basic, *Named, *TypeParam, *instance:
+ case *Basic, *Named, *TypeParam:
return true
}
return false
// For changes to this code the corresponding changes should be made to unifier.nify.
func identical(x, y Type, cmpTags bool, p *ifacePair) bool {
// types must be expanded for comparison
- x = expandf(x)
- y = expandf(y)
+ x = expand(x)
+ y = expand(y)
if x == y {
return true
+++ /dev/null
-// Copyright 2020 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package types
-
-// sanitizeInfo walks the types contained in info to ensure that all instances
-// are expanded.
-//
-// This includes some objects that may be shared across concurrent
-// type-checking passes (such as those in the universe scope), so we are
-// careful here not to write types that are already sanitized. This avoids a
-// data race as any shared types should already be sanitized.
-func sanitizeInfo(info *Info) {
- var s sanitizer = make(map[Type]Type)
-
- // Note: Some map entries are not references.
- // If modified, they must be assigned back.
-
- for e, tv := range info.Types {
- if typ := s.typ(tv.Type); typ != tv.Type {
- tv.Type = typ
- info.Types[e] = tv
- }
- }
-
- inferred := info.Inferred
- for e, inf := range inferred {
- changed := false
- for i, targ := range inf.TArgs {
- if typ := s.typ(targ); typ != targ {
- inf.TArgs[i] = typ
- changed = true
- }
- }
- if typ := s.typ(inf.Sig); typ != inf.Sig {
- inf.Sig = typ.(*Signature)
- changed = true
- }
- if changed {
- inferred[e] = inf
- }
- }
-
- for _, obj := range info.Defs {
- if obj != nil {
- if typ := s.typ(obj.Type()); typ != obj.Type() {
- obj.setType(typ)
- }
- }
- }
-
- for _, obj := range info.Uses {
- if obj != nil {
- if typ := s.typ(obj.Type()); typ != obj.Type() {
- obj.setType(typ)
- }
- }
- }
-
- // TODO(gri) sanitize as needed
- // - info.Implicits
- // - info.Selections
- // - info.Scopes
- // - info.InitOrder
-}
-
-type sanitizer map[Type]Type
-
-func (s sanitizer) typ(typ Type) Type {
- if typ == nil {
- return nil
- }
-
- if t, found := s[typ]; found {
- return t
- }
- s[typ] = typ
-
- switch t := typ.(type) {
- case *Basic, *top:
- // nothing to do
-
- case *Array:
- if elem := s.typ(t.elem); elem != t.elem {
- t.elem = elem
- }
-
- case *Slice:
- if elem := s.typ(t.elem); elem != t.elem {
- t.elem = elem
- }
-
- case *Struct:
- s.varList(t.fields)
-
- case *Pointer:
- if base := s.typ(t.base); base != t.base {
- t.base = base
- }
-
- case *Tuple:
- s.tuple(t)
-
- case *Signature:
- s.var_(t.recv)
- s.tuple(t.params)
- s.tuple(t.results)
-
- case *Union:
- s.typeList(t.types)
-
- case *Interface:
- s.funcList(t.methods)
- s.typeList(t.embeddeds)
- // TODO(gri) do we need to sanitize type sets?
- tset := t.typeSet()
- s.funcList(tset.methods)
- if types := s.typ(tset.types); types != tset.types {
- tset.types = types
- }
-
- case *Map:
- if key := s.typ(t.key); key != t.key {
- t.key = key
- }
- if elem := s.typ(t.elem); elem != t.elem {
- t.elem = elem
- }
-
- case *Chan:
- if elem := s.typ(t.elem); elem != t.elem {
- t.elem = elem
- }
-
- case *Named:
- if debug && t.check != nil {
- panic("internal error: Named.check != nil")
- }
- t.expand()
- if orig := s.typ(t.fromRHS); orig != t.fromRHS {
- t.fromRHS = orig
- }
- if under := s.typ(t.underlying); under != t.underlying {
- t.underlying = under
- }
- s.typeList(t.targs)
- s.funcList(t.methods)
-
- case *TypeParam:
- if bound := s.typ(t.bound); bound != t.bound {
- t.bound = bound
- }
-
- case *instance:
- typ = t.expand()
- s[t] = typ
-
- default:
- panic("unimplemented")
- }
-
- return typ
-}
-
-func (s sanitizer) var_(v *Var) {
- if v != nil {
- if typ := s.typ(v.typ); typ != v.typ {
- v.typ = typ
- }
- }
-}
-
-func (s sanitizer) varList(list []*Var) {
- for _, v := range list {
- s.var_(v)
- }
-}
-
-func (s sanitizer) tuple(t *Tuple) {
- if t != nil {
- s.varList(t.vars)
- }
-}
-
-func (s sanitizer) func_(f *Func) {
- if f != nil {
- if typ := s.typ(f.typ); typ != f.typ {
- f.typ = typ
- }
- }
-}
-
-func (s sanitizer) funcList(list []*Func) {
- for _, f := range list {
- s.func_(f)
- }
-}
-
-func (s sanitizer) typeList(list []Type) {
- for i, t := range list {
- if typ := s.typ(t); typ != t {
- list[i] = typ
- }
- }
-}
{Chan{}, 12, 24},
{Named{}, 84, 160},
{TypeParam{}, 28, 48},
- {instance{}, 48, 96},
{top{}, 0, 0},
// Objects
assert(len(tpars) == len(targs))
proj := make(map[*TypeParam]Type, len(tpars))
for i, tpar := range tpars {
- // We must expand type arguments otherwise *instance
- // types end up as components in composite types.
- // TODO(gri) explain why this causes problems, if it does
- targ := expand(targs[i]) // possibly nil
- targs[i] = targ
- proj[tpar.typ.(*TypeParam)] = targ
+ proj[tpar.typ.(*TypeParam)] = targs[i]
}
return &substMap{targs, proj}
}
// for recursive types (example: type T[P any] *T[P]).
subst.typMap = make(map[string]*Named)
}
+
return subst.typ(typ)
}
named := subst.check.newNamed(tname, t, t.Underlying(), t.TParams(), t.methods) // method signatures are updated lazily
named.targs = newTargs
subst.typMap[h] = named
+ t.complete() // 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())
+ dump(">>> underlying: %v", named.underlying)
+ assert(named.underlying != nil)
named.fromRHS = named.underlying // for cycle detection (Checker.validType)
return named
case *TypeParam:
return subst.smap.lookup(t)
- case *instance:
- // TODO(gri) can we avoid the expansion here and just substitute the type parameters?
- return subst.typ(t.expand())
-
default:
panic("unimplemented")
}
return u.s + 1
}
+// TODO(rfindley): we should probably report an error here as well, not
+// just when the type is first instantiated.
func NewT2[U any]() T2[U /* ERROR U has no type constraints */ ] {
- return T2[U /* ERROR U has no type constraints */ ]{}
+ return T2[U]{}
}
func _() {
id uint64 // unique id, for debugging only
obj *TypeName // corresponding type name
index int // type parameter index in source order, starting at 0
- bound Type // *Named or *Interface; underlying type is always *Interface
+ // TODO(rfindley): this could also be Typ[Invalid]. Verify that this is handled correctly.
+ bound Type // *Named or *Interface; underlying type is always *Interface
}
// NewTypeParam returns a new TypeParam. bound can be nil (and set later).
}
buf.WriteString(s + subscript(t.id))
- case *instance:
- buf.WriteByte(instanceMarker) // indicate "non-evaluated" syntactic instance
- writeTypeName(buf, t.base.obj, qf)
- buf.WriteByte('[')
- writeTypeList(buf, t.targs, qf, visited)
- buf.WriteByte(']')
-
case *top:
buf.WriteString("⊤")
// make sure we check instantiation works at least once
// and that the resulting type is valid
check.later(func() {
- t := typ.(*instance).expand()
+ t := expand(typ)
check.validType(t, nil)
})