go.tools/go/types: Method sets for all types

- fixed method set computation
- lazily compute it for a type on demand
- lazy allocation of various temporary maps

Also:
- removed Interface.IsEmpty, Scope.IsEmpty
  (these convenience functions didn't carry their weight)
- cosmetic changes

Next:
- consolidate various lookup APIs
- use lazily computed method sets for various checks
  instead of individual lookups

R=adonovan
CC=golang-dev
https://golang.org/cl/11385044
This commit is contained in:
Robert Griesemer 2013-07-16 22:18:08 -07:00
parent 2822addeae
commit 8901caa2b3
10 changed files with 228 additions and 111 deletions

View File

@ -236,7 +236,7 @@ func (check *checker) selector(x *operand, e *ast.SelectorExpr) {
}
// verify that m is in the method set of x.typ
// (the receiver is nil if f is an interface method)
// (the receiver is nil if m is an interface method)
if recv := m.typ.(*Signature).recv; recv != nil {
if _, isPtr := deref(recv.typ); isPtr && !indirect {
check.invalidOp(e.Pos(), "%s is not in method set of %s", sel, x.typ)
@ -264,14 +264,8 @@ func (check *checker) selector(x *operand, e *ast.SelectorExpr) {
case *Field:
x.mode = variable
x.typ = obj.typ
case *Func:
// TODO(gri) Temporary check to verify corresponding lookup via method sets.
// Remove eventually.
if m := NewMethodSet(x.typ).Lookup(check.pkg, sel); m != obj {
check.dump("%s: %v", e.Pos(), obj.name)
panic("method sets and lookup don't agree")
}
case *Func:
// TODO(gri) This code appears elsewhere, too. Factor!
// verify that obj is in the method set of x.typ (or &(x.typ) if x is addressable)
// (the receiver is nil if obj is an interface method)
@ -287,8 +281,35 @@ func (check *checker) selector(x *operand, e *ast.SelectorExpr) {
}
}
if debug {
// Verify that LookupFieldOrMethod and MethodSet.Lookup agree.
typ := x.typ
if x.mode == variable {
// If typ is not an (unnamed) pointer, use *typ instead,
// because the the method set of *typ includes the methods
// of typ.
// Variables are addressable, so we can always take their
// address.
if _, isPtr := typ.(*Pointer); !isPtr {
typ = &Pointer{base: typ}
}
}
// If we created a synthetic pointer type above, we will throw
// away the method set computed here after use.
// TODO(gri) Consider also using a method set cache for the lifetime
// of checker once we rely on MethodSet lookup instead of individual
// lookup.
mset := typ.MethodSet()
if m := mset.Lookup(check.pkg, sel); m == nil || m.Func != obj {
check.dump("%s: (%s).%v -> %s", e.Pos(), typ, obj.name, m)
check.dump("%s\n", mset)
panic("method sets and lookup don't agree")
}
}
x.mode = value
x.typ = obj.typ
default:
unreachable()
}

View File

@ -29,10 +29,6 @@ func New(str string) Type {
return typ
}
// TODO(gri): Try to find a better name than Eval. Not everybody
// agrees that it's the best name for the functionality provided.
// Change EvalNode in correspondence.
// Eval returns the type and, if constant, the value for the
// expression or type literal string str evaluated in scope.
// If the expression contains function literals, the function

View File

@ -430,7 +430,7 @@ func (check *checker) convertUntyped(x *operand, target Type) {
return // error already reported
}
case *Interface:
if !x.isNil() && !t.IsEmpty() /* empty interfaces are ok */ {
if !x.isNil() && t.NumMethods() > 0 /* empty interfaces are ok */ {
goto Error
}
// Update operand types to the default type rather then
@ -443,7 +443,7 @@ func (check *checker) convertUntyped(x *operand, target Type) {
target = Typ[UntypedNil]
} else {
// cannot assign untyped values to non-empty interfaces
if !t.IsEmpty() {
if t.NumMethods() > 0 {
goto Error
}
target = defaultType(x.typ)

View File

@ -72,8 +72,10 @@ func LookupFieldOrMethod(typ Type, pkg *Package, name string) (obj Object, index
}
func lookupFieldOrMethod(typ Type, pkg *Package, name string) (obj Object, index []int, indirect bool) {
// WARNING: The code in this function is extremely subtle - do not modify casually!
if name == "_" {
return // empty fields/methods are never found
return // blank fields/methods are never found
}
// Start with typ as single entry at lowest depth.
@ -82,14 +84,14 @@ func lookupFieldOrMethod(typ Type, pkg *Package, name string) (obj Object, index
t, _ := typ.(*Named)
current := []embeddedType{{t, nil, isPtr, false}}
// named types that we have seen already
seen := make(map[*Named]bool)
// named types that we have seen already, allocated lazily
var seen map[*Named]bool
// search current depth
for len(current) > 0 {
var next []embeddedType // embedded types found at current depth
// look for (pkg, name) in all types at this depth
// look for (pkg, name) in all types at current depth
for _, e := range current {
// The very first time only, e.typ may be nil.
// In this case, we don't have a named type and
@ -98,11 +100,14 @@ func lookupFieldOrMethod(typ Type, pkg *Package, name string) (obj Object, index
if seen[e.typ] {
// We have seen this type before, at a more shallow depth
// (note that multiples of this type at the current depth
// were eliminated before). The type at that depth shadows
// were consolidated before). The type at that depth shadows
// this same type at the current depth, so we can ignore
// this one.
continue
}
if seen == nil {
seen = make(map[*Named]bool)
}
seen[e.typ] = true
// look for a matching attached method
@ -224,7 +229,7 @@ func MissingMethod(typ Type, T *Interface) (method *Func, wrongType bool) {
// Note: This is stronger than the current spec. Should the spec require this?
// fast path for common case
if T.IsEmpty() {
if T.NumMethods() == 0 {
return
}

View File

@ -10,21 +10,52 @@ import (
"bytes"
"fmt"
"sort"
"sync"
)
// A MethodSet is an ordered set of methods.
// TODO(gri) Move Method and accessors to objects.go.
// A Method represents a concrete or abstract (interface)
// method of a method set.
type Method struct {
*Func
recv Type
index []int
indirect bool
}
// Recv returns the receiver type for m, which is the type
// for which the method set containing m was computed. For
// interface methods, the receiver type is the type of the
// interface.
func (m *Method) Recv() Type { return m.recv }
// Index describes the path to the concrete (possibly embedded)
// function implementing this method. See LookupFieldOrMethod
// for details.
func (m *Method) Index() []int { return m.index }
// Indirect reports whether any pointer indirections was
// required to get from a value of m's receiver type to
// the receiver type of the concrete function implementing m.
// For interface methods, Indirect is undefined.
func (m *Method) Indirect() bool { return m.indirect }
// A MethodSet is an ordered set of concrete or abstract (interface) methods.
// The zero value for a MethodSet is a ready-to-use empty method set.
type MethodSet struct {
list []*Func
list []*Method
}
func (s *MethodSet) String() string {
var buf bytes.Buffer
fmt.Fprint(&buf, "MethodSet{")
if len(s.list) > 0 {
fmt.Fprintln(&buf)
if s.Len() == 0 {
return "MethodSet {}"
}
var buf bytes.Buffer
fmt.Fprintln(&buf, "MethodSet {")
for _, m := range s.list {
fmt.Fprintf(&buf, "\t%s\n", m.uniqueName())
fmt.Fprintf(&buf, "\t%s -> %s\n", m.uniqueName(), m.Func)
}
fmt.Fprintln(&buf, "}")
return buf.String()
@ -33,18 +64,20 @@ func (s *MethodSet) String() string {
// Len returns the number of methods in s.
func (s *MethodSet) Len() int { return len(s.list) }
// At returns the i'th method in s.
func (s *MethodSet) At(i int) *Func { return s.list[i] }
// At returns the i'th method in s for 0 <= i < s.Len().
func (s *MethodSet) At(i int) *Method { return s.list[i] }
// Lookup returns the method with matching package and name, or nil if not found.
func (s *MethodSet) Lookup(pkg *Package, name string) *Func {
key := (&object{pkg: pkg, name: name}).uniqueName()
func (s *MethodSet) Lookup(pkg *Package, name string) *Method {
if s.Len() == 0 {
return nil
}
key := (&object{pkg: pkg, name: name}).uniqueName()
i := sort.Search(len(s.list), func(i int) bool {
m := s.list[i]
return m.uniqueName() >= key
})
if i < len(s.list) {
m := s.list[i]
if m.uniqueName() == key {
@ -54,12 +87,39 @@ func (s *MethodSet) Lookup(pkg *Package, name string) *Func {
return nil
}
// Shared empty method set.
var emptyMethodSet MethodSet
// A cachedMethodSet provides access to a method set
// for a given type by computing it once on demand,
// and then caching it for future use. Threadsafe.
type cachedMethodSet struct {
mset *MethodSet
mu sync.RWMutex // protects mset
}
// Of returns the (possibly cached) method set for typ.
// Threadsafe.
func (c *cachedMethodSet) of(typ Type) *MethodSet {
c.mu.RLock()
mset := c.mset
c.mu.RUnlock()
if mset == nil {
mset = NewMethodSet(typ)
c.mu.Lock()
c.mset = mset
c.mu.Unlock()
}
return mset
}
// NewMethodSet computes the method set for the given type.
// BUG(gri): The pointer-ness of the receiver type is still ignored.
// It always returns a non-nil method set, even if it is empty.
func NewMethodSet(typ Type) *MethodSet {
// method set up to the current depth
// TODO(gri) allocate lazily, method sets are often empty
base := make(methodSet)
// WARNING: The code in this function is extremely subtle - do not modify casually!
// method set up to the current depth, allocated lazily
var base methodSet
// Start with typ as single entry at lowest depth.
// If typ is not a named type, insert a nil type instead.
@ -67,16 +127,16 @@ func NewMethodSet(typ Type) *MethodSet {
t, _ := typ.(*Named)
current := []embeddedType{{t, nil, isPtr, false}}
// named types that we have seen already
seen := make(map[*Named]bool)
// named types that we have seen already, allocated lazily
var seen map[*Named]bool
// collect methods at current depth
for len(current) > 0 {
var next []embeddedType // embedded types found at current depth
// field and method sets for current depth
fset := make(fieldSet)
mset := make(methodSet)
// field and method sets at current depth, allocated lazily
var fset fieldSet
var mset methodSet
for _, e := range current {
// The very first time only, e.typ may be nil.
@ -86,14 +146,17 @@ func NewMethodSet(typ Type) *MethodSet {
if seen[e.typ] {
// We have seen this type before, at a more shallow depth
// (note that multiples of this type at the current depth
// were eliminated before). The type at that depth shadows
// were consolidated before). The type at that depth shadows
// this same type at the current depth, so we can ignore
// this one.
continue
}
if seen == nil {
seen = make(map[*Named]bool)
}
seen[e.typ] = true
mset.add(e.typ.methods, e.multiples)
mset = mset.add(e.typ.methods, e.index, e.indirect, e.multiples)
// continue with underlying type
typ = e.typ.underlying
@ -101,8 +164,8 @@ func NewMethodSet(typ Type) *MethodSet {
switch t := typ.(type) {
case *Struct:
for _, f := range t.fields {
fset.add(f, e.multiples)
for i, f := range t.fields {
fset = fset.add(f, e.multiples)
// Embedded fields are always of the form T or *T where
// T is a named type. If typ appeared multiple times at
@ -110,16 +173,16 @@ func NewMethodSet(typ Type) *MethodSet {
// depth.
if f.anonymous {
// Ignore embedded basic types - only user-defined
// named types can have methods or have struct fields.
// named types can have methods or struct fields.
typ, isPtr := deref(f.typ)
if t, _ := typ.(*Named); t != nil {
next = append(next, embeddedType{t, nil, e.indirect || isPtr, e.multiples})
next = append(next, embeddedType{t, concat(e.index, i), e.indirect || isPtr, e.multiples})
}
}
}
case *Interface:
mset.add(t.methods, e.multiples)
mset = mset.add(t.methods, e.index, true, e.multiples)
}
}
@ -131,6 +194,9 @@ func NewMethodSet(typ Type) *MethodSet {
if _, found := fset[k]; found {
m = nil // collision
}
if base == nil {
base = make(methodSet)
}
base[k] = m
}
}
@ -141,7 +207,10 @@ func NewMethodSet(typ Type) *MethodSet {
for k, f := range fset {
if f == nil {
if _, found := base[k]; !found {
base[k] = nil
if base == nil {
base = make(methodSet)
}
base[k] = nil // collision
}
}
}
@ -149,60 +218,84 @@ func NewMethodSet(typ Type) *MethodSet {
current = consolidateMultiples(next)
}
if len(base) == 0 {
return &emptyMethodSet
}
// collect methods
var list []*Func
var list []*Method
for _, m := range base {
if m != nil {
m.recv = typ
list = append(list, m)
}
}
sort.Sort(byUniqueName(list))
return &MethodSet{list}
}
// A fieldSet is a set of fields and name collisions.
// A conflict indicates that multiple fields with the same package and name appeared.
// A collision indicates that multiple fields with the
// same unique name appeared.
type fieldSet map[string]*Field // a nil entry indicates a name collision
// Add adds field f to the field set s.
// If multiples is set, f appears multiple times
// and is treated as a collision at this level.
func (s fieldSet) add(f *Field, multiples bool) {
// and is treated as a collision.
func (s fieldSet) add(f *Field, multiples bool) fieldSet {
if s == nil {
s = make(fieldSet)
}
key := f.uniqueName()
// if f is not in the set, add it
if !multiples {
if _, found := s[key]; !found {
s[key] = f
return
return s
}
}
s[key] = nil // collision
return s
}
// A methodSet is a set of methods and name collisions.
// A conflict indicates that multiple methods with the same package and name appeared.
type methodSet map[string]*Func // a nil entry indicates a name collision
// A collision indicates that multiple methods with the
// same unique name appeared.
type methodSet map[string]*Method // a nil entry indicates a name collision
// Add adds all methods in list to the method set s.
// If multiples is set, every method in list appears multiple times
// and is treated as a collision at this level.
func (s methodSet) add(list []*Func, multiples bool) {
for _, m := range list {
key := m.uniqueName()
// if m is not in the set, add it
// Add adds all functions in list to the method set s.
// If multiples is set, every function in list appears multiple times
// and is treated as a collision.
func (s methodSet) add(list []*Func, index []int, indirect bool, multiples bool) methodSet {
if len(list) == 0 {
return s
}
if s == nil {
s = make(methodSet)
}
for i, f := range list {
key := f.uniqueName()
// if f is not in the set, add it
if !multiples {
if _, found := s[key]; !found {
s[key] = m
if _, found := s[key]; !found && (indirect || !ptrRecv(f)) {
s[key] = &Method{Func: f, index: concat(index, i), indirect: indirect}
continue
}
}
s[key] = nil // collision
}
return s
}
// ptrRecv reports whether the receiver is of the form *T.
// The receiver must exist.
func ptrRecv(f *Func) bool {
_, isPtr := deref(f.typ.(*Signature).recv.typ)
return isPtr
}
// byUniqueName function lists can be sorted by their unique names.
type byUniqueName []*Func
type byUniqueName []*Method
func (a byUniqueName) Len() int { return len(a) }
func (a byUniqueName) Less(i, j int) bool { return a[i].uniqueName() < a[j].uniqueName() }

View File

@ -194,7 +194,7 @@ func (x *operand) isAssignableTo(ctxt *Context, T Type) bool {
return Vb.kind == UntypedBool && isBoolean(Tu)
}
case *Interface:
return x.isNil() || t.IsEmpty()
return x.isNil() || t.NumMethods() == 0
case *Pointer, *Signature, *Slice, *Map, *Chan:
return x.isNil()
}

View File

@ -510,7 +510,7 @@ func (check *checker) declareType(obj *TypeName, typ ast.Expr, def *Named, cycle
}
// typecheck method signatures
var methods []*Func
if !scope.IsEmpty() {
if scope.NumEntries() > 0 {
for _, obj := range scope.entries {
m := obj.(*Func)

View File

@ -39,9 +39,7 @@ func NewScope(parent *Scope) *Scope {
}
// Parent returns the scope's containing (parent) scope.
func (s *Scope) Parent() *Scope {
return s.parent
}
func (s *Scope) Parent() *Scope { return s.parent }
// Node returns the ast.Node responsible for this scope,
// which may be one of the following:
@ -59,9 +57,7 @@ func (s *Scope) Parent() *Scope {
//
// The result is nil if there is no corresponding node
// (universe and package scopes).
func (s *Scope) Node() ast.Node {
return s.node
}
func (s *Scope) Node() ast.Node { return s.node }
// NumEntries() returns the number of scope entries.
// If s == nil, the result is 0.
@ -72,16 +68,8 @@ func (s *Scope) NumEntries() int {
return len(s.entries)
}
// IsEmpty reports whether the scope is empty.
// If s == nil, the result is true.
func (s *Scope) IsEmpty() bool {
return s == nil || len(s.entries) == 0
}
// At returns the i'th scope entry for 0 <= i < NumEntries().
func (s *Scope) At(i int) Object {
return s.entries[i]
}
func (s *Scope) At(i int) Object { return s.entries[i] }
// NumChildren() returns the number of scopes nested in s.
// If s == nil, the result is 0.
@ -93,9 +81,7 @@ func (s *Scope) NumChildren() int {
}
// Child returns the i'th child scope for 0 <= i < NumChildren().
func (s *Scope) Child(i int) *Scope {
return s.children[i]
}
func (s *Scope) Child(i int) *Scope { return s.children[i] }
// Lookup returns the object in scope s with the given package
// and name if such an object exists; otherwise the result is nil.

View File

@ -14,6 +14,9 @@ type Type interface {
// Underlying returns the underlying type of a type.
Underlying() Type
// MethodSet returns the method set of a type.
MethodSet() *MethodSet
// String returns a string representation of a type.
String() string
}
@ -121,9 +124,11 @@ func (s *Slice) Elem() Type { return s.elt }
// A Struct represents a struct type.
type Struct struct {
fields []*Field
tags []string // field tags; nil if there are no tags
offsets []int64 // field offsets in bytes, lazily computed
fields []*Field
tags []string // field tags; nil if there are no tags
// TODO(gri) access to offsets is not threadsafe - fix this
offsets []int64 // field offsets in bytes, lazily initialized
mset cachedMethodSet // method set, lazily initialized
}
// NewStruct returns a new struct with the given fields and corresponding field tags.
@ -150,11 +155,12 @@ func (s *Struct) Tag(i int) string {
// A Pointer represents a pointer type.
type Pointer struct {
base Type
base Type // element type
mset cachedMethodSet // method set, lazily initialized
}
// NewPointer returns a new pointer type for the given element (base) type.
func NewPointer(elem Type) *Pointer { return &Pointer{elem} }
func NewPointer(elem Type) *Pointer { return &Pointer{base: elem} }
// Elem returns the element type for the given pointer p.
func (p *Pointer) Elem() Type { return p.base }
@ -266,30 +272,24 @@ type Builtin struct {
}
// Name returns the name of the built-in function b.
func (b *Builtin) Name() string {
return b.name
}
func (b *Builtin) Name() string { return b.name }
// An Interface represents an interface type.
type Interface struct {
methods []*Func
methods []*Func // methods declared with or embedded in this interface
mset cachedMethodSet // method set for interface, lazily initialized
}
// NewInterface returns a new interface for the given methods.
func NewInterface(methods []*Func) *Interface {
return &Interface{methods}
return &Interface{methods: methods}
}
// NumMethods returns the number of methods of interface t.
func (t *Interface) NumMethods() int { return len(t.methods) }
// Method returns the i'th method of interface t for 0 <= i < t.NumMethods().
func (t *Interface) Method(i int) *Func {
return t.methods[i]
}
// IsEmpty() reports whether t is an empty interface.
func (t *Interface) IsEmpty() bool { return len(t.methods) == 0 }
func (t *Interface) Method(i int) *Func { return t.methods[i] }
// A Map represents a map type.
type Map struct {
@ -326,10 +326,11 @@ func (c *Chan) Elem() Type { return c.elt }
// A Named represents a named type.
type Named struct {
obj *TypeName // corresponding declared object
underlying Type // possibly a *Named if !complete; never a *Named if complete
complete bool // if set, the underlying type has been determined
methods []*Func // methods declared for this type (not the method set of this type)
obj *TypeName // corresponding declared object
underlying Type // possibly a *Named if !complete; never a *Named if complete
complete bool // if set, the underlying type has been determined
methods []*Func // methods declared for this type (not the method set of this type)
mset cachedMethodSet // method set for this type, lazily initialized
}
// NewNamed returns a new named type for the given type name, underlying type, and associated methods.
@ -339,7 +340,7 @@ func NewNamed(obj *TypeName, underlying Type, methods []*Func) *Named {
if _, ok := underlying.(*Named); ok {
panic("types.NewNamed: underlying type must not be *Named")
}
typ := &Named{obj, underlying, true, methods}
typ := &Named{obj: obj, underlying: underlying, complete: true, methods: methods}
if obj.typ == nil {
obj.typ = typ
}
@ -353,9 +354,7 @@ func (t *Named) Obj() *TypeName { return t.obj }
func (t *Named) NumMethods() int { return len(t.methods) }
// Method returns the i'th method of named type t for 0 <= i < t.NumMethods().
func (t *Named) Method(i int) *Func {
return t.methods[i]
}
func (t *Named) Method(i int) *Func { return t.methods[i] }
// Implementations for Type methods.
@ -372,6 +371,19 @@ func (t *Map) Underlying() Type { return t }
func (t *Chan) Underlying() Type { return t }
func (t *Named) Underlying() Type { return t.underlying }
func (t *Basic) MethodSet() *MethodSet { return &emptyMethodSet }
func (t *Array) MethodSet() *MethodSet { return &emptyMethodSet }
func (t *Slice) MethodSet() *MethodSet { return &emptyMethodSet }
func (t *Struct) MethodSet() *MethodSet { return t.mset.of(t) }
func (t *Pointer) MethodSet() *MethodSet { return t.mset.of(t) }
func (t *Tuple) MethodSet() *MethodSet { return &emptyMethodSet }
func (t *Signature) MethodSet() *MethodSet { return &emptyMethodSet }
func (t *Builtin) MethodSet() *MethodSet { return &emptyMethodSet }
func (t *Interface) MethodSet() *MethodSet { return t.mset.of(t) }
func (t *Map) MethodSet() *MethodSet { return &emptyMethodSet }
func (t *Chan) MethodSet() *MethodSet { return &emptyMethodSet }
func (t *Named) MethodSet() *MethodSet { return t.mset.of(t) }
func (t *Basic) String() string { return typeString(t) }
func (t *Array) String() string { return typeString(t) }
func (t *Slice) String() string { return typeString(t) }

View File

@ -143,6 +143,10 @@ func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node) {
pkg.Prog.concreteMethods[method] = fn
}
case *types.Method:
// TODO(adonovan): do something more sensible here?
memberFromObject(pkg, obj.Func, syntax)
default: // (incl. *types.Package)
panic(fmt.Sprintf("unexpected Object type: %T", obj))
}