[PATCH 0/2] MR10960: scrrun/dictionary: Match native key semantics for Boolean, Currency, and Empty keys
Boolean and Currency keys were rejected outright; hash them by their numeric value like the other numeric types. Compare numeric keys at double precision instead of single, so that values differing only beyond float precision (e.g. 16777216 and 16777217) remain distinct keys. Treat Empty as the zero/default value of the other key's type, matching any numeric zero (including False and date 0) or the empty string. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10960
From: Francis De Brabandere <francisdb@gmail.com> Tests for native key-matching semantics not yet implemented in Wine: Boolean and Currency keys, value-based numeric comparison at double precision across types, and Empty matching the zero/default value of any numeric type or the empty string. --- dlls/scrrun/tests/dictionary.c | 152 +++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) diff --git a/dlls/scrrun/tests/dictionary.c b/dlls/scrrun/tests/dictionary.c index e1cb33c45a7..b12606ec8a4 100644 --- a/dlls/scrrun/tests/dictionary.c +++ b/dlls/scrrun/tests/dictionary.c @@ -744,6 +744,40 @@ if (0) { /* crashes on native */ ok(V_VT(&hash) == VT_I4, "got %d\n", V_VT(&hash)); ok(V_I4(&hash) == expected, "got hash %#lx, expected %#lx\n", V_I4(&hash), expected); + /* Boolean keys hash by their numeric value. */ + V_VT(&key) = VT_BOOL; + V_BOOL(&key) = VARIANT_FALSE; + V_I4(&hash) = 5678; + hr = IDictionary_get_HashVal(dict, &key, &hash); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(V_VT(&hash) == VT_I4, "got %d\n", V_VT(&hash)); + todo_wine ok(V_I4(&hash) == get_num_hash(0.0f), "Unexpected hash %#lx.\n", V_I4(&hash)); + + V_VT(&key) = VT_BOOL; + V_BOOL(&key) = VARIANT_TRUE; + V_I4(&hash) = 5678; + hr = IDictionary_get_HashVal(dict, &key, &hash); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(V_VT(&hash) == VT_I4, "got %d\n", V_VT(&hash)); + todo_wine ok(V_I4(&hash) == get_num_hash((FLOAT)VARIANT_TRUE), "Unexpected hash %#lx.\n", V_I4(&hash)); + + /* Currency keys hash by their numeric value, matching equal numbers. */ + V_VT(&key) = VT_CY; + V_CY(&key).int64 = 0; + V_I4(&hash) = 5678; + hr = IDictionary_get_HashVal(dict, &key, &hash); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(V_VT(&hash) == VT_I4, "got %d\n", V_VT(&hash)); + todo_wine ok(V_I4(&hash) == get_num_hash(0.0f), "Unexpected hash %#lx.\n", V_I4(&hash)); + + V_VT(&key) = VT_CY; + V_CY(&key).int64 = 5 * 10000; + V_I4(&hash) = 5678; + hr = IDictionary_get_HashVal(dict, &key, &hash); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(V_VT(&hash) == VT_I4, "got %d\n", V_VT(&hash)); + todo_wine ok(V_I4(&hash) == get_num_hash(5.0f), "Unexpected hash %#lx.\n", V_I4(&hash)); + V_VT(&key) = VT_EMPTY; V_I4(&key) = 1234; V_I4(&hash) = 5678; @@ -1050,6 +1084,123 @@ static void test_Add(void) IDictionary_Release(dict); } +/* Add two keys to a fresh dictionary; returns TRUE if the second key is + * treated as a duplicate of the first. */ +static BOOL keys_match(VARIANT *key1, VARIANT *key2) +{ + IDictionary *dict; + VARIANT item; + HRESULT hr; + + if (FAILED(CoCreateInstance(&CLSID_Dictionary, NULL, CLSCTX_INPROC_SERVER|CLSCTX_INPROC_HANDLER, + &IID_IDictionary, (void**)&dict))) + return FALSE; + + V_VT(&item) = VT_I2; + V_I2(&item) = 1; + if (FAILED(IDictionary_Add(dict, key1, &item))) + { + IDictionary_Release(dict); + return FALSE; + } + + hr = IDictionary_Add(dict, key2, &item); + IDictionary_Release(dict); + return hr == CTL_E_KEY_ALREADY_EXISTS; +} + +static void test_key_matching(void) +{ + static const VARTYPE zero_keys[] = { + VT_I2, VT_I4, VT_UI1, VT_R4, VT_R8, VT_DATE, VT_BOOL, VT_CY, + }; + VARIANT a, b; + unsigned i; + + /* Numeric keys compare by value across types (Byte/Int/Long/Single/ + * Double/Date/Currency/Boolean), independent of the source type. */ + V_VT(&a) = VT_I2; + V_I2(&a) = 5; + V_VT(&b) = VT_R8; + V_R8(&b) = 5.0; + ok(keys_match(&a, &b), "I2 5 should match R8 5.0\n"); + + V_VT(&b) = VT_CY; + V_CY(&b).int64 = 5 * 10000; + todo_wine ok(keys_match(&a, &b), "I2 5 should match CY 5\n"); + + V_VT(&b) = VT_DATE; + V_DATE(&b) = 5.0; + ok(keys_match(&a, &b), "I2 5 should match DATE 5\n"); + + V_VT(&a) = VT_BOOL; + V_BOOL(&a) = VARIANT_TRUE; + V_VT(&b) = VT_I2; + V_I2(&b) = -1; + todo_wine ok(keys_match(&a, &b), "True should match I2 -1\n"); + + /* Comparison uses double precision: values that differ in R8 but collapse + * to the same R4 are still distinct keys. */ + V_VT(&a) = VT_R8; + V_R8(&a) = 16777216.0; + V_VT(&b) = VT_R8; + V_R8(&b) = 16777217.0; + todo_wine ok(!keys_match(&a, &b), "16777216.0 and 16777217.0 should be distinct keys\n"); + + V_R8(&a) = 5.0; + V_R8(&b) = 5.0000001; + todo_wine ok(!keys_match(&a, &b), "5.0 and 5.0000001 should be distinct keys\n"); + + /* Empty matches the zero/default value of any numeric type, and False. */ + for (i = 0; i < ARRAY_SIZE(zero_keys); i++) + { + V_VT(&a) = VT_EMPTY; + V_VT(&b) = zero_keys[i]; + if (zero_keys[i] == VT_CY) + V_CY(&b).int64 = 0; + else if (zero_keys[i] == VT_R8 || zero_keys[i] == VT_DATE) + V_R8(&b) = 0.0; + else if (zero_keys[i] == VT_R4) + V_R4(&b) = 0.0f; + else + V_I4(&b) = 0; + todo_wine ok(keys_match(&a, &b), "Empty should match zero key vt %d\n", zero_keys[i]); + } + + /* Empty matches the empty string, but not a non-empty one. */ + V_VT(&a) = VT_EMPTY; + V_VT(&b) = VT_BSTR; + V_BSTR(&b) = SysAllocString(L""); + todo_wine ok(keys_match(&a, &b), "Empty should match empty string\n"); + VariantClear(&b); + + V_VT(&b) = VT_BSTR; + V_BSTR(&b) = SysAllocString(L"0"); + ok(!keys_match(&a, &b), "Empty should not match \"0\"\n"); + VariantClear(&b); + + /* Empty does not match a non-zero number or Null. */ + V_VT(&b) = VT_I2; + V_I2(&b) = 5; + ok(!keys_match(&a, &b), "Empty should not match I2 5\n"); + + V_VT(&b) = VT_NULL; + ok(!keys_match(&a, &b), "Empty should not match Null\n"); + + /* A numeric zero does not match the empty string (only Empty bridges). */ + V_VT(&a) = VT_I2; + V_I2(&a) = 0; + V_VT(&b) = VT_BSTR; + V_BSTR(&b) = SysAllocString(L""); + ok(!keys_match(&a, &b), "I2 0 should not match empty string\n"); + VariantClear(&b); + + /* Null matches only Null. */ + V_VT(&a) = VT_NULL; + V_VT(&b) = VT_NULL; + ok(keys_match(&a, &b), "Null should match Null\n"); +} + static void test_IEnumVARIANT(void) { IUnknown *enum1, *enum2; @@ -1230,6 +1381,7 @@ START_TEST(dictionary) test_Remove(); test_Item(); test_Add(); + test_key_matching(); test_IEnumVARIANT(); test_putref_Item(); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10960
From: Francis De Brabandere <francisdb@gmail.com> Boolean and Currency keys were rejected outright; hash them by their numeric value like the other numeric types. Compare numeric keys at double precision instead of single, so that values differing only beyond float precision (e.g. 16777216 and 16777217) remain distinct keys. Treat Empty as the zero/default value of the other key's type, matching any numeric zero (including False and date 0) or the empty string. --- dlls/scrrun/dictionary.c | 54 ++++++++++++++++++++++++++++++---- dlls/scrrun/tests/dictionary.c | 28 +++++++++--------- 2 files changed, 62 insertions(+), 20 deletions(-) diff --git a/dlls/scrrun/dictionary.c b/dlls/scrrun/dictionary.c index af771c9245a..07cd05d32b0 100644 --- a/dlls/scrrun/dictionary.c +++ b/dlls/scrrun/dictionary.c @@ -119,6 +119,8 @@ static inline BOOL is_numeric_key(const VARIANT *key) case VT_DATE: case VT_R4: case VT_R8: + case VT_BOOL: + case VT_CY: return TRUE; default: return FALSE; @@ -152,19 +154,48 @@ static inline BOOL numeric_key_eq(const VARIANT *key1, const VARIANT *key2) VARIANT v1, v2; VariantInit(&v1); - if (FAILED(VariantChangeType(&v1, key1, 0, VT_R4))) + if (FAILED(VariantChangeType(&v1, key1, 0, VT_R8))) return FALSE; VariantInit(&v2); - if (FAILED(VariantChangeType(&v2, key2, 0, VT_R4))) + if (FAILED(VariantChangeType(&v2, key2, 0, VT_R8))) return FALSE; - return V_R4(&v1) == V_R4(&v2); + return V_R8(&v1) == V_R8(&v2); +} + +/* Empty matches the zero/default value of the other key's type: any numeric + * zero (including False and date 0), the empty string, or another Empty. */ +static BOOL empty_matches_key(const VARIANT *key) +{ + VARIANT v; + + switch (V_VT(key)) + { + case VT_EMPTY: + return TRUE; + case VT_BSTR: + { + const WCHAR *str = get_key_strptr(key); + return !str || !*str; + } + default: + if (!is_numeric_key(key)) + return FALSE; + VariantInit(&v); + if (FAILED(VariantChangeType(&v, key, 0, VT_R8))) + return FALSE; + return V_R8(&v) == 0.0; + } } static BOOL is_matching_key(const struct dictionary *dict, const struct keyitem_pair *pair, const VARIANT *key, DWORD hash) { - if (is_string_key(key) != is_string_key(&pair->key)) + if (V_VT(key) == VT_EMPTY || V_VT(&pair->key) == VT_EMPTY) + { + return empty_matches_key(V_VT(key) == VT_EMPTY ? &pair->key : key); + } + else if (is_string_key(key) != is_string_key(&pair->key)) { return FALSE; } @@ -188,9 +219,9 @@ static BOOL is_matching_key(const struct dictionary *dict, const struct keyitem_ { return hash == pair->hash && numeric_key_eq(key, &pair->key); } - else if (V_VT(&pair->key) == VT_EMPTY || V_VT(&pair->key) == VT_NULL) + else if (V_VT(&pair->key) == VT_NULL) { - return V_VT(&pair->key) == V_VT(key); + return V_VT(key) == VT_NULL; } else { @@ -919,6 +950,17 @@ static HRESULT WINAPI dictionary_get_HashVal(IDictionary *iface, VARIANT *key, V case VT_R8|VT_BYREF: case VT_R8: return get_flt_hash(V_VT(key) & VT_BYREF ? *V_R8REF(key) : V_R8(key), &V_I4(hash)); + case VT_BOOL|VT_BYREF: + case VT_BOOL: + V_I4(hash) = get_num_hash(V_VT(key) & VT_BYREF ? *V_BOOLREF(key) : V_BOOL(key)); + break; + case VT_CY|VT_BYREF: + case VT_CY: + { + CY cy = V_VT(key) & VT_BYREF ? *V_CYREF(key) : V_CY(key); + V_I4(hash) = get_num_hash((FLOAT)((double)cy.int64 / 10000.0)); + break; + } case VT_EMPTY: case VT_NULL: V_I4(hash) = 0; diff --git a/dlls/scrrun/tests/dictionary.c b/dlls/scrrun/tests/dictionary.c index b12606ec8a4..90fb0e283c2 100644 --- a/dlls/scrrun/tests/dictionary.c +++ b/dlls/scrrun/tests/dictionary.c @@ -749,34 +749,34 @@ if (0) { /* crashes on native */ V_BOOL(&key) = VARIANT_FALSE; V_I4(&hash) = 5678; hr = IDictionary_get_HashVal(dict, &key, &hash); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ok(V_VT(&hash) == VT_I4, "got %d\n", V_VT(&hash)); - todo_wine ok(V_I4(&hash) == get_num_hash(0.0f), "Unexpected hash %#lx.\n", V_I4(&hash)); + ok(V_I4(&hash) == get_num_hash(0.0f), "Unexpected hash %#lx.\n", V_I4(&hash)); V_VT(&key) = VT_BOOL; V_BOOL(&key) = VARIANT_TRUE; V_I4(&hash) = 5678; hr = IDictionary_get_HashVal(dict, &key, &hash); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ok(V_VT(&hash) == VT_I4, "got %d\n", V_VT(&hash)); - todo_wine ok(V_I4(&hash) == get_num_hash((FLOAT)VARIANT_TRUE), "Unexpected hash %#lx.\n", V_I4(&hash)); + ok(V_I4(&hash) == get_num_hash((FLOAT)VARIANT_TRUE), "Unexpected hash %#lx.\n", V_I4(&hash)); /* Currency keys hash by their numeric value, matching equal numbers. */ V_VT(&key) = VT_CY; V_CY(&key).int64 = 0; V_I4(&hash) = 5678; hr = IDictionary_get_HashVal(dict, &key, &hash); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ok(V_VT(&hash) == VT_I4, "got %d\n", V_VT(&hash)); - todo_wine ok(V_I4(&hash) == get_num_hash(0.0f), "Unexpected hash %#lx.\n", V_I4(&hash)); + ok(V_I4(&hash) == get_num_hash(0.0f), "Unexpected hash %#lx.\n", V_I4(&hash)); V_VT(&key) = VT_CY; V_CY(&key).int64 = 5 * 10000; V_I4(&hash) = 5678; hr = IDictionary_get_HashVal(dict, &key, &hash); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ok(V_VT(&hash) == VT_I4, "got %d\n", V_VT(&hash)); - todo_wine ok(V_I4(&hash) == get_num_hash(5.0f), "Unexpected hash %#lx.\n", V_I4(&hash)); + ok(V_I4(&hash) == get_num_hash(5.0f), "Unexpected hash %#lx.\n", V_I4(&hash)); V_VT(&key) = VT_EMPTY; V_I4(&key) = 1234; @@ -1127,7 +1127,7 @@ static void test_key_matching(void) V_VT(&b) = VT_CY; V_CY(&b).int64 = 5 * 10000; - todo_wine ok(keys_match(&a, &b), "I2 5 should match CY 5\n"); + ok(keys_match(&a, &b), "I2 5 should match CY 5\n"); V_VT(&b) = VT_DATE; V_DATE(&b) = 5.0; @@ -1137,7 +1137,7 @@ static void test_key_matching(void) V_BOOL(&a) = VARIANT_TRUE; V_VT(&b) = VT_I2; V_I2(&b) = -1; - todo_wine ok(keys_match(&a, &b), "True should match I2 -1\n"); + ok(keys_match(&a, &b), "True should match I2 -1\n"); /* Comparison uses double precision: values that differ in R8 but collapse * to the same R4 are still distinct keys. */ @@ -1145,11 +1145,11 @@ static void test_key_matching(void) V_R8(&a) = 16777216.0; V_VT(&b) = VT_R8; V_R8(&b) = 16777217.0; - todo_wine ok(!keys_match(&a, &b), "16777216.0 and 16777217.0 should be distinct keys\n"); + ok(!keys_match(&a, &b), "16777216.0 and 16777217.0 should be distinct keys\n"); V_R8(&a) = 5.0; V_R8(&b) = 5.0000001; - todo_wine ok(!keys_match(&a, &b), "5.0 and 5.0000001 should be distinct keys\n"); + ok(!keys_match(&a, &b), "5.0 and 5.0000001 should be distinct keys\n"); /* Empty matches the zero/default value of any numeric type, and False. */ for (i = 0; i < ARRAY_SIZE(zero_keys); i++) @@ -1164,14 +1164,14 @@ static void test_key_matching(void) V_R4(&b) = 0.0f; else V_I4(&b) = 0; - todo_wine ok(keys_match(&a, &b), "Empty should match zero key vt %d\n", zero_keys[i]); + ok(keys_match(&a, &b), "Empty should match zero key vt %d\n", zero_keys[i]); } /* Empty matches the empty string, but not a non-empty one. */ V_VT(&a) = VT_EMPTY; V_VT(&b) = VT_BSTR; V_BSTR(&b) = SysAllocString(L""); - todo_wine ok(keys_match(&a, &b), "Empty should match empty string\n"); + ok(keys_match(&a, &b), "Empty should match empty string\n"); VariantClear(&b); V_VT(&b) = VT_BSTR; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10960
Do you have a real world use case for CY/BOOL types? -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10960#note_140759
On Wed May 20 09:06:14 2026 +0000, Nikolay Sivov wrote:
Do you have a real world use case for CY/BOOL types? No. Do you want me to remove the fixes for those?
My workflow is: I find an issue (in vpinball scripts running using libwinevbs), test on windows, find more issues, fix all. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10960#note_140765
participants (3)
-
Francis De Brabandere -
Francis De Brabandere (@francisdb) -
Nikolay Sivov (@nsivov)