go.tools/go/loader: permit Create* methods to specify the ad-hoc package's path

CL 49530047 made the (over-)simplifying assumption that the
Path of an ad-hoc (Created) package should default to its
Name, i.e. package declaration.

With this change, the Name is still always computed from the
package declaration (by go/types) but the Path may be
specified by the loader.Config.  If "", the value of the Name
is used, which is not globally unique.

R=gri, axwalk
CC=golang-codereviews
https://golang.org/cl/55180043
This commit is contained in:
Alan Donovan 2014-01-22 09:59:19 -05:00
parent 074bd4ac9c
commit 0dcaae1610
12 changed files with 52 additions and 68 deletions

View File

@ -25,16 +25,17 @@
// // See FromArgsUsage for help.
// rest, err := conf.FromArgs(os.Args[1:])
//
// // Parse the specified files and create an ad-hoc package.
// // Parse the specified files and create an ad-hoc package with path "foo".
// // All files must have the same 'package' declaration.
// err := conf.CreateFromFilenames("foo.go", "bar.go")
// err := conf.CreateFromFilenames("foo", "foo.go", "bar.go")
//
// // Create an ad-hoc package from the specified already-parsed files.
// // Create an ad-hoc package with path "foo" from
// // the specified already-parsed files.
// // All ASTs must have the same 'package' declaration.
// err := conf.CreateFromFiles(parsedFiles)
// err := conf.CreateFromFiles("foo", parsedFiles)
//
// // Add "runtime" to the set of packages to be loaded.
// err := conf.Import("runtime")
// conf.Import("runtime")
//
// // Adds "fmt" and "fmt_test" to the set of packages
// // to be loaded. "fmt" will include *_test.go files.
@ -92,6 +93,8 @@ package loader
// (*Config).CreateFromFiles has a nasty precondition.
// - Ideally some of this logic would move under the umbrella of
// go/types; see bug 7114.
// - s/path/importPath/g to avoid ambiguity with other meanings of
// "path": a file name, a colon-separated directory list.
import (
"errors"
@ -157,13 +160,15 @@ type Config struct {
Build *build.Context
// CreatePkgs specifies a list of non-importable initial
// packages to create. Each element is a list of parsed files
// to be type-checked into a new package whose name is taken
// from ast.File.Package.
// packages to create. Each element specifies a list of
// parsed files to be type-checked into a new package, and a
// path for that package. If the path is "", the package's
// name will be used instead. The path needn't be globally
// unique.
//
// The resulting packages will appear in the corresponding
// elements of the Program.Created slice.
CreatePkgs [][]*ast.File
CreatePkgs []CreatePkg
// ImportPkgs specifies a set of initial packages to load from
// source. The map keys are package import paths, used to
@ -176,6 +181,11 @@ type Config struct {
ImportPkgs map[string]bool
}
type CreatePkg struct {
Path string
Files []*ast.File
}
// A Program is a Go program loaded from source or binary
// as specified by a Config.
type Program struct {
@ -224,9 +234,8 @@ Each argument may take one of two forms:
1. A comma-separated list of *.go source files.
All of the specified files are loaded, parsed and type-checked
as a single package. The name of the package is taken from the
files' package declarations, which must all be equal. All the
files must belong to the same directory.
as a single package.
All the files must belong to the same directory.
2. An import path.
@ -268,8 +277,8 @@ func (conf *Config) FromArgs(args []string) (rest []string, err error) {
if strings.HasSuffix(arg, ".go") {
// Assume arg is a comma-separated list of *.go files
// comprising a single package.
err = conf.CreateFromFilenames(strings.Split(arg, ",")...)
// denoting a single ad-hoc package.
err = conf.CreateFromFilenames("", strings.Split(arg, ",")...)
} else {
// Assume arg is a directory name denoting a
// package, perhaps plus an external test
@ -291,27 +300,27 @@ func (conf *Config) FromArgs(args []string) (rest []string, err error) {
// specified *.go files and adds a package entry for them to
// conf.CreatePkgs.
//
func (conf *Config) CreateFromFilenames(filenames ...string) error {
func (conf *Config) CreateFromFilenames(path string, filenames ...string) error {
files, err := parseFiles(conf.fset(), ".", filenames...)
if err != nil {
return err
}
conf.CreateFromFiles(files...)
conf.CreateFromFiles(path, files...)
return nil
}
// CreateFromFiles is a convenience function that adds a CreatePkgs
// entry for the specified parsed files.
// entry to create package of the specified path and parsed files.
//
// Precondition: conf.Fset is non-nil and was the fileset used to parse
// the files. (e.g. the files came from conf.ParseFile().)
//
func (conf *Config) CreateFromFiles(files ...*ast.File) {
func (conf *Config) CreateFromFiles(path string, files ...*ast.File) {
if conf.Fset == nil {
panic("nil Fset")
}
conf.CreatePkgs = append(conf.CreatePkgs, files)
conf.CreatePkgs = append(conf.CreatePkgs, CreatePkg{path, files})
}
// ImportWithTests is a convenience function that adds path to
@ -320,7 +329,7 @@ func (conf *Config) CreateFromFiles(files ...*ast.File) {
// its directory that contain a "package x" (not "package x_test")
// declaration.
//
// In addition, if any *_test.go files contain a "package <path>_test"
// In addition, if any *_test.go files contain a "package x_test"
// declaration, an additional package comprising just those files will
// be added to CreatePkgs.
//
@ -344,7 +353,7 @@ func (conf *Config) ImportWithTests(path string) error {
return err
}
if len(xtestFiles) > 0 {
conf.CreateFromFiles(xtestFiles...)
conf.CreateFromFiles(path+"_test", xtestFiles...)
}
// Mark the non-xtest package for augmentation with
@ -450,14 +459,12 @@ func (conf *Config) Load() (*Program, error) {
prog.Imported[path] = info
}
for _, files := range conf.CreatePkgs {
pkgname, err := packageName(files, conf.Fset)
if err != nil {
return nil, err
for _, create := range conf.CreatePkgs {
path := create.Path
if create.Path == "" && len(create.Files) > 0 {
path = create.Files[0].Name.Name
}
// TODO(adonovan): pkgnames are not unique, but the
// typechecker assumes they are in its Id() logic.
prog.Created = append(prog.Created, imp.createPackage(pkgname, files...))
prog.Created = append(prog.Created, imp.createPackage(path, create.Files...))
}
if len(prog.Imported)+len(prog.Created) == 0 {

View File

@ -93,7 +93,7 @@ func TestEnclosingFunction(t *testing.T) {
continue
}
conf.CreateFromFiles(f)
conf.CreateFromFiles("main", f)
iprog, err := conf.Load()
if err != nil {

View File

@ -4,11 +4,7 @@
package loader
// This file defines various utility functions exposed by the package
// and used by it.
import (
"fmt"
"go/ast"
"go/build"
"go/parser"
@ -104,25 +100,6 @@ func unreachable() {
panic("unreachable")
}
func packageName(files []*ast.File, fset *token.FileSet) (string, error) {
if len(files) == 0 {
return "", fmt.Errorf("no files in package")
}
// Take the package name from the 'package decl' in each file,
// all of which must match.
pkgname := files[0].Name.Name
for _, file := range files[1:] {
if pn := file.Name.Name; pn != pkgname {
err := fmt.Errorf("can't load package: found packages %s (%s) and %s (%s)",
pkgname, filename(files[0], fset),
pn, filename(file, fset))
return "", err
}
// TODO(adonovan): check dirnames are equal, like 'go build' does.
}
return pkgname, nil
}
// TODO(adonovan): make this a method: func (*token.File) Contains(token.Pos)
func tokenFileContainsPos(f *token.File, pos token.Pos) bool {
p := int(pos)

View File

@ -49,7 +49,7 @@ func main() {
}
// Create single-file main package and import its dependencies.
conf.CreateFromFiles(file)
conf.CreateFromFiles("main", file)
iprog, err := conf.Load()
if err != nil {

View File

@ -161,7 +161,7 @@ func doOneInput(input, filename string) bool {
}
// Create single-file main package and import its dependencies.
conf.CreateFromFiles(f)
conf.CreateFromFiles("main", f)
iprog, err := conf.Load()
if err != nil {
fmt.Println(err)

View File

@ -11,7 +11,7 @@ import "testing"
func log(f func(*testing.T)) {
// The PTS of f is the set of called tests. TestingQuux is not present.
print(f) // @pointsto a.Test | a.TestFoo
print(f) // @pointsto main.Test | main.TestFoo
}
func Test(t *testing.T) {
@ -36,7 +36,7 @@ func ExampleBar() {
}
// Excludes TestingQuux.
// @calls testing.tRunner -> a.Test
// @calls testing.tRunner -> a.TestFoo
// @calls testing.runExample -> a.ExampleBar
// @calls (*testing.B).runN -> a.BenchmarkFoo
// @calls testing.tRunner -> main.Test
// @calls testing.tRunner -> main.TestFoo
// @calls testing.runExample -> main.ExampleBar
// @calls (*testing.B).runN -> main.BenchmarkFoo

View File

@ -47,7 +47,7 @@ func main() {
t.Error(err)
return
}
conf.CreateFromFiles(f)
conf.CreateFromFiles("main", f)
iprog, err := conf.Load()
if err != nil {
@ -212,7 +212,7 @@ func TestTypesWithMethodSets(t *testing.T) {
t.Errorf("test %d: %s", i, err)
continue
}
conf.CreateFromFiles(f)
conf.CreateFromFiles("p", f)
iprog, err := conf.Load()
if err != nil {

View File

@ -50,7 +50,7 @@ func main() {
}
// Create single-file main package.
conf.CreateFromFiles(file)
conf.CreateFromFiles("main", file)
// Load the main package and its dependencies.
iprog, err := conf.Load()

View File

@ -171,7 +171,7 @@ func run(t *testing.T, dir, input string, success successPredicate) bool {
conf := loader.Config{SourceImports: true}
// TODO(adonovan): add the following packages' tests, which pass:
// "flag", "unicode", "unicode/utf8", "testing", "log", "path".
if err := conf.CreateFromFilenames(inputs...); err != nil {
if err := conf.CreateFromFilenames("", inputs...); err != nil {
t.Errorf("CreateFromFilenames(%s) failed: %s", inputs, err)
return false
}
@ -319,7 +319,7 @@ func TestTestmainPackage(t *testing.T) {
// CreateTestMainPackage should return nil if there were no tests.
func TestNullTestmainPackage(t *testing.T) {
var conf loader.Config
if err := conf.CreateFromFilenames("testdata/b_test.go"); err != nil {
if err := conf.CreateFromFilenames("", "testdata/b_test.go"); err != nil {
t.Fatalf("ParseFile failed: %s", err)
}
iprog, err := conf.Load()

View File

@ -30,7 +30,7 @@ func TestObjValueLookup(t *testing.T) {
t.Error(err)
return
}
conf.CreateFromFiles(f)
conf.CreateFromFiles("main", f)
// Maps each var Ident (represented "name:linenum") to the
// kind of ssa.Value we expect (represented "Constant", "&Alloc").
@ -194,7 +194,7 @@ func TestValueForExpr(t *testing.T) {
t.Error(err)
return
}
conf.CreateFromFiles(f)
conf.CreateFromFiles("main", f)
iprog, err := conf.Load()
if err != nil {

View File

@ -22,7 +22,7 @@ func TestSwitches(t *testing.T) {
return
}
conf.CreateFromFiles(f)
conf.CreateFromFiles("main", f)
iprog, err := conf.Load()
if err != nil {
t.Error(err)

View File

@ -261,7 +261,7 @@ func TestMultipleQueries(t *testing.T) {
buildContext.GOPATH = "testdata"
conf := loader.Config{Build: &buildContext}
filename := "testdata/src/main/multi.go"
conf.CreateFromFilenames(filename)
conf.CreateFromFilenames("", filename)
iprog, err := conf.Load()
if err != nil {
t.Fatalf("Load failed: %s", err)