Alan Donovan | 7347379 | 2016-02-11 17:57:17 -0500 | [diff] [blame] | 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. |
| 4 | |
| 5 | package main |
| 6 | |
| 7 | import ( |
| 8 | "fmt" |
| 9 | "go/ast" |
| 10 | "go/token" |
| 11 | "go/types" |
| 12 | "reflect" |
| 13 | "sort" |
| 14 | "strings" |
| 15 | |
Alan Donovan | 37bb37e | 2016-02-11 22:57:18 -0500 | [diff] [blame] | 16 | "golang.org/x/tools/cmd/guru/serial" |
Alan Donovan | 7347379 | 2016-02-11 17:57:17 -0500 | [diff] [blame] | 17 | "golang.org/x/tools/go/loader" |
| 18 | "golang.org/x/tools/go/types/typeutil" |
Alan Donovan | 7347379 | 2016-02-11 17:57:17 -0500 | [diff] [blame] | 19 | "golang.org/x/tools/refactor/importgraph" |
| 20 | ) |
| 21 | |
Koichi Shiraishi | be3ddff | 2017-07-10 14:54:58 +0900 | [diff] [blame] | 22 | // The implements function displays the "implements" relation as it pertains to the |
Alan Donovan | 7347379 | 2016-02-11 17:57:17 -0500 | [diff] [blame] | 23 | // selected type. |
| 24 | // If the selection is a method, 'implements' displays |
| 25 | // the corresponding methods of the types that would have been reported |
| 26 | // by an implements query on the receiver type. |
| 27 | // |
| 28 | func implements(q *Query) error { |
| 29 | lconf := loader.Config{Build: q.Build} |
| 30 | allowErrors(&lconf) |
| 31 | |
| 32 | qpkg, err := importQueryPackage(q.Pos, &lconf) |
| 33 | if err != nil { |
| 34 | return err |
| 35 | } |
| 36 | |
| 37 | // Set the packages to search. |
| 38 | if len(q.Scope) > 0 { |
| 39 | // Inspect all packages in the analysis scope, if specified. |
| 40 | if err := setPTAScope(&lconf, q.Scope); err != nil { |
| 41 | return err |
| 42 | } |
| 43 | } else { |
| 44 | // Otherwise inspect the forward and reverse |
| 45 | // transitive closure of the selected package. |
| 46 | // (In theory even this is incomplete.) |
| 47 | _, rev, _ := importgraph.Build(q.Build) |
| 48 | for path := range rev.Search(qpkg) { |
| 49 | lconf.ImportWithTests(path) |
| 50 | } |
| 51 | |
| 52 | // TODO(adonovan): for completeness, we should also |
| 53 | // type-check and inspect function bodies in all |
| 54 | // imported packages. This would be expensive, but we |
| 55 | // could optimize by skipping functions that do not |
| 56 | // contain type declarations. This would require |
| 57 | // changing the loader's TypeCheckFuncBodies hook to |
| 58 | // provide the []*ast.File. |
| 59 | } |
| 60 | |
| 61 | // Load/parse/type-check the program. |
| 62 | lprog, err := lconf.Load() |
| 63 | if err != nil { |
| 64 | return err |
| 65 | } |
Alan Donovan | 7347379 | 2016-02-11 17:57:17 -0500 | [diff] [blame] | 66 | |
| 67 | qpos, err := parseQueryPos(lprog, q.Pos, false) |
| 68 | if err != nil { |
| 69 | return err |
| 70 | } |
| 71 | |
| 72 | // Find the selected type. |
| 73 | path, action := findInterestingNode(qpos.info, qpos.path) |
| 74 | |
| 75 | var method *types.Func |
| 76 | var T types.Type // selected type (receiver if method != nil) |
| 77 | |
| 78 | switch action { |
| 79 | case actionExpr: |
| 80 | // method? |
| 81 | if id, ok := path[0].(*ast.Ident); ok { |
| 82 | if obj, ok := qpos.info.ObjectOf(id).(*types.Func); ok { |
| 83 | recv := obj.Type().(*types.Signature).Recv() |
| 84 | if recv == nil { |
| 85 | return fmt.Errorf("this function is not a method") |
| 86 | } |
| 87 | method = obj |
| 88 | T = recv.Type() |
| 89 | } |
| 90 | } |
Alan Donovan | 0f5f9fc | 2016-04-17 12:46:58 -0400 | [diff] [blame] | 91 | |
| 92 | // If not a method, use the expression's type. |
| 93 | if T == nil { |
| 94 | T = qpos.info.TypeOf(path[0].(ast.Expr)) |
| 95 | } |
| 96 | |
Alan Donovan | 7347379 | 2016-02-11 17:57:17 -0500 | [diff] [blame] | 97 | case actionType: |
| 98 | T = qpos.info.TypeOf(path[0].(ast.Expr)) |
| 99 | } |
| 100 | if T == nil { |
Alan Donovan | 0f5f9fc | 2016-04-17 12:46:58 -0400 | [diff] [blame] | 101 | return fmt.Errorf("not a type, method, or value") |
Alan Donovan | 7347379 | 2016-02-11 17:57:17 -0500 | [diff] [blame] | 102 | } |
| 103 | |
| 104 | // Find all named types, even local types (which can have |
Alan Donovan | 6e7ee5a | 2017-02-09 14:54:08 -0500 | [diff] [blame] | 105 | // methods due to promotion) and the built-in "error". |
| 106 | // We ignore aliases 'type M = N' to avoid duplicate |
| 107 | // reporting of the Named type N. |
| 108 | var allNamed []*types.Named |
Alan Donovan | 7347379 | 2016-02-11 17:57:17 -0500 | [diff] [blame] | 109 | for _, info := range lprog.AllPackages { |
| 110 | for _, obj := range info.Defs { |
Alan Donovan | 6e7ee5a | 2017-02-09 14:54:08 -0500 | [diff] [blame] | 111 | if obj, ok := obj.(*types.TypeName); ok && !isAlias(obj) { |
| 112 | if named, ok := obj.Type().(*types.Named); ok { |
| 113 | allNamed = append(allNamed, named) |
| 114 | } |
Alan Donovan | 7347379 | 2016-02-11 17:57:17 -0500 | [diff] [blame] | 115 | } |
| 116 | } |
| 117 | } |
Alan Donovan | 6e7ee5a | 2017-02-09 14:54:08 -0500 | [diff] [blame] | 118 | allNamed = append(allNamed, types.Universe.Lookup("error").Type().(*types.Named)) |
Alan Donovan | 7347379 | 2016-02-11 17:57:17 -0500 | [diff] [blame] | 119 | |
| 120 | var msets typeutil.MethodSetCache |
| 121 | |
| 122 | // Test each named type. |
| 123 | var to, from, fromPtr []types.Type |
| 124 | for _, U := range allNamed { |
| 125 | if isInterface(T) { |
| 126 | if msets.MethodSet(T).Len() == 0 { |
| 127 | continue // empty interface |
| 128 | } |
| 129 | if isInterface(U) { |
| 130 | if msets.MethodSet(U).Len() == 0 { |
| 131 | continue // empty interface |
| 132 | } |
| 133 | |
| 134 | // T interface, U interface |
| 135 | if !types.Identical(T, U) { |
| 136 | if types.AssignableTo(U, T) { |
| 137 | to = append(to, U) |
| 138 | } |
| 139 | if types.AssignableTo(T, U) { |
| 140 | from = append(from, U) |
| 141 | } |
| 142 | } |
| 143 | } else { |
| 144 | // T interface, U concrete |
| 145 | if types.AssignableTo(U, T) { |
| 146 | to = append(to, U) |
| 147 | } else if pU := types.NewPointer(U); types.AssignableTo(pU, T) { |
| 148 | to = append(to, pU) |
| 149 | } |
| 150 | } |
| 151 | } else if isInterface(U) { |
| 152 | if msets.MethodSet(U).Len() == 0 { |
| 153 | continue // empty interface |
| 154 | } |
| 155 | |
| 156 | // T concrete, U interface |
| 157 | if types.AssignableTo(T, U) { |
| 158 | from = append(from, U) |
| 159 | } else if pT := types.NewPointer(T); types.AssignableTo(pT, U) { |
| 160 | fromPtr = append(fromPtr, U) |
| 161 | } |
| 162 | } |
| 163 | } |
| 164 | |
| 165 | var pos interface{} = qpos |
| 166 | if nt, ok := deref(T).(*types.Named); ok { |
| 167 | pos = nt.Obj() |
| 168 | } |
| 169 | |
| 170 | // Sort types (arbitrarily) to ensure test determinism. |
| 171 | sort.Sort(typesByString(to)) |
| 172 | sort.Sort(typesByString(from)) |
| 173 | sort.Sort(typesByString(fromPtr)) |
| 174 | |
| 175 | var toMethod, fromMethod, fromPtrMethod []*types.Selection // contain nils |
| 176 | if method != nil { |
| 177 | for _, t := range to { |
| 178 | toMethod = append(toMethod, |
| 179 | types.NewMethodSet(t).Lookup(method.Pkg(), method.Name())) |
| 180 | } |
| 181 | for _, t := range from { |
| 182 | fromMethod = append(fromMethod, |
| 183 | types.NewMethodSet(t).Lookup(method.Pkg(), method.Name())) |
| 184 | } |
| 185 | for _, t := range fromPtr { |
| 186 | fromPtrMethod = append(fromPtrMethod, |
| 187 | types.NewMethodSet(t).Lookup(method.Pkg(), method.Name())) |
| 188 | } |
| 189 | } |
| 190 | |
Alan Donovan | 2da0720 | 2016-04-01 15:04:45 -0400 | [diff] [blame] | 191 | q.Output(lprog.Fset, &implementsResult{ |
Alan Donovan | 7347379 | 2016-02-11 17:57:17 -0500 | [diff] [blame] | 192 | qpos, T, pos, to, from, fromPtr, method, toMethod, fromMethod, fromPtrMethod, |
Alan Donovan | 2da0720 | 2016-04-01 15:04:45 -0400 | [diff] [blame] | 193 | }) |
Alan Donovan | 7347379 | 2016-02-11 17:57:17 -0500 | [diff] [blame] | 194 | return nil |
| 195 | } |
| 196 | |
| 197 | type implementsResult struct { |
| 198 | qpos *queryPos |
| 199 | |
| 200 | t types.Type // queried type (not necessarily named) |
| 201 | pos interface{} // pos of t (*types.Name or *QueryPos) |
| 202 | to []types.Type // named or ptr-to-named types assignable to interface T |
| 203 | from []types.Type // named interfaces assignable from T |
| 204 | fromPtr []types.Type // named interfaces assignable only from *T |
| 205 | |
| 206 | // if a method was queried: |
| 207 | method *types.Func // queried method |
| 208 | toMethod []*types.Selection // method of type to[i], if any |
| 209 | fromMethod []*types.Selection // method of type from[i], if any |
| 210 | fromPtrMethod []*types.Selection // method of type fromPtrMethod[i], if any |
| 211 | } |
| 212 | |
Alan Donovan | 2da0720 | 2016-04-01 15:04:45 -0400 | [diff] [blame] | 213 | func (r *implementsResult) PrintPlain(printf printfFunc) { |
Alan Donovan | 7347379 | 2016-02-11 17:57:17 -0500 | [diff] [blame] | 214 | relation := "is implemented by" |
| 215 | |
| 216 | meth := func(sel *types.Selection) { |
| 217 | if sel != nil { |
| 218 | printf(sel.Obj(), "\t%s method (%s).%s", |
| 219 | relation, r.qpos.typeString(sel.Recv()), sel.Obj().Name()) |
| 220 | } |
| 221 | } |
| 222 | |
| 223 | if isInterface(r.t) { |
| 224 | if types.NewMethodSet(r.t).Len() == 0 { // TODO(adonovan): cache mset |
| 225 | printf(r.pos, "empty interface type %s", r.qpos.typeString(r.t)) |
| 226 | return |
| 227 | } |
| 228 | |
| 229 | if r.method == nil { |
| 230 | printf(r.pos, "interface type %s", r.qpos.typeString(r.t)) |
| 231 | } else { |
| 232 | printf(r.method, "abstract method %s", r.qpos.objectString(r.method)) |
| 233 | } |
| 234 | |
| 235 | // Show concrete types (or methods) first; use two passes. |
| 236 | for i, sub := range r.to { |
| 237 | if !isInterface(sub) { |
| 238 | if r.method == nil { |
| 239 | printf(deref(sub).(*types.Named).Obj(), "\t%s %s type %s", |
| 240 | relation, typeKind(sub), r.qpos.typeString(sub)) |
| 241 | } else { |
| 242 | meth(r.toMethod[i]) |
| 243 | } |
| 244 | } |
| 245 | } |
| 246 | for i, sub := range r.to { |
| 247 | if isInterface(sub) { |
| 248 | if r.method == nil { |
| 249 | printf(sub.(*types.Named).Obj(), "\t%s %s type %s", |
| 250 | relation, typeKind(sub), r.qpos.typeString(sub)) |
| 251 | } else { |
| 252 | meth(r.toMethod[i]) |
| 253 | } |
| 254 | } |
| 255 | } |
| 256 | |
| 257 | relation = "implements" |
| 258 | for i, super := range r.from { |
| 259 | if r.method == nil { |
| 260 | printf(super.(*types.Named).Obj(), "\t%s %s", |
| 261 | relation, r.qpos.typeString(super)) |
| 262 | } else { |
| 263 | meth(r.fromMethod[i]) |
| 264 | } |
| 265 | } |
| 266 | } else { |
| 267 | relation = "implements" |
| 268 | |
| 269 | if r.from != nil { |
| 270 | if r.method == nil { |
| 271 | printf(r.pos, "%s type %s", |
| 272 | typeKind(r.t), r.qpos.typeString(r.t)) |
| 273 | } else { |
| 274 | printf(r.method, "concrete method %s", |
| 275 | r.qpos.objectString(r.method)) |
| 276 | } |
| 277 | for i, super := range r.from { |
| 278 | if r.method == nil { |
| 279 | printf(super.(*types.Named).Obj(), "\t%s %s", |
| 280 | relation, r.qpos.typeString(super)) |
| 281 | } else { |
| 282 | meth(r.fromMethod[i]) |
| 283 | } |
| 284 | } |
| 285 | } |
| 286 | if r.fromPtr != nil { |
| 287 | if r.method == nil { |
| 288 | printf(r.pos, "pointer type *%s", r.qpos.typeString(r.t)) |
| 289 | } else { |
| 290 | // TODO(adonovan): de-dup (C).f and (*C).f implementing (I).f. |
| 291 | printf(r.method, "concrete method %s", |
| 292 | r.qpos.objectString(r.method)) |
| 293 | } |
| 294 | |
| 295 | for i, psuper := range r.fromPtr { |
| 296 | if r.method == nil { |
| 297 | printf(psuper.(*types.Named).Obj(), "\t%s %s", |
| 298 | relation, r.qpos.typeString(psuper)) |
| 299 | } else { |
| 300 | meth(r.fromPtrMethod[i]) |
| 301 | } |
| 302 | } |
| 303 | } else if r.from == nil { |
| 304 | printf(r.pos, "%s type %s implements only interface{}", |
| 305 | typeKind(r.t), r.qpos.typeString(r.t)) |
| 306 | } |
| 307 | } |
| 308 | } |
| 309 | |
Alan Donovan | 2da0720 | 2016-04-01 15:04:45 -0400 | [diff] [blame] | 310 | func (r *implementsResult) JSON(fset *token.FileSet) []byte { |
| 311 | var method *serial.DescribeMethod |
| 312 | if r.method != nil { |
| 313 | method = &serial.DescribeMethod{ |
| 314 | Name: r.qpos.objectString(r.method), |
| 315 | Pos: fset.Position(r.method.Pos()).String(), |
| 316 | } |
| 317 | } |
| 318 | return toJSON(&serial.Implements{ |
Alan Donovan | 7347379 | 2016-02-11 17:57:17 -0500 | [diff] [blame] | 319 | T: makeImplementsType(r.t, fset), |
| 320 | AssignableTo: makeImplementsTypes(r.to, fset), |
| 321 | AssignableFrom: makeImplementsTypes(r.from, fset), |
| 322 | AssignableFromPtr: makeImplementsTypes(r.fromPtr, fset), |
| 323 | AssignableToMethod: methodsToSerial(r.qpos.info.Pkg, r.toMethod, fset), |
| 324 | AssignableFromMethod: methodsToSerial(r.qpos.info.Pkg, r.fromMethod, fset), |
| 325 | AssignableFromPtrMethod: methodsToSerial(r.qpos.info.Pkg, r.fromPtrMethod, fset), |
Alan Donovan | 2da0720 | 2016-04-01 15:04:45 -0400 | [diff] [blame] | 326 | Method: method, |
| 327 | }) |
| 328 | |
Alan Donovan | 7347379 | 2016-02-11 17:57:17 -0500 | [diff] [blame] | 329 | } |
| 330 | |
| 331 | func makeImplementsTypes(tt []types.Type, fset *token.FileSet) []serial.ImplementsType { |
| 332 | var r []serial.ImplementsType |
| 333 | for _, t := range tt { |
| 334 | r = append(r, makeImplementsType(t, fset)) |
| 335 | } |
| 336 | return r |
| 337 | } |
| 338 | |
| 339 | func makeImplementsType(T types.Type, fset *token.FileSet) serial.ImplementsType { |
| 340 | var pos token.Pos |
| 341 | if nt, ok := deref(T).(*types.Named); ok { // implementsResult.t may be non-named |
| 342 | pos = nt.Obj().Pos() |
| 343 | } |
| 344 | return serial.ImplementsType{ |
| 345 | Name: T.String(), |
| 346 | Pos: fset.Position(pos).String(), |
| 347 | Kind: typeKind(T), |
| 348 | } |
| 349 | } |
| 350 | |
| 351 | // typeKind returns a string describing the underlying kind of type, |
| 352 | // e.g. "slice", "array", "struct". |
| 353 | func typeKind(T types.Type) string { |
| 354 | s := reflect.TypeOf(T.Underlying()).String() |
| 355 | return strings.ToLower(strings.TrimPrefix(s, "*types.")) |
| 356 | } |
| 357 | |
| 358 | func isInterface(T types.Type) bool { return types.IsInterface(T) } |
| 359 | |
| 360 | type typesByString []types.Type |
| 361 | |
| 362 | func (p typesByString) Len() int { return len(p) } |
| 363 | func (p typesByString) Less(i, j int) bool { return p[i].String() < p[j].String() } |
| 364 | func (p typesByString) Swap(i, j int) { p[i], p[j] = p[j], p[i] } |