Skip to content

Commit

Permalink
reflect: add MapIter.SetKey and MapIter.SetValue
Browse files Browse the repository at this point in the history
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
  • Loading branch information
josharian committed May 18, 2021
1 parent 690a8c3 commit 886f4ef
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 0 deletions.
41 changes: 41 additions & 0 deletions src/reflect/all_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,47 @@ func TestSetValue(t *testing.T) {
}
}

func TestMapIterSet(t *testing.T) {
m := make(map[string]interface{}, len(valueTests))
for _, tt := range valueTests {
m[tt.s] = tt.i
}
v := ValueOf(m)

k := New(v.Type().Key()).Elem()
e := New(v.Type().Elem()).Elem()

iter := v.MapRange()
for iter.Next() {
iter.SetKey(k)
iter.SetValue(e)
want := m[k.String()]
got := e.Interface()
if got != want {
t.Errorf("%q: want (%T) %v, got (%T) %v", k.String(), want, want, got, got)
}
if setkey, key := valueToString(k), valueToString(iter.Key()); setkey != key {
t.Errorf("MapIter.Key() = %q, MapIter.SetKey() = %q", key, setkey)
}
if setval, val := valueToString(e), valueToString(iter.Value()); setval != val {
t.Errorf("MapIter.Value() = %q, MapIter.SetValue() = %q", val, setval)
}
}

got := int(testing.AllocsPerRun(10, func() {
iter := v.MapRange()
for iter.Next() {
iter.SetKey(k)
iter.SetValue(e)
}
}))
// Making a *MapIter and making an hiter both allocate.
// Those should be the only two allocations.
if got != 2 {
t.Errorf("wanted 2 allocs, got %d", got)
}
}

func TestCanSetField(t *testing.T) {
type embed struct{ x, X int }
type Embed struct{ x, X int }
Expand Down
50 changes: 50 additions & 0 deletions src/reflect/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -1563,6 +1563,31 @@ func (it *MapIter) Key() Value {
return copyVal(ktype, it.m.flag.ro()|flag(ktype.Kind()), mapiterkey(it.it))
}

// SetKey assigns dst to the key of the iterator's current map entry.
// It is equivalent to dst.Set(it.Key()), but it avoids allocating a new Value.
// As in Go, the key must be assignable to dst's type.
func (it *MapIter) SetKey(dst Value) {
if it.it == nil {
panic("MapIter.SetKey called before Next")
}
if mapiterkey(it.it) == nil {
panic("MapIter.SetKey called on exhausted iterator")
}

dst.mustBeAssignable()
var target unsafe.Pointer
if dst.kind() == Interface {
target = dst.ptr
}

t := (*mapType)(unsafe.Pointer(it.m.typ))
ktype := t.key

key := Value{ktype, mapiterkey(it.it), it.m.flag.ro() | flag(ktype.Kind())}
key = key.assignTo("reflect.MapIter.SetKey", dst.typ, target)
typedmemmove(dst.typ, dst.ptr, key.ptr)
}

// Value returns the value of the iterator's current map entry.
func (it *MapIter) Value() Value {
if it.it == nil {
Expand All @@ -1577,6 +1602,31 @@ func (it *MapIter) Value() Value {
return copyVal(vtype, it.m.flag.ro()|flag(vtype.Kind()), mapiterelem(it.it))
}

// SetValue assigns dst to the value of the iterator's current map entry.
// It is equivalent to dst.Set(it.Value()), but it avoids allocating a new Value.
// As in Go, the value must be assignable to dst's type.
func (it *MapIter) SetValue(dst Value) {
if it.it == nil {
panic("MapIter.SetValue called before Next")
}
if mapiterkey(it.it) == nil {
panic("MapIter.SetValue called on exhausted iterator")
}

dst.mustBeAssignable()
var target unsafe.Pointer
if dst.kind() == Interface {
target = dst.ptr
}

t := (*mapType)(unsafe.Pointer(it.m.typ))
vtype := t.elem

elem := Value{vtype, mapiterelem(it.it), it.m.flag.ro() | flag(vtype.Kind())}
elem = elem.assignTo("reflect.MapIter.SetValue", dst.typ, target)
typedmemmove(dst.typ, dst.ptr, elem.ptr)
}

// Next advances the map iterator and reports whether there is another
// entry. It returns false when the iterator is exhausted; subsequent
// calls to Key, Value, or Next will panic.
Expand Down

0 comments on commit 886f4ef

Please sign in to comment.