src

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

qf1005.go (2628B)


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