From 39f590f2a9782b67542357388dc0902e131a5e2f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 18 Jun 2026 16:44:12 +0000 Subject: [PATCH 1/7] Initial plan From eef42d3a1be625a7d60e4eff351fd937f195f786 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 18 Jun 2026 16:59:30 +0000 Subject: [PATCH 2/7] Apply remaining changes --- Distribution/LuaBridge/LuaBridge.h | 58 ++++++---- Source/LuaBridge/detail/CFunctions.h | 75 +++++++++---- Tests/Source/IssueTests.cpp | 154 +++++++++++++++++++++++++++ 3 files changed, 242 insertions(+), 45 deletions(-) diff --git a/Distribution/LuaBridge/LuaBridge.h b/Distribution/LuaBridge/LuaBridge.h index 1bdcdbe9..e857461d 100644 --- a/Distribution/LuaBridge/LuaBridge.h +++ b/Distribution/LuaBridge/LuaBridge.h @@ -8254,7 +8254,7 @@ inline std::optional try_call_newindex_extensible(lua_State* L, const char* } template -inline std::optional try_call_parent_newindex(lua_State* L) +inline std::optional try_call_parent_newindex_setters(lua_State* L) { LUABRIDGE_ASSERT(lua_istable(L, -1)); @@ -8304,6 +8304,37 @@ inline std::optional try_call_parent_newindex(lua_State* L) lua_pop(L, 1); } + lua_pop(L, 1); + } + + lua_pop(L, 1); + return std::nullopt; +} + +template +inline std::optional try_call_parent_newindex_fallbacks(lua_State* L) +{ + LUABRIDGE_ASSERT(lua_istable(L, -1)); + + lua_rawgetp_x(L, -1, getParentKey()); + if (! lua_istable(L, -1)) + { + lua_pop(L, 1); + return std::nullopt; + } + + const int parentListIndex = lua_absindex(L, -1); + const int parentCount = get_length(L, parentListIndex); + + for (int i = 1; i <= parentCount; ++i) + { + lua_rawgeti(L, parentListIndex, i); + if (! lua_istable(L, -1)) + { + lua_pop(L, 1); + continue; + } + lua_rawgetp_x(L, -1, getNewIndexFallbackKey()); if (lua_iscfunction(L, -1)) { @@ -8359,6 +8390,9 @@ inline int newindex_metamethod(lua_State* L) lua_pop(L, 1); + if (auto result = try_call_parent_newindex_setters(L)) + return *result; + if constexpr (IsObject) { if (auto result = try_call_newindex_fallback(L)) @@ -8384,27 +8418,7 @@ inline int newindex_metamethod(lua_State* L) } } - lua_rawgetp_x(L, -1, getPropsetKey()); - if (lua_istable(L, -1)) - { - lua_pushvalue(L, 2); - lua_rawget(L, -2); - lua_remove(L, -2); - - if (lua_iscfunction(L, -1)) - { - lua_remove(L, -2); - if constexpr (IsObject) - lua_pushvalue(L, 1); - lua_pushvalue(L, 3); - lua_call(L, IsObject ? 2 : 1, 0); - return 0; - } - } - - lua_pop(L, 1); - - if (auto result = try_call_parent_newindex(L)) + if (auto result = try_call_parent_newindex_fallbacks(L)) return *result; if constexpr (IsObject) diff --git a/Source/LuaBridge/detail/CFunctions.h b/Source/LuaBridge/detail/CFunctions.h index d7c3fe56..5301cea3 100644 --- a/Source/LuaBridge/detail/CFunctions.h +++ b/Source/LuaBridge/detail/CFunctions.h @@ -1045,8 +1045,14 @@ inline std::optional try_call_newindex_extensible(lua_State* L, const char* return 0; } +/** + * @brief Scan the flattened parent list for a property setter matching the key (stack[2]). + * + * Only setters are checked; __newindex fallbacks are intentionally ignored so that a + * registered property anywhere in the hierarchy always takes priority over any fallback. + */ template -inline std::optional try_call_parent_newindex(lua_State* L) +inline std::optional try_call_parent_newindex_setters(lua_State* L) { LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: mt @@ -1096,6 +1102,43 @@ inline std::optional try_call_parent_newindex(lua_State* L) lua_pop(L, 1); // Stack: mt, parent list, parent mt } + lua_pop(L, 1); // Stack: mt, parent list + } + + lua_pop(L, 1); // Stack: mt + return std::nullopt; +} + +/** + * @brief Scan the flattened parent list for a __newindex fallback. + * + * Only fallbacks are checked; property setters are intentionally ignored because they + * have already been searched by try_call_parent_newindex_setters. + */ +template +inline std::optional try_call_parent_newindex_fallbacks(lua_State* L) +{ + LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: mt + + lua_rawgetp_x(L, -1, getParentKey()); // Stack: mt, parent list | nil + if (! lua_istable(L, -1)) + { + lua_pop(L, 1); // Stack: mt + return std::nullopt; + } + + const int parentListIndex = lua_absindex(L, -1); + const int parentCount = get_length(L, parentListIndex); + + for (int i = 1; i <= parentCount; ++i) + { + lua_rawgeti(L, parentListIndex, i); // Stack: mt, parent list, parent mt + if (! lua_istable(L, -1)) + { + lua_pop(L, 1); + continue; + } + lua_rawgetp_x(L, -1, getNewIndexFallbackKey()); // Stack: mt, parent list, parent mt, nifb | nil if (lua_iscfunction(L, -1)) { @@ -1152,6 +1195,13 @@ inline int newindex_metamethod(lua_State* L) lua_pop(L, 1); // Stack: mt + // Before consulting any __newindex fallback, scan the entire parent hierarchy for a + // matching property setter. This ensures that a registered property anywhere in the + // inheritance chain always takes priority over a __newindex fallback defined at a + // narrower scope (e.g. an intermediate or leaf class). + if (auto result = try_call_parent_newindex_setters(L)) + return *result; + if constexpr (IsObject) { if (auto result = try_call_newindex_fallback(L)) @@ -1178,28 +1228,7 @@ inline int newindex_metamethod(lua_State* L) } } - // Try in the propget key - lua_rawgetp_x(L, -1, getPropsetKey()); // Stack: mt, propset table (ps) - if (lua_istable(L, -1)) - { - lua_pushvalue(L, 2); // Stack: mt, ps, field name - lua_rawget(L, -2); // Stack: mt, ps, setter | nil - lua_remove(L, -2); // Stack: mt, setter | nil - - if (lua_iscfunction(L, -1)) // Stack: mt, setter - { - lua_remove(L, -2); // Stack: setter - if constexpr (IsObject) - lua_pushvalue(L, 1); // Stack: setter, table | userdata - lua_pushvalue(L, 3); // Stack: setter, table | userdata, new value - lua_call(L, IsObject ? 2 : 1, 0); // Stack: - - return 0; - } - } - - lua_pop(L, 1); // Stack: mt - - if (auto result = try_call_parent_newindex(L)) + if (auto result = try_call_parent_newindex_fallbacks(L)) return *result; if constexpr (IsObject) diff --git a/Tests/Source/IssueTests.cpp b/Tests/Source/IssueTests.cpp index 032fa252..329bc721 100644 --- a/Tests/Source/IssueTests.cpp +++ b/Tests/Source/IssueTests.cpp @@ -4,6 +4,9 @@ #include "TestBase.h" +#include +#include + struct IssueTests : TestBase { }; @@ -266,3 +269,154 @@ TEST_F(IssueTests, IssueMainThread) luabridge::LuaRef test = luabridge::getGlobal(L, "test"); EXPECT_TRUE(test.call()); } + +// ============================================================================= +// Issue 242: try_call_parent_newindex must scan all parent setters before +// considering any __newindex fallback. +// ============================================================================= + +namespace { + +struct Issue242Base +{ + int x = 0; + std::map extra; + + int getX() const { return x; } + void setX(int v) { x = v; } +}; + +struct Issue242Middle : Issue242Base +{ + // Intermediate class that adds a __newindex fallback. + // fallbackWritten tracks whether the fallback was called. + bool fallbackWritten = false; + + luabridge::LuaRef onNewIndex(const luabridge::LuaRef& key, const luabridge::LuaRef& value, lua_State* L) + { + fallbackWritten = true; + extra[key.tostring()] = value.unsafe_cast(); + return value; + } + + luabridge::LuaRef onIndex(const luabridge::LuaRef& key, lua_State* L) + { + auto it = extra.find(key.tostring()); + if (it != extra.end()) + { + auto res = luabridge::push(L, it->second); + (void)res; + return luabridge::LuaRef::fromStack(L); + } + lua_pushnil(L); + return luabridge::LuaRef::fromStack(L); + } +}; + +struct Issue242Derived : Issue242Middle +{ + // Most-derived class; inherits everything from Middle and Base. +}; + +} // namespace + +// Scenario 1 (from the issue): Base has a setter for 'x'; Derived has a __newindex +// fallback. Setting 'x' on a Derived instance must invoke Base's setter, NOT the +// fallback. +TEST_F(IssueTests, Issue242_BaseSetterTakesPriorityOverDerivedNewIndexFallback) +{ + Issue242Middle obj; + + luabridge::getGlobalNamespace(L) + .beginClass("Base") + .addProperty("x", &Issue242Base::getX, &Issue242Base::setX) + .endClass() + .deriveClass("Middle") + .addIndexMetaMethod(&Issue242Middle::onIndex) + .addNewIndexMetaMethod(&Issue242Middle::onNewIndex) + .endClass(); + + luabridge::setGlobal(L, &obj, "obj"); + + // Writing 'x' must reach the registered setter, not the fallback. + ASSERT_TRUE(runLua("obj.x = 99")); + EXPECT_EQ(99, obj.x); + EXPECT_FALSE(obj.fallbackWritten); + + // Reading 'x' back must return the value set via the C++ property. + ASSERT_TRUE(runLua("result = obj.x")); + EXPECT_EQ(99, result()); +} + +// Scenario 2: deep hierarchy — A has a setter; B (intermediate) has a __newindex +// fallback; C (most-derived) has neither. Setting 'x' on a C instance must invoke +// A's setter, skipping B's fallback. +TEST_F(IssueTests, Issue242_DeepHierarchyBaseSetterTakesPriorityOverIntermediateNewIndexFallback) +{ + Issue242Derived obj; + + luabridge::getGlobalNamespace(L) + .beginClass("Base") + .addProperty("x", &Issue242Base::getX, &Issue242Base::setX) + .endClass() + .deriveClass("Middle") + .addIndexMetaMethod(&Issue242Middle::onIndex) + .addNewIndexMetaMethod(&Issue242Middle::onNewIndex) + .endClass() + .deriveClass("Derived") + .endClass(); + + luabridge::setGlobal(L, &obj, "obj"); + + ASSERT_TRUE(runLua("obj.x = 42")); + EXPECT_EQ(42, obj.x); + EXPECT_FALSE(obj.fallbackWritten); +} + +// Scenario 3: the __newindex fallback is still invoked for keys that have no +// registered setter anywhere in the hierarchy. +TEST_F(IssueTests, Issue242_NewIndexFallbackCalledForUnregisteredProperties) +{ + Issue242Middle obj; + + luabridge::getGlobalNamespace(L) + .beginClass("Base") + .addProperty("x", &Issue242Base::getX, &Issue242Base::setX) + .endClass() + .deriveClass("Middle") + .addIndexMetaMethod(&Issue242Middle::onIndex) + .addNewIndexMetaMethod(&Issue242Middle::onNewIndex) + .endClass(); + + luabridge::setGlobal(L, &obj, "obj"); + + // 'y' has no setter in the hierarchy → fallback must be called. + ASSERT_TRUE(runLua("obj.y = 7")); + EXPECT_TRUE(obj.fallbackWritten); + EXPECT_EQ(7, obj.extra["y"]); + // 'x' is still handled by the C++ setter. + ASSERT_TRUE(runLua("obj.x = 5")); + EXPECT_EQ(5, obj.x); +} + +// Scenario 4: a setter defined on the derived class itself still takes priority +// over a __newindex fallback on the same class. +TEST_F(IssueTests, Issue242_DerivedOwnSetterTakesPriorityOverItsOwnNewIndexFallback) +{ + Issue242Middle obj; + + luabridge::getGlobalNamespace(L) + .beginClass("Base") + .endClass() + .deriveClass("Middle") + .addProperty("x", &Issue242Base::getX, &Issue242Base::setX) + .addIndexMetaMethod(&Issue242Middle::onIndex) + .addNewIndexMetaMethod(&Issue242Middle::onNewIndex) + .endClass(); + + luabridge::setGlobal(L, &obj, "obj"); + + ASSERT_TRUE(runLua("obj.x = 11")); + EXPECT_EQ(11, obj.x); + EXPECT_FALSE(obj.fallbackWritten); +} From 1a0a1cef5ee61ed7cec217f0c419994b789c6ad7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 18 Jun 2026 17:10:59 +0000 Subject: [PATCH 3/7] Changes before error encountered Agent-Logs-Url: https://github.com/kunitoki/LuaBridge3/sessions/2c0f7e14-d35b-4772-98a2-4f466f6d6cb6 --- Distribution/LuaBridge/LuaBridge.h | 65 +++++++++++++---- Source/LuaBridge/detail/CFunctions.h | 81 +++++++++++++++++---- Tests/Source/IssueTests.cpp | 103 +++++++++++++++++++++++++++ 3 files changed, 219 insertions(+), 30 deletions(-) diff --git a/Distribution/LuaBridge/LuaBridge.h b/Distribution/LuaBridge/LuaBridge.h index e857461d..7d5d06c0 100644 --- a/Distribution/LuaBridge/LuaBridge.h +++ b/Distribution/LuaBridge/LuaBridge.h @@ -7632,7 +7632,7 @@ inline std::optional try_call_index_extensible(lua_State* L, const char* ke } template -inline std::optional try_call_parent_index_fallback(lua_State* L, const char* key) +inline std::optional try_call_parent_index_extensibles(lua_State* L, const char* key) { LUABRIDGE_ASSERT(lua_istable(L, -1)); @@ -7669,6 +7669,40 @@ inline std::optional try_call_parent_index_fallback(lua_State* L, const cha } } + lua_pop(L, 1); + } + + lua_pop(L, 1); + return std::nullopt; +} + +template +inline std::optional try_call_parent_index_fallbacks(lua_State* L, const char* key) +{ + LUABRIDGE_ASSERT(lua_istable(L, -1)); + + if (key == nullptr) + return std::nullopt; + + lua_rawgetp_x(L, -1, getParentKey()); + if (! lua_istable(L, -1)) + { + lua_pop(L, 1); + return std::nullopt; + } + + const int parentListIndex = lua_absindex(L, -1); + const int parentCount = get_length(L, parentListIndex); + + for (int i = 1; i <= parentCount; ++i) + { + lua_rawgeti(L, parentListIndex, i); + if (! lua_istable(L, -1)) + { + lua_pop(L, 1); + continue; + } + lua_rawgetp_x(L, -1, getIndexFallbackKey()); if (lua_iscfunction(L, -1)) { @@ -7719,19 +7753,6 @@ inline int index_metamethod(lua_State* L) for (;;) { - if constexpr (IsObject) - { - - if (auto result = try_call_index_fallback(L)) - return *result; - } - else - { - - if (auto result = try_call_static_index_fallback(L)) - return *result; - } - if (lua_istable(L, 1)) { if constexpr (IsObject) @@ -7858,7 +7879,21 @@ inline int index_metamethod(lua_State* L) return *result; } - if (auto result = try_call_parent_index_fallback(L, key)) + if (auto result = try_call_parent_index_extensibles(L, key)) + return *result; + + if constexpr (IsObject) + { + if (auto result = try_call_index_fallback(L)) + return *result; + } + else + { + if (auto result = try_call_static_index_fallback(L)) + return *result; + } + + if (auto result = try_call_parent_index_fallbacks(L, key)) return *result; lua_pop(L, 1); diff --git a/Source/LuaBridge/detail/CFunctions.h b/Source/LuaBridge/detail/CFunctions.h index 5301cea3..69c8915c 100644 --- a/Source/LuaBridge/detail/CFunctions.h +++ b/Source/LuaBridge/detail/CFunctions.h @@ -390,8 +390,14 @@ inline std::optional try_call_index_extensible(lua_State* L, const char* ke return std::nullopt; } +/** + * @brief Scan the flattened parent list for an extensible class getter. + * + * Only extensible checks are performed; __index fallbacks are intentionally ignored so that a + * registered getter anywhere in the hierarchy always takes priority over any fallback. + */ template -inline std::optional try_call_parent_index_fallback(lua_State* L, const char* key) +inline std::optional try_call_parent_index_extensibles(lua_State* L, const char* key) { LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: mt @@ -428,6 +434,46 @@ inline std::optional try_call_parent_index_fallback(lua_State* L, const cha } } + lua_pop(L, 1); // Stack: mt, parent list + } + + lua_pop(L, 1); // Stack: mt + return std::nullopt; +} + +/** + * @brief Scan the flattened parent list for a __index fallback. + * + * Only fallbacks are checked; extensible getters are intentionally ignored because they + * have already been searched by try_call_parent_index_extensibles. + */ +template +inline std::optional try_call_parent_index_fallbacks(lua_State* L, const char* key) +{ + LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: mt + + if (key == nullptr) + return std::nullopt; + + lua_rawgetp_x(L, -1, getParentKey()); // Stack: mt, parent list | nil + if (! lua_istable(L, -1)) + { + lua_pop(L, 1); // Stack: mt + return std::nullopt; + } + + const int parentListIndex = lua_absindex(L, -1); + const int parentCount = get_length(L, parentListIndex); + + for (int i = 1; i <= parentCount; ++i) + { + lua_rawgeti(L, parentListIndex, i); // Stack: mt, parent list, parent mt + if (! lua_istable(L, -1)) + { + lua_pop(L, 1); + continue; + } + lua_rawgetp_x(L, -1, getIndexFallbackKey()); // Stack: mt, parent list, parent mt, ifb | nil if (lua_iscfunction(L, -1)) { @@ -479,19 +525,6 @@ inline int index_metamethod(lua_State* L) for (;;) { - if constexpr (IsObject) - { - // Repeat the lookup in the index fallback - if (auto result = try_call_index_fallback(L)) - return *result; - } - else - { - // Repeat the lookup in the static index fallback - if (auto result = try_call_static_index_fallback(L)) - return *result; - } - // Search into self or metatable if (lua_istable(L, 1)) { @@ -624,7 +657,25 @@ inline int index_metamethod(lua_State* L) return *result; } - if (auto result = try_call_parent_index_fallback(L, key)) + // Before consulting any __index fallback, scan the entire parent hierarchy for a + // matching extensible getter. This ensures that a registered getter anywhere in the + // inheritance chain always takes priority over a __index fallback defined at a + // narrower scope (e.g. an intermediate or leaf class). + if (auto result = try_call_parent_index_extensibles(L, key)) + return *result; + + if constexpr (IsObject) + { + if (auto result = try_call_index_fallback(L)) + return *result; + } + else + { + if (auto result = try_call_static_index_fallback(L)) + return *result; + } + + if (auto result = try_call_parent_index_fallbacks(L, key)) return *result; lua_pop(L, 1); // Stack: - diff --git a/Tests/Source/IssueTests.cpp b/Tests/Source/IssueTests.cpp index 329bc721..77954475 100644 --- a/Tests/Source/IssueTests.cpp +++ b/Tests/Source/IssueTests.cpp @@ -420,3 +420,106 @@ TEST_F(IssueTests, Issue242_DerivedOwnSetterTakesPriorityOverItsOwnNewIndexFallb EXPECT_EQ(11, obj.x); EXPECT_FALSE(obj.fallbackWritten); } + +// ============================================================================= +// Issue 242 (index): try_call_parent_index must scan all parent getters before +// considering any __index fallback. +// ============================================================================= + +// Scenario 1: Base has a getter for 'x'; Middle has a __index fallback. +// Reading 'x' on a Middle instance must invoke Base's getter, NOT the fallback. +TEST_F(IssueTests, Issue242_BaseGetterTakesPriorityOverDerivedIndexFallback) +{ + Issue242Middle obj; + obj.x = 55; + + luabridge::getGlobalNamespace(L) + .beginClass("Base") + .addProperty("x", &Issue242Base::getX, &Issue242Base::setX) + .endClass() + .deriveClass("Middle") + .addIndexMetaMethod(&Issue242Middle::onIndex) + .addNewIndexMetaMethod(&Issue242Middle::onNewIndex) + .endClass(); + + luabridge::setGlobal(L, &obj, "obj"); + + // Reading 'x' must reach the registered getter, not the __index fallback. + ASSERT_TRUE(runLua("result = obj.x")); + EXPECT_EQ(55, result()); +} + +// Scenario 2: deep hierarchy — A has a getter; B (intermediate) has a __index +// fallback; C (most-derived) has neither. Reading 'x' on a C instance must invoke +// A's getter, skipping B's fallback. +TEST_F(IssueTests, Issue242_DeepHierarchyBaseGetterTakesPriorityOverIntermediateIndexFallback) +{ + Issue242Derived obj; + obj.x = 77; + + luabridge::getGlobalNamespace(L) + .beginClass("Base") + .addProperty("x", &Issue242Base::getX, &Issue242Base::setX) + .endClass() + .deriveClass("Middle") + .addIndexMetaMethod(&Issue242Middle::onIndex) + .addNewIndexMetaMethod(&Issue242Middle::onNewIndex) + .endClass() + .deriveClass("Derived") + .endClass(); + + luabridge::setGlobal(L, &obj, "obj"); + + ASSERT_TRUE(runLua("result = obj.x")); + EXPECT_EQ(77, result()); +} + +// Scenario 3: the __index fallback is still invoked for keys that have no +// registered getter anywhere in the hierarchy. +TEST_F(IssueTests, Issue242_IndexFallbackCalledForUnregisteredProperties) +{ + Issue242Middle obj; + obj.extra["y"] = 42; + + luabridge::getGlobalNamespace(L) + .beginClass("Base") + .addProperty("x", &Issue242Base::getX, &Issue242Base::setX) + .endClass() + .deriveClass("Middle") + .addIndexMetaMethod(&Issue242Middle::onIndex) + .addNewIndexMetaMethod(&Issue242Middle::onNewIndex) + .endClass(); + + luabridge::setGlobal(L, &obj, "obj"); + + // 'y' has no getter in the hierarchy → fallback must be called. + ASSERT_TRUE(runLua("result = obj.y")); + EXPECT_EQ(42, result()); + + // 'x' is still handled by the C++ getter. + obj.x = 99; + ASSERT_TRUE(runLua("result = obj.x")); + EXPECT_EQ(99, result()); +} + +// Scenario 4: a getter defined on the derived class itself still takes priority +// over a __index fallback on the same class. +TEST_F(IssueTests, Issue242_DerivedOwnGetterTakesPriorityOverItsOwnIndexFallback) +{ + Issue242Middle obj; + obj.x = 33; + + luabridge::getGlobalNamespace(L) + .beginClass("Base") + .endClass() + .deriveClass("Middle") + .addProperty("x", &Issue242Base::getX, &Issue242Base::setX) + .addIndexMetaMethod(&Issue242Middle::onIndex) + .addNewIndexMetaMethod(&Issue242Middle::onNewIndex) + .endClass(); + + luabridge::setGlobal(L, &obj, "obj"); + + ASSERT_TRUE(runLua("result = obj.x")); + EXPECT_EQ(33, result()); +} From 7294db0f2f0a9818119f8da5b476198c6a63f6d3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 18 Jun 2026 20:16:45 +0000 Subject: [PATCH 4/7] Fix cross-class member pointer overload resolution and restore __index fallback priority semantics - Decouple registration class C from member-owner class B in 4 getter and 4 setter push_class_property_getter/setter overloads so that base-class member function pointers work correctly when registering properties on a derived class (e.g. addProperty("x", &Base::getX, &Base::setX) on Class). - Restore __index fallback priority inside index_metamethod for two cases that the previous WIP commit broke: 1. Static __index: static fallback must be consulted before registered static property getters (StaticIndexFallbackCanShadowExistingStaticProperty). 2. Instance __index with allowOverridingMethods: the instance fallback must be consulted before class-table Lua methods so Lua-side overrides take effect (IndexFallbackMetaMethodFreeFunctorOnClassOverwriteProperty). For plain instance __index (no allowOverridingMethods), the fallback is still consulted only after all registered getters across the full hierarchy (Issue 242 fix remains intact). All 1080 tests pass. --- Distribution/LuaBridge/LuaBridge.h | 55 +++++++++++++++++++--------- Source/LuaBridge/detail/CFunctions.h | 55 +++++++++++++++++++--------- 2 files changed, 76 insertions(+), 34 deletions(-) diff --git a/Distribution/LuaBridge/LuaBridge.h b/Distribution/LuaBridge/LuaBridge.h index 7d5d06c0..49acee4a 100644 --- a/Distribution/LuaBridge/LuaBridge.h +++ b/Distribution/LuaBridge/LuaBridge.h @@ -7753,6 +7753,28 @@ inline int index_metamethod(lua_State* L) for (;;) { + const Options options = get_class_options(L, -1); + + // For static __index: the static fallback takes priority over registered static + // property getters so that a user-defined static __index fallback can shadow + // static properties. + // For instance __index with allowOverridingMethods: the instance fallback takes + // priority over class-table Lua methods, enabling Lua-side method overrides via + // __newindex. + if constexpr (IsObject) + { + if (options.test(extensibleClass | allowOverridingMethods)) + { + if (auto result = try_call_index_fallback(L)) + return *result; + } + } + else + { + if (auto result = try_call_static_index_fallback(L)) + return *result; + } + if (lua_istable(L, 1)) { if constexpr (IsObject) @@ -7786,7 +7808,6 @@ inline int index_metamethod(lua_State* L) LUABRIDGE_ASSERT(lua_isnil(L, -1)); lua_pop(L, 1); - const Options options = get_class_options(L, -1); if (options.test(extensibleClass | allowOverridingMethods)) { if (auto result = try_call_index_extensible(L, key)) @@ -9495,8 +9516,8 @@ void push_class_property_getter(lua_State* L, T (U::*value), const char* debugna lua_pushcclosure_x(L, &property_getter::call, debugname, 1); } -template -void push_class_property_getter(lua_State* L, T (C::*getter)() const, const char* debugname) +template +void push_class_property_getter(lua_State* L, T (B::*getter)() const, const char* debugname) { using GetType = decltype(getter); @@ -9504,8 +9525,8 @@ void push_class_property_getter(lua_State* L, T (C::*getter)() const, const char lua_pushcclosure_x(L, &invoke_const_member_function, debugname, 1); } -template -void push_class_property_getter(lua_State* L, T (C::*getter)() const noexcept, const char* debugname) +template +void push_class_property_getter(lua_State* L, T (B::*getter)() const noexcept, const char* debugname) { using GetType = decltype(getter); @@ -9513,8 +9534,8 @@ void push_class_property_getter(lua_State* L, T (C::*getter)() const noexcept, c lua_pushcclosure_x(L, &invoke_const_member_function, debugname, 1); } -template -void push_class_property_getter(lua_State* L, T (C::*getter)(lua_State*) const, const char* debugname) +template +void push_class_property_getter(lua_State* L, T (B::*getter)(lua_State*) const, const char* debugname) { using GetType = decltype(getter); @@ -9522,8 +9543,8 @@ void push_class_property_getter(lua_State* L, T (C::*getter)(lua_State*) const, lua_pushcclosure_x(L, &invoke_const_member_function, debugname, 1); } -template -void push_class_property_getter(lua_State* L, T (C::*getter)(lua_State*) const noexcept, const char* debugname) +template +void push_class_property_getter(lua_State* L, T (B::*getter)(lua_State*) const noexcept, const char* debugname) { using GetType = decltype(getter); @@ -9650,8 +9671,8 @@ void push_class_property_setter(lua_State* L, T U::*value, const char* debugname lua_pushcclosure_x(L, &property_setter::call, debugname, 1); } -template -void push_class_property_setter(lua_State* L, void (C::*setter)(T), const char* debugname) +template +void push_class_property_setter(lua_State* L, void (B::*setter)(T), const char* debugname) { using SetType = decltype(setter); @@ -9659,8 +9680,8 @@ void push_class_property_setter(lua_State* L, void (C::*setter)(T), const char* lua_pushcclosure_x(L, &invoke_member_function, debugname, 1); } -template -void push_class_property_setter(lua_State* L, void (C::*setter)(T) noexcept, const char* debugname) +template +void push_class_property_setter(lua_State* L, void (B::*setter)(T) noexcept, const char* debugname) { using SetType = decltype(setter); @@ -9668,8 +9689,8 @@ void push_class_property_setter(lua_State* L, void (C::*setter)(T) noexcept, con lua_pushcclosure_x(L, &invoke_member_function, debugname, 1); } -template -void push_class_property_setter(lua_State* L, void (C::*setter)(T, lua_State*), const char* debugname) +template +void push_class_property_setter(lua_State* L, void (B::*setter)(T, lua_State*), const char* debugname) { using SetType = decltype(setter); @@ -9677,8 +9698,8 @@ void push_class_property_setter(lua_State* L, void (C::*setter)(T, lua_State*), lua_pushcclosure_x(L, &invoke_member_function, debugname, 1); } -template -void push_class_property_setter(lua_State* L, void (C::*setter)(T, lua_State*) noexcept, const char* debugname) +template +void push_class_property_setter(lua_State* L, void (B::*setter)(T, lua_State*) noexcept, const char* debugname) { using SetType = decltype(setter); diff --git a/Source/LuaBridge/detail/CFunctions.h b/Source/LuaBridge/detail/CFunctions.h index 69c8915c..51c2b86f 100644 --- a/Source/LuaBridge/detail/CFunctions.h +++ b/Source/LuaBridge/detail/CFunctions.h @@ -525,6 +525,28 @@ inline int index_metamethod(lua_State* L) for (;;) { + const Options options = get_class_options(L, -1); // Stack: mt + + // For static __index: the static fallback takes priority over registered static + // property getters so that a user-defined static __index fallback can shadow + // static properties. + // For instance __index with allowOverridingMethods: the instance fallback takes + // priority over class-table Lua methods, enabling Lua-side method overrides via + // __newindex. + if constexpr (IsObject) + { + if (options.test(extensibleClass | allowOverridingMethods)) + { + if (auto result = try_call_index_fallback(L)) + return *result; + } + } + else + { + if (auto result = try_call_static_index_fallback(L)) + return *result; + } + // Search into self or metatable if (lua_istable(L, 1)) { @@ -560,7 +582,6 @@ inline int index_metamethod(lua_State* L) lua_pop(L, 1); // Stack: mt // Repeat the lookup in the index extensible, for method overrides - const Options options = get_class_options(L, -1); // Stack: mt if (options.test(extensibleClass | allowOverridingMethods)) { if (auto result = try_call_index_extensible(L, key)) @@ -2476,8 +2497,8 @@ void push_class_property_getter(lua_State* L, T (U::*value), const char* debugna lua_pushcclosure_x(L, &property_getter::call, debugname, 1); } -template -void push_class_property_getter(lua_State* L, T (C::*getter)() const, const char* debugname) +template +void push_class_property_getter(lua_State* L, T (B::*getter)() const, const char* debugname) { using GetType = decltype(getter); @@ -2485,8 +2506,8 @@ void push_class_property_getter(lua_State* L, T (C::*getter)() const, const char lua_pushcclosure_x(L, &invoke_const_member_function, debugname, 1); } -template -void push_class_property_getter(lua_State* L, T (C::*getter)() const noexcept, const char* debugname) +template +void push_class_property_getter(lua_State* L, T (B::*getter)() const noexcept, const char* debugname) { using GetType = decltype(getter); @@ -2494,8 +2515,8 @@ void push_class_property_getter(lua_State* L, T (C::*getter)() const noexcept, c lua_pushcclosure_x(L, &invoke_const_member_function, debugname, 1); } -template -void push_class_property_getter(lua_State* L, T (C::*getter)(lua_State*) const, const char* debugname) +template +void push_class_property_getter(lua_State* L, T (B::*getter)(lua_State*) const, const char* debugname) { using GetType = decltype(getter); @@ -2503,8 +2524,8 @@ void push_class_property_getter(lua_State* L, T (C::*getter)(lua_State*) const, lua_pushcclosure_x(L, &invoke_const_member_function, debugname, 1); } -template -void push_class_property_getter(lua_State* L, T (C::*getter)(lua_State*) const noexcept, const char* debugname) +template +void push_class_property_getter(lua_State* L, T (B::*getter)(lua_State*) const noexcept, const char* debugname) { using GetType = decltype(getter); @@ -2639,8 +2660,8 @@ void push_class_property_setter(lua_State* L, T U::*value, const char* debugname lua_pushcclosure_x(L, &property_setter::call, debugname, 1); } -template -void push_class_property_setter(lua_State* L, void (C::*setter)(T), const char* debugname) +template +void push_class_property_setter(lua_State* L, void (B::*setter)(T), const char* debugname) { using SetType = decltype(setter); @@ -2648,8 +2669,8 @@ void push_class_property_setter(lua_State* L, void (C::*setter)(T), const char* lua_pushcclosure_x(L, &invoke_member_function, debugname, 1); } -template -void push_class_property_setter(lua_State* L, void (C::*setter)(T) noexcept, const char* debugname) +template +void push_class_property_setter(lua_State* L, void (B::*setter)(T) noexcept, const char* debugname) { using SetType = decltype(setter); @@ -2657,8 +2678,8 @@ void push_class_property_setter(lua_State* L, void (C::*setter)(T) noexcept, con lua_pushcclosure_x(L, &invoke_member_function, debugname, 1); } -template -void push_class_property_setter(lua_State* L, void (C::*setter)(T, lua_State*), const char* debugname) +template +void push_class_property_setter(lua_State* L, void (B::*setter)(T, lua_State*), const char* debugname) { using SetType = decltype(setter); @@ -2666,8 +2687,8 @@ void push_class_property_setter(lua_State* L, void (C::*setter)(T, lua_State*), lua_pushcclosure_x(L, &invoke_member_function, debugname, 1); } -template -void push_class_property_setter(lua_State* L, void (C::*setter)(T, lua_State*) noexcept, const char* debugname) +template +void push_class_property_setter(lua_State* L, void (B::*setter)(T, lua_State*) noexcept, const char* debugname) { using SetType = decltype(setter); From c10fb0259e14112005208547ae9d17010c231aa0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 18 Jun 2026 22:23:52 +0000 Subject: [PATCH 5/7] Add static_assert(is_same_v || is_base_of_v) to push_class_property getter/setter overloads with C and B params --- Distribution/LuaBridge/LuaBridge.h | 16 ++++++++++++++++ Source/LuaBridge/detail/CFunctions.h | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/Distribution/LuaBridge/LuaBridge.h b/Distribution/LuaBridge/LuaBridge.h index 49acee4a..d5ce9cf2 100644 --- a/Distribution/LuaBridge/LuaBridge.h +++ b/Distribution/LuaBridge/LuaBridge.h @@ -9519,6 +9519,8 @@ void push_class_property_getter(lua_State* L, T (U::*value), const char* debugna template void push_class_property_getter(lua_State* L, T (B::*getter)() const, const char* debugname) { + static_assert(std::is_same_v || std::is_base_of_v); + using GetType = decltype(getter); new (lua_newuserdata_x(L, sizeof(GetType))) GetType(getter); @@ -9528,6 +9530,8 @@ void push_class_property_getter(lua_State* L, T (B::*getter)() const, const char template void push_class_property_getter(lua_State* L, T (B::*getter)() const noexcept, const char* debugname) { + static_assert(std::is_same_v || std::is_base_of_v); + using GetType = decltype(getter); new (lua_newuserdata_x(L, sizeof(GetType))) GetType(getter); @@ -9537,6 +9541,8 @@ void push_class_property_getter(lua_State* L, T (B::*getter)() const noexcept, c template void push_class_property_getter(lua_State* L, T (B::*getter)(lua_State*) const, const char* debugname) { + static_assert(std::is_same_v || std::is_base_of_v); + using GetType = decltype(getter); new (lua_newuserdata_x(L, sizeof(GetType))) GetType(getter); @@ -9546,6 +9552,8 @@ void push_class_property_getter(lua_State* L, T (B::*getter)(lua_State*) const, template void push_class_property_getter(lua_State* L, T (B::*getter)(lua_State*) const noexcept, const char* debugname) { + static_assert(std::is_same_v || std::is_base_of_v); + using GetType = decltype(getter); new (lua_newuserdata_x(L, sizeof(GetType))) GetType(getter); @@ -9674,6 +9682,8 @@ void push_class_property_setter(lua_State* L, T U::*value, const char* debugname template void push_class_property_setter(lua_State* L, void (B::*setter)(T), const char* debugname) { + static_assert(std::is_same_v || std::is_base_of_v); + using SetType = decltype(setter); new (lua_newuserdata_x(L, sizeof(SetType))) SetType(setter); @@ -9683,6 +9693,8 @@ void push_class_property_setter(lua_State* L, void (B::*setter)(T), const char* template void push_class_property_setter(lua_State* L, void (B::*setter)(T) noexcept, const char* debugname) { + static_assert(std::is_same_v || std::is_base_of_v); + using SetType = decltype(setter); new (lua_newuserdata_x(L, sizeof(SetType))) SetType(setter); @@ -9692,6 +9704,8 @@ void push_class_property_setter(lua_State* L, void (B::*setter)(T) noexcept, con template void push_class_property_setter(lua_State* L, void (B::*setter)(T, lua_State*), const char* debugname) { + static_assert(std::is_same_v || std::is_base_of_v); + using SetType = decltype(setter); new (lua_newuserdata_x(L, sizeof(SetType))) SetType(setter); @@ -9701,6 +9715,8 @@ void push_class_property_setter(lua_State* L, void (B::*setter)(T, lua_State*), template void push_class_property_setter(lua_State* L, void (B::*setter)(T, lua_State*) noexcept, const char* debugname) { + static_assert(std::is_same_v || std::is_base_of_v); + using SetType = decltype(setter); new (lua_newuserdata_x(L, sizeof(SetType))) SetType(setter); diff --git a/Source/LuaBridge/detail/CFunctions.h b/Source/LuaBridge/detail/CFunctions.h index 51c2b86f..116e9b20 100644 --- a/Source/LuaBridge/detail/CFunctions.h +++ b/Source/LuaBridge/detail/CFunctions.h @@ -2500,6 +2500,8 @@ void push_class_property_getter(lua_State* L, T (U::*value), const char* debugna template void push_class_property_getter(lua_State* L, T (B::*getter)() const, const char* debugname) { + static_assert(std::is_same_v || std::is_base_of_v); + using GetType = decltype(getter); new (lua_newuserdata_x(L, sizeof(GetType))) GetType(getter); @@ -2509,6 +2511,8 @@ void push_class_property_getter(lua_State* L, T (B::*getter)() const, const char template void push_class_property_getter(lua_State* L, T (B::*getter)() const noexcept, const char* debugname) { + static_assert(std::is_same_v || std::is_base_of_v); + using GetType = decltype(getter); new (lua_newuserdata_x(L, sizeof(GetType))) GetType(getter); @@ -2518,6 +2522,8 @@ void push_class_property_getter(lua_State* L, T (B::*getter)() const noexcept, c template void push_class_property_getter(lua_State* L, T (B::*getter)(lua_State*) const, const char* debugname) { + static_assert(std::is_same_v || std::is_base_of_v); + using GetType = decltype(getter); new (lua_newuserdata_x(L, sizeof(GetType))) GetType(getter); @@ -2527,6 +2533,8 @@ void push_class_property_getter(lua_State* L, T (B::*getter)(lua_State*) const, template void push_class_property_getter(lua_State* L, T (B::*getter)(lua_State*) const noexcept, const char* debugname) { + static_assert(std::is_same_v || std::is_base_of_v); + using GetType = decltype(getter); new (lua_newuserdata_x(L, sizeof(GetType))) GetType(getter); @@ -2663,6 +2671,8 @@ void push_class_property_setter(lua_State* L, T U::*value, const char* debugname template void push_class_property_setter(lua_State* L, void (B::*setter)(T), const char* debugname) { + static_assert(std::is_same_v || std::is_base_of_v); + using SetType = decltype(setter); new (lua_newuserdata_x(L, sizeof(SetType))) SetType(setter); @@ -2672,6 +2682,8 @@ void push_class_property_setter(lua_State* L, void (B::*setter)(T), const char* template void push_class_property_setter(lua_State* L, void (B::*setter)(T) noexcept, const char* debugname) { + static_assert(std::is_same_v || std::is_base_of_v); + using SetType = decltype(setter); new (lua_newuserdata_x(L, sizeof(SetType))) SetType(setter); @@ -2681,6 +2693,8 @@ void push_class_property_setter(lua_State* L, void (B::*setter)(T) noexcept, con template void push_class_property_setter(lua_State* L, void (B::*setter)(T, lua_State*), const char* debugname) { + static_assert(std::is_same_v || std::is_base_of_v); + using SetType = decltype(setter); new (lua_newuserdata_x(L, sizeof(SetType))) SetType(setter); @@ -2690,6 +2704,8 @@ void push_class_property_setter(lua_State* L, void (B::*setter)(T, lua_State*), template void push_class_property_setter(lua_State* L, void (B::*setter)(T, lua_State*) noexcept, const char* debugname) { + static_assert(std::is_same_v || std::is_base_of_v); + using SetType = decltype(setter); new (lua_newuserdata_x(L, sizeof(SetType))) SetType(setter); From 4a8ed98af99af1822060eb729ca3114d6fd96e37 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 18 Jun 2026 22:39:04 +0000 Subject: [PATCH 6/7] Update static_assert to use std::derived_from in push_class_property getter/setter overloads --- Source/LuaBridge/detail/CFunctions.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Source/LuaBridge/detail/CFunctions.h b/Source/LuaBridge/detail/CFunctions.h index 116e9b20..62d8ceff 100644 --- a/Source/LuaBridge/detail/CFunctions.h +++ b/Source/LuaBridge/detail/CFunctions.h @@ -2500,7 +2500,7 @@ void push_class_property_getter(lua_State* L, T (U::*value), const char* debugna template void push_class_property_getter(lua_State* L, T (B::*getter)() const, const char* debugname) { - static_assert(std::is_same_v || std::is_base_of_v); + static_assert(std::is_same_v || std::derived_from); using GetType = decltype(getter); @@ -2511,7 +2511,7 @@ void push_class_property_getter(lua_State* L, T (B::*getter)() const, const char template void push_class_property_getter(lua_State* L, T (B::*getter)() const noexcept, const char* debugname) { - static_assert(std::is_same_v || std::is_base_of_v); + static_assert(std::is_same_v || std::derived_from); using GetType = decltype(getter); @@ -2522,7 +2522,7 @@ void push_class_property_getter(lua_State* L, T (B::*getter)() const noexcept, c template void push_class_property_getter(lua_State* L, T (B::*getter)(lua_State*) const, const char* debugname) { - static_assert(std::is_same_v || std::is_base_of_v); + static_assert(std::is_same_v || std::derived_from); using GetType = decltype(getter); @@ -2533,7 +2533,7 @@ void push_class_property_getter(lua_State* L, T (B::*getter)(lua_State*) const, template void push_class_property_getter(lua_State* L, T (B::*getter)(lua_State*) const noexcept, const char* debugname) { - static_assert(std::is_same_v || std::is_base_of_v); + static_assert(std::is_same_v || std::derived_from); using GetType = decltype(getter); @@ -2671,7 +2671,7 @@ void push_class_property_setter(lua_State* L, T U::*value, const char* debugname template void push_class_property_setter(lua_State* L, void (B::*setter)(T), const char* debugname) { - static_assert(std::is_same_v || std::is_base_of_v); + static_assert(std::is_same_v || std::derived_from); using SetType = decltype(setter); @@ -2682,7 +2682,7 @@ void push_class_property_setter(lua_State* L, void (B::*setter)(T), const char* template void push_class_property_setter(lua_State* L, void (B::*setter)(T) noexcept, const char* debugname) { - static_assert(std::is_same_v || std::is_base_of_v); + static_assert(std::is_same_v || std::derived_from); using SetType = decltype(setter); @@ -2693,7 +2693,7 @@ void push_class_property_setter(lua_State* L, void (B::*setter)(T) noexcept, con template void push_class_property_setter(lua_State* L, void (B::*setter)(T, lua_State*), const char* debugname) { - static_assert(std::is_same_v || std::is_base_of_v); + static_assert(std::is_same_v || std::derived_from); using SetType = decltype(setter); @@ -2704,7 +2704,7 @@ void push_class_property_setter(lua_State* L, void (B::*setter)(T, lua_State*), template void push_class_property_setter(lua_State* L, void (B::*setter)(T, lua_State*) noexcept, const char* debugname) { - static_assert(std::is_same_v || std::is_base_of_v); + static_assert(std::is_same_v || std::derived_from); using SetType = decltype(setter); From af20c1925e188bbdbc89e241d4f415e8f5df26a5 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Fri, 19 Jun 2026 00:47:25 +0200 Subject: [PATCH 7/7] Update static_assert to use is_base_of instead of derived_from --- Source/LuaBridge/detail/CFunctions.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Source/LuaBridge/detail/CFunctions.h b/Source/LuaBridge/detail/CFunctions.h index 62d8ceff..116e9b20 100644 --- a/Source/LuaBridge/detail/CFunctions.h +++ b/Source/LuaBridge/detail/CFunctions.h @@ -2500,7 +2500,7 @@ void push_class_property_getter(lua_State* L, T (U::*value), const char* debugna template void push_class_property_getter(lua_State* L, T (B::*getter)() const, const char* debugname) { - static_assert(std::is_same_v || std::derived_from); + static_assert(std::is_same_v || std::is_base_of_v); using GetType = decltype(getter); @@ -2511,7 +2511,7 @@ void push_class_property_getter(lua_State* L, T (B::*getter)() const, const char template void push_class_property_getter(lua_State* L, T (B::*getter)() const noexcept, const char* debugname) { - static_assert(std::is_same_v || std::derived_from); + static_assert(std::is_same_v || std::is_base_of_v); using GetType = decltype(getter); @@ -2522,7 +2522,7 @@ void push_class_property_getter(lua_State* L, T (B::*getter)() const noexcept, c template void push_class_property_getter(lua_State* L, T (B::*getter)(lua_State*) const, const char* debugname) { - static_assert(std::is_same_v || std::derived_from); + static_assert(std::is_same_v || std::is_base_of_v); using GetType = decltype(getter); @@ -2533,7 +2533,7 @@ void push_class_property_getter(lua_State* L, T (B::*getter)(lua_State*) const, template void push_class_property_getter(lua_State* L, T (B::*getter)(lua_State*) const noexcept, const char* debugname) { - static_assert(std::is_same_v || std::derived_from); + static_assert(std::is_same_v || std::is_base_of_v); using GetType = decltype(getter); @@ -2671,7 +2671,7 @@ void push_class_property_setter(lua_State* L, T U::*value, const char* debugname template void push_class_property_setter(lua_State* L, void (B::*setter)(T), const char* debugname) { - static_assert(std::is_same_v || std::derived_from); + static_assert(std::is_same_v || std::is_base_of_v); using SetType = decltype(setter); @@ -2682,7 +2682,7 @@ void push_class_property_setter(lua_State* L, void (B::*setter)(T), const char* template void push_class_property_setter(lua_State* L, void (B::*setter)(T) noexcept, const char* debugname) { - static_assert(std::is_same_v || std::derived_from); + static_assert(std::is_same_v || std::is_base_of_v); using SetType = decltype(setter); @@ -2693,7 +2693,7 @@ void push_class_property_setter(lua_State* L, void (B::*setter)(T) noexcept, con template void push_class_property_setter(lua_State* L, void (B::*setter)(T, lua_State*), const char* debugname) { - static_assert(std::is_same_v || std::derived_from); + static_assert(std::is_same_v || std::is_base_of_v); using SetType = decltype(setter); @@ -2704,7 +2704,7 @@ void push_class_property_setter(lua_State* L, void (B::*setter)(T, lua_State*), template void push_class_property_setter(lua_State* L, void (B::*setter)(T, lua_State*) noexcept, const char* debugname) { - static_assert(std::is_same_v || std::derived_from); + static_assert(std::is_same_v || std::is_base_of_v); using SetType = decltype(setter);