Skip to content

Commit 886f4ef

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 886f4ef

File tree

2 files changed

+91
-0
lines changed

2 files changed

+91
-0
lines changed

src/reflect/all_test.go

+41
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,47 @@ func TestSetValue(t *testing.T) {
335335
}
336336
}
337337

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

src/reflect/value.go

+50
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)