Skip to content

Commit 54cdef1

Browse files
committed
reflect: add MapIter.SetKey and MapIter.SetValue
These augment the existing MapIter.Key and MapIter.Value methods. The existing methods return new Values. Constructing these new Values often requires allocating. These methods allow the caller to bring their own storage. The naming is somewhat unfortunate, in that the spec uses the word "element" instead of "value", as do the reflect.Type methods. In a vacuum, MapIter.SetElem would be preferable. However, matching the existing methods is more important. Fixes #32424 Fixes #46131 Change-Id: I19c4d95c432f63dfe52cde96d2125abd021f24fa Reviewed-on: https://go-review.googlesource.com/c/go/+/320929 Trust: Josh Bleecher Snyder <[email protected]> Run-TryBot: Josh Bleecher Snyder <[email protected]> Reviewed-by: Keith Randall <[email protected]>
1 parent 5d863f8 commit 54cdef1

File tree

2 files changed

+99
-4
lines changed

2 files changed

+99
-4
lines changed

src/reflect/all_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,47 @@ func TestSetValue(t *testing.T) {
336336
}
337337
}
338338

339+
func TestMapIterSet(t *testing.T) {
340+
m := make(map[string]interface{}, len(valueTests))
341+
for _, tt := range valueTests {
342+
m[tt.s] = tt.i
343+
}
344+
v := ValueOf(m)
345+
346+
k := New(v.Type().Key()).Elem()
347+
e := New(v.Type().Elem()).Elem()
348+
349+
iter := v.MapRange()
350+
for iter.Next() {
351+
iter.SetKey(k)
352+
iter.SetValue(e)
353+
want := m[k.String()]
354+
got := e.Interface()
355+
if got != want {
356+
t.Errorf("%q: want (%T) %v, got (%T) %v", k.String(), want, want, got, got)
357+
}
358+
if setkey, key := valueToString(k), valueToString(iter.Key()); setkey != key {
359+
t.Errorf("MapIter.Key() = %q, MapIter.SetKey() = %q", key, setkey)
360+
}
361+
if setval, val := valueToString(e), valueToString(iter.Value()); setval != val {
362+
t.Errorf("MapIter.Value() = %q, MapIter.SetValue() = %q", val, setval)
363+
}
364+
}
365+
366+
got := int(testing.AllocsPerRun(10, func() {
367+
iter := v.MapRange()
368+
for iter.Next() {
369+
iter.SetKey(k)
370+
iter.SetValue(e)
371+
}
372+
}))
373+
// Making a *MapIter and making an hiter both allocate.
374+
// Those should be the only two allocations.
375+
if got != 2 {
376+
t.Errorf("wanted 2 allocs, got %d", got)
377+
}
378+
}
379+
339380
func TestCanSetField(t *testing.T) {
340381
type embed struct{ x, X int }
341382
type Embed struct{ x, X int }

src/reflect/value.go

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1578,27 +1578,81 @@ func (it *MapIter) Key() Value {
15781578
if it.it == nil {
15791579
panic("MapIter.Key called before Next")
15801580
}
1581-
if mapiterkey(it.it) == nil {
1581+
iterkey := mapiterkey(it.it)
1582+
if iterkey == nil {
15821583
panic("MapIter.Key called on exhausted iterator")
15831584
}
15841585

15851586
t := (*mapType)(unsafe.Pointer(it.m.typ))
15861587
ktype := t.key
1587-
return copyVal(ktype, it.m.flag.ro()|flag(ktype.Kind()), mapiterkey(it.it))
1588+
return copyVal(ktype, it.m.flag.ro()|flag(ktype.Kind()), iterkey)
1589+
}
1590+
1591+
// SetKey assigns dst to the key of the iterator's current map entry.
1592+
// It is equivalent to dst.Set(it.Key()), but it avoids allocating a new Value.
1593+
// As in Go, the key must be assignable to dst's type.
1594+
func (it *MapIter) SetKey(dst Value) {
1595+
if it.it == nil {
1596+
panic("MapIter.SetKey called before Next")
1597+
}
1598+
iterkey := mapiterkey(it.it)
1599+
if iterkey == nil {
1600+
panic("MapIter.SetKey called on exhausted iterator")
1601+
}
1602+
1603+
dst.mustBeAssignable()
1604+
var target unsafe.Pointer
1605+
if dst.kind() == Interface {
1606+
target = dst.ptr
1607+
}
1608+
1609+
t := (*mapType)(unsafe.Pointer(it.m.typ))
1610+
ktype := t.key
1611+
1612+
key := Value{ktype, iterkey, it.m.flag | flag(ktype.Kind())}
1613+
key = key.assignTo("reflect.MapIter.SetKey", dst.typ, target)
1614+
typedmemmove(dst.typ, dst.ptr, key.ptr)
15881615
}
15891616

15901617
// Value returns the value of the iterator's current map entry.
15911618
func (it *MapIter) Value() Value {
15921619
if it.it == nil {
15931620
panic("MapIter.Value called before Next")
15941621
}
1595-
if mapiterkey(it.it) == nil {
1622+
iterelem := mapiterelem(it.it)
1623+
if iterelem == nil {
15961624
panic("MapIter.Value called on exhausted iterator")
15971625
}
15981626

15991627
t := (*mapType)(unsafe.Pointer(it.m.typ))
16001628
vtype := t.elem
1601-
return copyVal(vtype, it.m.flag.ro()|flag(vtype.Kind()), mapiterelem(it.it))
1629+
return copyVal(vtype, it.m.flag.ro()|flag(vtype.Kind()), iterelem)
1630+
}
1631+
1632+
// SetValue assigns dst to the value of the iterator's current map entry.
1633+
// It is equivalent to dst.Set(it.Value()), but it avoids allocating a new Value.
1634+
// As in Go, the value must be assignable to dst's type.
1635+
func (it *MapIter) SetValue(dst Value) {
1636+
if it.it == nil {
1637+
panic("MapIter.SetValue called before Next")
1638+
}
1639+
iterelem := mapiterelem(it.it)
1640+
if iterelem == nil {
1641+
panic("MapIter.SetValue called on exhausted iterator")
1642+
}
1643+
1644+
dst.mustBeAssignable()
1645+
var target unsafe.Pointer
1646+
if dst.kind() == Interface {
1647+
target = dst.ptr
1648+
}
1649+
1650+
t := (*mapType)(unsafe.Pointer(it.m.typ))
1651+
vtype := t.elem
1652+
1653+
elem := Value{vtype, iterelem, it.m.flag | flag(vtype.Kind())}
1654+
elem = elem.assignTo("reflect.MapIter.SetValue", dst.typ, target)
1655+
typedmemmove(dst.typ, dst.ptr, elem.ptr)
16021656
}
16031657

16041658
// Next advances the map iterator and reports whether there is another

0 commit comments

Comments
 (0)