Skip to content

Commit c1eaf76

Browse files
committed
gopls/internal/analysis/stdversion: set RunDespiteErrors
This change enables RunDespiteErrors, after auditing the code. This should give more timely feedback while editing. Also, it moves the vet/gopls common code (DisallowedSymbols) to typesinternal.TooNewStdSymbols, out of the gopls module, in anticipation of adding this analyzer to vet. Updates golang/go#46136 Change-Id: I8d742bf543c9146376d43ae94f7adae3b453e471 Reviewed-on: https://go-review.googlesource.com/c/tools/+/570138 LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Alan Donovan <[email protected]> Reviewed-by: Robert Findley <[email protected]>
1 parent c67485c commit c1eaf76

File tree

6 files changed

+120
-94
lines changed

6 files changed

+120
-94
lines changed

gopls/doc/analyzers.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,8 @@ have false positives, for example if fields or methods are accessed
739739
through a type alias that is guarded by a Go version constraint.
740740

741741

742+
[Full documentation](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/stdversion)
743+
742744
**Enabled by default.**
743745

744746
## **stringintconv**

gopls/internal/analysis/stdversion/stdversion.go

Lines changed: 23 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@ import (
1515
"golang.org/x/tools/go/analysis"
1616
"golang.org/x/tools/go/analysis/passes/inspect"
1717
"golang.org/x/tools/go/ast/inspector"
18-
"golang.org/x/tools/gopls/internal/util/slices"
19-
"golang.org/x/tools/internal/stdlib"
18+
"golang.org/x/tools/internal/typesinternal"
2019
"golang.org/x/tools/internal/versions"
2120
)
2221

@@ -35,32 +34,34 @@ through a type alias that is guarded by a Go version constraint.
3534
`
3635

3736
var Analyzer = &analysis.Analyzer{
38-
Name: "stdversion",
39-
Doc: Doc,
40-
Requires: []*analysis.Analyzer{inspect.Analyzer},
41-
Run: run,
37+
Name: "stdversion",
38+
Doc: Doc,
39+
Requires: []*analysis.Analyzer{inspect.Analyzer},
40+
URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/stdversion",
41+
RunDespiteErrors: true,
42+
Run: run,
4243
}
4344

4445
func run(pass *analysis.Pass) (any, error) {
4546
// Prior to go1.22, versions.FileVersion returns only the
4647
// toolchain version, which is of no use to us, so
4748
// disable this analyzer on earlier versions.
48-
if !slices.Contains(build.Default.ReleaseTags, "go1.22") {
49+
if !slicesContains(build.Default.ReleaseTags, "go1.22") {
4950
return nil, nil
5051
}
5152

52-
// disallowedSymbolsMemo returns the set of standard library symbols
53+
// disallowedSymbols returns the set of standard library symbols
5354
// in a given package that are disallowed at the specified Go version.
5455
type key struct {
5556
pkg *types.Package
5657
version string
5758
}
5859
memo := make(map[key]map[types.Object]string) // records symbol's minimum Go version
59-
disallowedSymbolsMemo := func(pkg *types.Package, version string) map[types.Object]string {
60+
disallowedSymbols := func(pkg *types.Package, version string) map[types.Object]string {
6061
k := key{pkg, version}
6162
disallowed, ok := memo[k]
6263
if !ok {
63-
disallowed = DisallowedSymbols(pkg, version)
64+
disallowed = typesinternal.TooNewStdSymbols(pkg, version)
6465
memo[k] = disallowed
6566
}
6667
return disallowed
@@ -91,7 +92,7 @@ func run(pass *analysis.Pass) (any, error) {
9192
case *ast.Ident:
9293
if fileVersion != "" {
9394
if obj, ok := pass.TypesInfo.Uses[n]; ok && obj.Pkg() != nil {
94-
disallowed := disallowedSymbolsMemo(obj.Pkg(), fileVersion)
95+
disallowed := disallowedSymbols(obj.Pkg(), fileVersion)
9596
if minVersion, ok := disallowed[origin(obj)]; ok {
9697
noun := "module"
9798
if fileVersion != pkgVersion {
@@ -107,86 +108,7 @@ func run(pass *analysis.Pass) (any, error) {
107108
return nil, nil
108109
}
109110

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 {
116-
disallowed := make(map[types.Object]string)
117-
118-
// Pass 1: package-level symbols.
119-
symbols := stdlib.PackageSymbols[pkg.Path()]
120-
for _, sym := range symbols {
121-
symver := sym.Version.String()
122-
if versions.Before(version, symver) {
123-
switch sym.Kind {
124-
case stdlib.Func, stdlib.Var, stdlib.Const, stdlib.Type:
125-
disallowed[pkg.Scope().Lookup(sym.Name)] = symver
126-
}
127-
}
128-
}
129-
130-
// Pass 2: fields and methods.
131-
//
132-
// We allow fields and methods if their associated type is
133-
// disallowed, as otherwise we would report false positives
134-
// for compatibility shims. Consider:
135-
//
136-
// //go:build go1.22
137-
// type T struct { F std.Real } // correct new API
138-
//
139-
// //go:build !go1.22
140-
// type T struct { F fake } // shim
141-
// type fake struct { ... }
142-
// func (fake) M () {}
143-
//
144-
// These alternative declarations of T use either the std.Real
145-
// type, introduced in go1.22, or a fake type, for the field
146-
// F. (The fakery could be arbitrarily deep, involving more
147-
// nested fields and methods than are shown here.) Clients
148-
// that use the compatibility shim T will compile with any
149-
// version of go, whether older or newer than go1.22, but only
150-
// the newer version will use the std.Real implementation.
151-
//
152-
// Now consider a reference to method M in new(T).F.M() in a
153-
// module that requires a minimum of go1.21. The analysis may
154-
// occur using a version of Go higher than 1.21, selecting the
155-
// first version of T, so the method M is Real.M. This would
156-
// spuriously cause the analyzer to report a reference to a
157-
// too-new symbol even though this expression compiles just
158-
// fine (with the fake implementation) using go1.21.
159-
for _, sym := range symbols {
160-
symVersion := sym.Version.String()
161-
if !versions.Before(version, symVersion) {
162-
continue // allowed
163-
}
164-
165-
var obj types.Object
166-
switch sym.Kind {
167-
case stdlib.Field:
168-
typename, name := sym.SplitField()
169-
t := pkg.Scope().Lookup(typename)
170-
if disallowed[t] == "" {
171-
obj, _, _ = types.LookupFieldOrMethod(t.Type(), false, pkg, name)
172-
}
173-
174-
case stdlib.Method:
175-
ptr, recvname, name := sym.SplitMethod()
176-
t := pkg.Scope().Lookup(recvname)
177-
if disallowed[t] == "" {
178-
obj, _, _ = types.LookupFieldOrMethod(t.Type(), ptr, pkg, name)
179-
}
180-
}
181-
if obj != nil {
182-
disallowed[obj] = symVersion
183-
}
184-
}
185-
186-
return disallowed
187-
}
188-
189-
// Reduced from ../../golang/util.go. Good enough for now.
111+
// Reduced from x/tools/gopls/internal/golang/util.go. Good enough for now.
190112
// TODO(adonovan): use ast.IsGenerated in go1.21.
191113
func isGenerated(f *ast.File) bool {
192114
for _, group := range f.Comments {
@@ -218,3 +140,13 @@ func origin(obj types.Object) types.Object {
218140
}
219141
return obj
220142
}
143+
144+
// TODO(adonovan): use go1.21 slices.Contains.
145+
func slicesContains[S ~[]E, E comparable](slice S, x E) bool {
146+
for _, elem := range slice {
147+
if elem == x {
148+
return true
149+
}
150+
}
151+
return false
152+
}

gopls/internal/analysis/stdversion/testdata/test.txtar

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ func _() {
7979
new(types.Package).GoVersion() // want `types.GoVersion requires go1.21 or later \(module is go1.20\)`
8080
}
8181

82+
invalid syntax // exercise RunDespiteErrors
83+
8284
-- sub/tagged.go --
8385
//go:build go1.21
8486

@@ -99,3 +101,4 @@ func _() {
99101

100102
new(types.Package).GoVersion() // ok: file requires go1.21
101103
}
104+

gopls/internal/golang/completion/completion.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ import (
2929

3030
"golang.org/x/sync/errgroup"
3131
"golang.org/x/tools/go/ast/astutil"
32-
"golang.org/x/tools/gopls/internal/analysis/stdversion"
3332
"golang.org/x/tools/gopls/internal/cache"
3433
"golang.org/x/tools/gopls/internal/cache/metadata"
3534
"golang.org/x/tools/gopls/internal/file"
@@ -247,7 +246,7 @@ type completer struct {
247246
methodSetCache map[methodSetKey]*types.MethodSet
248247

249248
// tooNewSymbolsCache is a cache of
250-
// [stdversion.DisallowedSymbols], recording for each std
249+
// [typesinternal.TooNewStdSymbols], recording for each std
251250
// package which of its exported symbols are too new for
252251
// the version of Go in force in the completion file.
253252
// (The value is the minimum version in the form "go1.%d".)
@@ -281,7 +280,7 @@ func (c *completer) tooNew(obj types.Object) bool {
281280
}
282281
disallowed, ok := c.tooNewSymbolsCache[pkg]
283282
if !ok {
284-
disallowed = stdversion.DisallowedSymbols(pkg, c.goversion)
283+
disallowed = typesinternal.TooNewStdSymbols(pkg, c.goversion)
285284
c.tooNewSymbolsCache[pkg] = disallowed
286285
}
287286
return disallowed[obj] != ""

gopls/internal/settings/api_json.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/typesinternal/toonew.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Copyright 2024 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package typesinternal
6+
7+
import (
8+
"go/types"
9+
10+
"golang.org/x/tools/internal/stdlib"
11+
"golang.org/x/tools/internal/versions"
12+
)
13+
14+
// TooNewStdSymbols computes the set of package-level symbols
15+
// exported by pkg that are not available at the specified version.
16+
// The result maps each symbol to its minimum version.
17+
//
18+
// The pkg is allowed to contain type errors.
19+
func TooNewStdSymbols(pkg *types.Package, version string) map[types.Object]string {
20+
disallowed := make(map[types.Object]string)
21+
22+
// Pass 1: package-level symbols.
23+
symbols := stdlib.PackageSymbols[pkg.Path()]
24+
for _, sym := range symbols {
25+
symver := sym.Version.String()
26+
if versions.Before(version, symver) {
27+
switch sym.Kind {
28+
case stdlib.Func, stdlib.Var, stdlib.Const, stdlib.Type:
29+
disallowed[pkg.Scope().Lookup(sym.Name)] = symver
30+
}
31+
}
32+
}
33+
34+
// Pass 2: fields and methods.
35+
//
36+
// We allow fields and methods if their associated type is
37+
// disallowed, as otherwise we would report false positives
38+
// for compatibility shims. Consider:
39+
//
40+
// //go:build go1.22
41+
// type T struct { F std.Real } // correct new API
42+
//
43+
// //go:build !go1.22
44+
// type T struct { F fake } // shim
45+
// type fake struct { ... }
46+
// func (fake) M () {}
47+
//
48+
// These alternative declarations of T use either the std.Real
49+
// type, introduced in go1.22, or a fake type, for the field
50+
// F. (The fakery could be arbitrarily deep, involving more
51+
// nested fields and methods than are shown here.) Clients
52+
// that use the compatibility shim T will compile with any
53+
// version of go, whether older or newer than go1.22, but only
54+
// the newer version will use the std.Real implementation.
55+
//
56+
// Now consider a reference to method M in new(T).F.M() in a
57+
// module that requires a minimum of go1.21. The analysis may
58+
// occur using a version of Go higher than 1.21, selecting the
59+
// first version of T, so the method M is Real.M. This would
60+
// spuriously cause the analyzer to report a reference to a
61+
// too-new symbol even though this expression compiles just
62+
// fine (with the fake implementation) using go1.21.
63+
for _, sym := range symbols {
64+
symVersion := sym.Version.String()
65+
if !versions.Before(version, symVersion) {
66+
continue // allowed
67+
}
68+
69+
var obj types.Object
70+
switch sym.Kind {
71+
case stdlib.Field:
72+
typename, name := sym.SplitField()
73+
if t := pkg.Scope().Lookup(typename); t != nil && disallowed[t] == "" {
74+
obj, _, _ = types.LookupFieldOrMethod(t.Type(), false, pkg, name)
75+
}
76+
77+
case stdlib.Method:
78+
ptr, recvname, name := sym.SplitMethod()
79+
if t := pkg.Scope().Lookup(recvname); t != nil && disallowed[t] == "" {
80+
obj, _, _ = types.LookupFieldOrMethod(t.Type(), ptr, pkg, name)
81+
}
82+
}
83+
if obj != nil {
84+
disallowed[obj] = symVersion
85+
}
86+
}
87+
88+
return disallowed
89+
}

0 commit comments

Comments
 (0)