diff --git a/include/chaiscript/dispatchkit/dynamic_object_detail.hpp b/include/chaiscript/dispatchkit/dynamic_object_detail.hpp index 1a599d0a..e9c8012e 100644 --- a/include/chaiscript/dispatchkit/dynamic_object_detail.hpp +++ b/include/chaiscript/dispatchkit/dynamic_object_detail.hpp @@ -103,7 +103,9 @@ namespace chaiscript { assert(types.size() > 1); // assert(types[1].bare_equal(user_type())); - types[1] = t_objectti; + // When the object type_info is undefined (ChaiScript-defined class), use + // Dynamic_Object so that dispatch priority scoring works correctly. + types[1] = t_objectti.is_undef() ? user_type() : t_objectti; return types; } diff --git a/include/chaiscript/dispatchkit/proxy_functions.hpp b/include/chaiscript/dispatchkit/proxy_functions.hpp index 002e473b..326fa257 100644 --- a/include/chaiscript/dispatchkit/proxy_functions.hpp +++ b/include/chaiscript/dispatchkit/proxy_functions.hpp @@ -380,7 +380,15 @@ namespace chaiscript { for (const auto &t : t_types.types()) { if (t.second.is_undef()) { - types.push_back(chaiscript::detail::Get_Type_Info::get()); + if (!t.first.empty()) { + // Named type without C++ type_info — assumed to be a Dynamic_Object subtype. + // Using Dynamic_Object type_info ensures correct dispatch priority so that + // user-defined overrides on specific subtypes are tried before generic + // Dynamic_Object built-in functions. + types.push_back(user_type()); + } else { + types.push_back(chaiscript::detail::Get_Type_Info::get()); + } } else { types.push_back(t.second); } @@ -806,6 +814,30 @@ namespace chaiscript { numdiffs = plist.size(); } + // Deprioritize C++ registered generic Dynamic_Object functions when the + // actual first argument is a specific Dynamic_Object subtype. This allows + // user-defined operator overrides (e.g. `[]`) on a subtype to take precedence + // over the built-in Dynamic_Object operations such as get_attr. + // Only deprioritize non-dynamic (C++ registered) functions — ChaiScript-defined + // functions (Dynamic_Proxy_Function) have their own type matching via Param_Types. + if (!plist.empty() + && dynamic_cast(func.get()) == nullptr) { + static const auto dynamic_object_ti = user_type(); + if (func->get_param_types().size() > 1 + && func->get_param_types()[1].bare_equal(dynamic_object_ti) + && plist[0].get_type_info().bare_equal(dynamic_object_ti) + && func->dynamic_object_type_name().empty()) { + try { + const auto &d = boxed_cast(plist[0], &t_conversions); + if (d.get_type_name() != "Dynamic_Object") { + numdiffs = plist.size(); + } + } catch (const std::bad_cast &) { + // not a Dynamic_Object, ignore + } + } + } + ordered_funcs.emplace_back(numdiffs, func.get()); } } diff --git a/unittests/index_operator_override.chai b/unittests/index_operator_override.chai new file mode 100644 index 00000000..2a3d89c3 --- /dev/null +++ b/unittests/index_operator_override.chai @@ -0,0 +1,45 @@ + +// Test overriding [] operator with various index types (issue #398) + +class my_class +{ + def my_class() + { + } +}; + +def `[]`(my_class o, idx) +{ + return "Hello World!"; +} + +var o = my_class(); + +// Integer index should work +assert_equal("Hello World!", o[3]); + +// String index should work +assert_equal("Hello World!", o["3"]); + +// String variable as index should work +var s = "abc"; +assert_equal("Hello World!", o[s]); + +// Typed string parameter override +class my_class2 +{ + def my_class2() + { + } +}; + +def `[]`(my_class2 o, string key) +{ + return "key=" + key; +} + +var o2 = my_class2(); +assert_equal("key=foo", o2["foo"]); + +var key = "bar"; +assert_equal("key=bar", o2[key]);