mirror of https://github.com/golang/go.git
internal/facts: share go/analysis/internal/facts with gopls
This change moves the facts package so that it can be reused by gopls. It remains a tools-internal API. A forthcoming reimplementation of gopls's analysis driver will make use of the new packages and the features mentioned below. Also: - change parameter of read() callback from 'path string' to *Package. - use NewDecoder().Decode() to amortize import-map computation across calls. Change-Id: Id10cd02c0c241353524d568d5299d81457f571f8 Reviewed-on: https://go-review.googlesource.com/c/tools/+/444796 Reviewed-by: Tim King <taking@google.com> Reviewed-by: Robert Findley <rfindley@google.com> Run-TryBot: Alan Donovan <adonovan@google.com>
This commit is contained in:
parent
2e0ca3aded
commit
d6511e5e9f
|
|
@ -50,7 +50,7 @@ import (
|
|||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/internal/analysisflags"
|
||||
"golang.org/x/tools/go/analysis/internal/facts"
|
||||
"golang.org/x/tools/internal/facts"
|
||||
"golang.org/x/tools/internal/typeparams"
|
||||
)
|
||||
|
||||
|
|
@ -287,13 +287,13 @@ func run(fset *token.FileSet, cfg *Config, analyzers []*analysis.Analyzer) ([]re
|
|||
analyzers = filtered
|
||||
|
||||
// Read facts from imported packages.
|
||||
read := func(path string) ([]byte, error) {
|
||||
if vetx, ok := cfg.PackageVetx[path]; ok {
|
||||
read := func(imp *types.Package) ([]byte, error) {
|
||||
if vetx, ok := cfg.PackageVetx[imp.Path()]; ok {
|
||||
return ioutil.ReadFile(vetx)
|
||||
}
|
||||
return nil, nil // no .vetx file, no facts
|
||||
}
|
||||
facts, err := facts.Decode(pkg, read)
|
||||
facts, err := facts.NewDecoder(pkg).Decode(read)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -152,6 +152,23 @@ type gobFact struct {
|
|||
Fact analysis.Fact // type and value of user-defined Fact
|
||||
}
|
||||
|
||||
// A Decoder decodes the facts from the direct imports of the package
|
||||
// provided to NewEncoder. A single decoder may be used to decode
|
||||
// multiple fact sets (e.g. each for a different set of fact types)
|
||||
// for the same package. Each call to Decode returns an independent
|
||||
// fact set.
|
||||
type Decoder struct {
|
||||
pkg *types.Package
|
||||
packages map[string]*types.Package
|
||||
}
|
||||
|
||||
// NewDecoder returns a fact decoder for the specified package.
|
||||
func NewDecoder(pkg *types.Package) *Decoder {
|
||||
// Compute the import map for this package.
|
||||
// See the package doc comment.
|
||||
return &Decoder{pkg, importMap(pkg.Imports())}
|
||||
}
|
||||
|
||||
// Decode decodes all the facts relevant to the analysis of package pkg.
|
||||
// The read function reads serialized fact data from an external source
|
||||
// for one of of pkg's direct imports. The empty file is a valid
|
||||
|
|
@ -159,28 +176,24 @@ type gobFact struct {
|
|||
//
|
||||
// It is the caller's responsibility to call gob.Register on all
|
||||
// necessary fact types.
|
||||
func Decode(pkg *types.Package, read func(packagePath string) ([]byte, error)) (*Set, error) {
|
||||
// Compute the import map for this package.
|
||||
// See the package doc comment.
|
||||
packages := importMap(pkg.Imports())
|
||||
|
||||
func (d *Decoder) Decode(read func(*types.Package) ([]byte, error)) (*Set, error) {
|
||||
// Read facts from imported packages.
|
||||
// Facts may describe indirectly imported packages, or their objects.
|
||||
m := make(map[key]analysis.Fact) // one big bucket
|
||||
for _, imp := range pkg.Imports() {
|
||||
for _, imp := range d.pkg.Imports() {
|
||||
logf := func(format string, args ...interface{}) {
|
||||
if debug {
|
||||
prefix := fmt.Sprintf("in %s, importing %s: ",
|
||||
pkg.Path(), imp.Path())
|
||||
d.pkg.Path(), imp.Path())
|
||||
log.Print(prefix, fmt.Sprintf(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
// Read the gob-encoded facts.
|
||||
data, err := read(imp.Path())
|
||||
data, err := read(imp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("in %s, can't import facts for package %q: %v",
|
||||
pkg.Path(), imp.Path(), err)
|
||||
d.pkg.Path(), imp.Path(), err)
|
||||
}
|
||||
if len(data) == 0 {
|
||||
continue // no facts
|
||||
|
|
@ -195,7 +208,7 @@ func Decode(pkg *types.Package, read func(packagePath string) ([]byte, error)) (
|
|||
|
||||
// Parse each one into a key and a Fact.
|
||||
for _, f := range gobFacts {
|
||||
factPkg := packages[f.PkgPath]
|
||||
factPkg := d.packages[f.PkgPath]
|
||||
if factPkg == nil {
|
||||
// Fact relates to a dependency that was
|
||||
// unused in this translation unit. Skip.
|
||||
|
|
@ -222,7 +235,7 @@ func Decode(pkg *types.Package, read func(packagePath string) ([]byte, error)) (
|
|||
}
|
||||
}
|
||||
|
||||
return &Set{pkg: pkg, m: m}, nil
|
||||
return &Set{pkg: d.pkg, m: m}, nil
|
||||
}
|
||||
|
||||
// Encode encodes a set of facts to a memory buffer.
|
||||
|
|
@ -14,8 +14,8 @@ import (
|
|||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/analysis/analysistest"
|
||||
"golang.org/x/tools/go/analysis/internal/facts"
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/tools/internal/facts"
|
||||
"golang.org/x/tools/internal/testenv"
|
||||
"golang.org/x/tools/internal/typeparams"
|
||||
)
|
||||
|
|
@ -216,7 +216,7 @@ type pkgLookups struct {
|
|||
// are passed during analysis. It operates on a group of Go file contents. Then
|
||||
// for each <package, []lookup> in tests it does the following:
|
||||
// 1. loads and type checks the package,
|
||||
// 2. calls facts.Decode to loads the facts exported by its imports,
|
||||
// 2. calls (*facts.Decoder).Decode to load the facts exported by its imports,
|
||||
// 3. exports a myFact Fact for all of package level objects,
|
||||
// 4. For each lookup for the current package:
|
||||
// 4.a) lookup the types.Object for an Go source expression in the curent package
|
||||
|
|
@ -239,7 +239,7 @@ func testEncodeDecode(t *testing.T, files map[string]string, tests []pkgLookups)
|
|||
// factmap represents the passing of encoded facts from one
|
||||
// package to another. In practice one would use the file system.
|
||||
factmap := make(map[string][]byte)
|
||||
read := func(path string) ([]byte, error) { return factmap[path], nil }
|
||||
read := func(imp *types.Package) ([]byte, error) { return factmap[imp.Path()], nil }
|
||||
|
||||
// Analyze packages in order, look up various objects accessible within
|
||||
// each package, and see if they have a fact. The "analysis" exports a
|
||||
|
|
@ -255,7 +255,7 @@ func testEncodeDecode(t *testing.T, files map[string]string, tests []pkgLookups)
|
|||
}
|
||||
|
||||
// decode
|
||||
facts, err := facts.Decode(pkg, read)
|
||||
facts, err := facts.NewDecoder(pkg).Decode(read)
|
||||
if err != nil {
|
||||
t.Fatalf("Decode failed: %v", err)
|
||||
}
|
||||
|
|
@ -357,7 +357,7 @@ func TestFactFilter(t *testing.T) {
|
|||
}
|
||||
|
||||
obj := pkg.Scope().Lookup("A")
|
||||
s, err := facts.Decode(pkg, func(string) ([]byte, error) { return nil, nil })
|
||||
s, err := facts.NewDecoder(pkg).Decode(func(*types.Package) ([]byte, error) { return nil, nil })
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -20,6 +20,9 @@ import (
|
|||
//
|
||||
// Packages in the map that are only indirectly imported may be
|
||||
// incomplete (!pkg.Complete()).
|
||||
//
|
||||
// TODO(adonovan): opt: compute this information more efficiently
|
||||
// by obtaining it from the internals of the gcexportdata decoder.
|
||||
func importMap(imports []*types.Package) map[string]*types.Package {
|
||||
objects := make(map[types.Object]bool)
|
||||
packages := make(map[string]*types.Package)
|
||||
Loading…
Reference in New Issue