mirror of
https://github.com/golang/go
synced 2024-11-18 23:24:39 -07:00
ea99b82c7b
This change uses the missing imports detection to return diagnostics and warning messages to the user. Updates golang/go#34484 Change-Id: If7bb7e702b8bdbd7e1ad5e26f93acc50d16209b0 Reviewed-on: https://go-review.googlesource.com/c/tools/+/196985 Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Ian Cottrell <iancottrell@google.com>
328 lines
8.3 KiB
Go
328 lines
8.3 KiB
Go
package cache
|
|
|
|
import (
|
|
"context"
|
|
"go/types"
|
|
|
|
"golang.org/x/tools/go/packages"
|
|
"golang.org/x/tools/internal/span"
|
|
"golang.org/x/tools/internal/telemetry/log"
|
|
"golang.org/x/tools/internal/telemetry/tag"
|
|
errors "golang.org/x/xerrors"
|
|
)
|
|
|
|
type snapshot struct {
|
|
id uint64
|
|
|
|
packages map[span.URI]map[packageKey]*checkPackageHandle
|
|
ids map[span.URI][]packageID
|
|
metadata map[packageID]*metadata
|
|
}
|
|
|
|
type metadata struct {
|
|
id packageID
|
|
pkgPath packagePath
|
|
name string
|
|
files []span.URI
|
|
typesSizes types.Sizes
|
|
parents map[packageID]bool
|
|
children map[packageID]*metadata
|
|
errors []packages.Error
|
|
missingDeps map[packagePath]struct{}
|
|
}
|
|
|
|
func (v *view) getSnapshot(uri span.URI) ([]*metadata, []*checkPackageHandle) {
|
|
v.snapshotMu.Lock()
|
|
defer v.snapshotMu.Unlock()
|
|
|
|
var m []*metadata
|
|
for _, id := range v.snapshot.ids[uri] {
|
|
m = append(m, v.snapshot.metadata[id])
|
|
}
|
|
var cphs []*checkPackageHandle
|
|
for _, cph := range v.snapshot.packages[uri] {
|
|
cphs = append(cphs, cph)
|
|
}
|
|
return m, cphs
|
|
}
|
|
|
|
func (v *view) getMetadata(uri span.URI) []*metadata {
|
|
v.snapshotMu.Lock()
|
|
defer v.snapshotMu.Unlock()
|
|
|
|
var m []*metadata
|
|
for _, id := range v.snapshot.ids[uri] {
|
|
m = append(m, v.snapshot.metadata[id])
|
|
}
|
|
return m
|
|
}
|
|
|
|
func (v *view) getPackages(uri span.URI) map[packageKey]*checkPackageHandle {
|
|
v.snapshotMu.Lock()
|
|
defer v.snapshotMu.Unlock()
|
|
|
|
return v.snapshot.packages[uri]
|
|
}
|
|
|
|
func (v *view) updateMetadata(ctx context.Context, uri span.URI, pkgs []*packages.Package) ([]*metadata, map[packageID]map[packagePath]struct{}, error) {
|
|
v.snapshotMu.Lock()
|
|
defer v.snapshotMu.Unlock()
|
|
|
|
// Clear metadata since we are re-running go/packages.
|
|
prevMissingImports := make(map[packageID]map[packagePath]struct{})
|
|
for _, id := range v.snapshot.ids[uri] {
|
|
if m, ok := v.snapshot.metadata[id]; ok && len(m.missingDeps) > 0 {
|
|
prevMissingImports[id] = m.missingDeps
|
|
}
|
|
}
|
|
without := make(map[span.URI]struct{})
|
|
for _, id := range v.snapshot.ids[uri] {
|
|
v.remove(id, without, map[packageID]struct{}{})
|
|
}
|
|
v.snapshot = v.snapshot.cloneMetadata(without)
|
|
|
|
var results []*metadata
|
|
for _, pkg := range pkgs {
|
|
log.Print(ctx, "go/packages.Load", tag.Of("package", pkg.PkgPath), tag.Of("files", pkg.CompiledGoFiles))
|
|
|
|
// Build the import graph for this package.
|
|
if err := v.updateImportGraph(ctx, &importGraph{
|
|
pkgPath: packagePath(pkg.PkgPath),
|
|
pkg: pkg,
|
|
parent: nil,
|
|
}); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
results = append(results, v.snapshot.metadata[packageID(pkg.ID)])
|
|
}
|
|
if len(results) == 0 {
|
|
return nil, nil, errors.Errorf("no metadata for %s", uri)
|
|
}
|
|
return results, prevMissingImports, nil
|
|
}
|
|
|
|
type importGraph struct {
|
|
pkgPath packagePath
|
|
pkg *packages.Package
|
|
parent *metadata
|
|
}
|
|
|
|
func (v *view) updateImportGraph(ctx context.Context, g *importGraph) error {
|
|
// Recreate the metadata rather than reusing it to avoid locking.
|
|
m := &metadata{
|
|
id: packageID(g.pkg.ID),
|
|
pkgPath: g.pkgPath,
|
|
name: g.pkg.Name,
|
|
typesSizes: g.pkg.TypesSizes,
|
|
errors: g.pkg.Errors,
|
|
}
|
|
for _, filename := range g.pkg.CompiledGoFiles {
|
|
uri := span.FileURI(filename)
|
|
v.snapshot.ids[uri] = append(v.snapshot.ids[uri], m.id)
|
|
m.files = append(m.files, uri)
|
|
}
|
|
// Preserve the import graph.
|
|
if original, ok := v.snapshot.metadata[m.id]; ok {
|
|
m.children = original.children
|
|
m.parents = original.parents
|
|
}
|
|
if m.children == nil {
|
|
m.children = make(map[packageID]*metadata)
|
|
}
|
|
if m.parents == nil {
|
|
m.parents = make(map[packageID]bool)
|
|
}
|
|
|
|
// Add the metadata to the cache.
|
|
v.snapshot.metadata[m.id] = m
|
|
|
|
// Connect the import graph.
|
|
if g.parent != nil {
|
|
m.parents[g.parent.id] = true
|
|
g.parent.children[m.id] = m
|
|
}
|
|
for importPath, importPkg := range g.pkg.Imports {
|
|
importPkgPath := packagePath(importPath)
|
|
if importPkgPath == g.pkgPath {
|
|
return errors.Errorf("cycle detected in %s", importPath)
|
|
}
|
|
// Don't remember any imports with significant errors.
|
|
if importPkgPath != "unsafe" && len(importPkg.CompiledGoFiles) == 0 {
|
|
if m.missingDeps == nil {
|
|
m.missingDeps = make(map[packagePath]struct{})
|
|
}
|
|
m.missingDeps[importPkgPath] = struct{}{}
|
|
continue
|
|
}
|
|
if _, ok := m.children[packageID(importPkg.ID)]; !ok {
|
|
if err := v.updateImportGraph(ctx, &importGraph{
|
|
pkgPath: importPkgPath,
|
|
pkg: importPkg,
|
|
parent: m,
|
|
}); err != nil {
|
|
log.Error(ctx, "error in dependency", err)
|
|
}
|
|
}
|
|
}
|
|
// Clear out any imports that have been removed since the package was last loaded.
|
|
for importID := range m.children {
|
|
child, ok := v.snapshot.metadata[importID]
|
|
if !ok {
|
|
continue
|
|
}
|
|
importPath := string(child.pkgPath)
|
|
if _, ok := g.pkg.Imports[importPath]; ok {
|
|
continue
|
|
}
|
|
delete(m.children, importID)
|
|
delete(child.parents, m.id)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (v *view) updatePackages(cphs []*checkPackageHandle) {
|
|
v.snapshotMu.Lock()
|
|
defer v.snapshotMu.Unlock()
|
|
|
|
for _, cph := range cphs {
|
|
for _, ph := range cph.files {
|
|
uri := ph.File().Identity().URI
|
|
if _, ok := v.snapshot.packages[uri]; !ok {
|
|
v.snapshot.packages[uri] = make(map[packageKey]*checkPackageHandle)
|
|
}
|
|
v.snapshot.packages[uri][packageKey{
|
|
id: cph.m.id,
|
|
mode: ph.Mode(),
|
|
}] = cph
|
|
}
|
|
}
|
|
}
|
|
|
|
// invalidateContent invalidates the content of a Go file,
|
|
// including any position and type information that depends on it.
|
|
func (v *view) invalidateContent(ctx context.Context, f *goFile) {
|
|
f.handleMu.Lock()
|
|
defer f.handleMu.Unlock()
|
|
|
|
without := make(map[span.URI]struct{})
|
|
|
|
// Remove the package and all of its reverse dependencies from the cache.
|
|
v.snapshotMu.Lock()
|
|
defer v.snapshotMu.Unlock()
|
|
|
|
for _, id := range v.snapshot.ids[f.URI()] {
|
|
f.view.remove(id, without, map[packageID]struct{}{})
|
|
}
|
|
v.snapshot = v.snapshot.clonePackages(without)
|
|
f.handle = nil
|
|
}
|
|
|
|
// invalidateMeta invalidates package metadata for all files in f's
|
|
// package. This forces f's package's metadata to be reloaded next
|
|
// time the package is checked.
|
|
func (v *view) invalidateMetadata(uri span.URI) {
|
|
v.snapshotMu.Lock()
|
|
defer v.snapshotMu.Unlock()
|
|
|
|
without := make(map[span.URI]struct{})
|
|
|
|
for _, id := range v.snapshot.ids[uri] {
|
|
v.remove(id, without, map[packageID]struct{}{})
|
|
}
|
|
v.snapshot = v.snapshot.cloneMetadata(without)
|
|
}
|
|
|
|
// remove invalidates a package and its reverse dependencies in the view's
|
|
// package cache. It is assumed that the caller has locked both the mutexes
|
|
// of both the mcache and the pcache.
|
|
func (v *view) remove(id packageID, toDelete map[span.URI]struct{}, seen map[packageID]struct{}) {
|
|
if _, ok := seen[id]; ok {
|
|
return
|
|
}
|
|
m, ok := v.snapshot.metadata[id]
|
|
if !ok {
|
|
return
|
|
}
|
|
seen[id] = struct{}{}
|
|
for parentID := range m.parents {
|
|
v.remove(parentID, toDelete, seen)
|
|
}
|
|
for _, uri := range m.files {
|
|
toDelete[uri] = struct{}{}
|
|
}
|
|
}
|
|
|
|
func (s *snapshot) clonePackages(without map[span.URI]struct{}) *snapshot {
|
|
result := &snapshot{
|
|
id: s.id + 1,
|
|
packages: make(map[span.URI]map[packageKey]*checkPackageHandle),
|
|
ids: s.ids,
|
|
metadata: s.metadata,
|
|
}
|
|
for k, v := range s.packages {
|
|
if _, ok := without[k]; ok {
|
|
continue
|
|
}
|
|
result.packages[k] = v
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (s *snapshot) cloneMetadata(without map[span.URI]struct{}) *snapshot {
|
|
result := &snapshot{
|
|
id: s.id + 1,
|
|
packages: s.packages,
|
|
ids: make(map[span.URI][]packageID),
|
|
metadata: make(map[packageID]*metadata),
|
|
}
|
|
withoutIDs := make(map[packageID]struct{})
|
|
for k, ids := range s.ids {
|
|
if _, ok := without[k]; ok {
|
|
for _, id := range ids {
|
|
withoutIDs[id] = struct{}{}
|
|
}
|
|
continue
|
|
}
|
|
result.ids[k] = ids
|
|
}
|
|
for k, v := range s.metadata {
|
|
if _, ok := withoutIDs[k]; ok {
|
|
continue
|
|
}
|
|
result.metadata[k] = v
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (v *view) reverseDependencies(ctx context.Context, uri span.URI) map[span.URI]struct{} {
|
|
seen := make(map[packageID]struct{})
|
|
uris := make(map[span.URI]struct{})
|
|
|
|
v.snapshotMu.Lock()
|
|
defer v.snapshotMu.Unlock()
|
|
|
|
for _, id := range v.snapshot.ids[uri] {
|
|
v.rdeps(id, seen, uris, id)
|
|
}
|
|
return uris
|
|
}
|
|
|
|
func (v *view) rdeps(topID packageID, seen map[packageID]struct{}, results map[span.URI]struct{}, id packageID) {
|
|
if _, ok := seen[id]; ok {
|
|
return
|
|
}
|
|
seen[id] = struct{}{}
|
|
m, ok := v.snapshot.metadata[id]
|
|
if !ok {
|
|
return
|
|
}
|
|
if id != topID {
|
|
for _, uri := range m.files {
|
|
results[uri] = struct{}{}
|
|
}
|
|
}
|
|
for parentID := range m.parents {
|
|
v.rdeps(topID, seen, results, parentID)
|
|
}
|
|
}
|