| // Copyright 2019 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package cache |
| |
| import ( |
| "context" |
| "fmt" |
| "go/ast" |
| "go/scanner" |
| "go/token" |
| "go/types" |
| |
| "golang.org/x/tools/go/analysis" |
| "golang.org/x/tools/go/packages" |
| "golang.org/x/tools/internal/span" |
| ) |
| |
| type importer struct { |
| view *view |
| |
| // seen maintains the set of previously imported packages. |
| // If we have seen a package that is already in this map, we have a circular import. |
| seen map[packageID]struct{} |
| |
| // topLevelPkgID is the ID of the package from which type-checking began. |
| topLevelPkgID packageID |
| |
| ctx context.Context |
| fset *token.FileSet |
| } |
| |
| func (imp *importer) Import(pkgPath string) (*types.Package, error) { |
| id, ok := imp.view.mcache.ids[packagePath(pkgPath)] |
| if !ok { |
| return nil, fmt.Errorf("no known ID for %s", pkgPath) |
| } |
| pkg, err := imp.getPkg(id) |
| if err != nil { |
| return nil, err |
| } |
| return pkg.types, nil |
| } |
| |
| func (imp *importer) getPkg(id packageID) (*pkg, error) { |
| if _, ok := imp.seen[id]; ok { |
| return nil, fmt.Errorf("circular import detected") |
| } |
| imp.view.pcache.mu.Lock() |
| e, ok := imp.view.pcache.packages[id] |
| |
| if ok { |
| // cache hit |
| imp.view.pcache.mu.Unlock() |
| // wait for entry to become ready |
| <-e.ready |
| } else { |
| // cache miss |
| e = &entry{ready: make(chan struct{})} |
| imp.view.pcache.packages[id] = e |
| imp.view.pcache.mu.Unlock() |
| |
| // This goroutine becomes responsible for populating |
| // the entry and broadcasting its readiness. |
| e.pkg, e.err = imp.typeCheck(id) |
| close(e.ready) |
| } |
| |
| if e.err != nil { |
| // If the import had been previously canceled, and that error cached, try again. |
| if e.err == context.Canceled && imp.ctx.Err() == nil { |
| imp.view.pcache.mu.Lock() |
| // Clear out canceled cache entry if it is still there. |
| if imp.view.pcache.packages[id] == e { |
| delete(imp.view.pcache.packages, id) |
| } |
| imp.view.pcache.mu.Unlock() |
| return imp.getPkg(id) |
| } |
| return nil, e.err |
| } |
| |
| return e.pkg, nil |
| } |
| |
| func (imp *importer) typeCheck(id packageID) (*pkg, error) { |
| meta, ok := imp.view.mcache.packages[id] |
| if !ok { |
| return nil, fmt.Errorf("no metadata for %v", id) |
| } |
| pkg := &pkg{ |
| id: meta.id, |
| pkgPath: meta.pkgPath, |
| files: meta.files, |
| imports: make(map[packagePath]*pkg), |
| typesSizes: meta.typesSizes, |
| typesInfo: &types.Info{ |
| Types: make(map[ast.Expr]types.TypeAndValue), |
| Defs: make(map[*ast.Ident]types.Object), |
| Uses: make(map[*ast.Ident]types.Object), |
| Implicits: make(map[ast.Node]types.Object), |
| Selections: make(map[*ast.SelectorExpr]*types.Selection), |
| Scopes: make(map[ast.Node]*types.Scope), |
| }, |
| analyses: make(map[*analysis.Analyzer]*analysisEntry), |
| } |
| // Ignore function bodies for any dependency packages. |
| ignoreFuncBodies := imp.topLevelPkgID != pkg.id |
| files, parseErrs, err := imp.parseFiles(meta.files, ignoreFuncBodies) |
| if err != nil { |
| return nil, err |
| } |
| for _, err := range parseErrs { |
| imp.view.appendPkgError(pkg, err) |
| } |
| |
| // Use the default type information for the unsafe package. |
| if meta.pkgPath == "unsafe" { |
| pkg.types = types.Unsafe |
| } else if len(files) == 0 { // not the unsafe package, no parsed files |
| return nil, fmt.Errorf("no parsed files for package %s", pkg.pkgPath) |
| } else { |
| pkg.types = types.NewPackage(string(meta.pkgPath), meta.name) |
| } |
| |
| pkg.syntax = files |
| |
| // Handle circular imports by copying previously seen imports. |
| seen := make(map[packageID]struct{}) |
| for k, v := range imp.seen { |
| seen[k] = v |
| } |
| seen[id] = struct{}{} |
| |
| cfg := &types.Config{ |
| Error: func(err error) { |
| imp.view.appendPkgError(pkg, err) |
| }, |
| IgnoreFuncBodies: ignoreFuncBodies, |
| Importer: &importer{ |
| view: imp.view, |
| ctx: imp.ctx, |
| fset: imp.fset, |
| topLevelPkgID: imp.topLevelPkgID, |
| seen: seen, |
| }, |
| } |
| check := types.NewChecker(cfg, imp.fset, pkg.types, pkg.typesInfo) |
| check.Files(pkg.GetSyntax()) |
| |
| // Add every file in this package to our cache. |
| imp.cachePackage(imp.ctx, pkg, meta) |
| |
| return pkg, nil |
| } |
| |
| func (imp *importer) cachePackage(ctx context.Context, pkg *pkg, meta *metadata) { |
| for _, filename := range pkg.files { |
| f, err := imp.view.getFile(span.FileURI(filename)) |
| if err != nil { |
| imp.view.session.log.Errorf(ctx, "no file: %v", err) |
| continue |
| } |
| gof, ok := f.(*goFile) |
| if !ok { |
| imp.view.session.log.Errorf(ctx, "%v is not a Go file", f.URI()) |
| continue |
| } |
| // Set the package even if we failed to parse the file. |
| gof.pkg = pkg |
| |
| // Get the *token.File directly from the AST. |
| gof.ast = pkg.syntax[filename] |
| if gof.ast == nil { |
| imp.view.session.log.Errorf(ctx, "no AST information for %s", filename) |
| continue |
| } |
| if gof.ast.file == nil { |
| imp.view.session.log.Errorf(ctx, "no AST for %s", filename) |
| continue |
| } |
| pos := gof.ast.file.Pos() |
| if !pos.IsValid() { |
| imp.view.session.log.Errorf(ctx, "AST for %s has an invalid position", filename) |
| continue |
| } |
| tok := imp.view.session.cache.FileSet().File(pos) |
| if tok == nil { |
| imp.view.session.log.Errorf(ctx, "no *token.File for %s", filename) |
| continue |
| } |
| gof.token = tok |
| gof.imports = gof.ast.file.Imports |
| } |
| |
| // Set imports of package to correspond to cached packages. |
| // We lock the package cache, but we shouldn't get any inconsistencies |
| // because we are still holding the lock on the view. |
| for importPath := range meta.children { |
| importPkg, err := imp.getPkg(importPath) |
| if err != nil { |
| continue |
| } |
| pkg.imports[importPkg.pkgPath] = importPkg |
| } |
| } |
| |
| func (v *view) appendPkgError(pkg *pkg, err error) { |
| if err == nil { |
| return |
| } |
| var errs []packages.Error |
| switch err := err.(type) { |
| case *scanner.Error: |
| errs = append(errs, packages.Error{ |
| Pos: err.Pos.String(), |
| Msg: err.Msg, |
| Kind: packages.ParseError, |
| }) |
| case scanner.ErrorList: |
| // The first parser error is likely the root cause of the problem. |
| if err.Len() > 0 { |
| errs = append(errs, packages.Error{ |
| Pos: err[0].Pos.String(), |
| Msg: err[0].Msg, |
| Kind: packages.ParseError, |
| }) |
| } |
| case types.Error: |
| errs = append(errs, packages.Error{ |
| Pos: v.Session().Cache().FileSet().Position(err.Pos).String(), |
| Msg: err.Msg, |
| Kind: packages.TypeError, |
| }) |
| } |
| pkg.errors = append(pkg.errors, errs...) |
| } |