1
0

refactor field list and field value list

This commit is contained in:
Arpad Ryszka 2025-09-03 22:12:13 +02:00
parent bcadfd8b71
commit a7b031f3b4
9 changed files with 330 additions and 221 deletions

203
field.go
View File

@ -51,76 +51,12 @@ func fieldFromType(name string, t reflect.Type) Field {
var f Field var f Field
ft := unpackType(t, pointer|slice) ft := unpackType(t, pointer|slice)
f.typ = scalarType(ft) f.typ = scalarType(ft)
f.size = scalarSize(ft)
f.list = acceptsList(t) f.list = acceptsList(t)
f.name = strcase.ToKebab(name) f.name = strcase.ToKebab(name)
f.path = []string{name} f.path = []string{name}
return f return f
} }
func nilField() Field {
var fi Field
fi.typ = Any
return fi
}
func fieldFromValue(v reflect.Value) Field {
var fi Field
fi.value = v.Interface()
fi.typ = scalarType(v.Type())
fi.size = valueSize(v)
return fi
}
func fields(t reflect.Type) []Field {
t = unpackType(t, pointer)
list := t.Kind() == reflect.Slice
t = unpackType(t, pointer|slice)
if !isStruct(t) {
return nil
}
var f []Field
for i := 0; i < t.NumField(); i++ {
tfi := t.Field(i)
if !exported(tfi.Name) && !tfi.Anonymous {
continue
}
if acceptsScalar(tfi.Type) {
fi := fieldFromType(tfi.Name, tfi.Type)
f = append(f, fi)
continue
}
ffi := fields(tfi.Type)
if !tfi.Anonymous {
for i := range ffi {
ffi[i].name = fmt.Sprintf(
"%s-%s",
strcase.ToKebab(tfi.Name),
ffi[i].name,
)
ffi[i].path = append(
[]string{tfi.Name},
ffi[i].path...,
)
}
}
f = append(f, ffi...)
}
if list {
for i := range f {
f[i].list = true
}
}
return f
}
func prependFieldName(name string, f []Field) { func prependFieldName(name string, f []Field) {
for i := range f { for i := range f {
if f[i].name == "" { if f[i].name == "" {
@ -134,19 +70,74 @@ func prependFieldName(name string, f []Field) {
} }
} }
func fields(t reflect.Type) []Field {
t = unpackType(t, pointer|slice)
if !isStruct(t) {
return nil
}
var f []Field
for i := 0; i < t.NumField(); i++ {
fi := t.Field(i)
if !exported(fi.Name) && !fi.Anonymous {
continue
}
if acceptsScalar(fi.Type) {
fi := fieldFromType(fi.Name, fi.Type)
f = append(f, fi)
continue
}
ffi := fields(fi.Type)
if !fi.Anonymous {
prependFieldName(fi.Name, ffi)
}
if acceptsList(fi.Type) {
for i := range ffi {
ffi[i].list = true
}
}
f = append(f, ffi...)
}
return f
}
func freeFields(f []Field) { func freeFields(f []Field) {
for i := range f { for i := range f {
f[i].free = true f[i].free = true
} }
} }
func scalarFieldValues(name string, t reflect.Type, v reflect.Value) []Field {
var f []Field
vv := unpackAllValues(v)
for _, vvi := range vv {
if !vvi.IsValid() || !isScalar(vvi.Type()) {
continue
}
fi := fieldFromType(name, t)
fi.value = vvi.Interface()
f = append(f, fi)
}
if len(f) == 0 {
f = append(f, fieldFromType(name, t))
}
return f
}
func scalarMapFields(v reflect.Value) []Field { func scalarMapFields(v reflect.Value) []Field {
var f []Field var f []Field
for _, key := range v.MapKeys() { for _, key := range v.MapKeys() {
name := key.Interface().(string) name := key.Interface().(string)
value := v.MapIndex(key) value := v.MapIndex(key)
fk := fieldValues(value) fk := scalarFieldValues(name, v.Type().Elem(), value)
prependFieldName(name, fk)
freeFields(fk) freeFields(fk)
f = append(f, fk...) f = append(f, fk...)
} }
@ -154,55 +145,60 @@ func scalarMapFields(v reflect.Value) []Field {
return f return f
} }
func fieldValues(v reflect.Value) []Field { func structFields(v reflect.Value) []Field {
if !v.IsValid() {
return []Field{nilField()}
}
var f []Field var f []Field
v = unpackValue(v, pointer|anytype|iface)
t := v.Type() t := v.Type()
if isScalar(t) { for i := 0; i < t.NumField(); i++ {
return []Field{fieldFromValue(v)} fi := t.Field(i)
if !exported(fi.Name) && !fi.Anonymous {
continue
} }
if v.Kind() == reflect.Slice { vi := v.Field(i)
for i := 0; i < v.Len(); i++ { if acceptsScalar(fi.Type) {
f = append(f, fieldValues(v.Index(i))...) f = append(f, scalarFieldValues(fi.Name, fi.Type, vi)...)
continue
}
ffi := fieldValues(vi)
if !fi.Anonymous {
prependFieldName(fi.Name, ffi)
}
if acceptsList(fi.Type) {
for i := range ffi {
ffi[i].list = true
}
}
f = append(f, ffi...)
} }
return f return f
} }
if isScalarMap(t) { func fieldValues(v reflect.Value) []Field {
return scalarMapFields(v) if !v.IsValid() {
}
if !isStruct(t) {
return nil return nil
} }
for i := 0; i < t.NumField(); i++ { var values []reflect.Value
tfi := t.Field(i) allValues := unpackAllValues(v)
if !exported(tfi.Name) && !tfi.Anonymous { for _, vi := range allValues {
if acceptsFields(vi.Type()) {
values = append(values, vi)
}
}
var f []Field
for _, vi := range values {
ti := vi.Type()
if isScalarMap(ti) {
f = append(f, scalarMapFields(vi)...)
continue continue
} }
vfi := v.Field(i) f = append(f, structFields(vi)...)
if tfi.Anonymous && !vfi.CanAddr() {
continue
}
vfi = unpackValue(vfi, pointer|iface|anytype)
switch {
case tfi.Anonymous:
ff := fieldValues(vfi)
f = append(f, ff...)
default:
ff := fieldValues(vfi)
prependFieldName(tfi.Name, ff)
f = append(f, ff...)
}
} }
return f return f
@ -218,11 +214,11 @@ func takeFieldValues(f []Field) []any {
} }
func bindScalarField(receiver reflect.Value, values []Field) bool { func bindScalarField(receiver reflect.Value, values []Field) bool {
v := takeFieldValues(values)
if !receiver.CanSet() { if !receiver.CanSet() {
return bindScalar(receiver, v) return false
} }
v := takeFieldValues(values)
rv, ok := allocate(receiver.Type(), len(v)) rv, ok := allocate(receiver.Type(), len(v))
if !ok { if !ok {
return false return false
@ -379,7 +375,8 @@ func bindField(receiver reflect.Value, values []Field) bool {
} }
if values[0].name == "" && len(values[0].path) == 0 { if values[0].name == "" && len(values[0].path) == 0 {
return bindScalarField(receiver, values) ret := bindScalarField(receiver, values)
return ret
} }
listReceiver := unpackValue(receiver, pointer|iface|anytype) listReceiver := unpackValue(receiver, pointer|iface|anytype)

View File

@ -51,6 +51,7 @@ func TestField(t *testing.T) {
Hola []int Hola []int
} }
t.Run("mixed", func(t *testing.T) {
f := bind.Fields[s2]() f := bind.Fields[s2]()
if len(f) != 6 { if len(f) != 6 {
t.Fatal(notation.Sprintwt(f)) t.Fatal(notation.Sprintwt(f))
@ -90,6 +91,7 @@ func TestField(t *testing.T) {
if !slices.Equal(hola.Path(), []string{"Hola"}) || !hola.List() { if !slices.Equal(hola.Path(), []string{"Hola"}) || !hola.List() {
t.Fatal(hola.Name()) t.Fatal(hola.Name())
} }
})
t.Run("cannot have fields", func(t *testing.T) { t.Run("cannot have fields", func(t *testing.T) {
type i []int type i []int
@ -109,19 +111,33 @@ func TestField(t *testing.T) {
t.Run("list", func(t *testing.T) { t.Run("list", func(t *testing.T) {
type s struct{ Foo int } type s struct{ Foo int }
f := bind.Fields[[]s]() f := bind.Fields[[]s]()
if len(f) != 1 || !f[0].List() { if len(f) != 1 || f[0].List() {
t.Fatal() t.Fatal()
} }
}) })
t.Run("anonymous fields", func(t *testing.T) {
type s0 struct{ Foo int }
type s1 struct {
s0
Bar int
}
f := bind.Fields[s1]()
if len(f) != 2 || f[0].Name() != "foo" || f[1].Name() != "bar" {
t.Fatal(notation.Sprint(f))
}
})
}) })
t.Run("field values", func(t *testing.T) { t.Run("field values", func(t *testing.T) {
t.Run("has circular reference", func(t *testing.T) { t.Run("has circular reference", func(t *testing.T) {
type s struct{ Foo any } type s struct{ Foo any }
var v s var v s
v.Foo = v v.Foo = &v
if len(bind.FieldValues(v)) != 0 { f := bind.FieldValues(v)
t.Fatal() if len(f) != 0 {
t.Fatal(notation.Sprint(f))
} }
}) })
@ -158,7 +174,7 @@ func TestField(t *testing.T) {
t.Run("not a struct", func(t *testing.T) { t.Run("not a struct", func(t *testing.T) {
v := []int{21, 42, 84} v := []int{21, 42, 84}
f := bind.FieldValues(v) f := bind.FieldValues(v)
if len(f) != 3 || f[0].Value() != 21 || f[1].Value() != 42 || f[2].Value() != 84 { if len(f) != 0 {
t.Fatal() t.Fatal()
} }
}) })
@ -217,7 +233,7 @@ func TestField(t *testing.T) {
type s struct{ Foo any } type s struct{ Foo any }
v := s{Foo: []any{map[string]int{"foo": 42}}} v := s{Foo: []any{map[string]int{"foo": 42}}}
f := bind.FieldValues(v) f := bind.FieldValues(v)
if len(f) != 1 || f[0].Name() != "foo-foo" || f[0].Value() != 42 { if len(f) != 1 || f[0].Name() != "foo" || f[0].Value() != nil {
t.Fatal(notation.Sprintwt(f)) t.Fatal(notation.Sprintwt(f))
} }
}) })
@ -226,9 +242,8 @@ func TestField(t *testing.T) {
type s struct{ Foo any } type s struct{ Foo any }
v := s{Foo: []any{s{Foo: 21}, s{Foo: 42}}} v := s{Foo: []any{s{Foo: 21}, s{Foo: 42}}}
f := bind.FieldValues(v) f := bind.FieldValues(v)
if len(f) != 2 || if len(f) != 1 ||
f[0].Name() != "foo-foo" || f[0].Value() != 21 || f[0].Name() != "foo" || f[0].Value() != nil {
f[1].Name() != "foo-foo" || f[1].Value() != 42 {
t.Fatal() t.Fatal()
} }
}) })
@ -248,12 +263,13 @@ func TestField(t *testing.T) {
s0 s0
Bar int Bar int
} }
var v s1 var v s1
v.Foo = 21 v.Foo = 21
v.Bar = 42 v.Bar = 42
f := bind.FieldValues(v) f := bind.FieldValues(v)
if len(f) != 1 || f[0].Name() != "bar" || f[0].Value() != 42 { if len(f) != 2 || f[0].Name() != "foo" || f[0].Value() != 21 || f[1].Name() != "bar" || f[1].Value() != 42 {
t.Fatal() t.Fatal(notation.Sprint(f))
} }
}) })
@ -273,6 +289,75 @@ func TestField(t *testing.T) {
t.Fatal(notation.Sprintwt(f)) t.Fatal(notation.Sprintwt(f))
} }
}) })
t.Run("any type field nil", func(t *testing.T) {
type s struct{ Foo any }
var v s
f := bind.FieldValues(v)
if len(f) != 1 || f[0].Name() != "foo" || f[0].Type() != bind.Any {
t.Fatal()
}
})
t.Run("any type field not nil", func(t *testing.T) {
type s struct{ Foo any }
v := s{42}
f := bind.FieldValues(v)
if len(f) != 1 || f[0].Name() != "foo" || f[0].Type() != bind.Any || f[0].Value() != 42 {
t.Fatal(notation.Sprintt(v))
}
})
t.Run("slice field empty", func(t *testing.T) {
type s struct{ Foo []int }
var v s
f := bind.FieldValues(v)
if len(f) != 1 || f[0].Name() != "foo" || !f[0].List() || f[0].Type() != bind.Int || f[0].Value() != nil {
t.Fatal(notation.Sprint(f))
}
})
t.Run("slice field not empty", func(t *testing.T) {
type s struct{ Foo []int }
v := s{[]int{21, 42, 84}}
f := bind.FieldValues(v)
if len(f) != 3 ||
f[0].Name() != "foo" || !f[0].List() || f[0].Type() != bind.Int || f[0].Value() != 21 ||
f[1].Name() != "foo" || !f[1].List() || f[1].Type() != bind.Int || f[1].Value() != 42 ||
f[2].Name() != "foo" || !f[2].List() || f[2].Type() != bind.Int || f[2].Value() != 84 {
t.Fatal()
}
})
t.Run("slice field with any type empty", func(t *testing.T) {
type s struct{ Foo []any }
var v s
f := bind.FieldValues(v)
if len(f) != 1 || f[0].Name() != "foo" || !f[0].List() || f[0].Type() != bind.Any || f[0].Value() != nil {
t.Fatal()
}
})
t.Run("slice field with any type not empty", func(t *testing.T) {
type s struct{ Foo []any }
v := s{[]any{21, 42, 84}}
f := bind.FieldValues(v)
if len(f) != 3 ||
f[0].Name() != "foo" || !f[0].List() || f[0].Type() != bind.Any || f[0].Value() != 21 ||
f[1].Name() != "foo" || !f[1].List() || f[1].Type() != bind.Any || f[1].Value() != 42 ||
f[2].Name() != "foo" || !f[2].List() || f[2].Type() != bind.Any || f[2].Value() != 84 {
t.Fatal(notation.Sprintw(f))
}
})
t.Run("struct in its own field via interface", func(t *testing.T) {
var s struct{ F any }
s.F = s
f := bind.FieldValues(s)
if len(f) != 1 || f[0].Name() != "f" || f[0].Value() != nil {
t.Fatal(notation.Sprint(f))
}
})
}) })
t.Run("bind fields", func(t *testing.T) { t.Run("bind fields", func(t *testing.T) {
@ -375,7 +460,7 @@ func TestField(t *testing.T) {
len(v.Foo[0].Bar) != 1 || v.Foo[0].Bar[0] != 21 || len(v.Foo[0].Bar) != 1 || v.Foo[0].Bar[0] != 21 ||
len(v.Foo[1].Bar) != 1 || v.Foo[1].Bar[0] != 42 || len(v.Foo[1].Bar) != 1 || v.Foo[1].Bar[0] != 42 ||
len(v.Foo[2].Bar) != 1 || v.Foo[2].Bar[0] != 84 { len(v.Foo[2].Bar) != 1 || v.Foo[2].Bar[0] != 84 {
t.Fatal() t.Fatal(notation.Sprintw(v))
} }
}) })

81
lib.go
View File

@ -1,18 +1,30 @@
// Package bind provides functions to work with flattened lists of structure fields. // Package bind provides functions to work with flattened lists of structure fields.
package bind package bind
// Scalar defines scalar types that can be used as field values and are not traversed. import "reflect"
type Scalar int
// Field type represents the possible types of a field.
type FieldType = reflect.Kind
const ( const (
Any Scalar = iota Bool FieldType = reflect.Bool
Bool Int FieldType = reflect.Int
Int Int8 FieldType = reflect.Int8
Uint Int16 FieldType = reflect.Int16
Float Int32 FieldType = reflect.Int32
String Int64 FieldType = reflect.Int64
Duration Uint FieldType = reflect.Uint
Time Uint8 FieldType = reflect.Uint8
Uint16 FieldType = reflect.Uint16
Uint32 FieldType = reflect.Uint32
Uint64 FieldType = reflect.Uint64
Uintptr FieldType = reflect.Uintptr
Float32 FieldType = reflect.Float32
Float64 FieldType = reflect.Float64
Any FieldType = reflect.Interface
Struct FieldType = reflect.Struct
Duration FieldType = 0xfffe
Time FieldType = 0xffff
) )
// Field is a field of structure or a compatible map. It is used as the output of the inspection functions, and // Field is a field of structure or a compatible map. It is used as the output of the inspection functions, and
@ -22,8 +34,7 @@ type Field struct {
path []string path []string
name string name string
list bool list bool
typ Scalar typ reflect.Kind
size int
free bool free bool
value any value any
} }
@ -41,6 +52,11 @@ func BindScalarCreate[T any](value ...any) (T, bool) {
return bindScalarCreateReflect[T](value) return bindScalarCreateReflect[T](value)
} }
// BindScalarCreate is like BindScalar, but it allocates the receiver from the type t.
func BindScalarCreateWith(t reflect.Type, value ...any) (reflect.Value, bool) {
return reflect.Value{}, false
}
// ValueByPath defines a field for input to BindFields or BindFieldsCreate. It defines the field by its exact // ValueByPath defines a field for input to BindFields or BindFieldsCreate. It defines the field by its exact
// field path in the receiver structure. // field path in the receiver structure.
func ValueByPath(path []string, value any) Field { func ValueByPath(path []string, value any) Field {
@ -77,7 +93,7 @@ func (f Field) Name() string {
func (f Field) List() bool { return f.list } func (f Field) List() bool { return f.list }
// Type returns the scalar type of the field. // Type returns the scalar type of the field.
func (f Field) Type() Scalar { func (f Field) Type() reflect.Kind {
if !f.input { if !f.input {
return f.typ return f.typ
} }
@ -85,15 +101,6 @@ func (f Field) Type() Scalar {
return scalarTypeReflect(f.value) return scalarTypeReflect(f.value)
} }
// Size returns the bitsize of the field.
func (f Field) Size() int {
if !f.input {
return f.size
}
return valueSizeReflect(f.value)
}
// Free indicates that the field was found in a map. // Free indicates that the field was found in a map.
func (f Field) Free() bool { func (f Field) Free() bool {
return f.free return f.free
@ -109,6 +116,11 @@ func Fields[T any]() []Field {
return fieldsReflect[T]() return fieldsReflect[T]()
} }
// Fields of is like Fields but uses type t as the input.
func FieldsOf(t reflect.Type) []Field {
return nil
}
// FieldValues returns the fields of a structure value recursively. It traverses through pointers, slices and // FieldValues returns the fields of a structure value recursively. It traverses through pointers, slices and
// interfaces. // interfaces.
func FieldValues(structure any) []Field { func FieldValues(structure any) []Field {
@ -127,22 +139,47 @@ func BindFieldsCreate[T any](values ...Field) (T, []Field) {
return bindFieldsCreateReflect[T](values) return bindFieldsCreateReflect[T](values)
} }
// BindFieldsCreate is like BindFields, but it allocates the receiver from type t.
func BindFieldsCreateWith(t reflect.Type, values ...Field) (reflect.Value, []Field) {
return reflect.Value{}, nil
}
// AcceptsScalar checks if a type can be used with BindScalarCreate or the values of the type with BindScalar. // AcceptsScalar checks if a type can be used with BindScalarCreate or the values of the type with BindScalar.
func AcceptsScalar[T any]() bool { func AcceptsScalar[T any]() bool {
return acceptsScalarReflect[T]() return acceptsScalarReflect[T]()
} }
// TypeAcceptsScalar is like AcceptsScalar, but uses type t as input.
func TypeAcceptsScalar(t reflect.Type) bool {
return false
}
// AcceptsFields checks if a type can be used with BindFieldsCreate or the values of the type with BindFields. // AcceptsFields checks if a type can be used with BindFieldsCreate or the values of the type with BindFields.
func AcceptsFields[T any]() bool { func AcceptsFields[T any]() bool {
return acceptsFieldsReflect[T]() return acceptsFieldsReflect[T]()
} }
// TypeAcceptsFields is like AcceptsFields, but uses type t as input.
func TypeAcceptsFields(t reflect.Type) bool {
return false
}
// AcceptsList checks if a type can be used to bind multiple values. // AcceptsList checks if a type can be used to bind multiple values.
func AcceptsList[T any]() bool { func AcceptsList[T any]() bool {
return acceptsListReflect[T]() return acceptsListReflect[T]()
} }
// TypeAcceptsList is like AcceptsList, but uses type t as input.
func TypeAcceptsList(t reflect.Type) bool {
return false
}
// Bindable is the same as AcceptsScalar[T]() || AcceptsFields[T](). // Bindable is the same as AcceptsScalar[T]() || AcceptsFields[T]().
func Bindable[T any]() bool { func Bindable[T any]() bool {
return bindableReflect[T]() return bindableReflect[T]()
} }
// BindableType is like Bindable, but uses type t as input.
func BindableType(t reflect.Type) bool {
return false
}

View File

@ -17,23 +17,23 @@ func bindScalar(receiver reflect.Value, values []any) bool {
return false return false
} }
r := reflect.ValueOf(values[0]) v := reflect.ValueOf(values[0])
r = unpackValue(r, pointer|iface|slice) v = unpackValue(v, pointer|iface)
if !r.IsValid() && !isAny(receiver.Type()) { if !v.IsValid() && !isAny(receiver.Type()) {
return false return false
} }
if !r.IsValid() { if !v.IsValid() {
receiver.Set(reflect.Zero(receiver.Type())) receiver.Set(reflect.Zero(receiver.Type()))
return true return true
} }
v, ok := scan(receiver.Type(), r.Interface()) vv, ok := scan(receiver.Type(), v.Interface())
if !ok { if !ok {
return false return false
} }
receiver.Set(reflect.ValueOf(v)) receiver.Set(reflect.ValueOf(vv))
return true return true
} }

View File

@ -44,7 +44,7 @@ func TestScalar(t *testing.T) {
t.Run("cannot scan", func(t *testing.T) { t.Run("cannot scan", func(t *testing.T) {
var i int var i int
if bind.BindScalar(i, "foo") { if bind.BindScalar(&i, "foo") {
t.Fatal() t.Fatal()
} }
}) })

View File

@ -90,7 +90,7 @@ func scanString(t reflect.Type, s string) (any, bool) {
v, err = time.ParseDuration(s) v, err = time.ParseDuration(s)
} }
if err != nil { if !isDuration(t) || err != nil {
v, err = parseInt(s, int(t.Size())) v, err = parseInt(s, int(t.Size()))
} }
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:

View File

@ -115,9 +115,10 @@ func TestScan(t *testing.T) {
}) })
t.Run("duration convert", func(t *testing.T) { t.Run("duration convert", func(t *testing.T) {
// not supported, otherwise any integer field would become convertible:
type dur time.Duration type dur time.Duration
var d dur var d dur
if !bind.BindScalar(&d, "9s") || d != dur(9*time.Second) { if bind.BindScalar(&d, "9s") {
t.Fatal(d) t.Fatal(d)
} }
}) })

63
type.go
View File

@ -20,7 +20,7 @@ func (f unpackFlag) has(v unpackFlag) bool {
return f&v > 0 return f&v > 0
} }
func scalarType(t reflect.Type) Scalar { func scalarType(t reflect.Type) reflect.Kind {
switch t.Kind() { switch t.Kind() {
case reflect.Bool: case reflect.Bool:
return Bool return Bool
@ -29,13 +29,13 @@ func scalarType(t reflect.Type) Scalar {
return Duration return Duration
} }
return Int return FieldType(t.Kind())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return Uint return FieldType(t.Kind())
case reflect.Float32, reflect.Float64: case reflect.Float32, reflect.Float64:
return Float return FieldType(t.Kind())
case reflect.String: case reflect.String:
return String return FieldType(t.Kind())
default: default:
if isTime(t) { if isTime(t) {
return Time return Time
@ -45,41 +45,20 @@ func scalarType(t reflect.Type) Scalar {
} }
} }
func scalarTypeReflect(v any) Scalar { func scalarTypeReflect(v any) FieldType {
t := reflect.TypeOf(v) t := reflect.TypeOf(v)
if t == nil { if t == nil {
return Any return Any
} }
if hasCircularType(t) {
return Any
}
t = unpackType(t, pointer|slice)
return scalarType(t) return scalarType(t)
} }
func scalarSize(t reflect.Type) int {
return int(t.Size()) * 8
}
func valueSize(v reflect.Value) int {
switch v.Kind() {
case reflect.String:
return v.Len() * 8
case reflect.Interface:
return valueSize(unpackValue(v, pointer|slice|iface|anytype))
case reflect.Invalid:
return 0
default:
return scalarSize(v.Type())
}
}
func valueSizeReflect(v any) int {
r := reflect.ValueOf(v)
if hasCircularReference(r) {
return 0
}
return valueSize(r)
}
func setVisited[T comparable](visited map[T]bool, k T) map[T]bool { func setVisited[T comparable](visited map[T]bool, k T) map[T]bool {
s := make(map[T]bool) s := make(map[T]bool)
for v := range visited { for v := range visited {
@ -191,7 +170,7 @@ func isTime(t reflect.Type) bool {
} }
func isDuration(t reflect.Type) bool { func isDuration(t reflect.Type) bool {
return t.ConvertibleTo(reflect.TypeFor[time.Duration]()) return t == reflect.TypeFor[time.Duration]()
} }
func isScalar(t reflect.Type) bool { func isScalar(t reflect.Type) bool {
@ -271,6 +250,24 @@ func unpackValue(v reflect.Value, unpack unpackFlag) reflect.Value {
return v return v
} }
func unpackAllValues(v reflect.Value) []reflect.Value {
if !v.IsValid() {
return nil
}
v = unpackValue(v, pointer|iface|anytype)
if v.Kind() != reflect.Slice {
return []reflect.Value{v}
}
var all []reflect.Value
for i := 0; i < v.Len(); i++ {
all = append(all, unpackAllValues(v.Index(i))...)
}
return all
}
func acceptsScalar(t reflect.Type) bool { func acceptsScalar(t reflect.Type) bool {
t = unpackType(t, pointer|slice) t = unpackType(t, pointer|slice)
return isAny(t) || isScalar(t) return isAny(t) || isScalar(t)

View File

@ -349,14 +349,6 @@ func TestTypeChecks(t *testing.T) {
} }
}) })
t.Run("via struct field and interface", func(t *testing.T) {
var s struct{ F any }
s.F = s
if len(bind.FieldValues(s)) > 0 {
t.Fatal()
}
})
t.Run("value with circular type", func(t *testing.T) { t.Run("value with circular type", func(t *testing.T) {
type p *p type p *p
var v p var v p