Skip to content

Commit c67485c

Browse files
committed
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 <[email protected]> Reviewed-by: Robert Findley <[email protected]>
1 parent 1f580da commit c67485c

File tree

4 files changed

+128
-10
lines changed

4 files changed

+128
-10
lines changed

gopls/internal/analysis/stdversion/stdversion.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func run(pass *analysis.Pass) (any, error) {
6060
k := key{pkg, version}
6161
disallowed, ok := memo[k]
6262
if !ok {
63-
disallowed = disallowedSymbols(pkg, version)
63+
disallowed = DisallowedSymbols(pkg, version)
6464
memo[k] = disallowed
6565
}
6666
return disallowed
@@ -107,10 +107,12 @@ func run(pass *analysis.Pass) (any, error) {
107107
return nil, nil
108108
}
109109

110-
// disallowedSymbols computes the set of package-level symbols
111-
// exported by direct imports of pkg that are not available at the
112-
// specified version. The result maps each symbol to its minimum version.
113-
func disallowedSymbols(pkg *types.Package, version string) map[types.Object]string {
110+
// DisallowedSymbols computes the set of package-level symbols
111+
// exported by pkg that are not available at the specified version.
112+
// The result maps each symbol to its minimum version.
113+
//
114+
// (It is exported for use in gopls' completion.)
115+
func DisallowedSymbols(pkg *types.Package, version string) map[types.Object]string {
114116
disallowed := make(map[types.Object]string)
115117

116118
// Pass 1: package-level symbols.

gopls/internal/golang/completion/completion.go

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"context"
1111
"fmt"
1212
"go/ast"
13+
"go/build"
1314
"go/constant"
1415
"go/parser"
1516
"go/printer"
@@ -28,6 +29,7 @@ import (
2829

2930
"golang.org/x/sync/errgroup"
3031
"golang.org/x/tools/go/ast/astutil"
32+
"golang.org/x/tools/gopls/internal/analysis/stdversion"
3133
"golang.org/x/tools/gopls/internal/cache"
3234
"golang.org/x/tools/gopls/internal/cache/metadata"
3335
"golang.org/x/tools/gopls/internal/file"
@@ -37,6 +39,7 @@ import (
3739
"golang.org/x/tools/gopls/internal/settings"
3840
goplsastutil "golang.org/x/tools/gopls/internal/util/astutil"
3941
"golang.org/x/tools/gopls/internal/util/safetoken"
42+
"golang.org/x/tools/gopls/internal/util/slices"
4043
"golang.org/x/tools/gopls/internal/util/typesutil"
4144
"golang.org/x/tools/internal/aliases"
4245
"golang.org/x/tools/internal/event"
@@ -194,6 +197,11 @@ type completer struct {
194197
// file is the AST of the file associated with this completion request.
195198
file *ast.File
196199

200+
// goversion is the version of Go in force in the file, as
201+
// defined by x/tools/internal/versions. Empty if unknown.
202+
// TODO(adonovan): with go1.22+ it should always be known.
203+
goversion string
204+
197205
// (tokFile, pos) is the position at which the request was triggered.
198206
tokFile *token.File
199207
pos token.Pos
@@ -238,6 +246,13 @@ type completer struct {
238246
// for deep completions.
239247
methodSetCache map[methodSetKey]*types.MethodSet
240248

249+
// tooNewSymbolsCache is a cache of
250+
// [stdversion.DisallowedSymbols], recording for each std
251+
// package which of its exported symbols are too new for
252+
// the version of Go in force in the completion file.
253+
// (The value is the minimum version in the form "go1.%d".)
254+
tooNewSymbolsCache map[*types.Package]map[types.Object]string
255+
241256
// mapper converts the positions in the file from which the completion originated.
242257
mapper *protocol.Mapper
243258

@@ -257,6 +272,21 @@ type completer struct {
257272
scopes []*types.Scope
258273
}
259274

275+
// tooNew reports whether obj is a standard library symbol that is too
276+
// new for the specified Go version.
277+
func (c *completer) tooNew(obj types.Object) bool {
278+
pkg := obj.Pkg()
279+
if pkg == nil {
280+
return false // unsafe.Pointer or error.Error
281+
}
282+
disallowed, ok := c.tooNewSymbolsCache[pkg]
283+
if !ok {
284+
disallowed = stdversion.DisallowedSymbols(pkg, c.goversion)
285+
c.tooNewSymbolsCache[pkg] = disallowed
286+
}
287+
return disallowed[obj] != ""
288+
}
289+
260290
// funcInfo holds info about a function object.
261291
type funcInfo struct {
262292
// sig is the function declaration enclosing the position.
@@ -530,6 +560,12 @@ func Completion(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, p
530560
scopes := golang.CollectScopes(pkg.GetTypesInfo(), path, pos)
531561
scopes = append(scopes, pkg.GetTypes().Scope(), types.Universe)
532562

563+
var goversion string // "" => no version check
564+
// Prior go1.22, the behavior of FileVersion is not useful to us.
565+
if slices.Contains(build.Default.ReleaseTags, "go1.22") {
566+
goversion = versions.FileVersion(pkg.GetTypesInfo(), pgf.File) // may be ""
567+
}
568+
533569
opts := snapshot.Options()
534570
c := &completer{
535571
pkg: pkg,
@@ -544,6 +580,7 @@ func Completion(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, p
544580
filename: fh.URI().Path(),
545581
tokFile: pgf.Tok,
546582
file: pgf.File,
583+
goversion: goversion,
547584
path: path,
548585
pos: pos,
549586
seen: make(map[types.Object]bool),
@@ -564,11 +601,12 @@ func Completion(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, p
564601
completeFunctionCalls: opts.CompleteFunctionCalls,
565602
},
566603
// default to a matcher that always matches
567-
matcher: prefixMatcher(""),
568-
methodSetCache: make(map[methodSetKey]*types.MethodSet),
569-
mapper: pgf.Mapper,
570-
startTime: startTime,
571-
scopes: scopes,
604+
matcher: prefixMatcher(""),
605+
methodSetCache: make(map[methodSetKey]*types.MethodSet),
606+
tooNewSymbolsCache: make(map[*types.Package]map[types.Object]string),
607+
mapper: pgf.Mapper,
608+
startTime: startTime,
609+
scopes: scopes,
572610
}
573611

574612
ctx, cancel := context.WithCancel(ctx)
@@ -1469,6 +1507,9 @@ func (c *completer) packageMembers(pkg *types.Package, score float64, imp *impor
14691507
scope := pkg.Scope()
14701508
for _, name := range scope.Names() {
14711509
obj := scope.Lookup(name)
1510+
if c.tooNew(obj) {
1511+
continue // std symbol too new for file's Go version
1512+
}
14721513
cb(candidate{
14731514
obj: obj,
14741515
score: score,
@@ -1506,6 +1547,11 @@ func (c *completer) methodsAndFields(typ types.Type, addressable bool, imp *impo
15061547
}
15071548

15081549
for i := 0; i < mset.Len(); i++ {
1550+
obj := mset.At(i).Obj()
1551+
// to the other side of the cb() queue?
1552+
if c.tooNew(obj) {
1553+
continue // std method too new for file's Go version
1554+
}
15091555
cb(candidate{
15101556
obj: mset.At(i).Obj(),
15111557
score: stdScore,
@@ -1516,6 +1562,9 @@ func (c *completer) methodsAndFields(typ types.Type, addressable bool, imp *impo
15161562

15171563
// Add fields of T.
15181564
eachField(typ, func(v *types.Var) {
1565+
if c.tooNew(v) {
1566+
return // std field too new for file's Go version
1567+
}
15191568
cb(candidate{
15201569
obj: v,
15211570
score: stdScore - 0.01,
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
Test of imported completions respecting the effective Go version of the file.
2+
3+
(See "un-" prefixed file for same test of unimported completions.)
4+
5+
These symbols below were introduced in go1.20:
6+
7+
types.Satisfied
8+
ast.File.FileStart
9+
(*token.FileSet).RemoveFile
10+
11+
The underlying logic depends on versions.FileVersion, which only
12+
behaves correctly in go1.22. (When go1.22 is assured, we can remove
13+
the min_go flag but leave the test inputs unchanged.)
14+
15+
-- flags --
16+
-ignore_extra_diags -min_go=go1.22
17+
18+
-- go.mod --
19+
module example.com
20+
21+
go 1.19
22+
23+
-- a/a.go --
24+
package a
25+
26+
import "go/ast"
27+
import "go/token"
28+
import "go/types"
29+
30+
// package-level func
31+
var _ = types.Imple //@rankl("Imple", "Implements")
32+
var _ = types.Satis //@rankl("Satis", "!Satisfies")
33+
34+
// (Apparently we don't even offer completions of methods
35+
// of types from unimported packages, so the fact that
36+
// we don't implement std version filtering isn't evident.)
37+
38+
// field
39+
var _ = new(ast.File).Packa //@rankl("Packa", "Package")
40+
var _ = new(ast.File).FileS //@rankl("FileS", "!FileStart")
41+
42+
// method
43+
var _ = new(token.FileSet).Add //@rankl("Add", "AddFile")
44+
var _ = new(token.FileSet).Remove //@rankl("Remove", "!RemoveFile")
45+
46+
-- b/b.go --
47+
//go:build go1.20
48+
49+
package a
50+
51+
import "go/ast"
52+
import "go/token"
53+
import "go/types"
54+
55+
// package-level func
56+
var _ = types.Imple //@rankl("Imple", "Implements")
57+
var _ = types.Satis //@rankl("Satis", "Satisfies")
58+
59+
// field
60+
var _ = new(ast.File).Packa //@rankl("Packa", "Package")
61+
var _ = new(ast.File).FileS //@rankl("FileS", "FileStart")
62+
63+
// method
64+
var _ = new(token.FileSet).Add //@rankl("Add", "AddFile")
65+
var _ = new(token.FileSet).Remove //@rankl("Remove", "RemoveFile")

gopls/internal/test/marker/testdata/completion/unimported-std.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
Test of unimported completions respecting the effective Go version of the file.
22

3+
(See unprefixed file for same test of imported completions.)
4+
35
These symbols below were introduced in go1.20:
46

57
types.Satisfied

0 commit comments

Comments
 (0)