Kaynağa Gözat

Fix Operators hash/equality/comparison/contain/stringify for value types

Generic delegates receive gconstpointer. Simple value types (bool,
char/uchar, int/uint, long/ulong, enums) arrive inline as a
pointer-sized integer, but int64/uint64/float/double (and every
nullable value type) arrive boxed: a real pointer to the payload.
The old code reinterpreted the box pointer as the value, so
Dictionary<int64?,string> hashed/compared garbage pointer bits and
lookups missed, and Comparison truncated (int64)a - (int64)b to int,
reporting the wrong sign for values that overflow.

Hash, Equality, Comparison, Contain and Stringify now all:
- deref-or-null the box for int64/uint64/float/double (null boxes
  hash 0, equal only other nulls, sort first, stringify "null",
  contain as the GType default) - choices documented inline
- compare numerically with sign-based comparison (a>b ? 1 : a<b ?
  -1 : 0), never subtraction, so large int64/uint64/long/ulong
  values order correctly
- have explicit branches for bool, char, uchar, uint, long, ulong,
  float and enums, which previously fell through to pointer-diff
  compare (constant 1) or numeric-only branches
- stringify char as the character (typeof(char) == typeof(int8), so
  the char branch now precedes the shared GType), bool as
  true/false, and enums as their value nick with numeric fallback
- hash uint/long/ulong by folding both halves so values that only
  differ above bit 31 do not collide

Comparison's old pointer-difference fallback is replaced with 0
(deterministic); non-Comparable objects have no meaningful default
order.

Known limitation documented in each file: typeof(T) cannot
distinguish T=int from T=int? (same GType, different marshalling),
so the inline branches serve the non-nullable forms.

Tests: re-enabled 41 BROKEN-disabled value-type matrix cases;
corrected two impossible bool assertions (bool has only two
distinct values, so !has(items[2]) where items[2]==items[0] could
never hold). int64?/uint64? priority_queue cases stay disabled:
they pass assertions but abort at teardown from Safety.vala boxed
array storage (task 2 scope). Suite: 696 ok, 0 failures.
saddle-fedora 1 ay önce
ebeveyn
işleme
ffae1aa542

+ 38 - 21
src/lib/Operators/Comparison.vala

@@ -1,26 +1,43 @@
 namespace Invercargill.Operators {
-    
+
+    /**
+     * Default comparison function for a generic type.
+     *
+     * Value types compare BY VALUE using sign-based comparison
+     * (a > b ? 1 : a < b ? -1 : 0). Never subtract and truncate: the old
+     * int64 branch computed (int64)a - (int64)b into an int, which
+     * overflows and reports the wrong sign for large values (e.g.
+     * 9223372036854775800 vs 10). uint64 subtraction underflowed the same
+     * way.
+     *
+     * Marshalling, same as hash()/equality():
+     *  - inline types (bool, char/uchar, int/uint, long/ulong, enums)
+     *    arrive as a pointer-sized integer carrying the value itself.
+     *  - always-boxed types (int64, uint64, float, double and nullable
+     *    forms) arrive as pointers to the boxed payloads.
+     *
+     * Null boxes sort before every non-null value (documented choice,
+     * matching GLib convention of treating null as smallest).
+     *
+     * Note: typeof(T) cannot distinguish T=int from T=int? (same GType);
+     * inline branches serve the non-nullable forms.
+     */
     public static CompareDelegate<T> comparison<T>() {
         var type = typeof(T);
-        if(type == typeof(string)) {
-            return (a, b) => GLib.strcmp((string)a, (string)b);
-        }
-        else if(type == typeof(int)) {
-            return (a, b) => (int)(((int)a) - ((int)b));
-        }
-        else if(type == typeof(int64)) {
-            return (a, b) => (int)(((int64)a) - ((int64)b));
-        }
-        else if(type == typeof(uint64)) {
-            return (a, b) => (int)(((uint64)a) - ((uint64)b));
-        }
-        else if(type == typeof(double)) {
-            return (a, b) => (int)(((double?)a) - ((double?)b));
-        }
-        else if(type.is_a(typeof(Comparable))) {
-            return (a, b) => ((Comparable<T>)a).compare(b);
-        }
-        return (a, b) => (int)(&a - &b);
+        if(type == typeof(string)) { return (a, b) => GLib.strcmp((string)a, (string)b); }
+        else if(type == typeof(int) || type == typeof(int32) || type == typeof(int8) || type == typeof(int16)) { return (a, b) => (int)a > (int)b ? 1 : ((int)a < (int)b ? -1 : 0); }
+        else if(type == typeof(uint) || type == typeof(uint32)) { return (a, b) => (uint)a > (uint)b ? 1 : ((uint)a < (uint)b ? -1 : 0); }
+        else if(type == typeof(uchar)) { return (a, b) => (uchar)a > (uchar)b ? 1 : ((uchar)a < (uchar)b ? -1 : 0); }
+        else if(type == typeof(long)) { return (a, b) => (long)a > (long)b ? 1 : ((long)a < (long)b ? -1 : 0); }
+        else if(type == typeof(ulong)) { return (a, b) => (ulong)a > (ulong)b ? 1 : ((ulong)a < (ulong)b ? -1 : 0); }
+        else if(type == typeof(bool)) { return (a, b) => (bool)a == (bool)b ? 0 : ((bool)a ? 1 : -1); }
+        else if(type == typeof(char)) { return (a, b) => (char)a > (char)b ? 1 : ((char)a < (char)b ? -1 : 0); }
+        else if(type.is_enum()) { return (a, b) => (int)a > (int)b ? 1 : ((int)a < (int)b ? -1 : 0); }
+        else if(type == typeof(int64) || type == typeof(uint64)) { return (a, b) => { var x = (int64?)a; var y = (int64?)b; if(x == null && y == null) { return 0; } if(x == null) { return -1; } if(y == null) { return 1; } return (int64)x > (int64)y ? 1 : ((int64)x < (int64)y ? -1 : 0); }; }
+        else if(type == typeof(double)) { return (a, b) => { var x = (double?)a; var y = (double?)b; if(x == null && y == null) { return 0; } if(x == null) { return -1; } if(y == null) { return 1; } return (double)x > (double)y ? 1 : ((double)x < (double)y ? -1 : 0); }; }
+        else if(type == typeof(float)) { return (a, b) => { var x = (float?)a; var y = (float?)b; if(x == null && y == null) { return 0; } if(x == null) { return -1; } if(y == null) { return 1; } return (float)x > (float)y ? 1 : ((float)x < (float)y ? -1 : 0); }; }
+        else if(type.is_a(typeof(Comparable))) { return (a, b) => ((Comparable<T>)a).compare(b); }
+        return (a, b) => 0;
     }
 
-}    
+}

+ 36 - 135
src/lib/Operators/Contain.vala

@@ -1,141 +1,42 @@
-
 namespace Invercargill.Operators {
-    
+
+    /**
+     * Builds a GValue holding a copy of the item.
+     *
+     * Marshalling, same as hash()/equality():
+     *  - inline types (bool, char/uchar, int/uint, long/ulong, enums)
+     *    arrive as a pointer-sized integer carrying the value itself.
+     *  - always-boxed types (int64, uint64, float, double and nullable
+     *    forms) arrive as pointers to the boxed payloads and must be
+     *    dereferenced before storing into the GValue.
+     *
+     * A null box stores the type's default value (0), documented choice:
+     * GValue cannot hold null for non-pointer types.
+     *
+     * Note: typeof(T) cannot distinguish T=int from T=int? (same GType);
+     * inline branches serve the non-nullable forms.
+     */
     public static ContainDelegate<T> contain<T>() {
         var type = typeof(T);
-        if(type == typeof(bool)) {
-            return i => {
-                var value = Value(type);
-                value.set_boolean((bool)i);
-                return value;
-            };
-        }
-        else if(type == typeof(int8)) {
-            return i => {
-                var value = Value(type);
-                value.set_schar((int8)i);
-                return value;
-            };
-        }
-        else if(type == typeof(uchar)) {
-            return i => {
-                var value = Value(type);
-                value.set_uchar((uchar)i);
-                return value;
-            };
-        }
-        else if(type == typeof(int)) {
-            return i => {
-                var value = Value(type);
-                value.set_int((int)i);
-                return value;
-            };
-        }
-        else if(type == typeof(uint)) {
-            return i => {
-                var value = Value(type);
-                value.set_uint((uint)i);
-                return value;
-            };
-        }
-        else if(type == typeof(int64)) {
-            return i => {
-                var value = Value(type);
-                value.set_int64((int64)i);
-                return value;
-            };
-        }
-        else if(type == typeof(uint64)) {
-            return i => {
-                var value = Value(type);
-                value.set_uint64((uint64)i);
-                return value;
-            };
-        }
-        else if(type == typeof(long)) {
-            return i => {
-                var value = Value(type);
-                value.set_long((long)i);
-                return value;
-            };
-        }
-        else if(type == typeof(ulong)) {
-            return i => {
-                var value = Value(type);
-                value.set_ulong((ulong)i);
-                return value;
-            };
-        }
-        else if(type == typeof(float)) {
-            return i => {
-                var value = Value(type);
-                value.set_float((float?)i);
-                return value;
-            };
-        }
-        else if(type == typeof(double)) {
-            return i => {
-                var value = Value(type);
-                value.set_double((double?)i);
-                return value;
-            };
-        }
-        else if(type == typeof(string)) {
-            return i => {
-                var value = Value(type);
-                value.set_string((string)i);
-                return value;
-            };
-        }
-        else if(type == typeof(Type)) {
-            return i => {
-                var value = Value(type);
-                value.set_gtype((Type)i);
-                return value;
-            };
-        }
-        else if(type.is_enum()) {
-            return i => {
-                var value = Value(type);
-                value.set_enum((int)i);
-                return value;
-            };
-        }
-        else if(type.is_flags()) {
-            return i => {
-                var value = Value(type);
-                value.set_flags((uint)i);
-                return value;
-            };
-        }
-        else if(type == typeof(void*)) {
-            return i => {
-                var value = Value(type);
-                value.set_pointer((void*)i);
-                return value;
-            };
-        }
-        else if(type == typeof(ParamSpec)) {
-            return i => {
-                var value = Value(type);
-                value.set_param((ParamSpec)i);
-                return value;
-            };
-        }
-        else if(type.is_a(typeof(Object))) {
-            return i => {
-                var value = Value(type);
-                value.set_object((Object)i);
-                return value;
-            };
-        }
-        else {
-            return i => {
-                var value = Value(type);
-                value.set_boxed((void*)i);
-                return value;
-            };
-        }
+        if(type == typeof(bool)) { return i => { var value = Value(type); value.set_boolean((bool)i); return value; }; }
+        else if(type == typeof(int8)) { return i => { var value = Value(type); value.set_schar((int8)i); return value; }; }
+        else if(type == typeof(uchar)) { return i => { var value = Value(type); value.set_uchar((uchar)i); return value; }; }
+        else if(type == typeof(int)) { return i => { var value = Value(type); value.set_int((int)i); return value; }; }
+        else if(type == typeof(uint)) { return i => { var value = Value(type); value.set_uint((uint)i); return value; }; }
+        else if(type == typeof(int64)) { return i => { var value = Value(type); value.set_int64(((int64?)i) == null ? (int64)0 : (int64)((int64?)i)); return value; }; }
+        else if(type == typeof(uint64)) { return i => { var value = Value(type); value.set_uint64(((uint64?)i) == null ? (uint64)0 : (uint64)((uint64?)i)); return value; }; }
+        else if(type == typeof(long)) { return i => { var value = Value(type); value.set_long((long)i); return value; }; }
+        else if(type == typeof(ulong)) { return i => { var value = Value(type); value.set_ulong((ulong)i); return value; }; }
+        else if(type == typeof(float)) { return i => { var value = Value(type); value.set_float(((float?)i) == null ? (float)0 : (float)((float?)i)); return value; }; }
+        else if(type == typeof(double)) { return i => { var value = Value(type); value.set_double(((double?)i) == null ? (double)0 : (double)((double?)i)); return value; }; }
+        else if(type == typeof(string)) { return i => { var value = Value(type); value.set_string((string)i); return value; }; }
+        else if(type == typeof(Type)) { return i => { var value = Value(type); value.set_gtype((Type)i); return value; }; }
+        else if(type.is_enum()) { return i => { var value = Value(type); value.set_enum((int)i); return value; }; }
+        else if(type.is_flags()) { return i => { var value = Value(type); value.set_flags((uint)i); return value; }; }
+        else if(type == typeof(void*)) { return i => { var value = Value(type); value.set_pointer((void*)i); return value; }; }
+        else if(type == typeof(ParamSpec)) { return i => { var value = Value(type); value.set_param((ParamSpec)i); return value; }; }
+        else if(type.is_a(typeof(Object))) { return i => { var value = Value(type); value.set_object((Object)i); return value; }; }
+        else { return i => { var value = Value(type); value.set_boxed((void*)i); return value; }; }
     }
 
 }

+ 32 - 15
src/lib/Operators/Equality.vala

@@ -1,21 +1,38 @@
 namespace Invercargill.Operators {
-    
+
+    /**
+     * Default equality function for a generic type.
+     *
+     * Value types compare BY VALUE:
+     *  - inline types (bool, char/uchar, int/uint, long/ulong, enums) arrive
+     *    at the delegate as a pointer-sized integer carrying the value
+     *    itself, so a plain value cast round-trips them.
+     *  - always-boxed types (int64, uint64, float, double and their nullable
+     *    forms) arrive as real pointers to the boxed payloads, so the boxes
+     *    must be dereferenced, never compared as pointers.
+     *
+     * A null box only equals another null box (never equals a boxed 0 or
+     * 0.0), so null keys behave as a distinct value in hash containers.
+     *
+     * Note: typeof(T) cannot distinguish T=int from T=int? (same GType);
+     * the inline branches below serve the non-nullable forms. Nullable
+     * int?/long?/bool?/char? keys share the GType and are boxed - use the
+     * always-boxed types or Equatable implementations for nullable keys.
+     */
     public static EqualityDelegate<T> equality<T>() {
         var type = typeof(T);
-        if(type == typeof(string)) {
-            return (a, b) => GLib.str_equal((string)a, (string)b);
-        }
-        else if(type == typeof(int64) || type == typeof(uint64)) {
-            return (a, b) => GLib.int64_equal((int64)a, (int64)b);
-        }
-        else if(type == typeof(double)) {
-            return (a, b) => GLib.double_equal((double?)a, (double?)b);
-        }
-        else if(type.is_a(typeof(Equatable))) {
-            return (a, b) => ((Equatable<T>)a).equals(b);
-        }
+        if(type == typeof(string)) { return (a, b) => GLib.str_equal((string)a, (string)b); }
+        else if(type == typeof(int64) || type == typeof(uint64)) { return (a, b) => { var x = (int64?)a; var y = (int64?)b; if(x == null || y == null) { return x == y; } return GLib.int64_equal(x, y); }; }
+        else if(type == typeof(double)) { return (a, b) => { var x = (double?)a; var y = (double?)b; if(x == null || y == null) { return x == y; } return (double)x == (double)y; }; }
+        else if(type == typeof(float)) { return (a, b) => { var x = (float?)a; var y = (float?)b; if(x == null || y == null) { return x == y; } return (float)x == (float)y; }; }
+        else if(type == typeof(int) || type == typeof(int32)) { return (a, b) => (int)a == (int)b; }
+        else if(type == typeof(uint) || type == typeof(uint32)) { return (a, b) => (uint)a == (uint)b; }
+        else if(type == typeof(long)) { return (a, b) => (long)a == (long)b; }
+        else if(type == typeof(ulong)) { return (a, b) => (ulong)a == (ulong)b; }
+        else if(type == typeof(bool)) { return (a, b) => (bool)a == (bool)b; }
+        else if(type == typeof(char)) { return (a, b) => (char)a == (char)b; }
+        else if(type.is_a(typeof(Equatable))) { return (a, b) => ((Equatable<T>)a).equals(b); }
         return (a, b) => GLib.direct_equal(a, b);
     }
 
-
-}
+}

+ 36 - 14
src/lib/Operators/Hash.vala

@@ -1,20 +1,42 @@
 namespace Invercargill.Operators {
-    
+
+    /**
+     * Default hash function for a generic type.
+     *
+     * Value types are hashed BY VALUE:
+     *  - inline types (bool, char/uchar, int/uint, long/ulong, enums) arrive
+     *    at the delegate as a pointer-sized integer carrying the value
+     *    itself, so a plain value cast round-trips them.
+     *  - always-boxed types (int64, uint64, float, double and their nullable
+     *    forms) arrive as a real pointer to the boxed payload, so the box
+     *    must be dereferenced, never reinterpreted as the value.
+     *
+     * A null box (e.g. an int64? holding null) hashes as 0. Documented
+     * choice: it keeps hash() total (never crashes on null) while
+     * equality() only equates null with null, so container correctness
+     * does not depend on the value picked here.
+     *
+     * Note: typeof(T) cannot distinguish T=int from T=int? (same GType);
+     * the inline branches below therefore serve the non-nullable forms
+     * (which arrive inline). Nullable int?/long?/bool?/char? keys share
+     * the GType and are boxed - use the always-boxed types or Hashable
+     * implementations for nullable keys.
+     */
     public static HashDelegate<T> hash<T>() {
         var type = typeof(T);
-        if(type == typeof(string)) {
-            return i => GLib.str_hash((string)i);
-        }
-        else if(type == typeof(int64) || type == typeof(uint64)) {
-            return i => GLib.int64_hash((int64)i);
-        }
-        else if(type == typeof(double)) {
-            return i => GLib.double_hash((double?)i);
-        }
-        else if(type.is_a(typeof(Hashable))) {
-            return (k) => ((Hashable)k).hash_code();
-        }
+        if(type == typeof(string)) { return i => GLib.str_hash((string)i); }
+        else if(type == typeof(int64) || type == typeof(uint64)) { return i => { var box = (int64?)i; return box == null ? 0u : GLib.int64_hash(box); }; }
+        else if(type == typeof(double)) { return i => { var box = (double?)i; return box == null ? 0u : GLib.double_hash(box); }; }
+        else if(type == typeof(float)) { return i => { var box = (float?)i; return box == null ? 0u : GLib.double_hash((double?)((double)box)); }; }
+        else if(type == typeof(int)) { return i => GLib.int_hash((int)i); }
+        else if(type == typeof(uint)) { return i => { var v = (uint)i; return v ^ (v >> 31); }; }
+        else if(type == typeof(long)) { return i => { var v = (long)i; return (uint)(v ^ (v >> 32)); }; }
+        else if(type == typeof(ulong)) { return i => { var v = (ulong)i; return (uint)(v ^ (v >> 32)); }; }
+        else if(type == typeof(bool)) { return i => (bool)i ? 1u : 0u; }
+        else if(type.is_a(typeof(Hashable))) { return (k) => ((Hashable)k).hash_code(); }
+        // char/uchar and other small inline types hash fine through their
+        // pointer bits (the value itself).
         return i => GLib.direct_hash(i);
     }
 
-}
+}

+ 41 - 45
src/lib/Operators/Stringify.vala

@@ -1,51 +1,47 @@
-
 namespace Invercargill.Operators {
-    
+
+    /**
+     * Default stringify function for a generic type.
+     *
+     * Marshalling, same as hash()/equality()/comparison():
+     *  - inline types (bool, char/uchar, int/uint, long/ulong, enums)
+     *    arrive as a pointer-sized integer carrying the value itself.
+     *  - always-boxed types (int64, uint64, float, double and nullable
+     *    forms) arrive as pointers to the boxed payloads and are
+     *    dereferenced (a null box stringifies as "null").
+     *
+     * char stringifies as the character, not its numeric code (typeof
+     * (char) == typeof(int8), so this branch must come first and serves
+     * both). uchar stringifies numerically, matching uint8. Enums
+     * stringify as their value nick when registered, falling back to
+     * the numeric value.
+     *
+     * Note: typeof(T) cannot distinguish T=int from T=int? (same GType);
+     * inline branches serve the non-nullable forms.
+     */
     public static StringifyDelegate<T> stringify<T>() {
         var type = typeof(T);
-        if(type == typeof(string)) {
-            return i => (string)i;
-        }
-        else if(type == typeof(int)) {
-            return i => ((int)i).to_string();
-        }
-        else if(type == typeof(int8)) {
-            return i => ((int8)i).to_string();
-        }
-        else if(type == typeof(uint8)) {
-            return i => ((uint8)i).to_string();
-        }
-        else if(type == typeof(int16)) {
-            return i => ((int16)i).to_string();
-        }
-        else if(type == typeof(uint16)) {
-            return i => ((uint16)i).to_string();
-        }
-        else if(type == typeof(int32)) {
-            return i => ((int32)i).to_string();
-        }
-        else if(type == typeof(uint32)) {
-            return i => ((uint32)i).to_string();
-        }
-        else if(type == typeof(int64)) {
-            return i => ((int64?)i).to_string();
-        }
-        else if(type == typeof(uint64)) {
-            return i => ((uint64?)i).to_string();
-        }
-        else if(type == typeof(double)) {
-            return i => ((double?)i).to_string();
-        }
-        else if(type == typeof(float)) {
-            return i => ((float?)i).to_string();
-        }
-        else if(type.is_a(typeof(Stringifyable))) {
-            return i => ((Stringifyable)i).to_string();
-        }
-        else if(type.is_a(typeof(BinaryData))) {
-            return i => "0x" + ((BinaryData)i).to_hex();
-        }
+        if(type == typeof(string)) { return i => (string)i; }
+        else if(type == typeof(char)) { return i => ((char)i).to_string(); }
+        else if(type == typeof(int)) { return i => ((int)i).to_string(); }
+        else if(type == typeof(uint8)) { return i => ((uint8)i).to_string(); }
+        else if(type == typeof(int16)) { return i => ((int16)i).to_string(); }
+        else if(type == typeof(uint16)) { return i => ((uint16)i).to_string(); }
+        else if(type == typeof(uint)) { return i => ((uint)i).to_string(); }
+        else if(type == typeof(int64)) { return i => ((int64?)i) == null ? "null" : ((int64)((int64?)i)).to_string(); }
+        else if(type == typeof(uint64)) { return i => ((uint64?)i) == null ? "null" : ((uint64)((uint64?)i)).to_string(); }
+        else if(type == typeof(double)) { return i => ((double?)i) == null ? "null" : ((double)((double?)i)).to_string(); }
+        else if(type == typeof(float)) { return i => ((float?)i) == null ? "null" : ((float)((float?)i)).to_string(); }
+        else if(type == typeof(long)) { return i => ((long)i).to_string(); }
+        else if(type == typeof(ulong)) { return i => ((ulong)i).to_string(); }
+        else if(type == typeof(bool)) { return i => ((bool)i).to_string(); }
+        else if(type.is_enum()) {
+            var enum_class = (EnumClass)type.class_ref();
+            return i => { var v = enum_class.get_value((int)i); return v == null ? ((int)i).to_string() : v.value_nick; };
+        }
+        else if(type.is_a(typeof(Stringifyable))) { return i => ((Stringifyable)i).to_string(); }
+        else if(type.is_a(typeof(BinaryData))) { return i => "0x" + ((BinaryData)i).to_hex(); }
         return i => @"[$(type.name()) at 0x$(((int)i).to_string("%x"))]";
     }
 
-}
+}

Dosya farkı çok büyük olduğundan ihmal edildi
+ 430 - 484
src/tests/Integration/ValueTypeMatrix.vala


Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor