Skip to content

Commit 5bf50a0

Browse files
committed
reflect: add iterator equivalents for NumField, NumIn, NumOut and NumMethod
The new methods are Type.Fields, Type.Methods, Type.Ins, Type.Outs, Value.Fields and Value.Methods. These methods have been introduced into the reflect package (as well as tests) replacing any three-clause for loops where possible. Fixes golang#66631
1 parent 34e6762 commit 5bf50a0

File tree

8 files changed

+131
-15
lines changed

8 files changed

+131
-15
lines changed

api/next/66631.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pkg reflect, type Type interface, Fields() iter.Seq[StructField] #66631
2+
pkg reflect, type Type interface, Methods() iter.Seq[Method] #66631
3+
pkg reflect, type Type interface, Ins() iter.Seq[Type] #66631
4+
pkg reflect, type Type interface, Outs() iter.Seq[Type] #66631
5+
pkg reflect, method (Value) Fields() iter.Seq2[StructField, Value] #66631
6+
pkg reflect, method (Value) Methods() iter.Seq2[Method, Value] #66631
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[reflect.Type] includes new methods that return iterators for a type's fields, methods, inputs and outputs.
2+
[reflect.Value.Fields] and [reflect.Value.Methods] have also been introduced, these return iterators which
3+
yield both a [reflect.Value] as well as the corresponding [reflect.StructField] or [reflect.Method].

src/reflect/abi_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,8 @@ func TestReflectCallABI(t *testing.T) {
175175
t.Fatalf("test case has different number of inputs and outputs: %d in, %d out", typ.NumIn(), typ.NumOut())
176176
}
177177
var args []reflect.Value
178-
for i := 0; i < typ.NumIn(); i++ {
179-
args = append(args, genValue(t, typ.In(i), r))
178+
for arg := range typ.Ins() {
179+
args = append(args, genValue(t, arg, r))
180180
}
181181
results := fn.Call(args)
182182
for i := range results {

src/reflect/all_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8505,8 +8505,7 @@ func TestInitFuncTypes(t *testing.T) {
85058505
go func() {
85068506
defer wg.Done()
85078507
ipT := TypeOf(net.IP{})
8508-
for i := 0; i < ipT.NumMethod(); i++ {
8509-
_ = ipT.Method(i)
8508+
for range ipT.Methods() {
85108509
}
85118510
}()
85128511
}

src/reflect/benchmark_test.go

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -146,10 +146,8 @@ func BenchmarkIsZero(b *testing.B) {
146146
s.ArrayInt_1024_NoZero[512] = 1
147147
source := ValueOf(s)
148148

149-
for i := 0; i < source.NumField(); i++ {
150-
name := source.Type().Field(i).Name
151-
value := source.Field(i)
152-
b.Run(name, func(b *testing.B) {
149+
for field, value := range source.Fields() {
150+
b.Run(field.Name, func(b *testing.B) {
153151
for i := 0; i < b.N; i++ {
154152
sink = value.IsZero()
155153
}
@@ -175,9 +173,8 @@ func BenchmarkSetZero(b *testing.B) {
175173
Struct Value
176174
})).Elem()
177175

178-
for i := 0; i < source.NumField(); i++ {
179-
name := source.Type().Field(i).Name
180-
value := source.Field(i)
176+
for field, value := range source.Fields() {
177+
name := field.Name
181178
zero := Zero(value.Type())
182179
b.Run(name+"/Direct", func(b *testing.B) {
183180
for i := 0; i < b.N; i++ {

src/reflect/example_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,7 @@ func ExampleStructTag_Lookup() {
9696

9797
s := S{}
9898
st := reflect.TypeOf(s)
99-
for i := 0; i < st.NumField(); i++ {
100-
field := st.Field(i)
99+
for field := range st.Fields() {
101100
if alias, ok := field.Tag.Lookup("alias"); ok {
102101
if alias == "" {
103102
fmt.Println("(blank)")

src/reflect/type.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package reflect
1818
import (
1919
"internal/abi"
2020
"internal/goarch"
21+
"iter"
2122
"runtime"
2223
"strconv"
2324
"sync"
@@ -64,6 +65,10 @@ type Type interface {
6465
// This may make the executable binary larger but will not affect execution time.
6566
Method(int) Method
6667

68+
// Methods returns an iterator over each method in the type's method set. The sequence is
69+
// equivalent to calling Method successively for each index i in the range [0, NumMethod()).
70+
Methods() iter.Seq[Method]
71+
6772
// MethodByName returns the method with that name in the type's
6873
// method set and a boolean indicating if the method was found.
6974
//
@@ -172,6 +177,11 @@ type Type interface {
172177
// It panics if i is not in the range [0, NumField()).
173178
Field(i int) StructField
174179

180+
// Fields returns an iterator over each struct field for struct type t. The sequence is
181+
// equivalent to calling Field successively for each index i in the range [0, NumField()).
182+
// It panics if the type's Kind is not Struct.
183+
Fields() iter.Seq[StructField]
184+
175185
// FieldByIndex returns the nested field corresponding
176186
// to the index sequence. It is equivalent to calling Field
177187
// successively for each index i.
@@ -208,6 +218,11 @@ type Type interface {
208218
// It panics if i is not in the range [0, NumIn()).
209219
In(i int) Type
210220

221+
// Ins returns an iterator over each input parameter of function type t. The sequence
222+
// is equivalent to calling In successively for each index i in the range [0, NumIn()).
223+
// It panics if the type's Kind is not Func.
224+
Ins() iter.Seq[Type]
225+
211226
// Key returns a map type's key type.
212227
// It panics if the type's Kind is not Map.
213228
Key() Type
@@ -233,6 +248,11 @@ type Type interface {
233248
// It panics if i is not in the range [0, NumOut()).
234249
Out(i int) Type
235250

251+
// Outs returns an iterator over each output parameter of function type t. The sequence
252+
// is equivalent to calling Out successively for each index i in the range [0, NumOut()).
253+
// It panics if the type's Kind is not Func.
254+
Outs() iter.Seq[Type]
255+
236256
// OverflowComplex reports whether the complex128 x cannot be represented by type t.
237257
// It panics if t's Kind is not Complex64 or Complex128.
238258
OverflowComplex(x complex128) bool
@@ -934,6 +954,55 @@ func canRangeFunc2(t *abi.Type) bool {
934954
return yield.InCount == 2 && yield.OutCount == 1 && yield.Out(0).Kind() == abi.Bool
935955
}
936956

957+
func (t *rtype) Fields() iter.Seq[StructField] {
958+
if t.Kind() != Struct {
959+
panic("reflect: Fields of non-struct type " + t.String())
960+
}
961+
return func(yield func(StructField) bool) {
962+
for i := range t.NumField() {
963+
if !yield(t.Field(i)) {
964+
return
965+
}
966+
}
967+
}
968+
}
969+
970+
func (t *rtype) Methods() iter.Seq[Method] {
971+
return func(yield func(Method) bool) {
972+
for i := range t.NumMethod() {
973+
if !yield(t.Method(i)) {
974+
return
975+
}
976+
}
977+
}
978+
}
979+
980+
func (t *rtype) Ins() iter.Seq[Type] {
981+
if t.Kind() != Func {
982+
panic("reflect: Ins of non-func type " + t.String())
983+
}
984+
return func(yield func(Type) bool) {
985+
for i := range t.NumIn() {
986+
if !yield(t.In(i)) {
987+
return
988+
}
989+
}
990+
}
991+
}
992+
993+
func (t *rtype) Outs() iter.Seq[Type] {
994+
if t.Kind() != Func {
995+
panic("reflect: Outs of non-func type " + t.String())
996+
}
997+
return func(yield func(Type) bool) {
998+
for i := range t.NumOut() {
999+
if !yield(t.Out(i)) {
1000+
return
1001+
}
1002+
}
1003+
}
1004+
}
1005+
9371006
// add returns p+x.
9381007
//
9391008
// The whySafe string is ignored, so that the function still inlines

src/reflect/value.go

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"internal/goarch"
1111
"internal/itoa"
1212
"internal/unsafeheader"
13+
"iter"
1314
"math"
1415
"runtime"
1516
"unsafe"
@@ -2620,6 +2621,48 @@ func (v Value) UnsafePointer() unsafe.Pointer {
26202621
panic(&ValueError{"reflect.Value.UnsafePointer", v.kind()})
26212622
}
26222623

2624+
// Fields returns an iterator over each [StructField] of v along with its [Value].
2625+
//
2626+
// The sequence is equivalent to calling [Value.Field] successively
2627+
// for each index i in the range [0, NumField()).
2628+
//
2629+
// It panics if v's Kind is not Struct.
2630+
func (v Value) Fields() iter.Seq2[StructField, Value] {
2631+
if t.Kind() != Struct {
2632+
panic("reflect: Fields of non-struct type " + t.String())
2633+
}
2634+
return func(yield func(StructField, Value) bool) {
2635+
rtype := v.Type()
2636+
for i := range v.NumField() {
2637+
if !yield(rtype.Field(i), v.Field(i)) {
2638+
return
2639+
}
2640+
}
2641+
}
2642+
}
2643+
2644+
// Methods returns an iterator over each [Method] of v along with the corresponding
2645+
// method [Value]; this is a function with v bound as the receiver. As such, the
2646+
// receiver shouldn't be included in the arguments to [Value.Call].
2647+
//
2648+
// The sequence is equivalent to calling [Value.Method] successively
2649+
// for each index i in the range [0, NumMethod()).
2650+
//
2651+
// Methods panics if v is a nil interface value.
2652+
//
2653+
// Calling this method will force the linker to retain all exported methods in all packages.
2654+
// This may make the executable binary larger but will not affect execution time.
2655+
func (v Value) Methods() iter.Seq2[Method, Value] {
2656+
return func(yield func(Method, Value) bool) {
2657+
rtype := v.Type()
2658+
for i := range v.NumMethod() {
2659+
if !yield(rtype.Method(i), v.Method(i)) {
2660+
return
2661+
}
2662+
}
2663+
}
2664+
}
2665+
26232666
// StringHeader is the runtime representation of a string.
26242667
// It cannot be used safely or portably and its representation may
26252668
// change in a later release.
@@ -3221,8 +3264,8 @@ func (v Value) Comparable() bool {
32213264
return v.IsNil() || v.Elem().Comparable()
32223265

32233266
case Struct:
3224-
for i := 0; i < v.NumField(); i++ {
3225-
if !v.Field(i).Comparable() {
3267+
for _, value := range v.Fields() {
3268+
if !value.Comparable() {
32263269
return false
32273270
}
32283271
}

0 commit comments

Comments
 (0)