Fix #398: Allow overriding [] operator with string index on Dynamic_Object subtypes (#667)

The built-in Dynamic_Object::get_attr was always winning dispatch over
user-defined [] overrides when a string index was used, because its C++
type signature (Dynamic_Object, const string&) scored numdiffs=0 while
ChaiScript-defined functions had higher numdiffs due to undefined type
info. The fix deprioritizes generic C++ Dynamic_Object functions when the
actual first argument is a specific subtype, and maps named ChaiScript
type parameters to Dynamic_Object type_info for correct dispatch scoring.

Co-authored-by: leftibot <leftibot@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
leftibot 2026-04-11 18:59:09 -06:00 committed by GitHub
parent bcd07e05cd
commit 726169acc8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 81 additions and 2 deletions

View File

@ -103,7 +103,9 @@ namespace chaiscript {
assert(types.size() > 1);
// assert(types[1].bare_equal(user_type<Boxed_Value>()));
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<Dynamic_Object>() : t_objectti;
return types;
}

View File

@ -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<Boxed_Value>::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<Dynamic_Object>());
} else {
types.push_back(chaiscript::detail::Get_Type_Info<Boxed_Value>::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<const Dynamic_Proxy_Function *>(func.get()) == nullptr) {
static const auto dynamic_object_ti = user_type<Dynamic_Object>();
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<const Dynamic_Object &>(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());
}
}

View File

@ -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]);