Commit 8d7d02f1 authored by Alan Donovan's avatar Alan Donovan

go/internal/gcimporter: populate (*types.Package).Imports

This is an upstream change to the tools repo:
https://go-review.googlesource.com/#/c/8924/

Change-Id: I01fb1b2e9ec834354994c544f65c8ec8267c9626
Reviewed-on: https://go-review.googlesource.com/8954
Run-TryBot: Robert Griesemer <gri@golang.org>
Reviewed-by: 's avatarRobert Griesemer <gri@golang.org>
parent af161c57
...@@ -14,6 +14,7 @@ import ( ...@@ -14,6 +14,7 @@ import (
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
"sort"
"strconv" "strconv"
"strings" "strings"
"text/scanner" "text/scanner"
...@@ -74,18 +75,18 @@ func FindPkg(path, srcDir string) (filename, id string) { ...@@ -74,18 +75,18 @@ func FindPkg(path, srcDir string) (filename, id string) {
} }
// ImportData imports a package by reading the gc-generated export data, // ImportData imports a package by reading the gc-generated export data,
// adds the corresponding package object to the imports map indexed by id, // adds the corresponding package object to the packages map indexed by id,
// and returns the object. // and returns the object.
// //
// The imports map must contains all packages already imported. The data // The packages map must contains all packages already imported. The data
// reader position must be the beginning of the export data section. The // reader position must be the beginning of the export data section. The
// filename is only used in error messages. // filename is only used in error messages.
// //
// If imports[id] contains the completely imported package, that package // If packages[id] contains the completely imported package, that package
// can be used directly, and there is no need to call this function (but // can be used directly, and there is no need to call this function (but
// there is also no harm but for extra time used). // there is also no harm but for extra time used).
// //
func ImportData(imports map[string]*types.Package, filename, id string, data io.Reader) (pkg *types.Package, err error) { func ImportData(packages map[string]*types.Package, filename, id string, data io.Reader) (pkg *types.Package, err error) {
// support for parser error handling // support for parser error handling
defer func() { defer func() {
switch r := recover().(type) { switch r := recover().(type) {
...@@ -99,18 +100,18 @@ func ImportData(imports map[string]*types.Package, filename, id string, data io. ...@@ -99,18 +100,18 @@ func ImportData(imports map[string]*types.Package, filename, id string, data io.
}() }()
var p parser var p parser
p.init(filename, id, data, imports) p.init(filename, id, data, packages)
pkg = p.parseExport() pkg = p.parseExport()
return return
} }
// Import imports a gc-generated package given its import path, adds the // Import imports a gc-generated package given its import path, adds the
// corresponding package object to the imports map, and returns the object. // corresponding package object to the packages map, and returns the object.
// Local import paths are interpreted relative to the current working directory. // Local import paths are interpreted relative to the current working directory.
// The imports map must contains all packages already imported. // The packages map must contain all packages already imported.
// //
func Import(imports map[string]*types.Package, path string) (pkg *types.Package, err error) { func Import(packages map[string]*types.Package, path string) (pkg *types.Package, err error) {
// package "unsafe" is handled by the type checker // package "unsafe" is handled by the type checker
if path == "unsafe" { if path == "unsafe" {
panic(`gcimporter.Import called for package "unsafe"`) panic(`gcimporter.Import called for package "unsafe"`)
...@@ -131,7 +132,7 @@ func Import(imports map[string]*types.Package, path string) (pkg *types.Package, ...@@ -131,7 +132,7 @@ func Import(imports map[string]*types.Package, path string) (pkg *types.Package,
} }
// no need to re-import if the package was imported completely before // no need to re-import if the package was imported completely before
if pkg = imports[id]; pkg != nil && pkg.Complete() { if pkg = packages[id]; pkg != nil && pkg.Complete() {
return return
} }
...@@ -153,7 +154,7 @@ func Import(imports map[string]*types.Package, path string) (pkg *types.Package, ...@@ -153,7 +154,7 @@ func Import(imports map[string]*types.Package, path string) (pkg *types.Package,
return return
} }
pkg, err = ImportData(imports, filename, id, buf) pkg, err = ImportData(packages, filename, id, buf)
return return
} }
...@@ -170,14 +171,15 @@ func Import(imports map[string]*types.Package, path string) (pkg *types.Package, ...@@ -170,14 +171,15 @@ func Import(imports map[string]*types.Package, path string) (pkg *types.Package,
// parser parses the exports inside a gc compiler-produced // parser parses the exports inside a gc compiler-produced
// object/archive file and populates its scope with the results. // object/archive file and populates its scope with the results.
type parser struct { type parser struct {
scanner scanner.Scanner scanner scanner.Scanner
tok rune // current token tok rune // current token
lit string // literal string; only valid for Ident, Int, String tokens lit string // literal string; only valid for Ident, Int, String tokens
id string // package id of imported package id string // package id of imported package
imports map[string]*types.Package // package id -> package object sharedPkgs map[string]*types.Package // package id -> package object (across importer)
localPkgs map[string]*types.Package // package id -> package object (just this package)
} }
func (p *parser) init(filename, id string, src io.Reader, imports map[string]*types.Package) { func (p *parser) init(filename, id string, src io.Reader, packages map[string]*types.Package) {
p.scanner.Init(src) p.scanner.Init(src)
p.scanner.Error = func(_ *scanner.Scanner, msg string) { p.error(msg) } p.scanner.Error = func(_ *scanner.Scanner, msg string) { p.error(msg) }
p.scanner.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanChars | scanner.ScanStrings | scanner.ScanComments | scanner.SkipComments p.scanner.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanChars | scanner.ScanStrings | scanner.ScanComments | scanner.SkipComments
...@@ -185,10 +187,10 @@ func (p *parser) init(filename, id string, src io.Reader, imports map[string]*ty ...@@ -185,10 +187,10 @@ func (p *parser) init(filename, id string, src io.Reader, imports map[string]*ty
p.scanner.Filename = filename // for good error messages p.scanner.Filename = filename // for good error messages
p.next() p.next()
p.id = id p.id = id
p.imports = imports p.sharedPkgs = packages
if debug { if debug {
// check consistency of imports map // check consistency of packages map
for _, pkg := range imports { for _, pkg := range packages {
if pkg.Name() == "" { if pkg.Name() == "" {
fmt.Printf("no package name for %s\n", pkg.Path()) fmt.Printf("no package name for %s\n", pkg.Path())
} }
...@@ -334,17 +336,32 @@ func (p *parser) parseQualifiedName() (id, name string) { ...@@ -334,17 +336,32 @@ func (p *parser) parseQualifiedName() (id, name string) {
// getPkg returns the package for a given id. If the package is // getPkg returns the package for a given id. If the package is
// not found but we have a package name, create the package and // not found but we have a package name, create the package and
// add it to the p.imports map. // add it to the p.localPkgs and p.sharedPkgs maps.
//
// id identifies a package, usually by a canonical package path like
// "encoding/json" but possibly by a non-canonical import path like
// "./json".
// //
func (p *parser) getPkg(id, name string) *types.Package { func (p *parser) getPkg(id, name string) *types.Package {
// package unsafe is not in the imports map - handle explicitly // package unsafe is not in the packages maps - handle explicitly
if id == "unsafe" { if id == "unsafe" {
return types.Unsafe return types.Unsafe
} }
pkg := p.imports[id]
pkg := p.localPkgs[id]
if pkg == nil && name != "" { if pkg == nil && name != "" {
pkg = types.NewPackage(id, name) // first import of id from this package
p.imports[id] = pkg pkg = p.sharedPkgs[id]
if pkg == nil {
// first import of id by this importer
pkg = types.NewPackage(id, name)
p.sharedPkgs[id] = pkg
}
if p.localPkgs == nil {
p.localPkgs = make(map[string]*types.Package)
}
p.localPkgs[id] = pkg
} }
return pkg return pkg
} }
...@@ -405,21 +422,21 @@ func (p *parser) parseMapType() types.Type { ...@@ -405,21 +422,21 @@ func (p *parser) parseMapType() types.Type {
// //
// If materializePkg is set, the returned package is guaranteed to be set. // If materializePkg is set, the returned package is guaranteed to be set.
// For fully qualified names, the returned package may be a fake package // For fully qualified names, the returned package may be a fake package
// (without name, scope, and not in the p.imports map), created for the // (without name, scope, and not in the p.sharedPkgs map), created for the
// sole purpose of providing a package path. Fake packages are created // sole purpose of providing a package path. Fake packages are created
// when the package id is not found in the p.imports map; in that case // when the package id is not found in the p.sharedPkgs map; in that case
// we cannot create a real package because we don't have a package name. // we cannot create a real package because we don't have a package name.
// For non-qualified names, the returned package is the imported package. // For non-qualified names, the returned package is the imported package.
// //
func (p *parser) parseName(materializePkg bool) (pkg *types.Package, name string) { func (p *parser) parseName(materializePkg bool) (pkg *types.Package, name string) {
switch p.tok { switch p.tok {
case scanner.Ident: case scanner.Ident:
pkg = p.imports[p.id] pkg = p.sharedPkgs[p.id]
name = p.lit name = p.lit
p.next() p.next()
case '?': case '?':
// anonymous // anonymous
pkg = p.imports[p.id] pkg = p.sharedPkgs[p.id]
p.next() p.next()
case '@': case '@':
// exported name prefixed with package path // exported name prefixed with package path
...@@ -950,8 +967,25 @@ func (p *parser) parseExport() *types.Package { ...@@ -950,8 +967,25 @@ func (p *parser) parseExport() *types.Package {
p.errorf("expected no scanner errors, got %d", n) p.errorf("expected no scanner errors, got %d", n)
} }
// Record all referenced packages as imports.
var imports []*types.Package
for id, pkg2 := range p.localPkgs {
if id == p.id {
continue // avoid self-edge
}
imports = append(imports, pkg2)
}
sort.Sort(byPath(imports))
pkg.SetImports(imports)
// package was imported completely and without errors // package was imported completely and without errors
pkg.MarkComplete() pkg.MarkComplete()
return pkg return pkg
} }
type byPath []*types.Package
func (a byPath) Len() int { return len(a) }
func (a byPath) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a byPath) Less(i, j int) bool { return a[i].Path() < a[j].Path() }
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
package gcimporter package gcimporter
import ( import (
"fmt"
"go/build" "go/build"
"io/ioutil" "io/ioutil"
"os" "os"
...@@ -58,15 +59,15 @@ func compile(t *testing.T, dirname, filename string) string { ...@@ -58,15 +59,15 @@ func compile(t *testing.T, dirname, filename string) string {
// as if all tested packages were imported into a single package. // as if all tested packages were imported into a single package.
var imports = make(map[string]*types.Package) var imports = make(map[string]*types.Package)
func testPath(t *testing.T, path string) bool { func testPath(t *testing.T, path string) *types.Package {
t0 := time.Now() t0 := time.Now()
_, err := Import(imports, path) pkg, err := Import(imports, path)
if err != nil { if err != nil {
t.Errorf("testPath(%s): %s", path, err) t.Errorf("testPath(%s): %s", path, err)
return false return nil
} }
t.Logf("testPath(%s): %v", path, time.Since(t0)) t.Logf("testPath(%s): %v", path, time.Since(t0))
return true return pkg
} }
const maxTime = 30 * time.Second const maxTime = 30 * time.Second
...@@ -88,7 +89,7 @@ func testDir(t *testing.T, dir string, endTime time.Time) (nimports int) { ...@@ -88,7 +89,7 @@ func testDir(t *testing.T, dir string, endTime time.Time) (nimports int) {
for _, ext := range pkgExts { for _, ext := range pkgExts {
if strings.HasSuffix(f.Name(), ext) { if strings.HasSuffix(f.Name(), ext) {
name := f.Name()[0 : len(f.Name())-len(ext)] // remove extension name := f.Name()[0 : len(f.Name())-len(ext)] // remove extension
if testPath(t, filepath.Join(dir, name)) { if testPath(t, filepath.Join(dir, name)) != nil {
nimports++ nimports++
} }
} }
...@@ -118,8 +119,17 @@ func TestImport(t *testing.T) { ...@@ -118,8 +119,17 @@ func TestImport(t *testing.T) {
} }
nimports := 0 nimports := 0
if testPath(t, "./testdata/exports") { if pkg := testPath(t, "./testdata/exports"); pkg != nil {
nimports++ nimports++
// The package's Imports should include all the types
// referenced by the exportdata, which may be more than
// the import statements in the package's source, but
// fewer than the transitive closure of dependencies.
want := `[package ast ("go/ast") package token ("go/token") package runtime ("runtime")]`
got := fmt.Sprint(pkg.Imports())
if got != want {
t.Errorf(`Package("exports").Imports() = %s, want %s`, got, want)
}
} }
nimports += testDir(t, "", time.Now().Add(maxTime)) // installed packages nimports += testDir(t, "", time.Now().Add(maxTime)) // installed packages
t.Logf("tested %d imports", nimports) t.Logf("tested %d imports", nimports)
......
...@@ -45,8 +45,12 @@ func (pkg *Package) Complete() bool { return pkg.complete } ...@@ -45,8 +45,12 @@ func (pkg *Package) Complete() bool { return pkg.complete }
// MarkComplete marks a package as complete. // MarkComplete marks a package as complete.
func (pkg *Package) MarkComplete() { pkg.complete = true } func (pkg *Package) MarkComplete() { pkg.complete = true }
// Imports returns the list of packages explicitly imported by // Imports returns the list of packages directly imported by
// pkg; the list is in source order. Package unsafe is excluded. // pkg; the list is in source order. Package unsafe is excluded.
//
// If pkg was loaded from export data, Imports includes packages that
// provide package-level objects referenced by pkg. This may be more or
// less than the set of packages directly imported by pkg's source code.
func (pkg *Package) Imports() []*Package { return pkg.imports } func (pkg *Package) Imports() []*Package { return pkg.imports }
// SetImports sets the list of explicitly imported packages to list. // SetImports sets the list of explicitly imported packages to list.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment