qf1005.go raw

   1  package qf1005
   2  
   3  import (
   4  	"go/ast"
   5  	"go/constant"
   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/lint"
  12  	"honnef.co/go/tools/analysis/report"
  13  	"honnef.co/go/tools/go/ast/astutil"
  14  	"honnef.co/go/tools/pattern"
  15  
  16  	"golang.org/x/tools/go/analysis"
  17  	"golang.org/x/tools/go/analysis/passes/inspect"
  18  )
  19  
  20  var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
  21  	Analyzer: &analysis.Analyzer{
  22  		Name:     "QF1005",
  23  		Run:      run,
  24  		Requires: []*analysis.Analyzer{inspect.Analyzer},
  25  	},
  26  	Doc: &lint.RawDocumentation{
  27  		Title:    `Expand call to \'math.Pow\'`,
  28  		Text:     `Some uses of \'math.Pow\' can be simplified to basic multiplication.`,
  29  		Before:   `math.Pow(x, 2)`,
  30  		After:    `x * x`,
  31  		Since:    "2021.1",
  32  		Severity: lint.SeverityHint,
  33  	},
  34  })
  35  
  36  var Analyzer = SCAnalyzer.Analyzer
  37  
  38  var mathPowQ = pattern.MustParse(`(CallExpr (Symbol "math.Pow") [x (IntegerLiteral n)])`)
  39  
  40  func run(pass *analysis.Pass) (interface{}, error) {
  41  	fn := func(node ast.Node) {
  42  		matcher, ok := code.Match(pass, mathPowQ, node)
  43  		if !ok {
  44  			return
  45  		}
  46  
  47  		x := matcher.State["x"].(ast.Expr)
  48  		if code.MayHaveSideEffects(pass, x, nil) {
  49  			return
  50  		}
  51  		n, ok := constant.Int64Val(constant.ToInt(matcher.State["n"].(types.TypeAndValue).Value))
  52  		if !ok {
  53  			return
  54  		}
  55  
  56  		needConversion := false
  57  		if T, ok := pass.TypesInfo.Types[x]; ok && T.Value != nil {
  58  			info := types.Info{
  59  				Types: map[ast.Expr]types.TypeAndValue{},
  60  			}
  61  
  62  			// determine if the constant expression would have type float64 if used on its own
  63  			if err := types.CheckExpr(pass.Fset, pass.Pkg, x.Pos(), x, &info); err != nil {
  64  				// This should not happen
  65  				return
  66  			}
  67  			if T, ok := info.Types[x].Type.(*types.Basic); ok {
  68  				if T.Kind() != types.UntypedFloat && T.Kind() != types.Float64 {
  69  					needConversion = true
  70  				}
  71  			} else {
  72  				needConversion = true
  73  			}
  74  		}
  75  
  76  		var replacement ast.Expr
  77  		switch n {
  78  		case 0:
  79  			replacement = &ast.BasicLit{
  80  				Kind:  token.FLOAT,
  81  				Value: "1.0",
  82  			}
  83  		case 1:
  84  			replacement = x
  85  		case 2, 3:
  86  			r := &ast.BinaryExpr{
  87  				X:  x,
  88  				Op: token.MUL,
  89  				Y:  x,
  90  			}
  91  			for i := 3; i <= int(n); i++ {
  92  				r = &ast.BinaryExpr{
  93  					X:  r,
  94  					Op: token.MUL,
  95  					Y:  x,
  96  				}
  97  			}
  98  
  99  			rc, ok := astutil.CopyExpr(r)
 100  			if !ok {
 101  				return
 102  			}
 103  			replacement = astutil.SimplifyParentheses(rc)
 104  		default:
 105  			return
 106  		}
 107  		if needConversion && n != 0 {
 108  			replacement = &ast.CallExpr{
 109  				Fun:  &ast.Ident{Name: "float64"},
 110  				Args: []ast.Expr{replacement},
 111  			}
 112  		}
 113  		report.Report(pass, node, "could expand call to math.Pow",
 114  			report.Fixes(edit.Fix("Expand call to math.Pow", edit.ReplaceWithNode(pass.Fset, node, replacement))))
 115  	}
 116  	code.Preorder(pass, fn, (*ast.CallExpr)(nil))
 117  	return nil, nil
 118  }
 119