Skip to content

Commit 24f9eb2

Browse files
committed
[dev.typeparams] go/types: introduce type set abstraction for interfaces
This is a port of CL 329309 to go/types, with minor updates for API differences and to handle methodset.go, which doesn't exist in types2. A couple pre-existing comments were adjusted to match types2. Change-Id: I3fd556e1326013a694ff5edb8518ca24c27bd10b Reviewed-on: https://go-review.googlesource.com/c/go/+/334894 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 b296e54 commit 24f9eb2

19 files changed

+210
-203
lines changed

src/go/types/api_typeparams.go

-4
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,6 @@ func NewTypeParam(obj *TypeName, index int, bound Type) *TypeParam {
2121
func (s *Signature) TParams() []*TypeName { return s._TParams() }
2222
func (s *Signature) SetTParams(tparams []*TypeName) { s._SetTParams(tparams) }
2323

24-
func (t *Interface) HasTypeList() bool { return t._HasTypeList() }
25-
func (t *Interface) IsComparable() bool { return t._IsComparable() }
26-
func (t *Interface) IsConstraint() bool { return t._IsConstraint() }
27-
2824
func (t *Named) TParams() []*TypeName { return t._TParams() }
2925
func (t *Named) TArgs() []Type { return t._TArgs() }
3026
func (t *Named) SetTArgs(args []Type) { t._SetTArgs(args) }

src/go/types/builtins.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -785,7 +785,7 @@ func (check *Checker) applyTypeFunc(f func(Type) Type, x Type) Type {
785785
tpar := NewTypeName(token.NoPos, nil /* = Universe pkg */, "<type parameter>", nil)
786786
ptyp := check.newTypeParam(tpar, 0, &emptyInterface) // assigns type to tpar as a side-effect
787787
tsum := newUnion(rtypes, tildes)
788-
ptyp.bound = &Interface{allMethods: markComplete, allTypes: tsum}
788+
ptyp.bound = &Interface{complete: true, tset: &TypeSet{types: tsum}}
789789

790790
return ptyp
791791
}

src/go/types/call.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,7 @@ func (check *Checker) callExpr(x *operand, call *ast.CallExpr) exprKind {
109109
break
110110
}
111111
if t := asInterface(T); t != nil {
112-
check.completeInterface(token.NoPos, t)
113-
if t._IsConstraint() {
112+
if t.IsConstraint() {
114113
check.errorf(call, _Todo, "cannot use interface %s in conversion (contains type list or is comparable)", T)
115114
break
116115
}

src/go/types/expr.go

-1
Original file line numberDiff line numberDiff line change
@@ -682,7 +682,6 @@ func (check *Checker) implicitTypeAndValue(x *operand, target Type) (Type, const
682682
return Typ[UntypedNil], nil, 0
683683
}
684684
// cannot assign untyped values to non-empty interfaces
685-
check.completeInterface(token.NoPos, t)
686685
if !t.Empty() {
687686
return nil, nil, _InvalidUntypedConversion
688687
}

src/go/types/infer.go

+8-19
Original file line numberDiff line numberDiff line change
@@ -316,24 +316,13 @@ func (w *tpWalker) isParameterized(typ Type) (res bool) {
316316
return w.isParameterized(t.params) || w.isParameterized(t.results)
317317

318318
case *Interface:
319-
if t.allMethods != nil {
320-
// TODO(rFindley) at some point we should enforce completeness here
321-
for _, m := range t.allMethods {
322-
if w.isParameterized(m.typ) {
323-
return true
324-
}
319+
tset := t.typeSet()
320+
for _, m := range tset.methods {
321+
if w.isParameterized(m.typ) {
322+
return true
325323
}
326-
return w.isParameterized(t.allTypes)
327324
}
328-
329-
return t.iterate(func(t *Interface) bool {
330-
for _, m := range t.methods {
331-
if w.isParameterized(m.typ) {
332-
return true
333-
}
334-
}
335-
return w.isParameterizedList(t.embeddeds)
336-
}, nil)
325+
return w.isParameterized(tset.types)
337326

338327
case *Map:
339328
return w.isParameterized(t.key) || w.isParameterized(t.elem)
@@ -471,15 +460,15 @@ func (check *Checker) inferB(tparams []*TypeName, targs []Type, report bool) (ty
471460
// structuralType returns the structural type of a constraint, if any.
472461
func (check *Checker) structuralType(constraint Type) Type {
473462
if iface, _ := under(constraint).(*Interface); iface != nil {
474-
check.completeInterface(token.NoPos, iface)
475-
if u, _ := iface.allTypes.(*Union); u != nil {
463+
types := iface.typeSet().types
464+
if u, _ := types.(*Union); u != nil {
476465
if u.NumTerms() == 1 {
477466
// TODO(gri) do we need to respect tilde?
478467
return u.types[0]
479468
}
480469
return nil
481470
}
482-
return iface.allTypes
471+
return types
483472
}
484473
return nil
485474
}

src/go/types/interface.go

+36-27
Original file line numberDiff line numberDiff line change
@@ -98,17 +98,24 @@ func (check *Checker) interfaceType(ityp *Interface, iface *ast.InterfaceType, d
9898
check.posMap[ityp] = append(check.posMap[ityp], tlist[0].(*ast.UnaryExpr).X.Pos())
9999
}
100100

101+
// All methods and embedded elements for this interface are collected;
102+
// i.e., this interface is may be used in a type set computation.
103+
ityp.complete = true
104+
101105
if len(ityp.methods) == 0 && len(ityp.embeddeds) == 0 {
102106
// empty interface
103-
ityp.allMethods = markComplete
107+
ityp.tset = &topTypeSet
104108
return
105109
}
106110

107111
// sort for API stability
108112
sortMethods(ityp.methods)
109113
sortTypes(ityp.embeddeds)
110114

111-
check.later(func() { check.completeInterface(iface.Pos(), ityp) })
115+
// Compute type set with a non-nil *Checker as soon as possible
116+
// to report any errors. Subsequent uses of type sets should be
117+
// using this computed type set and won't need to pass in a *Checker.
118+
check.later(func() { newTypeSet(check, iface.Pos(), ityp) })
112119
}
113120

114121
func flattenUnion(list []ast.Expr, x ast.Expr) []ast.Expr {
@@ -119,24 +126,26 @@ func flattenUnion(list []ast.Expr, x ast.Expr) []ast.Expr {
119126
return append(list, x)
120127
}
121128

122-
func (check *Checker) completeInterface(pos token.Pos, ityp *Interface) {
123-
if ityp.allMethods != nil {
124-
return
129+
// newTypeSet may be called with check == nil.
130+
// TODO(gri) move this function into typeset.go eventually
131+
func newTypeSet(check *Checker, pos token.Pos, ityp *Interface) *TypeSet {
132+
if ityp.tset != nil {
133+
return ityp.tset
125134
}
126135

127-
// completeInterface may be called via the LookupFieldOrMethod,
128-
// MissingMethod, Identical, or IdenticalIgnoreTags external API
129-
// in which case check will be nil. In this case, type-checking
130-
// must be finished and all interfaces should have been completed.
131-
if check == nil {
132-
panic("internal error: incomplete interface")
136+
// If the interface is not fully set up yet, the type set will
137+
// not be complete, which may lead to errors when using the the
138+
// type set (e.g. missing method). Don't compute a partial type
139+
// set (and don't store it!), so that we still compute the full
140+
// type set eventually. Instead, return the top type set and
141+
// let any follow-on errors play out.
142+
//
143+
// TODO(gri) Consider recording when this happens and reporting
144+
// it as an error (but only if there were no other errors so to
145+
// to not have unnecessary follow-on errors).
146+
if !ityp.complete {
147+
return &topTypeSet
133148
}
134-
completeInterface(check, pos, ityp)
135-
}
136-
137-
// completeInterface may be called with check == nil.
138-
func completeInterface(check *Checker, pos token.Pos, ityp *Interface) {
139-
assert(ityp.allMethods == nil)
140149

141150
if check != nil && trace {
142151
// Types don't generally have position information.
@@ -146,11 +155,11 @@ func completeInterface(check *Checker, pos token.Pos, ityp *Interface) {
146155
pos = ityp.methods[0].pos
147156
}
148157

149-
check.trace(pos, "complete %s", ityp)
158+
check.trace(pos, "type set for %s", ityp)
150159
check.indent++
151160
defer func() {
152161
check.indent--
153-
check.trace(pos, "=> %s (methods = %v, types = %v)", ityp, ityp.allMethods, ityp.allTypes)
162+
check.trace(pos, "=> %s ", ityp.typeSet())
154163
}()
155164
}
156165

@@ -159,7 +168,7 @@ func completeInterface(check *Checker, pos token.Pos, ityp *Interface) {
159168
// have valid interfaces. Mark the interface as complete to avoid
160169
// infinite recursion if the validType check occurs later for some
161170
// reason.
162-
ityp.allMethods = markComplete
171+
ityp.tset = new(TypeSet) // TODO(gri) is this sufficient?
163172

164173
// Methods of embedded interfaces are collected unchanged; i.e., the identity
165174
// of a method I.m's Func Object of an interface I is the same as that of
@@ -229,14 +238,12 @@ func completeInterface(check *Checker, pos token.Pos, ityp *Interface) {
229238
var types Type
230239
switch t := under(typ).(type) {
231240
case *Interface:
232-
if t.allMethods == nil {
233-
completeInterface(check, pos, t)
234-
}
235-
for _, m := range t.allMethods {
241+
tset := newTypeSet(check, pos, t)
242+
for _, m := range tset.methods {
236243
addMethod(pos, m, false) // use embedding position pos rather than m.pos
237244

238245
}
239-
types = t.allTypes
246+
types = tset.types
240247
case *Union:
241248
// TODO(gri) combine with default case once we have
242249
// converted all tests to new notation and we
@@ -273,9 +280,11 @@ func completeInterface(check *Checker, pos token.Pos, ityp *Interface) {
273280

274281
if methods != nil {
275282
sort.Sort(byUniqueMethodName(methods))
276-
ityp.allMethods = methods
283+
ityp.tset.methods = methods
277284
}
278-
ityp.allTypes = allTypes
285+
ityp.tset.types = allTypes
286+
287+
return ityp.tset
279288
}
280289

281290
func sortTypes(list []Type) {

src/go/types/lookup.go

+6-13
Original file line numberDiff line numberDiff line change
@@ -186,9 +186,7 @@ func (check *Checker) rawLookupFieldOrMethod(T Type, addressable bool, pkg *Pack
186186

187187
case *Interface:
188188
// look for a matching method
189-
// TODO(gri) t.allMethods is sorted - use binary search
190-
check.completeInterface(token.NoPos, t)
191-
if i, m := lookupMethod(t.allMethods, pkg, name); m != nil {
189+
if i, m := t.typeSet().LookupMethod(pkg, name); m != nil {
192190
assert(m.typ != nil)
193191
index = concat(e.index, i)
194192
if obj != nil || e.multiples {
@@ -199,9 +197,7 @@ func (check *Checker) rawLookupFieldOrMethod(T Type, addressable bool, pkg *Pack
199197
}
200198

201199
case *_TypeParam:
202-
// only consider explicit methods in the type parameter bound, not
203-
// methods that may be common to all types in the type list.
204-
if i, m := lookupMethod(t.Bound().allMethods, pkg, name); m != nil {
200+
if i, m := t.Bound().typeSet().LookupMethod(pkg, name); m != nil {
205201
assert(m.typ != nil)
206202
index = concat(e.index, i)
207203
if obj != nil || e.multiples {
@@ -307,18 +303,15 @@ func MissingMethod(V Type, T *Interface, static bool) (method *Func, wrongType b
307303
// To improve error messages, also report the wrong signature
308304
// when the method exists on *V instead of V.
309305
func (check *Checker) missingMethod(V Type, T *Interface, static bool) (method, wrongType *Func) {
310-
check.completeInterface(token.NoPos, T)
311-
312306
// fast path for common case
313307
if T.Empty() {
314308
return
315309
}
316310

317311
if ityp := asInterface(V); ityp != nil {
318-
check.completeInterface(token.NoPos, ityp)
319-
// TODO(gri) allMethods is sorted - can do this more efficiently
320-
for _, m := range T.allMethods {
321-
_, f := lookupMethod(ityp.allMethods, m.pkg, m.name)
312+
// TODO(gri) the methods are sorted - could do this more efficiently
313+
for _, m := range T.typeSet().methods {
314+
_, f := ityp.typeSet().LookupMethod(m.pkg, m.name)
322315

323316
if f == nil {
324317
// if m is the magic method == we're ok (interfaces are comparable)
@@ -356,7 +349,7 @@ func (check *Checker) missingMethod(V Type, T *Interface, static bool) (method,
356349
// A concrete type implements T if it implements all methods of T.
357350
Vd, _ := deref(V)
358351
Vn := asNamed(Vd)
359-
for _, m := range T.allMethods {
352+
for _, m := range T.typeSet().methods {
360353
// TODO(gri) should this be calling lookupFieldOrMethod instead (and why not)?
361354
obj, _, _ := check.rawLookupFieldOrMethod(V, false, m.pkg, m.name)
362355

src/go/types/methodset.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,10 @@ func NewMethodSet(T Type) *MethodSet {
157157
}
158158

159159
case *Interface:
160-
mset = mset.add(t.allMethods, e.index, true, e.multiples)
160+
mset = mset.add(t.typeSet().methods, e.index, true, e.multiples)
161161

162162
case *_TypeParam:
163-
mset = mset.add(t.Bound().allMethods, e.index, true, e.multiples)
163+
mset = mset.add(t.Bound().typeSet().methods, e.index, true, e.multiples)
164164
}
165165
}
166166

src/go/types/predicates.go

+4-16
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,6 @@
66

77
package types
88

9-
import (
10-
"go/token"
11-
)
12-
139
// isNamed reports whether typ has a name.
1410
// isNamed may be called with types that are not fully set up.
1511
func isNamed(typ Type) bool {
@@ -109,7 +105,7 @@ func comparable(T Type, seen map[Type]bool) bool {
109105
//
110106
// is not comparable because []byte is not comparable.
111107
if t := asTypeParam(T); t != nil && optype(t) == theTop {
112-
return t.Bound()._IsComparable()
108+
return t.Bound().IsComparable()
113109
}
114110

115111
switch t := optype(T).(type) {
@@ -133,7 +129,7 @@ func comparable(T Type, seen map[Type]bool) bool {
133129
return comparable(t, seen)
134130
})
135131
case *_TypeParam:
136-
return t.Bound()._IsComparable()
132+
return t.Bound().IsComparable()
137133
}
138134
return false
139135
}
@@ -291,16 +287,8 @@ func (check *Checker) identical0(x, y Type, cmpTags bool, p *ifacePair) bool {
291287
// the same names and identical function types. Lower-case method names from
292288
// different packages are always different. The order of the methods is irrelevant.
293289
if y, ok := y.(*Interface); ok {
294-
// If identical0 is called (indirectly) via an external API entry point
295-
// (such as Identical, IdenticalIgnoreTags, etc.), check is nil. But in
296-
// that case, interfaces are expected to be complete and lazy completion
297-
// here is not needed.
298-
if check != nil {
299-
check.completeInterface(token.NoPos, x)
300-
check.completeInterface(token.NoPos, y)
301-
}
302-
a := x.allMethods
303-
b := y.allMethods
290+
a := x.typeSet().methods
291+
b := y.typeSet().methods
304292
if len(a) == len(b) {
305293
// Interface types are the only types where cycles can occur
306294
// that are not "terminated" via named types; and such cycles

src/go/types/sanitize.go

+5-3
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,11 @@ func (s sanitizer) typ(typ Type) Type {
113113
case *Interface:
114114
s.funcList(t.methods)
115115
s.typeList(t.embeddeds)
116-
s.funcList(t.allMethods)
117-
if allTypes := s.typ(t.allTypes); allTypes != t.allTypes {
118-
t.allTypes = allTypes
116+
// TODO(gri) do we need to sanitize type sets?
117+
tset := t.typeSet()
118+
s.funcList(tset.methods)
119+
if types := s.typ(tset.types); types != tset.types {
120+
tset.types = types
119121
}
120122

121123
case *Map:

src/go/types/sizeof_test.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func TestSizeof(t *testing.T) {
2727
{Tuple{}, 12, 24},
2828
{Signature{}, 44, 88},
2929
{Union{}, 24, 48},
30-
{Interface{}, 52, 104},
30+
{Interface{}, 40, 80},
3131
{Map{}, 16, 32},
3232
{Chan{}, 12, 24},
3333
{Named{}, 84, 160},
@@ -48,6 +48,7 @@ func TestSizeof(t *testing.T) {
4848
// Misc
4949
{Scope{}, 40, 80},
5050
{Package{}, 40, 80},
51+
{TypeSet{}, 20, 40},
5152
}
5253
for _, test := range tests {
5354
got := reflect.TypeOf(test.val).Size()

0 commit comments

Comments
 (0)