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