From a7b031f3b411306e51e08a18a62b059269153292 Mon Sep 17 00:00:00 2001 From: Arpad Ryszka Date: Wed, 3 Sep 2025 22:12:13 +0200 Subject: [PATCH] refactor field list and field value list --- field.go | 207 ++++++++++++++++++++++++------------------------- field_test.go | 173 ++++++++++++++++++++++++++++++----------- lib.go | 81 +++++++++++++------ scalar.go | 12 +-- scalar_test.go | 2 +- scan.go | 2 +- scan_test.go | 3 +- type.go | 63 +++++++-------- type_test.go | 8 -- 9 files changed, 330 insertions(+), 221 deletions(-) diff --git a/field.go b/field.go index 59b0284..8eec45a 100644 --- a/field.go +++ b/field.go @@ -51,76 +51,12 @@ func fieldFromType(name string, t reflect.Type) Field { var f Field ft := unpackType(t, pointer|slice) f.typ = scalarType(ft) - f.size = scalarSize(ft) f.list = acceptsList(t) f.name = strcase.ToKebab(name) f.path = []string{name} 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) { for i := range f { 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) { for i := range f { 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 { var f []Field for _, key := range v.MapKeys() { name := key.Interface().(string) value := v.MapIndex(key) - fk := fieldValues(value) - prependFieldName(name, fk) + fk := scalarFieldValues(name, v.Type().Elem(), value) freeFields(fk) f = append(f, fk...) } @@ -154,55 +145,60 @@ func scalarMapFields(v reflect.Value) []Field { return f } -func fieldValues(v reflect.Value) []Field { - if !v.IsValid() { - return []Field{nilField()} - } - +func structFields(v reflect.Value) []Field { var f []Field - v = unpackValue(v, pointer|anytype|iface) t := v.Type() - if isScalar(t) { - return []Field{fieldFromValue(v)} - } - - if v.Kind() == reflect.Slice { - for i := 0; i < v.Len(); i++ { - f = append(f, fieldValues(v.Index(i))...) + for i := 0; i < t.NumField(); i++ { + fi := t.Field(i) + if !exported(fi.Name) && !fi.Anonymous { + continue } - return f + vi := v.Field(i) + if acceptsScalar(fi.Type) { + 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...) } - if isScalarMap(t) { - return scalarMapFields(v) - } + return f +} - if !isStruct(t) { +func fieldValues(v reflect.Value) []Field { + if !v.IsValid() { return nil } - for i := 0; i < t.NumField(); i++ { - tfi := t.Field(i) - if !exported(tfi.Name) && !tfi.Anonymous { + var values []reflect.Value + allValues := unpackAllValues(v) + 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 } - vfi := v.Field(i) - 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...) - } + f = append(f, structFields(vi)...) } return f @@ -218,11 +214,11 @@ func takeFieldValues(f []Field) []any { } func bindScalarField(receiver reflect.Value, values []Field) bool { - v := takeFieldValues(values) if !receiver.CanSet() { - return bindScalar(receiver, v) + return false } + v := takeFieldValues(values) rv, ok := allocate(receiver.Type(), len(v)) if !ok { return false @@ -379,7 +375,8 @@ func bindField(receiver reflect.Value, values []Field) bool { } 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) diff --git a/field_test.go b/field_test.go index d62e07b..f2b92b9 100644 --- a/field_test.go +++ b/field_test.go @@ -51,45 +51,47 @@ func TestField(t *testing.T) { Hola []int } - f := bind.Fields[s2]() - if len(f) != 6 { - t.Fatal(notation.Sprintwt(f)) - } + t.Run("mixed", func(t *testing.T) { + f := bind.Fields[s2]() + if len(f) != 6 { + t.Fatal(notation.Sprintwt(f)) + } - m := make(map[string]bind.Field) - for _, fi := range f { - m[fi.Name()] = fi - } + m := make(map[string]bind.Field) + for _, fi := range f { + m[fi.Name()] = fi + } - fieldOne := m["field-one"] - if !slices.Equal(fieldOne.Path(), []string{"FieldOne"}) { - t.Fatal(fieldOne.Name()) - } + fieldOne := m["field-one"] + if !slices.Equal(fieldOne.Path(), []string{"FieldOne"}) { + t.Fatal(fieldOne.Name()) + } - fooBar := m["foo-bar"] - if !slices.Equal(fooBar.Path(), []string{"FooBar"}) { - t.Fatal(fooBar.Name()) - } + fooBar := m["foo-bar"] + if !slices.Equal(fooBar.Path(), []string{"FooBar"}) { + t.Fatal(fooBar.Name()) + } - bazFoo := m["baz-foo"] - if !slices.Equal(bazFoo.Path(), []string{"Baz", "Foo"}) { - t.Fatal(bazFoo.Name()) - } + bazFoo := m["baz-foo"] + if !slices.Equal(bazFoo.Path(), []string{"Baz", "Foo"}) { + t.Fatal(bazFoo.Name()) + } - quxFoo := m["qux-foo"] - if !slices.Equal(quxFoo.Path(), []string{"Qux", "Foo"}) { - t.Fatal(quxFoo.Name()) - } + quxFoo := m["qux-foo"] + if !slices.Equal(quxFoo.Path(), []string{"Qux", "Foo"}) { + t.Fatal(quxFoo.Name()) + } - que := m["que"] - if !slices.Equal(que.Path(), []string{"Que"}) || que.Type() != bind.Bool { - t.Fatal(que.Name()) - } + que := m["que"] + if !slices.Equal(que.Path(), []string{"Que"}) || que.Type() != bind.Bool { + t.Fatal(que.Name()) + } - hola := m["hola"] - if !slices.Equal(hola.Path(), []string{"Hola"}) || !hola.List() { - t.Fatal(hola.Name()) - } + hola := m["hola"] + if !slices.Equal(hola.Path(), []string{"Hola"}) || !hola.List() { + t.Fatal(hola.Name()) + } + }) t.Run("cannot have fields", func(t *testing.T) { type i []int @@ -109,19 +111,33 @@ func TestField(t *testing.T) { t.Run("list", func(t *testing.T) { type s struct{ Foo int } f := bind.Fields[[]s]() - if len(f) != 1 || !f[0].List() { + if len(f) != 1 || f[0].List() { 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("has circular reference", func(t *testing.T) { type s struct{ Foo any } var v s - v.Foo = v - if len(bind.FieldValues(v)) != 0 { - t.Fatal() + v.Foo = &v + f := bind.FieldValues(v) + 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) { v := []int{21, 42, 84} 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() } }) @@ -217,7 +233,7 @@ func TestField(t *testing.T) { type s struct{ Foo any } v := s{Foo: []any{map[string]int{"foo": 42}}} 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)) } }) @@ -226,9 +242,8 @@ func TestField(t *testing.T) { type s struct{ Foo any } v := s{Foo: []any{s{Foo: 21}, s{Foo: 42}}} f := bind.FieldValues(v) - if len(f) != 2 || - f[0].Name() != "foo-foo" || f[0].Value() != 21 || - f[1].Name() != "foo-foo" || f[1].Value() != 42 { + if len(f) != 1 || + f[0].Name() != "foo" || f[0].Value() != nil { t.Fatal() } }) @@ -248,12 +263,13 @@ func TestField(t *testing.T) { s0 Bar int } + var v s1 v.Foo = 21 v.Bar = 42 f := bind.FieldValues(v) - if len(f) != 1 || f[0].Name() != "bar" || f[0].Value() != 42 { - t.Fatal() + if len(f) != 2 || f[0].Name() != "foo" || f[0].Value() != 21 || f[1].Name() != "bar" || f[1].Value() != 42 { + t.Fatal(notation.Sprint(f)) } }) @@ -273,6 +289,75 @@ func TestField(t *testing.T) { 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) { @@ -375,7 +460,7 @@ func TestField(t *testing.T) { 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[2].Bar) != 1 || v.Foo[2].Bar[0] != 84 { - t.Fatal() + t.Fatal(notation.Sprintw(v)) } }) diff --git a/lib.go b/lib.go index e8bb09c..9754e49 100644 --- a/lib.go +++ b/lib.go @@ -1,18 +1,30 @@ // Package bind provides functions to work with flattened lists of structure fields. package bind -// Scalar defines scalar types that can be used as field values and are not traversed. -type Scalar int +import "reflect" + +// Field type represents the possible types of a field. +type FieldType = reflect.Kind const ( - Any Scalar = iota - Bool - Int - Uint - Float - String - Duration - Time + Bool FieldType = reflect.Bool + Int FieldType = reflect.Int + Int8 FieldType = reflect.Int8 + Int16 FieldType = reflect.Int16 + Int32 FieldType = reflect.Int32 + Int64 FieldType = reflect.Int64 + Uint FieldType = reflect.Uint + 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 @@ -22,8 +34,7 @@ type Field struct { path []string name string list bool - typ Scalar - size int + typ reflect.Kind free bool value any } @@ -41,6 +52,11 @@ func BindScalarCreate[T any](value ...any) (T, bool) { 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 // field path in the receiver structure. func ValueByPath(path []string, value any) Field { @@ -77,7 +93,7 @@ func (f Field) Name() string { func (f Field) List() bool { return f.list } // Type returns the scalar type of the field. -func (f Field) Type() Scalar { +func (f Field) Type() reflect.Kind { if !f.input { return f.typ } @@ -85,15 +101,6 @@ func (f Field) Type() Scalar { 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. func (f Field) Free() bool { return f.free @@ -109,6 +116,11 @@ func Fields[T any]() []Field { 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 // interfaces. func FieldValues(structure any) []Field { @@ -127,22 +139,47 @@ func BindFieldsCreate[T any](values ...Field) (T, []Field) { 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. func AcceptsScalar[T any]() bool { 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. func AcceptsFields[T any]() bool { 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. func AcceptsList[T any]() bool { 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](). func Bindable[T any]() bool { return bindableReflect[T]() } + +// BindableType is like Bindable, but uses type t as input. +func BindableType(t reflect.Type) bool { + return false +} diff --git a/scalar.go b/scalar.go index 728c13e..362d4ec 100644 --- a/scalar.go +++ b/scalar.go @@ -17,23 +17,23 @@ func bindScalar(receiver reflect.Value, values []any) bool { return false } - r := reflect.ValueOf(values[0]) - r = unpackValue(r, pointer|iface|slice) - if !r.IsValid() && !isAny(receiver.Type()) { + v := reflect.ValueOf(values[0]) + v = unpackValue(v, pointer|iface) + if !v.IsValid() && !isAny(receiver.Type()) { return false } - if !r.IsValid() { + if !v.IsValid() { receiver.Set(reflect.Zero(receiver.Type())) return true } - v, ok := scan(receiver.Type(), r.Interface()) + vv, ok := scan(receiver.Type(), v.Interface()) if !ok { return false } - receiver.Set(reflect.ValueOf(v)) + receiver.Set(reflect.ValueOf(vv)) return true } diff --git a/scalar_test.go b/scalar_test.go index 4a2504a..2a0c3b9 100644 --- a/scalar_test.go +++ b/scalar_test.go @@ -44,7 +44,7 @@ func TestScalar(t *testing.T) { t.Run("cannot scan", func(t *testing.T) { var i int - if bind.BindScalar(i, "foo") { + if bind.BindScalar(&i, "foo") { t.Fatal() } }) diff --git a/scan.go b/scan.go index 2ef0713..e42bbda 100644 --- a/scan.go +++ b/scan.go @@ -90,7 +90,7 @@ func scanString(t reflect.Type, s string) (any, bool) { v, err = time.ParseDuration(s) } - if err != nil { + if !isDuration(t) || err != nil { v, err = parseInt(s, int(t.Size())) } case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: diff --git a/scan_test.go b/scan_test.go index 9a6ecef..3b51e47 100644 --- a/scan_test.go +++ b/scan_test.go @@ -115,9 +115,10 @@ func TestScan(t *testing.T) { }) t.Run("duration convert", func(t *testing.T) { + // not supported, otherwise any integer field would become convertible: type dur time.Duration var d dur - if !bind.BindScalar(&d, "9s") || d != dur(9*time.Second) { + if bind.BindScalar(&d, "9s") { t.Fatal(d) } }) diff --git a/type.go b/type.go index c35c27a..cd64240 100644 --- a/type.go +++ b/type.go @@ -20,7 +20,7 @@ func (f unpackFlag) has(v unpackFlag) bool { return f&v > 0 } -func scalarType(t reflect.Type) Scalar { +func scalarType(t reflect.Type) reflect.Kind { switch t.Kind() { case reflect.Bool: return Bool @@ -29,13 +29,13 @@ func scalarType(t reflect.Type) Scalar { return Duration } - return Int + return FieldType(t.Kind()) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return Uint + return FieldType(t.Kind()) case reflect.Float32, reflect.Float64: - return Float + return FieldType(t.Kind()) case reflect.String: - return String + return FieldType(t.Kind()) default: if isTime(t) { 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) if t == nil { return Any } + if hasCircularType(t) { + return Any + } + + t = unpackType(t, pointer|slice) 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 { s := make(map[T]bool) for v := range visited { @@ -191,7 +170,7 @@ func isTime(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 { @@ -271,6 +250,24 @@ func unpackValue(v reflect.Value, unpack unpackFlag) reflect.Value { 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 { t = unpackType(t, pointer|slice) return isAny(t) || isScalar(t) diff --git a/type_test.go b/type_test.go index 5219465..8be1b63 100644 --- a/type_test.go +++ b/type_test.go @@ -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) { type p *p var v p