Skip to content

Commit c700c23

Browse files
committed
reflect: make Value.IsZero identical to v == zero
The upcoming built-in zero value provides an idiomatic way to test for zero by comparing to the zero literal: v == zero. The reflect package is meant to provide a programmatic way to perform operations that the Go language itself provides. Thus, it seems prudent that reflect.ValueOf(&v).Elem().IsZero() is identical to v == zero. This change alters the behavior of Value.IsZero in two concrete ways: * negative zero is identical to zero * blank fields in a struct are ignored Prior to this change, we were already in an inconsistent state due to a regression introduced by CL 411478. The new behavior was already the case for comparable composite types. This change makes it consistent for all other types (in particular incomparable composite types and standalone numbers). Updates #61372 Fixes #61827 Change-Id: Id23fb97eb3b8921417cc75a1d3ead963e22dc3d9 Reviewed-on: https://go-review.googlesource.com/c/go/+/517777 Reviewed-by: Russ Cox <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]>
1 parent 7015ed8 commit c700c23

File tree

3 files changed

+34
-8
lines changed

3 files changed

+34
-8
lines changed

doc/go1.22.html

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,20 @@ <h3 id="minor_library_changes">Minor changes to the library</h3>
100100
</dd>
101101
</dl>
102102

103+
<dl id="reflect"><dt><a href="/pkg/reflect/">reflect</a></dt>
104+
<dd>
105+
<p><!-- https://go.dev/issue/61827, CL 517777 -->
106+
The <a href="/pkg/reflect/#Value.IsZero"><code>Value.IsZero</code></a>
107+
method will now return true for a floating-point or complex
108+
negative zero, and will return true for a struct value if a
109+
blank field (a field named <code>_</code>) somehow has a
110+
non-zero value.
111+
These changes make <code>IsZero</code> consistent with comparing
112+
a value to zero using the languague <code>==</code> operator.
113+
</p>
114+
</dd>
115+
</dl>
116+
103117
<h2 id="ports">Ports</h2>
104118

105119
<p>

src/reflect/all_test.go

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1396,6 +1396,11 @@ func TestIsNil(t *testing.T) {
13961396
NotNil(fi, t)
13971397
}
13981398

1399+
func setField[S, V any](in S, offset uintptr, value V) (out S) {
1400+
*(*V)(unsafe.Add(unsafe.Pointer(&in), offset)) = value
1401+
return in
1402+
}
1403+
13991404
func TestIsZero(t *testing.T) {
14001405
for i, tt := range []struct {
14011406
x any
@@ -1429,14 +1434,14 @@ func TestIsZero(t *testing.T) {
14291434
{float32(1.2), false},
14301435
{float64(0), true},
14311436
{float64(1.2), false},
1432-
{math.Copysign(0, -1), false},
1437+
{math.Copysign(0, -1), true},
14331438
{complex64(0), true},
14341439
{complex64(1.2), false},
14351440
{complex128(0), true},
14361441
{complex128(1.2), false},
1437-
{complex(math.Copysign(0, -1), 0), false},
1438-
{complex(0, math.Copysign(0, -1)), false},
1439-
{complex(math.Copysign(0, -1), math.Copysign(0, -1)), false},
1442+
{complex(math.Copysign(0, -1), 0), true},
1443+
{complex(0, math.Copysign(0, -1)), true},
1444+
{complex(math.Copysign(0, -1), math.Copysign(0, -1)), true},
14401445
{uintptr(0), true},
14411446
{uintptr(128), false},
14421447
// Array
@@ -1485,6 +1490,14 @@ func TestIsZero(t *testing.T) {
14851490
{struct{ s []int }{[]int{1}}, false}, // incomparable struct
14861491
{struct{ Value }{}, true},
14871492
{struct{ Value }{ValueOf(0)}, false},
1493+
{struct{ _, a, _ uintptr }{}, true}, // comparable struct with blank fields
1494+
{setField(struct{ _, a, _ uintptr }{}, 0*unsafe.Sizeof(uintptr(0)), 1), true},
1495+
{setField(struct{ _, a, _ uintptr }{}, 1*unsafe.Sizeof(uintptr(0)), 1), false},
1496+
{setField(struct{ _, a, _ uintptr }{}, 2*unsafe.Sizeof(uintptr(0)), 1), true},
1497+
{struct{ _, a, _ func() }{}, true}, // incomparable struct with blank fields
1498+
{setField(struct{ _, a, _ func() }{}, 0*unsafe.Sizeof((func())(nil)), func() {}), true},
1499+
{setField(struct{ _, a, _ func() }{}, 1*unsafe.Sizeof((func())(nil)), func() {}), false},
1500+
{setField(struct{ _, a, _ func() }{}, 2*unsafe.Sizeof((func())(nil)), func() {}), true},
14881501
// UnsafePointer
14891502
{(unsafe.Pointer)(nil), true},
14901503
{(unsafe.Pointer)(new(int)), false},

src/reflect/value.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1594,10 +1594,9 @@ func (v Value) IsZero() bool {
15941594
case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr:
15951595
return v.Uint() == 0
15961596
case Float32, Float64:
1597-
return math.Float64bits(v.Float()) == 0
1597+
return v.Float() == 0
15981598
case Complex64, Complex128:
1599-
c := v.Complex()
1600-
return math.Float64bits(real(c)) == 0 && math.Float64bits(imag(c)) == 0
1599+
return v.Complex() == 0
16011600
case Array:
16021601
// If the type is comparable, then compare directly with zero.
16031602
if v.typ().Equal != nil && v.typ().Size() <= maxZero {
@@ -1633,7 +1632,7 @@ func (v Value) IsZero() bool {
16331632

16341633
n := v.NumField()
16351634
for i := 0; i < n; i++ {
1636-
if !v.Field(i).IsZero() {
1635+
if !v.Field(i).IsZero() && v.Type().Field(i).Name != "_" {
16371636
return false
16381637
}
16391638
}

0 commit comments

Comments
 (0)