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.
18 "golang.org/x/tools/cmd/guru/serial"
19 "golang.org/x/tools/go/ast/astutil"
20 "golang.org/x/tools/go/loader"
21 "golang.org/x/tools/go/types/typeutil"
24 // describe describes the syntax node denoted by the query position,
26 // - its syntactic category
27 // - the definition of its referent (for identifiers) [now redundant]
28 // - its type, fields, and methods (for an expression or type expression)
30 func describe(q *Query) error {
31 lconf := loader.Config{Build: q.Build}
34 if _, err := importQueryPackage(q.Pos, &lconf); err != nil {
38 // Load/parse/type-check the program.
39 lprog, err := lconf.Load()
44 qpos, err := parseQueryPos(lprog, q.Pos, true) // (need exact pos)
49 if false { // debugging
50 fprintf(os.Stderr, lprog.Fset, qpos.path[0], "you selected: %s %s",
51 astutil.NodeDescription(qpos.path[0]), pathToString(qpos.path))
55 path, action := findInterestingNode(qpos.info, qpos.path)
58 qr, err = describeValue(qpos, path)
61 qr, err = describeType(qpos, path)
64 qr, err = describePackage(qpos, path)
67 qr, err = describeStmt(qpos, path)
70 qr = &describeUnknownResult{path[0]}
73 panic(action) // unreachable
78 q.Output(lprog.Fset, qr)
82 type describeUnknownResult struct {
86 func (r *describeUnknownResult) PrintPlain(printf printfFunc) {
87 // Nothing much to say about misc syntax.
88 printf(r.node, "%s", astutil.NodeDescription(r.node))
91 func (r *describeUnknownResult) JSON(fset *token.FileSet) []byte {
92 return toJSON(&serial.Describe{
93 Desc: astutil.NodeDescription(r.node),
94 Pos: fset.Position(r.node.Pos()).String(),
101 actionUnknown action = iota // None of the below
102 actionExpr // FuncDecl, true Expr or Ident(types.{Const,Var})
103 actionType // type Expr or Ident(types.TypeName).
104 actionStmt // Stmt or Ident(types.Label)
105 actionPackage // Ident(types.Package) or ImportSpec
108 // findInterestingNode classifies the syntax node denoted by path as one of:
109 // - an expression, part of an expression or a reference to a constant
111 // - a type, part of a type, or a reference to a named type;
112 // - a statement, part of a statement, or a label referring to a statement;
113 // - part of a package declaration or import spec.
114 // - none of the above.
115 // and returns the most "interesting" associated node, which may be
116 // the same node, an ancestor or a descendent.
118 func findInterestingNode(pkginfo *loader.PackageInfo, path []ast.Node) ([]ast.Node, action) {
119 // TODO(adonovan): integrate with go/types/stdlib_test.go and
120 // apply this to every AST node we can find to make sure it
123 // TODO(adonovan): audit for ParenExpr safety, esp. since we
124 // traverse up and down.
126 // TODO(adonovan): if the users selects the "." in
127 // "fmt.Fprintf()", they'll get an ambiguous selection error;
128 // we won't even reach here. Can we do better?
130 // TODO(adonovan): describing a field within 'type T struct {...}'
131 // describes the (anonymous) struct type and concludes "no methods".
132 // We should ascend to the enclosing type decl, if any.
135 switch n := path[0].(type) {
137 if len(n.Specs) == 1 {
138 // Descend to sole {Import,Type,Value}Spec child.
139 path = append([]ast.Node{n.Specs[0]}, path...)
142 return path, actionUnknown // uninteresting
145 // Descend to function name.
146 path = append([]ast.Node{n.Name}, path...)
149 case *ast.ImportSpec:
150 return path, actionPackage
153 if len(n.Names) == 1 {
154 // Descend to sole Ident child.
155 path = append([]ast.Node{n.Names[0]}, path...)
158 return path, actionUnknown // uninteresting
161 // Descend to type name.
162 path = append([]ast.Node{n.Name}, path...)
165 case *ast.Comment, *ast.CommentGroup, *ast.File, *ast.KeyValueExpr, *ast.CommClause:
166 return path, actionUnknown // uninteresting
169 return path, actionStmt
177 return path, actionType
180 // Continue to enclosing node.
181 // e.g. [...]T in ArrayType
182 // f(x...) in CallExpr
183 // f(x...T) in FuncType
186 // TODO(adonovan): this needs more thought,
187 // since fields can be so many things.
188 if len(n.Names) == 1 {
189 // Descend to sole Ident child.
190 path = append([]ast.Node{n.Names[0]}, path...)
193 // Zero names (e.g. anon field in struct)
194 // or multiple field or param names:
195 // continue to enclosing field list.
198 // Continue to enclosing node:
199 // {Struct,Func,Interface}Type or FuncDecl.
202 if _, ok := path[1].(*ast.ImportSpec); ok {
203 return path[1:], actionPackage
205 return path, actionExpr
207 case *ast.SelectorExpr:
208 // TODO(adonovan): use Selections info directly.
209 if pkginfo.Uses[n.Sel] == nil {
210 // TODO(adonovan): is this reachable?
211 return path, actionUnknown
213 // Descend to .Sel child.
214 path = append([]ast.Node{n.Sel}, path...)
218 switch pkginfo.ObjectOf(n).(type) {
220 return path, actionPackage
223 return path, actionExpr
226 return path, actionStmt
228 case *types.TypeName:
229 return path, actionType
232 // For x in 'struct {x T}', return struct type, for now.
233 if _, ok := path[1].(*ast.Field); ok {
234 _ = path[2].(*ast.FieldList) // assertion
235 if _, ok := path[3].(*ast.StructType); ok {
236 return path[3:], actionType
239 return path, actionExpr
242 return path, actionExpr
245 // For reference to built-in function, return enclosing call.
246 path = path[1:] // ascend to enclosing function call
250 return path, actionExpr
254 switch path[1].(type) {
255 case *ast.SelectorExpr:
256 // Return enclosing selector expression.
257 return path[1:], actionExpr
260 // TODO(adonovan): test this.
262 // struct { f, g int }
264 // func (f T) method(f, g int) (f, g bool)
266 // switch path[3].(type) {
267 // case *ast.FuncDecl:
268 // case *ast.StructType:
269 // case *ast.InterfaceType:
272 // return path[1:], actionExpr
274 // Unclear what to do with these.
275 // Struct.Fields -- field
276 // Interface.Methods -- field
277 // FuncType.{Params.Results} -- actionExpr
278 // FuncDecl.Recv -- actionExpr
282 return path, actionPackage
284 case *ast.ImportSpec:
285 return path[1:], actionPackage
288 // e.g. blank identifier
289 // or y in "switch y := x.(type)"
290 // or code in a _test.go file that's not part of the package.
291 return path, actionUnknown
295 if pkginfo.Types[n].IsType() {
296 return path, actionType
298 return path, actionExpr
301 // All Expr but {BasicLit,Ident,StarExpr} are
302 // "true" expressions that evaluate to a value.
303 return path, actionExpr
310 return nil, actionUnknown // unreachable
313 func describeValue(qpos *queryPos, path []ast.Node) (*describeValueResult, error) {
316 switch n := path[0].(type) {
318 // ambiguous ValueSpec containing multiple names
319 return nil, fmt.Errorf("multiple value specification")
321 obj = qpos.info.ObjectOf(n)
326 // TODO(adonovan): is this reachable?
327 return nil, fmt.Errorf("unexpected AST for expr: %T", n)
330 typ := qpos.info.TypeOf(expr)
332 typ = types.Typ[types.Invalid]
334 constVal := qpos.info.Types[expr].Value
335 if c, ok := obj.(*types.Const); ok {
339 return &describeValueResult{
343 names: appendNames(nil, typ),
346 methods: accessibleMethods(typ, qpos.info.Pkg),
347 fields: accessibleFields(typ, qpos.info.Pkg),
351 // appendNames returns named types found within the Type by
352 // removing map, pointer, channel, slice, and array constructors.
353 // It does not descend into structs or interfaces.
354 func appendNames(names []*types.Named, typ types.Type) []*types.Named {
355 // elemType specifies type that has some element in it
356 // such as array, slice, chan, pointer
357 type elemType interface {
361 switch t := typ.(type) {
363 names = append(names, t)
365 names = appendNames(names, t.Key())
366 names = appendNames(names, t.Elem())
368 names = appendNames(names, t.Elem())
374 type describeValueResult struct {
376 expr ast.Expr // query node
377 typ types.Type // type of expression
378 names []*types.Named // named types within typ
379 constVal constant.Value // value of expression, if constant
380 obj types.Object // var/func/const object, if expr was Ident
381 methods []*types.Selection
382 fields []describeField
385 func (r *describeValueResult) PrintPlain(printf printfFunc) {
386 var prefix, suffix string
387 if r.constVal != nil {
388 suffix = fmt.Sprintf(" of value %s", r.constVal)
390 switch obj := r.obj.(type) {
392 if recv := obj.Type().(*types.Signature).Recv(); recv != nil {
393 if _, ok := recv.Type().Underlying().(*types.Interface); ok {
394 prefix = "interface method "
401 // Describe the expression.
403 if r.obj.Pos() == r.expr.Pos() {
405 printf(r.expr, "definition of %s%s%s", prefix, r.qpos.objectString(r.obj), suffix)
408 printf(r.expr, "reference to %s%s%s", prefix, r.qpos.objectString(r.obj), suffix)
409 if def := r.obj.Pos(); def != token.NoPos {
410 printf(def, "defined here")
414 desc := astutil.NodeDescription(r.expr)
416 // constant expression
417 printf(r.expr, "%s%s", desc, suffix)
419 // non-constant expression
420 printf(r.expr, "%s of type %s", desc, r.qpos.typeString(r.typ))
424 printMethods(printf, r.expr, r.methods)
425 printFields(printf, r.expr, r.fields)
426 printNamedTypes(printf, r.expr, r.names)
429 func (r *describeValueResult) JSON(fset *token.FileSet) []byte {
430 var value, objpos string
431 if r.constVal != nil {
432 value = r.constVal.String()
435 objpos = fset.Position(r.obj.Pos()).String()
438 typesPos := make([]serial.Definition, len(r.names))
439 for i, t := range r.names {
440 typesPos[i] = serial.Definition{
441 ObjPos: fset.Position(t.Obj().Pos()).String(),
442 Desc: r.qpos.typeString(t),
446 return toJSON(&serial.Describe{
447 Desc: astutil.NodeDescription(r.expr),
448 Pos: fset.Position(r.expr.Pos()).String(),
450 Value: &serial.DescribeValue{
451 Type: r.qpos.typeString(r.typ),
459 // ---- TYPE ------------------------------------------------------------
461 func describeType(qpos *queryPos, path []ast.Node) (*describeTypeResult, error) {
462 var description string
464 switch n := path[0].(type) {
466 obj := qpos.info.ObjectOf(n).(*types.TypeName)
469 description = "alias of "
470 } else if obj.Pos() == n.Pos() {
471 description = "definition of " // (Named type)
472 } else if _, ok := typ.(*types.Basic); ok {
473 description = "reference to built-in "
475 description = "reference to " // (Named type)
479 typ = qpos.info.TypeOf(n)
483 return nil, fmt.Errorf("unexpected AST for type: %T", n)
486 description = description + "type " + qpos.typeString(typ)
488 // Show sizes for structs and named types (it's fairly obvious for others).
490 case *types.Named, *types.Struct:
491 szs := types.StdSizes{WordSize: 8, MaxAlign: 8} // assume amd64
492 description = fmt.Sprintf("%s (size %d, align %d)", description,
493 szs.Sizeof(typ), szs.Alignof(typ))
496 return &describeTypeResult{
499 description: description,
501 methods: accessibleMethods(typ, qpos.info.Pkg),
502 fields: accessibleFields(typ, qpos.info.Pkg),
506 type describeTypeResult struct {
511 methods []*types.Selection
512 fields []describeField
515 type describeField struct {
516 implicits []*types.Named
520 func printMethods(printf printfFunc, node ast.Node, methods []*types.Selection) {
521 if len(methods) > 0 {
522 printf(node, "Methods:")
524 for _, meth := range methods {
525 // Print the method type relative to the package
526 // in which it was defined, not the query package,
527 printf(meth.Obj(), "\t%s",
528 types.SelectionString(meth, types.RelativeTo(meth.Obj().Pkg())))
532 func printFields(printf printfFunc, node ast.Node, fields []describeField) {
534 printf(node, "Fields:")
537 // Align the names and the types (requires two passes).
540 for _, f := range fields {
542 for _, fld := range f.implicits {
543 buf.WriteString(fld.Obj().Name())
546 buf.WriteString(f.field.Name())
548 if n := utf8.RuneCountInString(name); n > width {
551 names = append(names, name)
554 for i, f := range fields {
555 // Print the field type relative to the package
556 // in which it was defined, not the query package,
557 printf(f.field, "\t%*s %s", -width, names[i],
558 types.TypeString(f.field.Type(), types.RelativeTo(f.field.Pkg())))
562 func printNamedTypes(printf printfFunc, node ast.Node, names []*types.Named) {
564 printf(node, "Named types:")
567 for _, t := range names {
568 // Print the type relative to the package
569 // in which it was defined, not the query package,
570 printf(t.Obj(), "\ttype %s defined here",
571 types.TypeString(t.Obj().Type(), types.RelativeTo(t.Obj().Pkg())))
575 func (r *describeTypeResult) PrintPlain(printf printfFunc) {
576 printf(r.node, "%s", r.description)
578 // Show the underlying type for a reference to a named type.
579 if nt, ok := r.typ.(*types.Named); ok && r.node.Pos() != nt.Obj().Pos() {
580 // TODO(adonovan): improve display of complex struct/interface types.
581 printf(nt.Obj(), "defined as %s", r.qpos.typeString(nt.Underlying()))
584 printMethods(printf, r.node, r.methods)
585 if len(r.methods) == 0 {
586 // Only report null result for type kinds
587 // capable of bearing methods.
588 switch r.typ.(type) {
589 case *types.Interface, *types.Struct, *types.Named:
590 printf(r.node, "No methods.")
594 printFields(printf, r.node, r.fields)
597 func (r *describeTypeResult) JSON(fset *token.FileSet) []byte {
598 var namePos, nameDef string
599 if nt, ok := r.typ.(*types.Named); ok {
600 namePos = fset.Position(nt.Obj().Pos()).String()
601 nameDef = nt.Underlying().String()
603 return toJSON(&serial.Describe{
605 Pos: fset.Position(r.node.Pos()).String(),
607 Type: &serial.DescribeType{
608 Type: r.qpos.typeString(r.typ),
611 Methods: methodsToSerial(r.qpos.info.Pkg, r.methods, fset),
616 // ---- PACKAGE ------------------------------------------------------------
618 func describePackage(qpos *queryPos, path []ast.Node) (*describePackageResult, error) {
619 var description string
620 var pkg *types.Package
621 switch n := path[0].(type) {
622 case *ast.ImportSpec:
625 obj = qpos.info.Defs[n.Name]
627 obj = qpos.info.Implicits[n]
629 pkgname, _ := obj.(*types.PkgName)
631 return nil, fmt.Errorf("can't import package %s", n.Path.Value)
633 pkg = pkgname.Imported()
634 description = fmt.Sprintf("import of package %q", pkg.Path())
637 if _, isDef := path[1].(*ast.File); isDef {
640 description = fmt.Sprintf("definition of package %q", pkg.Path())
642 // e.g. import id "..."
644 pkg = qpos.info.ObjectOf(n).(*types.PkgName).Imported()
645 description = fmt.Sprintf("reference to package %q", pkg.Path())
650 return nil, fmt.Errorf("unexpected AST for package: %T", n)
653 var members []*describeMember
654 // NB: "unsafe" has no types.Package
656 // Enumerate the accessible package members
657 // in lexicographic order.
658 for _, name := range pkg.Scope().Names() {
659 if pkg == qpos.info.Pkg || ast.IsExported(name) {
660 mem := pkg.Scope().Lookup(name)
661 var methods []*types.Selection
662 if mem, ok := mem.(*types.TypeName); ok {
663 methods = accessibleMethods(mem.Type(), qpos.info.Pkg)
665 members = append(members, &describeMember{
674 return &describePackageResult{qpos.fset, path[0], description, pkg, members}, nil
677 type describePackageResult struct {
682 members []*describeMember // in lexicographic name order
685 type describeMember struct {
687 methods []*types.Selection // in types.MethodSet order
690 func (r *describePackageResult) PrintPlain(printf printfFunc) {
691 printf(r.node, "%s", r.description)
693 // Compute max width of name "column".
695 for _, mem := range r.members {
696 if l := len(mem.obj.Name()); l > maxname {
701 for _, mem := range r.members {
702 printf(mem.obj, "\t%s", formatMember(mem.obj, maxname))
703 for _, meth := range mem.methods {
704 printf(meth.Obj(), "\t\t%s", types.SelectionString(meth, types.RelativeTo(r.pkg)))
709 func formatMember(obj types.Object, maxname int) string {
710 qualifier := types.RelativeTo(obj.Pkg())
712 fmt.Fprintf(&buf, "%-5s %-*s", tokenOf(obj), maxname, obj.Name())
713 switch obj := obj.(type) {
715 fmt.Fprintf(&buf, " %s = %s", types.TypeString(obj.Type(), qualifier), obj.Val())
718 fmt.Fprintf(&buf, " %s", types.TypeString(obj.Type(), qualifier))
720 case *types.TypeName:
723 buf.WriteString(" = ")
726 typ = typ.Underlying()
729 // Abbreviate long aggregate type names.
730 switch typ := typ.(type) {
731 case *types.Interface:
732 if typ.NumMethods() > 1 {
733 typestr = "interface{...}"
736 if typ.NumFields() > 1 {
737 typestr = "struct{...}"
741 typestr = types.TypeString(typ, qualifier)
743 buf.WriteString(typestr)
746 fmt.Fprintf(&buf, " %s", types.TypeString(obj.Type(), qualifier))
751 func (r *describePackageResult) JSON(fset *token.FileSet) []byte {
752 var members []*serial.DescribeMember
753 for _, mem := range r.members {
758 switch obj := obj.(type) {
760 val = obj.Val().String()
761 case *types.TypeName:
763 alias = "= " // kludgy
765 typ = typ.Underlying()
768 members = append(members, &serial.DescribeMember{
770 Type: alias + typ.String(),
772 Pos: fset.Position(obj.Pos()).String(),
774 Methods: methodsToSerial(r.pkg, mem.methods, fset),
777 return toJSON(&serial.Describe{
779 Pos: fset.Position(r.node.Pos()).String(),
781 Package: &serial.DescribePackage{
788 func tokenOf(o types.Object) string {
794 case *types.TypeName:
801 return "builtin" // e.g. when describing package "unsafe"
810 // ---- STATEMENT ------------------------------------------------------------
812 func describeStmt(qpos *queryPos, path []ast.Node) (*describeStmtResult, error) {
813 var description string
814 switch n := path[0].(type) {
816 if qpos.info.Defs[n] != nil {
817 description = "labelled statement"
819 description = "reference to labelled statement"
823 // Nothing much to say about statements.
824 description = astutil.NodeDescription(n)
826 return &describeStmtResult{qpos.fset, path[0], description}, nil
829 type describeStmtResult struct {
835 func (r *describeStmtResult) PrintPlain(printf printfFunc) {
836 printf(r.node, "%s", r.description)
839 func (r *describeStmtResult) JSON(fset *token.FileSet) []byte {
840 return toJSON(&serial.Describe{
842 Pos: fset.Position(r.node.Pos()).String(),
847 // ------------------- Utilities -------------------
849 // pathToString returns a string containing the concrete types of the
851 func pathToString(path []ast.Node) string {
853 fmt.Fprint(&buf, "[")
854 for i, n := range path {
856 fmt.Fprint(&buf, " ")
858 fmt.Fprint(&buf, strings.TrimPrefix(fmt.Sprintf("%T", n), "*ast."))
860 fmt.Fprint(&buf, "]")
864 func accessibleMethods(t types.Type, from *types.Package) []*types.Selection {
865 var methods []*types.Selection
866 for _, meth := range typeutil.IntuitiveMethodSet(t, nil) {
867 if isAccessibleFrom(meth.Obj(), from) {
868 methods = append(methods, meth)
874 // accessibleFields returns the set of accessible
875 // field selections on a value of type recv.
876 func accessibleFields(recv types.Type, from *types.Package) []describeField {
877 wantField := func(f *types.Var) bool {
878 if !isAccessibleFrom(f, from) {
881 // Check that the field is not shadowed.
882 obj, _, _ := types.LookupFieldOrMethod(recv, true, f.Pkg(), f.Name())
886 var fields []describeField
887 var visit func(t types.Type, stack []*types.Named)
888 visit = func(t types.Type, stack []*types.Named) {
889 tStruct, ok := deref(t).Underlying().(*types.Struct)
894 for i := 0; i < tStruct.NumFields(); i++ {
895 f := tStruct.Field(i)
897 // Handle recursion through anonymous fields.
900 if ptr, ok := tf.(*types.Pointer); ok {
903 if named, ok := tf.(*types.Named); ok { // (be defensive)
904 // If we've already visited this named type
905 // on this path, break the cycle.
906 for _, x := range stack {
911 visit(f.Type(), append(stack, named))
915 // Save accessible fields.
917 fields = append(fields, describeField{
918 implicits: append([]*types.Named(nil), stack...),
929 func isAccessibleFrom(obj types.Object, pkg *types.Package) bool {
930 return ast.IsExported(obj.Name()) || obj.Pkg() == pkg
933 func methodsToSerial(this *types.Package, methods []*types.Selection, fset *token.FileSet) []serial.DescribeMethod {
934 qualifier := types.RelativeTo(this)
935 var jmethods []serial.DescribeMethod
936 for _, meth := range methods {
937 var ser serial.DescribeMethod
938 if meth != nil { // may contain nils when called by implements (on a method)
939 ser = serial.DescribeMethod{
940 Name: types.SelectionString(meth, qualifier),
941 Pos: fset.Position(meth.Obj().Pos()).String(),
944 jmethods = append(jmethods, ser)