diff --git a/go/types/call.go b/go/types/call.go index 67ddac3e..60e3cca9 100644 --- a/go/types/call.go +++ b/go/types/call.go @@ -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() } diff --git a/go/types/eval.go b/go/types/eval.go index 4fc161dc..028f5664 100644 --- a/go/types/eval.go +++ b/go/types/eval.go @@ -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 diff --git a/go/types/expr.go b/go/types/expr.go index 5b7b9575..74bdc42a 100644 --- a/go/types/expr.go +++ b/go/types/expr.go @@ -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) diff --git a/go/types/lookup.go b/go/types/lookup.go index 8b7842e4..296745bc 100644 --- a/go/types/lookup.go +++ b/go/types/lookup.go @@ -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 } diff --git a/go/types/methodset.go b/go/types/methodset.go index 70a838fb..83fa3ec0 100644 --- a/go/types/methodset.go +++ b/go/types/methodset.go @@ -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() } diff --git a/go/types/operand.go b/go/types/operand.go index 43d70b71..f713a9ac 100644 --- a/go/types/operand.go +++ b/go/types/operand.go @@ -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() } diff --git a/go/types/resolver.go b/go/types/resolver.go index 2e155864..f8efe583 100644 --- a/go/types/resolver.go +++ b/go/types/resolver.go @@ -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) diff --git a/go/types/scope.go b/go/types/scope.go index 649dd339..ed03219e 100644 --- a/go/types/scope.go +++ b/go/types/scope.go @@ -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. diff --git a/go/types/types.go b/go/types/types.go index 3528edad..4b1f4337 100644 --- a/go/types/types.go +++ b/go/types/types.go @@ -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) } diff --git a/ssa/create.go b/ssa/create.go index 5ca4bc01..5203c6c9 100644 --- a/ssa/create.go +++ b/ssa/create.go @@ -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)) }