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

All importers should populate the set of imported packages.  In Go 1.5
importer API, there will be no package map from which to compute the
transitive closure of dependencies, and SSA package creation needs
this information.

+ test.

Change-Id: I1c2823b07bf7316aa62c80e2ef2a0755cf6f5384
Reviewed-on: https://go-review.googlesource.com/8924
Reviewed-by: Robert Griesemer <gri@golang.org>
This commit is contained in:
Alan Donovan 2015-04-14 17:56:55 -04:00
parent 20186168d5
commit 77d380c9e6
3 changed files with 81 additions and 33 deletions

View File

@ -15,6 +15,7 @@ import (
"io"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"text/scanner"
@ -79,18 +80,18 @@ func FindPkg(path, srcDir string) (filename, id string) {
}
// 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.
//
// 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
// 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
// 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
defer func() {
switch r := recover().(type) {
@ -104,18 +105,18 @@ func ImportData(imports map[string]*types.Package, filename, id string, data io.
}()
var p parser
p.init(filename, id, data, imports)
p.init(filename, id, data, packages)
pkg = p.parseExport()
return
}
// 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.
// The imports map must contains all packages already imported.
// The packages map must contains 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) {
if path == "unsafe" {
return types.Unsafe, nil
}
@ -135,7 +136,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
if pkg = imports[id]; pkg != nil && pkg.Complete() {
if pkg = packages[id]; pkg != nil && pkg.Complete() {
return
}
@ -157,7 +158,7 @@ func Import(imports map[string]*types.Package, path string) (pkg *types.Package,
return
}
pkg, err = ImportData(imports, filename, id, buf)
pkg, err = ImportData(packages, filename, id, buf)
return
}
@ -178,10 +179,11 @@ type parser struct {
tok rune // current token
lit string // literal string; only valid for Ident, Int, String tokens
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.Error = func(_ *scanner.Scanner, msg string) { p.error(msg) }
p.scanner.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanChars | scanner.ScanStrings | scanner.ScanComments | scanner.SkipComments
@ -189,10 +191,10 @@ func (p *parser) init(filename, id string, src io.Reader, imports map[string]*ty
p.scanner.Filename = filename // for good error messages
p.next()
p.id = id
p.imports = imports
p.sharedPkgs = packages
if debug {
// check consistency of imports map
for _, pkg := range imports {
// check consistency of packages map
for _, pkg := range packages {
if pkg.Name() == "" {
fmt.Printf("no package name for %s\n", pkg.Path())
}
@ -338,17 +340,32 @@ func (p *parser) parseQualifiedName() (id, name string) {
// getPkg returns the package for a given id. If the package is
// 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 {
// package unsafe is not in the imports map - handle explicitly
// package unsafe is not in the packages maps - handle explicitly
if id == "unsafe" {
return types.Unsafe
}
pkg := p.imports[id]
pkg := p.localPkgs[id]
if pkg == nil && name != "" {
// first import of id from this package
pkg = p.sharedPkgs[id]
if pkg == nil {
// first import of id by this importer
pkg = types.NewPackage(id, name)
p.imports[id] = pkg
p.sharedPkgs[id] = pkg
}
if p.localPkgs == nil {
p.localPkgs = make(map[string]*types.Package)
}
p.localPkgs[id] = pkg
}
return pkg
}
@ -418,12 +435,12 @@ func (p *parser) parseMapType() types.Type {
func (p *parser) parseName(materializePkg bool) (pkg *types.Package, name string) {
switch p.tok {
case scanner.Ident:
pkg = p.imports[p.id]
pkg = p.sharedPkgs[p.id]
name = p.lit
p.next()
case '?':
// anonymous
pkg = p.imports[p.id]
pkg = p.sharedPkgs[p.id]
p.next()
case '@':
// exported name prefixed with package path
@ -954,8 +971,25 @@ func (p *parser) parseExport() *types.Package {
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
pkg.MarkComplete()
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() }

View File

@ -5,6 +5,7 @@
package gcimporter
import (
"fmt"
"go/build"
"io/ioutil"
"os"
@ -58,15 +59,15 @@ func compile(t *testing.T, dirname, filename string) string {
// as if all tested packages were imported into a single 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()
_, err := Import(imports, path)
pkg, err := Import(imports, path)
if err != nil {
t.Errorf("testPath(%s): %s", path, err)
return false
return nil
}
t.Logf("testPath(%s): %v", path, time.Since(t0))
return true
return pkg
}
const maxTime = 30 * time.Second
@ -88,7 +89,7 @@ func testDir(t *testing.T, dir string, endTime time.Time) (nimports int) {
for _, ext := range pkgExts {
if strings.HasSuffix(f.Name(), ext) {
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++
}
}
@ -118,8 +119,17 @@ func TestImport(t *testing.T) {
}
nimports := 0
if testPath(t, "./testdata/exports") {
if pkg := testPath(t, "./testdata/exports"); pkg != nil {
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
t.Logf("tested %d imports", nimports)

View File

@ -45,8 +45,12 @@ func (pkg *Package) Complete() bool { return pkg.complete }
// MarkComplete marks a package as complete.
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.
//
// 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 }
// SetImports sets the list of explicitly imported packages to list.