methodset.go raw

   1  // Copyright 2013 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  // This file implements method sets.
   6  
   7  package types
   8  
   9  import (
  10  	"fmt"
  11  	"sort"
  12  	"strings"
  13  )
  14  
  15  // A MethodSet is an ordered set of concrete or abstract (interface) methods;
  16  // a method is a [MethodVal] selection, and they are ordered by ascending m.Obj().Id().
  17  // The zero value for a MethodSet is a ready-to-use empty method set.
  18  type MethodSet struct {
  19  	list []*Selection
  20  }
  21  
  22  func (s *MethodSet) String() string {
  23  	if s.Len() == 0 {
  24  		return "MethodSet {}"
  25  	}
  26  
  27  	var buf strings.Builder
  28  	fmt.Fprintln(&buf, "MethodSet {")
  29  	for _, f := range s.list {
  30  		fmt.Fprintf(&buf, "\t%s\n", f)
  31  	}
  32  	fmt.Fprintln(&buf, "}")
  33  	return buf.String()
  34  }
  35  
  36  // Len returns the number of methods in s.
  37  func (s *MethodSet) Len() int { return len(s.list) }
  38  
  39  // At returns the i'th method in s for 0 <= i < s.Len().
  40  func (s *MethodSet) At(i int) *Selection { return s.list[i] }
  41  
  42  // Lookup returns the method with matching package and name, or nil if not found.
  43  func (s *MethodSet) Lookup(pkg *Package, name string) *Selection {
  44  	if s.Len() == 0 {
  45  		return nil
  46  	}
  47  
  48  	key := Id(pkg, name)
  49  	i := sort.Search(len(s.list), func(i int) bool {
  50  		m := s.list[i]
  51  		return m.obj.Id() >= key
  52  	})
  53  	if i < len(s.list) {
  54  		m := s.list[i]
  55  		if m.obj.Id() == key {
  56  			return m
  57  		}
  58  	}
  59  	return nil
  60  }
  61  
  62  // Shared empty method set.
  63  var emptyMethodSet MethodSet
  64  
  65  // Note: NewMethodSet is intended for external use only as it
  66  //       requires interfaces to be complete. It may be used
  67  //       internally if LookupFieldOrMethod completed the same
  68  //       interfaces beforehand.
  69  
  70  // NewMethodSet returns the method set for the given type T.
  71  // It always returns a non-nil method set, even if it is empty.
  72  func NewMethodSet(T Type) *MethodSet {
  73  	// WARNING: The code in this function is extremely subtle - do not modify casually!
  74  	//          This function and lookupFieldOrMethod should be kept in sync.
  75  
  76  	// TODO(rfindley) confirm that this code is in sync with lookupFieldOrMethod
  77  	//                with respect to type params.
  78  
  79  	// Methods cannot be associated with a named pointer type.
  80  	// (spec: "The type denoted by T is called the receiver base type;
  81  	// it must not be a pointer or interface type and it must be declared
  82  	// in the same package as the method.").
  83  	if t := asNamed(T); t != nil && isPointer(t) {
  84  		return &emptyMethodSet
  85  	}
  86  
  87  	// method set up to the current depth, allocated lazily
  88  	var base methodSet
  89  
  90  	typ, isPtr := deref(T)
  91  
  92  	// *typ where typ is an interface has no methods.
  93  	if isPtr && IsInterface(typ) {
  94  		return &emptyMethodSet
  95  	}
  96  
  97  	// Start with typ as single entry at shallowest depth.
  98  	current := []embeddedType{{typ, nil, isPtr, false}}
  99  
 100  	// seen tracks named types that we have seen already, allocated lazily.
 101  	// Used to avoid endless searches in case of recursive types.
 102  	//
 103  	// We must use a lookup on identity rather than a simple map[*Named]bool as
 104  	// instantiated types may be identical but not equal.
 105  	var seen instanceLookup
 106  
 107  	// collect methods at current depth
 108  	for len(current) > 0 {
 109  		var next []embeddedType // embedded types found at current depth
 110  
 111  		// field and method sets at current depth, indexed by names (Id's), and allocated lazily
 112  		var fset map[string]bool // we only care about the field names
 113  		var mset methodSet
 114  
 115  		for _, e := range current {
 116  			typ := e.typ
 117  
 118  			// If we have a named type, we may have associated methods.
 119  			// Look for those first.
 120  			if named := asNamed(typ); named != nil {
 121  				if alt := seen.lookup(named); alt != nil {
 122  					// We have seen this type before, at a more shallow depth
 123  					// (note that multiples of this type at the current depth
 124  					// were consolidated before). The type at that depth shadows
 125  					// this same type at the current depth, so we can ignore
 126  					// this one.
 127  					continue
 128  				}
 129  				seen.add(named)
 130  
 131  				for i := 0; i < named.NumMethods(); i++ {
 132  					mset = mset.addOne(named.Method(i), concat(e.index, i), e.indirect, e.multiples)
 133  				}
 134  			}
 135  
 136  			switch t := under(typ).(type) {
 137  			case *Struct:
 138  				for i, f := range t.fields {
 139  					if fset == nil {
 140  						fset = make(map[string]bool)
 141  					}
 142  					fset[f.Id()] = true
 143  
 144  					// Embedded fields are always of the form T or *T where
 145  					// T is a type name. If typ appeared multiple times at
 146  					// this depth, f.Type appears multiple times at the next
 147  					// depth.
 148  					if f.embedded {
 149  						typ, isPtr := deref(f.typ)
 150  						// TODO(gri) optimization: ignore types that can't
 151  						// have fields or methods (only Named, Struct, and
 152  						// Interface types need to be considered).
 153  						next = append(next, embeddedType{typ, concat(e.index, i), e.indirect || isPtr, e.multiples})
 154  					}
 155  				}
 156  
 157  			case *Interface:
 158  				mset = mset.add(t.typeSet().methods, e.index, true, e.multiples)
 159  			}
 160  		}
 161  
 162  		// Add methods and collisions at this depth to base if no entries with matching
 163  		// names exist already.
 164  		for k, m := range mset {
 165  			if _, found := base[k]; !found {
 166  				// Fields collide with methods of the same name at this depth.
 167  				if fset[k] {
 168  					m = nil // collision
 169  				}
 170  				if base == nil {
 171  					base = make(methodSet)
 172  				}
 173  				base[k] = m
 174  			}
 175  		}
 176  
 177  		// Add all (remaining) fields at this depth as collisions (since they will
 178  		// hide any method further down) if no entries with matching names exist already.
 179  		for k := range fset {
 180  			if _, found := base[k]; !found {
 181  				if base == nil {
 182  					base = make(methodSet)
 183  				}
 184  				base[k] = nil // collision
 185  			}
 186  		}
 187  
 188  		current = consolidateMultiples(next)
 189  	}
 190  
 191  	if len(base) == 0 {
 192  		return &emptyMethodSet
 193  	}
 194  
 195  	// collect methods
 196  	var list []*Selection
 197  	for _, m := range base {
 198  		if m != nil {
 199  			m.recv = T
 200  			list = append(list, m)
 201  		}
 202  	}
 203  	// sort by unique name
 204  	sort.Slice(list, func(i, j int) bool {
 205  		return list[i].obj.Id() < list[j].obj.Id()
 206  	})
 207  	return &MethodSet{list}
 208  }
 209  
 210  // A methodSet is a set of methods and name collisions.
 211  // A collision indicates that multiple methods with the
 212  // same unique id, or a field with that id appeared.
 213  type methodSet map[string]*Selection // a nil entry indicates a name collision
 214  
 215  // Add adds all functions in list to the method set s.
 216  // If multiples is set, every function in list appears multiple times
 217  // and is treated as a collision.
 218  func (s methodSet) add(list []*Func, index []int, indirect bool, multiples bool) methodSet {
 219  	if len(list) == 0 {
 220  		return s
 221  	}
 222  	for i, f := range list {
 223  		s = s.addOne(f, concat(index, i), indirect, multiples)
 224  	}
 225  	return s
 226  }
 227  
 228  func (s methodSet) addOne(f *Func, index []int, indirect bool, multiples bool) methodSet {
 229  	if s == nil {
 230  		s = make(methodSet)
 231  	}
 232  	key := f.Id()
 233  	// if f is not in the set, add it
 234  	if !multiples {
 235  		// TODO(gri) A found method may not be added because it's not in the method set
 236  		// (!indirect && f.hasPtrRecv()). A 2nd method on the same level may be in the method
 237  		// set and may not collide with the first one, thus leading to a false positive.
 238  		// Is that possible? Investigate.
 239  		if _, found := s[key]; !found && (indirect || !f.hasPtrRecv()) {
 240  			s[key] = &Selection{MethodVal, nil, f, index, indirect}
 241  			return s
 242  		}
 243  	}
 244  	s[key] = nil // collision
 245  	return s
 246  }
 247