Skip to content

Commit 2933c45

Browse files
committed
go/types: merge Named type loading and expansion
Named type expansion and loading were conceptually similar: a mechanism for lazily resolving type information in a concurrency-safe manner. Unify them into a 'resolve' method, that delegates to a resolver func to produce type parameters, underlying, and methods. By leveraging the sync.Once field on Named for instance expansion, we get closer to making instance expansion concurrency-safe, and remove the requirement that instPos guard instantiation. This will be cleaned up in a follow-up CL. This also fixes #47887 by causing substituted type instances to be expanded (in the old code, this could be fixed by setting instPos when substituting). For #47910 Fixes #47887 Change-Id: Ifc52a420dde76e3a46ce494fea9bd289bc8aca4c Reviewed-on: https://go-review.googlesource.com/c/go/+/349410 Trust: Robert Findley <[email protected]> Run-TryBot: Robert Findley <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Robert Griesemer <[email protected]>
1 parent 137543b commit 2933c45

File tree

9 files changed

+114
-96
lines changed

9 files changed

+114
-96
lines changed

src/go/types/decl.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ func (check *Checker) validType(typ Type, path []Object) typeInfo {
316316
}
317317

318318
case *Named:
319-
t.expand(check.conf.Environment)
319+
t.resolve(check.conf.Environment)
320320
// don't touch the type if it is from a different package or the Universe scope
321321
// (doing so would lead to a race condition - was issue #35049)
322322
if t.obj.pkg != check.pkg {
@@ -773,7 +773,7 @@ func (check *Checker) collectMethods(obj *TypeName) {
773773
}
774774

775775
if base != nil {
776-
base.load() // TODO(mdempsky): Probably unnecessary.
776+
base.resolve(nil) // TODO(mdempsky): Probably unnecessary.
777777
base.methods = append(base.methods, m)
778778
}
779779
}

src/go/types/instantiate.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,10 @@ func (check *Checker) instance(pos token.Pos, typ Type, targs []Type, env *Envir
116116
}
117117
}
118118
tname := NewTypeName(pos, t.obj.pkg, t.obj.name, nil)
119-
named := check.newNamed(tname, t, nil, nil, nil) // methods and tparams are set when named is loaded
119+
named := check.newNamed(tname, t, nil, nil, nil) // methods and tparams are set when named is resolved
120120
named.targs = NewTypeList(targs)
121121
named.instPos = &pos
122+
named.resolver = expandNamed
122123
if env != nil {
123124
// It's possible that we've lost a race to add named to the environment.
124125
// In this case, use whichever instance is recorded in the environment.

src/go/types/lookup.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ func lookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (o
124124
seen[named] = true
125125

126126
// look for a matching attached method
127-
named.load()
127+
named.resolve(nil)
128128
if i, m := lookupMethod(named.methods, pkg, name); m != nil {
129129
// potential match
130130
// caution: method may not have a proper signature yet

src/go/types/named.go

Lines changed: 44 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ type Named struct {
2222
targs *TypeList // type arguments (after instantiation), or nil
2323
methods []*Func // methods declared for this type (not the method set of this type); signatures are type-checked lazily
2424

25-
resolve func(*Named) ([]*TypeParam, Type, []*Func)
26-
once sync.Once
25+
// resolver may be provided to lazily resolve type parameters, underlying, and methods.
26+
resolver func(*Environment, *Named) (tparams *TypeParamList, underlying Type, methods []*Func)
27+
once sync.Once // ensures that tparams, underlying, and methods are resolved before accessing
2728
}
2829

2930
// NewNamed returns a new named type for the given type name, underlying type, and associated methods.
@@ -36,43 +37,22 @@ func NewNamed(obj *TypeName, underlying Type, methods []*Func) *Named {
3637
return (*Checker)(nil).newNamed(obj, nil, underlying, nil, methods)
3738
}
3839

39-
func (t *Named) load() *Named {
40-
// If t is an instantiated type, it derives its methods and tparams from its
41-
// base type. Since we expect type parameters and methods to be set after a
42-
// call to load, we must load the base and copy here.
43-
//
44-
// underlying is set when t is expanded.
45-
//
46-
// By convention, a type instance is loaded iff its tparams are set.
47-
if t.targs.Len() > 0 && t.tparams == nil {
48-
t.orig.load()
49-
t.tparams = t.orig.tparams
50-
t.methods = t.orig.methods
51-
}
52-
if t.resolve == nil {
40+
func (t *Named) resolve(env *Environment) *Named {
41+
if t.resolver == nil {
5342
return t
5443
}
5544

5645
t.once.Do(func() {
57-
// TODO(mdempsky): Since we're passing t to resolve anyway
46+
// TODO(mdempsky): Since we're passing t to the resolver anyway
5847
// (necessary because types2 expects the receiver type for methods
5948
// on defined interface types to be the Named rather than the
6049
// underlying Interface), maybe it should just handle calling
6150
// SetTypeParams, SetUnderlying, and AddMethod instead? Those
62-
// methods would need to support reentrant calls though. It would
51+
// methods would need to support reentrant calls though. It would
6352
// also make the API more future-proof towards further extensions
6453
// (like SetTypeParams).
65-
66-
tparams, underlying, methods := t.resolve(t)
67-
68-
switch underlying.(type) {
69-
case nil, *Named:
70-
panic("invalid underlying type")
71-
}
72-
73-
t.tparams = bindTParams(tparams)
74-
t.underlying = underlying
75-
t.methods = methods
54+
t.tparams, t.underlying, t.methods = t.resolver(env, t)
55+
t.fromRHS = t.underlying // for cycle detection
7656
})
7757
return t
7858
}
@@ -121,19 +101,19 @@ func (t *Named) _Orig() *Named { return t.orig }
121101

122102
// TypeParams returns the type parameters of the named type t, or nil.
123103
// The result is non-nil for an (originally) parameterized type even if it is instantiated.
124-
func (t *Named) TypeParams() *TypeParamList { return t.load().tparams }
104+
func (t *Named) TypeParams() *TypeParamList { return t.resolve(nil).tparams }
125105

126106
// SetTypeParams sets the type parameters of the named type t.
127-
func (t *Named) SetTypeParams(tparams []*TypeParam) { t.load().tparams = bindTParams(tparams) }
107+
func (t *Named) SetTypeParams(tparams []*TypeParam) { t.resolve(nil).tparams = bindTParams(tparams) }
128108

129109
// TypeArgs returns the type arguments used to instantiate the named type t.
130110
func (t *Named) TypeArgs() *TypeList { return t.targs }
131111

132112
// NumMethods returns the number of explicit methods whose receiver is named type t.
133-
func (t *Named) NumMethods() int { return len(t.load().methods) }
113+
func (t *Named) NumMethods() int { return len(t.resolve(nil).methods) }
134114

135115
// Method returns the i'th method of named type t for 0 <= i < t.NumMethods().
136-
func (t *Named) Method(i int) *Func { return t.load().methods[i] }
116+
func (t *Named) Method(i int) *Func { return t.resolve(nil).methods[i] }
137117

138118
// SetUnderlying sets the underlying type and marks t as complete.
139119
func (t *Named) SetUnderlying(underlying Type) {
@@ -143,18 +123,18 @@ func (t *Named) SetUnderlying(underlying Type) {
143123
if _, ok := underlying.(*Named); ok {
144124
panic("underlying type must not be *Named")
145125
}
146-
t.load().underlying = underlying
126+
t.resolve(nil).underlying = underlying
147127
}
148128

149129
// AddMethod adds method m unless it is already in the method list.
150130
func (t *Named) AddMethod(m *Func) {
151-
t.load()
131+
t.resolve(nil)
152132
if i, _ := lookupMethod(t.methods, m.pkg, m.name); i < 0 {
153133
t.methods = append(t.methods, m)
154134
}
155135
}
156136

157-
func (t *Named) Underlying() Type { return t.load().expand(nil).underlying }
137+
func (t *Named) Underlying() Type { return t.resolve(nil).underlying }
158138
func (t *Named) String() string { return TypeString(t, nil) }
159139

160140
// ----------------------------------------------------------------------------
@@ -240,43 +220,37 @@ func (n *Named) setUnderlying(typ Type) {
240220
}
241221
}
242222

243-
// expand ensures that the underlying type of n is instantiated.
223+
// expandNamed ensures that the underlying type of n is instantiated.
244224
// The underlying type will be Typ[Invalid] if there was an error.
245-
func (n *Named) expand(env *Environment) *Named {
246-
if n.instPos != nil {
247-
// n must be loaded before instantiation, in order to have accurate
248-
// tparams. This is done implicitly by the call to n.TypeParams, but making
249-
// it explicit is harmless: load is idempotent.
250-
n.load()
251-
var u Type
252-
if n.check.validateTArgLen(*n.instPos, n.tparams.Len(), n.targs.Len()) {
253-
// TODO(rfindley): handling an optional Checker and Environment here (and
254-
// in subst) feels overly complicated. Can we simplify?
255-
if env == nil {
256-
if n.check != nil {
257-
env = n.check.conf.Environment
258-
} else {
259-
// If we're instantiating lazily, we might be outside the scope of a
260-
// type-checking pass. In that case we won't have a pre-existing
261-
// environment, but don't want to create a duplicate of the current
262-
// instance in the process of expansion.
263-
env = NewEnvironment()
264-
}
265-
h := env.typeHash(n.orig, n.targs.list())
266-
// add the instance to the environment to avoid infinite recursion.
267-
// addInstance may return a different, existing instance, but we
268-
// shouldn't return that instance from expand.
269-
env.typeForHash(h, n)
225+
func expandNamed(env *Environment, n *Named) (*TypeParamList, Type, []*Func) {
226+
n.orig.resolve(env)
227+
228+
var u Type
229+
if n.check.validateTArgLen(*n.instPos, n.orig.tparams.Len(), n.targs.Len()) {
230+
// TODO(rfindley): handling an optional Checker and Environment here (and
231+
// in subst) feels overly complicated. Can we simplify?
232+
if env == nil {
233+
if n.check != nil {
234+
env = n.check.conf.Environment
235+
} else {
236+
// If we're instantiating lazily, we might be outside the scope of a
237+
// type-checking pass. In that case we won't have a pre-existing
238+
// environment, but don't want to create a duplicate of the current
239+
// instance in the process of expansion.
240+
env = NewEnvironment()
270241
}
271-
u = n.check.subst(*n.instPos, n.orig.underlying, makeSubstMap(n.TypeParams().list(), n.targs.list()), env)
272-
} else {
273-
u = Typ[Invalid]
242+
h := env.typeHash(n.orig, n.targs.list())
243+
// add the instance to the environment to avoid infinite recursion.
244+
// addInstance may return a different, existing instance, but we
245+
// shouldn't return that instance from expand.
246+
env.typeForHash(h, n)
274247
}
275-
n.underlying = u
276-
n.fromRHS = u
277-
n.instPos = nil
248+
u = n.check.subst(*n.instPos, n.orig.underlying, makeSubstMap(n.orig.tparams.list(), n.targs.list()), env)
249+
} else {
250+
u = Typ[Invalid]
278251
}
279-
return n
252+
n.instPos = nil
253+
return n.orig.tparams, u, n.orig.methods
280254
}
281255

282256
// safeUnderlying returns the underlying of typ without expanding instances, to
@@ -285,7 +259,7 @@ func (n *Named) expand(env *Environment) *Named {
285259
// TODO(rfindley): eliminate this function or give it a better name.
286260
func safeUnderlying(typ Type) Type {
287261
if t, _ := typ.(*Named); t != nil {
288-
return t.load().underlying
262+
return t.resolve(nil).underlying
289263
}
290264
return typ.Underlying()
291265
}

src/go/types/object.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,9 +232,21 @@ func NewTypeName(pos token.Pos, pkg *Package, name string, typ Type) *TypeName {
232232

233233
// _NewTypeNameLazy returns a new defined type like NewTypeName, but it
234234
// lazily calls resolve to finish constructing the Named object.
235-
func _NewTypeNameLazy(pos token.Pos, pkg *Package, name string, resolve func(named *Named) (tparams []*TypeParam, underlying Type, methods []*Func)) *TypeName {
235+
func _NewTypeNameLazy(pos token.Pos, pkg *Package, name string, load func(named *Named) (tparams []*TypeParam, underlying Type, methods []*Func)) *TypeName {
236236
obj := NewTypeName(pos, pkg, name, nil)
237-
NewNamed(obj, nil, nil).resolve = resolve
237+
238+
resolve := func(_ *Environment, t *Named) (*TypeParamList, Type, []*Func) {
239+
tparams, underlying, methods := load(t)
240+
241+
switch underlying.(type) {
242+
case nil, *Named:
243+
panic(fmt.Sprintf("invalid underlying type %T", t.underlying))
244+
}
245+
246+
return bindTParams(tparams), underlying, methods
247+
}
248+
249+
NewNamed(obj, nil, nil).resolver = resolve
238250
return obj
239251
}
240252

src/go/types/signature.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ func (check *Checker) funcType(sig *Signature, recvPar *ast.FieldList, ftyp *ast
200200
var err string
201201
switch T := rtyp.(type) {
202202
case *Named:
203-
T.expand(nil)
203+
T.resolve(check.conf.Environment)
204204
// The receiver type may be an instantiated type referred to
205205
// by an alias (which cannot have receiver parameters for now).
206206
if T.TypeArgs() != nil && sig.RecvTypeParams() == nil {

src/go/types/subst.go

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -182,13 +182,19 @@ func (subst *subster) typ(typ Type) Type {
182182
}
183183
}
184184

185-
if t.TypeParams().Len() == 0 {
185+
// subst is called by expandNamed, so in this function we need to be
186+
// careful not to call any methods that would cause t to be expanded: doing
187+
// so would result in deadlock.
188+
//
189+
// So we call t.orig.TypeParams() rather than t.TypeParams() here and
190+
// below.
191+
if t.orig.TypeParams().Len() == 0 {
186192
dump(">>> %s is not parameterized", t)
187193
return t // type is not parameterized
188194
}
189195

190196
var newTArgs []Type
191-
assert(t.targs.Len() == t.TypeParams().Len())
197+
assert(t.targs.Len() == t.orig.TypeParams().Len())
192198

193199
// already instantiated
194200
dump(">>> %s already instantiated", t)
@@ -201,7 +207,7 @@ func (subst *subster) typ(typ Type) Type {
201207
if new_targ != targ {
202208
dump(">>> substituted %d targ %s => %s", i, targ, new_targ)
203209
if newTArgs == nil {
204-
newTArgs = make([]Type, t.TypeParams().Len())
210+
newTArgs = make([]Type, t.orig.TypeParams().Len())
205211
copy(newTArgs, t.targs.list())
206212
}
207213
newTArgs[i] = new_targ
@@ -221,25 +227,22 @@ func (subst *subster) typ(typ Type) Type {
221227
return named
222228
}
223229

224-
// Create a new named type and populate the environment to avoid endless
230+
t.orig.resolve(subst.env)
231+
// Create a new instance and populate the environment to avoid endless
225232
// recursion. The position used here is irrelevant because validation only
226233
// occurs on t (we don't call validType on named), but we use subst.pos to
227234
// help with debugging.
228-
tname := NewTypeName(subst.pos, t.obj.pkg, t.obj.name, nil)
229-
t.load()
230-
// It's ok to provide a nil *Checker because the newly created type
231-
// doesn't need to be (lazily) expanded; it's expanded below.
232-
named := (*Checker)(nil).newNamed(tname, t.orig, nil, t.tparams, t.methods) // t is loaded, so tparams and methods are available
233-
named.targs = NewTypeList(newTArgs)
234-
subst.env.typeForHash(h, named)
235-
t.expand(subst.env) // must happen after env update to avoid infinite recursion
236-
237-
// do the substitution
238-
dump(">>> subst %s with %s (new: %s)", t.underlying, subst.smap, newTArgs)
239-
named.underlying = subst.typOrNil(t.underlying)
240-
dump(">>> underlying: %v", named.underlying)
235+
named := subst.check.instance(subst.pos, t.orig, newTArgs, subst.env).(*Named)
236+
// TODO(rfindley): we probably don't need to resolve here. Investigate if
237+
// this can be removed.
238+
named.resolve(subst.env)
241239
assert(named.underlying != nil)
242-
named.fromRHS = named.underlying // for consistency, though no cycle detection is necessary
240+
241+
// Note that if we were to expose substitution more generally (not just in
242+
// the context of a declaration), we'd have to substitute in
243+
// named.underlying as well.
244+
//
245+
// But this is unnecessary for now.
243246

244247
return named
245248

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright 2021 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package p
6+
7+
type Fooer[t any] interface {
8+
foo(Barer[t])
9+
}
10+
type Barer[t any] interface {
11+
bar(Bazer[t])
12+
}
13+
type Bazer[t any] interface {
14+
Fooer[t]
15+
baz(t)
16+
}
17+
18+
type Int int
19+
20+
func (n Int) baz(int) {}
21+
func (n Int) foo(b Barer[int]) { b.bar(n) }
22+
23+
type F[t any] interface { f(G[t]) }
24+
type G[t any] interface { g(H[t]) }
25+
type H[t any] interface { F[t] }
26+
27+
type T struct{}
28+
func (n T) f(b G[T]) { b.g(n) }

src/go/types/type.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ func asInterface(t Type) *Interface {
114114
func asNamed(t Type) *Named {
115115
e, _ := t.(*Named)
116116
if e != nil {
117-
e.expand(nil)
117+
e.resolve(nil)
118118
}
119119
return e
120120
}

0 commit comments

Comments
 (0)