1 package sharedcheck
2 3 import (
4 "fmt"
5 "go/ast"
6 "go/token"
7 "go/types"
8 9 "honnef.co/go/tools/analysis/code"
10 "honnef.co/go/tools/analysis/edit"
11 "honnef.co/go/tools/analysis/facts/generated"
12 "honnef.co/go/tools/analysis/facts/tokenfile"
13 "honnef.co/go/tools/analysis/report"
14 "honnef.co/go/tools/go/ast/astutil"
15 "honnef.co/go/tools/go/ir"
16 "honnef.co/go/tools/go/ir/irutil"
17 "honnef.co/go/tools/go/types/typeutil"
18 "honnef.co/go/tools/internal/passes/buildir"
19 20 "golang.org/x/tools/go/analysis"
21 "golang.org/x/tools/go/analysis/passes/inspect"
22 )
23 24 func CheckRangeStringRunes(pass *analysis.Pass) (interface{}, error) {
25 for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
26 cb := func(node ast.Node) bool {
27 rng, ok := node.(*ast.RangeStmt)
28 if !ok || !astutil.IsBlank(rng.Key) {
29 return true
30 }
31 32 v, _ := fn.ValueForExpr(rng.X)
33 34 // Check that we're converting from string to []rune
35 val, _ := v.(*ir.Convert)
36 if val == nil {
37 return true
38 }
39 Tsrc, ok := typeutil.CoreType(val.X.Type()).(*types.Basic)
40 if !ok || Tsrc.Kind() != types.String {
41 return true
42 }
43 Tdst, ok := typeutil.CoreType(val.Type()).(*types.Slice)
44 if !ok {
45 return true
46 }
47 TdstElem, ok := types.Unalias(Tdst.Elem()).(*types.Basic)
48 if !ok || TdstElem.Kind() != types.Int32 {
49 return true
50 }
51 52 // Check that the result of the conversion is only used to
53 // range over
54 refs := val.Referrers()
55 if refs == nil {
56 return true
57 }
58 59 // Expect two refs: one for obtaining the length of the slice,
60 // one for accessing the elements
61 if len(irutil.FilterDebug(*refs)) != 2 {
62 // TODO(dh): right now, we check that only one place
63 // refers to our slice. This will miss cases such as
64 // ranging over the slice twice. Ideally, we'd ensure that
65 // the slice is only used for ranging over (without
66 // accessing the key), but that is harder to do because in
67 // IR form, ranging over a slice looks like an ordinary
68 // loop with index increments and slice accesses. We'd
69 // have to look at the associated AST node to check that
70 // it's a range statement.
71 return true
72 }
73 74 pass.Reportf(rng.Pos(), "should range over string, not []rune(string)")
75 76 return true
77 }
78 if source := fn.Source(); source != nil {
79 ast.Inspect(source, cb)
80 }
81 }
82 return nil, nil
83 }
84 85 // RedundantTypeInDeclarationChecker returns a checker that flags variable declarations with redundantly specified types.
86 // That is, it flags 'var v T = e' where e's type is identical to T and 'var v = e' (or 'v := e') would have the same effect.
87 //
88 // It does not flag variables under the following conditions, to reduce the number of false positives:
89 // - global variables – these often specify types to aid godoc
90 // - files that use cgo – cgo code generation and pointer checking emits redundant types
91 //
92 // It does not flag variables under the following conditions, unless flagHelpfulTypes is true, to reduce the number of noisy positives:
93 // - packages that import syscall or unsafe – these sometimes use this form of assignment to make sure types are as expected
94 // - variables named the blank identifier – a pattern used to confirm the types of variables
95 // - untyped expressions on the rhs – the explicitness might aid readability
96 func RedundantTypeInDeclarationChecker(verb string, flagHelpfulTypes bool) *analysis.Analyzer {
97 fn := func(pass *analysis.Pass) (interface{}, error) {
98 eval := func(expr ast.Expr) (types.TypeAndValue, error) {
99 info := &types.Info{
100 Types: map[ast.Expr]types.TypeAndValue{},
101 }
102 err := types.CheckExpr(pass.Fset, pass.Pkg, expr.Pos(), expr, info)
103 return info.Types[expr], err
104 }
105 106 if !flagHelpfulTypes {
107 // Don't look at code in low-level packages
108 for _, imp := range pass.Pkg.Imports() {
109 if imp.Path() == "syscall" || imp.Path() == "unsafe" {
110 return nil, nil
111 }
112 }
113 }
114 115 fn := func(node ast.Node) {
116 decl := node.(*ast.GenDecl)
117 if decl.Tok != token.VAR {
118 return
119 }
120 121 gen, _ := code.Generator(pass, decl.Pos())
122 if gen == generated.Cgo {
123 // TODO(dh): remove this exception once we can use UsesCgo
124 return
125 }
126 127 // Delay looking up parent AST nodes until we have to
128 checkedDecl := false
129 130 specLoop:
131 for _, spec := range decl.Specs {
132 spec := spec.(*ast.ValueSpec)
133 if spec.Type == nil {
134 continue
135 }
136 if len(spec.Names) != len(spec.Values) {
137 continue
138 }
139 Tlhs := pass.TypesInfo.TypeOf(spec.Type)
140 for i, v := range spec.Values {
141 if !flagHelpfulTypes && spec.Names[i].Name == "_" {
142 continue specLoop
143 }
144 Trhs := pass.TypesInfo.TypeOf(v)
145 if !types.Identical(Tlhs, Trhs) {
146 continue specLoop
147 }
148 149 // Some expressions are untyped and get converted to the lhs type implicitly.
150 // This applies to untyped constants, shift operations with an untyped lhs, and possibly others.
151 //
152 // Check if the type is truly redundant, i.e. if the type on the lhs doesn't match the default type of the untyped constant.
153 tv, err := eval(v)
154 if err != nil {
155 panic(err)
156 }
157 if b, ok := types.Unalias(tv.Type).(*types.Basic); ok && (b.Info()&types.IsUntyped) != 0 {
158 if Tlhs != types.Default(b) {
159 // The rhs is untyped and its default type differs from the explicit type on the lhs
160 continue specLoop
161 }
162 switch v := v.(type) {
163 case *ast.Ident:
164 // Only flag named constant rhs if it's a predeclared identifier.
165 // Don't flag other named constants, as the explicit type may aid readability.
166 if pass.TypesInfo.ObjectOf(v).Pkg() != nil && !flagHelpfulTypes {
167 continue specLoop
168 }
169 case *ast.BasicLit:
170 // Do flag basic literals
171 default:
172 // Don't flag untyped rhs expressions unless flagHelpfulTypes is set
173 if !flagHelpfulTypes {
174 continue specLoop
175 }
176 }
177 }
178 }
179 180 if !checkedDecl {
181 // Don't flag global variables. These often have explicit types for godoc's sake.
182 path, _ := astutil.PathEnclosingInterval(code.File(pass, decl), decl.Pos(), decl.Pos())
183 pathLoop:
184 for _, el := range path {
185 switch el.(type) {
186 case *ast.FuncDecl, *ast.FuncLit:
187 checkedDecl = true
188 break pathLoop
189 }
190 }
191 if !checkedDecl {
192 // decl is not inside a function
193 break specLoop
194 }
195 }
196 197 report.Report(pass, spec.Type, fmt.Sprintf("%s omit type %s from declaration; it will be inferred from the right-hand side", verb, report.Render(pass, spec.Type)), report.FilterGenerated(),
198 report.Fixes(edit.Fix("Remove redundant type", edit.Delete(spec.Type))))
199 }
200 }
201 code.Preorder(pass, fn, (*ast.GenDecl)(nil))
202 return nil, nil
203 }
204 205 return &analysis.Analyzer{
206 Run: fn,
207 Requires: []*analysis.Analyzer{generated.Analyzer, inspect.Analyzer, tokenfile.Analyzer},
208 }
209 }
210