Skip to content

Commit

Permalink
gopls/internal/golang/completion: honor std symbol versions (imported)
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
adonovan committed Mar 8, 2024
1 parent 1f580da commit c67485c
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 10 deletions.
12 changes: 7 additions & 5 deletions gopls/internal/analysis/stdversion/stdversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func run(pass *analysis.Pass) (any, error) {
k := key{pkg, version}
disallowed, ok := memo[k]
if !ok {
disallowed = disallowedSymbols(pkg, version)
disallowed = DisallowedSymbols(pkg, version)
memo[k] = disallowed
}
return disallowed
Expand Down Expand Up @@ -107,10 +107,12 @@ func run(pass *analysis.Pass) (any, error) {
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.
Expand Down
59 changes: 54 additions & 5 deletions gopls/internal/golang/completion/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"context"
"fmt"
"go/ast"
"go/build"
"go/constant"
"go/parser"
"go/printer"
Expand All @@ -28,6 +29,7 @@ import (

"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"
Expand All @@ -37,6 +39,7 @@ import (
"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"
Expand Down Expand Up @@ -194,6 +197,11 @@ type completer struct {
// 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
Expand Down Expand Up @@ -238,6 +246,13 @@ type completer struct {
// 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

Expand All @@ -257,6 +272,21 @@ type completer struct {
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.
Expand Down Expand Up @@ -530,6 +560,12 @@ func Completion(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, p
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,
Expand All @@ -544,6 +580,7 @@ func Completion(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, p
filename: fh.URI().Path(),
tokFile: pgf.Tok,
file: pgf.File,
goversion: goversion,
path: path,
pos: pos,
seen: make(map[types.Object]bool),
Expand All @@ -564,11 +601,12 @@ func Completion(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, p
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)
Expand Down Expand Up @@ -1469,6 +1507,9 @@ func (c *completer) packageMembers(pkg *types.Package, score float64, imp *impor
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,
Expand Down Expand Up @@ -1506,6 +1547,11 @@ func (c *completer) methodsAndFields(typ types.Type, addressable bool, imp *impo
}

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,
Expand All @@ -1516,6 +1562,9 @@ func (c *completer) methodsAndFields(typ types.Type, addressable bool, imp *impo

// 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,
Expand Down
65 changes: 65 additions & 0 deletions gopls/internal/test/marker/testdata/completion/imported-std.txt
Original file line number Diff line number Diff line change
@@ -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")
Original file line number Diff line number Diff line change
@@ -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
Expand Down

0 comments on commit c67485c

Please sign in to comment.