From 5bf50a001e1dd7c3f5d7e0f241f5920b83b1a662 Mon Sep 17 00:00:00 2001 From: Quentin Quaadgras Date: Mon, 29 Sep 2025 11:45:36 +1300 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 any three-clause for loops where possible. Fixes #66631 --- api/next/66631.txt | 6 ++ doc/next/6-stdlib/99-minor/reflect/66631.md | 3 + 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, 131 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 00000000000000..ffc068bde636aa --- /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 00000000000000..9b452b817a1809 --- /dev/null +++ b/doc/next/6-stdlib/99-minor/reflect/66631.md @@ -0,0 +1,3 @@ +[reflect.Type] includes new methods that return iterators for a type's fields, methods, inputs and outputs. +[reflect.Value.Fields] and [reflect.Value.Methods] have also been introduced, these return iterators which +yield both a [reflect.Value] as well as the corresponding [reflect.StructField] or [reflect.Method]. diff --git a/src/reflect/abi_test.go b/src/reflect/abi_test.go index 9d93472779012b..576f288903dada 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 2a8c5206624146..68dc959c035980 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -8505,8 +8505,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 6b2f9ce7a0381a..d5cea2becf5265 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 b4f3b2932f78c3..bcc2303766e87e 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 fc6edb1e106751..d870eb6293b704 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 @@ -934,6 +954,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 c0ac45de77be85..65719d56437f44 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -10,6 +10,7 @@ import ( "internal/goarch" "internal/itoa" "internal/unsafeheader" + "iter" "math" "runtime" "unsafe" @@ -2620,6 +2621,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] { + if t.Kind() != Struct { + panic("reflect: Fields of non-struct type " + t.String()) + } + return func(yield func(StructField, Value) bool) { + rtype := v.Type() + for i := range v.NumField() { + if !yield(rtype.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. @@ -3221,8 +3264,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 } }