diff --git a/Source/LuaBridge/detail/CFunctions.h b/Source/LuaBridge/detail/CFunctions.h index 298a79c2..035f1ffb 100644 --- a/Source/LuaBridge/detail/CFunctions.h +++ b/Source/LuaBridge/detail/CFunctions.h @@ -185,6 +185,25 @@ inline Options get_class_options(lua_State* L, int index) return options; } +//================================================================================================= +/** + * @brief Push class or const table onto stack. + */ +inline void push_class_or_const_table(lua_State* L, int index) +{ + LUABRIDGE_ASSERT(lua_istable(L, index)); // Stack: mt + + lua_rawgetp(L, index, getClassKey()); // Stack: mt, class table (ct) | nil + if (! lua_istable(L, -1)) // Stack: mt, nil + { + lua_pop(L, 1); // Stack: mt + + lua_rawgetp(L, index, getConstKey()); // Stack: mt, const table (co) | nil + if (! lua_istable(L, -1)) // Stack: mt, nil + return; + } +} + //================================================================================================= /** * @brief __index metamethod for a namespace or class static and non-static members. @@ -217,6 +236,31 @@ inline std::optional try_call_index_fallback(lua_State* L) return std::nullopt; } +template +inline std::optional try_call_index_extensible(lua_State* L, const char* key) +{ + LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: mt + + if constexpr (IsObject) + push_class_or_const_table(L, -1); // Stack: mt, cl | co + else + lua_rawgetp(L, -1, getStaticKey()); // Stack: mt, st + + LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: mt, cl | co | st + rawgetfield(L, -1, key); // Stack: mt, st, ifbresult | nil + + if (! lua_isnoneornil(L, -1)) // Stack: mt, cl | co | st, ifbresult + { + lua_remove(L, -2); // Stack: mt, ifbresult + lua_remove(L, -2); // Stack: ifbresult + return 1; + } + + lua_pop(L, 2); // Stack: mt + return std::nullopt; +} + +template inline int index_metamethod(lua_State* L) { #if LUABRIDGE_SAFE_STACK_CHECKS @@ -238,19 +282,39 @@ inline int index_metamethod(lua_State* L) for (;;) { - // 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 constexpr (IsObject) { + // Repeat the lookup in the index fallback if (auto result = try_call_index_fallback(L)) return *result; } - // Search into the metatable + // Search into self or metatable + if (lua_istable(L, 1)) + { + if constexpr (IsObject) + lua_pushvalue(L, 1); // Stack: mt, self + else + push_class_or_const_table(L, -1); // Stack: mt, cl | co + + if (lua_istable(L, -1)) + { + lua_pushvalue(L, 2); // Stack: mt, self | cl | co, field name + lua_rawget(L, -2); // Stack: mt, self | cl | co, field | nil + lua_remove(L, -2); // Stack: mt, field | nil + if (! lua_isnil(L, -1)) // Stack: mt, field + { + lua_remove(L, -2); // Stack: field + return 1; + } + } + + lua_pop(L, 1); // Stack: mt + } + lua_pushvalue(L, 2); // Stack: mt, field name lua_rawget(L, -2); // Stack: mt, field | nil - - if (lua_iscfunction(L, -1)) // Stack: mt, field + if (! lua_isnil(L, -1)) // Stack: mt, field { lua_remove(L, -2); // Stack: field return 1; @@ -259,6 +323,14 @@ inline int index_metamethod(lua_State* L) LUABRIDGE_ASSERT(lua_isnil(L, -1)); // Stack: mt, nil 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)) + return *result; + } + // Try in the propget key lua_rawgetp(L, -1, getPropgetKey()); // Stack: mt, propget table (pg) LUABRIDGE_ASSERT(lua_istable(L, -1)); @@ -279,14 +351,34 @@ inline int index_metamethod(lua_State* L) lua_pop(L, 1); // Stack: mt // It may mean that the field may be in const table and it's constness violation. - // Don't check that, just return nil - // Repeat the lookup in the index fallback - if (auto result = try_call_index_fallback(L)) - return *result; + // Repeat the lookup in the parent metafield, or fallback to extensible class check. + lua_rawgetp(L, -1, getParentKey()); // Stack: mt, parent mt | nil + if (lua_isnil(L, -1)) // Stack: mt, nil + { + lua_pop(L, 2); // Stack: - + break; + } - // Repeat the lookup in the parent metafield, - // or return nil if the field doesn't exist. + // Remove the metatable and repeat the search in the parent one. + LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: mt, parent mt + lua_remove(L, -2); // Stack: parent mt + } + + lua_getmetatable(L, 1); // Stack: class/const table (mt) + LUABRIDGE_ASSERT(lua_istable(L, -1)); + + for (;;) + { + const Options options = get_class_options(L, -1); // Stack: mt + + if (options.test(extensibleClass | ~allowOverridingMethods)) + { + if (auto result = try_call_index_extensible(L, key)) + return *result; + } + + // Repeat the lookup in the parent metafield, or return nil if the field doesn't exist. lua_rawgetp(L, -1, getParentKey()); // Stack: mt, parent mt | nil if (lua_isnil(L, -1)) // Stack: mt, nil { @@ -309,87 +401,87 @@ inline int index_metamethod(lua_State* L) * Retrieves properties from propset tables. */ -inline std::optional try_call_newindex_fallback(lua_State* L, const char* key) +inline std::optional try_call_newindex_fallback(lua_State* L) { - LUABRIDGE_ASSERT(key != nullptr); LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: mt - lua_rawgetp(L, -1, getNewIndexFallbackKey()); // Stack: mt, nifb | nil + lua_rawgetp(L, -1, getNewIndexFallbackKey()); // Stack: mt, nifb (may be 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, 1); // stack: mt, nifb, arg1 + lua_pushvalue(L, 2); // stack: mt, nifb, arg1, arg2 + lua_pushvalue(L, 3); // stack: mt, nifb, arg1, arg2, arg3 + lua_call(L, 3, 0); // stack: mt + + return 0; +} + +inline std::optional try_call_newindex_extensible(lua_State* L, const char* key) +{ + LUABRIDGE_ASSERT(key != nullptr); + LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: mt - lua_pushvalue(L, -2); // Stack: mt, nifb, mt + lua_pushvalue(L, -1); // Stack: mt, mt for (;;) { - lua_rawgetp(L, -1, getClassKey()); // Stack: mt, nifb, mt, class table (ct) | nil - if (! lua_istable(L, -1)) // Stack: mt, nifb, mt, nil + push_class_or_const_table(L, -1); // Stack: mt, mt, class table (ct) | nil + if (! lua_istable(L, -1)) // Stack: mt, mt, nil { - lua_pop(L, 1); // Stack: mt, nifb, mt - - lua_rawgetp(L, -1, getConstKey()); // Stack: mt, nifb, mt, const table (ct) | nil - if (! lua_istable(L, -1)) // Stack: mt, nifb, mt, nil - { - lua_pop(L, 3); // Stack: mt - return std::nullopt; - } + lua_pop(L, 2); // Stack: mt + return std::nullopt; } - lua_pushvalue(L, 2); // Stack: mt, nifb, mt, ct, field name - lua_rawget(L, -2); // Stack: mt, nifb, mt, ct, field | nil + lua_pushvalue(L, 2); // Stack: mt, mt, ct | co, field name + lua_rawget(L, -2); // Stack: mt, mt, ct | co, field | nil - if (! lua_isnil(L, -1)) // Stack: mt, nifb, mt, ct, field + if (! lua_isnil(L, -1)) // Stack: mt, mt, ct | co, field { + if (! lua_iscfunction(L, -1)) + { + lua_pop(L, 1); + break; + } + // Obtain class options - const Options options = get_class_options(L, -2); // Stack: mt, nifb, mt, ct, field, + const Options options = get_class_options(L, -2); // Stack: mt, mt, ct | co, field if (! options.test(allowOverridingMethods)) luaL_error(L, "immutable member '%s'", key); - 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).c_str()); // Stack: mt, nifb, mt, ct, field, mt2 - - lua_pop(L, 2); // Stack: mt, nifb, mt, ct + rawsetfield(L, -2, make_super_method_name(key).c_str()); // Stack: mt, mt, ct | co break; } - lua_pop(L, 1); // Stack: mt, nifb, mt, ct + lua_pop(L, 1); // Stack: mt, mt, ct | co - 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_rawgetp(L, -2, getParentKey()); // Stack: mt, mt, ct | co, parent mt (pmt) | nil + if (lua_isnil(L, -1)) // Stack: mt, mt, ct | co, nil { - lua_pop(L, 1); // Stack: mt, nifb, mt, ct + lua_pop(L, 1); // Stack: mt, mt, ct | co break; } - 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 + LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: mt, mt, ct | co, pmt + lua_remove(L, -2); // Stack: mt, mt, pmt + lua_remove(L, -2); // Stack: mt, 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, arg1 - } + lua_remove(L, -2); // Stack: mt, ct | co + lua_getmetatable(L, -1); // Stack: mt, ct | co, mt2 + lua_pushvalue(L, 3); // Stack: mt, ct | co, mt2, arg3 + rawsetfield(L, -2, key); // Stack: mt, ct | co, mt2 - 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_pop(L, 2); // Stack: mt return 0; } -inline int newindex_metamethod(lua_State* L, bool pushSelf) +template +inline int newindex_metamethod(lua_State* L) { #if LUABRIDGE_SAFE_STACK_CHECKS luaL_checkstack(L, 3, detail::error_lua_stack_overflow); @@ -404,6 +496,8 @@ inline int newindex_metamethod(lua_State* L, bool pushSelf) for (;;) { + const Options options = get_class_options(L, -1); + // 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 @@ -418,19 +512,31 @@ inline int newindex_metamethod(lua_State* L, bool pushSelf) if (lua_iscfunction(L, -1)) // Stack: mt, setter { lua_remove(L, -2); // Stack: setter - if (pushSelf) + if constexpr (IsObject) lua_pushvalue(L, 1); // Stack: setter, table | userdata lua_pushvalue(L, 3); // Stack: setter, table | userdata, new value - lua_call(L, pushSelf ? 2 : 1, 0); // Stack: - + lua_call(L, IsObject ? 2 : 1, 0); // Stack: - return 0; } LUABRIDGE_ASSERT(lua_isnil(L, -1)); // Stack: mt, nil lua_pop(L, 1); // Stack: mt - // Try in the new index fallback - if (auto result = try_call_newindex_fallback(L, key)) - return *result; + if constexpr (IsObject) + { + // Try in the new index fallback + if (auto result = try_call_newindex_fallback(L)) + return *result; + } + else + { + // Try in the new index extensible + if (options.test(extensibleClass)) + { + if (auto result = try_call_newindex_extensible(L, key)) + return *result; + } + } // Try in the parent lua_rawgetp(L, -1, getParentKey()); // Stack: mt, parent mt | nil @@ -446,26 +552,6 @@ inline int newindex_metamethod(lua_State* L, bool pushSelf) return 0; } -//================================================================================================= -/** - * @brief __newindex metamethod for objects. - */ -inline int newindex_object_metamethod(lua_State* L) -{ - return newindex_metamethod(L, true); -} - -//================================================================================================= -/** - * @brief __newindex metamethod for namespace or class static members. - * - * Retrieves properties from propset tables. - */ -inline int newindex_static_metamethod(lua_State* L) -{ - return newindex_metamethod(L, false); -} - //================================================================================================= /** * @brief lua_CFunction to report an error writing to a read-only value. @@ -483,43 +569,6 @@ inline int read_only_error(lua_State* L) return 0; } -//================================================================================================= -/** - * @brief - */ -inline int index_extended_class(lua_State* L) -{ - LUABRIDGE_ASSERT(lua_istable(L, lua_upvalueindex(1))); - - if (! lua_isstring(L, -1)) - luaL_error(L, "%s", "invalid non string index access in extensible class"); - - const char* key = lua_tostring(L, -1); - LUABRIDGE_ASSERT(key != nullptr); - - lua_pushvalue(L, lua_upvalueindex(1)); - rawgetfield(L, -1, key); - - return 1; -} - -inline int newindex_extended_class(lua_State* L) -{ - LUABRIDGE_ASSERT(lua_istable(L, -3)); - - if (! lua_isstring(L, -2)) - luaL_error(L, "%s", "invalid non string new index access in extensible class"); - - const char* key = lua_tostring(L, -2); - LUABRIDGE_ASSERT(key != nullptr); - - lua_getmetatable(L, -3); - lua_pushvalue(L, -2); - rawsetfield(L, -2, key); - - return 0; -} - //================================================================================================= /** * @brief __tostring metamethod for a class. diff --git a/Source/LuaBridge/detail/ClassInfo.h b/Source/LuaBridge/detail/ClassInfo.h index 0382dd30..cac418bf 100644 --- a/Source/LuaBridge/detail/ClassInfo.h +++ b/Source/LuaBridge/detail/ClassInfo.h @@ -143,7 +143,12 @@ template ().find_first_of('.')> */ [[nodiscard]] inline const void* getIndexFallbackKey() { - return reinterpret_cast(0x81ca); + return reinterpret_cast(0x81ca); +} + +[[nodiscard]] inline const void* getIndexExtensibleKey() +{ + return reinterpret_cast(0x81cb); } //================================================================================================= @@ -152,7 +157,12 @@ template ().find_first_of('.')> */ [[nodiscard]] inline const void* getNewIndexFallbackKey() { - return reinterpret_cast(0x8107); + return reinterpret_cast(0x8107); +} + +[[nodiscard]] inline const void* getNewIndexExtensibleKey() +{ + return reinterpret_cast(0x8108); } //================================================================================================= diff --git a/Source/LuaBridge/detail/FuncTraits.h b/Source/LuaBridge/detail/FuncTraits.h index 8cd154bf..cfc87076 100644 --- a/Source/LuaBridge/detail/FuncTraits.h +++ b/Source/LuaBridge/detail/FuncTraits.h @@ -452,7 +452,7 @@ inline static constexpr bool is_proxy_member_function_v = template inline static constexpr bool is_const_proxy_function_v = is_proxy_member_function_v && - std::is_const_v>>; + std::is_const_v>>>; //================================================================================================= /** diff --git a/Source/LuaBridge/detail/LuaRef.h b/Source/LuaBridge/detail/LuaRef.h index 6e921e19..efa46f34 100644 --- a/Source/LuaBridge/detail/LuaRef.h +++ b/Source/LuaBridge/detail/LuaRef.h @@ -292,6 +292,29 @@ class LuaRefBase return metatable.isTable() && metatable["__call"].isFunction(); } + /** + * @brief Get the name of the class, only if it is a C++ registered class via the library. + * + * @returns An optional string containing the name used to register the class with `beginClass`, nullopt in case it's not a registered class. + */ + std::optional getClassName() + { + if (! isUserdata()) + return std::nullopt; + + const StackRestore stackRestore(m_L); + + impl().push(m_L); + if (! lua_getmetatable(m_L, -1)) + return std::nullopt; + + lua_rawgetp(m_L, -1, detail::getTypeKey()); + if (lua_isstring(m_L, -1)) + return lua_tostring(m_L, -1); + + return std::nullopt; + } + //============================================================================================= /** * @brief Perform a safe explicit conversion to the type T. diff --git a/Source/LuaBridge/detail/Namespace.h b/Source/LuaBridge/detail/Namespace.h index 9dbb3371..f9aa665d 100644 --- a/Source/LuaBridge/detail/Namespace.h +++ b/Source/LuaBridge/detail/Namespace.h @@ -169,24 +169,25 @@ class Namespace : public detail::Registrar std::string type_name = std::string(trueConst ? "const " : "") + name; // Stack: namespace table (ns) + lua_newtable(L); // Stack: ns, const table (co) lua_pushvalue(L, -1); // Stack: ns, co, co lua_setmetatable(L, -2); // co.__metatable = co. Stack: ns, co - pushunsigned(L, options.toUnderlying()); + pushunsigned(L, options.toUnderlying()); // Stack: ns, co, options lua_rawsetp(L, -2, detail::getClassOptionsKey()); // co [classOptionsKey] = options. Stack: ns, co - lua_pushstring(L, type_name.c_str()); + lua_pushstring(L, type_name.c_str()); // Stack: ns, co, name lua_rawsetp(L, -2, detail::getTypeKey()); // co [typeKey] = name. Stack: ns, co - lua_pushcfunction_x(L, &detail::index_metamethod); - rawsetfield(L, -2, "__index"); + lua_pushcfunction_x(L, &detail::index_metamethod); // Stack: ns, co, im + rawsetfield(L, -2, "__index"); // Stack: ns, co - lua_pushcfunction_x(L, &detail::newindex_object_metamethod); - rawsetfield(L, -2, "__newindex"); + lua_pushcfunction_x(L, &detail::newindex_metamethod); // Stack: ns, co, nim + rawsetfield(L, -2, "__newindex"); // Stack: ns, co - lua_newtable(L); - lua_rawsetp(L, -2, detail::getPropgetKey()); + lua_newtable(L); // Stack: ns, co, tb + lua_rawsetp(L, -2, detail::getPropgetKey()); // Stack: ns, co if (! options.test(visibleMetatables)) { @@ -229,17 +230,21 @@ class Namespace : public detail::Registrar LUABRIDGE_ASSERT(name != nullptr); // Stack: namespace table (ns), const table (co), class table (cl) - lua_newtable(L); // Stack: ns, co, cl, visible static table (vst) - lua_newtable(L); // Stack: ns, co, cl, st, static metatable (st) - lua_pushvalue(L, -1); // Stack: ns, co, cl, vst, st, st - lua_setmetatable(L, -3); // st.__metatable = mt. Stack: ns, co, cl, vst, st - lua_insert(L, -2); // Stack: ns, co, cl, st, vst - rawsetfield(L, -5, name); // ns [name] = vst. Stack: ns, co, cl, st - - lua_pushcfunction_x(L, &detail::index_metamethod); + + lua_newtable(L); // Stack: ns, co, cl, static table (st) + lua_newtable(L); // Stack: ns, co, cl, st, static metatable (mt) + lua_pushvalue(L, -1); // Stack: ns, co, cl, st, mt, mt + lua_setmetatable(L, -3); // st.__metatable = mt. Stack: ns, co, cl, st, mt + lua_insert(L, -2); // Stack: ns, co, cl, st, mt, st + rawsetfield(L, -5, name); // ns [name] = st. Stack: ns, co, cl, st, mt + + pushunsigned(L, options.toUnderlying()); // Stack: ns, co, cl, st, mt, options + lua_rawsetp(L, -2, detail::getClassOptionsKey()); // st [classOptionsKey] = options. Stack: ns, co, cl, st, mt + + lua_pushcfunction_x(L, &detail::index_metamethod); rawsetfield(L, -2, "__index"); - lua_pushcfunction_x(L, &detail::newindex_static_metamethod); + lua_pushcfunction_x(L, &detail::newindex_metamethod); rawsetfield(L, -2, "__newindex"); lua_newtable(L); // Stack: ns, co, cl, st, proget table (pg) @@ -307,13 +312,16 @@ class Namespace : public detail::Registrar lua_pop(L, 1); // Stack: ns createConstTable(name, true, options); // Stack: ns, const table (co) + ++m_stackSize; #if !defined(LUABRIDGE_ON_LUAU) lua_pushcfunction_x(L, &detail::gc_metamethod); // Stack: ns, co, function rawsetfield(L, -2, "__gc"); // co ["__gc"] = function. Stack: ns, co #endif - ++m_stackSize; + lua_pushcfunction_x(L, &detail::tostring_metamethod); + rawsetfield(L, -2, "__tostring"); createClassTable(name, options); // Stack: ns, co, class table (cl) + ++m_stackSize; #if !defined(LUABRIDGE_ON_LUAU) lua_pushcfunction_x(L, &detail::gc_metamethod); // Stack: ns, co, cl, function rawsetfield(L, -2, "__gc"); // cl ["__gc"] = function. Stack: ns, co, cl @@ -321,11 +329,15 @@ class Namespace : public detail::Registrar lua_pushcfunction_x(L, &detail::tostring_metamethod); rawsetfield(L, -2, "__tostring"); - ++m_stackSize; createStaticTable(name, options); // Stack: ns, co, cl, st ++m_stackSize; + lua_pushvalue(L, -1); // Stack: ns, co, cl, st, st + lua_rawsetp(L, -2, detail::getStaticKey()); // cl [staticKey] = st. Stack: ns, co, cl, st + lua_pushvalue(L, -1); // Stack: ns, co, cl, st, st + lua_rawsetp(L, -3, detail::getStaticKey()); // co [staticKey] = st. Stack: ns, co, cl, st + // Map T back to its tables. lua_pushvalue(L, -1); // Stack: ns, co, cl, st, st lua_rawsetp(L, LUA_REGISTRYINDEX, detail::getStaticRegistryKey()); // Stack: ns, co, cl, st @@ -333,17 +345,6 @@ class Namespace : public detail::Registrar lua_rawsetp(L, LUA_REGISTRYINDEX, detail::getClassRegistryKey()); // Stack: ns, co, cl, st lua_pushvalue(L, -3); // Stack: ns, co, cl, st, co lua_rawsetp(L, LUA_REGISTRYINDEX, detail::getConstRegistryKey()); // Stack: ns, co, cl, st - - // Setup class extensibility - if (options.test(extensibleClass)) - { - lua_pushcfunction_x(L, &detail::newindex_extended_class); // Stack: ns, co, cl, fn - lua_rawsetp(L, -2, detail::getNewIndexFallbackKey()); // Stack: ns, co, cl - - lua_pushvalue(L, -1); // Stack: ns, co, cl, st, st - lua_pushcclosure_x(L, &detail::index_extended_class, 1); // Stack: ns, co, cl, fn - lua_rawsetp(L, -3, detail::getIndexFallbackKey()); // Stack: ns, co, cl - } } else { @@ -380,24 +381,31 @@ class Namespace : public detail::Registrar LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: namespace table (ns) createConstTable(name, true, options); // Stack: ns, const table (co) + ++m_stackSize; #if !defined(LUABRIDGE_ON_LUAU) lua_pushcfunction_x(L, &detail::gc_metamethod); // Stack: ns, co, function rawsetfield(L, -2, "__gc"); // co ["__gc"] = function. Stack: ns, co #endif - ++m_stackSize; + lua_pushcfunction_x(L, &detail::tostring_metamethod); + rawsetfield(L, -2, "__tostring"); createClassTable(name, options); // Stack: ns, co, class table (cl) + ++m_stackSize; #if !defined(LUABRIDGE_ON_LUAU) 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 ++m_stackSize; + lua_pushvalue(L, -1); // Stack: ns, co, cl, st, st + lua_rawsetp(L, -2, detail::getStaticKey()); // cl [staticKey] = st. Stack: ns, co, cl, st + lua_pushvalue(L, -1); // Stack: ns, co, cl, st, st + lua_rawsetp(L, -3, detail::getStaticKey()); // co [staticKey] = st. Stack: ns, co, cl, st + lua_rawgetp(L, LUA_REGISTRYINDEX, staticKey); // Stack: ns, co, cl, st, parent st (pst) | nil if (lua_isnil(L, -1)) // Stack: ns, co, cl, st, nil { @@ -425,17 +433,6 @@ class Namespace : public detail::Registrar lua_rawsetp(L, LUA_REGISTRYINDEX, detail::getClassRegistryKey()); // Stack: ns, co, cl, st lua_pushvalue(L, -3); // Stack: ns, co, cl, st, co lua_rawsetp(L, LUA_REGISTRYINDEX, detail::getConstRegistryKey()); // Stack: ns, co, cl, st - - // Setup class extensibility - if (options.test(extensibleClass)) - { - lua_pushcfunction_x(L, &detail::newindex_extended_class); // Stack: ns, co, cl, fn - lua_rawsetp(L, -2, detail::getNewIndexFallbackKey()); // Stack: ns, co, cl - - lua_pushvalue(L, -1); // Stack: ns, co, cl, st, st - lua_pushcclosure_x(L, &detail::index_extended_class, 1); // Stack: ns, co, cl, fn - lua_rawsetp(L, -3, detail::getIndexFallbackKey()); // Stack: ns, co, cl - } } //========================================================================================= @@ -1493,27 +1490,25 @@ class Namespace : public detail::Registrar { LUABRIDGE_ASSERT(lua_istable(L, -1)); - { - lua_pushvalue(L, -1); // Stack: ns, ns + lua_pushvalue(L, -1); // Stack: ns, ns - // ns.__metatable = ns - lua_setmetatable(L, -2); // Stack: ns + // ns.__metatable = ns + lua_setmetatable(L, -2); // Stack: ns - // ns.__index = index_metamethod - lua_pushcfunction_x(L, &detail::index_metamethod); - rawsetfield(L, -2, "__index"); // Stack: ns + // ns.__index = index_static_metamethod + lua_pushcfunction_x(L, &detail::index_metamethod); + rawsetfield(L, -2, "__index"); // Stack: ns - lua_newtable(L); // Stack: ns, mt, propget table (pg) - lua_rawsetp(L, -2, detail::getPropgetKey()); // ns [propgetKey] = pg. Stack: ns + lua_newtable(L); // Stack: ns, mt, propget table (pg) + lua_rawsetp(L, -2, detail::getPropgetKey()); // ns [propgetKey] = pg. Stack: ns - lua_newtable(L); // Stack: ns, mt, propset table (ps) - lua_rawsetp(L, -2, detail::getPropsetKey()); // ns [propsetKey] = ps. Stack: ns + lua_newtable(L); // Stack: ns, mt, propset table (ps) + lua_rawsetp(L, -2, detail::getPropsetKey()); // ns [propsetKey] = ps. Stack: ns - if (! options.test(visibleMetatables)) - { - lua_pushboolean(L, 0); - rawsetfield(L, -2, "__metatable"); - } + if (! options.test(visibleMetatables)) + { + lua_pushboolean(L, 0); + rawsetfield(L, -2, "__metatable"); } ++m_stackSize; @@ -1548,12 +1543,12 @@ class Namespace : public detail::Registrar // ns.__metatable = ns lua_setmetatable(L, -2); // Stack: pns, ns - // ns.__index = index_metamethod - lua_pushcfunction_x(L, &detail::index_metamethod); + // ns.__index = index_static_metamethod + lua_pushcfunction_x(L, &detail::index_metamethod); rawsetfield(L, -2, "__index"); // Stack: pns, ns // ns.__newindex = newindex_static_metamethod - lua_pushcfunction_x(L, &detail::newindex_static_metamethod); + lua_pushcfunction_x(L, &detail::newindex_metamethod); rawsetfield(L, -2, "__newindex"); // Stack: pns, ns lua_newtable(L); // Stack: pns, ns, propget table (pg) diff --git a/Source/LuaBridge/detail/Userdata.h b/Source/LuaBridge/detail/Userdata.h index c8e91e11..b3122468 100644 --- a/Source/LuaBridge/detail/Userdata.h +++ b/Source/LuaBridge/detail/Userdata.h @@ -67,10 +67,8 @@ class Userdata const void* registryClassKey, bool canBeConst) { - index = lua_absindex(L, index); - - lua_getmetatable(L, index); // Stack: object metatable (ot) | nil - if (!lua_istable(L, -1)) + const int result = lua_getmetatable(L, index); // Stack: object metatable (ot) | nil + if (result == 0 || !lua_istable(L, -1)) { lua_rawgetp(L, LUA_REGISTRYINDEX, registryClassKey); // Stack: ot | nil, registry metatable (rt) | nil return throwBadArg(L, index); @@ -85,15 +83,10 @@ class Userdata // -> canBeConst = false, isConst = true // -> 'Class' registry table, 'const Class' object table // -> 'expected Class, got const Class' - bool isConst = lua_isnil(L, -1); // Stack: ot | nil, nil, rt - if (isConst && canBeConst) - { - lua_rawgetp(L, LUA_REGISTRYINDEX, registryConstKey); // Stack: ot, nil, rt - } - else - { - lua_rawgetp(L, LUA_REGISTRYINDEX, registryClassKey); // Stack: ot, co, rt - } + const bool isConst = lua_isnil(L, -1); // Stack: ot | nil, nil, rt + lua_rawgetp(L, LUA_REGISTRYINDEX, (isConst && canBeConst) + ? registryConstKey + : registryClassKey); // Stack: ot, co | nil, rt lua_insert(L, -3); // Stack: rt, ot, co | nil lua_pop(L, 1); // Stack: rt, ot @@ -122,13 +115,11 @@ class Userdata // no return } - static bool isInstance(lua_State* L, int index, const void* registryClassKey) + static bool isInstance(lua_State* L, int index, const void* registryKey) { - index = lua_absindex(L, index); - - int result = lua_getmetatable(L, index); // Stack: object metatable (ot) | nothing + const auto result = lua_getmetatable(L, index); // Stack: object metatable (ot) | nil if (result == 0) - return false; // Nothing was pushed on the stack + return false; if (!lua_istable(L, -1)) { @@ -136,7 +127,7 @@ class Userdata return false; } - lua_rawgetp(L, LUA_REGISTRYINDEX, registryClassKey); // Stack: ot, rt + lua_rawgetp(L, LUA_REGISTRYINDEX, registryKey); // Stack: ot, rt lua_insert(L, -2); // Stack: rt, ot for (;;) @@ -152,12 +143,15 @@ class Userdata if (lua_isnil(L, -1)) // Stack: rt, ot, nil { + // Drop the object metatable because it may be some parent metatable lua_pop(L, 3); // Stack: - return false; } lua_remove(L, -2); // Stack: rt, pot } + + // no return } static Userdata* throwBadArg(lua_State* L, int index) @@ -254,7 +248,8 @@ class Userdata template static bool isInstance(lua_State* L, int index) { - return isInstance(L, index, detail::getClassRegistryKey()); + return isInstance(L, index, detail::getClassRegistryKey()) + || isInstance(L, index, detail::getConstRegistryKey()); } protected: diff --git a/Tests/Source/ClassExtensibleTests.cpp b/Tests/Source/ClassExtensibleTests.cpp index f88e90f0..54f33e7c 100644 --- a/Tests/Source/ClassExtensibleTests.cpp +++ b/Tests/Source/ClassExtensibleTests.cpp @@ -127,6 +127,92 @@ TEST_F(ClassExtensibleTests, IndexFallbackMetaMethodFreeFunctor) ASSERT_EQ("123123", result()); } +TEST_F(ClassExtensibleTests, IndexFallbackMetaMethodFreeFunctorOnClass) +{ + auto indexMetaMethod = [&](OverridableX& x, const luabridge::LuaRef& key, lua_State* L) -> luabridge::LuaRef + { + auto it = x.data.find(key); + if (it != x.data.end()) + return it->second; + + return luabridge::LuaRef(L); + }; + + auto newIndexMetaMethod = [&](OverridableX& x, const luabridge::LuaRef& key, const luabridge::LuaRef& value, lua_State* L) -> luabridge::LuaRef + { + x.data.emplace(std::make_pair(key, value)); + return luabridge::LuaRef(L); + }; + + luabridge::getGlobalNamespace(L) + .beginClass("X", luabridge::extensibleClass) + .addIndexMetaMethod(indexMetaMethod) + .addNewIndexMetaMethod(newIndexMetaMethod) + .endClass(); + + OverridableX x, y; + luabridge::setGlobal(L, &x, "x"); + luabridge::setGlobal(L, &y, "y"); + + runLua(R"( + X.xyz = 1 + + function X:setProperty(v) + self.xyz = v + end + + function X:getProperty() + return self.xyz + end + + y.xyz = 100 + x:setProperty(2) + + result = x.xyz + y.xyz + )"); + ASSERT_EQ(102, result()); +} + +TEST_F(ClassExtensibleTests, IndexFallbackMetaMethodFreeFunctorOnClassOverwriteProperty) +{ + auto indexMetaMethod = [&](OverridableX& x, const luabridge::LuaRef& key, lua_State* L) -> luabridge::LuaRef + { + auto it = x.data.find(key); + if (it != x.data.end()) + return it->second; + + return luabridge::LuaRef(L); + }; + + auto newIndexMetaMethod = [&](OverridableX& x, const luabridge::LuaRef& key, const luabridge::LuaRef& value, lua_State* L) -> luabridge::LuaRef + { + x.data.emplace(std::make_pair(key, value)); + return luabridge::LuaRef(L); + }; + + luabridge::getGlobalNamespace(L) + .beginClass("X", luabridge::extensibleClass | luabridge::allowOverridingMethods) + .addIndexMetaMethod(indexMetaMethod) + .addNewIndexMetaMethod(newIndexMetaMethod) + .endClass(); + + OverridableX x, y; + luabridge::setGlobal(L, &x, "x"); + luabridge::setGlobal(L, &y, "y"); + + runLua(R"( + function X:property(v) + self.property = v + end + + x:property(2) + + result = x.property + )"); + ASSERT_TRUE(result().isNumber()); + EXPECT_EQ(2, result()); +} + TEST_F(ClassExtensibleTests, NewIndexFallbackMetaMethodMemberFptr) { luabridge::getGlobalNamespace(L) @@ -326,7 +412,8 @@ TEST_F(ClassExtensibleTests, ExtensibleDerivedClassAndBaseSameMethod) function ExtensibleBase:test() return 1338 end -- This is on purpose function ExtensibleDerived:test() return 42 end - local derived = ExtensibleDerived(); result = derived:test() + local derived = ExtensibleDerived() + result = derived:test() )"); EXPECT_EQ(42, result()); @@ -382,17 +469,23 @@ TEST_F(ClassExtensibleTests, ExtensibleClassExtendExistingMethodAllowingOverride ; runLua(R"( - function ExtensibleBase:baseClass() return 42 + self:super_baseClass() end + function ExtensibleBase:baseClass() + return 42 + self:super_baseClass() + end - local base = ExtensibleBase(); result = base:baseClass() + local base = ExtensibleBase() + result = base:baseClass() )"); EXPECT_EQ(43, result()); runLua(R"( - function ExtensibleBase:baseClassConst() return 42 + self:super_baseClassConst() end + function ExtensibleBase:baseClassConst() + return 42 + self:super_baseClassConst() + end - local base = ExtensibleBase(); result = base:baseClassConst() + local base = ExtensibleBase() + result = base:baseClassConst() )"); EXPECT_EQ(44, result()); @@ -501,32 +594,114 @@ TEST_F(ClassExtensibleTests, ExtensibleClassWithCustomIndexMethod) .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()); + return luabridge::LuaRef(L); }) .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()); + return luabridge::LuaRef(L); }) .endClass() ; runLua(R"( - function ExtensibleBase:test() return 41 + self.xyz + self:baseClass() end + function ExtensibleBase:test() + return 41 + self.xyz + self:baseClass() + end - local base = ExtensibleBase(); base.xyz = 1000; result = base:test() + local base = ExtensibleBase() + base.xyz = 1000 + result = base:test() )"); EXPECT_EQ(1042, result()); + + runLua(R"( + ExtensibleBase.staticProperty = 101; + result = ExtensibleBase.staticProperty + )"); + + EXPECT_EQ(101, result()); + + runLua(R"( + ExtensibleBase.staticProperty = nil; + result = ExtensibleBase.staticProperty + )"); + + EXPECT_TRUE(result().isNil()); +} + +namespace { +class NonExtensible +{ +public: + luabridge::LuaRef getProperty(const luabridge::LuaRef& key, lua_State* L) + { + auto it = properties.find(key); + if (it != properties.end()) + return it->second; + + return luabridge::LuaRef(L); + } + + luabridge::LuaRef setProperty(const luabridge::LuaRef& key, const luabridge::LuaRef& value, lua_State* L) + { + properties.emplace(key, value); + return luabridge::LuaRef(L); + } + +private: + std::unordered_map properties; +}; + +class DerivedExtensible : public NonExtensible +{ +public: + inline DerivedExtensible() {}; +}; +} // namespace + +TEST_F(ClassExtensibleTests, IndexAndNewMetaMethodCalledInBaseClass) +{ + luabridge::getGlobalNamespace(L) + .beginClass("NonExtensible") + .addIndexMetaMethod(&NonExtensible::getProperty) + .addNewIndexMetaMethod(&NonExtensible::setProperty) + .endClass() + .deriveClass("DerivedExtensible", luabridge::extensibleClass) + .addConstructor() + .endClass(); + + runLua(R"( + DerivedExtensible.property = 100 + + function DerivedExtensible:getProperty() + return self.property + end + + function DerivedExtensible:setProperty(value) + self.property = value + end + + local test = DerivedExtensible() + test:setProperty(2) + result = test:getProperty() + DerivedExtensible.property + )"); + + EXPECT_EQ(102, result()); + + runLua(R"( + local test = DerivedExtensible() + test.property = 3 + result = test.property + )"); + + EXPECT_EQ(3, result()); } namespace { diff --git a/Tests/Source/ClassTests.cpp b/Tests/Source/ClassTests.cpp index 9394f2d6..caa27795 100644 --- a/Tests/Source/ClassTests.cpp +++ b/Tests/Source/ClassTests.cpp @@ -113,6 +113,62 @@ T Class::staticData = {}; template const T Class::staticConstData = {}; + +template +class Foo +{ +public: + Foo() = default; + + Foo(const std::string& name) + : name(name) + { + } + + const std::string& getName() const + { + return name; + } + + static Foo& createRef(const std::string& name) + { + static Foo instance(name); + return instance; + } + + static const Foo& createConstRef(const std::string& name) + { + static Foo instance(name); + return instance; + } + +private: + std::string name; +}; + +template +class Bar +{ +public: + Bar() = default; + + void setFoo(const luabridge::LuaRef& ref) + { + if (ref.isInstance>()) + foo = ref.cast>().value(); + else + foo = Foo("undefined"); + } + + const std::string& getFooName() const + { + return foo.getName(); + } + +private: + Foo foo; +}; + } // namespace TEST_F(ClassTests, IsInstance) @@ -154,6 +210,130 @@ TEST_F(ClassTests, IsInstance) ASSERT_TRUE(luabridge::isInstance(L, -1)); } +TEST_F(ClassTests, IsInstanceRef) +{ + struct A {}; + using Fool = Foo; + using Barn = Bar; + + luabridge::getGlobalNamespace(L) + .beginClass("Foo") + .addConstructor() + .addStaticFunction("createRef", &Fool::createRef) + .addFunction("__tostring", +[](const Fool& self) { return self.getName(); }) + .endClass() + .beginClass("Bar") + .addConstructor() + .addFunction("setFoo", &Barn::setFoo) + .addFunction("getFooName", &Barn::getFooName) + .endClass(); + + runLua(R"( + local fooFirst = Foo('first') + + local bar = Bar() + bar:setFoo(fooFirst) + result = bar:getFooName() .. " " .. tostring(fooFirst) + )"); + EXPECT_EQ("first first", result()); + + runLua(R"( + local fooSecond = Foo.createRef('second') + + local bar = Bar() + bar:setFoo(fooSecond) + result = bar:getFooName() .. " " .. tostring(fooSecond) + )"); + EXPECT_EQ("second second", result()); +} + +TEST_F(ClassTests, IsInstanceConstRef) +{ + struct B {}; + using Fool = Foo; + using Barn = Bar; + + luabridge::getGlobalNamespace(L) + .beginClass("Foo") + .addConstructor() + .addStaticFunction("createConstRef", &Fool::createConstRef) + .addFunction("__tostring", +[](const Fool& self) { return self.getName(); }) + .endClass() + .beginClass("Bar") + .addConstructor() + .addFunction("setFoo", &Barn::setFoo) + .addFunction("getFooName", &Barn::getFooName) + .endClass(); + + runLua(R"( + local fooFirst = Foo('first') + + local bar = Bar() + bar:setFoo(fooFirst) + result = bar:getFooName() .. " " .. tostring(fooFirst) + )"); + EXPECT_EQ("first first", result()); + + runLua(R"( + local fooSecond = Foo.createConstRef('second') + + local bar = Bar() + bar:setFoo(fooSecond) + result = bar:getFooName() .. " " .. tostring(fooSecond) + )"); + EXPECT_EQ("second second", result()); +} + +TEST_F(ClassTests, RefExtensible) +{ + struct C {}; + using Fool = Foo; + + luabridge::getGlobalNamespace(L) + .beginClass("Foo", luabridge::extensibleClass) + .addConstructor() + .addStaticFunction("createRef", &Fool::createRef) + .addProperty("getName", &Fool::getName) + .endClass(); + + runLua(R"( + local foo = Foo.createRef('xyz') + result = foo.getName + )"); + EXPECT_EQ("xyz", result()); + + runLua(R"( + local foo = Foo.createRef('xyz') + result = foo.nonExistent + )"); + EXPECT_TRUE(result().isNil()); +} + +TEST_F(ClassTests, ConstRefExtensible) +{ + struct D {}; + using Fool = Foo; + + luabridge::getGlobalNamespace(L) + .beginClass("Foo", luabridge::extensibleClass) + .addConstructor() + .addStaticFunction("createConstRef", &Fool::createConstRef) + .addProperty("getName", &Fool::getName) + .endClass(); + + runLua(R"( + local foo = Foo.createConstRef('xyz') + result = foo.getName + )"); + EXPECT_EQ("xyz", result()); + + runLua(R"( + local foo = Foo.createConstRef('xyz') + result = foo.nonExistent + )"); + EXPECT_TRUE(result().isNil()); +} + TEST_F(ClassTests, PassingUnregisteredClassToLuaThrows) { using Unregistered = Class; @@ -825,6 +1005,23 @@ struct ClassProperties : ClassTests { }; +TEST_F(ClassProperties, FieldPointersNonRegistered) +{ + using Int = Class; + using UnregisteredInt = Class; + + luabridge::getGlobalNamespace(L) + .beginClass("Int") + .addProperty("data", [](const Int&) { return UnregisteredInt(1); }) + .endClass(); + +#if LUABRIDGE_HAS_EXCEPTIONS + ASSERT_THROW(runLua("result = Int().data"), std::exception); +#else + ASSERT_FALSE(runLua("result = Int().data")); +#endif +} + TEST_F(ClassProperties, FieldPointers) { using Int = Class; @@ -1462,6 +1659,23 @@ struct ClassStaticProperties : ClassTests { }; +TEST_F(ClassStaticProperties, FieldPointersNonRegistered) +{ + using Int = Class; + using UnregisteredInt = Class; + + luabridge::getGlobalNamespace(L) + .beginClass("Int") + .addStaticProperty("staticData", [] { return UnregisteredInt(1); }) + .endClass(); + +#if LUABRIDGE_HAS_EXCEPTIONS + ASSERT_THROW(runLua("result = Int.staticData"), std::exception); +#else + ASSERT_FALSE(runLua("result = Int.staticData")); +#endif +} + TEST_F(ClassStaticProperties, FieldPointers) { using Int = Class; diff --git a/Tests/Source/LuaRefTests.cpp b/Tests/Source/LuaRefTests.cpp index b2cbf225..3519448a 100644 --- a/Tests/Source/LuaRefTests.cpp +++ b/Tests/Source/LuaRefTests.cpp @@ -528,21 +528,10 @@ TEST_F(LuaRefTests, Pop) TEST_F(LuaRefTests, IsInstance) { - struct Base - { - }; - - struct Derived : Base - { - }; - - struct Other - { - }; - - struct Unknown : Base - { - }; + struct Base {}; + struct Derived : Base {}; + struct Other {}; + struct Unknown : Base {}; luabridge::getGlobalNamespace(L) .beginClass("Base") @@ -561,6 +550,8 @@ TEST_F(LuaRefTests, IsInstance) EXPECT_FALSE(result().isInstance()); EXPECT_FALSE(result().isInstance()); EXPECT_TRUE(result().isUserdata()); + EXPECT_FALSE(result().isNil()); + EXPECT_EQ("Base", result().getClassName().value_or("")); runLua("result = Derived ()"); EXPECT_TRUE(result().isInstance()); @@ -568,6 +559,8 @@ TEST_F(LuaRefTests, IsInstance) EXPECT_FALSE(result().isInstance()); EXPECT_FALSE(result().isInstance()); EXPECT_TRUE(result().isUserdata()); + EXPECT_FALSE(result().isNil()); + EXPECT_EQ("Derived", result().getClassName().value_or("")); runLua("result = Other ()"); EXPECT_FALSE(result().isInstance()); @@ -575,6 +568,35 @@ TEST_F(LuaRefTests, IsInstance) EXPECT_TRUE(result().isInstance()); EXPECT_FALSE(result().isInstance()); EXPECT_TRUE(result().isUserdata()); + EXPECT_FALSE(result().isNil()); + EXPECT_EQ("Other", result().getClassName().value_or("")); + + lua_newuserdata(L, sizeof(Unknown)); + EXPECT_TRUE(lua_isuserdata(L, -1)); + luaL_newmetatable(L, "Unknown"); + EXPECT_TRUE(lua_istable(L, -1)); + lua_setmetatable(L, -2); + EXPECT_TRUE(lua_isuserdata(L, -1)); + lua_setglobal(L, "unkown"); + runLua("result = unknown"); + EXPECT_FALSE(result().isInstance()); + EXPECT_FALSE(result().isInstance()); + EXPECT_FALSE(result().isInstance()); + EXPECT_FALSE(result().isInstance()); + //EXPECT_TRUE(result().isUserdata()); // TODO + EXPECT_FALSE(result().isTable()); + //EXPECT_FALSE(result().isNil()); // TODO + EXPECT_FALSE(result().getClassName()); + + runLua("result = {}"); + EXPECT_FALSE(result().isInstance()); + EXPECT_FALSE(result().isInstance()); + EXPECT_FALSE(result().isInstance()); + EXPECT_FALSE(result().isInstance()); + EXPECT_FALSE(result().isUserdata()); + EXPECT_TRUE(result().isTable()); + EXPECT_FALSE(result().isNil()); + EXPECT_FALSE(result().getClassName()); runLua("result = 3.14"); EXPECT_FALSE(result().isInstance()); @@ -582,6 +604,9 @@ TEST_F(LuaRefTests, IsInstance) EXPECT_FALSE(result().isInstance()); EXPECT_FALSE(result().isInstance()); EXPECT_FALSE(result().isUserdata()); + EXPECT_TRUE(result().isNumber()); + EXPECT_FALSE(result().isNil()); + EXPECT_FALSE(result().getClassName()); } TEST_F(LuaRefTests, Print) diff --git a/Tests/Source/TestBase.h b/Tests/Source/TestBase.h index fea2a151..7e3ac821 100644 --- a/Tests/Source/TestBase.h +++ b/Tests/Source/TestBase.h @@ -10,6 +10,7 @@ #include "Lua/LuaLibrary.h" #include "LuaBridge/LuaBridge.h" +#include "LuaBridge/Dump.h" #include