Skip to content

Commit 6887e99

Browse files
findleyrgopherbot
authored andcommitted
gopls/internal/cache: use a better view in viewOfLocked
The 'bestView' function was used in two places, where the meaning of 'best' differed. When re-evaluating view definitions in selectViewDefs, we may want to create a new view if none of them matched build tags. When operating on a file in viewOfLocked, we want to choose the most relevant view out of the existing view definitions. In golang/go#60776, we see that the latter concern was poorly handled by the 'bestView' abstraction. Returning nil was not, in fact, best, because it resulted in the file being associated with the default AdHoc view, which doesn't know about modules. Refactor so that viewOfLocked chooses the most relevant view, even if none match build tags. This causes the orphaned file diagnostic to more accurately report that the file is excluded due to build tags. Fixes golang/go#60776 Change-Id: I40f236b3b63468faa1dfe6ae6aeac590c952594f Reviewed-on: https://go-review.googlesource.com/c/tools/+/588941 Reviewed-by: Alan Donovan <[email protected]> Auto-Submit: Robert Findley <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent bd624fd commit 6887e99

File tree

3 files changed

+44
-20
lines changed

3 files changed

+44
-20
lines changed

gopls/internal/cache/session.go

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -427,11 +427,12 @@ func (s *Session) SnapshotOf(ctx context.Context, uri protocol.DocumentURI) (*Sn
427427
// we have no view containing a file.
428428
var errNoViews = errors.New("no views")
429429

430-
// viewOfLocked wraps bestViewForURI, memoizing its result.
430+
// viewOfLocked evaluates the best view for uri, memoizing its result in
431+
// s.viewMap.
431432
//
432433
// Precondition: caller holds s.viewMu lock.
433434
//
434-
// May return (nil, nil).
435+
// May return (nil, nil) if no best view can be determined.
435436
func (s *Session) viewOfLocked(ctx context.Context, uri protocol.DocumentURI) (*View, error) {
436437
v, hit := s.viewMap[uri]
437438
if !hit {
@@ -440,10 +441,19 @@ func (s *Session) viewOfLocked(ctx context.Context, uri protocol.DocumentURI) (*
440441
if err != nil {
441442
return nil, err
442443
}
443-
v, err = bestView(ctx, s, fh, s.views)
444+
bestViews, err := BestViews(ctx, s, fh.URI(), s.views)
444445
if err != nil {
445446
return nil, err
446447
}
448+
v = matchingView(fh, bestViews)
449+
if v == nil && len(bestViews) > 0 {
450+
// If we have candidate views, but none of them matched the file's build
451+
// constraints, then we are still better off using one of them here.
452+
// Otherwise, logic may fall back to an inferior view, which lacks
453+
// relevant module information, leading to misleading diagnostics.
454+
// (as in golang/go#60776).
455+
v = bestViews[0]
456+
}
447457
if s.viewMap == nil {
448458
return nil, errors.New("session is shut down")
449459
}
@@ -517,12 +527,13 @@ checkFiles:
517527
if err != nil {
518528
return nil, err
519529
}
520-
def, err := bestView(ctx, fs, fh, defs)
530+
bestViews, err := BestViews(ctx, fs, fh.URI(), defs)
521531
if err != nil {
522532
// We should never call selectViewDefs with a cancellable context, so
523533
// this should never fail.
524534
return nil, bug.Errorf("failed to find best view for open file: %v", err)
525535
}
536+
def := matchingView(fh, bestViews)
526537
if def != nil {
527538
continue // file covered by an existing view
528539
}
@@ -646,30 +657,28 @@ func BestViews[V viewDefiner](ctx context.Context, fs file.Source, uri protocol.
646657
return bestViews, nil
647658
}
648659

649-
// bestView returns the best View or viewDefinition that contains the
650-
// given file, or (nil, nil) if no matching view is found.
651-
//
652-
// bestView only returns an error in the event of context cancellation.
660+
// matchingView returns the View or viewDefinition out of bestViews that
661+
// matches the given file's build constraints, or nil if no match is found.
653662
//
654663
// Making this function generic is convenient so that we can avoid mapping view
655664
// definitions back to views inside Session.DidModifyFiles, where performance
656665
// matters. It is, however, not the cleanest application of generics.
657666
//
658667
// Note: keep this function in sync with defineView.
659-
func bestView[V viewDefiner](ctx context.Context, fs file.Source, fh file.Handle, views []V) (V, error) {
668+
func matchingView[V viewDefiner](fh file.Handle, bestViews []V) V {
660669
var zero V
661-
bestViews, err := BestViews(ctx, fs, fh.URI(), views)
662-
if err != nil || len(bestViews) == 0 {
663-
return zero, err
670+
671+
if len(bestViews) == 0 {
672+
return zero
664673
}
665674

666675
content, err := fh.Content()
676+
667677
// Port matching doesn't apply to non-go files, or files that no longer exist.
668678
// Note that the behavior here on non-existent files shouldn't matter much,
669-
// since there will be a subsequent failure. But it is simpler to preserve
670-
// the invariant that bestView only fails on context cancellation.
679+
// since there will be a subsequent failure.
671680
if fileKind(fh) != file.Go || err != nil {
672-
return bestViews[0], nil
681+
return bestViews[0]
673682
}
674683

675684
// Find the first view that matches constraints.
@@ -680,11 +689,11 @@ func bestView[V viewDefiner](ctx context.Context, fs file.Source, fh file.Handle
680689
def := v.definition()
681690
viewPort := port{def.GOOS(), def.GOARCH()}
682691
if viewPort.matches(path, content) {
683-
return v, nil
692+
return v
684693
}
685694
}
686695

687-
return zero, nil // no view found
696+
return zero // no view found
688697
}
689698

690699
// updateViewLocked recreates the view with the given options.

gopls/internal/cache/snapshot.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1424,11 +1424,18 @@ searchOverlays:
14241424
if initialErr != nil {
14251425
msg = fmt.Sprintf("initialization failed: %v", initialErr.MainError)
14261426
} else if goMod, err := nearestModFile(ctx, fh.URI(), s); err == nil && goMod != "" {
1427+
// Check if the file's module should be loadable by considering both
1428+
// loaded modules and workspace modules. The former covers cases where
1429+
// the file is outside of a workspace folder. The latter covers cases
1430+
// where the file is inside a workspace module, but perhaps no packages
1431+
// were loaded for that module.
1432+
_, loadedMod := loadedModFiles[goMod]
1433+
_, workspaceMod := s.view.viewDefinition.workspaceModFiles[goMod]
14271434
// If we have a relevant go.mod file, check whether the file is orphaned
14281435
// due to its go.mod file being inactive. We could also offer a
1429-
// prescriptive diagnostic in the case that there is no go.mod file, but it
1430-
// is harder to be precise in that case, and less important.
1431-
if _, ok := loadedModFiles[goMod]; !ok {
1436+
// prescriptive diagnostic in the case that there is no go.mod file, but
1437+
// it is harder to be precise in that case, and less important.
1438+
if !(loadedMod || workspaceMod) {
14321439
modDir := filepath.Dir(goMod.Path())
14331440
viewDir := s.view.folder.Dir.Path()
14341441

gopls/internal/test/marker/testdata/zeroconfig/nested.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@ func _() {
3636
fmt.Println(undef) //@diag("undef", re"undefined|undeclared")
3737
}
3838

39+
-- mod1/a/tagged.go --
40+
//go:build tag1
41+
42+
// golang/go#60776: verify that we get an accurate error about build tags
43+
// here, rather than an inaccurate error suggesting to add a go.work
44+
// file (which won't help).
45+
package a //@diag(re`package (a)`, re`excluded due to its build tags`)
46+
3947
-- mod1/b/b.go --
4048
package b
4149

0 commit comments

Comments
 (0)