package sa4026 import ( "fmt" "go/ast" "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/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: "SA4026", Run: run, Requires: []*analysis.Analyzer{inspect.Analyzer}, }, Doc: &lint.RawDocumentation{ Title: "Go constants cannot express negative zero", Text: `In IEEE 754 floating point math, zero has a sign and can be positive or negative. This can be useful in certain numerical code. Go constants, however, cannot express negative zero. This means that the literals \'-0.0\' and \'0.0\' have the same ideal value (zero) and will both represent positive zero at runtime. To explicitly and reliably create a negative zero, you can use the \'math.Copysign\' function: \'math.Copysign(0, -1)\'.`, Since: "2021.1", Severity: lint.SeverityWarning, MergeIf: lint.MergeIfAny, }, }) var Analyzer = SCAnalyzer.Analyzer var negativeZeroFloatQ = pattern.MustParse(` (Or (UnaryExpr "-" (BasicLit "FLOAT" "0.0")) (UnaryExpr "-" (CallExpr conv@(Object (Or "float32" "float64")) lit@(Or (BasicLit "INT" "0") (BasicLit "FLOAT" "0.0")))) (CallExpr conv@(Object (Or "float32" "float64")) (UnaryExpr "-" lit@(BasicLit "INT" "0"))))`) func run(pass *analysis.Pass) (interface{}, error) { fn := func(node ast.Node) { m, ok := code.Match(pass, negativeZeroFloatQ, node) if !ok { return } if conv, ok := m.State["conv"].(*types.TypeName); ok { var replacement string // TODO(dh): how does this handle type aliases? if conv.Name() == "float32" { replacement = `float32(math.Copysign(0, -1))` } else { replacement = `math.Copysign(0, -1)` } report.Report(pass, node, fmt.Sprintf("in Go, the floating-point expression '%s' is the same as '%s(%s)', it does not produce a negative zero", report.Render(pass, node), conv.Name(), report.Render(pass, m.State["lit"])), report.Fixes(edit.Fix("use math.Copysign to create negative zero", edit.ReplaceWithString(node, replacement)))) } else { const replacement = `math.Copysign(0, -1)` report.Report(pass, node, "in Go, the floating-point literal '-0.0' is the same as '0.0', it does not produce a negative zero", report.Fixes(edit.Fix("use math.Copysign to create negative zero", edit.ReplaceWithString(node, replacement)))) } } code.Preorder(pass, fn, (*ast.UnaryExpr)(nil), (*ast.CallExpr)(nil)) return nil, nil }