golang.org/x/tools/gopls@v0.15.3/internal/cache/methodsets/methodsets.go (about) 1 // Copyright 2023 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 methodsets defines an incremental, serializable index of 6 // method-set information that allows efficient 'implements' queries 7 // across packages of the workspace without using the type checker. 8 // 9 // This package provides only the "global" (all workspace) search; the 10 // "local" search within a given package uses a different 11 // implementation based on type-checker data structures for a single 12 // package plus variants; see ../implementation.go. 13 // The local algorithm is more precise as it tests function-local types too. 14 // 15 // A global index of function-local types is challenging since they 16 // may reference other local types, for which we would need to invent 17 // stable names, an unsolved problem described in passing in Go issue 18 // 57497. The global algorithm also does not index anonymous interface 19 // types, even outside function bodies. 20 // 21 // Consequently, global results are not symmetric: applying the 22 // operation twice may not get you back where you started. 23 package methodsets 24 25 // DESIGN 26 // 27 // See https://go.dev/cl/452060 for a minimal exposition of the algorithm. 28 // 29 // For each method, we compute a fingerprint: a string representing 30 // the method name and type such that equal fingerprint strings mean 31 // identical method types. 32 // 33 // For efficiency, the fingerprint is reduced to a single bit 34 // of a uint64, so that the method set can be represented as 35 // the union of those method bits (a uint64 bitmask). 36 // Assignability thus reduces to a subset check on bitmasks 37 // followed by equality checks on fingerprints. 38 // 39 // In earlier experiments, using 128-bit masks instead of 64 reduced 40 // the number of candidates by about 2x. Using (like a Bloom filter) a 41 // different hash function to compute a second 64-bit mask and 42 // performing a second mask test reduced it by about 4x. 43 // Neither had much effect on the running time, presumably because a 44 // single 64-bit mask is quite effective. See CL 452060 for details. 45 46 import ( 47 "fmt" 48 "go/token" 49 "go/types" 50 "hash/crc32" 51 "strconv" 52 "strings" 53 54 "golang.org/x/tools/go/types/objectpath" 55 "golang.org/x/tools/gopls/internal/util/frob" 56 "golang.org/x/tools/gopls/internal/util/safetoken" 57 "golang.org/x/tools/internal/typeparams" 58 ) 59 60 // An Index records the non-empty method sets of all package-level 61 // types in a package in a form that permits assignability queries 62 // without the type checker. 63 type Index struct { 64 pkg gobPackage 65 } 66 67 // Decode decodes the given gob-encoded data as an Index. 68 func Decode(data []byte) *Index { 69 var pkg gobPackage 70 packageCodec.Decode(data, &pkg) 71 return &Index{pkg} 72 } 73 74 // Encode encodes the receiver as gob-encoded data. 75 func (index *Index) Encode() []byte { 76 return packageCodec.Encode(index.pkg) 77 } 78 79 // NewIndex returns a new index of method-set information for all 80 // package-level types in the specified package. 81 func NewIndex(fset *token.FileSet, pkg *types.Package) *Index { 82 return new(indexBuilder).build(fset, pkg) 83 } 84 85 // A Location records the extent of an identifier in byte-offset form. 86 // 87 // Conversion to protocol (UTF-16) form is done by the caller after a 88 // search, not during index construction. 89 type Location struct { 90 Filename string 91 Start, End int // byte offsets 92 } 93 94 // A Key represents the method set of a given type in a form suitable 95 // to pass to the (*Index).Search method of many different Indexes. 96 type Key struct { 97 mset gobMethodSet // note: lacks position information 98 } 99 100 // KeyOf returns the search key for the method sets of a given type. 101 // It returns false if the type has no methods. 102 func KeyOf(t types.Type) (Key, bool) { 103 mset := methodSetInfo(t, nil) 104 if mset.Mask == 0 { 105 return Key{}, false // no methods 106 } 107 return Key{mset}, true 108 } 109 110 // A Result reports a matching type or method in a method-set search. 111 type Result struct { 112 Location Location // location of the type or method 113 114 // methods only: 115 PkgPath string // path of declaring package (may differ due to embedding) 116 ObjectPath objectpath.Path // path of method within declaring package 117 } 118 119 // Search reports each type that implements (or is implemented by) the 120 // type that produced the search key. If methodID is nonempty, only 121 // that method of each type is reported. 122 // 123 // The result does not include the error.Error method. 124 // TODO(adonovan): give this special case a more systematic treatment. 125 func (index *Index) Search(key Key, methodID string) []Result { 126 var results []Result 127 for _, candidate := range index.pkg.MethodSets { 128 // Traditionally this feature doesn't report 129 // interface/interface elements of the relation. 130 // I think that's a mistake. 131 // TODO(adonovan): UX: change it, here and in the local implementation. 132 if candidate.IsInterface && key.mset.IsInterface { 133 continue 134 } 135 if !satisfies(candidate, key.mset) && !satisfies(key.mset, candidate) { 136 continue 137 } 138 139 if candidate.Tricky { 140 // If any interface method is tricky then extra 141 // checking may be needed to eliminate a false positive. 142 // TODO(adonovan): implement it. 143 } 144 145 if methodID == "" { 146 results = append(results, Result{Location: index.location(candidate.Posn)}) 147 } else { 148 for _, m := range candidate.Methods { 149 // Here we exploit knowledge of the shape of the fingerprint string. 150 if strings.HasPrefix(m.Fingerprint, methodID) && 151 m.Fingerprint[len(methodID)] == '(' { 152 153 // Don't report error.Error among the results: 154 // it has no true source location, no package, 155 // and is excluded from the xrefs index. 156 if m.PkgPath == 0 || m.ObjectPath == 0 { 157 if methodID != "Error" { 158 panic("missing info for" + methodID) 159 } 160 continue 161 } 162 163 results = append(results, Result{ 164 Location: index.location(m.Posn), 165 PkgPath: index.pkg.Strings[m.PkgPath], 166 ObjectPath: objectpath.Path(index.pkg.Strings[m.ObjectPath]), 167 }) 168 break 169 } 170 } 171 } 172 } 173 return results 174 } 175 176 // satisfies does a fast check for whether x satisfies y. 177 func satisfies(x, y gobMethodSet) bool { 178 return y.IsInterface && x.Mask&y.Mask == y.Mask && subset(y, x) 179 } 180 181 // subset reports whether method set x is a subset of y. 182 func subset(x, y gobMethodSet) bool { 183 outer: 184 for _, mx := range x.Methods { 185 for _, my := range y.Methods { 186 if mx.Sum == my.Sum && mx.Fingerprint == my.Fingerprint { 187 continue outer // found; try next x method 188 } 189 } 190 return false // method of x not found in y 191 } 192 return true // all methods of x found in y 193 } 194 195 func (index *Index) location(posn gobPosition) Location { 196 return Location{ 197 Filename: index.pkg.Strings[posn.File], 198 Start: posn.Offset, 199 End: posn.Offset + posn.Len, 200 } 201 } 202 203 // An indexBuilder builds an index for a single package. 204 type indexBuilder struct { 205 gobPackage 206 stringIndex map[string]int 207 } 208 209 // build adds to the index all package-level named types of the specified package. 210 func (b *indexBuilder) build(fset *token.FileSet, pkg *types.Package) *Index { 211 _ = b.string("") // 0 => "" 212 213 objectPos := func(obj types.Object) gobPosition { 214 posn := safetoken.StartPosition(fset, obj.Pos()) 215 return gobPosition{b.string(posn.Filename), posn.Offset, len(obj.Name())} 216 } 217 218 objectpathFor := new(objectpath.Encoder).For 219 220 // setindexInfo sets the (Posn, PkgPath, ObjectPath) fields for each method declaration. 221 setIndexInfo := func(m *gobMethod, method *types.Func) { 222 // error.Error has empty Position, PkgPath, and ObjectPath. 223 if method.Pkg() == nil { 224 return 225 } 226 227 m.Posn = objectPos(method) 228 m.PkgPath = b.string(method.Pkg().Path()) 229 230 // Instantiations of generic methods don't have an 231 // object path, so we use the generic. 232 if p, err := objectpathFor(typeparams.OriginMethod(method)); err != nil { 233 panic(err) // can't happen for a method of a package-level type 234 } else { 235 m.ObjectPath = b.string(string(p)) 236 } 237 } 238 239 // We ignore aliases, though in principle they could define a 240 // struct{...} or interface{...} type, or an instantiation of 241 // a generic, that has a novel method set. 242 scope := pkg.Scope() 243 for _, name := range scope.Names() { 244 if tname, ok := scope.Lookup(name).(*types.TypeName); ok && !tname.IsAlias() { 245 if mset := methodSetInfo(tname.Type(), setIndexInfo); mset.Mask != 0 { 246 mset.Posn = objectPos(tname) 247 // Only record types with non-trivial method sets. 248 b.MethodSets = append(b.MethodSets, mset) 249 } 250 } 251 } 252 253 return &Index{pkg: b.gobPackage} 254 } 255 256 // string returns a small integer that encodes the string. 257 func (b *indexBuilder) string(s string) int { 258 i, ok := b.stringIndex[s] 259 if !ok { 260 i = len(b.Strings) 261 if b.stringIndex == nil { 262 b.stringIndex = make(map[string]int) 263 } 264 b.stringIndex[s] = i 265 b.Strings = append(b.Strings, s) 266 } 267 return i 268 } 269 270 // methodSetInfo returns the method-set fingerprint of a type. 271 // It calls the optional setIndexInfo function for each gobMethod. 272 // This is used during index construction, but not search (KeyOf), 273 // to store extra information. 274 func methodSetInfo(t types.Type, setIndexInfo func(*gobMethod, *types.Func)) gobMethodSet { 275 // For non-interface types, use *T 276 // (if T is not already a pointer) 277 // since it may have more methods. 278 mset := types.NewMethodSet(EnsurePointer(t)) 279 280 // Convert the method set into a compact summary. 281 var mask uint64 282 tricky := false 283 methods := make([]gobMethod, mset.Len()) 284 for i := 0; i < mset.Len(); i++ { 285 m := mset.At(i).Obj().(*types.Func) 286 fp, isTricky := fingerprint(m) 287 if isTricky { 288 tricky = true 289 } 290 sum := crc32.ChecksumIEEE([]byte(fp)) 291 methods[i] = gobMethod{Fingerprint: fp, Sum: sum} 292 if setIndexInfo != nil { 293 setIndexInfo(&methods[i], m) // set Position, PkgPath, ObjectPath 294 } 295 mask |= 1 << uint64(((sum>>24)^(sum>>16)^(sum>>8)^sum)&0x3f) 296 } 297 return gobMethodSet{ 298 IsInterface: types.IsInterface(t), 299 Tricky: tricky, 300 Mask: mask, 301 Methods: methods, 302 } 303 } 304 305 // EnsurePointer wraps T in a types.Pointer if T is a named, non-interface type. 306 // This is useful to make sure you consider a named type's full method set. 307 func EnsurePointer(T types.Type) types.Type { 308 if _, ok := T.(*types.Named); ok && !types.IsInterface(T) { 309 return types.NewPointer(T) 310 } 311 312 return T 313 } 314 315 // fingerprint returns an encoding of a method signature such that two 316 // methods with equal encodings have identical types, except for a few 317 // tricky types whose encodings may spuriously match and whose exact 318 // identity computation requires the type checker to eliminate false 319 // positives (which are rare). The boolean result indicates whether 320 // the result was one of these tricky types. 321 // 322 // In the standard library, 99.8% of package-level types have a 323 // non-tricky method-set. The most common exceptions are due to type 324 // parameters. 325 // 326 // The fingerprint string starts with method.Id() + "(". 327 func fingerprint(method *types.Func) (string, bool) { 328 var buf strings.Builder 329 tricky := false 330 var fprint func(t types.Type) 331 fprint = func(t types.Type) { 332 switch t := t.(type) { 333 case *types.Named: 334 tname := t.Obj() 335 if tname.Pkg() != nil { 336 buf.WriteString(strconv.Quote(tname.Pkg().Path())) 337 buf.WriteByte('.') 338 } else if tname.Name() != "error" && tname.Name() != "comparable" { 339 panic(tname) // error and comparable the only named types with no package 340 } 341 buf.WriteString(tname.Name()) 342 343 case *types.Array: 344 fmt.Fprintf(&buf, "[%d]", t.Len()) 345 fprint(t.Elem()) 346 347 case *types.Slice: 348 buf.WriteString("[]") 349 fprint(t.Elem()) 350 351 case *types.Pointer: 352 buf.WriteByte('*') 353 fprint(t.Elem()) 354 355 case *types.Map: 356 buf.WriteString("map[") 357 fprint(t.Key()) 358 buf.WriteByte(']') 359 fprint(t.Elem()) 360 361 case *types.Chan: 362 switch t.Dir() { 363 case types.SendRecv: 364 buf.WriteString("chan ") 365 case types.SendOnly: 366 buf.WriteString("<-chan ") 367 case types.RecvOnly: 368 buf.WriteString("chan<- ") 369 } 370 fprint(t.Elem()) 371 372 case *types.Tuple: 373 buf.WriteByte('(') 374 for i := 0; i < t.Len(); i++ { 375 if i > 0 { 376 buf.WriteByte(',') 377 } 378 fprint(t.At(i).Type()) 379 } 380 buf.WriteByte(')') 381 382 case *types.Basic: 383 // Use canonical names for uint8 and int32 aliases. 384 switch t.Kind() { 385 case types.Byte: 386 buf.WriteString("byte") 387 case types.Rune: 388 buf.WriteString("rune") 389 default: 390 buf.WriteString(t.String()) 391 } 392 393 case *types.Signature: 394 buf.WriteString("func") 395 fprint(t.Params()) 396 if t.Variadic() { 397 buf.WriteString("...") // not quite Go syntax 398 } 399 fprint(t.Results()) 400 401 case *types.Struct: 402 // Non-empty unnamed struct types in method 403 // signatures are vanishingly rare. 404 buf.WriteString("struct{") 405 for i := 0; i < t.NumFields(); i++ { 406 if i > 0 { 407 buf.WriteByte(';') 408 } 409 f := t.Field(i) 410 // This isn't quite right for embedded type aliases. 411 // (See types.TypeString(StructType) and #44410 for context.) 412 // But this is vanishingly rare. 413 if !f.Embedded() { 414 buf.WriteString(f.Id()) 415 buf.WriteByte(' ') 416 } 417 fprint(f.Type()) 418 if tag := t.Tag(i); tag != "" { 419 buf.WriteByte(' ') 420 buf.WriteString(strconv.Quote(tag)) 421 } 422 } 423 buf.WriteString("}") 424 425 case *types.Interface: 426 if t.NumMethods() == 0 { 427 buf.WriteString("any") // common case 428 } else { 429 // Interface assignability is particularly 430 // tricky due to the possibility of recursion. 431 tricky = true 432 // We could still give more disambiguating precision 433 // than "..." if we wanted to. 434 buf.WriteString("interface{...}") 435 } 436 437 case *types.TypeParam: 438 tricky = true 439 // TODO(adonovan): refine this by adding a numeric suffix 440 // indicating the index among the receiver type's parameters. 441 buf.WriteByte('?') 442 443 default: // incl. *types.Union 444 panic(t) 445 } 446 } 447 448 buf.WriteString(method.Id()) // e.g. "pkg.Type" 449 sig := method.Type().(*types.Signature) 450 fprint(sig.Params()) 451 fprint(sig.Results()) 452 return buf.String(), tricky 453 } 454 455 // -- serial format of index -- 456 457 // (The name says gob but in fact we use frob.) 458 var packageCodec = frob.CodecFor[gobPackage]() 459 460 // A gobPackage records the method set of each package-level type for a single package. 461 type gobPackage struct { 462 Strings []string // index of strings used by gobPosition.File, gobMethod.{Pkg,Object}Path 463 MethodSets []gobMethodSet 464 } 465 466 // A gobMethodSet records the method set of a single type. 467 type gobMethodSet struct { 468 Posn gobPosition 469 IsInterface bool 470 Tricky bool // at least one method is tricky; assignability requires go/types 471 Mask uint64 // mask with 1 bit from each of methods[*].sum 472 Methods []gobMethod 473 } 474 475 // A gobMethod records the name, type, and position of a single method. 476 type gobMethod struct { 477 Fingerprint string // string of form "methodID(params...)(results)" 478 Sum uint32 // checksum of fingerprint 479 480 // index records only (zero in KeyOf; also for index of error.Error). 481 Posn gobPosition // location of method declaration 482 PkgPath int // path of package containing method declaration 483 ObjectPath int // object path of method relative to PkgPath 484 } 485 486 // A gobPosition records the file, offset, and length of an identifier. 487 type gobPosition struct { 488 File int // index into gobPackage.Strings 489 Offset, Len int // in bytes 490 }