Skip to content

Commit f345449

Browse files
committed
gopls/internal/server: filter diagnostics to "best" views
Filter diagnostics only to the "best" view for a file. This reduces the likelihood that we show spurious import diagnostics due to module graph pruning, as reported by golang/go#66425. Absent a reproducer this is hard to test, yet the change makes intuitive sense (arguably): it is confusing if diagnostics are inconsistent with other operations like jump-to-definition that find the "best" view. Fixes golang/go#66425 Change-Id: Iadb1a01518a30cc3dad2d412b1ded612ab35d6cc Reviewed-on: https://go-review.googlesource.com/c/tools/+/574718 Reviewed-by: Alan Donovan <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 42d590c commit f345449

File tree

3 files changed

+55
-19
lines changed

3 files changed

+55
-19
lines changed

gopls/internal/cache/session.go

+25-17
Original file line numberDiff line numberDiff line change
@@ -533,27 +533,17 @@ checkFiles:
533533
// Views and viewDefinitions.
534534
type viewDefiner interface{ definition() *viewDefinition }
535535

536-
// bestView returns the best View or viewDefinition that contains the
537-
// given file, or (nil, nil) if no matching view is found.
538-
//
539-
// bestView only returns an error in the event of context cancellation.
540-
//
541-
// Making this function generic is convenient so that we can avoid mapping view
542-
// definitions back to views inside Session.DidModifyFiles, where performance
543-
// matters. It is, however, not the cleanest application of generics.
536+
// BestViews returns the most relevant subset of views for a given uri.
544537
//
545-
// Note: keep this function in sync with defineView.
546-
func bestView[V viewDefiner](ctx context.Context, fs file.Source, fh file.Handle, views []V) (V, error) {
547-
var zero V
548-
538+
// This may be used to filter diagnostics to the most relevant builds.
539+
func BestViews[V viewDefiner](ctx context.Context, fs file.Source, uri protocol.DocumentURI, views []V) ([]V, error) {
549540
if len(views) == 0 {
550-
return zero, nil // avoid the call to findRootPattern
541+
return nil, nil // avoid the call to findRootPattern
551542
}
552-
uri := fh.URI()
553543
dir := uri.Dir()
554544
modURI, err := findRootPattern(ctx, dir, "go.mod", fs)
555545
if err != nil {
556-
return zero, err
546+
return nil, err
557547
}
558548

559549
// Prefer GoWork > GoMod > GOPATH > GoPackages > AdHoc.
@@ -631,8 +621,26 @@ func bestView[V viewDefiner](ctx context.Context, fs file.Source, fh file.Handle
631621
bestViews = goPackagesViews
632622
case len(adHocViews) > 0:
633623
bestViews = adHocViews
634-
default:
635-
return zero, nil
624+
}
625+
626+
return bestViews, nil
627+
}
628+
629+
// bestView returns the best View or viewDefinition that contains the
630+
// given file, or (nil, nil) if no matching view is found.
631+
//
632+
// bestView only returns an error in the event of context cancellation.
633+
//
634+
// Making this function generic is convenient so that we can avoid mapping view
635+
// definitions back to views inside Session.DidModifyFiles, where performance
636+
// matters. It is, however, not the cleanest application of generics.
637+
//
638+
// Note: keep this function in sync with defineView.
639+
func bestView[V viewDefiner](ctx context.Context, fs file.Source, fh file.Handle, views []V) (V, error) {
640+
var zero V
641+
bestViews, err := BestViews(ctx, fs, fh.URI(), views)
642+
if err != nil || len(bestViews) == 0 {
643+
return zero, err
636644
}
637645

638646
content, err := fh.Content()

gopls/internal/golang/completion/completion.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -497,7 +497,7 @@ func Completion(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, p
497497
items, surrounding, innerErr := packageClauseCompletions(ctx, snapshot, fh, protoPos)
498498
if innerErr != nil {
499499
// return the error for GetParsedFile since it's more relevant in this situation.
500-
return nil, nil, fmt.Errorf("getting file %s for Completion: %w (package completions: %v)", fh.URI(), err, innerErr)
500+
return nil, nil, fmt.Errorf("getting file %s for Completion: %v (package completions: %v)", fh.URI(), err, innerErr)
501501
}
502502
return items, surrounding, nil
503503
}

gopls/internal/server/diagnostics.go

+29-1
Original file line numberDiff line numberDiff line change
@@ -831,7 +831,6 @@ func (s *server) updateOrphanedFileDiagnostics(ctx context.Context, modID uint64
831831
//
832832
// If the publication succeeds, it updates f.publishedHash and f.mustPublish.
833833
func (s *server) publishFileDiagnosticsLocked(ctx context.Context, views viewSet, uri protocol.DocumentURI, version int32, f *fileDiagnostics) error {
834-
835834
// We add a disambiguating suffix (e.g. " [darwin,arm64]") to
836835
// each diagnostic that doesn't occur in the default view;
837836
// see golang/go#65496.
@@ -851,6 +850,8 @@ func (s *server) publishFileDiagnosticsLocked(ctx context.Context, views viewSet
851850
for _, diag := range f.orphanedFileDiagnostics {
852851
add(diag, "")
853852
}
853+
854+
var allViews []*cache.View
854855
for view, viewDiags := range f.byView {
855856
if _, ok := views[view]; !ok {
856857
delete(f.byView, view) // view no longer exists
@@ -859,7 +860,34 @@ func (s *server) publishFileDiagnosticsLocked(ctx context.Context, views viewSet
859860
if viewDiags.version != version {
860861
continue // a payload of diagnostics applies to a specific file version
861862
}
863+
allViews = append(allViews, view)
864+
}
865+
866+
// Only report diagnostics from the best views for a file. This avoids
867+
// spurious import errors when a view has only a partial set of dependencies
868+
// for a package (golang/go#66425).
869+
//
870+
// It's ok to use the session to derive the eligible views, because we
871+
// publish diagnostics following any state change, so the set of best views
872+
// is eventually consistent.
873+
bestViews, err := cache.BestViews(ctx, s.session, uri, allViews)
874+
if err != nil {
875+
return err
876+
}
877+
878+
if len(bestViews) == 0 {
879+
// If we have no preferred diagnostics for a given file (i.e., the file is
880+
// not naturally nested within a view), then all diagnostics should be
881+
// considered valid.
882+
//
883+
// This could arise if the user jumps to definition outside the workspace.
884+
// There is no view that owns the file, so its diagnostics are valid from
885+
// any view.
886+
bestViews = allViews
887+
}
862888

889+
for _, view := range bestViews {
890+
viewDiags := f.byView[view]
863891
// Compute the view's suffix (e.g. " [darwin,arm64]").
864892
var suffix string
865893
{

0 commit comments

Comments
 (0)