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