package qf1005 import ( "go/ast" "go/constant" "go/token" "go/types" "honnef.co/go/tools/analysis/code" "honnef.co/go/tools/analysis/edit" "honnef.co/go/tools/analysis/lint" "honnef.co/go/tools/analysis/report" "honnef.co/go/tools/go/ast/astutil" "honnef.co/go/tools/pattern" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/inspect" ) var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ Analyzer: &analysis.Analyzer{ Name: "QF1005", Run: run, Requires: []*analysis.Analyzer{inspect.Analyzer}, }, Doc: &lint.RawDocumentation{ Title: `Expand call to \'math.Pow\'`, Text: `Some uses of \'math.Pow\' can be simplified to basic multiplication.`, Before: `math.Pow(x, 2)`, After: `x * x`, Since: "2021.1", Severity: lint.SeverityHint, }, }) var Analyzer = SCAnalyzer.Analyzer var mathPowQ = pattern.MustParse(`(CallExpr (Symbol "math.Pow") [x (IntegerLiteral n)])`) func run(pass *analysis.Pass) (interface{}, error) { fn := func(node ast.Node) { matcher, ok := code.Match(pass, mathPowQ, node) if !ok { return } x := matcher.State["x"].(ast.Expr) if code.MayHaveSideEffects(pass, x, nil) { return } n, ok := constant.Int64Val(constant.ToInt(matcher.State["n"].(types.TypeAndValue).Value)) if !ok { return } needConversion := false if T, ok := pass.TypesInfo.Types[x]; ok && T.Value != nil { info := types.Info{ Types: map[ast.Expr]types.TypeAndValue{}, } // determine if the constant expression would have type float64 if used on its own if err := types.CheckExpr(pass.Fset, pass.Pkg, x.Pos(), x, &info); err != nil { // This should not happen return } if T, ok := info.Types[x].Type.(*types.Basic); ok { if T.Kind() != types.UntypedFloat && T.Kind() != types.Float64 { needConversion = true } } else { needConversion = true } } var replacement ast.Expr switch n { case 0: replacement = &ast.BasicLit{ Kind: token.FLOAT, Value: "1.0", } case 1: replacement = x case 2, 3: r := &ast.BinaryExpr{ X: x, Op: token.MUL, Y: x, } for i := 3; i <= int(n); i++ { r = &ast.BinaryExpr{ X: r, Op: token.MUL, Y: x, } } rc, ok := astutil.CopyExpr(r) if !ok { return } replacement = astutil.SimplifyParentheses(rc) default: return } if needConversion && n != 0 { replacement = &ast.CallExpr{ Fun: &ast.Ident{Name: "float64"}, Args: []ast.Expr{replacement}, } } report.Report(pass, node, "could expand call to math.Pow", report.Fixes(edit.Fix("Expand call to math.Pow", edit.ReplaceWithNode(pass.Fset, node, replacement)))) } code.Preorder(pass, fn, (*ast.CallExpr)(nil)) return nil, nil }