diff --git a/internal/utils/deep_equal.go b/internal/utils/deep_equal.go deleted file mode 100644 index 72b5b12b..00000000 --- a/internal/utils/deep_equal.go +++ /dev/null @@ -1,243 +0,0 @@ -package utils - -import ( - "reflect" - "unsafe" -) - -// DeepEqual reports whether x and y are deeply equal. -// It supports numerics, strings, maps, slices, arrays, and structs (exported fields only). -// It's optimized for performance by avoiding reflection for common types and -// adaptively choosing between BFS and DFS traversal strategies. -func DeepEqual(x, y any) bool { - if x == nil || y == nil { - return x == y - } - - v1 := reflect.ValueOf(x) - v2 := reflect.ValueOf(y) - - if v1.Type() != v2.Type() { - return false - } - - return deepEqual(v1, v2, make(map[visit]bool), 0) -} - -// visit represents a visit to a pair of values during comparison -type visit struct { - a1, a2 unsafe.Pointer - typ reflect.Type -} - -// deepEqual performs the actual deep comparison with cycle detection -func deepEqual(v1, v2 reflect.Value, visited map[visit]bool, depth int) bool { - if !v1.IsValid() || !v2.IsValid() { - return v1.IsValid() == v2.IsValid() - } - - if v1.Type() != v2.Type() { - return false - } - - // Handle cycle detection for pointer-like types - if v1.CanAddr() && v2.CanAddr() { - addr1 := unsafe.Pointer(v1.UnsafeAddr()) - addr2 := unsafe.Pointer(v2.UnsafeAddr()) - typ := v1.Type() - v := visit{addr1, addr2, typ} - if visited[v] { - return true // already visiting, assume equal - } - visited[v] = true - defer delete(visited, v) - } - - switch v1.Kind() { - case reflect.Bool: - return v1.Bool() == v2.Bool() - - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return v1.Int() == v2.Int() - - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return v1.Uint() == v2.Uint() - - case reflect.Float32, reflect.Float64: - return floatEqual(v1.Float(), v2.Float()) - - case reflect.Complex64, reflect.Complex128: - c1, c2 := v1.Complex(), v2.Complex() - return floatEqual(real(c1), real(c2)) && floatEqual(imag(c1), imag(c2)) - - case reflect.String: - return v1.String() == v2.String() - - case reflect.Array: - return deepEqualArray(v1, v2, visited, depth) - - case reflect.Slice: - return deepEqualSlice(v1, v2, visited, depth) - - case reflect.Map: - return deepEqualMap(v1, v2, visited, depth) - - case reflect.Struct: - return deepEqualStruct(v1, v2, visited, depth) - - case reflect.Ptr: - if v1.IsNil() || v2.IsNil() { - return v1.IsNil() && v2.IsNil() - } - return deepEqual(v1.Elem(), v2.Elem(), visited, depth+1) - - case reflect.Interface: - if v1.IsNil() || v2.IsNil() { - return v1.IsNil() && v2.IsNil() - } - return deepEqual(v1.Elem(), v2.Elem(), visited, depth+1) - - default: - // For unsupported types (func, chan, etc.), fall back to basic equality - return v1.Interface() == v2.Interface() - } -} - -// floatEqual handles NaN cases properly -func floatEqual(f1, f2 float64) bool { - return f1 == f2 || (f1 != f1 && f2 != f2) // NaN == NaN -} - -// deepEqualArray compares arrays using DFS (since arrays have fixed size) -func deepEqualArray(v1, v2 reflect.Value, visited map[visit]bool, depth int) bool { - for i := range v1.Len() { - if !deepEqual(v1.Index(i), v2.Index(i), visited, depth+1) { - return false - } - } - return true -} - -// deepEqualSlice compares slices, choosing strategy based on size and depth -func deepEqualSlice(v1, v2 reflect.Value, visited map[visit]bool, depth int) bool { - if v1.IsNil() != v2.IsNil() { - return false - } - if v1.Len() != v2.Len() { - return false - } - if v1.IsNil() { - return true - } - - // Use BFS for large slices at shallow depth to improve cache locality - // Use DFS for small slices or deep nesting to reduce memory overhead - if shouldUseBFS(v1.Len(), depth) { - return deepEqualSliceBFS(v1, v2, visited, depth) - } - return deepEqualSliceDFS(v1, v2, visited, depth) -} - -// deepEqualSliceDFS uses depth-first traversal -func deepEqualSliceDFS(v1, v2 reflect.Value, visited map[visit]bool, depth int) bool { - for i := range v1.Len() { - if !deepEqual(v1.Index(i), v2.Index(i), visited, depth+1) { - return false - } - } - return true -} - -// deepEqualSliceBFS uses breadth-first traversal for better cache locality -func deepEqualSliceBFS(v1, v2 reflect.Value, visited map[visit]bool, depth int) bool { - length := v1.Len() - - // First, check all direct elements - for i := range length { - elem1, elem2 := v1.Index(i), v2.Index(i) - - // For simple types, compare directly - if isSimpleType(elem1.Kind()) { - if !deepEqual(elem1, elem2, visited, depth+1) { - return false - } - } - } - - // Then, recursively check complex elements - for i := range length { - elem1, elem2 := v1.Index(i), v2.Index(i) - - if !isSimpleType(elem1.Kind()) { - if !deepEqual(elem1, elem2, visited, depth+1) { - return false - } - } - } - - return true -} - -// deepEqualMap compares maps -func deepEqualMap(v1, v2 reflect.Value, visited map[visit]bool, depth int) bool { - if v1.IsNil() != v2.IsNil() { - return false - } - if v1.Len() != v2.Len() { - return false - } - if v1.IsNil() { - return true - } - - // Check all keys and values - for _, key := range v1.MapKeys() { - val1 := v1.MapIndex(key) - val2 := v2.MapIndex(key) - - if !val2.IsValid() { - return false // key doesn't exist in v2 - } - - if !deepEqual(val1, val2, visited, depth+1) { - return false - } - } - - return true -} - -// deepEqualStruct compares structs (exported fields only) -func deepEqualStruct(v1, v2 reflect.Value, visited map[visit]bool, depth int) bool { - typ := v1.Type() - - for i := range typ.NumField() { - field := typ.Field(i) - - // Skip unexported fields - if !field.IsExported() { - continue - } - - if !deepEqual(v1.Field(i), v2.Field(i), visited, depth+1) { - return false - } - } - - return true -} - -// shouldUseBFS determines whether to use BFS or DFS based on slice size and depth -func shouldUseBFS(length, depth int) bool { - // Use BFS for large slices at shallow depth (better cache locality) - // Use DFS for small slices or deep nesting (lower memory overhead) - return length > 100 && depth < 3 -} - -// isSimpleType checks if a type can be compared without deep recursion -func isSimpleType(kind reflect.Kind) bool { - if kind >= reflect.Bool && kind <= reflect.Complex128 { - return true - } - return kind == reflect.String -}