package bind import "reflect" type unpackFlag int const ( pointer unpackFlag = 1 << iota slice anytype iface ) func (f unpackFlag) has(v unpackFlag) bool { return f&v > 0 } func setVisited[T comparable](visited map[T]bool, k T) map[T]bool { s := make(map[T]bool) for v := range visited { s[v] = true } s[k] = true return s } func hasCircularType(visited map[reflect.Type]bool, t reflect.Type) bool { if visited[t] { return true } switch t.Kind() { case reflect.Pointer, reflect.Slice: visited = setVisited(visited, t) return hasCircularType(visited, t.Elem()) case reflect.Struct: visited = setVisited(visited, t) for i := 0; i < t.NumField(); i++ { if hasCircularType(visited, t.Field(i).Type) { return true } } return false default: return false } } func hasCircularReference(visited map[uintptr]bool, v reflect.Value) bool { if hasCircularType(nil, v.Type()) { return true } switch v.Kind() { case reflect.Pointer: p := v.Pointer() if visited[p] { return true } visited = setVisited(visited, v.Pointer()) return hasCircularReference(visited, v.Elem()) case reflect.Slice: p := v.Pointer() if visited[p] { return true } visited = setVisited(visited, v.Pointer()) for i := 0; i < v.Len(); i++ { if hasCircularReference(visited, v.Index(i)) { return true } } return false case reflect.Interface: if v.IsNil() { return false } return hasCircularReference(visited, v.Elem()) case reflect.Struct: for i := 0; i < v.NumField(); i++ { if hasCircularReference(visited, v.Field(i)) { return true } } return false } return false } func isAny(t reflect.Type) bool { return t.Kind() == reflect.Interface && t.NumMethod() == 0 } func isInterface(t reflect.Type) bool { return t.Kind() == reflect.Interface && t.NumMethod() > 0 } func isScalar(t reflect.Type) bool { switch t.Kind() { case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.Float32, reflect.Float64, reflect.String: return true default: return false } } func isScalarMap(t reflect.Type) bool { if t.Kind() != reflect.Map { return false } key := unpackType(t.Key(), pointer) if key.Kind() != reflect.String { return false } value := unpackType(t.Elem(), pointer|slice) return isAny(value) || isScalar(value) } func unpackType(t reflect.Type, unpack unpackFlag) reflect.Type { if unpack.has(pointer) && t.Kind() == reflect.Pointer { return unpackType(t.Elem(), unpack) } if unpack.has(slice) && t.Kind() == reflect.Slice { return unpackType(t.Elem(), unpack) } return t } func unpackValue(v reflect.Value, unpack unpackFlag) reflect.Value { if unpack.has(pointer) && v.Kind() == reflect.Pointer { return unpackValue(v.Elem(), unpack) } if unpack.has(slice) && v.Kind() == reflect.Slice && v.Len() > 0 { return unpackValue(v.Index(0), unpack) } if unpack.has(anytype) && isAny(v.Type()) && !v.IsNil() { return unpackValue(v.Elem(), unpack) } if unpack.has(iface) && isInterface(v.Type()) && !v.IsNil() { return unpackValue(v.Elem(), unpack) } return v } func acceptsScalar(t reflect.Type) bool { t = unpackType(t, pointer|slice) return isAny(t) || isScalar(t) } func acceptsFields(t reflect.Type) bool { t = unpackType(t, pointer|slice) return t.Kind() == reflect.Struct || isScalarMap(t) } func acceptsList(t reflect.Type) bool { if !bindable(t) { return false } t = unpackType(t, pointer) return t.Kind() == reflect.Slice } func bindable(t reflect.Type) bool { return acceptsScalar(t) || acceptsFields(t) } func acceptsScalarChecked(t reflect.Type) bool { if hasCircularType(nil, t) { return false } return acceptsScalar(t) } func acceptsFieldsChecked(t reflect.Type) bool { if hasCircularType(nil, t) { return false } return acceptsFields(t) } func acceptsListChecked(t reflect.Type) bool { if hasCircularType(nil, t) { return false } return acceptsList(t) } func bindableChecked(t reflect.Type) bool { if hasCircularType(nil, t) { return false } return bindable(t) } func acceptsScalarReflect[T any]() bool { return acceptsScalarChecked(reflect.TypeFor[T]()) } func acceptsFieldsReflect[T any]() bool { return acceptsFieldsChecked(reflect.TypeFor[T]()) } func acceptsListReflect[T any]() bool { return acceptsListChecked(reflect.TypeFor[T]()) } func bindableReflect[T any]() bool { return bindableChecked(reflect.TypeFor[T]()) } // expected to be called with types that can pass the bindable check func allocate(t reflect.Type, len int) (reflect.Value, bool) { if len == 0 { return reflect.Zero(t), false } if isAny(t) || isScalar(t) || t.Kind() == reflect.Struct || isScalarMap(t) { p := reflect.New(t) return p.Elem(), len == 1 } if t.Kind() == reflect.Slice { l := reflect.MakeSlice(t, len, len) for i := 0; i < len; i++ { e, ok := allocate(t.Elem(), 1) if !ok { return reflect.Zero(t), false } l.Index(i).Set(e) } return l, true } // must be pointer e, ok := allocate(t.Elem(), len) if !ok { return reflect.Zero(t), false } p := reflect.New(t.Elem()) p.Elem().Set(e) return p, true }