From 35329ce0aee3abaee793ac20389e8fcd0ecddd2a Mon Sep 17 00:00:00 2001 From: Timo Beckers Date: Mon, 13 Oct 2025 17:00:03 +0200 Subject: [PATCH] cmd/stringer: omit negative bounds check from String for unsigned integers CL 707396 ("cmd/stringer: fix bounds checking for integer types spanning full range") introduced a universal lower bounds check for all integers, including unsigned ints. Unfortunately, performing a <0 bounds check on a uint gets flagged by static analysis tools like staticcheck. This commit only emits the bounds check if the type in question is either signed or has a non-zero minimum value, in which case the bounds check is necessary for unsigned types as well. Signed-off-by: Timo Beckers --- cmd/stringer/golden_test.go | 53 +++++++++++++++++++++++++++++++++++++ cmd/stringer/stringer.go | 23 +++++++++++++++- 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/cmd/stringer/golden_test.go b/cmd/stringer/golden_test.go index 46118407124..256eec4e95a 100644 --- a/cmd/stringer/golden_test.go +++ b/cmd/stringer/golden_test.go @@ -38,6 +38,8 @@ var golden = []Golden{ {"prefix", "Type", false, prefix_in, prefix_out}, {"tokens", "", true, tokens_in, tokens_out}, {"overflow8", "", false, overflow8_in, overflow8_out}, + {"ubounds0", "", false, ubounds0_in, ubounds0_out}, + {"ubounds1", "", false, ubounds1_in, ubounds1_out}, } // Each example starts with "type XXX [u]int", with a single space separating them. @@ -988,6 +990,57 @@ func (i Overflow8) String() string { } ` +const ubounds0_in = `type UBounds uint8 +const ( + B0 UBounds = 0 +) +` + +const ubounds0_out = `func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[B0-0] +} + +const _UBounds_name = "B0" + +var _UBounds_index = [...]uint8{0, 2} + +func (i UBounds) String() string { + idx := int(i) - 0 + if idx >= len(_UBounds_index)-1 { + return "UBounds(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _UBounds_name[_UBounds_index[idx]:_UBounds_index[idx+1]] +} +` + +const ubounds1_in = `type UBounds uint8 +const ( + B1 UBounds = 1 +) +` +const ubounds1_out = `func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[B1-1] +} + +const _UBounds_name = "B1" + +var _UBounds_index = [...]uint8{0, 2} + +func (i UBounds) String() string { + idx := int(i) - 1 + if i < 1 || idx >= len(_UBounds_index)-1 { + return "UBounds(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _UBounds_name[_UBounds_index[idx]:_UBounds_index[idx+1]] +} +` + func TestGolden(t *testing.T) { testenv.NeedsTool(t, "go") diff --git a/cmd/stringer/stringer.go b/cmd/stringer/stringer.go index 7ff0ee8d0c8..3e1edeade6c 100644 --- a/cmd/stringer/stringer.go +++ b/cmd/stringer/stringer.go @@ -641,7 +641,12 @@ func (g *Generator) buildOneRun(runs [][]Value, typeName string) { values := runs[0] g.Printf("\n") g.declareIndexAndNameVar(values, typeName) - g.Printf(stringOneRun, typeName, values[0].String()) + + if values[0].signed || values[0].String() != "0" { + g.Printf(stringOneRun, typeName, values[0].String()) + } else { + g.Printf(stringOneRunUnsigned, typeName, values[0].String()) + } } // Arguments to format are: @@ -657,6 +662,22 @@ const stringOneRun = `func (i %[1]s) String() string { } ` +// Arguments to format are: +// +// [1]: type name +// [2]: lowest defined value for type, as a string +// +// This is the unsigned version of [stringOneRun] with the lower bound check +// removed. +const stringOneRunUnsigned = `func (i %[1]s) String() string { + idx := int(i) - %[2]s + if idx >= len(_%[1]s_index)-1 { + return "%[1]s(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _%[1]s_name[_%[1]s_index[idx] : _%[1]s_index[idx+1]] +} +` + // buildMultipleRuns generates the variables and String method for multiple runs of contiguous values. // For this pattern, a single Printf format won't do. func (g *Generator) buildMultipleRuns(runs [][]Value, typeName string) {