Commit 159797a5 authored by Alan Donovan's avatar Alan Donovan

go/importer: add ForCompiler, which accepts a token.FileSet

The importer.For function logically requires a FileSet, but did not
when it was first created because export data did not then record
position information. This change adds a new function, ForCompiler,
that has an additional FileSet parameter, and deprecates the For
function.

Before this change, cmd/vet would report confusing spurious
positions for token.Pos values produced by the importer.
The bug is essentially unfixable in cmd/vet.

This CL includes a test that the FileSet is correctly populated.

The changes to cmd/vendor will be applied upstream in a follow-up.

Fixes #28995

Change-Id: I9271bcb1f28e96845c913e15f0304bac93d4d4c4
Reviewed-on: https://go-review.googlesource.com/c/152258
Run-TryBot: Alan Donovan <adonovan@google.com>
Reviewed-by: default avatarDaniel Martí <mvdan@mvdan.cc>
Reviewed-by: default avatarRobert Griesemer <gri@golang.org>
parent 449e2f0b
...@@ -181,6 +181,11 @@ func readConfig(filename string) (*Config, error) { ...@@ -181,6 +181,11 @@ func readConfig(filename string) (*Config, error) {
return cfg, nil return cfg, nil
} }
var importerForCompiler = func(_ *token.FileSet, compiler string, lookup importer.Lookup) types.Importer {
// broken legacy implementation (github.com/golang/go/issues/28995)
return importer.For(compiler, lookup)
}
func run(fset *token.FileSet, cfg *Config, analyzers []*analysis.Analyzer) ([]result, error) { func run(fset *token.FileSet, cfg *Config, analyzers []*analysis.Analyzer) ([]result, error) {
// Load, parse, typecheck. // Load, parse, typecheck.
var files []*ast.File var files []*ast.File
...@@ -196,7 +201,7 @@ func run(fset *token.FileSet, cfg *Config, analyzers []*analysis.Analyzer) ([]re ...@@ -196,7 +201,7 @@ func run(fset *token.FileSet, cfg *Config, analyzers []*analysis.Analyzer) ([]re
} }
files = append(files, f) files = append(files, f)
} }
compilerImporter := importer.For(cfg.Compiler, func(path string) (io.ReadCloser, error) { compilerImporter := importerForCompiler(fset, cfg.Compiler, func(path string) (io.ReadCloser, error) {
// path is a resolved package path, not an import path. // path is a resolved package path, not an import path.
file, ok := cfg.PackageFile[path] file, ok := cfg.PackageFile[path]
if !ok { if !ok {
......
// +build go1.12
package unitchecker
import "go/importer"
func init() {
importerForCompiler = importer.ForCompiler
}
...@@ -20,7 +20,7 @@ import ( ...@@ -20,7 +20,7 @@ import (
// a given import path, or an error if no matching package is found. // a given import path, or an error if no matching package is found.
type Lookup func(path string) (io.ReadCloser, error) type Lookup func(path string) (io.ReadCloser, error)
// For returns an Importer for importing from installed packages // ForCompiler returns an Importer for importing from installed packages
// for the compilers "gc" and "gccgo", or for importing directly // for the compilers "gc" and "gccgo", or for importing directly
// from the source if the compiler argument is "source". In this // from the source if the compiler argument is "source". In this
// latter case, importing may fail under circumstances where the // latter case, importing may fail under circumstances where the
...@@ -39,10 +39,11 @@ type Lookup func(path string) (io.ReadCloser, error) ...@@ -39,10 +39,11 @@ type Lookup func(path string) (io.ReadCloser, error)
// (not relative or absolute ones); it is assumed that the translation // (not relative or absolute ones); it is assumed that the translation
// to canonical import paths is being done by the client of the // to canonical import paths is being done by the client of the
// importer. // importer.
func For(compiler string, lookup Lookup) types.Importer { func ForCompiler(fset *token.FileSet, compiler string, lookup Lookup) types.Importer {
switch compiler { switch compiler {
case "gc": case "gc":
return &gcimports{ return &gcimports{
fset: fset,
packages: make(map[string]*types.Package), packages: make(map[string]*types.Package),
lookup: lookup, lookup: lookup,
} }
...@@ -63,13 +64,21 @@ func For(compiler string, lookup Lookup) types.Importer { ...@@ -63,13 +64,21 @@ func For(compiler string, lookup Lookup) types.Importer {
panic("source importer for custom import path lookup not supported (issue #13847).") panic("source importer for custom import path lookup not supported (issue #13847).")
} }
return srcimporter.New(&build.Default, token.NewFileSet(), make(map[string]*types.Package)) return srcimporter.New(&build.Default, fset, make(map[string]*types.Package))
} }
// compiler not supported // compiler not supported
return nil return nil
} }
// For calls ForCompiler with a new FileSet.
//
// Deprecated: use ForCompiler, which populates a FileSet
// with the positions of objects created by the importer.
func For(compiler string, lookup Lookup) types.Importer {
return ForCompiler(token.NewFileSet(), compiler, lookup)
}
// Default returns an Importer for the compiler that built the running binary. // Default returns an Importer for the compiler that built the running binary.
// If available, the result implements types.ImporterFrom. // If available, the result implements types.ImporterFrom.
func Default() types.Importer { func Default() types.Importer {
...@@ -79,6 +88,7 @@ func Default() types.Importer { ...@@ -79,6 +88,7 @@ func Default() types.Importer {
// gc importer // gc importer
type gcimports struct { type gcimports struct {
fset *token.FileSet
packages map[string]*types.Package packages map[string]*types.Package
lookup Lookup lookup Lookup
} }
...@@ -91,7 +101,7 @@ func (m *gcimports) ImportFrom(path, srcDir string, mode types.ImportMode) (*typ ...@@ -91,7 +101,7 @@ func (m *gcimports) ImportFrom(path, srcDir string, mode types.ImportMode) (*typ
if mode != 0 { if mode != 0 {
panic("mode must be 0") panic("mode must be 0")
} }
return gcimporter.Import(m.packages, path, srcDir, m.lookup) return gcimporter.Import(m.fset, m.packages, path, srcDir, m.lookup)
} }
// gccgo importer // gccgo importer
......
...@@ -5,15 +5,18 @@ ...@@ -5,15 +5,18 @@
package importer package importer
import ( import (
"go/token"
"internal/testenv" "internal/testenv"
"io" "io"
"io/ioutil"
"os" "os"
"os/exec" "os/exec"
"runtime"
"strings" "strings"
"testing" "testing"
) )
func TestFor(t *testing.T) { func TestForCompiler(t *testing.T) {
testenv.MustHaveGoBuild(t) testenv.MustHaveGoBuild(t)
const thePackage = "math/big" const thePackage = "math/big"
...@@ -32,8 +35,10 @@ func TestFor(t *testing.T) { ...@@ -32,8 +35,10 @@ func TestFor(t *testing.T) {
t.Skip("golang.org/issue/22500") t.Skip("golang.org/issue/22500")
} }
fset := token.NewFileSet()
t.Run("LookupDefault", func(t *testing.T) { t.Run("LookupDefault", func(t *testing.T) {
imp := For(compiler, nil) imp := ForCompiler(fset, compiler, nil)
pkg, err := imp.Import(thePackage) pkg, err := imp.Import(thePackage)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
...@@ -41,6 +46,21 @@ func TestFor(t *testing.T) { ...@@ -41,6 +46,21 @@ func TestFor(t *testing.T) {
if pkg.Path() != thePackage { if pkg.Path() != thePackage {
t.Fatalf("Path() = %q, want %q", pkg.Path(), thePackage) t.Fatalf("Path() = %q, want %q", pkg.Path(), thePackage)
} }
// Check that the fileset positions are accurate.
// https://github.com/golang/go#28995
mathBigInt := pkg.Scope().Lookup("Int")
posn := fset.Position(mathBigInt.Pos()) // "$GOROOT/src/math/big/int.go:25:1"
filename := strings.Replace(posn.Filename, "$GOROOT", runtime.GOROOT(), 1)
data, err := ioutil.ReadFile(filename)
if err != nil {
t.Fatalf("can't read file containing declaration of math/big.Int: %v", err)
}
lines := strings.Split(string(data), "\n")
if posn.Line > len(lines) || !strings.HasPrefix(lines[posn.Line-1], "type Int") {
t.Fatalf("Object %v position %s does not contain its declaration",
mathBigInt, posn)
}
}) })
t.Run("LookupCustom", func(t *testing.T) { t.Run("LookupCustom", func(t *testing.T) {
...@@ -54,7 +74,7 @@ func TestFor(t *testing.T) { ...@@ -54,7 +74,7 @@ func TestFor(t *testing.T) {
} }
return f, nil return f, nil
} }
imp := For(compiler, lookup) imp := ForCompiler(fset, compiler, lookup)
pkg, err := imp.Import("math/bigger") pkg, err := imp.Import("math/bigger")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
......
...@@ -85,7 +85,7 @@ func FindPkg(path, srcDir string) (filename, id string) { ...@@ -85,7 +85,7 @@ func FindPkg(path, srcDir string) (filename, id string) {
// the corresponding package object to the packages map, and returns the object. // the corresponding package object to the packages map, and returns the object.
// The packages map must contain all packages already imported. // The packages map must contain all packages already imported.
// //
func Import(packages map[string]*types.Package, path, srcDir string, lookup func(path string) (io.ReadCloser, error)) (pkg *types.Package, err error) { func Import(fset *token.FileSet, packages map[string]*types.Package, path, srcDir string, lookup func(path string) (io.ReadCloser, error)) (pkg *types.Package, err error) {
var rc io.ReadCloser var rc io.ReadCloser
var id string var id string
if lookup != nil { if lookup != nil {
...@@ -152,10 +152,6 @@ func Import(packages map[string]*types.Package, path, srcDir string, lookup func ...@@ -152,10 +152,6 @@ func Import(packages map[string]*types.Package, path, srcDir string, lookup func
break break
} }
// TODO(gri): allow clients of go/importer to provide a FileSet.
// Or, define a new standard go/types/gcexportdata package.
fset := token.NewFileSet()
// The indexed export format starts with an 'i'; the older // The indexed export format starts with an 'i'; the older
// binary export format starts with a 'c', 'd', or 'v' // binary export format starts with a 'c', 'd', or 'v'
// (from "version"). Select appropriate importer. // (from "version"). Select appropriate importer.
......
...@@ -17,6 +17,7 @@ import ( ...@@ -17,6 +17,7 @@ import (
"testing" "testing"
"time" "time"
"go/token"
"go/types" "go/types"
) )
...@@ -55,7 +56,8 @@ func compile(t *testing.T, dirname, filename, outdirname string) string { ...@@ -55,7 +56,8 @@ func compile(t *testing.T, dirname, filename, outdirname string) string {
func testPath(t *testing.T, path, srcDir string) *types.Package { func testPath(t *testing.T, path, srcDir string) *types.Package {
t0 := time.Now() t0 := time.Now()
pkg, err := Import(make(map[string]*types.Package), path, srcDir, nil) fset := token.NewFileSet()
pkg, err := Import(fset, make(map[string]*types.Package), path, srcDir, nil)
if err != nil { if err != nil {
t.Errorf("testPath(%s): %s", path, err) t.Errorf("testPath(%s): %s", path, err)
return nil return nil
...@@ -158,6 +160,8 @@ func TestVersionHandling(t *testing.T) { ...@@ -158,6 +160,8 @@ func TestVersionHandling(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
fset := token.NewFileSet()
for _, f := range list { for _, f := range list {
name := f.Name() name := f.Name()
if !strings.HasSuffix(name, ".a") { if !strings.HasSuffix(name, ".a") {
...@@ -173,7 +177,7 @@ func TestVersionHandling(t *testing.T) { ...@@ -173,7 +177,7 @@ func TestVersionHandling(t *testing.T) {
} }
// test that export data can be imported // test that export data can be imported
_, err := Import(make(map[string]*types.Package), pkgpath, dir, nil) _, err := Import(fset, make(map[string]*types.Package), pkgpath, dir, nil)
if err != nil { if err != nil {
// ok to fail if it fails with a newer version error for select files // ok to fail if it fails with a newer version error for select files
if strings.Contains(err.Error(), "newer version") { if strings.Contains(err.Error(), "newer version") {
...@@ -209,7 +213,7 @@ func TestVersionHandling(t *testing.T) { ...@@ -209,7 +213,7 @@ func TestVersionHandling(t *testing.T) {
ioutil.WriteFile(filename, data, 0666) ioutil.WriteFile(filename, data, 0666)
// test that importing the corrupted file results in an error // test that importing the corrupted file results in an error
_, err = Import(make(map[string]*types.Package), pkgpath, corruptdir, nil) _, err = Import(fset, make(map[string]*types.Package), pkgpath, corruptdir, nil)
if err == nil { if err == nil {
t.Errorf("import corrupted %q succeeded", pkgpath) t.Errorf("import corrupted %q succeeded", pkgpath)
} else if msg := err.Error(); !strings.Contains(msg, "version skew") { } else if msg := err.Error(); !strings.Contains(msg, "version skew") {
...@@ -266,6 +270,7 @@ func TestImportedTypes(t *testing.T) { ...@@ -266,6 +270,7 @@ func TestImportedTypes(t *testing.T) {
t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
} }
fset := token.NewFileSet()
for _, test := range importedObjectTests { for _, test := range importedObjectTests {
s := strings.Split(test.name, ".") s := strings.Split(test.name, ".")
if len(s) != 2 { if len(s) != 2 {
...@@ -274,7 +279,7 @@ func TestImportedTypes(t *testing.T) { ...@@ -274,7 +279,7 @@ func TestImportedTypes(t *testing.T) {
importPath := s[0] importPath := s[0]
objName := s[1] objName := s[1]
pkg, err := Import(make(map[string]*types.Package), importPath, ".", nil) pkg, err := Import(fset, make(map[string]*types.Package), importPath, ".", nil)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue continue
...@@ -371,7 +376,8 @@ func TestCorrectMethodPackage(t *testing.T) { ...@@ -371,7 +376,8 @@ func TestCorrectMethodPackage(t *testing.T) {
} }
imports := make(map[string]*types.Package) imports := make(map[string]*types.Package)
_, err := Import(imports, "net/http", ".", nil) fset := token.NewFileSet()
_, err := Import(fset, imports, "net/http", ".", nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
...@@ -433,8 +439,9 @@ func TestIssue13898(t *testing.T) { ...@@ -433,8 +439,9 @@ func TestIssue13898(t *testing.T) {
} }
// import go/internal/gcimporter which imports go/types partially // import go/internal/gcimporter which imports go/types partially
fset := token.NewFileSet()
imports := make(map[string]*types.Package) imports := make(map[string]*types.Package)
_, err := Import(imports, "go/internal/gcimporter", ".", nil) _, err := Import(fset, imports, "go/internal/gcimporter", ".", nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
...@@ -502,8 +509,9 @@ func TestIssue15517(t *testing.T) { ...@@ -502,8 +509,9 @@ func TestIssue15517(t *testing.T) {
// file and package path are different, exposing the problem if present. // file and package path are different, exposing the problem if present.
// The same issue occurs with vendoring.) // The same issue occurs with vendoring.)
imports := make(map[string]*types.Package) imports := make(map[string]*types.Package)
fset := token.NewFileSet()
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
if _, err := Import(imports, "./././testdata/p", tmpdir, nil); err != nil { if _, err := Import(fset, imports, "./././testdata/p", tmpdir, nil); err != nil {
t.Fatal(err) t.Fatal(err)
} }
} }
...@@ -582,7 +590,8 @@ func TestIssue25596(t *testing.T) { ...@@ -582,7 +590,8 @@ func TestIssue25596(t *testing.T) {
} }
func importPkg(t *testing.T, path, srcDir string) *types.Package { func importPkg(t *testing.T, path, srcDir string) *types.Package {
pkg, err := Import(make(map[string]*types.Package), path, srcDir, nil) fset := token.NewFileSet()
pkg, err := Import(fset, make(map[string]*types.Package), path, srcDir, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
......
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