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 b1aff42 commit 83e7a81
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 0 deletions.
23 changes: 23 additions & 0 deletions src/reflect/all_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"math"
"math/rand"
"os"
"reflect"
. "reflect"
"reflect/internal/example1"
"reflect/internal/example2"
Expand Down Expand Up @@ -335,6 +336,28 @@ 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 := reflect.New(v.Type().Key()).Elem()
e := reflect.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)
}
}
}

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

// SetKey does dst.Set(it.Key()).
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 +1600,29 @@ func (it *MapIter) Value() Value {
return copyVal(vtype, it.m.flag.ro()|flag(vtype.Kind()), mapiterelem(it.it))
}

// SetValue does dst.Set(it.Value()).
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 83e7a81

Please sign in to comment.