github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/go/types/objectpath/objectpath.go (about) 1 // Copyright 2018 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 objectpath defines a naming scheme for types.Objects 6 // (that is, named entities in Go programs) relative to their enclosing 7 // package. 8 // 9 // Type-checker objects are canonical, so they are usually identified by 10 // their address in memory (a pointer), but a pointer has meaning only 11 // within one address space. By contrast, objectpath names allow the 12 // identity of an object to be sent from one program to another, 13 // establishing a correspondence between types.Object variables that are 14 // distinct but logically equivalent. 15 // 16 // A single object may have multiple paths. In this example, 17 // 18 // type A struct{ X int } 19 // type B A 20 // 21 // the field X has two paths due to its membership of both A and B. 22 // The For(obj) function always returns one of these paths, arbitrarily 23 // but consistently. 24 package objectpath 25 26 import ( 27 "fmt" 28 "go/types" 29 "sort" 30 "strconv" 31 "strings" 32 33 "golang.org/x/tools/internal/typeparams" 34 ) 35 36 // A Path is an opaque name that identifies a types.Object 37 // relative to its package. Conceptually, the name consists of a 38 // sequence of destructuring operations applied to the package scope 39 // to obtain the original object. 40 // The name does not include the package itself. 41 type Path string 42 43 // Encoding 44 // 45 // An object path is a textual and (with training) human-readable encoding 46 // of a sequence of destructuring operators, starting from a types.Package. 47 // The sequences represent a path through the package/object/type graph. 48 // We classify these operators by their type: 49 // 50 // PO package->object Package.Scope.Lookup 51 // OT object->type Object.Type 52 // TT type->type Type.{Elem,Key,Params,Results,Underlying} [EKPRU] 53 // TO type->object Type.{At,Field,Method,Obj} [AFMO] 54 // 55 // All valid paths start with a package and end at an object 56 // and thus may be defined by the regular language: 57 // 58 // objectpath = PO (OT TT* TO)* 59 // 60 // The concrete encoding follows directly: 61 // - The only PO operator is Package.Scope.Lookup, which requires an identifier. 62 // - The only OT operator is Object.Type, 63 // which we encode as '.' because dot cannot appear in an identifier. 64 // - The TT operators are encoded as [EKPRUTC]; 65 // one of these (TypeParam) requires an integer operand, 66 // which is encoded as a string of decimal digits. 67 // - The TO operators are encoded as [AFMO]; 68 // three of these (At,Field,Method) require an integer operand, 69 // which is encoded as a string of decimal digits. 70 // These indices are stable across different representations 71 // of the same package, even source and export data. 72 // The indices used are implementation specific and may not correspond to 73 // the argument to the go/types function. 74 // 75 // In the example below, 76 // 77 // package p 78 // 79 // type T interface { 80 // f() (a string, b struct{ X int }) 81 // } 82 // 83 // field X has the path "T.UM0.RA1.F0", 84 // representing the following sequence of operations: 85 // 86 // p.Lookup("T") T 87 // .Type().Underlying().Method(0). f 88 // .Type().Results().At(1) b 89 // .Type().Field(0) X 90 // 91 // The encoding is not maximally compact---every R or P is 92 // followed by an A, for example---but this simplifies the 93 // encoder and decoder. 94 const ( 95 // object->type operators 96 opType = '.' // .Type() (Object) 97 98 // type->type operators 99 opElem = 'E' // .Elem() (Pointer, Slice, Array, Chan, Map) 100 opKey = 'K' // .Key() (Map) 101 opParams = 'P' // .Params() (Signature) 102 opResults = 'R' // .Results() (Signature) 103 opUnderlying = 'U' // .Underlying() (Named) 104 opTypeParam = 'T' // .TypeParams.At(i) (Named, Signature) 105 opConstraint = 'C' // .Constraint() (TypeParam) 106 107 // type->object operators 108 opAt = 'A' // .At(i) (Tuple) 109 opField = 'F' // .Field(i) (Struct) 110 opMethod = 'M' // .Method(i) (Named or Interface; not Struct: "promoted" names are ignored) 111 opObj = 'O' // .Obj() (Named, TypeParam) 112 ) 113 114 // The For function returns the path to an object relative to its package, 115 // or an error if the object is not accessible from the package's Scope. 116 // 117 // The For function guarantees to return a path only for the following objects: 118 // - package-level types 119 // - exported package-level non-types 120 // - methods 121 // - parameter and result variables 122 // - struct fields 123 // These objects are sufficient to define the API of their package. 124 // The objects described by a package's export data are drawn from this set. 125 // 126 // For does not return a path for predeclared names, imported package 127 // names, local names, and unexported package-level names (except 128 // types). 129 // 130 // Example: given this definition, 131 // 132 // package p 133 // 134 // type T interface { 135 // f() (a string, b struct{ X int }) 136 // } 137 // 138 // For(X) would return a path that denotes the following sequence of operations: 139 // 140 // p.Scope().Lookup("T") (TypeName T) 141 // .Type().Underlying().Method(0). (method Func f) 142 // .Type().Results().At(1) (field Var b) 143 // .Type().Field(0) (field Var X) 144 // 145 // where p is the package (*types.Package) to which X belongs. 146 func For(obj types.Object) (Path, error) { 147 pkg := obj.Pkg() 148 149 // This table lists the cases of interest. 150 // 151 // Object Action 152 // ------ ------ 153 // nil reject 154 // builtin reject 155 // pkgname reject 156 // label reject 157 // var 158 // package-level accept 159 // func param/result accept 160 // local reject 161 // struct field accept 162 // const 163 // package-level accept 164 // local reject 165 // func 166 // package-level accept 167 // init functions reject 168 // concrete method accept 169 // interface method accept 170 // type 171 // package-level accept 172 // local reject 173 // 174 // The only accessible package-level objects are members of pkg itself. 175 // 176 // The cases are handled in four steps: 177 // 178 // 1. reject nil and builtin 179 // 2. accept package-level objects 180 // 3. reject obviously invalid objects 181 // 4. search the API for the path to the param/result/field/method. 182 183 // 1. reference to nil or builtin? 184 if pkg == nil { 185 return "", fmt.Errorf("predeclared %s has no path", obj) 186 } 187 scope := pkg.Scope() 188 189 // 2. package-level object? 190 if scope.Lookup(obj.Name()) == obj { 191 // Only exported objects (and non-exported types) have a path. 192 // Non-exported types may be referenced by other objects. 193 if _, ok := obj.(*types.TypeName); !ok && !obj.Exported() { 194 return "", fmt.Errorf("no path for non-exported %v", obj) 195 } 196 return Path(obj.Name()), nil 197 } 198 199 // 3. Not a package-level object. 200 // Reject obviously non-viable cases. 201 switch obj := obj.(type) { 202 case *types.TypeName: 203 if _, ok := obj.Type().(*typeparams.TypeParam); !ok { 204 // With the exception of type parameters, only package-level type names 205 // have a path. 206 return "", fmt.Errorf("no path for %v", obj) 207 } 208 case *types.Const, // Only package-level constants have a path. 209 *types.Label, // Labels are function-local. 210 *types.PkgName: // PkgNames are file-local. 211 return "", fmt.Errorf("no path for %v", obj) 212 213 case *types.Var: 214 // Could be: 215 // - a field (obj.IsField()) 216 // - a func parameter or result 217 // - a local var. 218 // Sadly there is no way to distinguish 219 // a param/result from a local 220 // so we must proceed to the find. 221 222 case *types.Func: 223 // A func, if not package-level, must be a method. 224 if recv := obj.Type().(*types.Signature).Recv(); recv == nil { 225 return "", fmt.Errorf("func is not a method: %v", obj) 226 } 227 228 if path, ok := concreteMethod(obj); ok { 229 // Fast path for concrete methods that avoids looping over scope. 230 return path, nil 231 } 232 233 default: 234 panic(obj) 235 } 236 237 // 4. Search the API for the path to the var (field/param/result) or method. 238 239 // First inspect package-level named types. 240 // In the presence of path aliases, these give 241 // the best paths because non-types may 242 // refer to types, but not the reverse. 243 empty := make([]byte, 0, 48) // initial space 244 names := scope.Names() 245 for _, name := range names { 246 o := scope.Lookup(name) 247 tname, ok := o.(*types.TypeName) 248 if !ok { 249 continue // handle non-types in second pass 250 } 251 252 path := append(empty, name...) 253 path = append(path, opType) 254 255 T := o.Type() 256 257 if tname.IsAlias() { 258 // type alias 259 if r := find(obj, T, path, nil); r != nil { 260 return Path(r), nil 261 } 262 } else { 263 if named, _ := T.(*types.Named); named != nil { 264 if r := findTypeParam(obj, typeparams.ForNamed(named), path, nil); r != nil { 265 // generic named type 266 return Path(r), nil 267 } 268 } 269 // defined (named) type 270 if r := find(obj, T.Underlying(), append(path, opUnderlying), nil); r != nil { 271 return Path(r), nil 272 } 273 } 274 } 275 276 // Then inspect everything else: 277 // non-types, and declared methods of defined types. 278 for _, name := range names { 279 o := scope.Lookup(name) 280 path := append(empty, name...) 281 if _, ok := o.(*types.TypeName); !ok { 282 if o.Exported() { 283 // exported non-type (const, var, func) 284 if r := find(obj, o.Type(), append(path, opType), nil); r != nil { 285 return Path(r), nil 286 } 287 } 288 continue 289 } 290 291 // Inspect declared methods of defined types. 292 if T, ok := o.Type().(*types.Named); ok { 293 path = append(path, opType) 294 // Note that method index here is always with respect 295 // to canonical ordering of methods, regardless of how 296 // they appear in the underlying type. 297 canonical := canonicalize(T) 298 for i := 0; i < len(canonical); i++ { 299 m := canonical[i] 300 path2 := appendOpArg(path, opMethod, i) 301 if m == obj { 302 return Path(path2), nil // found declared method 303 } 304 if r := find(obj, m.Type(), append(path2, opType), nil); r != nil { 305 return Path(r), nil 306 } 307 } 308 } 309 } 310 311 return "", fmt.Errorf("can't find path for %v in %s", obj, pkg.Path()) 312 } 313 314 func appendOpArg(path []byte, op byte, arg int) []byte { 315 path = append(path, op) 316 path = strconv.AppendInt(path, int64(arg), 10) 317 return path 318 } 319 320 // concreteMethod returns the path for meth, which must have a non-nil receiver. 321 // The second return value indicates success and may be false if the method is 322 // an interface method or if it is an instantiated method. 323 // 324 // This function is just an optimization that avoids the general scope walking 325 // approach. You are expected to fall back to the general approach if this 326 // function fails. 327 func concreteMethod(meth *types.Func) (Path, bool) { 328 // Concrete methods can only be declared on package-scoped named types. For 329 // that reason we can skip the expensive walk over the package scope: the 330 // path will always be package -> named type -> method. We can trivially get 331 // the type name from the receiver, and only have to look over the type's 332 // methods to find the method index. 333 // 334 // Methods on generic types require special consideration, however. Consider 335 // the following package: 336 // 337 // L1: type S[T any] struct{} 338 // L2: func (recv S[A]) Foo() { recv.Bar() } 339 // L3: func (recv S[B]) Bar() { } 340 // L4: type Alias = S[int] 341 // L5: func _[T any]() { var s S[int]; s.Foo() } 342 // 343 // The receivers of methods on generic types are instantiations. L2 and L3 344 // instantiate S with the type-parameters A and B, which are scoped to the 345 // respective methods. L4 and L5 each instantiate S with int. Each of these 346 // instantiations has its own method set, full of methods (and thus objects) 347 // with receivers whose types are the respective instantiations. In other 348 // words, we have 349 // 350 // S[A].Foo, S[A].Bar 351 // S[B].Foo, S[B].Bar 352 // S[int].Foo, S[int].Bar 353 // 354 // We may thus be trying to produce object paths for any of these objects. 355 // 356 // S[A].Foo and S[B].Bar are the origin methods, and their paths are S.Foo 357 // and S.Bar, which are the paths that this function naturally produces. 358 // 359 // S[A].Bar, S[B].Foo, and both methods on S[int] are instantiations that 360 // don't correspond to the origin methods. For S[int], this is significant. 361 // The most precise object path for S[int].Foo, for example, is Alias.Foo, 362 // not S.Foo. Our function, however, would produce S.Foo, which would 363 // resolve to a different object. 364 // 365 // For S[A].Bar and S[B].Foo it could be argued that S.Bar and S.Foo are 366 // still the correct paths, since only the origin methods have meaningful 367 // paths. But this is likely only true for trivial cases and has edge cases. 368 // Since this function is only an optimization, we err on the side of giving 369 // up, deferring to the slower but definitely correct algorithm. Most users 370 // of objectpath will only be giving us origin methods, anyway, as referring 371 // to instantiated methods is usually not useful. 372 373 if typeparams.OriginMethod(meth) != meth { 374 return "", false 375 } 376 377 recvT := meth.Type().(*types.Signature).Recv().Type() 378 if ptr, ok := recvT.(*types.Pointer); ok { 379 recvT = ptr.Elem() 380 } 381 382 named, ok := recvT.(*types.Named) 383 if !ok { 384 return "", false 385 } 386 387 if types.IsInterface(named) { 388 // Named interfaces don't have to be package-scoped 389 // 390 // TODO(dominikh): opt: if scope.Lookup(name) == named, then we can apply this optimization to interface 391 // methods, too, I think. 392 return "", false 393 } 394 395 // Preallocate space for the name, opType, opMethod, and some digits. 396 name := named.Obj().Name() 397 path := make([]byte, 0, len(name)+8) 398 path = append(path, name...) 399 path = append(path, opType) 400 canonical := canonicalize(named) 401 for i, m := range canonical { 402 if m == meth { 403 path = appendOpArg(path, opMethod, i) 404 return Path(path), true 405 } 406 } 407 408 panic(fmt.Sprintf("couldn't find method %s on type %s", meth, named)) 409 } 410 411 // find finds obj within type T, returning the path to it, or nil if not found. 412 // 413 // The seen map is used to short circuit cycles through type parameters. If 414 // nil, it will be allocated as necessary. 415 func find(obj types.Object, T types.Type, path []byte, seen map[*types.TypeName]bool) []byte { 416 switch T := T.(type) { 417 case *types.Basic, *types.Named: 418 // Named types belonging to pkg were handled already, 419 // so T must belong to another package. No path. 420 return nil 421 case *types.Pointer: 422 return find(obj, T.Elem(), append(path, opElem), seen) 423 case *types.Slice: 424 return find(obj, T.Elem(), append(path, opElem), seen) 425 case *types.Array: 426 return find(obj, T.Elem(), append(path, opElem), seen) 427 case *types.Chan: 428 return find(obj, T.Elem(), append(path, opElem), seen) 429 case *types.Map: 430 if r := find(obj, T.Key(), append(path, opKey), seen); r != nil { 431 return r 432 } 433 return find(obj, T.Elem(), append(path, opElem), seen) 434 case *types.Signature: 435 if r := findTypeParam(obj, typeparams.ForSignature(T), path, seen); r != nil { 436 return r 437 } 438 if r := find(obj, T.Params(), append(path, opParams), seen); r != nil { 439 return r 440 } 441 return find(obj, T.Results(), append(path, opResults), seen) 442 case *types.Struct: 443 for i := 0; i < T.NumFields(); i++ { 444 fld := T.Field(i) 445 path2 := appendOpArg(path, opField, i) 446 if fld == obj { 447 return path2 // found field var 448 } 449 if r := find(obj, fld.Type(), append(path2, opType), seen); r != nil { 450 return r 451 } 452 } 453 return nil 454 case *types.Tuple: 455 for i := 0; i < T.Len(); i++ { 456 v := T.At(i) 457 path2 := appendOpArg(path, opAt, i) 458 if v == obj { 459 return path2 // found param/result var 460 } 461 if r := find(obj, v.Type(), append(path2, opType), seen); r != nil { 462 return r 463 } 464 } 465 return nil 466 case *types.Interface: 467 for i := 0; i < T.NumMethods(); i++ { 468 m := T.Method(i) 469 path2 := appendOpArg(path, opMethod, i) 470 if m == obj { 471 return path2 // found interface method 472 } 473 if r := find(obj, m.Type(), append(path2, opType), seen); r != nil { 474 return r 475 } 476 } 477 return nil 478 case *typeparams.TypeParam: 479 name := T.Obj() 480 if name == obj { 481 return append(path, opObj) 482 } 483 if seen[name] { 484 return nil 485 } 486 if seen == nil { 487 seen = make(map[*types.TypeName]bool) 488 } 489 seen[name] = true 490 if r := find(obj, T.Constraint(), append(path, opConstraint), seen); r != nil { 491 return r 492 } 493 return nil 494 } 495 panic(T) 496 } 497 498 func findTypeParam(obj types.Object, list *typeparams.TypeParamList, path []byte, seen map[*types.TypeName]bool) []byte { 499 for i := 0; i < list.Len(); i++ { 500 tparam := list.At(i) 501 path2 := appendOpArg(path, opTypeParam, i) 502 if r := find(obj, tparam, path2, seen); r != nil { 503 return r 504 } 505 } 506 return nil 507 } 508 509 // Object returns the object denoted by path p within the package pkg. 510 func Object(pkg *types.Package, p Path) (types.Object, error) { 511 if p == "" { 512 return nil, fmt.Errorf("empty path") 513 } 514 515 pathstr := string(p) 516 var pkgobj, suffix string 517 if dot := strings.IndexByte(pathstr, opType); dot < 0 { 518 pkgobj = pathstr 519 } else { 520 pkgobj = pathstr[:dot] 521 suffix = pathstr[dot:] // suffix starts with "." 522 } 523 524 obj := pkg.Scope().Lookup(pkgobj) 525 if obj == nil { 526 return nil, fmt.Errorf("package %s does not contain %q", pkg.Path(), pkgobj) 527 } 528 529 // abstraction of *types.{Pointer,Slice,Array,Chan,Map} 530 type hasElem interface { 531 Elem() types.Type 532 } 533 // abstraction of *types.{Named,Signature} 534 type hasTypeParams interface { 535 TypeParams() *typeparams.TypeParamList 536 } 537 // abstraction of *types.{Named,TypeParam} 538 type hasObj interface { 539 Obj() *types.TypeName 540 } 541 542 // The loop state is the pair (t, obj), 543 // exactly one of which is non-nil, initially obj. 544 // All suffixes start with '.' (the only object->type operation), 545 // followed by optional type->type operations, 546 // then a type->object operation. 547 // The cycle then repeats. 548 var t types.Type 549 for suffix != "" { 550 code := suffix[0] 551 suffix = suffix[1:] 552 553 // Codes [AFM] have an integer operand. 554 var index int 555 switch code { 556 case opAt, opField, opMethod, opTypeParam: 557 rest := strings.TrimLeft(suffix, "0123456789") 558 numerals := suffix[:len(suffix)-len(rest)] 559 suffix = rest 560 i, err := strconv.Atoi(numerals) 561 if err != nil { 562 return nil, fmt.Errorf("invalid path: bad numeric operand %q for code %q", numerals, code) 563 } 564 index = int(i) 565 case opObj: 566 // no operand 567 default: 568 // The suffix must end with a type->object operation. 569 if suffix == "" { 570 return nil, fmt.Errorf("invalid path: ends with %q, want [AFMO]", code) 571 } 572 } 573 574 if code == opType { 575 if t != nil { 576 return nil, fmt.Errorf("invalid path: unexpected %q in type context", opType) 577 } 578 t = obj.Type() 579 obj = nil 580 continue 581 } 582 583 if t == nil { 584 return nil, fmt.Errorf("invalid path: code %q in object context", code) 585 } 586 587 // Inv: t != nil, obj == nil 588 589 switch code { 590 case opElem: 591 hasElem, ok := t.(hasElem) // Pointer, Slice, Array, Chan, Map 592 if !ok { 593 return nil, fmt.Errorf("cannot apply %q to %s (got %T, want pointer, slice, array, chan or map)", code, t, t) 594 } 595 t = hasElem.Elem() 596 597 case opKey: 598 mapType, ok := t.(*types.Map) 599 if !ok { 600 return nil, fmt.Errorf("cannot apply %q to %s (got %T, want map)", code, t, t) 601 } 602 t = mapType.Key() 603 604 case opParams: 605 sig, ok := t.(*types.Signature) 606 if !ok { 607 return nil, fmt.Errorf("cannot apply %q to %s (got %T, want signature)", code, t, t) 608 } 609 t = sig.Params() 610 611 case opResults: 612 sig, ok := t.(*types.Signature) 613 if !ok { 614 return nil, fmt.Errorf("cannot apply %q to %s (got %T, want signature)", code, t, t) 615 } 616 t = sig.Results() 617 618 case opUnderlying: 619 named, ok := t.(*types.Named) 620 if !ok { 621 return nil, fmt.Errorf("cannot apply %q to %s (got %T, want named)", code, t, t) 622 } 623 t = named.Underlying() 624 625 case opTypeParam: 626 hasTypeParams, ok := t.(hasTypeParams) // Named, Signature 627 if !ok { 628 return nil, fmt.Errorf("cannot apply %q to %s (got %T, want named or signature)", code, t, t) 629 } 630 tparams := hasTypeParams.TypeParams() 631 if n := tparams.Len(); index >= n { 632 return nil, fmt.Errorf("tuple index %d out of range [0-%d)", index, n) 633 } 634 t = tparams.At(index) 635 636 case opConstraint: 637 tparam, ok := t.(*typeparams.TypeParam) 638 if !ok { 639 return nil, fmt.Errorf("cannot apply %q to %s (got %T, want type parameter)", code, t, t) 640 } 641 t = tparam.Constraint() 642 643 case opAt: 644 tuple, ok := t.(*types.Tuple) 645 if !ok { 646 return nil, fmt.Errorf("cannot apply %q to %s (got %T, want tuple)", code, t, t) 647 } 648 if n := tuple.Len(); index >= n { 649 return nil, fmt.Errorf("tuple index %d out of range [0-%d)", index, n) 650 } 651 obj = tuple.At(index) 652 t = nil 653 654 case opField: 655 structType, ok := t.(*types.Struct) 656 if !ok { 657 return nil, fmt.Errorf("cannot apply %q to %s (got %T, want struct)", code, t, t) 658 } 659 if n := structType.NumFields(); index >= n { 660 return nil, fmt.Errorf("field index %d out of range [0-%d)", index, n) 661 } 662 obj = structType.Field(index) 663 t = nil 664 665 case opMethod: 666 hasMethods, ok := t.(hasMethods) // Interface or Named 667 if !ok { 668 return nil, fmt.Errorf("cannot apply %q to %s (got %T, want interface or named)", code, t, t) 669 } 670 canonical := canonicalize(hasMethods) 671 if n := len(canonical); index >= n { 672 return nil, fmt.Errorf("method index %d out of range [0-%d)", index, n) 673 } 674 obj = canonical[index] 675 t = nil 676 677 case opObj: 678 hasObj, ok := t.(hasObj) 679 if !ok { 680 return nil, fmt.Errorf("cannot apply %q to %s (got %T, want named or type param)", code, t, t) 681 } 682 obj = hasObj.Obj() 683 t = nil 684 685 default: 686 return nil, fmt.Errorf("invalid path: unknown code %q", code) 687 } 688 } 689 690 if obj.Pkg() != pkg { 691 return nil, fmt.Errorf("path denotes %s, which belongs to a different package", obj) 692 } 693 694 return obj, nil // success 695 } 696 697 // hasMethods is an abstraction of *types.{Interface,Named}. This is pulled up 698 // because it is used by methodOrdering, which is in turn used by both encoding 699 // and decoding. 700 type hasMethods interface { 701 Method(int) *types.Func 702 NumMethods() int 703 } 704 705 // canonicalize returns a canonical order for the methods in a hasMethod. 706 func canonicalize(hm hasMethods) []*types.Func { 707 count := hm.NumMethods() 708 if count <= 0 { 709 return nil 710 } 711 canon := make([]*types.Func, count) 712 for i := 0; i < count; i++ { 713 canon[i] = hm.Method(i) 714 } 715 less := func(i, j int) bool { 716 return canon[i].Id() < canon[j].Id() 717 } 718 sort.Slice(canon, less) 719 return canon 720 }