Skip to content

Commit ede5958

Browse files
committed
reflect: add Value.MapRange method and MapIter type
Example of use: iter := reflect.ValueOf(m).MapRange() for iter.Next() { k := iter.Key() v := iter.Value() ... } See issue #11104 Q. Are there any benchmarks that would exercise the new calls to copyval in existing code? Change-Id: Ic469fcab5f1d9d853e76225f89bde01ee1d36e7a Reviewed-on: https://go-review.googlesource.com/33572 Reviewed-by: Keith Randall <[email protected]>
1 parent 8c04258 commit ede5958

File tree

3 files changed

+215
-17
lines changed

3 files changed

+215
-17
lines changed

src/reflect/all_test.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6576,3 +6576,124 @@ func TestIssue22073(t *testing.T) {
65766576
// Shouldn't panic.
65776577
m.Call(nil)
65786578
}
6579+
6580+
func TestMapIterNonEmptyMap(t *testing.T) {
6581+
m := map[string]int{"one": 1, "two": 2, "three": 3}
6582+
iter := ValueOf(m).MapRange()
6583+
if got, want := iterateToString(iter), `[one: 1, three: 3, two: 2]`; got != want {
6584+
t.Errorf("iterator returned %s (after sorting), want %s", got, want)
6585+
}
6586+
}
6587+
6588+
func TestMapIterNilMap(t *testing.T) {
6589+
var m map[string]int
6590+
iter := ValueOf(m).MapRange()
6591+
if got, want := iterateToString(iter), `[]`; got != want {
6592+
t.Errorf("non-empty result iteratoring nil map: %s", got)
6593+
}
6594+
}
6595+
6596+
func TestMapIterSafety(t *testing.T) {
6597+
// Using a zero MapIter causes a panic, but not a crash.
6598+
func() {
6599+
defer func() { recover() }()
6600+
new(MapIter).Key()
6601+
t.Fatal("Key did not panic")
6602+
}()
6603+
func() {
6604+
defer func() { recover() }()
6605+
new(MapIter).Value()
6606+
t.Fatal("Value did not panic")
6607+
}()
6608+
func() {
6609+
defer func() { recover() }()
6610+
new(MapIter).Next()
6611+
t.Fatal("Next did not panic")
6612+
}()
6613+
6614+
// Calling Key/Value on a MapIter before Next
6615+
// causes a panic, but not a crash.
6616+
var m map[string]int
6617+
iter := ValueOf(m).MapRange()
6618+
6619+
func() {
6620+
defer func() { recover() }()
6621+
iter.Key()
6622+
t.Fatal("Key did not panic")
6623+
}()
6624+
func() {
6625+
defer func() { recover() }()
6626+
iter.Value()
6627+
t.Fatal("Value did not panic")
6628+
}()
6629+
6630+
// Calling Next, Key, or Value on an exhausted iterator
6631+
// causes a panic, but not a crash.
6632+
iter.Next() // -> false
6633+
func() {
6634+
defer func() { recover() }()
6635+
iter.Key()
6636+
t.Fatal("Key did not panic")
6637+
}()
6638+
func() {
6639+
defer func() { recover() }()
6640+
iter.Value()
6641+
t.Fatal("Value did not panic")
6642+
}()
6643+
func() {
6644+
defer func() { recover() }()
6645+
iter.Next()
6646+
t.Fatal("Next did not panic")
6647+
}()
6648+
}
6649+
6650+
func TestMapIterNext(t *testing.T) {
6651+
// The first call to Next should reflect any
6652+
// insertions to the map since the iterator was created.
6653+
m := map[string]int{}
6654+
iter := ValueOf(m).MapRange()
6655+
m["one"] = 1
6656+
if got, want := iterateToString(iter), `[one: 1]`; got != want {
6657+
t.Errorf("iterator returned deleted elements: got %s, want %s", got, want)
6658+
}
6659+
}
6660+
6661+
func TestMapIterDelete0(t *testing.T) {
6662+
// Delete all elements before first iteration.
6663+
m := map[string]int{"one": 1, "two": 2, "three": 3}
6664+
iter := ValueOf(m).MapRange()
6665+
delete(m, "one")
6666+
delete(m, "two")
6667+
delete(m, "three")
6668+
if got, want := iterateToString(iter), `[]`; got != want {
6669+
t.Errorf("iterator returned deleted elements: got %s, want %s", got, want)
6670+
}
6671+
}
6672+
6673+
func TestMapIterDelete1(t *testing.T) {
6674+
// Delete all elements after first iteration.
6675+
m := map[string]int{"one": 1, "two": 2, "three": 3}
6676+
iter := ValueOf(m).MapRange()
6677+
var got []string
6678+
for iter.Next() {
6679+
got = append(got, fmt.Sprint(iter.Key(), iter.Value()))
6680+
delete(m, "one")
6681+
delete(m, "two")
6682+
delete(m, "three")
6683+
}
6684+
if len(got) != 1 {
6685+
t.Errorf("iterator returned wrong number of elements: got %d, want 1", len(got))
6686+
}
6687+
}
6688+
6689+
// iterateToString returns the set of elements
6690+
// returned by an iterator in readable form.
6691+
func iterateToString(it *MapIter) string {
6692+
var got []string
6693+
for it.Next() {
6694+
line := fmt.Sprintf("%v: %v", it.Key(), it.Value())
6695+
got = append(got, line)
6696+
}
6697+
sort.Strings(got)
6698+
return "[" + strings.Join(got, ", ") + "]"
6699+
}

src/reflect/value.go

Lines changed: 89 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1085,14 +1085,7 @@ func (v Value) MapIndex(key Value) Value {
10851085
typ := tt.elem
10861086
fl := (v.flag | key.flag).ro()
10871087
fl |= flag(typ.Kind())
1088-
if !ifaceIndir(typ) {
1089-
return Value{typ, *(*unsafe.Pointer)(e), fl}
1090-
}
1091-
// Copy result so future changes to the map
1092-
// won't change the underlying value.
1093-
c := unsafe_New(typ)
1094-
typedmemmove(typ, c, e)
1095-
return Value{typ, c, fl | flagIndir}
1088+
return copyVal(typ, fl, e)
10961089
}
10971090

10981091
// MapKeys returns a slice containing all the keys present in the map,
@@ -1122,20 +1115,96 @@ func (v Value) MapKeys() []Value {
11221115
// we can do about it.
11231116
break
11241117
}
1125-
if ifaceIndir(keyType) {
1126-
// Copy result so future changes to the map
1127-
// won't change the underlying value.
1128-
c := unsafe_New(keyType)
1129-
typedmemmove(keyType, c, key)
1130-
a[i] = Value{keyType, c, fl | flagIndir}
1131-
} else {
1132-
a[i] = Value{keyType, *(*unsafe.Pointer)(key), fl}
1133-
}
1118+
a[i] = copyVal(keyType, fl, key)
11341119
mapiternext(it)
11351120
}
11361121
return a[:i]
11371122
}
11381123

1124+
// A MapIter is an iterator for ranging over a map.
1125+
// See Value.MapRange.
1126+
type MapIter struct {
1127+
m Value
1128+
it unsafe.Pointer
1129+
}
1130+
1131+
// Key returns the key of the iterator's current map entry.
1132+
func (it *MapIter) Key() Value {
1133+
if it.it == nil {
1134+
panic("MapIter.Key called before Next")
1135+
}
1136+
if mapiterkey(it.it) == nil {
1137+
panic("MapIter.Key called on exhausted iterator")
1138+
}
1139+
1140+
t := (*mapType)(unsafe.Pointer(it.m.typ))
1141+
ktype := t.key
1142+
return copyVal(ktype, it.m.flag.ro()|flag(ktype.Kind()), mapiterkey(it.it))
1143+
}
1144+
1145+
// Value returns the value of the iterator's current map entry.
1146+
func (it *MapIter) Value() Value {
1147+
if it.it == nil {
1148+
panic("MapIter.Value called before Next")
1149+
}
1150+
if mapiterkey(it.it) == nil {
1151+
panic("MapIter.Value called on exhausted iterator")
1152+
}
1153+
1154+
t := (*mapType)(unsafe.Pointer(it.m.typ))
1155+
vtype := t.elem
1156+
return copyVal(vtype, it.m.flag.ro()|flag(vtype.Kind()), mapitervalue(it.it))
1157+
}
1158+
1159+
// Next advances the map iterator and reports whether there is another
1160+
// entry. It returns false when the iterator is exhausted; subsequent
1161+
// calls to Key, Value, or Next will panic.
1162+
func (it *MapIter) Next() bool {
1163+
if it.it == nil {
1164+
it.it = mapiterinit(it.m.typ, it.m.pointer())
1165+
} else {
1166+
if mapiterkey(it.it) == nil {
1167+
panic("MapIter.Next called on exhausted iterator")
1168+
}
1169+
mapiternext(it.it)
1170+
}
1171+
return mapiterkey(it.it) != nil
1172+
}
1173+
1174+
// MapRange returns a range iterator for a map.
1175+
// It panics if v's Kind is not Map.
1176+
//
1177+
// Call Next to advance the iterator, and Key/Value to access each entry.
1178+
// Next returns false when the iterator is exhausted.
1179+
// MapRange follows the same iteration semantics as a range statement.
1180+
//
1181+
// Example:
1182+
//
1183+
// iter := reflect.ValueOf(m).MapRange()
1184+
// for iter.Next() {
1185+
// k := iter.Key()
1186+
// v := iter.Value()
1187+
// ...
1188+
// }
1189+
//
1190+
func (v Value) MapRange() *MapIter {
1191+
v.mustBe(Map)
1192+
return &MapIter{m: v}
1193+
}
1194+
1195+
// copyVal returns a Value containing the map key or value at ptr,
1196+
// allocating a new variable as needed.
1197+
func copyVal(typ *rtype, fl flag, ptr unsafe.Pointer) Value {
1198+
if ifaceIndir(typ) {
1199+
// Copy result so future changes to the map
1200+
// won't change the underlying value.
1201+
c := unsafe_New(typ)
1202+
typedmemmove(typ, c, ptr)
1203+
return Value{typ, c, fl | flagIndir}
1204+
}
1205+
return Value{typ, *(*unsafe.Pointer)(ptr), fl}
1206+
}
1207+
11391208
// Method returns a function value corresponding to v's i'th method.
11401209
// The arguments to a Call on the returned function should not include
11411210
// a receiver; the returned function will always use v as the receiver.
@@ -2554,6 +2623,9 @@ func mapiterinit(t *rtype, m unsafe.Pointer) unsafe.Pointer
25542623
//go:noescape
25552624
func mapiterkey(it unsafe.Pointer) (key unsafe.Pointer)
25562625

2626+
//go:noescape
2627+
func mapitervalue(it unsafe.Pointer) (value unsafe.Pointer)
2628+
25572629
//go:noescape
25582630
func mapiternext(it unsafe.Pointer)
25592631

src/runtime/map.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1282,6 +1282,11 @@ func reflect_mapiterkey(it *hiter) unsafe.Pointer {
12821282
return it.key
12831283
}
12841284

1285+
//go:linkname reflect_mapitervalue reflect.mapitervalue
1286+
func reflect_mapitervalue(it *hiter) unsafe.Pointer {
1287+
return it.value
1288+
}
1289+
12851290
//go:linkname reflect_maplen reflect.maplen
12861291
func reflect_maplen(h *hmap) int {
12871292
if h == nil {

0 commit comments

Comments
 (0)