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: 8768ef71b9
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>
This commit is contained in:
Quentin Quaadgras 2025-11-19 21:18:39 +00:00 committed by Alan Donovan
parent 12d437c09a
commit e8fdfeb72b
8 changed files with 132 additions and 15 deletions

6
api/next/66631.txt Normal file
View file

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

View file

@ -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]).

View 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()) t.Fatalf("test case has different number of inputs and outputs: %d in, %d out", typ.NumIn(), typ.NumOut())
} }
var args []reflect.Value var args []reflect.Value
for i := 0; i < typ.NumIn(); i++ { for arg := range typ.Ins() {
args = append(args, genValue(t, typ.In(i), r)) args = append(args, genValue(t, arg, r))
} }
results := fn.Call(args) results := fn.Call(args)
for i := range results { for i := range results {

View file

@ -8491,8 +8491,7 @@ func TestInitFuncTypes(t *testing.T) {
go func() { go func() {
defer wg.Done() defer wg.Done()
ipT := TypeOf(net.IP{}) ipT := TypeOf(net.IP{})
for i := 0; i < ipT.NumMethod(); i++ { for range ipT.Methods() {
_ = ipT.Method(i)
} }
}() }()
} }

View file

@ -146,10 +146,8 @@ func BenchmarkIsZero(b *testing.B) {
s.ArrayInt_1024_NoZero[512] = 1 s.ArrayInt_1024_NoZero[512] = 1
source := ValueOf(s) source := ValueOf(s)
for i := 0; i < source.NumField(); i++ { for field, value := range source.Fields() {
name := source.Type().Field(i).Name b.Run(field.Name, func(b *testing.B) {
value := source.Field(i)
b.Run(name, func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
sink = value.IsZero() sink = value.IsZero()
} }
@ -175,9 +173,8 @@ func BenchmarkSetZero(b *testing.B) {
Struct Value Struct Value
})).Elem() })).Elem()
for i := 0; i < source.NumField(); i++ { for field, value := range source.Fields() {
name := source.Type().Field(i).Name name := field.Name
value := source.Field(i)
zero := Zero(value.Type()) zero := Zero(value.Type())
b.Run(name+"/Direct", func(b *testing.B) { b.Run(name+"/Direct", func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {

View file

@ -96,8 +96,7 @@ func ExampleStructTag_Lookup() {
s := S{} s := S{}
st := reflect.TypeOf(s) st := reflect.TypeOf(s)
for i := 0; i < st.NumField(); i++ { for field := range st.Fields() {
field := st.Field(i)
if alias, ok := field.Tag.Lookup("alias"); ok { if alias, ok := field.Tag.Lookup("alias"); ok {
if alias == "" { if alias == "" {
fmt.Println("(blank)") fmt.Println("(blank)")

View file

@ -18,6 +18,7 @@ package reflect
import ( import (
"internal/abi" "internal/abi"
"internal/goarch" "internal/goarch"
"iter"
"runtime" "runtime"
"strconv" "strconv"
"sync" "sync"
@ -64,6 +65,10 @@ type Type interface {
// This may make the executable binary larger but will not affect execution time. // This may make the executable binary larger but will not affect execution time.
Method(int) Method 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 // MethodByName returns the method with that name in the type's
// method set and a boolean indicating if the method was found. // 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()). // It panics if i is not in the range [0, NumField()).
Field(i int) StructField 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 // FieldByIndex returns the nested field corresponding
// to the index sequence. It is equivalent to calling Field // to the index sequence. It is equivalent to calling Field
// successively for each index i. // successively for each index i.
@ -208,6 +218,11 @@ type Type interface {
// It panics if i is not in the range [0, NumIn()). // It panics if i is not in the range [0, NumIn()).
In(i int) Type 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. // Key returns a map type's key type.
// It panics if the type's Kind is not Map. // It panics if the type's Kind is not Map.
Key() Type Key() Type
@ -233,6 +248,11 @@ type Type interface {
// It panics if i is not in the range [0, NumOut()). // It panics if i is not in the range [0, NumOut()).
Out(i int) Type 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. // OverflowComplex reports whether the complex128 x cannot be represented by type t.
// It panics if t's Kind is not Complex64 or Complex128. // It panics if t's Kind is not Complex64 or Complex128.
OverflowComplex(x complex128) bool 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 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. // add returns p+x.
// //
// The whySafe string is ignored, so that the function still inlines // The whySafe string is ignored, so that the function still inlines

View file

@ -10,6 +10,7 @@ import (
"internal/goarch" "internal/goarch"
"internal/strconv" "internal/strconv"
"internal/unsafeheader" "internal/unsafeheader"
"iter"
"math" "math"
"runtime" "runtime"
"unsafe" "unsafe"
@ -2631,6 +2632,48 @@ func (v Value) UnsafePointer() unsafe.Pointer {
panic(&ValueError{"reflect.Value.UnsafePointer", v.kind()}) 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. // StringHeader is the runtime representation of a string.
// It cannot be used safely or portably and its representation may // It cannot be used safely or portably and its representation may
// change in a later release. // change in a later release.
@ -3232,8 +3275,8 @@ func (v Value) Comparable() bool {
return v.IsNil() || v.Elem().Comparable() return v.IsNil() || v.Elem().Comparable()
case Struct: case Struct:
for i := 0; i < v.NumField(); i++ { for _, value := range v.Fields() {
if !v.Field(i).Comparable() { if !value.Comparable() {
return false return false
} }
} }