diff --git a/staticcheck/sa4026/sa4026.go b/staticcheck/sa4026/sa4026.go index 31681d178..6c14fb9db 100644 --- a/staticcheck/sa4026/sa4026.go +++ b/staticcheck/sa4026/sa4026.go @@ -2,7 +2,9 @@ package sa4026 import ( "fmt" + "go/ast" "go/types" + "strconv" "honnef.co/go/tools/analysis/code" "honnef.co/go/tools/analysis/edit" @@ -53,26 +55,63 @@ var negativeZeroFloatQ = pattern.MustParse(` (UnaryExpr "-" lit@(BasicLit "INT" "0"))))`) func run(pass *analysis.Pass) (any, error) { + mathQualifier := func(node ast.Node) string { + for _, file := range pass.Files { + if node.Pos() < file.Pos() || node.Pos() > file.End() { + continue + } + for _, spec := range file.Imports { + path, err := strconv.Unquote(spec.Path.Value) + if err != nil || path != "math" { + continue + } + var pkgName *types.PkgName + if spec.Name != nil { + pkgName, _ = pass.TypesInfo.Defs[spec.Name].(*types.PkgName) + } else { + pkgName, _ = pass.TypesInfo.Implicits[spec].(*types.PkgName) + } + if pkgName != nil && pkgName.Name() != "." && pkgName.Name() != "_" { + scope := pass.TypesInfo.Scopes[file].Innermost(node.Pos()) + if scope != nil { + _, obj := scope.LookupParent(pkgName.Name(), node.Pos()) + if obj == pkgName { + return pkgName.Name() + } + } + } + } + } + return "" + } for node, m := range code.Matches(pass, negativeZeroFloatQ) { + mathName := mathQualifier(node) 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))` + replacement = fmt.Sprintf(`float32(%s.Copysign(0, -1))`, mathName) } else { - replacement = `math.Copysign(0, -1)` + replacement = fmt.Sprintf(`%s.Copysign(0, -1)`, mathName) + } + var opts []report.Option + if mathName != "" { + opts = append(opts, report.Fixes(edit.Fix("Use math.Copysign to create negative zero", edit.ReplaceWithString(node, replacement)))) } 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)))) + opts...) } else { - const replacement = `math.Copysign(0, -1)` + var opts []report.Option + if mathName != "" { + opts = append(opts, report.Fixes(edit.Fix("Use math.Copysign to create negative zero", edit.ReplaceWithString(node, fmt.Sprintf(`%s.Copysign(0, -1)`, mathName))))) + } 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)))) + opts...) } } return nil, nil diff --git a/staticcheck/sa4026/testdata/go1.0/CheckNegativeZeroFloat/CheckNegativeZeroFloat.go b/staticcheck/sa4026/testdata/go1.0/CheckNegativeZeroFloat/CheckNegativeZeroFloat.go index 45cf17bc2..de0eb3276 100644 --- a/staticcheck/sa4026/testdata/go1.0/CheckNegativeZeroFloat/CheckNegativeZeroFloat.go +++ b/staticcheck/sa4026/testdata/go1.0/CheckNegativeZeroFloat/CheckNegativeZeroFloat.go @@ -1,8 +1,11 @@ package pkg +import m "math" + const x = 0.0 func fn() { + _ = m.Copysign _ = -0.0 //@ diag(`in Go, the floating-point literal '-0.0' is the same as '0.0', it does not produce a negative zero`) _ = float32(-0.0) //@ diag(`in Go, the floating-point literal '-0.0' is the same as '0.0', it does not produce a negative zero`) _ = float64(-0.0) //@ diag(`in Go, the floating-point literal '-0.0' is the same as '0.0', it does not produce a negative zero`) diff --git a/staticcheck/sa4026/testdata/go1.0/CheckNegativeZeroFloat/CheckNegativeZeroFloat.go.golden b/staticcheck/sa4026/testdata/go1.0/CheckNegativeZeroFloat/CheckNegativeZeroFloat.go.golden index dfafa54b0..bc5d31214 100644 --- a/staticcheck/sa4026/testdata/go1.0/CheckNegativeZeroFloat/CheckNegativeZeroFloat.go.golden +++ b/staticcheck/sa4026/testdata/go1.0/CheckNegativeZeroFloat/CheckNegativeZeroFloat.go.golden @@ -1,15 +1,18 @@ package pkg +import m "math" + const x = 0.0 func fn() { - _ = math.Copysign(0, -1) //@ diag(`in Go, the floating-point literal '-0.0' is the same as '0.0', it does not produce a negative zero`) - _ = float32(math.Copysign(0, -1)) //@ diag(`in Go, the floating-point literal '-0.0' is the same as '0.0', it does not produce a negative zero`) - _ = float64(math.Copysign(0, -1)) //@ diag(`in Go, the floating-point literal '-0.0' is the same as '0.0', it does not produce a negative zero`) - _ = float32(math.Copysign(0, -1)) //@ diag(`in Go, the floating-point expression '-float32(0)' is the same as 'float32(0)', it does not produce a negative zero`) - _ = math.Copysign(0, -1) //@ diag(`in Go, the floating-point expression '-float64(0)' is the same as 'float64(0)', it does not produce a negative zero`) - _ = float32(math.Copysign(0, -1)) //@ diag(`in Go, the floating-point expression '-float32(0.0)' is the same as 'float32(0.0)', it does not produce a negative zero`) - _ = math.Copysign(0, -1) //@ diag(`in Go, the floating-point expression '-float64(0.0)' is the same as 'float64(0.0)', it does not produce a negative zero`) + _ = m.Copysign + _ = m.Copysign(0, -1) //@ diag(`in Go, the floating-point literal '-0.0' is the same as '0.0', it does not produce a negative zero`) + _ = float32(m.Copysign(0, -1)) //@ diag(`in Go, the floating-point literal '-0.0' is the same as '0.0', it does not produce a negative zero`) + _ = float64(m.Copysign(0, -1)) //@ diag(`in Go, the floating-point literal '-0.0' is the same as '0.0', it does not produce a negative zero`) + _ = float32(m.Copysign(0, -1)) //@ diag(`in Go, the floating-point expression '-float32(0)' is the same as 'float32(0)', it does not produce a negative zero`) + _ = m.Copysign(0, -1) //@ diag(`in Go, the floating-point expression '-float64(0)' is the same as 'float64(0)', it does not produce a negative zero`) + _ = float32(m.Copysign(0, -1)) //@ diag(`in Go, the floating-point expression '-float32(0.0)' is the same as 'float32(0.0)', it does not produce a negative zero`) + _ = m.Copysign(0, -1) //@ diag(`in Go, the floating-point expression '-float64(0.0)' is the same as 'float64(0.0)', it does not produce a negative zero`) // intentionally not flagged _ = -x diff --git a/staticcheck/sa4026/testdata/go1.0/CheckNegativeZeroScope/import.go b/staticcheck/sa4026/testdata/go1.0/CheckNegativeZeroScope/import.go new file mode 100644 index 000000000..71753edbf --- /dev/null +++ b/staticcheck/sa4026/testdata/go1.0/CheckNegativeZeroScope/import.go @@ -0,0 +1,5 @@ +package pkg + +import m "math" + +var _ = m.Copysign diff --git a/staticcheck/sa4026/testdata/go1.0/CheckNegativeZeroScope/import.go.golden b/staticcheck/sa4026/testdata/go1.0/CheckNegativeZeroScope/import.go.golden new file mode 100644 index 000000000..71753edbf --- /dev/null +++ b/staticcheck/sa4026/testdata/go1.0/CheckNegativeZeroScope/import.go.golden @@ -0,0 +1,5 @@ +package pkg + +import m "math" + +var _ = m.Copysign diff --git a/staticcheck/sa4026/testdata/go1.0/CheckNegativeZeroScope/other.go b/staticcheck/sa4026/testdata/go1.0/CheckNegativeZeroScope/other.go new file mode 100644 index 000000000..9ab409cd2 --- /dev/null +++ b/staticcheck/sa4026/testdata/go1.0/CheckNegativeZeroScope/other.go @@ -0,0 +1,6 @@ +package pkg + +func other() { + _ = -0.0 //@ diag(`does not produce a negative zero`) + _ = float32(-0) //@ diag(`does not produce a negative zero`) +} diff --git a/staticcheck/sa4026/testdata/go1.0/CheckNegativeZeroScope/other.go.golden b/staticcheck/sa4026/testdata/go1.0/CheckNegativeZeroScope/other.go.golden new file mode 100644 index 000000000..9ab409cd2 --- /dev/null +++ b/staticcheck/sa4026/testdata/go1.0/CheckNegativeZeroScope/other.go.golden @@ -0,0 +1,6 @@ +package pkg + +func other() { + _ = -0.0 //@ diag(`does not produce a negative zero`) + _ = float32(-0) //@ diag(`does not produce a negative zero`) +} diff --git a/staticcheck/sa4026/testdata/go1.0/CheckNegativeZeroScope/shadow.go b/staticcheck/sa4026/testdata/go1.0/CheckNegativeZeroScope/shadow.go new file mode 100644 index 000000000..c08524e37 --- /dev/null +++ b/staticcheck/sa4026/testdata/go1.0/CheckNegativeZeroScope/shadow.go @@ -0,0 +1,12 @@ +package pkg + +import m "math" + +var _ = m.Copysign + +func shadow() { + m := 1 + _ = m + _ = -0.0 //@ diag(`does not produce a negative zero`) + _ = float32(-0) //@ diag(`does not produce a negative zero`) +} diff --git a/staticcheck/sa4026/testdata/go1.0/CheckNegativeZeroScope/shadow.go.golden b/staticcheck/sa4026/testdata/go1.0/CheckNegativeZeroScope/shadow.go.golden new file mode 100644 index 000000000..c08524e37 --- /dev/null +++ b/staticcheck/sa4026/testdata/go1.0/CheckNegativeZeroScope/shadow.go.golden @@ -0,0 +1,12 @@ +package pkg + +import m "math" + +var _ = m.Copysign + +func shadow() { + m := 1 + _ = m + _ = -0.0 //@ diag(`does not produce a negative zero`) + _ = float32(-0) //@ diag(`does not produce a negative zero`) +}