diff --git a/oracle/definition.go b/oracle/definition.go index a0340c6a4f..eaf754e10a 100644 --- a/oracle/definition.go +++ b/oracle/definition.go @@ -23,7 +23,7 @@ func definition(q *Query) error { lconf := loader.Config{Build: q.Build} allowErrors(&lconf) - if err := importQueryPackage(q.Pos, &lconf); err != nil { + if _, err := importQueryPackage(q.Pos, &lconf); err != nil { return err } diff --git a/oracle/describe.go b/oracle/describe.go index ea1c5ecf7c..db53696e06 100644 --- a/oracle/describe.go +++ b/oracle/describe.go @@ -31,7 +31,7 @@ func describe(q *Query) error { lconf := loader.Config{Build: q.Build} allowErrors(&lconf) - if err := importQueryPackage(q.Pos, &lconf); err != nil { + if _, err := importQueryPackage(q.Pos, &lconf); err != nil { return err } diff --git a/oracle/freevars.go b/oracle/freevars.go index 400a118f6b..c0937f9d87 100644 --- a/oracle/freevars.go +++ b/oracle/freevars.go @@ -33,7 +33,7 @@ func freevars(q *Query) error { lconf := loader.Config{Build: q.Build} allowErrors(&lconf) - if err := importQueryPackage(q.Pos, &lconf); err != nil { + if _, err := importQueryPackage(q.Pos, &lconf); err != nil { return err } diff --git a/oracle/implements.go b/oracle/implements.go index 3155ca29e9..bf58154e8a 100644 --- a/oracle/implements.go +++ b/oracle/implements.go @@ -16,10 +16,11 @@ import ( "golang.org/x/tools/go/types" "golang.org/x/tools/go/types/typeutil" "golang.org/x/tools/oracle/serial" + "golang.org/x/tools/refactor/importgraph" ) // Implements displays the "implements" relation as it pertains to the -// selected type within a single package. +// selected type. // If the selection is a method, 'implements' displays // the corresponding methods of the types that would have been reported // by an implements query on the receiver type. @@ -28,10 +29,35 @@ func implements(q *Query) error { lconf := loader.Config{Build: q.Build} allowErrors(&lconf) - if err := importQueryPackage(q.Pos, &lconf); err != nil { + qpkg, err := importQueryPackage(q.Pos, &lconf) + if err != nil { return err } + // Set the packages to search. + if len(q.Scope) > 0 { + // Inspect all packages in the analysis scope, if specified. + if err := setPTAScope(&lconf, q.Scope); err != nil { + return err + } + } else { + // Otherwise inspect the forward and reverse + // transitive closure of the selected package. + // (In theory even this is incomplete.) + _, rev, _ := importgraph.Build(q.Build) + for path := range rev.Search(qpkg) { + lconf.ImportWithTests(path) + } + + // TODO(adonovan): for completeness, we should also + // type-check and inspect function bodies in all + // imported packages. This would be expensive, but we + // could optimize by skipping functions that do not + // contain type declarations. This would require + // changing the loader's TypeCheckFuncBodies hook to + // provide the []*ast.File. + } + // Load/parse/type-check the program. lprog, err := lconf.Load() if err != nil { @@ -72,10 +98,6 @@ func implements(q *Query) error { // Find all named types, even local types (which can have // methods via promotion) and the built-in "error". - // - // TODO(adonovan): include all packages in PTA scope too? - // i.e. don't reduceScope? - // var allNamed []types.Type for _, info := range lprog.AllPackages { for _, obj := range info.Defs { diff --git a/oracle/oracle.go b/oracle/oracle.go index 544cfa410f..afc50a973d 100644 --- a/oracle/oracle.go +++ b/oracle/oracle.go @@ -190,10 +190,11 @@ func setupPTA(prog *ssa.Program, lprog *loader.Program, ptaLog io.Writer, reflec // importQueryPackage finds the package P containing the // query position and tells conf to import it. -func importQueryPackage(pos string, conf *loader.Config) error { +// It returns the package's path. +func importQueryPackage(pos string, conf *loader.Config) (string, error) { fqpos, err := fastQueryPos(pos) if err != nil { - return err // bad query + return "", err // bad query } filename := fqpos.fset.File(fqpos.start).Name() @@ -202,10 +203,10 @@ func importQueryPackage(pos string, conf *loader.Config) error { // TODO(adonovan): ensure we report a clear error. _, importPath, err := guessImportPath(filename, conf.Build) if err != nil { - return err // can't find GOPATH dir + return "", err // can't find GOPATH dir } if importPath == "" { - return fmt.Errorf("can't guess import path from %s", filename) + return "", fmt.Errorf("can't guess import path from %s", filename) } // Check that it's possible to load the queried package. @@ -215,7 +216,7 @@ func importQueryPackage(pos string, conf *loader.Config) error { cfg2.CgoEnabled = false bp, err := cfg2.Import(importPath, "", 0) if err != nil { - return err // no files for package + return "", err // no files for package } switch pkgContainsFile(bp, filename) { @@ -227,13 +228,13 @@ func importQueryPackage(pos string, conf *loader.Config) error { case 'G': conf.Import(importPath) default: - return fmt.Errorf("package %q doesn't contain file %s", + return "", fmt.Errorf("package %q doesn't contain file %s", importPath, filename) } conf.TypeCheckFuncBodies = func(p string) bool { return p == importPath } - return nil + return importPath, nil } // pkgContainsFile reports whether file was among the packages Go diff --git a/oracle/referrers.go b/oracle/referrers.go index 7383d1f44a..12b0316268 100644 --- a/oracle/referrers.go +++ b/oracle/referrers.go @@ -24,7 +24,7 @@ func referrers(q *Query) error { lconf := loader.Config{Build: q.Build} allowErrors(&lconf) - if err := importQueryPackage(q.Pos, &lconf); err != nil { + if _, err := importQueryPackage(q.Pos, &lconf); err != nil { return err } diff --git a/oracle/testdata/src/implements-methods-json/main.golden b/oracle/testdata/src/implements-methods-json/main.golden index fa117df41f..831900a588 100644 --- a/oracle/testdata/src/implements-methods-json/main.golden +++ b/oracle/testdata/src/implements-methods-json/main.golden @@ -274,6 +274,18 @@ "name": "lib.Type", "pos": "testdata/src/lib/lib.go:3:6", "kind": "basic" + }, + { + "name": "main.I", + "pos": "testdata/src/implements-methods-json/main.go:35:6", + "kind": "interface" + } + ], + "from": [ + { + "name": "main.I", + "pos": "testdata/src/implements-methods-json/main.go:35:6", + "kind": "interface" } ], "method": { @@ -284,6 +296,16 @@ { "name": "method (lib.Type) Method(x *int) *int", "pos": "testdata/src/lib/lib.go:5:13" + }, + { + "name": "method (main.I) Method(*int) *int", + "pos": "testdata/src/implements-methods-json/main.go:36:2" + } + ], + "from_method": [ + { + "name": "method (main.I) Method(*int) *int", + "pos": "testdata/src/implements-methods-json/main.go:36:2" } ] } diff --git a/oracle/testdata/src/implements-methods/main.golden b/oracle/testdata/src/implements-methods/main.golden index bd591e8476..227d305cbd 100644 --- a/oracle/testdata/src/implements-methods/main.golden +++ b/oracle/testdata/src/implements-methods/main.golden @@ -34,4 +34,6 @@ concrete method func (sorter).Len() int -------- @implements I.Method -------- abstract method func (I).Method(*int) *int is implemented by method (lib.Type).Method + is implemented by method (main.I).Method + implements method (main.I).Method diff --git a/oracle/testdata/src/implements/main.golden b/oracle/testdata/src/implements/main.golden index ee00f3d9fe..cb2f2ac57f 100644 --- a/oracle/testdata/src/implements/main.golden +++ b/oracle/testdata/src/implements/main.golden @@ -41,4 +41,6 @@ slice type sorter -------- @implements I -------- interface type I is implemented by basic type lib.Type + is implemented by interface type main.I + implements main.I