gopls/internal/golang/completion: honor std symbol versions (imported)
This change is the counterpart to CL 569435 for completions
in imported packages.
Updates golang/go#46136
Change-Id: I57011897c395d37a89a8e3a99e8c3511de017ad3
Reviewed-on: https://go-review.googlesource.com/c/tools/+/569796
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Robert Findley <rfindley@google.com>
diff --git a/gopls/internal/analysis/stdversion/stdversion.go b/gopls/internal/analysis/stdversion/stdversion.go
index 5166837..3e165ff 100644
--- a/gopls/internal/analysis/stdversion/stdversion.go
+++ b/gopls/internal/analysis/stdversion/stdversion.go
@@ -60,7 +60,7 @@
k := key{pkg, version}
disallowed, ok := memo[k]
if !ok {
- disallowed = disallowedSymbols(pkg, version)
+ disallowed = DisallowedSymbols(pkg, version)
memo[k] = disallowed
}
return disallowed
@@ -107,10 +107,12 @@
return nil, nil
}
-// disallowedSymbols computes the set of package-level symbols
-// exported by direct imports of pkg that are not available at the
-// specified version. The result maps each symbol to its minimum version.
-func disallowedSymbols(pkg *types.Package, version string) map[types.Object]string {
+// DisallowedSymbols computes the set of package-level symbols
+// exported by pkg that are not available at the specified version.
+// The result maps each symbol to its minimum version.
+//
+// (It is exported for use in gopls' completion.)
+func DisallowedSymbols(pkg *types.Package, version string) map[types.Object]string {
disallowed := make(map[types.Object]string)
// Pass 1: package-level symbols.
diff --git a/gopls/internal/golang/completion/completion.go b/gopls/internal/golang/completion/completion.go
index 6f3b7c1..fe66afa 100644
--- a/gopls/internal/golang/completion/completion.go
+++ b/gopls/internal/golang/completion/completion.go
@@ -10,6 +10,7 @@
"context"
"fmt"
"go/ast"
+ "go/build"
"go/constant"
"go/parser"
"go/printer"
@@ -28,6 +29,7 @@
"golang.org/x/sync/errgroup"
"golang.org/x/tools/go/ast/astutil"
+ "golang.org/x/tools/gopls/internal/analysis/stdversion"
"golang.org/x/tools/gopls/internal/cache"
"golang.org/x/tools/gopls/internal/cache/metadata"
"golang.org/x/tools/gopls/internal/file"
@@ -37,6 +39,7 @@
"golang.org/x/tools/gopls/internal/settings"
goplsastutil "golang.org/x/tools/gopls/internal/util/astutil"
"golang.org/x/tools/gopls/internal/util/safetoken"
+ "golang.org/x/tools/gopls/internal/util/slices"
"golang.org/x/tools/gopls/internal/util/typesutil"
"golang.org/x/tools/internal/aliases"
"golang.org/x/tools/internal/event"
@@ -194,6 +197,11 @@
// file is the AST of the file associated with this completion request.
file *ast.File
+ // goversion is the version of Go in force in the file, as
+ // defined by x/tools/internal/versions. Empty if unknown.
+ // TODO(adonovan): with go1.22+ it should always be known.
+ goversion string
+
// (tokFile, pos) is the position at which the request was triggered.
tokFile *token.File
pos token.Pos
@@ -238,6 +246,13 @@
// for deep completions.
methodSetCache map[methodSetKey]*types.MethodSet
+ // tooNewSymbolsCache is a cache of
+ // [stdversion.DisallowedSymbols], recording for each std
+ // package which of its exported symbols are too new for
+ // the version of Go in force in the completion file.
+ // (The value is the minimum version in the form "go1.%d".)
+ tooNewSymbolsCache map[*types.Package]map[types.Object]string
+
// mapper converts the positions in the file from which the completion originated.
mapper *protocol.Mapper
@@ -257,6 +272,21 @@
scopes []*types.Scope
}
+// tooNew reports whether obj is a standard library symbol that is too
+// new for the specified Go version.
+func (c *completer) tooNew(obj types.Object) bool {
+ pkg := obj.Pkg()
+ if pkg == nil {
+ return false // unsafe.Pointer or error.Error
+ }
+ disallowed, ok := c.tooNewSymbolsCache[pkg]
+ if !ok {
+ disallowed = stdversion.DisallowedSymbols(pkg, c.goversion)
+ c.tooNewSymbolsCache[pkg] = disallowed
+ }
+ return disallowed[obj] != ""
+}
+
// funcInfo holds info about a function object.
type funcInfo struct {
// sig is the function declaration enclosing the position.
@@ -530,6 +560,12 @@
scopes := golang.CollectScopes(pkg.GetTypesInfo(), path, pos)
scopes = append(scopes, pkg.GetTypes().Scope(), types.Universe)
+ var goversion string // "" => no version check
+ // Prior go1.22, the behavior of FileVersion is not useful to us.
+ if slices.Contains(build.Default.ReleaseTags, "go1.22") {
+ goversion = versions.FileVersion(pkg.GetTypesInfo(), pgf.File) // may be ""
+ }
+
opts := snapshot.Options()
c := &completer{
pkg: pkg,
@@ -544,6 +580,7 @@
filename: fh.URI().Path(),
tokFile: pgf.Tok,
file: pgf.File,
+ goversion: goversion,
path: path,
pos: pos,
seen: make(map[types.Object]bool),
@@ -564,11 +601,12 @@
completeFunctionCalls: opts.CompleteFunctionCalls,
},
// default to a matcher that always matches
- matcher: prefixMatcher(""),
- methodSetCache: make(map[methodSetKey]*types.MethodSet),
- mapper: pgf.Mapper,
- startTime: startTime,
- scopes: scopes,
+ matcher: prefixMatcher(""),
+ methodSetCache: make(map[methodSetKey]*types.MethodSet),
+ tooNewSymbolsCache: make(map[*types.Package]map[types.Object]string),
+ mapper: pgf.Mapper,
+ startTime: startTime,
+ scopes: scopes,
}
ctx, cancel := context.WithCancel(ctx)
@@ -1469,6 +1507,9 @@
scope := pkg.Scope()
for _, name := range scope.Names() {
obj := scope.Lookup(name)
+ if c.tooNew(obj) {
+ continue // std symbol too new for file's Go version
+ }
cb(candidate{
obj: obj,
score: score,
@@ -1506,6 +1547,11 @@
}
for i := 0; i < mset.Len(); i++ {
+ obj := mset.At(i).Obj()
+ // to the other side of the cb() queue?
+ if c.tooNew(obj) {
+ continue // std method too new for file's Go version
+ }
cb(candidate{
obj: mset.At(i).Obj(),
score: stdScore,
@@ -1516,6 +1562,9 @@
// Add fields of T.
eachField(typ, func(v *types.Var) {
+ if c.tooNew(v) {
+ return // std field too new for file's Go version
+ }
cb(candidate{
obj: v,
score: stdScore - 0.01,
diff --git a/gopls/internal/test/marker/testdata/completion/imported-std.txt b/gopls/internal/test/marker/testdata/completion/imported-std.txt
new file mode 100644
index 0000000..436bbba
--- /dev/null
+++ b/gopls/internal/test/marker/testdata/completion/imported-std.txt
@@ -0,0 +1,65 @@
+Test of imported completions respecting the effective Go version of the file.
+
+(See "un-" prefixed file for same test of unimported completions.)
+
+These symbols below were introduced in go1.20:
+
+ types.Satisfied
+ ast.File.FileStart
+ (*token.FileSet).RemoveFile
+
+The underlying logic depends on versions.FileVersion, which only
+behaves correctly in go1.22. (When go1.22 is assured, we can remove
+the min_go flag but leave the test inputs unchanged.)
+
+-- flags --
+-ignore_extra_diags -min_go=go1.22
+
+-- go.mod --
+module example.com
+
+go 1.19
+
+-- a/a.go --
+package a
+
+import "go/ast"
+import "go/token"
+import "go/types"
+
+// package-level func
+var _ = types.Imple //@rankl("Imple", "Implements")
+var _ = types.Satis //@rankl("Satis", "!Satisfies")
+
+// (Apparently we don't even offer completions of methods
+// of types from unimported packages, so the fact that
+// we don't implement std version filtering isn't evident.)
+
+// field
+var _ = new(ast.File).Packa //@rankl("Packa", "Package")
+var _ = new(ast.File).FileS //@rankl("FileS", "!FileStart")
+
+// method
+var _ = new(token.FileSet).Add //@rankl("Add", "AddFile")
+var _ = new(token.FileSet).Remove //@rankl("Remove", "!RemoveFile")
+
+-- b/b.go --
+//go:build go1.20
+
+package a
+
+import "go/ast"
+import "go/token"
+import "go/types"
+
+// package-level func
+var _ = types.Imple //@rankl("Imple", "Implements")
+var _ = types.Satis //@rankl("Satis", "Satisfies")
+
+// field
+var _ = new(ast.File).Packa //@rankl("Packa", "Package")
+var _ = new(ast.File).FileS //@rankl("FileS", "FileStart")
+
+// method
+var _ = new(token.FileSet).Add //@rankl("Add", "AddFile")
+var _ = new(token.FileSet).Remove //@rankl("Remove", "RemoveFile")
diff --git a/gopls/internal/test/marker/testdata/completion/unimported-std.txt b/gopls/internal/test/marker/testdata/completion/unimported-std.txt
index b99e85a..ce07546 100644
--- a/gopls/internal/test/marker/testdata/completion/unimported-std.txt
+++ b/gopls/internal/test/marker/testdata/completion/unimported-std.txt
@@ -1,5 +1,7 @@
Test of unimported completions respecting the effective Go version of the file.
+(See unprefixed file for same test of imported completions.)
+
These symbols below were introduced in go1.20:
types.Satisfied