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 } }