From e8fdfeb72b0468b645f256bcaf46570f866a54fd Mon Sep 17 00:00:00 2001 From: Quentin Quaadgras Date: Wed, 19 Nov 2025 21:18:39 +0000 Subject: [PATCH] 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 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 Reviewed-by: Cherry Mui Reviewed-by: Alan Donovan Auto-Submit: Alan Donovan --- api/next/66631.txt | 6 ++ doc/next/6-stdlib/99-minor/reflect/66631.md | 4 ++ src/reflect/abi_test.go | 4 +- src/reflect/all_test.go | 3 +- src/reflect/benchmark_test.go | 11 ++-- src/reflect/example_test.go | 3 +- src/reflect/type.go | 69 +++++++++++++++++++++ src/reflect/value.go | 47 +++++++++++++- 8 files changed, 132 insertions(+), 15 deletions(-) create mode 100644 api/next/66631.txt create mode 100644 doc/next/6-stdlib/99-minor/reflect/66631.md diff --git a/api/next/66631.txt b/api/next/66631.txt new file mode 100644 index 0000000000..ffc068bde6 --- /dev/null +++ b/api/next/66631.txt @@ -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 index 0000000000..ec5a04ca88 --- /dev/null +++ b/doc/next/6-stdlib/99-minor/reflect/66631.md @@ -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]). diff --git a/src/reflect/abi_test.go b/src/reflect/abi_test.go index 9d93472779..576f288903 100644 --- a/src/reflect/abi_test.go +++ b/src/reflect/abi_test.go @@ -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 { diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go index 30ec3fad51..54a80e98c7 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -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() { } }() } diff --git a/src/reflect/benchmark_test.go b/src/reflect/benchmark_test.go index 6b2f9ce7a0..d5cea2becf 100644 --- a/src/reflect/benchmark_test.go +++ b/src/reflect/benchmark_test.go @@ -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++ { diff --git a/src/reflect/example_test.go b/src/reflect/example_test.go index b4f3b2932f..bcc2303766 100644 --- a/src/reflect/example_test.go +++ b/src/reflect/example_test.go @@ -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)") diff --git a/src/reflect/type.go b/src/reflect/type.go index 914b5443f3..a2cab6eb7a 100644 --- a/src/reflect/type.go +++ b/src/reflect/type.go @@ -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 diff --git a/src/reflect/value.go b/src/reflect/value.go index a82d976c47..7f0ec2a397 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -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 } } -- 2.52.0