From 819d3b26c4f703024fb6ba7a36ce090e9f2898f8 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Thu, 6 Jul 2023 09:54:34 +0200 Subject: [PATCH 1/7] Overridable metamethods from lua --- Source/LuaBridge/detail/CFunctions.h | 19 ++++++++++++++++--- Source/LuaBridge/detail/Namespace.h | 2 ++ Tests/Source/ClassTests.cpp | 23 +++++++++++++++++++++++ 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/Source/LuaBridge/detail/CFunctions.h b/Source/LuaBridge/detail/CFunctions.h index 7d4d6847..2ea3ca76 100644 --- a/Source/LuaBridge/detail/CFunctions.h +++ b/Source/LuaBridge/detail/CFunctions.h @@ -337,8 +337,21 @@ inline std::optional try_call_newindex_fallback(lua_State* L, const char* k lua_pushvalue(L, -2); // Stack: mt, nifb, mt, ct, field, mt2, field rawsetfield(L, -2, (std::string("super_") + key).c_str()); // Stack: mt, nifb, mt, ct, field, mt2 - lua_pop(L, 3); // Stack: mt, nifb, mt - break; + if (is_metamethod(key)) + { + lua_pop(L, 2); // Stack: mt, nifb, mt, ct + lua_remove(L, -4); // Stack: nifb, mt, ct + lua_remove(L, -2); // Stack: nifb, ct + lua_pushvalue(L, 2); // Stack: nifb, arg1 (ct), arg2 + lua_pushvalue(L, 3); // Stack: nifb, arg1 (ct), arg2, arg3 + lua_call(L, 3, 0); // Stack: - + return 0; + } + else + { + lua_pop(L, 3); // Stack: mt, nifb, mt, ct + break; + } } lua_pop(L, 2); // Stack: mt, nifb, mt @@ -510,7 +523,7 @@ inline int newindex_extended_class(lua_State* L) template static int tostring_metamethod(lua_State* L) { - const void* ptr = lua_topointer(L, -1); + const void* ptr = lua_topointer(L, 1); lua_getmetatable(L, -1); // Stack: metatable (mt) lua_rawgetp(L, -1, getTypeKey()); // Stack: mt, classname (cn) diff --git a/Source/LuaBridge/detail/Namespace.h b/Source/LuaBridge/detail/Namespace.h index 22f29a8a..0982d6d0 100644 --- a/Source/LuaBridge/detail/Namespace.h +++ b/Source/LuaBridge/detail/Namespace.h @@ -384,6 +384,8 @@ class Namespace : public detail::Registrar lua_pushcfunction_x(L, &detail::gc_metamethod); // Stack: ns, co, cl, function rawsetfield(L, -2, "__gc"); // cl ["__gc"] = function. Stack: ns, co, cl #endif + lua_pushcfunction_x(L, &detail::tostring_metamethod); + rawsetfield(L, -2, "__tostring"); ++m_stackSize; createStaticTable(name, options); // Stack: ns, co, cl, st diff --git a/Tests/Source/ClassTests.cpp b/Tests/Source/ClassTests.cpp index 89dcd01f..3d5924b5 100644 --- a/Tests/Source/ClassTests.cpp +++ b/Tests/Source/ClassTests.cpp @@ -3028,6 +3028,29 @@ TEST_F(ClassTests, ExtensibleDerivedOverrideOneFunctionCallBaseForTheOther) EXPECT_EQ(103, result()); } +TEST_F(ClassTests, ExtensibleClassCustomMetamethods) +{ + auto options = luabridge::allowOverridingMethods | luabridge::extensibleClass; + + luabridge::getGlobalNamespace(L) + .beginClass("ExtensibleBase", options) + .addConstructor() + .addFunction("baseClass", &ExtensibleBase::baseClass) + .endClass() + ; + + runLua(R"( + function ExtensibleBase:__tostring() + return ('ExtensibleBase(%d)'):format(self:baseClass()) + end + + local base = ExtensibleBase(); result = tostring(base) + )"); + + EXPECT_EQ("ExtensibleBase(1)", result()); +} + + TEST_F(ClassTests, ExtensibleClassWithCustomIndexMethod) { luabridge::getGlobalNamespace(L) From 7085e9c6496c68104fd400266a7b160f4636ad1a Mon Sep 17 00:00:00 2001 From: kunitoki Date: Thu, 6 Jul 2023 10:15:35 +0200 Subject: [PATCH 2/7] More tests --- Tests/Source/ClassTests.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Tests/Source/ClassTests.cpp b/Tests/Source/ClassTests.cpp index 651c0d88..8faf8b7a 100644 --- a/Tests/Source/ClassTests.cpp +++ b/Tests/Source/ClassTests.cpp @@ -3050,6 +3050,27 @@ TEST_F(ClassTests, ExtensibleClassCustomMetamethods) EXPECT_EQ("ExtensibleBase(1)", result()); } +TEST_F(ClassTests, ExtensibleClassCustomMetamethodsSuper) +{ + auto options = luabridge::allowOverridingMethods | luabridge::extensibleClass; + + luabridge::getGlobalNamespace(L) + .beginClass("ExtensibleBase", options) + .addConstructor() + .endClass() + ; + + runLua(R"( + function ExtensibleBase:__tostring() + return '123456 - ' .. self:super___tostring() + end + + local base = ExtensibleBase(); result = tostring(base) + )"); + + EXPECT_EQ(0u, result().find("123456")); + EXPECT_NE(std::string::npos, result().find("ExtensibleBase")); +} TEST_F(ClassTests, ExtensibleClassWithCustomIndexMethod) { From 5e0fd92bfbd40562ee0da9f7e6e975d5fceccbc5 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Thu, 6 Jul 2023 22:15:14 +0200 Subject: [PATCH 3/7] Fix non registered metamethods --- Source/LuaBridge/detail/CFunctions.h | 116 ++++++++++++++------------- Tests/Source/ClassTests.cpp | 26 +++++- 2 files changed, 84 insertions(+), 58 deletions(-) diff --git a/Source/LuaBridge/detail/CFunctions.h b/Source/LuaBridge/detail/CFunctions.h index 2ea3ca76..e20e7e4d 100644 --- a/Source/LuaBridge/detail/CFunctions.h +++ b/Source/LuaBridge/detail/CFunctions.h @@ -151,6 +151,35 @@ inline bool is_metamethod(std::string_view method_name) return result != metamethods.end() && *result == method_name; } +/** + * @brief Make super method name. + */ +inline std::string make_super_method_name(const char* name, bool is_metamethod_name) +{ + return is_metamethod_name + ? (std::string("super") + name) + : (std::string("super_") + name); +} + +//================================================================================================= +/** + * @brief Make super method name. + */ +inline Options get_class_options(lua_State* L, int index) +{ + LUABRIDGE_ASSERT(lua_istable(L, index)); // Stack: mt + + Options options = defaultOptions; + + lua_rawgetp(L, index, getClassOptionsKey()); // Stack: mt, ifb (may be nil) + if (lua_isnumber(L, -1)) + options = Options::fromUnderlying(lua_tointeger(L, -1)); + + lua_pop(L, 1); + + return options; +} + //================================================================================================= /** * @brief __index metamethod for a namespace or class static and non-static members. @@ -208,16 +237,8 @@ inline int index_metamethod(lua_State* L) for (;;) { - // Obtain class options - Options options = defaultOptions; - - lua_rawgetp(L, -1, getClassOptionsKey()); // Stack: mt, ifb (may be nil) - if (lua_isnumber(L, -1)) - options = Options::fromUnderlying(lua_tointeger(L, -1)); - - lua_pop(L, 1); - // If we allow method overriding, we need to prioritise it + const Options options = get_class_options(L, -1); // Stack: mt if (options.test(allowOverridingMethods)) { if (auto result = try_call_index_fallback(L)) @@ -291,13 +312,15 @@ inline std::optional try_call_newindex_fallback(lua_State* L, const char* k { LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: mt - lua_rawgetp(L, -1, getNewIndexFallbackKey()); // Stack: mt, nifb (may be nil) + lua_rawgetp(L, -1, getNewIndexFallbackKey()); // Stack: mt, nifb | nil if (! lua_iscfunction(L, -1)) { lua_pop(L, 1); // Stack: mt return std::nullopt; } + const bool is_key_metamethod = is_metamethod(key); + lua_pushvalue(L, -2); // Stack: mt, nifb, mt for (;;) @@ -318,62 +341,49 @@ inline std::optional try_call_newindex_fallback(lua_State* L, const char* k lua_pushvalue(L, 2); // Stack: mt, nifb, mt, ct, field name lua_rawget(L, -2); // Stack: mt, nifb, mt, ct, field | nil - if (! lua_isnil(L, -1)) // Stack: mt, nifb, mt, ct, nil + if (! lua_isnil(L, -1)) // Stack: mt, nifb, mt, ct, field { - Options options = defaultOptions; - lua_rawgetp(L, -2, getClassOptionsKey()); // Stack: mt, nifb, mt, ct, field, opt | nil - if (lua_isnumber(L, -1)) - options = Options::fromUnderlying(lua_tointeger(L, -1)); - lua_pop(L, 1); // Stack: mt, nifb, mt, ct, field - + // Obtain class options + const Options options = get_class_options(L, -2); // Stack: mt, nifb, mt, ct, field, if (! options.test(allowOverridingMethods)) - { - lua_pop(L, 5); // Stack: - luaL_error(L, "immutable member '%s'", key); - return 0; - } lua_getmetatable(L, 1); // Stack: mt, nifb, mt, ct, field, mt2 lua_pushvalue(L, -2); // Stack: mt, nifb, mt, ct, field, mt2, field - rawsetfield(L, -2, (std::string("super_") + key).c_str()); // Stack: mt, nifb, mt, ct, field, mt2 + rawsetfield(L, -2, make_super_method_name(key, is_key_metamethod).c_str()); // Stack: mt, nifb, mt, ct, field, mt2 - if (is_metamethod(key)) - { - lua_pop(L, 2); // Stack: mt, nifb, mt, ct - lua_remove(L, -4); // Stack: nifb, mt, ct - lua_remove(L, -2); // Stack: nifb, ct - lua_pushvalue(L, 2); // Stack: nifb, arg1 (ct), arg2 - lua_pushvalue(L, 3); // Stack: nifb, arg1 (ct), arg2, arg3 - lua_call(L, 3, 0); // Stack: - - return 0; - } - else - { - lua_pop(L, 3); // Stack: mt, nifb, mt, ct - break; - } + lua_pop(L, 2); // Stack: mt, nifb, mt, ct + break; } - lua_pop(L, 2); // Stack: mt, nifb, mt + lua_pop(L, 1); // Stack: mt, nifb, mt, ct - lua_rawgetp(L, -1, getParentKey()); // Stack: mt, nifb, mt, parent mt | nil - if (lua_isnil(L, -1)) // Stack: mt, nifb, mt + lua_rawgetp(L, -2, getParentKey()); // Stack: mt, nifb, mt, ct, parent mt (pmt) | nil + if (lua_isnil(L, -1)) // Stack: mt, nifb, mt, ct, nil { - lua_pop(L, 1); // Stack: mt, nifb, mt + lua_pop(L, 1); // Stack: mt, nifb, mt, ct break; } - LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: mt, nifb, mt, parent mt - lua_remove(L, -2); // Stack: mt, nifb, parent mt + LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: mt, nifb, mt, ct, pmt + lua_remove(L, -2); // Stack: mt, nifb, mt, pmt + lua_remove(L, -2); // Stack: mt, nifb, pmt + } + + if (is_key_metamethod) + { + lua_remove(L, -2); // Stack: mt, nifb, ct + } + else + { + lua_pop(L, 2); // Stack: mt, nifb + lua_pushvalue(L, 1); // Stack: mt, nifb, mt, arg1 } - lua_pop(L, 1); // Stack: mt, nifb + lua_pushvalue(L, 2); // Stack: mt, nifb, arg1 | ct, arg2 + lua_pushvalue(L, 3); // Stack: mt, nifb, arg1 | ct, arg2, arg3 + lua_call(L, 3, 0); // Stack: mt - lua_remove(L, -2); // Stack: nifb - lua_pushvalue(L, 1); // Stack: nifb, arg1 - lua_pushvalue(L, 2); // Stack: nifb, arg1, arg2 - lua_pushvalue(L, 3); // Stack: nifb, arg1, arg2, arg3 - lua_call(L, 3, 0); // Stack: - return 0; } @@ -395,11 +405,7 @@ inline int newindex_metamethod(lua_State* L, bool pushSelf) // Try in the property set table lua_rawgetp(L, -1, getPropsetKey()); // Stack: mt, propset table (ps) | nil if (lua_isnil(L, -1)) // Stack: mt, nil - { - lua_pop(L, 2); // Stack: - luaL_error(L, "no member named '%s'", key); - return 0; - } LUABRIDGE_ASSERT(lua_istable(L, -1)); @@ -427,11 +433,7 @@ inline int newindex_metamethod(lua_State* L, bool pushSelf) // Try in the parent lua_rawgetp(L, -1, getParentKey()); // Stack: mt, parent mt | nil if (lua_isnil(L, -1)) // Stack: mt, nil - { - lua_pop(L, 2); // Stack: - luaL_error(L, "no writable member '%s'", key); - return 0; - } LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: mt, parent mt lua_remove(L, -2); // Stack: parent mt diff --git a/Tests/Source/ClassTests.cpp b/Tests/Source/ClassTests.cpp index 8faf8b7a..ecee3d9f 100644 --- a/Tests/Source/ClassTests.cpp +++ b/Tests/Source/ClassTests.cpp @@ -3050,6 +3050,30 @@ TEST_F(ClassTests, ExtensibleClassCustomMetamethods) EXPECT_EQ("ExtensibleBase(1)", result()); } +TEST_F(ClassTests, ExtensibleClassCustomMetamethodEq) +{ + auto options = luabridge::allowOverridingMethods | luabridge::extensibleClass; + + luabridge::getGlobalNamespace(L) + .beginClass("ExtensibleBase", options) + .addConstructor() + .addFunction("baseClass", &ExtensibleBase::baseClass) + .endClass() + ; + + runLua(R"( + function ExtensibleBase:__eq(other) + return self:baseClass() == other:baseClass() + end + + local base1 = ExtensibleBase() + local base2 = ExtensibleBase() + result = base1 == base2 + )"); + + EXPECT_TRUE(result()); +} + TEST_F(ClassTests, ExtensibleClassCustomMetamethodsSuper) { auto options = luabridge::allowOverridingMethods | luabridge::extensibleClass; @@ -3062,7 +3086,7 @@ TEST_F(ClassTests, ExtensibleClassCustomMetamethodsSuper) runLua(R"( function ExtensibleBase:__tostring() - return '123456 - ' .. self:super___tostring() + return '123456 - ' .. self:super__tostring() end local base = ExtensibleBase(); result = tostring(base) From 79e0d9c6749e24f615abce11f9454d828e961158 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Thu, 6 Jul 2023 22:20:14 +0200 Subject: [PATCH 4/7] Fix stack comment --- Source/LuaBridge/detail/CFunctions.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/LuaBridge/detail/CFunctions.h b/Source/LuaBridge/detail/CFunctions.h index e20e7e4d..0179f62d 100644 --- a/Source/LuaBridge/detail/CFunctions.h +++ b/Source/LuaBridge/detail/CFunctions.h @@ -377,7 +377,7 @@ inline std::optional try_call_newindex_fallback(lua_State* L, const char* k else { lua_pop(L, 2); // Stack: mt, nifb - lua_pushvalue(L, 1); // Stack: mt, nifb, mt, arg1 + lua_pushvalue(L, 1); // Stack: mt, nifb, arg1 } lua_pushvalue(L, 2); // Stack: mt, nifb, arg1 | ct, arg2 From ae6fe622654c625b6045dc19a9d773c75976f4ab Mon Sep 17 00:00:00 2001 From: kunitoki Date: Fri, 7 Jul 2023 08:37:54 +0200 Subject: [PATCH 5/7] Split tests in separate file --- Tests/CMakeLists.txt | 1 + Tests/Source/ClassExtensibleTests.cpp | 627 +++++++++++++++++++++++++ Tests/Source/ClassTests.cpp | 636 +------------------------- 3 files changed, 642 insertions(+), 622 deletions(-) create mode 100644 Tests/Source/ClassExtensibleTests.cpp diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index eb045efe..a0e2d9c3 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -14,6 +14,7 @@ add_subdirectory(${LUABRIDGE_RAVI_LOCATION} ravi) set (LUABRIDGE_TEST_SOURCE_FILES Source/AmalgamateTests.cpp Source/ArrayTests.cpp + Source/ClassExtensibleTests.cpp Source/ClassTests.cpp Source/CoroutineTests.cpp Source/EnumTests.cpp diff --git a/Tests/Source/ClassExtensibleTests.cpp b/Tests/Source/ClassExtensibleTests.cpp new file mode 100644 index 00000000..f88e90f0 --- /dev/null +++ b/Tests/Source/ClassExtensibleTests.cpp @@ -0,0 +1,627 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2023, Lucio Asnaghi +// SPDX-License-Identifier: MIT + +#include "TestBase.h" + +#include +#include +#include +#include +#include + +struct ClassExtensibleTests : TestBase +{ + template + T variable(const std::string& name) + { + runLua("result = " + name); + return result(); + } +}; + +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(ClassExtensibleTests, 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(ClassExtensibleTests, 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(ClassExtensibleTests, 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(ClassExtensibleTests, 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(ClassExtensibleTests, 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(ClassExtensibleTests, 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()); +} + +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(ClassExtensibleTests, 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(ClassExtensibleTests, 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(ClassExtensibleTests, 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(ClassExtensibleTests, 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(ClassExtensibleTests, 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(ClassExtensibleTests, 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(ClassExtensibleTests, 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(ClassExtensibleTests, 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(ClassExtensibleTests, 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(ClassExtensibleTests, ExtensibleClassCustomMetamethods) +{ + auto options = luabridge::allowOverridingMethods | luabridge::extensibleClass; + + luabridge::getGlobalNamespace(L) + .beginClass("ExtensibleBase", options) + .addConstructor() + .addFunction("baseClass", &ExtensibleBase::baseClass) + .endClass() + ; + + runLua(R"( + function ExtensibleBase:__tostring() + return ('ExtensibleBase(%d)'):format(self:baseClass()) + end + + local base = ExtensibleBase(); result = tostring(base) + )"); + + EXPECT_EQ("ExtensibleBase(1)", result()); +} + +TEST_F(ClassExtensibleTests, ExtensibleClassCustomMetamethodsSuper) +{ + auto options = luabridge::allowOverridingMethods | luabridge::extensibleClass; + + luabridge::getGlobalNamespace(L) + .beginClass("ExtensibleBase", options) + .addConstructor() + .endClass() + ; + + runLua(R"( + function ExtensibleBase:__tostring() + return '123456 - ' .. self:super__tostring() + end + + local base = ExtensibleBase(); result = tostring(base) + )"); + + EXPECT_EQ(0u, result().find("123456")); + EXPECT_NE(std::string::npos, result().find("ExtensibleBase")); +} + +TEST_F(ClassExtensibleTests, ExtensibleClassCustomMetamethodEq) +{ + auto options = luabridge::allowOverridingMethods | luabridge::extensibleClass; + + luabridge::getGlobalNamespace(L) + .beginClass("ExtensibleBase", options) + .addConstructor() + .addFunction("baseClass", &ExtensibleBase::baseClass) + .endClass() + ; + + runLua(R"( + function ExtensibleBase:__eq(other) + return self:baseClass() == other:baseClass() + end + + local base1 = ExtensibleBase() + local base2 = ExtensibleBase() + result = base1 == base2 + )"); + + EXPECT_TRUE(result()); +} + +TEST_F(ClassExtensibleTests, 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 { +class ExampleStringifiableClass +{ +public: + ExampleStringifiableClass() + : a(0), b(0), c(0) + { + } + + std::string tostring() const + { + return "whatever"; + } + + int a, b, c; +}; +} // namespace + +TEST_F(ClassExtensibleTests, MetatableSecurityNotHidden) +{ + { + luabridge::getGlobalNamespace(L) + .beginNamespace("test", luabridge::visibleMetatables) + .endNamespace(); + + runLua("local t = test; result = getmetatable(t)"); + + const auto res = result(); + ASSERT_TRUE(res.isTable()); + } + + { + luabridge::getGlobalNamespace(L) + .beginClass("ExampleStringifiableClass", luabridge::visibleMetatables) + .addConstructor() + .addFunction("__tostring", &ExampleStringifiableClass::tostring) + .endClass(); + + runLua("local t = ExampleStringifiableClass(); result = getmetatable(t)"); + + const auto res = result(); + ASSERT_TRUE(res.isTable()); + } +} + +TEST_F(ClassExtensibleTests, MetatableSecurity) +{ + { + luabridge::getGlobalNamespace(L) + .beginNamespace("test") + .endNamespace(); + + runLua("local t = test; result = getmetatable(t)"); + + const auto res = result(); + ASSERT_TRUE(res.isBool()); + EXPECT_FALSE(res.unsafe_cast()); + } + + { + luabridge::getGlobalNamespace(L) + .beginClass("ExampleStringifiableClass") + .addConstructor() + .addFunction("__tostring", &ExampleStringifiableClass::tostring) + .endClass(); + + runLua("local t = ExampleStringifiableClass(); result = getmetatable(t)"); + + const auto res = result(); + ASSERT_TRUE(res.isBool()); + EXPECT_FALSE(res.unsafe_cast()); + } +} + +#if ! LUABRIDGEDEMO_LUAU && LUABRIDGEDEMO_LUA_VERSION >= 502 +TEST_F(ClassExtensibleTests, MetatablePrinting) +{ + luabridge::getGlobalNamespace(L) + .beginClass("ExampleStringifiableClass", luabridge::visibleMetatables) + .addConstructor() + .endClass(); + + runLua(R"( + local text = '' + local mt = getmetatable(ExampleStringifiableClass) + for k, v in pairs(mt) do + text = text .. ('%s - %s'):format(k, v) + end + result = text + )"); + + const auto res = result(); + ASSERT_TRUE(res.isString()); + EXPECT_FALSE(res.unsafe_cast().empty()); +} +#endif diff --git a/Tests/Source/ClassTests.cpp b/Tests/Source/ClassTests.cpp index ecee3d9f..06ceae8d 100644 --- a/Tests/Source/ClassTests.cpp +++ b/Tests/Source/ClassTests.cpp @@ -165,7 +165,7 @@ TEST_F(ClassTests, PassingUnregisteredClassToLuaThrows) Unregistered value(1); const Unregistered constValue(2); - + #if LUABRIDGE_HAS_EXCEPTIONS ASSERT_THROW(process_fn(value), std::exception); ASSERT_THROW(process_fn(constValue), std::exception); @@ -511,17 +511,17 @@ T proxyConstFunctionNoexcept(const Class* object, T value) noexcept int proxyCFunctionState(lua_State* L) { using Int = Class; - + auto ref = luabridge::LuaRef::fromStack(L, 1); if (!ref.isUserdata() || !ref.isInstance()) { return 0; } - + auto arg = luabridge::LuaRef::fromStack(L, 2); if (!arg.isNumber()) { return 0; } - + [[maybe_unused]] auto result = luabridge::push(L, arg.unsafe_cast() + 1000); return 1; @@ -2240,13 +2240,13 @@ TEST_F(ClassMetaMethods, SimulateArray) { if (index < 0) luaL_error(L, "Invalid index access in table %d", index); - + if (! ref.isString()) luaL_error(L, "Invalid value provided to set table at index %d", index); if (index >= static_cast(data.size())) data.resize(index + 1); - + data[index] = ref.unsafe_cast(); }) .endTable(); @@ -2317,7 +2317,7 @@ TEST_F(ClassTests, ConstructorWithReferences) struct OuterClass { OuterClass(const InnerClass& x) : y(x) {} - + private: [[maybe_unused]] InnerClass y; }; @@ -2385,7 +2385,7 @@ TEST_F(ClassTests, ConstructorTakesMoreThanEightArgs) a9_ = a9; a10_ = a10; } - + int a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_, a10_; }; @@ -2414,7 +2414,7 @@ TEST_F(ClassTests, MethodTakesMoreThanEightArgs) struct WideClass { WideClass() = default; - + void testLotsOfArgs(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9, int a10) { a1_ = a1; @@ -2428,7 +2428,7 @@ TEST_F(ClassTests, MethodTakesMoreThanEightArgs) a9_ = a9; a10_ = a10; } - + int a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_, a10_; }; @@ -2509,7 +2509,7 @@ class BaseExampleClass public: BaseExampleClass() = default; virtual ~BaseExampleClass() = default; - + virtual void virtualFunction(int arg) = 0; virtual int virtualCFunction(lua_State*) = 0; virtual void virtualFunctionConst(int arg) const = 0; @@ -2534,7 +2534,7 @@ class DerivedExampleClass : public BaseExampleClass { public: DerivedExampleClass() = default; - + void virtualFunction(int arg) override { virtualFunction_ = arg; @@ -2600,7 +2600,7 @@ TEST_F(ClassTests, NilCanBeConvertedToNullptrButNotToReference) struct X {}; bool result = false, resultConst = false, called = false; - + luabridge::getGlobalNamespace(L) .addFunction("TakeNullptr", [&result](X* iAmNullptr) { result = (iAmNullptr == nullptr); }) .addFunction("TakeConstNullptr", [&resultConst](const X* iAmNullptr) { resultConst = (iAmNullptr == nullptr); }) @@ -2610,7 +2610,7 @@ TEST_F(ClassTests, NilCanBeConvertedToNullptrButNotToReference) runLua("TakeNullptr(nil)"); EXPECT_TRUE(result); - + runLua("TakeConstNullptr(nil)"); EXPECT_TRUE(resultConst); @@ -2622,516 +2622,6 @@ 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()); -} - -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, ExtensibleClassCustomMetamethods) -{ - auto options = luabridge::allowOverridingMethods | luabridge::extensibleClass; - - luabridge::getGlobalNamespace(L) - .beginClass("ExtensibleBase", options) - .addConstructor() - .addFunction("baseClass", &ExtensibleBase::baseClass) - .endClass() - ; - - runLua(R"( - function ExtensibleBase:__tostring() - return ('ExtensibleBase(%d)'):format(self:baseClass()) - end - - local base = ExtensibleBase(); result = tostring(base) - )"); - - EXPECT_EQ("ExtensibleBase(1)", result()); -} - -TEST_F(ClassTests, ExtensibleClassCustomMetamethodEq) -{ - auto options = luabridge::allowOverridingMethods | luabridge::extensibleClass; - - luabridge::getGlobalNamespace(L) - .beginClass("ExtensibleBase", options) - .addConstructor() - .addFunction("baseClass", &ExtensibleBase::baseClass) - .endClass() - ; - - runLua(R"( - function ExtensibleBase:__eq(other) - return self:baseClass() == other:baseClass() - end - - local base1 = ExtensibleBase() - local base2 = ExtensibleBase() - result = base1 == base2 - )"); - - EXPECT_TRUE(result()); -} - -TEST_F(ClassTests, ExtensibleClassCustomMetamethodsSuper) -{ - auto options = luabridge::allowOverridingMethods | luabridge::extensibleClass; - - luabridge::getGlobalNamespace(L) - .beginClass("ExtensibleBase", options) - .addConstructor() - .endClass() - ; - - runLua(R"( - function ExtensibleBase:__tostring() - return '123456 - ' .. self:super__tostring() - end - - local base = ExtensibleBase(); result = tostring(base) - )"); - - EXPECT_EQ(0u, result().find("123456")); - EXPECT_NE(std::string::npos, result().find("ExtensibleBase")); -} - -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 @@ -3185,103 +2675,6 @@ TEST_F(ClassTests, OveralignedClasses) EXPECT_TRUE(result()); } -namespace { -class ExampleStringifiableClass -{ -public: - ExampleStringifiableClass() - : a(0), b(0), c(0) - { - } - - std::string tostring() const - { - return "whatever"; - } - - int a, b, c; -}; -} // namespace - -TEST_F(ClassTests, MetatableSecurityNotHidden) -{ - { - luabridge::getGlobalNamespace(L) - .beginNamespace("test", luabridge::visibleMetatables) - .endNamespace(); - - runLua("local t = test; result = getmetatable(t)"); - - const auto res = result(); - ASSERT_TRUE(res.isTable()); - } - - { - luabridge::getGlobalNamespace(L) - .beginClass("ExampleStringifiableClass", luabridge::visibleMetatables) - .addConstructor() - .addFunction("__tostring", &ExampleStringifiableClass::tostring) - .endClass(); - - runLua("local t = ExampleStringifiableClass(); result = getmetatable(t)"); - - const auto res = result(); - ASSERT_TRUE(res.isTable()); - } -} - -TEST_F(ClassTests, MetatableSecurity) -{ - { - luabridge::getGlobalNamespace(L) - .beginNamespace("test") - .endNamespace(); - - runLua("local t = test; result = getmetatable(t)"); - - const auto res = result(); - ASSERT_TRUE(res.isBool()); - EXPECT_FALSE(res.unsafe_cast()); - } - - { - luabridge::getGlobalNamespace(L) - .beginClass("ExampleStringifiableClass") - .addConstructor() - .addFunction("__tostring", &ExampleStringifiableClass::tostring) - .endClass(); - - runLua("local t = ExampleStringifiableClass(); result = getmetatable(t)"); - - const auto res = result(); - ASSERT_TRUE(res.isBool()); - EXPECT_FALSE(res.unsafe_cast()); - } -} - -#if ! LUABRIDGEDEMO_LUAU && LUABRIDGEDEMO_LUA_VERSION >= 502 -TEST_F(ClassTests, MetatablePrinting) -{ - luabridge::getGlobalNamespace(L) - .beginClass("ExampleStringifiableClass", luabridge::visibleMetatables) - .addConstructor() - .endClass(); - - runLua(R"( - local text = '' - local mt = getmetatable(ExampleStringifiableClass) - for k, v in pairs(mt) do - text = text .. ('%s - %s'):format(k, v) - end - result = text - )"); - - const auto res = result(); - ASSERT_TRUE(res.isString()); - EXPECT_FALSE(res.unsafe_cast().empty()); -} -#endif - namespace { struct XYZ { int x = 0; }; struct ABC { float y = 0.0f; }; @@ -3382,7 +2775,6 @@ TEST_F(ClassTests, WrongThrowBadArgObjectDescription) ASSERT_FALSE(result); EXPECT_NE(std::string::npos, errorMessage.find("got ABC")); } - #endif } From 5d26e2ca0d645736ae4feaa5b407ec181ae453b7 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Fri, 7 Jul 2023 08:40:56 +0200 Subject: [PATCH 6/7] Fix super method name --- Source/LuaBridge/detail/CFunctions.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Source/LuaBridge/detail/CFunctions.h b/Source/LuaBridge/detail/CFunctions.h index 0179f62d..77d5a0e9 100644 --- a/Source/LuaBridge/detail/CFunctions.h +++ b/Source/LuaBridge/detail/CFunctions.h @@ -154,9 +154,11 @@ inline bool is_metamethod(std::string_view method_name) /** * @brief Make super method name. */ -inline std::string make_super_method_name(const char* name, bool is_metamethod_name) +inline std::string make_super_method_name(const char* name) { - return is_metamethod_name + LUABRIDGE_ASSERT(name != nullptr); + + return (std::string_view(name).find("_") == 0u) ? (std::string("super") + name) : (std::string("super_") + name); } @@ -350,7 +352,7 @@ inline std::optional try_call_newindex_fallback(lua_State* L, const char* k lua_getmetatable(L, 1); // Stack: mt, nifb, mt, ct, field, mt2 lua_pushvalue(L, -2); // Stack: mt, nifb, mt, ct, field, mt2, field - rawsetfield(L, -2, make_super_method_name(key, is_key_metamethod).c_str()); // Stack: mt, nifb, mt, ct, field, mt2 + rawsetfield(L, -2, make_super_method_name(key).c_str()); // Stack: mt, nifb, mt, ct, field, mt2 lua_pop(L, 2); // Stack: mt, nifb, mt, ct break; From 6dfaa32eb7118bd099f2d01a81c520a9ae68255b Mon Sep 17 00:00:00 2001 From: kunitoki Date: Fri, 7 Jul 2023 09:29:11 +0200 Subject: [PATCH 7/7] More cosmetics --- Source/LuaBridge/detail/CFunctions.h | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/Source/LuaBridge/detail/CFunctions.h b/Source/LuaBridge/detail/CFunctions.h index 77d5a0e9..298a79c2 100644 --- a/Source/LuaBridge/detail/CFunctions.h +++ b/Source/LuaBridge/detail/CFunctions.h @@ -147,6 +147,9 @@ inline bool is_metamethod(std::string_view method_name) "__unm" ); + if (method_name.size() <= 2 || method_name[0] != '_' || method_name[1] != '_') + return false; + auto result = std::lower_bound(metamethods.begin(), metamethods.end(), method_name); return result != metamethods.end() && *result == method_name; } @@ -227,14 +230,10 @@ inline int index_metamethod(lua_State* L) // Protect internal meta methods const char* key = lua_tostring(L, 2); - if (key != nullptr) + if (key != nullptr && is_metamethod(key)) { - const auto name = std::string_view(key); - if (name.size() > 2 && name[0] == '_' && name[1] == '_' && is_metamethod(name)) - { - lua_pushnil(L); - return 1; - } + lua_pushnil(L); + return 1; } for (;;) @@ -312,6 +311,7 @@ inline int index_metamethod(lua_State* L) inline std::optional try_call_newindex_fallback(lua_State* L, const char* key) { + LUABRIDGE_ASSERT(key != nullptr); LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: mt lua_rawgetp(L, -1, getNewIndexFallbackKey()); // Stack: mt, nifb | nil @@ -1020,8 +1020,7 @@ inline int try_overload_functions(lua_State* L) if (err == LUABRIDGE_LUA_OK) { // calculate number of return values and return - const int nresults = lua_gettop(L) - nargs - 4; // 4: overloads, errors, key, table - return nresults; + return lua_gettop(L) - nargs - 4; // 4: overloads, errors, key, table } else if (err == LUA_ERRRUN) {