src

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

sa4026.go (2497B)


      1 package sa4026
      2 
      3 import (
      4 	"fmt"
      5 	"go/types"
      6 
      7 	"honnef.co/go/tools/analysis/code"
      8 	"honnef.co/go/tools/analysis/edit"
      9 	"honnef.co/go/tools/analysis/lint"
     10 	"honnef.co/go/tools/analysis/report"
     11 	"honnef.co/go/tools/pattern"
     12 
     13 	"golang.org/x/tools/go/analysis"
     14 )
     15 
     16 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
     17 	Analyzer: &analysis.Analyzer{
     18 		Name:     "SA4026",
     19 		Run:      run,
     20 		Requires: code.RequiredAnalyzers,
     21 	},
     22 	Doc: &lint.RawDocumentation{
     23 		Title: "Go constants cannot express negative zero",
     24 		Text: `In IEEE 754 floating point math, zero has a sign and can be positive
     25 or negative. This can be useful in certain numerical code.
     26 
     27 Go constants, however, cannot express negative zero. This means that
     28 the literals \'-0.0\' and \'0.0\' have the same ideal value (zero) and
     29 will both represent positive zero at runtime.
     30 
     31 To explicitly and reliably create a negative zero, you can use the
     32 \'math.Copysign\' function: \'math.Copysign(0, -1)\'.`,
     33 		Since:    "2021.1",
     34 		Severity: lint.SeverityWarning,
     35 		MergeIf:  lint.MergeIfAny,
     36 	},
     37 })
     38 
     39 var Analyzer = SCAnalyzer.Analyzer
     40 
     41 var negativeZeroFloatQ = pattern.MustParse(`
     42 	(Or
     43 		(UnaryExpr
     44 			"-"
     45 			(BasicLit "FLOAT" "0.0"))
     46 
     47 		(UnaryExpr
     48 			"-"
     49 			(CallExpr conv@(Object (Or "float32" "float64")) lit@(Or (BasicLit "INT" "0") (BasicLit "FLOAT" "0.0"))))
     50 
     51 		(CallExpr
     52 			conv@(Object (Or "float32" "float64"))
     53 			(UnaryExpr "-" lit@(BasicLit "INT" "0"))))`)
     54 
     55 func run(pass *analysis.Pass) (any, error) {
     56 	for node, m := range code.Matches(pass, negativeZeroFloatQ) {
     57 		if conv, ok := m.State["conv"].(*types.TypeName); ok {
     58 			var replacement string
     59 			// TODO(dh): how does this handle type aliases?
     60 			if conv.Name() == "float32" {
     61 				replacement = `float32(math.Copysign(0, -1))`
     62 			} else {
     63 				replacement = `math.Copysign(0, -1)`
     64 			}
     65 			report.Report(pass, node,
     66 				fmt.Sprintf("in Go, the floating-point expression '%s' is the same as '%s(%s)', it does not produce a negative zero",
     67 					report.Render(pass, node),
     68 					conv.Name(),
     69 					report.Render(pass, m.State["lit"])),
     70 				report.Fixes(edit.Fix("Use math.Copysign to create negative zero", edit.ReplaceWithString(node, replacement))))
     71 		} else {
     72 			const replacement = `math.Copysign(0, -1)`
     73 			report.Report(pass, node,
     74 				"in Go, the floating-point literal '-0.0' is the same as '0.0', it does not produce a negative zero",
     75 				report.Fixes(edit.Fix("Use math.Copysign to create negative zero", edit.ReplaceWithString(node, replacement))))
     76 		}
     77 	}
     78 	return nil, nil
     79 }