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/typesinternal"
19 "golang.org/x/tools/internal/versions"
20 )
21
22 const Doc = `report uses of too-new standard library symbols
23
24 The stdversion analyzer reports references to symbols in the standard
25 library that were introduced by a Go release higher than the one in
26 force in the referring file. (Recall that the file's Go version is
27 defined by the 'go' directive its module's go.mod file, or by a
28 "//go:build go1.X" build tag at the top of the file.)
29
30 The analyzer does not report a diagnostic for a reference to a "too
31 new" field or method of a type that is itself "too new", as this may
32 have false positives, for example if fields or methods are accessed
33 through a type alias that is guarded by a Go version constraint.
34 `
35
36 var Analyzer = &analysis.Analyzer{
37 Name: "stdversion",
38 Doc: Doc,
39 Requires: []*analysis.Analyzer{inspect.Analyzer},
40 URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/stdversion",
41 RunDespiteErrors: true,
42 Run: run,
43 }
44
45 func run(pass *analysis.Pass) (any, error) {
46
47
48
49 if !slices.Contains(build.Default.ReleaseTags, "go1.22") {
50 return nil, nil
51 }
52
53
54
55
56 pkgVersion := pass.Pkg.GoVersion()
57 if !versions.AtLeast(pkgVersion, "go1.21") {
58 return nil, nil
59 }
60
61
62
63 type key struct {
64 pkg *types.Package
65 version string
66 }
67 memo := make(map[key]map[types.Object]string)
68 disallowedSymbols := func(pkg *types.Package, version string) map[types.Object]string {
69 k := key{pkg, version}
70 disallowed, ok := memo[k]
71 if !ok {
72 disallowed = typesinternal.TooNewStdSymbols(pkg, version)
73 memo[k] = disallowed
74 }
75 return disallowed
76 }
77
78
79
80 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
81 nodeFilter := []ast.Node{
82 (*ast.File)(nil),
83 (*ast.Ident)(nil),
84 }
85 var fileVersion string
86 inspect.Preorder(nodeFilter, func(n ast.Node) {
87 switch n := n.(type) {
88 case *ast.File:
89 if ast.IsGenerated(n) {
90
91 fileVersion = ""
92 } else {
93 fileVersion = versions.Lang(versions.FileVersion(pass.TypesInfo, n))
94
95 }
96
97 case *ast.Ident:
98 if fileVersion != "" {
99 if obj, ok := pass.TypesInfo.Uses[n]; ok && obj.Pkg() != nil {
100 disallowed := disallowedSymbols(obj.Pkg(), fileVersion)
101 if minVersion, ok := disallowed[origin(obj)]; ok {
102 noun := "module"
103 if fileVersion != pkgVersion {
104 noun = "file"
105 }
106 pass.ReportRangef(n, "%s.%s requires %v or later (%s is %s)",
107 obj.Pkg().Name(), obj.Name(), minVersion, noun, 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
View as plain text