Skip to content

Commit 208c46b

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 golang#32424 Fixes golang#46131 Change-Id: I19c4d95c432f63dfe52cde96d2125abd021f24fa
1 parent 690a8c3 commit 208c46b

File tree

2 files changed

+92
-0
lines changed

2 files changed

+92
-0
lines changed

src/reflect/all_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"math"
1515
"math/rand"
1616
"os"
17+
"reflect"
1718
. "reflect"
1819
"reflect/internal/example1"
1920
"reflect/internal/example2"
@@ -335,6 +336,47 @@ func TestSetValue(t *testing.T) {
335336
}
336337
}
337338

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 := reflect.New(v.Type().Key()).Elem()
347+
e := reflect.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+
338380
func TestCanSetField(t *testing.T) {
339381
type embed struct{ x, X int }
340382
type Embed struct{ x, X int }

src/reflect/value.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1563,6 +1563,31 @@ func (it *MapIter) Key() Value {
15631563
return copyVal(ktype, it.m.flag.ro()|flag(ktype.Kind()), mapiterkey(it.it))
15641564
}
15651565

1566+
// SetKey assigns dst to the key of the iterator's current map entry.
1567+
// It is equivalent to dst.Set(it.Key()), but it avoids allocating a new Value.
1568+
// As in Go, the key must be assignable to dst's type.
1569+
func (it *MapIter) SetKey(dst Value) {
1570+
if it.it == nil {
1571+
panic("MapIter.SetKey called before Next")
1572+
}
1573+
if mapiterkey(it.it) == nil {
1574+
panic("MapIter.SetKey called on exhausted iterator")
1575+
}
1576+
1577+
dst.mustBeAssignable()
1578+
var target unsafe.Pointer
1579+
if dst.kind() == Interface {
1580+
target = dst.ptr
1581+
}
1582+
1583+
t := (*mapType)(unsafe.Pointer(it.m.typ))
1584+
ktype := t.key
1585+
1586+
key := Value{ktype, mapiterkey(it.it), it.m.flag.ro() | flag(ktype.Kind())}
1587+
key = key.assignTo("reflect.MapIter.SetKey", dst.typ, target)
1588+
typedmemmove(dst.typ, dst.ptr, key.ptr)
1589+
}
1590+
15661591
// Value returns the value of the iterator's current map entry.
15671592
func (it *MapIter) Value() Value {
15681593
if it.it == nil {
@@ -1577,6 +1602,31 @@ func (it *MapIter) Value() Value {
15771602
return copyVal(vtype, it.m.flag.ro()|flag(vtype.Kind()), mapiterelem(it.it))
15781603
}
15791604

1605+
// SetValue assigns dst to the value of the iterator's current map entry.
1606+
// It is equivalent to dst.Set(it.Value()), but it avoids allocating a new Value.
1607+
// As in Go, the value must be assignable to dst's type.
1608+
func (it *MapIter) SetValue(dst Value) {
1609+
if it.it == nil {
1610+
panic("MapIter.SetValue called before Next")
1611+
}
1612+
if mapiterkey(it.it) == nil {
1613+
panic("MapIter.SetValue called on exhausted iterator")
1614+
}
1615+
1616+
dst.mustBeAssignable()
1617+
var target unsafe.Pointer
1618+
if dst.kind() == Interface {
1619+
target = dst.ptr
1620+
}
1621+
1622+
t := (*mapType)(unsafe.Pointer(it.m.typ))
1623+
vtype := t.elem
1624+
1625+
elem := Value{vtype, mapiterelem(it.it), it.m.flag.ro() | flag(vtype.Kind())}
1626+
elem = elem.assignTo("reflect.MapIter.SetValue", dst.typ, target)
1627+
typedmemmove(dst.typ, dst.ptr, elem.ptr)
1628+
}
1629+
15801630
// Next advances the map iterator and reports whether there is another
15811631
// entry. It returns false when the iterator is exhausted; subsequent
15821632
// calls to Key, Value, or Next will panic.

0 commit comments

Comments
 (0)