src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

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 }