// vendored packages. See https://golang.org/s/go15vendor.
// If possible, external implementations should implement ImporterFrom.
type Importer interface {
- // Import returns the imported package for the given import
- // path, or an error if the package couldn't be imported.
- // Two calls to Import with the same path return the same
- // package.
+ // Import returns the imported package for the given import path.
+ // The semantics is like for ImporterFrom.ImportFrom except that
+ // dir and mode are ignored (since they are not present).
Import(path string) (*Package, error)
}
Importer
// ImportFrom returns the imported package for the given import
- // path when imported by the package in srcDir, or an error
- // if the package couldn't be imported. The mode value must
- // be 0; it is reserved for future use.
- // Two calls to ImportFrom with the same path and srcDir return
- // the same package.
- ImportFrom(path, srcDir string, mode ImportMode) (*Package, error)
+ // path when imported by a package file located in dir.
+ // If the import failed, besides returning an error, ImportFrom
+ // is encouraged to cache and return a package anyway, if one
+ // was created. This will reduce package inconsistencies and
+ // follow-on type checker errors due to the missing package.
+ // The mode value must be 0; it is reserved for future use.
+ // Two calls to ImportFrom with the same path and dir must
+ // return the same package.
+ ImportFrom(path, dir string, mode ImportMode) (*Package, error)
}
// A Config specifies the configuration for type checking.
// identifiers referring to package C (which won't find an object).
// This feature is intended for the standard library cmd/api tool.
//
- // Caution: Effects may be unpredictable due to follow-up errors.
+ // Caution: Effects may be unpredictable due to follow-on errors.
// Do not use casually!
FakeImportC bool
}
}
}
+
+// TestFailedImport tests that we don't get follow-on errors
+// elsewhere in a package due to failing to import a package.
+func TestFailedImport(t *testing.T) {
+ testenv.MustHaveGoBuild(t)
+
+ const src = `
+package p
+
+import "foo" // should only see an error here
+
+const c = foo.C
+type T = foo.T
+var v T = c
+func f(x T) T { return foo.F(x) }
+`
+ fset := token.NewFileSet()
+ f, err := parser.ParseFile(fset, "src", src, 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ files := []*ast.File{f}
+
+ // type-check using all possible importers
+ for _, compiler := range []string{"gc", "gccgo", "source"} {
+ errcount := 0
+ conf := Config{
+ Error: func(err error) {
+ // we should only see the import error
+ if errcount > 0 || !strings.Contains(err.Error(), "could not import foo") {
+ t.Errorf("for %s importer, got unexpected error: %v", compiler, err)
+ }
+ errcount++
+ },
+ Importer: importer.For(compiler, nil),
+ }
+
+ info := &Info{
+ Uses: make(map[*ast.Ident]Object),
+ }
+ pkg, _ := conf.Check("p", fset, files, info)
+ if pkg == nil {
+ t.Errorf("for %s importer, type-checking failed to return a package", compiler)
+ continue
+ }
+
+ imports := pkg.Imports()
+ if len(imports) != 1 {
+ t.Errorf("for %s importer, got %d imports, want 1", compiler, len(imports))
+ continue
+ }
+ imp := imports[0]
+ if imp.Name() != "foo" {
+ t.Errorf(`for %s importer, got %q, want "foo"`, compiler, imp.Name())
+ continue
+ }
+
+ // verify that all uses of foo refer to the imported package foo (imp)
+ for ident, obj := range info.Uses {
+ if ident.Name == "foo" {
+ if obj, ok := obj.(*PkgName); ok {
+ if obj.Imported() != imp {
+ t.Errorf("%s resolved to %v; want %v", ident, obj.Imported(), imp)
+ }
+ } else {
+ t.Errorf("%s resolved to %v; want package name", ident, obj)
+ }
+ }
+ }
+ }
+}
return fmt.Sprintf("file[%d]", fileNo)
}
+func (check *Checker) importPackage(pos token.Pos, path, dir string) *Package {
+ // If we already have a package for the given (path, dir)
+ // pair, use it instead of doing a full import.
+ // Checker.impMap only caches packages that are marked Complete
+ // or fake (dummy packages for failed imports). Incomplete but
+ // non-fake packages do require an import to complete them.
+ key := importKey{path, dir}
+ imp := check.impMap[key]
+ if imp != nil {
+ return imp
+ }
+
+ // no package yet => import it
+ if path == "C" && check.conf.FakeImportC {
+ imp = NewPackage("C", "C")
+ imp.fake = true
+ } else {
+ // ordinary import
+ var err error
+ if importer := check.conf.Importer; importer == nil {
+ err = fmt.Errorf("Config.Importer not installed")
+ } else if importerFrom, ok := importer.(ImporterFrom); ok {
+ imp, err = importerFrom.ImportFrom(path, dir, 0)
+ if imp == nil && err == nil {
+ err = fmt.Errorf("Config.Importer.ImportFrom(%s, %s, 0) returned nil but no error", path, dir)
+ }
+ } else {
+ imp, err = importer.Import(path)
+ if imp == nil && err == nil {
+ err = fmt.Errorf("Config.Importer.Import(%s) returned nil but no error", path)
+ }
+ }
+ if err != nil {
+ check.errorf(pos, "could not import %s (%s)", path, err)
+ if imp == nil {
+ // create a new fake package
+ // come up with a sensible package name (heuristic)
+ name := path
+ if i := len(name); i > 0 && name[i-1] == '/' {
+ name = name[:i-1]
+ }
+ if i := strings.LastIndex(name, "/"); i >= 0 {
+ name = name[i+1:]
+ }
+ imp = NewPackage(path, name)
+ }
+ // continue to use the package as best as we can
+ imp.fake = true // avoid follow-up lookup failures
+ }
+ }
+
+ // package should be complete or marked fake, but be cautious
+ if imp.complete || imp.fake {
+ check.impMap[key] = imp
+ return imp
+ }
+
+ // something went wrong (importer may have returned incomplete package without error)
+ return nil
+}
+
// collectObjects collects all file and package objects and inserts them
// into their respective scopes. It also performs imports and associates
// methods with receiver base type names.
// pkgImports is the set of packages already imported by any package file seen
// so far. Used to avoid duplicate entries in pkg.imports. Allocate and populate
// it (pkg.imports may not be empty if we are checking test files incrementally).
+ // Note that pkgImports is keyed by package (and thus package path), not by an
+ // importKey value. Two different importKey values may map to the same package
+ // which is why we cannot use the check.impMap here.
var pkgImports = make(map[*Package]bool)
for _, imp := range pkg.imports {
pkgImports[imp] = true
}
- // srcDir is the directory used by the Importer to look up packages.
- // The typechecker itself doesn't need this information so it is not
- // explicitly provided. Instead, we extract it from position info of
- // the source files as needed.
- // This is the only place where the type-checker (just the importer)
- // needs to know the actual source location of a file.
- // TODO(gri) can we come up with a better API instead?
- var srcDir string
- if len(check.files) > 0 {
- // FileName may be "" (typically for tests) in which case
- // we get "." as the srcDir which is what we would want.
- srcDir = dir(check.fset.Position(check.files[0].Name.Pos()).Filename)
- }
-
for fileNo, file := range check.files {
// The package identifier denotes the current package,
// but there is no corresponding package object.
fileScope := NewScope(check.pkg.scope, pos, end, check.filename(fileNo))
check.recordScope(file, fileScope)
+ // determine file directory, necessary to resolve imports
+ // FileName may be "" (typically for tests) in which case
+ // we get "." as the directory which is what we would want.
+ fileDir := dir(check.fset.Position(file.Name.Pos()).Filename)
+
for _, decl := range file.Decls {
switch d := decl.(type) {
case *ast.BadDecl:
switch s := spec.(type) {
case *ast.ImportSpec:
// import package
- var imp *Package
path, err := validatedImportPath(s.Path.Value)
if err != nil {
check.errorf(s.Path.Pos(), "invalid import path (%s)", err)
continue
}
- if path == "C" && check.conf.FakeImportC {
- // TODO(gri) shouldn't create a new one each time
- imp = NewPackage("C", "C")
- imp.fake = true
- } else {
- // ordinary import
- if importer := check.conf.Importer; importer == nil {
- err = fmt.Errorf("Config.Importer not installed")
- } else if importerFrom, ok := importer.(ImporterFrom); ok {
- imp, err = importerFrom.ImportFrom(path, srcDir, 0)
- if imp == nil && err == nil {
- err = fmt.Errorf("Config.Importer.ImportFrom(%s, %s, 0) returned nil but no error", path, pkg.path)
- }
- } else {
- imp, err = importer.Import(path)
- if imp == nil && err == nil {
- err = fmt.Errorf("Config.Importer.Import(%s) returned nil but no error", path)
- }
- }
- if err != nil {
- check.errorf(s.Path.Pos(), "could not import %s (%s)", path, err)
- continue
- }
+
+ imp := check.importPackage(s.Path.Pos(), path, fileDir)
+ if imp == nil {
+ continue
}
// add package to list of explicit imports