1
2
3
4
5
6
7 package stdversion
8
9 import (
10 "go/ast"
11 "go/build"
12 "go/types"
13 "slices"
14
15 "golang.org/x/tools/go/analysis"
16 "golang.org/x/tools/go/analysis/passes/inspect"
17 "golang.org/x/tools/go/ast/inspector"
18 "golang.org/x/tools/internal/stdlib"
19 "golang.org/x/tools/internal/typesinternal"
20 "golang.org/x/tools/internal/versions"
21 )
22
23 const Doc = `report uses of too-new standard library symbols
24
25 The stdversion analyzer reports references to symbols in the standard
26 library that were introduced by a Go release higher than the one in
27 force in the referring file. (Recall that the file's Go version is
28 defined by the 'go' directive its module's go.mod file, or by a
29 "//go:build go1.X" build tag at the top of the file.)
30
31 The analyzer does not report a diagnostic for a reference to a "too
32 new" field or method of a type that is itself "too new", as this may
33 have false positives, for example if fields or methods are accessed
34 through a type alias that is guarded by a Go version constraint.
35 `
36
37 var Analyzer = &analysis.Analyzer{
38 Name: "stdversion",
39 Doc: Doc,
40 Requires: []*analysis.Analyzer{inspect.Analyzer},
41 URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/stdversion",
42 RunDespiteErrors: true,
43 Run: run,
44 }
45
46 func run(pass *analysis.Pass) (any, error) {
47
48
49
50 if !slices.Contains(build.Default.ReleaseTags, "go1.22") {
51 return nil, nil
52 }
53
54
55
56
57 pkgVersion := pass.Pkg.GoVersion()
58 if !versions.AtLeast(pkgVersion, "go1.21") {
59 return nil, nil
60 }
61
62
63
64 type key struct {
65 pkg *types.Package
66 version string
67 }
68 memo := make(map[key]map[types.Object]stdlib.Symbol)
69 disallowedSymbols := func(pkg *types.Package, version string) map[types.Object]stdlib.Symbol {
70 k := key{pkg, version}
71 disallowed, ok := memo[k]
72 if !ok {
73 disallowed = typesinternal.TooNewStdSymbols(pkg, version)
74 memo[k] = disallowed
75 }
76 return disallowed
77 }
78
79
80
81 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
82 nodeFilter := []ast.Node{
83 (*ast.File)(nil),
84 (*ast.Ident)(nil),
85 }
86 var fileVersion string
87 inspect.Preorder(nodeFilter, func(n ast.Node) {
88 switch n := n.(type) {
89 case *ast.File:
90 if ast.IsGenerated(n) {
91
92 fileVersion = ""
93 } else {
94 fileVersion = versions.Lang(versions.FileVersion(pass.TypesInfo, n))
95
96 }
97
98 case *ast.Ident:
99 if fileVersion != "" {
100 if obj, ok := pass.TypesInfo.Uses[n]; ok && obj.Pkg() != nil {
101 disallowed := disallowedSymbols(obj.Pkg(), fileVersion)
102 if sym, ok := disallowed[origin(obj)]; ok {
103 pass.ReportRangef(n, "%s.%s requires %v or later (%s is %s)",
104 obj.Pkg().Name(), sym.Name,
105 sym.Version,
106 cond(fileVersion != pkgVersion, "file", "module"),
107 fileVersion)
108 }
109 }
110 }
111 }
112 })
113 return nil, nil
114 }
115
116
117 func origin(obj types.Object) types.Object {
118 switch obj := obj.(type) {
119 case *types.Var:
120 return obj.Origin()
121 case *types.Func:
122 return obj.Origin()
123 case *types.TypeName:
124 if named, ok := obj.Type().(*types.Named); ok {
125 return named.Origin().Obj()
126 }
127 }
128 return obj
129 }
130
131 func cond[T any](cond bool, t, f T) T {
132 if cond {
133 return t
134 } else {
135 return f
136 }
137 }
138
View as plain text