.gitignore added
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.1.0 / cmd / guru / describe.go
diff --git a/.config/coc/extensions/coc-go-data/tools/pkg/mod/golang.org/x/tools@v0.1.0/cmd/guru/describe.go b/.config/coc/extensions/coc-go-data/tools/pkg/mod/golang.org/x/tools@v0.1.0/cmd/guru/describe.go
new file mode 100644 (file)
index 0000000..125e409
--- /dev/null
@@ -0,0 +1,947 @@
+// Copyright 2013 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.
+
+package main
+
+import (
+       "bytes"
+       "fmt"
+       "go/ast"
+       "go/constant"
+       "go/token"
+       "go/types"
+       "os"
+       "strings"
+       "unicode/utf8"
+
+       "golang.org/x/tools/cmd/guru/serial"
+       "golang.org/x/tools/go/ast/astutil"
+       "golang.org/x/tools/go/loader"
+       "golang.org/x/tools/go/types/typeutil"
+)
+
+// describe describes the syntax node denoted by the query position,
+// including:
+// - its syntactic category
+// - the definition of its referent (for identifiers) [now redundant]
+// - its type, fields, and methods (for an expression or type expression)
+//
+func describe(q *Query) error {
+       lconf := loader.Config{Build: q.Build}
+       allowErrors(&lconf)
+
+       if _, err := importQueryPackage(q.Pos, &lconf); err != nil {
+               return err
+       }
+
+       // Load/parse/type-check the program.
+       lprog, err := lconf.Load()
+       if err != nil {
+               return err
+       }
+
+       qpos, err := parseQueryPos(lprog, q.Pos, true) // (need exact pos)
+       if err != nil {
+               return err
+       }
+
+       if false { // debugging
+               fprintf(os.Stderr, lprog.Fset, qpos.path[0], "you selected: %s %s",
+                       astutil.NodeDescription(qpos.path[0]), pathToString(qpos.path))
+       }
+
+       var qr QueryResult
+       path, action := findInterestingNode(qpos.info, qpos.path)
+       switch action {
+       case actionExpr:
+               qr, err = describeValue(qpos, path)
+
+       case actionType:
+               qr, err = describeType(qpos, path)
+
+       case actionPackage:
+               qr, err = describePackage(qpos, path)
+
+       case actionStmt:
+               qr, err = describeStmt(qpos, path)
+
+       case actionUnknown:
+               qr = &describeUnknownResult{path[0]}
+
+       default:
+               panic(action) // unreachable
+       }
+       if err != nil {
+               return err
+       }
+       q.Output(lprog.Fset, qr)
+       return nil
+}
+
+type describeUnknownResult struct {
+       node ast.Node
+}
+
+func (r *describeUnknownResult) PrintPlain(printf printfFunc) {
+       // Nothing much to say about misc syntax.
+       printf(r.node, "%s", astutil.NodeDescription(r.node))
+}
+
+func (r *describeUnknownResult) JSON(fset *token.FileSet) []byte {
+       return toJSON(&serial.Describe{
+               Desc: astutil.NodeDescription(r.node),
+               Pos:  fset.Position(r.node.Pos()).String(),
+       })
+}
+
+type action int
+
+const (
+       actionUnknown action = iota // None of the below
+       actionExpr                  // FuncDecl, true Expr or Ident(types.{Const,Var})
+       actionType                  // type Expr or Ident(types.TypeName).
+       actionStmt                  // Stmt or Ident(types.Label)
+       actionPackage               // Ident(types.Package) or ImportSpec
+)
+
+// findInterestingNode classifies the syntax node denoted by path as one of:
+//    - an expression, part of an expression or a reference to a constant
+//      or variable;
+//    - a type, part of a type, or a reference to a named type;
+//    - a statement, part of a statement, or a label referring to a statement;
+//    - part of a package declaration or import spec.
+//    - none of the above.
+// and returns the most "interesting" associated node, which may be
+// the same node, an ancestor or a descendent.
+//
+func findInterestingNode(pkginfo *loader.PackageInfo, path []ast.Node) ([]ast.Node, action) {
+       // TODO(adonovan): integrate with go/types/stdlib_test.go and
+       // apply this to every AST node we can find to make sure it
+       // doesn't crash.
+
+       // TODO(adonovan): audit for ParenExpr safety, esp. since we
+       // traverse up and down.
+
+       // TODO(adonovan): if the users selects the "." in
+       // "fmt.Fprintf()", they'll get an ambiguous selection error;
+       // we won't even reach here.  Can we do better?
+
+       // TODO(adonovan): describing a field within 'type T struct {...}'
+       // describes the (anonymous) struct type and concludes "no methods".
+       // We should ascend to the enclosing type decl, if any.
+
+       for len(path) > 0 {
+               switch n := path[0].(type) {
+               case *ast.GenDecl:
+                       if len(n.Specs) == 1 {
+                               // Descend to sole {Import,Type,Value}Spec child.
+                               path = append([]ast.Node{n.Specs[0]}, path...)
+                               continue
+                       }
+                       return path, actionUnknown // uninteresting
+
+               case *ast.FuncDecl:
+                       // Descend to function name.
+                       path = append([]ast.Node{n.Name}, path...)
+                       continue
+
+               case *ast.ImportSpec:
+                       return path, actionPackage
+
+               case *ast.ValueSpec:
+                       if len(n.Names) == 1 {
+                               // Descend to sole Ident child.
+                               path = append([]ast.Node{n.Names[0]}, path...)
+                               continue
+                       }
+                       return path, actionUnknown // uninteresting
+
+               case *ast.TypeSpec:
+                       // Descend to type name.
+                       path = append([]ast.Node{n.Name}, path...)
+                       continue
+
+               case *ast.Comment, *ast.CommentGroup, *ast.File, *ast.KeyValueExpr, *ast.CommClause:
+                       return path, actionUnknown // uninteresting
+
+               case ast.Stmt:
+                       return path, actionStmt
+
+               case *ast.ArrayType,
+                       *ast.StructType,
+                       *ast.FuncType,
+                       *ast.InterfaceType,
+                       *ast.MapType,
+                       *ast.ChanType:
+                       return path, actionType
+
+               case *ast.Ellipsis:
+                       // Continue to enclosing node.
+                       // e.g. [...]T in ArrayType
+                       //      f(x...) in CallExpr
+                       //      f(x...T) in FuncType
+
+               case *ast.Field:
+                       // TODO(adonovan): this needs more thought,
+                       // since fields can be so many things.
+                       if len(n.Names) == 1 {
+                               // Descend to sole Ident child.
+                               path = append([]ast.Node{n.Names[0]}, path...)
+                               continue
+                       }
+                       // Zero names (e.g. anon field in struct)
+                       // or multiple field or param names:
+                       // continue to enclosing field list.
+
+               case *ast.FieldList:
+                       // Continue to enclosing node:
+                       // {Struct,Func,Interface}Type or FuncDecl.
+
+               case *ast.BasicLit:
+                       if _, ok := path[1].(*ast.ImportSpec); ok {
+                               return path[1:], actionPackage
+                       }
+                       return path, actionExpr
+
+               case *ast.SelectorExpr:
+                       // TODO(adonovan): use Selections info directly.
+                       if pkginfo.Uses[n.Sel] == nil {
+                               // TODO(adonovan): is this reachable?
+                               return path, actionUnknown
+                       }
+                       // Descend to .Sel child.
+                       path = append([]ast.Node{n.Sel}, path...)
+                       continue
+
+               case *ast.Ident:
+                       switch pkginfo.ObjectOf(n).(type) {
+                       case *types.PkgName:
+                               return path, actionPackage
+
+                       case *types.Const:
+                               return path, actionExpr
+
+                       case *types.Label:
+                               return path, actionStmt
+
+                       case *types.TypeName:
+                               return path, actionType
+
+                       case *types.Var:
+                               // For x in 'struct {x T}', return struct type, for now.
+                               if _, ok := path[1].(*ast.Field); ok {
+                                       _ = path[2].(*ast.FieldList) // assertion
+                                       if _, ok := path[3].(*ast.StructType); ok {
+                                               return path[3:], actionType
+                                       }
+                               }
+                               return path, actionExpr
+
+                       case *types.Func:
+                               return path, actionExpr
+
+                       case *types.Builtin:
+                               // For reference to built-in function, return enclosing call.
+                               path = path[1:] // ascend to enclosing function call
+                               continue
+
+                       case *types.Nil:
+                               return path, actionExpr
+                       }
+
+                       // No object.
+                       switch path[1].(type) {
+                       case *ast.SelectorExpr:
+                               // Return enclosing selector expression.
+                               return path[1:], actionExpr
+
+                       case *ast.Field:
+                               // TODO(adonovan): test this.
+                               // e.g. all f in:
+                               //  struct { f, g int }
+                               //  interface { f() }
+                               //  func (f T) method(f, g int) (f, g bool)
+                               //
+                               // switch path[3].(type) {
+                               // case *ast.FuncDecl:
+                               // case *ast.StructType:
+                               // case *ast.InterfaceType:
+                               // }
+                               //
+                               // return path[1:], actionExpr
+                               //
+                               // Unclear what to do with these.
+                               // Struct.Fields             -- field
+                               // Interface.Methods         -- field
+                               // FuncType.{Params.Results} -- actionExpr
+                               // FuncDecl.Recv             -- actionExpr
+
+                       case *ast.File:
+                               // 'package foo'
+                               return path, actionPackage
+
+                       case *ast.ImportSpec:
+                               return path[1:], actionPackage
+
+                       default:
+                               // e.g. blank identifier
+                               // or y in "switch y := x.(type)"
+                               // or code in a _test.go file that's not part of the package.
+                               return path, actionUnknown
+                       }
+
+               case *ast.StarExpr:
+                       if pkginfo.Types[n].IsType() {
+                               return path, actionType
+                       }
+                       return path, actionExpr
+
+               case ast.Expr:
+                       // All Expr but {BasicLit,Ident,StarExpr} are
+                       // "true" expressions that evaluate to a value.
+                       return path, actionExpr
+               }
+
+               // Ascend to parent.
+               path = path[1:]
+       }
+
+       return nil, actionUnknown // unreachable
+}
+
+func describeValue(qpos *queryPos, path []ast.Node) (*describeValueResult, error) {
+       var expr ast.Expr
+       var obj types.Object
+       switch n := path[0].(type) {
+       case *ast.ValueSpec:
+               // ambiguous ValueSpec containing multiple names
+               return nil, fmt.Errorf("multiple value specification")
+       case *ast.Ident:
+               obj = qpos.info.ObjectOf(n)
+               expr = n
+       case ast.Expr:
+               expr = n
+       default:
+               // TODO(adonovan): is this reachable?
+               return nil, fmt.Errorf("unexpected AST for expr: %T", n)
+       }
+
+       typ := qpos.info.TypeOf(expr)
+       if typ == nil {
+               typ = types.Typ[types.Invalid]
+       }
+       constVal := qpos.info.Types[expr].Value
+       if c, ok := obj.(*types.Const); ok {
+               constVal = c.Val()
+       }
+
+       return &describeValueResult{
+               qpos:     qpos,
+               expr:     expr,
+               typ:      typ,
+               names:    appendNames(nil, typ),
+               constVal: constVal,
+               obj:      obj,
+               methods:  accessibleMethods(typ, qpos.info.Pkg),
+               fields:   accessibleFields(typ, qpos.info.Pkg),
+       }, nil
+}
+
+// appendNames returns named types found within the Type by
+// removing map, pointer, channel, slice, and array constructors.
+// It does not descend into structs or interfaces.
+func appendNames(names []*types.Named, typ types.Type) []*types.Named {
+       // elemType specifies type that has some element in it
+       // such as array, slice, chan, pointer
+       type elemType interface {
+               Elem() types.Type
+       }
+
+       switch t := typ.(type) {
+       case *types.Named:
+               names = append(names, t)
+       case *types.Map:
+               names = appendNames(names, t.Key())
+               names = appendNames(names, t.Elem())
+       case elemType:
+               names = appendNames(names, t.Elem())
+       }
+
+       return names
+}
+
+type describeValueResult struct {
+       qpos     *queryPos
+       expr     ast.Expr       // query node
+       typ      types.Type     // type of expression
+       names    []*types.Named // named types within typ
+       constVal constant.Value // value of expression, if constant
+       obj      types.Object   // var/func/const object, if expr was Ident
+       methods  []*types.Selection
+       fields   []describeField
+}
+
+func (r *describeValueResult) PrintPlain(printf printfFunc) {
+       var prefix, suffix string
+       if r.constVal != nil {
+               suffix = fmt.Sprintf(" of value %s", r.constVal)
+       }
+       switch obj := r.obj.(type) {
+       case *types.Func:
+               if recv := obj.Type().(*types.Signature).Recv(); recv != nil {
+                       if _, ok := recv.Type().Underlying().(*types.Interface); ok {
+                               prefix = "interface method "
+                       } else {
+                               prefix = "method "
+                       }
+               }
+       }
+
+       // Describe the expression.
+       if r.obj != nil {
+               if r.obj.Pos() == r.expr.Pos() {
+                       // defining ident
+                       printf(r.expr, "definition of %s%s%s", prefix, r.qpos.objectString(r.obj), suffix)
+               } else {
+                       // referring ident
+                       printf(r.expr, "reference to %s%s%s", prefix, r.qpos.objectString(r.obj), suffix)
+                       if def := r.obj.Pos(); def != token.NoPos {
+                               printf(def, "defined here")
+                       }
+               }
+       } else {
+               desc := astutil.NodeDescription(r.expr)
+               if suffix != "" {
+                       // constant expression
+                       printf(r.expr, "%s%s", desc, suffix)
+               } else {
+                       // non-constant expression
+                       printf(r.expr, "%s of type %s", desc, r.qpos.typeString(r.typ))
+               }
+       }
+
+       printMethods(printf, r.expr, r.methods)
+       printFields(printf, r.expr, r.fields)
+       printNamedTypes(printf, r.expr, r.names)
+}
+
+func (r *describeValueResult) JSON(fset *token.FileSet) []byte {
+       var value, objpos string
+       if r.constVal != nil {
+               value = r.constVal.String()
+       }
+       if r.obj != nil {
+               objpos = fset.Position(r.obj.Pos()).String()
+       }
+
+       typesPos := make([]serial.Definition, len(r.names))
+       for i, t := range r.names {
+               typesPos[i] = serial.Definition{
+                       ObjPos: fset.Position(t.Obj().Pos()).String(),
+                       Desc:   r.qpos.typeString(t),
+               }
+       }
+
+       return toJSON(&serial.Describe{
+               Desc:   astutil.NodeDescription(r.expr),
+               Pos:    fset.Position(r.expr.Pos()).String(),
+               Detail: "value",
+               Value: &serial.DescribeValue{
+                       Type:     r.qpos.typeString(r.typ),
+                       TypesPos: typesPos,
+                       Value:    value,
+                       ObjPos:   objpos,
+               },
+       })
+}
+
+// ---- TYPE ------------------------------------------------------------
+
+func describeType(qpos *queryPos, path []ast.Node) (*describeTypeResult, error) {
+       var description string
+       var typ types.Type
+       switch n := path[0].(type) {
+       case *ast.Ident:
+               obj := qpos.info.ObjectOf(n).(*types.TypeName)
+               typ = obj.Type()
+               if isAlias(obj) {
+                       description = "alias of "
+               } else if obj.Pos() == n.Pos() {
+                       description = "definition of " // (Named type)
+               } else if _, ok := typ.(*types.Basic); ok {
+                       description = "reference to built-in "
+               } else {
+                       description = "reference to " // (Named type)
+               }
+
+       case ast.Expr:
+               typ = qpos.info.TypeOf(n)
+
+       default:
+               // Unreachable?
+               return nil, fmt.Errorf("unexpected AST for type: %T", n)
+       }
+
+       description = description + "type " + qpos.typeString(typ)
+
+       // Show sizes for structs and named types (it's fairly obvious for others).
+       switch typ.(type) {
+       case *types.Named, *types.Struct:
+               szs := types.StdSizes{WordSize: 8, MaxAlign: 8} // assume amd64
+               description = fmt.Sprintf("%s (size %d, align %d)", description,
+                       szs.Sizeof(typ), szs.Alignof(typ))
+       }
+
+       return &describeTypeResult{
+               qpos:        qpos,
+               node:        path[0],
+               description: description,
+               typ:         typ,
+               methods:     accessibleMethods(typ, qpos.info.Pkg),
+               fields:      accessibleFields(typ, qpos.info.Pkg),
+       }, nil
+}
+
+type describeTypeResult struct {
+       qpos        *queryPos
+       node        ast.Node
+       description string
+       typ         types.Type
+       methods     []*types.Selection
+       fields      []describeField
+}
+
+type describeField struct {
+       implicits []*types.Named
+       field     *types.Var
+}
+
+func printMethods(printf printfFunc, node ast.Node, methods []*types.Selection) {
+       if len(methods) > 0 {
+               printf(node, "Methods:")
+       }
+       for _, meth := range methods {
+               // Print the method type relative to the package
+               // in which it was defined, not the query package,
+               printf(meth.Obj(), "\t%s",
+                       types.SelectionString(meth, types.RelativeTo(meth.Obj().Pkg())))
+       }
+}
+
+func printFields(printf printfFunc, node ast.Node, fields []describeField) {
+       if len(fields) > 0 {
+               printf(node, "Fields:")
+       }
+
+       // Align the names and the types (requires two passes).
+       var width int
+       var names []string
+       for _, f := range fields {
+               var buf bytes.Buffer
+               for _, fld := range f.implicits {
+                       buf.WriteString(fld.Obj().Name())
+                       buf.WriteByte('.')
+               }
+               buf.WriteString(f.field.Name())
+               name := buf.String()
+               if n := utf8.RuneCountInString(name); n > width {
+                       width = n
+               }
+               names = append(names, name)
+       }
+
+       for i, f := range fields {
+               // Print the field type relative to the package
+               // in which it was defined, not the query package,
+               printf(f.field, "\t%*s %s", -width, names[i],
+                       types.TypeString(f.field.Type(), types.RelativeTo(f.field.Pkg())))
+       }
+}
+
+func printNamedTypes(printf printfFunc, node ast.Node, names []*types.Named) {
+       if len(names) > 0 {
+               printf(node, "Named types:")
+       }
+
+       for _, t := range names {
+               // Print the type relative to the package
+               // in which it was defined, not the query package,
+               printf(t.Obj(), "\ttype %s defined here",
+                       types.TypeString(t.Obj().Type(), types.RelativeTo(t.Obj().Pkg())))
+       }
+}
+
+func (r *describeTypeResult) PrintPlain(printf printfFunc) {
+       printf(r.node, "%s", r.description)
+
+       // Show the underlying type for a reference to a named type.
+       if nt, ok := r.typ.(*types.Named); ok && r.node.Pos() != nt.Obj().Pos() {
+               // TODO(adonovan): improve display of complex struct/interface types.
+               printf(nt.Obj(), "defined as %s", r.qpos.typeString(nt.Underlying()))
+       }
+
+       printMethods(printf, r.node, r.methods)
+       if len(r.methods) == 0 {
+               // Only report null result for type kinds
+               // capable of bearing methods.
+               switch r.typ.(type) {
+               case *types.Interface, *types.Struct, *types.Named:
+                       printf(r.node, "No methods.")
+               }
+       }
+
+       printFields(printf, r.node, r.fields)
+}
+
+func (r *describeTypeResult) JSON(fset *token.FileSet) []byte {
+       var namePos, nameDef string
+       if nt, ok := r.typ.(*types.Named); ok {
+               namePos = fset.Position(nt.Obj().Pos()).String()
+               nameDef = nt.Underlying().String()
+       }
+       return toJSON(&serial.Describe{
+               Desc:   r.description,
+               Pos:    fset.Position(r.node.Pos()).String(),
+               Detail: "type",
+               Type: &serial.DescribeType{
+                       Type:    r.qpos.typeString(r.typ),
+                       NamePos: namePos,
+                       NameDef: nameDef,
+                       Methods: methodsToSerial(r.qpos.info.Pkg, r.methods, fset),
+               },
+       })
+}
+
+// ---- PACKAGE ------------------------------------------------------------
+
+func describePackage(qpos *queryPos, path []ast.Node) (*describePackageResult, error) {
+       var description string
+       var pkg *types.Package
+       switch n := path[0].(type) {
+       case *ast.ImportSpec:
+               var obj types.Object
+               if n.Name != nil {
+                       obj = qpos.info.Defs[n.Name]
+               } else {
+                       obj = qpos.info.Implicits[n]
+               }
+               pkgname, _ := obj.(*types.PkgName)
+               if pkgname == nil {
+                       return nil, fmt.Errorf("can't import package %s", n.Path.Value)
+               }
+               pkg = pkgname.Imported()
+               description = fmt.Sprintf("import of package %q", pkg.Path())
+
+       case *ast.Ident:
+               if _, isDef := path[1].(*ast.File); isDef {
+                       // e.g. package id
+                       pkg = qpos.info.Pkg
+                       description = fmt.Sprintf("definition of package %q", pkg.Path())
+               } else {
+                       // e.g. import id "..."
+                       //  or  id.F()
+                       pkg = qpos.info.ObjectOf(n).(*types.PkgName).Imported()
+                       description = fmt.Sprintf("reference to package %q", pkg.Path())
+               }
+
+       default:
+               // Unreachable?
+               return nil, fmt.Errorf("unexpected AST for package: %T", n)
+       }
+
+       var members []*describeMember
+       // NB: "unsafe" has no types.Package
+       if pkg != nil {
+               // Enumerate the accessible package members
+               // in lexicographic order.
+               for _, name := range pkg.Scope().Names() {
+                       if pkg == qpos.info.Pkg || ast.IsExported(name) {
+                               mem := pkg.Scope().Lookup(name)
+                               var methods []*types.Selection
+                               if mem, ok := mem.(*types.TypeName); ok {
+                                       methods = accessibleMethods(mem.Type(), qpos.info.Pkg)
+                               }
+                               members = append(members, &describeMember{
+                                       mem,
+                                       methods,
+                               })
+
+                       }
+               }
+       }
+
+       return &describePackageResult{qpos.fset, path[0], description, pkg, members}, nil
+}
+
+type describePackageResult struct {
+       fset        *token.FileSet
+       node        ast.Node
+       description string
+       pkg         *types.Package
+       members     []*describeMember // in lexicographic name order
+}
+
+type describeMember struct {
+       obj     types.Object
+       methods []*types.Selection // in types.MethodSet order
+}
+
+func (r *describePackageResult) PrintPlain(printf printfFunc) {
+       printf(r.node, "%s", r.description)
+
+       // Compute max width of name "column".
+       maxname := 0
+       for _, mem := range r.members {
+               if l := len(mem.obj.Name()); l > maxname {
+                       maxname = l
+               }
+       }
+
+       for _, mem := range r.members {
+               printf(mem.obj, "\t%s", formatMember(mem.obj, maxname))
+               for _, meth := range mem.methods {
+                       printf(meth.Obj(), "\t\t%s", types.SelectionString(meth, types.RelativeTo(r.pkg)))
+               }
+       }
+}
+
+func formatMember(obj types.Object, maxname int) string {
+       qualifier := types.RelativeTo(obj.Pkg())
+       var buf bytes.Buffer
+       fmt.Fprintf(&buf, "%-5s %-*s", tokenOf(obj), maxname, obj.Name())
+       switch obj := obj.(type) {
+       case *types.Const:
+               fmt.Fprintf(&buf, " %s = %s", types.TypeString(obj.Type(), qualifier), obj.Val())
+
+       case *types.Func:
+               fmt.Fprintf(&buf, " %s", types.TypeString(obj.Type(), qualifier))
+
+       case *types.TypeName:
+               typ := obj.Type()
+               if isAlias(obj) {
+                       buf.WriteString(" = ")
+               } else {
+                       buf.WriteByte(' ')
+                       typ = typ.Underlying()
+               }
+               var typestr string
+               // Abbreviate long aggregate type names.
+               switch typ := typ.(type) {
+               case *types.Interface:
+                       if typ.NumMethods() > 1 {
+                               typestr = "interface{...}"
+                       }
+               case *types.Struct:
+                       if typ.NumFields() > 1 {
+                               typestr = "struct{...}"
+                       }
+               }
+               if typestr == "" {
+                       typestr = types.TypeString(typ, qualifier)
+               }
+               buf.WriteString(typestr)
+
+       case *types.Var:
+               fmt.Fprintf(&buf, " %s", types.TypeString(obj.Type(), qualifier))
+       }
+       return buf.String()
+}
+
+func (r *describePackageResult) JSON(fset *token.FileSet) []byte {
+       var members []*serial.DescribeMember
+       for _, mem := range r.members {
+               obj := mem.obj
+               typ := obj.Type()
+               var val string
+               var alias string
+               switch obj := obj.(type) {
+               case *types.Const:
+                       val = obj.Val().String()
+               case *types.TypeName:
+                       if isAlias(obj) {
+                               alias = "= " // kludgy
+                       } else {
+                               typ = typ.Underlying()
+                       }
+               }
+               members = append(members, &serial.DescribeMember{
+                       Name:    obj.Name(),
+                       Type:    alias + typ.String(),
+                       Value:   val,
+                       Pos:     fset.Position(obj.Pos()).String(),
+                       Kind:    tokenOf(obj),
+                       Methods: methodsToSerial(r.pkg, mem.methods, fset),
+               })
+       }
+       return toJSON(&serial.Describe{
+               Desc:   r.description,
+               Pos:    fset.Position(r.node.Pos()).String(),
+               Detail: "package",
+               Package: &serial.DescribePackage{
+                       Path:    r.pkg.Path(),
+                       Members: members,
+               },
+       })
+}
+
+func tokenOf(o types.Object) string {
+       switch o.(type) {
+       case *types.Func:
+               return "func"
+       case *types.Var:
+               return "var"
+       case *types.TypeName:
+               return "type"
+       case *types.Const:
+               return "const"
+       case *types.PkgName:
+               return "package"
+       case *types.Builtin:
+               return "builtin" // e.g. when describing package "unsafe"
+       case *types.Nil:
+               return "nil"
+       case *types.Label:
+               return "label"
+       }
+       panic(o)
+}
+
+// ---- STATEMENT ------------------------------------------------------------
+
+func describeStmt(qpos *queryPos, path []ast.Node) (*describeStmtResult, error) {
+       var description string
+       switch n := path[0].(type) {
+       case *ast.Ident:
+               if qpos.info.Defs[n] != nil {
+                       description = "labelled statement"
+               } else {
+                       description = "reference to labelled statement"
+               }
+
+       default:
+               // Nothing much to say about statements.
+               description = astutil.NodeDescription(n)
+       }
+       return &describeStmtResult{qpos.fset, path[0], description}, nil
+}
+
+type describeStmtResult struct {
+       fset        *token.FileSet
+       node        ast.Node
+       description string
+}
+
+func (r *describeStmtResult) PrintPlain(printf printfFunc) {
+       printf(r.node, "%s", r.description)
+}
+
+func (r *describeStmtResult) JSON(fset *token.FileSet) []byte {
+       return toJSON(&serial.Describe{
+               Desc:   r.description,
+               Pos:    fset.Position(r.node.Pos()).String(),
+               Detail: "unknown",
+       })
+}
+
+// ------------------- Utilities -------------------
+
+// pathToString returns a string containing the concrete types of the
+// nodes in path.
+func pathToString(path []ast.Node) string {
+       var buf bytes.Buffer
+       fmt.Fprint(&buf, "[")
+       for i, n := range path {
+               if i > 0 {
+                       fmt.Fprint(&buf, " ")
+               }
+               fmt.Fprint(&buf, strings.TrimPrefix(fmt.Sprintf("%T", n), "*ast."))
+       }
+       fmt.Fprint(&buf, "]")
+       return buf.String()
+}
+
+func accessibleMethods(t types.Type, from *types.Package) []*types.Selection {
+       var methods []*types.Selection
+       for _, meth := range typeutil.IntuitiveMethodSet(t, nil) {
+               if isAccessibleFrom(meth.Obj(), from) {
+                       methods = append(methods, meth)
+               }
+       }
+       return methods
+}
+
+// accessibleFields returns the set of accessible
+// field selections on a value of type recv.
+func accessibleFields(recv types.Type, from *types.Package) []describeField {
+       wantField := func(f *types.Var) bool {
+               if !isAccessibleFrom(f, from) {
+                       return false
+               }
+               // Check that the field is not shadowed.
+               obj, _, _ := types.LookupFieldOrMethod(recv, true, f.Pkg(), f.Name())
+               return obj == f
+       }
+
+       var fields []describeField
+       var visit func(t types.Type, stack []*types.Named)
+       visit = func(t types.Type, stack []*types.Named) {
+               tStruct, ok := deref(t).Underlying().(*types.Struct)
+               if !ok {
+                       return
+               }
+       fieldloop:
+               for i := 0; i < tStruct.NumFields(); i++ {
+                       f := tStruct.Field(i)
+
+                       // Handle recursion through anonymous fields.
+                       if f.Anonymous() {
+                               tf := f.Type()
+                               if ptr, ok := tf.(*types.Pointer); ok {
+                                       tf = ptr.Elem()
+                               }
+                               if named, ok := tf.(*types.Named); ok { // (be defensive)
+                                       // If we've already visited this named type
+                                       // on this path, break the cycle.
+                                       for _, x := range stack {
+                                               if x == named {
+                                                       continue fieldloop
+                                               }
+                                       }
+                                       visit(f.Type(), append(stack, named))
+                               }
+                       }
+
+                       // Save accessible fields.
+                       if wantField(f) {
+                               fields = append(fields, describeField{
+                                       implicits: append([]*types.Named(nil), stack...),
+                                       field:     f,
+                               })
+                       }
+               }
+       }
+       visit(recv, nil)
+
+       return fields
+}
+
+func isAccessibleFrom(obj types.Object, pkg *types.Package) bool {
+       return ast.IsExported(obj.Name()) || obj.Pkg() == pkg
+}
+
+func methodsToSerial(this *types.Package, methods []*types.Selection, fset *token.FileSet) []serial.DescribeMethod {
+       qualifier := types.RelativeTo(this)
+       var jmethods []serial.DescribeMethod
+       for _, meth := range methods {
+               var ser serial.DescribeMethod
+               if meth != nil { // may contain nils when called by implements (on a method)
+                       ser = serial.DescribeMethod{
+                               Name: types.SelectionString(meth, qualifier),
+                               Pos:  fset.Position(meth.Obj().Pos()).String(),
+                       }
+               }
+               jmethods = append(jmethods, ser)
+       }
+       return jmethods
+}