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

View File

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

View File

@ -4,11 +4,7 @@
package loader package loader
// This file defines various utility functions exposed by the package
// and used by it.
import ( import (
"fmt"
"go/ast" "go/ast"
"go/build" "go/build"
"go/parser" "go/parser"
@ -104,25 +100,6 @@ func unreachable() {
panic("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) // TODO(adonovan): make this a method: func (*token.File) Contains(token.Pos)
func tokenFileContainsPos(f *token.File, pos token.Pos) bool { func tokenFileContainsPos(f *token.File, pos token.Pos) bool {
p := int(pos) p := int(pos)

View File

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

View File

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

View File

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

View File

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

View File

@ -50,7 +50,7 @@ func main() {
} }
// Create single-file main package. // Create single-file main package.
conf.CreateFromFiles(file) conf.CreateFromFiles("main", file)
// Load the main package and its dependencies. // Load the main package and its dependencies.
iprog, err := conf.Load() 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} conf := loader.Config{SourceImports: true}
// TODO(adonovan): add the following packages' tests, which pass: // TODO(adonovan): add the following packages' tests, which pass:
// "flag", "unicode", "unicode/utf8", "testing", "log", "path". // "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) t.Errorf("CreateFromFilenames(%s) failed: %s", inputs, err)
return false return false
} }
@ -319,7 +319,7 @@ func TestTestmainPackage(t *testing.T) {
// CreateTestMainPackage should return nil if there were no tests. // CreateTestMainPackage should return nil if there were no tests.
func TestNullTestmainPackage(t *testing.T) { func TestNullTestmainPackage(t *testing.T) {
var conf loader.Config 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) t.Fatalf("ParseFile failed: %s", err)
} }
iprog, err := conf.Load() iprog, err := conf.Load()

View File

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

View File

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

View File

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