diff --git a/Distribution/LuaBridge/LuaBridge.h b/Distribution/LuaBridge/LuaBridge.h index f2e5eafc..d8baa17f 100644 --- a/Distribution/LuaBridge/LuaBridge.h +++ b/Distribution/LuaBridge/LuaBridge.h @@ -598,6 +598,41 @@ void* lua_newuserdata_aligned(lua_State* L, Args&&... args) return pointer; } +template +int lua_deleteuserdata_pointer(lua_State* L) +{ + assert(isfulluserdata(L, 1)); + + T** aligned = align(lua_touserdata(L, 1)); + delete *aligned; + + return 0; +} + +template +void* lua_newuserdata_pointer(lua_State* L, T* ptr) +{ +#if LUABRIDGE_ON_LUAU + void* pointer = lua_newuserdatadtor(L, maximum_space_needed_to_align(), [](void* x) + { + T** aligned = align(x); + delete *aligned; + }); +#else + void* pointer = lua_newuserdata_x(L, maximum_space_needed_to_align()); + + lua_newtable(L); + lua_pushcfunction_x(L, &lua_deleteuserdata_pointer); + rawsetfield(L, -2, "__gc"); + lua_setmetatable(L, -2); +#endif + + T** aligned = align(pointer); + *aligned = ptr; + + return pointer; +} + inline int raise_lua_error(lua_State* L, const char* fmt, ...) { va_list argp; @@ -3122,6 +3157,7 @@ class Userdata lua_remove(L, -2); } + unreachable(); } static bool isInstance(lua_State* L, int index, const void* registryKey) @@ -3159,6 +3195,7 @@ class Userdata lua_remove(L, -2); } + unreachable(); } static Userdata* throwBadArg(lua_State* L, int index) @@ -5062,6 +5099,76 @@ struct Stack } }; +template +struct Stack> +{ + static Result push(lua_State* L, const std::reference_wrapper& reference) + { + lua_newuserdata_aligned>(L, reference.get()); + + luaL_newmetatable(L, typeName()); + lua_pushvalue(L, -2); + lua_pushcclosure_x(L, &get_set_reference_value, "reference_wrapper", 1); + rawsetfield(L, -2, "__call"); + lua_setmetatable(L, -2); + + return {}; + } + + static TypeResult> get(lua_State* L, int index) + { + auto ptr = luaL_testudata(L, index, typeName()); + if (ptr == nullptr) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + auto reference = reinterpret_cast*>(ptr); + if (reference == nullptr) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + return *reference; + } + + static bool isInstance(lua_State* L, int index) + { + return luaL_testudata(L, index, typeName()) != nullptr; + } + +private: + static const char* typeName() + { + static const std::string s{ detail::typeName>() }; + return s.c_str(); + } + + template + static int get_set_reference_value(lua_State* L) + { + LUABRIDGE_ASSERT(lua_isuserdata(L, lua_upvalueindex(1))); + + std::reference_wrapper* ptr = static_cast*>(lua_touserdata(L, lua_upvalueindex(1))); + LUABRIDGE_ASSERT(ptr != nullptr); + + if (lua_gettop(L) > 1) + { + auto result = Stack::get(L, 2); + if (! result) + luaL_error(L, "%s", result.message().c_str()); + + ptr->get() = *result; + + return 0; + } + else + { + auto result = Stack::push(L, ptr->get()); + if (! result) + luaL_error(L, "%s", result.message().c_str()); + + return 1; + } + } +}; + template <> struct Stack { @@ -5125,7 +5232,6 @@ struct Stack }; namespace detail { - template struct StackOpSelector { @@ -5179,7 +5285,6 @@ struct StackOpSelector static bool isInstance(lua_State* L, int index) { return Stack::isInstance(L, index); } }; - } template @@ -6103,10 +6208,21 @@ template auto unwrap_argument_or_error(lua_State* L, std::size_t index, std::size_t start) { auto result = Stack::get(L, static_cast(index + start)); - if (! result) - raise_lua_error(L, "Error decoding argument #%d: %s", static_cast(index + 1), result.error_cstr()); + if (result) + return std::move(*result); + + if constexpr (! std::is_lvalue_reference_v) + { + using U = std::reference_wrapper>; + + auto resultRef = Stack::get(L, static_cast(index)); + if (resultRef) + return (*resultRef).get(); + } + + raise_lua_error(L, "Error decoding argument #%d: %s", static_cast(index + 1), result.message().c_str()); - return std::move(*result); + unreachable(); } template diff --git a/Source/LuaBridge/detail/CFunctions.h b/Source/LuaBridge/detail/CFunctions.h index dcd98e57..e8e99156 100644 --- a/Source/LuaBridge/detail/CFunctions.h +++ b/Source/LuaBridge/detail/CFunctions.h @@ -39,10 +39,22 @@ template auto unwrap_argument_or_error(lua_State* L, std::size_t index, std::size_t start) { auto result = Stack::get(L, static_cast(index + start)); - if (! result) - raise_lua_error(L, "Error decoding argument #%d: %s", static_cast(index + 1), result.error_cstr()); + if (result) + return std::move(*result); + + // TODO - this might be costly, how to deal with it ? + if constexpr (! std::is_lvalue_reference_v) + { + using U = std::reference_wrapper>; - return std::move(*result); + auto resultRef = Stack::get(L, static_cast(index)); + if (resultRef) + return (*resultRef).get(); + } + + raise_lua_error(L, "Error decoding argument #%d: %s", static_cast(index + 1), result.message().c_str()); + + unreachable(); } template @@ -1058,7 +1070,6 @@ struct property_getter return 1; } }; - /** * @brief lua_CFunction to get a class data member. * diff --git a/Source/LuaBridge/detail/LuaHelpers.h b/Source/LuaBridge/detail/LuaHelpers.h index c93f1c25..9ff40b46 100644 --- a/Source/LuaBridge/detail/LuaHelpers.h +++ b/Source/LuaBridge/detail/LuaHelpers.h @@ -563,6 +563,47 @@ void* lua_newuserdata_aligned(lua_State* L, Args&&... args) return pointer; } +/** + * @brief Deallocate lua userdata from pointer. + */ +template +int lua_deleteuserdata_pointer(lua_State* L) +{ + assert(isfulluserdata(L, 1)); + + T** aligned = align(lua_touserdata(L, 1)); + delete *aligned; + + return 0; +} + +/** + * @brief Allocate lua userdata from pointer. + */ +template +void* lua_newuserdata_pointer(lua_State* L, T* ptr) +{ +#if LUABRIDGE_ON_LUAU + void* pointer = lua_newuserdatadtor(L, maximum_space_needed_to_align(), [](void* x) + { + T** aligned = align(x); + delete *aligned; + }); +#else + void* pointer = lua_newuserdata_x(L, maximum_space_needed_to_align()); + + lua_newtable(L); + lua_pushcfunction_x(L, &lua_deleteuserdata_pointer); + rawsetfield(L, -2, "__gc"); + lua_setmetatable(L, -2); +#endif + + T** aligned = align(pointer); + *aligned = ptr; + + return pointer; +} + /** * @brief Safe error able to walk backwards for error reporting correctly. */ diff --git a/Source/LuaBridge/detail/Stack.h b/Source/LuaBridge/detail/Stack.h index 51df9554..019b82bc 100644 --- a/Source/LuaBridge/detail/Stack.h +++ b/Source/LuaBridge/detail/Stack.h @@ -1335,6 +1335,80 @@ struct Stack } }; +//================================================================================================= +/** + * @brief Stack specialization for `std::reference_wrapper`. + */ +template +struct Stack> +{ + static Result push(lua_State* L, const std::reference_wrapper& reference) + { + lua_newuserdata_aligned>(L, reference.get()); + + luaL_newmetatable(L, typeName()); + lua_pushvalue(L, -2); + lua_pushcclosure_x(L, &get_set_reference_value, "reference_wrapper", 1); + rawsetfield(L, -2, "__call"); + lua_setmetatable(L, -2); + + return {}; + } + + static TypeResult> get(lua_State* L, int index) + { + auto ptr = luaL_testudata(L, index, typeName()); + if (ptr == nullptr) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + auto reference = reinterpret_cast*>(ptr); + if (reference == nullptr) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + return *reference; + } + + static bool isInstance(lua_State* L, int index) + { + return luaL_testudata(L, index, typeName()) != nullptr; + } + +private: + static const char* typeName() + { + static const std::string s{ detail::typeName>() }; + return s.c_str(); + } + + template + static int get_set_reference_value(lua_State* L) + { + LUABRIDGE_ASSERT(lua_isuserdata(L, lua_upvalueindex(1))); + + std::reference_wrapper* ptr = static_cast*>(lua_touserdata(L, lua_upvalueindex(1))); + LUABRIDGE_ASSERT(ptr != nullptr); + + if (lua_gettop(L) > 1) + { + auto result = Stack::get(L, 2); + if (! result) + luaL_error(L, "%s", result.message().c_str()); + + ptr->get() = *result; + + return 0; + } + else + { + auto result = Stack::push(L, ptr->get()); + if (! result) + luaL_error(L, "%s", result.message().c_str()); + + return 1; + } + } +}; + //================================================================================================= /** * @brief Specialization for `void*` and `const void*` type. @@ -1404,7 +1478,6 @@ struct Stack //================================================================================================= namespace detail { - template struct StackOpSelector { @@ -1458,7 +1531,6 @@ struct StackOpSelector static bool isInstance(lua_State* L, int index) { return Stack::isInstance(L, index); } }; - } // namespace detail template diff --git a/Source/LuaBridge/detail/Userdata.h b/Source/LuaBridge/detail/Userdata.h index 7f4d4331..fcd1a6e5 100644 --- a/Source/LuaBridge/detail/Userdata.h +++ b/Source/LuaBridge/detail/Userdata.h @@ -195,7 +195,7 @@ class Userdata lua_remove(L, -2); // Stack: rt, pot } - // no return + unreachable(); } static bool isInstance(lua_State* L, int index, const void* registryKey) @@ -234,7 +234,7 @@ class Userdata lua_remove(L, -2); // Stack: rt, pot } - // no return + unreachable(); } public: diff --git a/Tests/Source/ClassTests.cpp b/Tests/Source/ClassTests.cpp index 09e4e554..f508403c 100644 --- a/Tests/Source/ClassTests.cpp +++ b/Tests/Source/ClassTests.cpp @@ -3033,6 +3033,581 @@ TEST_F(ClassTests, NilCanBeConvertedToNullptrButNotToReference) EXPECT_FALSE(called); } +namespace { +struct OverridableX +{ + OverridableX() = default; + + luabridge::LuaRef indexMetaMethod(const luabridge::LuaRef& key, lua_State* L); + luabridge::LuaRef newIndexMetaMethod(const luabridge::LuaRef& key, const luabridge::LuaRef& value, lua_State* L); + + std::unordered_map data; +}; + +luabridge::LuaRef indexMetaMethodFunction(OverridableX& x, const luabridge::LuaRef& key, lua_State* L) +{ + if (key.tostring() == "xyz") + { + if (!luabridge::push(L, "123")) + lua_pushnil(L); + } + else + { + auto it = x.data.find(key); + if (it != x.data.end()) + return it->second; + + lua_pushnil(L); + } + + return luabridge::LuaRef::fromStack(L); +} + +luabridge::LuaRef OverridableX::indexMetaMethod(const luabridge::LuaRef& key, lua_State* L) +{ + return indexMetaMethodFunction(*this, key, L); +} + +luabridge::LuaRef newIndexMetaMethodFunction(OverridableX& x, const luabridge::LuaRef& key, const luabridge::LuaRef& value, lua_State* L) +{ + x.data.emplace(std::make_pair(key, value)); + return value; +} + +luabridge::LuaRef OverridableX::newIndexMetaMethod(const luabridge::LuaRef& key, const luabridge::LuaRef& value, lua_State* L) +{ + return newIndexMetaMethodFunction(*this, key, value, L); +} +} // namespace + +TEST_F(ClassTests, IndexFallbackMetaMethodMemberFptr) +{ + luabridge::getGlobalNamespace(L) + .beginClass("X") + .addIndexMetaMethod(&OverridableX::indexMetaMethod) + .endClass(); + + OverridableX x; + luabridge::setGlobal(L, &x, "x"); + + runLua("result = x.xyz"); + ASSERT_EQ("123", result()); +} + +TEST_F(ClassTests, IndexFallbackMetaMethodFunctionPtr) +{ + luabridge::getGlobalNamespace(L) + .beginClass("X") + .addIndexMetaMethod(&indexMetaMethodFunction) + .endClass(); + + OverridableX x; + luabridge::setGlobal(L, &x, "x"); + + runLua("result = x.xyz"); + ASSERT_EQ("123", result()); +} + +TEST_F(ClassTests, IndexFallbackMetaMethodFreeFunctor) +{ + std::string capture = "123"; + + auto indexMetaMethod = [&capture](OverridableX&, luabridge::LuaRef key, lua_State* L) -> luabridge::LuaRef + { + if (key.tostring() == "xyz") + { + if (!luabridge::push(L, capture + "123")) + lua_pushnil(L); + } + else + { + if (!luabridge::push(L, 456)) + lua_pushnil(L); + } + + return luabridge::LuaRef::fromStack(L); + }; + + luabridge::getGlobalNamespace(L) + .beginClass("X") + .addIndexMetaMethod(indexMetaMethod) + .endClass(); + + OverridableX x; + luabridge::setGlobal(L, &x, "x"); + + runLua("result = x.xyz"); + ASSERT_EQ("123123", result()); +} + +TEST_F(ClassTests, NewIndexFallbackMetaMethodMemberFptr) +{ + luabridge::getGlobalNamespace(L) + .beginClass("X") + .addIndexMetaMethod(&OverridableX::indexMetaMethod) + .addNewIndexMetaMethod(&OverridableX::newIndexMetaMethod) + .endClass(); + + OverridableX x; + luabridge::setGlobal(L, &x, "x"); + + runLua("x.qwertyuiop = 123; result = x.qwertyuiop"); + ASSERT_EQ(123, result()); +} + +TEST_F(ClassTests, NewIndexFallbackMetaMethodFunctionPtr) +{ + luabridge::getGlobalNamespace(L) + .beginClass("X") + .addIndexMetaMethod(&indexMetaMethodFunction) + .addNewIndexMetaMethod(&newIndexMetaMethodFunction) + .endClass(); + + OverridableX x; + luabridge::setGlobal(L, &x, "x"); + + runLua("x.qwertyuiop = 123; result = x.qwertyuiop"); + ASSERT_EQ(123, result()); +} + +TEST_F(ClassTests, NewIndexFallbackMetaMethodFreeFunctor) +{ + int capture = 123; + + auto newIndexMetaMethod = [&capture](OverridableX& x, const luabridge::LuaRef& key, const luabridge::LuaRef& value, lua_State* L) -> luabridge::LuaRef + { + if (!luabridge::push(L, capture + value.unsafe_cast())) + lua_pushnil(L); + + auto v = luabridge::LuaRef::fromStack(L); + x.data.emplace(std::make_pair(key, v)); + return v; + }; + + luabridge::getGlobalNamespace(L) + .beginClass("X") + .addIndexMetaMethod(&indexMetaMethodFunction) + .addNewIndexMetaMethod(newIndexMetaMethod) + .endClass(); + + OverridableX x; + luabridge::setGlobal(L, &x, "x"); + + runLua("x.qwertyuiop = 123; result = x.qwertyuiop"); + ASSERT_EQ(246, result()); +} + +TEST_F(ClassTests, ReferenceWrapperRead) +{ + int x = 13; + std::reference_wrapper ref_wrap_x(x); + + luabridge::getGlobalNamespace(L) + .beginNamespace("test") + .addProperty("ref_wrap_x", &ref_wrap_x) + .addFunction("changeReference", [](std::reference_wrapper r) { r.get() = 100; }) + .endNamespace(); + + runLua(R"( + result = test.ref_wrap_x + test.changeReference(result) + )"); + + EXPECT_TRUE(result().isUserdata()); + EXPECT_EQ(x, result().unsafe_cast>().get()); + EXPECT_EQ(100, x); +} + +TEST_F(ClassTests, ReferenceWrapperWrite) +{ + int x = 13; + std::reference_wrapper ref_wrap_x(x); + + luabridge::getGlobalNamespace(L) + .beginNamespace("test") + .addProperty("ref_wrap_x", &ref_wrap_x) + .endNamespace(); + + runLua(R"( + test.ref_wrap_x(100) + result = test.ref_wrap_x + )"); + + EXPECT_TRUE(result().isUserdata()); + EXPECT_EQ(x, result().unsafe_cast>().get()); + EXPECT_EQ(100, x); +} + +TEST_F(ClassTests, ReferenceWrapperRedirect) +{ + int x = 13; + int y = 100; + std::reference_wrapper ref_wrap_x(x); + std::reference_wrapper ref_wrap_y(y); + + luabridge::getGlobalNamespace(L) + .beginNamespace("test") + .addProperty("ref_wrap_x", &ref_wrap_x, &ref_wrap_x) + .addProperty("ref_wrap_y", &ref_wrap_y) + .endNamespace(); + + runLua(R"( + test.ref_wrap_x = test.ref_wrap_y + result = test.ref_wrap_x + )"); + + EXPECT_TRUE(result().isUserdata()); + EXPECT_EQ(y, result().unsafe_cast>().get()); +} + +TEST_F(ClassTests, ReferenceWrapperDecaysToType) +{ + int x = 13; + std::reference_wrapper ref_wrap_x(x); + + luabridge::getGlobalNamespace(L) + .beginNamespace("test") + .addProperty("ref_wrap_x", &ref_wrap_x) + .addFunction("takeReference", [](int r) { return r * 10; }) + .endNamespace(); + + runLua(R"( + result = test.takeReference(test.ref_wrap_x) + )"); + + EXPECT_EQ(130, result().unsafe_cast()); +} + +TEST_F(ClassTests, ReferenceWrapperFailsOnInvalidType) +{ + int x = 13; + std::reference_wrapper ref_wrap_x(x); + + float y = 1.0f; + std::reference_wrapper ref_wrap_y(y); + + luabridge::getGlobalNamespace(L) + .beginNamespace("test") + .addProperty("ref_wrap_x", &ref_wrap_x) + .addProperty("ref_wrap_y", &ref_wrap_y) + .addFunction("takeReference1", [](float r) { return r * 10; }) + .addFunction("takeReference2", [](int r) { return r * 10; }) + .addFunction("takeReference3", [](std::reference_wrapper r) { return r.get() * 10; }) + .addFunction("takeReference4", [](std::reference_wrapper r) { return r.get() * 10; }) + .endNamespace(); + +#if LUABRIDGE_HAS_EXCEPTIONS + EXPECT_THROW(runLua("result = test.takeReference1(test.ref_wrap_x)"), std::exception); + EXPECT_THROW(runLua("result = test.takeReference2(test.ref_wrap_y)"), std::exception); + EXPECT_THROW(runLua("result = test.takeReference3(test.ref_wrap_x)"), std::exception); + EXPECT_THROW(runLua("result = test.takeReference4(test.ref_wrap_y)"), std::exception); +#else + EXPECT_FALSE(runLua("result = test.takeReference1(test.ref_wrap_x)")); + EXPECT_FALSE(runLua("result = test.takeReference2(test.ref_wrap_y)")); + EXPECT_FALSE(runLua("result = test.takeReference3(test.ref_wrap_x)")); + EXPECT_FALSE(runLua("result = test.takeReference4(test.ref_wrap_y)")); +#endif +} + +TEST_F(ClassTests, ReferenceWrapperAccessFromLua) +{ + int x = 13; + std::reference_wrapper ref_wrap_x(x); + + luabridge::getGlobalNamespace(L) + .beginNamespace("test") + .addProperty("ref_wrap_x", &ref_wrap_x) + .endNamespace(); + + runLua(R"( + function xyz(x) + return x() * 10 + end + + result = xyz(test.ref_wrap_x) + )"); + + EXPECT_EQ(130, result().unsafe_cast()); +} + +namespace { +struct ExtensibleBase +{ + ExtensibleBase() = default; + + int baseClass() { return 1; } + int baseClassConst() const { return 2; } + + std::unordered_map properties; +}; + +struct ExtensibleDerived : ExtensibleBase +{ + ExtensibleDerived() = default; + + int derivedClass() { return 11; } + int derivedClassConst() const { return 22; } +}; + +} // namespace + +TEST_F(ClassTests, ExtensibleClass) +{ + luabridge::getGlobalNamespace(L) + .beginClass("ExtensibleBase", luabridge::extensibleClass) + .addConstructor() + .addFunction("baseClass", &ExtensibleBase::baseClass) + .endClass() + ; + + runLua(R"( + function ExtensibleBase:test() return 41 + self:baseClass() end + + local base = ExtensibleBase(); result = base:test() + )"); + + EXPECT_EQ(42, result()); +} + +TEST_F(ClassTests, ExtensibleBaseClassNotDerived) +{ + luabridge::getGlobalNamespace(L) + .beginClass("ExtensibleBase", luabridge::extensibleClass) + .addConstructor() + .addFunction("baseClass", &ExtensibleBase::baseClass) + .endClass() + .deriveClass("ExtensibleDerived") + .addConstructor() + .endClass() + ; + + runLua(R"( + function ExtensibleBase:test() return 41 + self:baseClass() end + + local derived = ExtensibleDerived(); result = derived:test() + )"); + + EXPECT_EQ(42, result()); +} + +TEST_F(ClassTests, ExtensibleDerivedClassNotBase) +{ + luabridge::getGlobalNamespace(L) + .beginClass("ExtensibleBase") + .addConstructor() + .addFunction("baseClass", &ExtensibleBase::baseClass) + .endClass() + .deriveClass("ExtensibleDerived", luabridge::extensibleClass) + .addConstructor() + .endClass() + ; + + runLua(R"( + function ExtensibleDerived:test() return 41 + self:baseClass() end + + local derived = ExtensibleDerived(); result = derived:test() + )"); + + EXPECT_EQ(42, result()); +} + +TEST_F(ClassTests, ExtensibleDerivedClassAndBase) +{ + luabridge::getGlobalNamespace(L) + .beginClass("ExtensibleBase", luabridge::extensibleClass) + .addConstructor() + .addFunction("baseClass", &ExtensibleBase::baseClass) + .endClass() + .deriveClass("ExtensibleDerived", luabridge::extensibleClass) + .addConstructor() + .addFunction("derivedClass", &ExtensibleDerived::derivedClass) + .endClass() + ; + + runLua(R"( + function ExtensibleBase:test1() return self:baseClass() end + function ExtensibleDerived:test2() return self:derivedClass() end + + local derived = ExtensibleDerived(); result = derived:test1() - derived:test2() + )"); + + EXPECT_EQ(-10, result()); +} + +TEST_F(ClassTests, ExtensibleDerivedClassAndBaseCascading) +{ + luabridge::getGlobalNamespace(L) + .beginClass("ExtensibleBase", luabridge::extensibleClass) + .addConstructor() + .addFunction("baseClass", &ExtensibleBase::baseClass) + .endClass() + .deriveClass("ExtensibleDerived", luabridge::extensibleClass) + .addConstructor() + .addFunction("derivedClass", &ExtensibleDerived::derivedClass) + .endClass() + ; + + runLua(R"( + function ExtensibleBase:testBase() return self:baseClass() end + function ExtensibleDerived:testDerived() return self:testBase() end + + local derived = ExtensibleDerived(); result = derived:testDerived() + )"); + + EXPECT_EQ(1, result()); +} + +TEST_F(ClassTests, ExtensibleDerivedClassAndBaseSameMethod) +{ + luabridge::getGlobalNamespace(L) + .beginClass("ExtensibleBase", luabridge::extensibleClass) + .addConstructor() + .endClass() + .deriveClass("ExtensibleDerived", luabridge::extensibleClass) + .addConstructor() + .endClass() + ; + + runLua(R"( + function ExtensibleBase:test() return 1337 end + function ExtensibleBase:test() return 1338 end -- This is on purpose + function ExtensibleDerived:test() return 42 end + + local derived = ExtensibleDerived(); result = derived:test() + )"); + + EXPECT_EQ(42, result()); +} + +TEST_F(ClassTests, ExtensibleClassExtendExistingMethod) +{ + luabridge::getGlobalNamespace(L) + .beginClass("ExtensibleBase", luabridge::extensibleClass) + .addConstructor() + .addFunction("baseClass", &ExtensibleBase::baseClass) + .addFunction("baseClassConst", &ExtensibleBase::baseClassConst) + .endClass() + ; + +#if LUABRIDGE_HAS_EXCEPTIONS + EXPECT_ANY_THROW(runLua(R"( + function ExtensibleBase:baseClass() return 42 end + + local base = ExtensibleBase(); result = base:baseClass() + )")); +#else + EXPECT_FALSE(runLua(R"( + function ExtensibleBase:baseClass() return 42 end + + local base = ExtensibleBase(); result = base:baseClass() + )")); +#endif + +#if LUABRIDGE_HAS_EXCEPTIONS + EXPECT_ANY_THROW(runLua(R"( + function ExtensibleBase:baseClassConst() return 42 end + + local base = ExtensibleBase(); result = base:baseClassConst() + )")); +#else + EXPECT_FALSE(runLua(R"( + function ExtensibleBase:baseClassConst() return 42 end + + local base = ExtensibleBase(); result = base:baseClassConst() + )")); +#endif +} + +TEST_F(ClassTests, ExtensibleClassExtendExistingMethodAllowingOverride) +{ + luabridge::getGlobalNamespace(L) + .beginClass("ExtensibleBase", luabridge::extensibleClass | luabridge::allowOverridingMethods) + .addConstructor() + .addFunction("baseClass", &ExtensibleBase::baseClass) + .addFunction("baseClassConst", &ExtensibleBase::baseClassConst) + .endClass() + ; + + runLua(R"( + function ExtensibleBase:baseClass() return 42 + self:super_baseClass() end + + local base = ExtensibleBase(); result = base:baseClass() + )"); + + EXPECT_EQ(43, result()); + + runLua(R"( + function ExtensibleBase:baseClassConst() return 42 + self:super_baseClassConst() end + + local base = ExtensibleBase(); result = base:baseClassConst() + )"); + + EXPECT_EQ(44, result()); +} + +TEST_F(ClassTests, ExtensibleDerivedOverrideOneFunctionCallBaseForTheOther) +{ + constexpr auto options = luabridge::extensibleClass | luabridge::allowOverridingMethods; + + luabridge::getGlobalNamespace(L) + .beginClass("ExtensibleBase", options) + .addConstructor() + .addFunction("baseClass", &ExtensibleBase::baseClass) + .addFunction("baseClassConst", &ExtensibleBase::baseClassConst) + .endClass() + .deriveClass("ExtensibleDerived", options) + .addConstructor() + .addFunction("derivedClass", &ExtensibleDerived::derivedClass) + .addFunction("derivedClassConst", &ExtensibleDerived::derivedClassConst) + .endClass() + ; + + runLua(R"( + function ExtensibleDerived:baseClass() return 100 + self:super_baseClass() end + + local derived = ExtensibleDerived() + result = derived:baseClass() + derived:baseClassConst() + )"); + + EXPECT_EQ(103, result()); +} + +TEST_F(ClassTests, ExtensibleClassWithCustomIndexMethod) +{ + luabridge::getGlobalNamespace(L) + .beginClass("ExtensibleBase", luabridge::extensibleClass) + .addConstructor() + .addFunction("baseClass", &ExtensibleBase::baseClass) + .addIndexMetaMethod([](ExtensibleBase& self, const luabridge::LuaRef& key, lua_State* L) + { + auto metatable = luabridge::getGlobal(L, "ExtensibleBase").getMetatable(); + if (auto value = metatable[key]) + return value.cast().value(); + + auto it = self.properties.find(key); + if (it != self.properties.end()) + return it->second; + + luaL_error(L, "%s", "Failed lookup of key !"); + return luabridge::LuaRef(L, luabridge::LuaNil()); + }) + .addNewIndexMetaMethod([](ExtensibleBase& self, const luabridge::LuaRef& key, const luabridge::LuaRef& value, lua_State* L) + { + self.properties.emplace(std::make_pair (key, value)); + return luabridge::LuaRef(L, luabridge::LuaNil()); + }) + .endClass() + ; + + runLua(R"( + function ExtensibleBase:test() return 41 + self.xyz + self:baseClass() end + + local base = ExtensibleBase(); base.xyz = 1000; result = base:test() + )"); + + EXPECT_EQ(1042, result()); +} + namespace { template struct alignas(Alignment) Vec