diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go index 17104ad4fabe57..eb14654709c660 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -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 } diff --git a/src/reflect/value.go b/src/reflect/value.go index 418dff781f4ecc..29dafffdc72936 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -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 { @@ -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.