Skip to content

Commit 9c9e11f

Browse files
committed
gopls/internal/lsp/cache: load modules by dir, not module path
As uncovered in golang/go#59458, loading modules by <modulepath>/... can be incorrect in cases where the package pattern matches more than one module in the module graph. In such cases, it is possible that we will query modules that do not have a corresponding entry in the go.sum file (due to pruning). Fix this by loading <dir>/... for each module root directory. Fixes golang/go#59458 Change-Id: Ia163f4ab18847289941385e4eb9233906a4363c6 Reviewed-on: https://go-review.googlesource.com/c/tools/+/485840 Run-TryBot: Robert Findley <[email protected]> gopls-CI: kokoro <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Alan Donovan <[email protected]>
1 parent b35949e commit 9c9e11f

File tree

6 files changed

+111
-19
lines changed

6 files changed

+111
-19
lines changed

Diff for: gopls/internal/lsp/cache/load.go

+3-8
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,9 @@ func (s *snapshot) load(ctx context.Context, allowNetwork bool, scopes ...loadSc
7272
}
7373

7474
case moduleLoadScope:
75-
switch scope {
76-
case "std", "cmd":
77-
query = append(query, string(scope))
78-
default:
79-
modQuery := fmt.Sprintf("%s/...", scope)
80-
query = append(query, modQuery)
81-
moduleQueries[modQuery] = string(scope)
82-
}
75+
modQuery := fmt.Sprintf("%s%c...", scope.dir, filepath.Separator)
76+
query = append(query, modQuery)
77+
moduleQueries[modQuery] = string(scope.modulePath)
8378

8479
case viewLoadScope:
8580
// If we are outside of GOPATH, a module, or some other known

Diff for: gopls/internal/lsp/cache/pkg.go

+5-2
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,11 @@ type loadScope interface {
7474
type (
7575
fileLoadScope span.URI // load packages containing a file (including command-line-arguments)
7676
packageLoadScope string // load a specific package (the value is its PackageID)
77-
moduleLoadScope string // load packages in a specific module
78-
viewLoadScope span.URI // load the workspace
77+
moduleLoadScope struct {
78+
dir string // dir containing the go.mod file
79+
modulePath string // parsed module path
80+
}
81+
viewLoadScope span.URI // load the workspace
7982
)
8083

8184
// Implement the loadScope interface.

Diff for: gopls/internal/lsp/cache/view.go

+11-2
Original file line numberDiff line numberDiff line change
@@ -702,6 +702,12 @@ func (s *snapshot) loadWorkspace(ctx context.Context, firstAttempt bool) (loadEr
702702

703703
if len(s.workspaceModFiles) > 0 {
704704
for modURI := range s.workspaceModFiles {
705+
// Verify that the modfile is valid before trying to load it.
706+
//
707+
// TODO(rfindley): now that we no longer need to parse the modfile in
708+
// order to load scope, we could move these diagnostics to a more general
709+
// location where we diagnose problems with modfiles or the workspace.
710+
//
705711
// Be careful not to add context cancellation errors as critical module
706712
// errors.
707713
fh, err := s.ReadFile(ctx, modURI)
@@ -722,8 +728,11 @@ func (s *snapshot) loadWorkspace(ctx context.Context, firstAttempt bool) (loadEr
722728
addError(modURI, fmt.Errorf("no module path for %s", modURI))
723729
continue
724730
}
725-
path := parsed.File.Module.Mod.Path
726-
scopes = append(scopes, moduleLoadScope(path))
731+
moduleDir := filepath.Dir(modURI.Filename())
732+
// Previously, we loaded <modulepath>/... for each module path, but that
733+
// is actually incorrect when the pattern may match packages in more than
734+
// one module. See golang/go#59458 for more details.
735+
scopes = append(scopes, moduleLoadScope{dir: moduleDir, modulePath: parsed.File.Module.Mod.Path})
727736
}
728737
} else {
729738
scopes = append(scopes, viewLoadScope("LOAD_VIEW"))

Diff for: gopls/internal/regtest/diagnostics/diagnostics_test.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -1814,7 +1814,8 @@ func main() {}
18141814
Run(t, files, func(t *testing.T, env *Env) {
18151815
env.OpenFile("go.mod")
18161816
env.AfterChange(
1817-
LogMatching(protocol.Info, `.*query=\[builtin mod.com/...\].*`, 1, false),
1817+
// Check that we have only loaded "<dir>/..." once.
1818+
LogMatching(protocol.Info, `.*query=.*\.\.\..*`, 1, false),
18181819
)
18191820
})
18201821
}

Diff for: gopls/internal/regtest/workspace/fromenv_test.go

+17-6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
package workspace
66

77
import (
8+
"fmt"
9+
"path/filepath"
810
"testing"
911

1012
. "golang.org/x/tools/gopls/internal/lsp/regtest"
@@ -14,7 +16,12 @@ import (
1416
// Test that setting go.work via environment variables or settings works.
1517
func TestUseGoWorkOutsideTheWorkspace(t *testing.T) {
1618
testenv.NeedsGo1Point(t, 18)
17-
const files = `
19+
20+
// As discussed in
21+
// https://github.com/golang/go/issues/59458#issuecomment-1513794691, we must
22+
// use \-separated paths in go.work use directives for this test to work
23+
// correctly on windows.
24+
var files = fmt.Sprintf(`
1825
-- work/a/go.mod --
1926
module a.com
2027
@@ -41,15 +48,19 @@ package c
4148
go 1.18
4249
4350
use (
44-
$SANDBOX_WORKDIR/work/a
45-
$SANDBOX_WORKDIR/work/b
46-
$SANDBOX_WORKDIR/other/c
51+
%s
52+
%s
53+
%s
4754
)
48-
`
55+
`,
56+
filepath.Join("$SANDBOX_WORKDIR", "work", "a"),
57+
filepath.Join("$SANDBOX_WORKDIR", "work", "b"),
58+
filepath.Join("$SANDBOX_WORKDIR", "other", "c"),
59+
)
4960

5061
WithOptions(
5162
WorkspaceFolders("work"), // use a nested workspace dir, so that GOWORK is outside the workspace
52-
EnvVars{"GOWORK": "$SANDBOX_WORKDIR/config/go.work"},
63+
EnvVars{"GOWORK": filepath.Join("$SANDBOX_WORKDIR", "config", "go.work")},
5364
).Run(t, files, func(t *testing.T, env *Env) {
5465
// When we have an explicit GOWORK set, we should get a file watch request.
5566
env.OnceMet(

Diff for: gopls/internal/regtest/workspace/metadata_test.go

+73
Original file line numberDiff line numberDiff line change
@@ -179,3 +179,76 @@ func Hello() int {
179179
}
180180
})
181181
}
182+
183+
// Test for golang/go#59458. With lazy module loading, we may not need
184+
// transitively required modules.
185+
func TestNestedModuleLoading_Issue59458(t *testing.T) {
186+
testenv.NeedsGo1Point(t, 17) // needs lazy module loading
187+
188+
// In this test, module b.com/nested requires b.com/other, which in turn
189+
// requires b.com, but b.com/nested does not reach b.com through the package
190+
// graph. Therefore, b.com/nested does not need b.com on 1.17 and later,
191+
// thanks to graph pruning.
192+
//
193+
// We verify that we can load b.com/nested successfully. Previously, we
194+
// couldn't, because loading the pattern b.com/nested/... matched the module
195+
// b.com, which exists in the module graph but does not have a go.sum entry.
196+
197+
const proxy = `
198+
-- [email protected]/go.mod --
199+
module b.com
200+
201+
go 1.18
202+
-- [email protected]/b/b.go --
203+
package b
204+
205+
func Hello() {}
206+
207+
-- b.com/[email protected]/go.mod --
208+
module b.com/other
209+
210+
go 1.18
211+
212+
require b.com v1.2.3
213+
-- b.com/[email protected]/go.sun --
214+
b.com v1.2.3 h1:AGjCxWRJLUuJiZ21IUTByr9buoa6+B6Qh5LFhVLKpn4=
215+
-- b.com/[email protected]/bar/bar.go --
216+
package bar
217+
218+
import "b.com/b"
219+
220+
func _() {
221+
b.Hello()
222+
}
223+
-- b.com/[email protected]/foo/foo.go --
224+
package foo
225+
226+
const Foo = 0
227+
`
228+
229+
const files = `
230+
-- go.mod --
231+
module b.com/nested
232+
233+
go 1.18
234+
235+
require b.com/other v1.4.6
236+
-- go.sum --
237+
b.com/other v1.4.6 h1:pHXSzGsk6DamYXp9uRdDB9A/ZQqAN9it+JudU0sBf94=
238+
b.com/other v1.4.6/go.mod h1:T0TYuGdAHw4p/l0+1P/yhhYHfZRia7PaadNVDu58OWM=
239+
-- nested.go --
240+
package nested
241+
242+
import "b.com/other/foo"
243+
244+
const C = foo.Foo
245+
`
246+
WithOptions(
247+
ProxyFiles(proxy),
248+
).Run(t, files, func(t *testing.T, env *Env) {
249+
env.OnceMet(
250+
InitialWorkspaceLoad,
251+
NoDiagnostics(),
252+
)
253+
})
254+
}

0 commit comments

Comments
 (0)