blob: eaa4ae819af0804c8426160f5cc9864b4af565a5 [file]
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build ignore
// The objectpath command is a debugging tool that prints
// the object path of each symbol declared in a package.
//
// An object path is a unique identifier for an exported Go language symbol
// (function, type, variable, etc) relative to its declaring package.
// See [golang.org/x/tools/go/types/objectpath] for details.
//
// Usage: go run ./go/types/objectpath/main.go [packages...]
package main
import (
"cmp"
"flag"
"fmt"
"go/types"
"log"
"maps"
"os"
"path/filepath"
"slices"
"strconv"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/go/types/objectpath"
"golang.org/x/tools/internal/typesinternal"
)
func main() {
log.SetPrefix("objectpath: ")
log.SetFlags(0)
flag.Parse()
exitcode := 0
cfg := &packages.Config{Mode: packages.LoadSyntax}
pkgs, err := packages.Load(cfg, flag.Args()...)
if err != nil {
log.Fatalf("packages.Load: %v", err)
}
if packages.PrintErrors(pkgs) > 0 {
exitcode = 1
}
for _, pkg := range pkgs {
var (
fset = pkg.Fset
objectpathFor = new(objectpath.Encoder).For
)
// objString formats a symbol relative to this package.
objString := func(obj types.Object) string {
return types.ObjectString(obj, func(p *types.Package) string {
if p == pkg.Types {
return ""
}
return p.Name()
})
}
// print prints a symbol's name, declared location, and object path.
print := func(obj types.Object) {
var got string
path, err := objectpathFor(obj)
if err != nil {
if mayFail(obj) {
return
}
exitcode = 1
got = "error: " + err.Error()
} else {
got = strconv.Quote(string(path))
}
posn := fset.Position(obj.Pos())
posn.Filename = filepath.Base(posn.Filename)
fmt.Printf("%20s: %v = %s\n", posn.String(), objString(obj), got)
}
// Gather all objects declared in this package, in source order.
objs := slices.Collect(maps.Values(pkg.TypesInfo.Defs))
objs = slices.DeleteFunc(objs, func(obj types.Object) bool {
return obj == nil
})
slices.SortFunc(objs, func(x, y types.Object) int {
return cmp.Compare(x.Pos(), y.Pos())
})
// Show each object's path.
for _, obj := range objs {
if obj != nil {
print(obj)
}
}
}
if exitcode != 0 {
log.Printf("there were errors")
}
os.Exit(exitcode)
}
// mayFail reports whether [objectpath.Encoder.For] is
// permitted to fail for this kind of symbol.
func mayFail(obj types.Object) bool {
// "The For function guarantees to return a path only for the following objects:
// - package-level types
// - exported package-level non-types
// - methods
// - parameter and result variables
// - struct fields"
if typesinternal.IsPackageLevel(obj) && is[*types.TypeName](obj) {
return false // package-level types
}
if obj.Exported() && typesinternal.IsPackageLevel(obj) && !is[*types.TypeName](obj) {
return false // exported package-level non-types
}
// The remaining rules are inaccurate: clearly,
// - local interface methods,
// - params/results of local functions, and
// - fields of local structs
// cannot have object paths.
// So don't enforce them for now.
// TODO(adonovan): clarify the documentation and update this check.
//
// if fn, ok := obj.(*types.Func); ok && fn.Signature().Recv() != nil {
// return false // methods
// }
// if v, ok := obj.(*types.Var); ok {
// case types.ParamVar, types.ResultVar, types.RecvVar:
// return false // parameter and result variables
// case types.FieldVar:
// return false // struct fields
// }
// }
return true
}
func is[T any](x any) bool {
_, ok := x.(T)
return ok
}