diff --git a/cmd/guru/describe.go b/cmd/guru/describe.go index def908e0..abb0de65 100644 --- a/cmd/guru/describe.go +++ b/cmd/guru/describe.go @@ -451,6 +451,7 @@ func describeType(qpos *queryPos, path []ast.Node) (*describeTypeResult, error) description: description, typ: t, methods: accessibleMethods(t, qpos.info.Pkg), + fields: accessibleFields(t, qpos.info.Pkg), }, nil } @@ -460,6 +461,12 @@ type describeTypeResult struct { description string typ types.Type methods []*types.Selection + fields []describeField +} + +type describeField struct { + implicits []*types.Named + field *types.Var } func (r *describeTypeResult) display(printf printfFunc) { @@ -476,15 +483,32 @@ func (r *describeTypeResult) display(printf printfFunc) { if len(r.methods) > 0 { printf(r.node, "Method set:") for _, meth := range r.methods { - // TODO(adonovan): print these relative - // to the owning package, not the - // query package. - printf(meth.Obj(), "\t%s", r.qpos.selectionString(meth)) + // 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()))) } } else { printf(r.node, "No methods.") } } + + // Print the fields, if any. + if len(r.fields) > 0 { + printf(r.node, "Fields:") + for _, f := range r.fields { + var buf bytes.Buffer + for _, fld := range f.implicits { + buf.WriteString(fld.Obj().Name()) + buf.WriteByte('.') + } + + // Print the field type relative to the package + // in which it was defined, not the query package, + printf(f.field, "\t%s%s %s", buf.String(), f.field.Name(), + types.TypeString(f.field.Type(), types.RelativeTo(f.field.Pkg()))) + } + } } func (r *describeTypeResult) toSerial(res *serial.Result, fset *token.FileSet) { @@ -752,6 +776,61 @@ func accessibleMethods(t types.Type, from *types.Package) []*types.Selection { 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 } diff --git a/cmd/guru/testdata/src/describe/main.go b/cmd/guru/testdata/src/describe/main.go index 5f01bb31..aa70160f 100644 --- a/cmd/guru/testdata/src/describe/main.go +++ b/cmd/guru/testdata/src/describe/main.go @@ -7,6 +7,7 @@ package describe // @describe pkgdecl "describe" // TODO(adonovan): more coverage of the (extensive) logic. import ( + "lib" "nosuchpkg" // @describe badimport1 "nosuchpkg" nosuchpkg2 "nosuchpkg" // @describe badimport2 "nosuchpkg2" _ "unsafe" // @describe unsafe "unsafe" @@ -83,6 +84,8 @@ func main() { // @describe func-def-main "main" _ = a2 var _ int // @describe var-decl-stmt2 "var _ int" var _ int // @describe var-def-blank "_" + + var _ lib.Outer // @describe lib-outer "Outer" } type I interface { // @describe def-iface-I "I" diff --git a/cmd/guru/testdata/src/describe/main.golden b/cmd/guru/testdata/src/describe/main.golden index bc6d9f70..d33d350c 100644 --- a/cmd/guru/testdata/src/describe/main.golden +++ b/cmd/guru/testdata/src/describe/main.golden @@ -174,6 +174,15 @@ definition of var _ int -------- @describe var-def-blank -------- definition of var _ int +-------- @describe lib-outer -------- +reference to type lib.Outer (size 56, align 8) +defined as struct{A int; b int; lib.inner} +No methods. +Fields: + A int + inner.C bool + inner.recursive.E bool + -------- @describe def-iface-I -------- definition of type I (size 16, align 8) Method set: diff --git a/cmd/guru/testdata/src/imports/main.golden b/cmd/guru/testdata/src/imports/main.golden index 91442106..2e9d607c 100644 --- a/cmd/guru/testdata/src/imports/main.golden +++ b/cmd/guru/testdata/src/imports/main.golden @@ -9,6 +9,7 @@ import of package "hash/fnv" import of package "lib" const Const untyped int = 3 func Func func() + type Outer struct{...} type Sorter interface{...} method (Sorter) Len() int method (Sorter) Less(i int, j int) bool @@ -33,7 +34,7 @@ defined here reference to type lib.Type (size 8, align 8) defined as int Method set: - method (lib.Type) Method(x *int) *int + method (Type) Method(x *int) *int -------- @describe ref-method -------- reference to method func (lib.Type).Method(x *int) *int @@ -47,6 +48,7 @@ this *int may point to these objects: reference to package "lib" const Const untyped int = 3 func Func func() + type Outer struct{...} type Sorter interface{...} method (Sorter) Len() int method (Sorter) Less(i int, j int) bool diff --git a/cmd/guru/testdata/src/lib/lib.go b/cmd/guru/testdata/src/lib/lib.go index 9131c274..742cdbfa 100644 --- a/cmd/guru/testdata/src/lib/lib.go +++ b/cmd/guru/testdata/src/lib/lib.go @@ -18,3 +18,20 @@ type Sorter interface { Less(i, j int) bool Swap(i, j int) } + +type Outer struct { + A int + b int + inner +} + +type inner struct { + C bool + d string + recursive +} + +type recursive struct { + E bool + *inner +}