1
0
bind/type.go
2025-08-31 00:40:47 +02:00

312 lines
5.7 KiB
Go

package bind
import "reflect"
type unpackFlag int
const (
pointer unpackFlag = 1 << iota
slice
anytype
iface
)
var typeCirc = make(map[reflect.Type]bool)
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 checkHasCircularType(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 checkHasCircularType(visited, t.Elem())
case reflect.Struct:
visited = setVisited(visited, t)
for i := 0; i < t.NumField(); i++ {
if checkHasCircularType(visited, t.Field(i).Type) {
return true
}
}
return false
default:
return false
}
}
func hasCircularType(t reflect.Type) bool {
if has, cached := typeCirc[t]; cached {
return has
}
has := checkHasCircularType(nil, t)
typeCirc[t] = has
return has
}
func checkHasCircularReference(visited map[uintptr]bool, v reflect.Value) bool {
if !v.IsValid() {
return false
}
if hasCircularType(v.Type()) {
return true
}
switch v.Kind() {
case reflect.Pointer:
p := v.Pointer()
if visited[p] {
return true
}
visited = setVisited(visited, v.Pointer())
return checkHasCircularReference(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 checkHasCircularReference(visited, v.Index(i)) {
return true
}
}
return false
case reflect.Interface:
if v.IsNil() {
return false
}
return checkHasCircularReference(visited, v.Elem())
case reflect.Struct:
for i := 0; i < v.NumField(); i++ {
if checkHasCircularReference(visited, v.Field(i)) {
return true
}
}
return false
}
return false
}
func hasCircularReference(v reflect.Value) bool {
return checkHasCircularReference(nil, v)
}
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 v.IsZero() {
return v
}
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.IsZero() && !v.IsNil() {
return unpackValue(v.Elem(), unpack)
}
if unpack.has(iface) && isInterface(v.Type()) && !v.IsZero() && !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(t) {
return false
}
return acceptsScalar(t)
}
func acceptsFieldsChecked(t reflect.Type) bool {
if hasCircularType(t) {
return false
}
return acceptsFields(t)
}
func acceptsListChecked(t reflect.Type) bool {
if hasCircularType(t) {
return false
}
return acceptsList(t)
}
func bindableChecked(t reflect.Type) bool {
if hasCircularType(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 {
p := reflect.New(t)
return p.Elem(), len == 1
}
if isScalarMap(t) {
return reflect.MakeMap(t), 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
}
if t.Kind() == reflect.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
}
return reflect.Zero(t), false
}