Skip to content

Commit 83c6c25

Browse files
committed
gopls/internal/server: analyze widest packages for open files
This change causes server.diagnose to choose a covering set of packages for the open files preferring the widest package for a given package path. This has two benefits: correctness, for analyzers (such as unusedparams) that make incorrect deductions when additional files may be added to the package; and efficiency, as it requires fewer packages in general. Change-Id: I21576c6ce4c136d6c72ccce76971a782abd4df53 Reviewed-on: https://go-review.googlesource.com/c/tools/+/556816 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Robert Findley <[email protected]>
1 parent c467be3 commit 83c6c25

File tree

4 files changed

+44
-4
lines changed

4 files changed

+44
-4
lines changed

gopls/internal/analysis/unusedparams/unusedparams.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,15 @@ func run(pass *analysis.Pass) (any, error) {
5252
// use(func() { ... }) address-taken
5353
//
5454

55+
// Note: this algorithm relies on the assumption that the
56+
// analyzer is called only for the "widest" package for a
57+
// given file: that is, p_test in preference to p, if both
58+
// exist. Analyzing only package p may produce diagnostics
59+
// that would be falsified based on declarations in p_test.go
60+
// files. The gopls analysis driver does this, but most
61+
// drivers to not, so running this command in, say,
62+
// unitchecker or multichecker may produce incorrect results.
63+
5564
// Gather global information:
5665
// - uses of functions not in call position
5766
// - unexported interface methods

gopls/internal/lsp/cache/analysis.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ const AnalysisProgressTitle = "Analyzing Dependencies"
172172
// The analyzers list must be duplicate free; order does not matter.
173173
//
174174
// Notifications of progress may be sent to the optional reporter.
175-
func (s *Snapshot) Analyze(ctx context.Context, pkgs map[PackageID]unit, analyzers []*settings.Analyzer, reporter *progress.Tracker) ([]*Diagnostic, error) {
175+
func (s *Snapshot) Analyze(ctx context.Context, pkgs map[PackageID]*metadata.Package, analyzers []*settings.Analyzer, reporter *progress.Tracker) ([]*Diagnostic, error) {
176176
start := time.Now() // for progress reporting
177177

178178
var tagStr string // sorted comma-separated list of PackageIDs

gopls/internal/lsp/source/diagnostics.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"context"
99

1010
"golang.org/x/tools/gopls/internal/lsp/cache"
11+
"golang.org/x/tools/gopls/internal/lsp/cache/metadata"
1112
"golang.org/x/tools/gopls/internal/lsp/progress"
1213
"golang.org/x/tools/gopls/internal/lsp/protocol"
1314
"golang.org/x/tools/gopls/internal/settings"
@@ -18,7 +19,7 @@ import (
1819
//
1920
// If the provided tracker is non-nil, it may be used to provide notifications
2021
// of the ongoing analysis pass.
21-
func Analyze(ctx context.Context, snapshot *cache.Snapshot, pkgIDs map[PackageID]unit, tracker *progress.Tracker) (map[protocol.DocumentURI][]*cache.Diagnostic, error) {
22+
func Analyze(ctx context.Context, snapshot *cache.Snapshot, pkgIDs map[PackageID]*metadata.Package, tracker *progress.Tracker) (map[protocol.DocumentURI][]*cache.Diagnostic, error) {
2223
// Exit early if the context has been canceled. This also protects us
2324
// from a race on Options, see golang/go#36699.
2425
if ctx.Err() != nil {

gopls/internal/server/diagnostics.go

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -415,9 +415,30 @@ func (s *server) diagnose(ctx context.Context, snapshot *cache.Snapshot) (diagMa
415415
}()
416416

417417
// Run type checking and go/analysis diagnosis of packages in parallel.
418+
//
419+
// For analysis, we use the *widest* package for each open file,
420+
// for two reasons:
421+
//
422+
// - Correctness: some analyzers (e.g. unusedparam) depend
423+
// on it. If applied to a non-test package for which a
424+
// corresponding test package exists, they make assumptions
425+
// that are falsified in the test package, for example that
426+
// all references to unexported symbols are visible to the
427+
// analysis.
428+
//
429+
// - Efficiency: it may yield a smaller covering set of
430+
// PackageIDs for a given set of files. For example, {x.go,
431+
// x_test.go} is covered by the single package x_test using
432+
// "widest". (Using "narrowest", it would be covered only by
433+
// the pair of packages {x, x_test}, Originally we used all
434+
// covering packages, so {x.go} alone would be analyzed
435+
// twice.)
418436
var (
419437
toDiagnose = make(map[metadata.PackageID]*metadata.Package)
420-
toAnalyze = make(map[metadata.PackageID]unit)
438+
toAnalyze = make(map[metadata.PackageID]*metadata.Package)
439+
440+
// secondary index, used to eliminate narrower packages.
441+
toAnalyzeWidest = make(map[source.PackagePath]*metadata.Package)
421442
)
422443
for _, mp := range workspacePkgs {
423444
var hasNonIgnored, hasOpenFile bool
@@ -432,7 +453,16 @@ func (s *server) diagnose(ctx context.Context, snapshot *cache.Snapshot) (diagMa
432453
if hasNonIgnored {
433454
toDiagnose[mp.ID] = mp
434455
if hasOpenFile {
435-
toAnalyze[mp.ID] = unit{}
456+
if prev, ok := toAnalyzeWidest[mp.PkgPath]; ok {
457+
if len(prev.CompiledGoFiles) >= len(mp.CompiledGoFiles) {
458+
// Previous entry is not narrower; keep it.
459+
continue
460+
}
461+
// Evict previous (narrower) entry.
462+
delete(toAnalyze, prev.ID)
463+
}
464+
toAnalyze[mp.ID] = mp
465+
toAnalyzeWidest[mp.PkgPath] = mp
436466
}
437467
}
438468
}

0 commit comments

Comments
 (0)