]> Cypherpunks repositories - gostls13.git/commitdiff
reflect: add iterator equivalents for NumField, NumIn, NumOut and NumMethod
authorQuentin Quaadgras <quentin@splizard.com>
Wed, 19 Nov 2025 21:18:39 +0000 (21:18 +0000)
committerAlan Donovan <adonovan@google.com>
Tue, 25 Nov 2025 17:18:42 +0000 (09:18 -0800)
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 three-clause for loops where possible.

Fixes #66631

Change-Id: Iab346e52c0eadd7817afae96d9ef73a35db65fd2
GitHub-Last-Rev: 8768ef71b9fd74536094cb1072c7075dc732cd5c
GitHub-Pull-Request: golang/go#75646
Reviewed-on: https://go-review.googlesource.com/c/go/+/707356
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Reviewed-by: Alan Donovan <adonovan@google.com>
Auto-Submit: Alan Donovan <adonovan@google.com>

api/next/66631.txt [new file with mode: 0644]
doc/next/6-stdlib/99-minor/reflect/66631.md [new file with mode: 0644]
src/reflect/abi_test.go
src/reflect/all_test.go
src/reflect/benchmark_test.go
src/reflect/example_test.go
src/reflect/type.go
src/reflect/value.go

diff --git a/api/next/66631.txt b/api/next/66631.txt
new file mode 100644 (file)
index 0000000..ffc068b
--- /dev/null
@@ -0,0 +1,6 @@
+pkg reflect, type Type interface, Fields() iter.Seq[StructField] #66631
+pkg reflect, type Type interface, Methods() iter.Seq[Method] #66631
+pkg reflect, type Type interface, Ins() iter.Seq[Type] #66631
+pkg reflect, type Type interface, Outs() iter.Seq[Type] #66631
+pkg reflect, method (Value) Fields() iter.Seq2[StructField, Value] #66631
+pkg reflect, method (Value) Methods() iter.Seq2[Method, Value] #66631
diff --git a/doc/next/6-stdlib/99-minor/reflect/66631.md b/doc/next/6-stdlib/99-minor/reflect/66631.md
new file mode 100644 (file)
index 0000000..ec5a04c
--- /dev/null
@@ -0,0 +1,4 @@
+[reflect.Type] includes new methods that return iterators for a type's fields, methods, inputs and outputs.
+Similarly, [reflect.Value] includes two new methods that return iterators over a value's fields or methods, 
+each element being a pair of the value ([reflect.Value]) and its type information ([reflect.StructField] or 
+[reflect.Method]).
index 9d93472779012ba5a1d2a5694a2872787e0b789a..576f288903dada930ceff46f71d3401167b2574e 100644 (file)
@@ -175,8 +175,8 @@ func TestReflectCallABI(t *testing.T) {
                                t.Fatalf("test case has different number of inputs and outputs: %d in, %d out", typ.NumIn(), typ.NumOut())
                        }
                        var args []reflect.Value
-                       for i := 0; i < typ.NumIn(); i++ {
-                               args = append(args, genValue(t, typ.In(i), r))
+                       for arg := range typ.Ins() {
+                               args = append(args, genValue(t, arg, r))
                        }
                        results := fn.Call(args)
                        for i := range results {
index 30ec3fad512f3bdcaae2300a10b3ded05741ead4..54a80e98c7eb5fb22a3751c7467aee883bb63908 100644 (file)
@@ -8491,8 +8491,7 @@ func TestInitFuncTypes(t *testing.T) {
                go func() {
                        defer wg.Done()
                        ipT := TypeOf(net.IP{})
-                       for i := 0; i < ipT.NumMethod(); i++ {
-                               _ = ipT.Method(i)
+                       for range ipT.Methods() {
                        }
                }()
        }
index 6b2f9ce7a0381ac0eb8ccee2945fbaf2eb9afbd0..d5cea2becf52654570a66c721ba92f1dd564ba8b 100644 (file)
@@ -146,10 +146,8 @@ func BenchmarkIsZero(b *testing.B) {
        s.ArrayInt_1024_NoZero[512] = 1
        source := ValueOf(s)
 
-       for i := 0; i < source.NumField(); i++ {
-               name := source.Type().Field(i).Name
-               value := source.Field(i)
-               b.Run(name, func(b *testing.B) {
+       for field, value := range source.Fields() {
+               b.Run(field.Name, func(b *testing.B) {
                        for i := 0; i < b.N; i++ {
                                sink = value.IsZero()
                        }
@@ -175,9 +173,8 @@ func BenchmarkSetZero(b *testing.B) {
                Struct    Value
        })).Elem()
 
-       for i := 0; i < source.NumField(); i++ {
-               name := source.Type().Field(i).Name
-               value := source.Field(i)
+       for field, value := range source.Fields() {
+               name := field.Name
                zero := Zero(value.Type())
                b.Run(name+"/Direct", func(b *testing.B) {
                        for i := 0; i < b.N; i++ {
index b4f3b2932f78c368492d5d46d86319776676ce77..bcc2303766e87e992d156c50694eb2cca0cabff8 100644 (file)
@@ -96,8 +96,7 @@ func ExampleStructTag_Lookup() {
 
        s := S{}
        st := reflect.TypeOf(s)
-       for i := 0; i < st.NumField(); i++ {
-               field := st.Field(i)
+       for field := range st.Fields() {
                if alias, ok := field.Tag.Lookup("alias"); ok {
                        if alias == "" {
                                fmt.Println("(blank)")
index 914b5443f3500eb9419b9eb72a4fe669187324b7..a2cab6eb7a5e4a4eec334e03f8fda9c0c5ad248e 100644 (file)
@@ -18,6 +18,7 @@ package reflect
 import (
        "internal/abi"
        "internal/goarch"
+       "iter"
        "runtime"
        "strconv"
        "sync"
@@ -64,6 +65,10 @@ type Type interface {
        // This may make the executable binary larger but will not affect execution time.
        Method(int) Method
 
+       // Methods returns an iterator over each method in the type's method set. The sequence is
+       // equivalent to calling Method successively for each index i in the range [0, NumMethod()).
+       Methods() iter.Seq[Method]
+
        // MethodByName returns the method with that name in the type's
        // method set and a boolean indicating if the method was found.
        //
@@ -172,6 +177,11 @@ type Type interface {
        // It panics if i is not in the range [0, NumField()).
        Field(i int) StructField
 
+       // Fields returns an iterator over each struct field for struct type t. The sequence is
+       // equivalent to calling Field successively for each index i in the range [0, NumField()).
+       // It panics if the type's Kind is not Struct.
+       Fields() iter.Seq[StructField]
+
        // FieldByIndex returns the nested field corresponding
        // to the index sequence. It is equivalent to calling Field
        // successively for each index i.
@@ -208,6 +218,11 @@ type Type interface {
        // It panics if i is not in the range [0, NumIn()).
        In(i int) Type
 
+       // Ins returns an iterator over each input parameter of function type t. The sequence
+       // is equivalent to calling In successively for each index i in the range [0, NumIn()).
+       // It panics if the type's Kind is not Func.
+       Ins() iter.Seq[Type]
+
        // Key returns a map type's key type.
        // It panics if the type's Kind is not Map.
        Key() Type
@@ -233,6 +248,11 @@ type Type interface {
        // It panics if i is not in the range [0, NumOut()).
        Out(i int) Type
 
+       // Outs returns an iterator over each output parameter of function type t. The sequence
+       // is equivalent to calling Out successively for each index i in the range [0, NumOut()).
+       // It panics if the type's Kind is not Func.
+       Outs() iter.Seq[Type]
+
        // OverflowComplex reports whether the complex128 x cannot be represented by type t.
        // It panics if t's Kind is not Complex64 or Complex128.
        OverflowComplex(x complex128) bool
@@ -937,6 +957,55 @@ func canRangeFunc2(t *abi.Type) bool {
        return yield.InCount == 2 && yield.OutCount == 1 && yield.Out(0).Kind() == abi.Bool
 }
 
+func (t *rtype) Fields() iter.Seq[StructField] {
+       if t.Kind() != Struct {
+               panic("reflect: Fields of non-struct type " + t.String())
+       }
+       return func(yield func(StructField) bool) {
+               for i := range t.NumField() {
+                       if !yield(t.Field(i)) {
+                               return
+                       }
+               }
+       }
+}
+
+func (t *rtype) Methods() iter.Seq[Method] {
+       return func(yield func(Method) bool) {
+               for i := range t.NumMethod() {
+                       if !yield(t.Method(i)) {
+                               return
+                       }
+               }
+       }
+}
+
+func (t *rtype) Ins() iter.Seq[Type] {
+       if t.Kind() != Func {
+               panic("reflect: Ins of non-func type " + t.String())
+       }
+       return func(yield func(Type) bool) {
+               for i := range t.NumIn() {
+                       if !yield(t.In(i)) {
+                               return
+                       }
+               }
+       }
+}
+
+func (t *rtype) Outs() iter.Seq[Type] {
+       if t.Kind() != Func {
+               panic("reflect: Outs of non-func type " + t.String())
+       }
+       return func(yield func(Type) bool) {
+               for i := range t.NumOut() {
+                       if !yield(t.Out(i)) {
+                               return
+                       }
+               }
+       }
+}
+
 // add returns p+x.
 //
 // The whySafe string is ignored, so that the function still inlines
index a82d976c4787352e22c942a82a92a5d0e21e0e63..7f0ec2a397a3c4fb8458d81dd1bd7f7a8fccb83a 100644 (file)
@@ -10,6 +10,7 @@ import (
        "internal/goarch"
        "internal/strconv"
        "internal/unsafeheader"
+       "iter"
        "math"
        "runtime"
        "unsafe"
@@ -2631,6 +2632,48 @@ func (v Value) UnsafePointer() unsafe.Pointer {
        panic(&ValueError{"reflect.Value.UnsafePointer", v.kind()})
 }
 
+// Fields returns an iterator over each [StructField] of v along with its [Value].
+//
+// The sequence is equivalent to calling [Value.Field] successively
+// for each index i in the range [0, NumField()).
+//
+// It panics if v's Kind is not Struct.
+func (v Value) Fields() iter.Seq2[StructField, Value] {
+       t := v.Type()
+       if t.Kind() != Struct {
+               panic("reflect: Fields of non-struct type " + t.String())
+       }
+       return func(yield func(StructField, Value) bool) {
+               for i := range v.NumField() {
+                       if !yield(t.Field(i), v.Field(i)) {
+                               return
+                       }
+               }
+       }
+}
+
+// Methods returns an iterator over each [Method] of v along with the corresponding
+// method [Value]; this is a function with v bound as the receiver. As such, the
+// receiver shouldn't be included in the arguments to [Value.Call].
+//
+// The sequence is equivalent to calling [Value.Method] successively
+// for each index i in the range [0, NumMethod()).
+//
+// Methods panics if v is a nil interface value.
+//
+// Calling this method will force the linker to retain all exported methods in all packages.
+// This may make the executable binary larger but will not affect execution time.
+func (v Value) Methods() iter.Seq2[Method, Value] {
+       return func(yield func(Method, Value) bool) {
+               rtype := v.Type()
+               for i := range v.NumMethod() {
+                       if !yield(rtype.Method(i), v.Method(i)) {
+                               return
+                       }
+               }
+       }
+}
+
 // StringHeader is the runtime representation of a string.
 // It cannot be used safely or portably and its representation may
 // change in a later release.
@@ -3232,8 +3275,8 @@ func (v Value) Comparable() bool {
                return v.IsNil() || v.Elem().Comparable()
 
        case Struct:
-               for i := 0; i < v.NumField(); i++ {
-                       if !v.Field(i).Comparable() {
+               for _, value := range v.Fields() {
+                       if !value.Comparable() {
                                return false
                        }
                }