From 746ced55019d07f3de25e40ce6efb20dbd4ae95b Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 21 Dec 2024 08:58:05 -0600 Subject: [PATCH] Revert behavior of deep= on mutable keys. Mutable keys are a minefield for comparisons, as resolving equality require re-implementing a lot of the internal structures, as well as dealing with multiple mutable keys that are in the same equivalency class by deep=. Simplifying the implementation to not resole mutable keys is much simpler, faster, and has the benefit that deep= and deep-not= do not need to allocate. --- CHANGELOG.md | 1 + src/boot/boot.janet | 16 ++-------------- src/core/struct.c | 11 +++++++++++ test/suite-boot.janet | 16 ++++++++++------ test/suite-marsh.janet | 4 ++-- 5 files changed, 26 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 131455250..a83876c86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ All notable changes to this project will be documented in this file. ## ??? - Unreleased +- Add `struct/rawget` - Fix `deep=` and `deep-not=` to better handle degenerate cases with mutable table keys - Long strings will now dedent on `\r\n` instead of just `\n`. - Add `ev/to-file` for synchronous resource operations diff --git a/src/boot/boot.janet b/src/boot/boot.janet index f0b586319..577a67fbc 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -2259,8 +2259,6 @@ :string (buffer ds) ds)) -(def- mutable-types {:table true :array true :buffer true}) - (defn deep-not= ``Like `not=`, but mutable types (arrays, tables, buffers) are considered equal if they have identical structure. Much slower than `not=`.`` @@ -2282,20 +2280,10 @@ (or (= tx :struct) (= tx :table)) (or (not= (length x) (length y)) (do + (def rawget (if (= tx :struct) struct/rawget table/rawget)) (var ret false) - (def mut-keys-x @{}) (eachp [k v] x - (if (get mutable-types (type k)) - (let [kk (freeze k)] - (put mut-keys-x kk (put (get mut-keys-x kk @{}) (freeze v) true))) - (if (deep-not= (get y k) v) (break (set ret true))))) - (when (next mut-keys-x) # handle case when we have mutable keys separately - (def mut-keys-y @{}) - (eachp [k v] y - (if (get mutable-types (type k)) - (let [kk (freeze k)] - (put mut-keys-y kk (put (get mut-keys-y kk @{}) (freeze v) true))))) - (set ret (deep-not= mut-keys-x mut-keys-y))) + (if (deep-not= (rawget y k) v) (break (set ret true)))) ret)) (= tx :buffer) (not= 0 (- (length x) (length y)) (memcmp x y)) (not= x y)))) diff --git a/src/core/struct.c b/src/core/struct.c index acc9a9211..2e4be6270 100644 --- a/src/core/struct.c +++ b/src/core/struct.c @@ -294,6 +294,16 @@ JANET_CORE_FN(cfun_struct_to_table, return janet_wrap_table(tab); } +JANET_CORE_FN(cfun_struct_rawget, + "(struct/rawget st key)", + "Gets a value from a struct `st` without looking at the prototype struct. " + "If `st` does not contain the key directly, the function will return " + "nil without checking the prototype. Returns the value in the struct.") { + janet_fixarity(argc, 2); + JanetStruct st = janet_getstruct(argv, 0); + return janet_struct_rawget(st, argv[1]); +} + /* Load the struct module */ void janet_lib_struct(JanetTable *env) { JanetRegExt struct_cfuns[] = { @@ -301,6 +311,7 @@ void janet_lib_struct(JanetTable *env) { JANET_CORE_REG("struct/getproto", cfun_struct_getproto), JANET_CORE_REG("struct/proto-flatten", cfun_struct_flatten), JANET_CORE_REG("struct/to-table", cfun_struct_to_table), + JANET_CORE_REG("struct/rawget", cfun_struct_rawget), JANET_REG_END }; janet_core_cfuns_ext(env, NULL, struct_cfuns); diff --git a/test/suite-boot.janet b/test/suite-boot.janet index 045f03688..b8f226257 100644 --- a/test/suite-boot.janet +++ b/test/suite-boot.janet @@ -1004,12 +1004,16 @@ # issue #1535 (loop [i :range [1 1000]] - (assert (deep= @{:key1 "value1" @"key" "value2"} - @{:key1 "value1" @"key" "value2"}) "deep= mutable keys")) + (assert (deep-not= @{:key1 "value1" @"key" "value2"} + @{:key1 "value1" @"key" "value2"}) "deep= mutable keys")) (assert (deep-not= {"abc" 123} {@"abc" 123}) "deep= mutable keys vs immutable key") -(assert (deep= {@"" 1 @"" 2 @"" 3} {@"" 1 @"" 2 @"" 3}) "deep= duplicate mutable keys") -(assert (deep= {@"" @"" @"" @"" @"" 3} {@"" @"" @"" @"" @"" 3}) "deep= duplicate mutable keys 2") -(assert (deep= {@[] @"" @[] @"" @[] 3} {@[] @"" @[] @"" @[] 3}) "deep= duplicate mutable keys 3") -(assert (deep= {@{} @"" @{} @"" @{} 3} {@{} @"" @{} @"" @{} 3}) "deep= duplicate mutable keys 4") +(assert (deep-not= {@"" 1 @"" 2 @"" 3} {@"" 1 @"" 2 @"" 3}) "deep= duplicate mutable keys") +(assert (deep-not= {@"" @"" @"" @"" @"" 3} {@"" @"" @"" @"" @"" 3}) "deep= duplicate mutable keys 2") +(assert (deep-not= {@[] @"" @[] @"" @[] 3} {@[] @"" @[] @"" @[] 3}) "deep= duplicate mutable keys 3") +(assert (deep-not= {@{} @"" @{} @"" @{} 3} {@{} @"" @{} @"" @{} 3}) "deep= duplicate mutable keys 4") +(assert (deep-not= @{:key1 "value1" @"key2" @"value2"} + @{:key1 "value1" @"key2" "value2"}) "deep= mutable keys") +(assert (deep-not= @{:key1 "value1" [@"key2"] @"value2"} + @{:key1 "value1" [@"key2"] @"value2"}) "deep= mutable keys") (end-suite) diff --git a/test/suite-marsh.janet b/test/suite-marsh.janet index b9f4d2772..1bcfb0d50 100644 --- a/test/suite-marsh.janet +++ b/test/suite-marsh.janet @@ -207,7 +207,7 @@ neldb\0\0\0\xD8\x05printG\x01\0\xDE\xDE\xDE'\x03\0marshal_tes/\x02 (assert (= 2 (length tclone)) "table/weak-values marsh 2") (gccollect) (assert (= 1 (length t)) "table/weak-value marsh 3") -(assert (deep= t tclone) "table/weak-values marsh 4") +(assert (deep= (freeze t) (freeze tclone)) "table/weak-values marsh 4") # tables with prototypes (def t (table/weak-values 1)) @@ -219,7 +219,7 @@ neldb\0\0\0\xD8\x05printG\x01\0\xDE\xDE\xDE'\x03\0marshal_tes/\x02 (assert (= 2 (length tclone)) "marsh weak tables with prototypes 2") (gccollect) (assert (= 1 (length t)) "marsh weak tables with prototypes 3") -(assert (deep= t tclone) "marsh weak tables with prototypes 4") +(assert (deep= (freeze t) (freeze tclone)) "marsh weak tables with prototypes 4") (assert (deep= (getproto t) (getproto tclone)) "marsh weak tables with prototypes 5") (end-suite)