# HG changeset patch # User David Demelier # Date 1403446749 -7200 # Node ID 5e1001649d073f65d247cbc7921628c894d4eb92 # Parent 927c4f3b8a8877ab17f6de0df6b7177f5f1fa49b Finalize LuaeClass and type cast diff -r 927c4f3b8a88 -r 5e1001649d07 C++/Luae.h --- a/C++/Luae.h Mon Jun 09 13:05:33 2014 +0200 +++ b/C++/Luae.h Sun Jun 22 16:19:09 2014 +0200 @@ -39,10 +39,30 @@ * This class adds lot of functions for Lua and C++. */ class Luae { +public: + /* + * These fields are stored in the Lua registry. + * + * FieldRefs - this one is used to reference shared_ptr to reuse + * objects and avoid new creation. + * FieldTop - this field store the current stack size and is used + * with assertBegin and assertEnd. + * FieldClasses - this field store the table of created classes. It is used + * to verify type cast of LuaeClass'es. + */ + static constexpr const char *FieldRefs = "__luae_refs"; + static constexpr const char *FieldTop = "__luae_topcheck"; + static constexpr const char *FieldClasses = "__luae_classes"; + + /* + * These fields are stored in the metatable of the object. + * + * FieldName - this is the field stored in the metatable to get the real + * userdata type. + */ + static constexpr const char *FieldName = "__luea_name"; + private: - static constexpr const char *FieldRefs = "__luae_refs"; - static constexpr const char *FieldTop = "__luae_topcheck"; - /* * Wrapper for dofile and dostring. */ @@ -61,6 +81,7 @@ * be pushed as a userdata, it must match the following requirements: * * - Copy constructible + * - Move constructible (at least) * - TypeInfo overload must have const char *name and inherit TypeUserdata * * The following example code allows the class Object to be pushed @@ -186,6 +207,7 @@ }; private: + /* Tests */ template struct IsCustom { static const bool value = TypeInfo::isCustom; @@ -204,6 +226,7 @@ static const bool value = TypeInfo::isUserdata; }; + /* Helpers */ template using EnableIf = typename std::enable_if::type; @@ -393,6 +416,19 @@ } /** + * Check if the object at index idx has a field in its metatable. + * + * @param L the Lua state + * @param idx the object index + * @param name the name + * @return true if has the field + */ + static inline bool getmetafield(lua_State *L, int idx, const std::string &name) + { + return luaL_getmetafield(L, idx, name.c_str()); + } + + /** * Get the metatable of the value at the given index. Returns false * if does not have a metatable and pushes nothing. * @@ -500,6 +536,16 @@ } /** + * Create a new table on the stack. + * + * @param L the Lua state + */ + static inline void newtable(lua_State *L) + { + lua_newtable(L); + } + + /** * Pops a key from the stack and pushes a key-value pair from the table * at the given index. * @@ -993,6 +1039,9 @@ setfield(L, LUA_REGISTRYINDEX, FieldRefs); } + /* + * Not already existing? Create and return it. + */ rawget(L, -1, value.get()); if (type(L, -1) == LUA_TNIL) { pop(L); @@ -1012,11 +1061,12 @@ * * @param L the Lua state * @param s the string + * @return 1 */ template - static void push(lua_State *L, const char (&s)[N]) + static int push(lua_State *L, const char (&s)[N]) { - push(L, s); + return push(L, s); } /** @@ -1161,8 +1211,6 @@ } /** - * Check for a userdata from the stack but without checking if it's a real - * LuaeClass one. * * @param L the Lua state * @param idx the index @@ -1172,7 +1220,44 @@ template static inline T toType(lua_State *L, int idx, const char *meta) { - return reinterpret_cast(luaL_checkudata(L, idx, meta)); + assertBegin(L); + + bool isclass(false); + + if (getmetafield(L, idx, FieldName)) { + isclass = true; + auto name = lua_tostring(L, -1); + + lua_pop(L, 1); + lua_getfield(L, LUA_REGISTRYINDEX, FieldClasses); + + // No registered class + if (lua_type(L, -1) != LUA_TTABLE) { + lua_pop(L, 1); + luaL_error(L, "%s has not been registered as a class", name); + // NOTREACHED + } + + lua_getfield(L, -1, name); // classes[name] + lua_getfield(L, -1, meta); // t[meta] == true? + auto value = lua_toboolean(L, -1); + lua_pop(L, 3); + + if (!value) + luaL_error(L, "invalid cast from %s to %s", name, meta); + // NOTREACHED + } + + assertEnd(L, 0); + + /* + * If the object is a class, we don't need to check the + * metatable anymore. + */ + if (!isclass) + return reinterpret_cast(luaL_checkudata(L, idx, meta)); + + return reinterpret_cast(lua_touserdata(L, idx)); } }; diff -r 927c4f3b8a88 -r 5e1001649d07 C++/LuaeClass.cpp --- a/C++/LuaeClass.cpp Mon Jun 09 13:05:33 2014 +0200 +++ b/C++/LuaeClass.cpp Sun Jun 22 16:19:09 2014 +0200 @@ -16,89 +16,113 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include "Luae.h" #include "LuaeClass.h" -const char *LuaeClass::FieldName = "__name"; -const char *LuaeClass::FieldParents = "__parents"; +LuaeClass::Def::Def(const std::string &name, + const Methods &methods, + const Methods &metamethods) + : name(name) + , methods(methods) + , metamethods(metamethods) +{ +} + +LuaeClass::Def::Def(const std::string &name, + const Methods &methods, + const Methods &metamethods, + const Def &defparent) + : Def(name, methods, metamethods) +{ + parent = std::make_shared(defparent); +} + +void LuaeClass::registerInheritance(lua_State *L, const Def &def) +{ + Luae::getfield(L, LUA_REGISTRYINDEX, Luae::FieldClasses); + + // The table __luae_classes does not exists? + if (Luae::type(L, -1) == LUA_TNIL) { + Luae::pop(L); + Luae::newtable(L); + Luae::pushvalue(L, -1); + Luae::setfield(L, LUA_REGISTRYINDEX, Luae::FieldClasses); + } + + // The description already exists? + Luae::getfield(L, -1, def.name); + + if (Luae::type(L, -1) == LUA_TNIL) { + Luae::pop(L); + Luae::newtable(L); + + for (const auto *parent = def.parent.get(); parent != nullptr; parent = parent->parent.get()) { + Luae::push(L, true); + Luae::setfield(L, -2, parent->name); + } + + // Also allow cast for itself + Luae::push(L, true); + Luae::setfield(L, -2, def.name); + + // Set this map to the registry + Luae::setfield(L, -2, def.name); + } else + Luae::pop(L); + + Luae::pop(L); +} void LuaeClass::create(lua_State *L, const Def &def) { - luaL_newmetatable(L, def.name.c_str()); - - // Store the name of the class - lua_pushlstring(L, def.name.c_str(), def.name.length()); - lua_setfield(L, -2, FieldName); + Luae::assertBegin(L); + Luae::newmetatable(L, def.name); - // Store the parents names - int i = 0; - - lua_createtable(L, 0, 0); - for (auto d(def.parent); d != nullptr; d = d->parent) { - lua_pushlstring(L, d->name.c_str(), d->name.length()); - lua_rawseti(L, -2, ++i); - } - lua_setfield(L, -2, FieldParents); + // Set the name to the metatable to get the real userdata type + Luae::push(L, def.name); + Luae::setfield(L, -2, Luae::FieldName); // Metamethods if (def.metamethods.size() > 0) { - for (auto m : def.metamethods) { - lua_pushcfunction(L, m.func); - lua_setfield(L, -2, m.name); + for (const auto &m : def.metamethods) { + Luae::pushfunction(L, m.func); + Luae::setfield(L, -2, m.name); } } // Methods - lua_createtable(L, 0, 0); - for (auto m : def.methods) { - lua_pushcfunction(L, m.func); - lua_setfield(L, -2, m.name); + Luae::newtable(L); + for (const auto &m : def.methods) { + Luae::pushfunction(L, m.func); + Luae::setfield(L, -2, m.name); } - // Create the inheritance - if (def.parent != nullptr) { - luaL_newmetatable(L, def.parent->name.c_str()); - lua_setmetatable(L, -2); + /* + * Here is how inheritance works. If we have a parent, we use the + * metatable parent as the metatable of the __index functions table. + * + * So we have the following: + * + * The top square is the metatable name, the square below is the + * functions table set to __index. + * + * +-----------------+ +-----------------+ + * | Base | <-- inherits -- | Child | + * +-----Methods-----+ +-----Methods-----+ + * | apply | | foo | + * +-----------------+ +----Metatable----+ (metatable for methods table) + * <-- points to | | + * +-----------------+ + */ + if (def.parent) { + Luae::newmetatable(L, def.parent->name); + Luae::setmetatable(L, -2); } - lua_setfield(L, -2, "__index"); - - lua_pop(L, 1); -} - -void LuaeClass::testShared(lua_State *L, int index, const char *meta) -{ - luaL_checktype(L, index, LUA_TUSERDATA); - if (!luaL_getmetafield(L, index, FieldName)) - luaL_error(L, "invalid type cast"); - - // Get the class name - const char *name = lua_tostring(L, -1); - lua_pop(L, 1); - - bool found(false); + Luae::setfield(L, -2, "__index"); + Luae::pop(L); - if (std::string(name) == std::string(meta)) { - found = true; - } else { - if (!luaL_getmetafield(L, index, FieldParents)) - luaL_error(L, "invalid type cast"); - - lua_pushnil(L); - while (lua_next(L, -2) != 0) { - if (lua_type(L, -2) != LUA_TSTRING) { - lua_pop(L, 1); - continue; - } + // Now register the class map for type safe cast in __luae_class + registerInheritance(L, def); - auto tn = lua_tostring(L, -1); - if (std::string(tn) == std::string(meta)) - found = true; - - lua_pop(L, 1); - } - - lua_pop(L, 1); - } - - if (!found) - luaL_error(L, "invalid cast from `%s' to `%s'", name, meta); -} + Luae::assertEnd(L, 0); +} \ No newline at end of file diff -r 927c4f3b8a88 -r 5e1001649d07 C++/LuaeClass.h --- a/C++/LuaeClass.h Mon Jun 09 13:05:33 2014 +0200 +++ b/C++/LuaeClass.h Sun Jun 22 16:19:09 2014 +0200 @@ -19,6 +19,10 @@ #ifndef _LUAE_CLASS_H_ #define _LUAE_CLASS_H_ +#include +#include +#include + #include /** @@ -33,36 +37,48 @@ /** * Methods for a class. */ - using Methods = std::vector; - - /** - * Smart pointer for Luae objects. - */ - template - using Ptr = std::shared_ptr; + using Methods = std::vector; /** * @struct Def * @brief Definition of a class */ struct Def { - std::string name; //!< metatable name - Methods methods; //!< methods - Methods metamethods; //!< metamethods - const Def *parent; //!< optional parent class + std::string name; //!< metatable name + Methods methods; //!< methods + Methods metamethods; //!< metamethods + std::shared_ptr parent; //!< optional parent class + + /** + * Constructor for base or final classes. No parent. + * + * @param name the class name + * @param methods the methods + * @param metamethods the metamethods + */ + Def(const std::string &name, + const Methods &methods, + const Methods &metamethods); + + /** + * Child class constructor. Use parent as the parent class + * of this one. + * + * @param name the class name + * @param methods the methods + * @param metamethods the metamethods + * @param parent the parent + */ + Def(const std::string &name, + const Methods &methods, + const Methods &metamethods, + const Def &parent); }; - /** - * The field stored in the object metatable about the object metatable - * name. - */ - static const char *FieldName; +private: + static void registerInheritance(lua_State *L, const Def &def); - /** - * The field that holds all parent classes. It is used to verify casts. - */ - static const char *FieldParents; - +public: /** * Initialize a new object. * @@ -70,85 +86,6 @@ * @param def the definition */ static void create(lua_State *L, const Def &def); - - /** - * Push a shared object to Lua, it also push it to the "__refs" - * table with __mode = "v". That is if we need to push the object - * again we use the same reference so Lua get always the same - * userdata and gain the following benefits: - * - * 1. The user can use the userdata as table key - * 2. A performance gain thanks to less allocations - * - * @param L the Lua state - * @param o the object to push - * @param name the object metatable name - */ - template - static void pushShared(lua_State *L, Ptr o, const std::string &name) - { - lua_getfield(L, LUA_REGISTRYINDEX, LuaeState::FieldRefs); - assert(lua_type(L, -1) == LUA_TTABLE); - - lua_rawgetp(L, -1, o.get()); - - if (lua_type(L, -1) == LUA_TNIL) { - lua_pop(L, 1); - - new (L, name.c_str()) std::shared_ptr(o); - - lua_pushvalue(L, -1); - lua_rawsetp(L, -3, o.get()); - } - - lua_replace(L, -2); - } - - /** - * Check if the object at index is suitable for cast to meta. Calls - * luaL_error if not. - * - * @param L the Lua state - * @param index the value index - * @param meta the object name - */ - static void testShared(lua_State *L, int index, const char *meta); - - /** - * Get an object from Lua that was previously push with pushShared. - * - * @param L the Lua state - * @param index the object index - * @param meta the object metatable name - * @return the object - */ - template - static Ptr getShared(lua_State *L, int index, const char *meta) - { - testShared(L, index, meta); - - return *static_cast *>(lua_touserdata(L, index)); - } - - /** - * Delete the shared pointer at the given index. This function does - * not check if the type is valid for performance reason. And because - * it's usually called in __gc, there is no reason to check. - * - * Also return 0 the __gc method can directly call - * return LuaeClass::deleteShared(...) - * - * @param L the Lua state - * @param index the index - * @return 0 for convenience - */ - template - static int deleteShared(lua_State *L, int index) - { - static_cast *>(lua_touserdata(L, index))->~shared_ptr(); - - return 0; - } }; #endif // !_LUAE_CLASS_H_ diff -r 927c4f3b8a88 -r 5e1001649d07 C++/Tests/Luae/CMakeLists.txt --- a/C++/Tests/Luae/CMakeLists.txt Mon Jun 09 13:05:33 2014 +0200 +++ b/C++/Tests/Luae/CMakeLists.txt Sun Jun 22 16:19:09 2014 +0200 @@ -28,14 +28,32 @@ TestLuae.h ) +set( + LUAE_CLASSES_SOURCES + ${code_SOURCE_DIR}/C++/Luae.cpp + ${code_SOURCE_DIR}/C++/Luae.h + ${code_SOURCE_DIR}/C++/LuaeClass.cpp + ${code_SOURCE_DIR}/C++/LuaeClass.h + ${code_SOURCE_DIR}/C++/LuaeState.cpp + ${code_SOURCE_DIR}/C++/LuaeState.h + TestLuaeClass.cpp + TestLuaeClass.h +) + define_test(luae "${LUAE_SOURCES}") target_include_directories(luae PRIVATE ${LUA52_INCLUDE_DIR}) target_link_libraries(luae ${LUA52_LIBRARIES}) -add_custom_command( - TARGET luae - POST_BUILD - COMMENT "Copying Lua example files" - COMMAND - ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/scripts $ -) \ No newline at end of file +define_test(luae-class "${LUAE_CLASSES_SOURCES}") +target_include_directories(luae-class PRIVATE ${LUA52_INCLUDE_DIR}) +target_link_libraries(luae-class ${LUA52_LIBRARIES}) + +foreach (target luae luae-class) + add_custom_command( + TARGET ${target} + POST_BUILD + COMMENT "Copying Lua example files" + COMMAND + ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/scripts $ + ) +endforeach () \ No newline at end of file diff -r 927c4f3b8a88 -r 5e1001649d07 C++/Tests/Luae/TestLuaeClass.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/Tests/Luae/TestLuaeClass.cpp Sun Jun 22 16:19:09 2014 +0200 @@ -0,0 +1,183 @@ +/* + * TestLuaeClass.cpp -- test the LuaeClass class + * + * Copyright (c) 2013, 2014 David Demelier + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include +#include + +#include "TestLuaeClass.h" + +/* --------------------------------------------------------- + * Fake class for TestLuaeClass::create() + * --------------------------------------------------------- */ + +class Object {}; + +template <> +struct Luae::TypeInfo : public Luae::TypeUserdata { + static constexpr const char *name = "Object"; +}; + +/* --------------------------------------------------------- + * Fake classes for TestLuaeClass::testInheritance() + * --------------------------------------------------------- */ + +class Base {}; +class Child {}; + +template <> +struct Luae::TypeInfo : public Luae::TypeUserdata { + static constexpr const char *name = "Base"; +}; + +template <> +struct Luae::TypeInfo : public Luae::TypeUserdata { + static constexpr const char *name = "Child"; +}; + +void TestLuaeClass::create() +{ + LuaeState L; + Luae::openlibs(L); + + LuaeClass::Def cls("Test", {}, {}); + LuaeClass::create(L, cls); + + Luae::getfield(L, LUA_REGISTRYINDEX, Luae::FieldClasses); + CPPUNIT_ASSERT_MESSAGE("Missing __luae_class", Luae::type(L, -1) == LUA_TTABLE); + + Luae::getfield(L, -1, "Test"); + CPPUNIT_ASSERT_MESSAGE("Missing Test definition", Luae::type(L, -1) == LUA_TTABLE); +} + +void TestLuaeClass::createInheritance() +{ + LuaeState L; + Luae::openlibs(L); + + LuaeClass::Def ac("A", {}, {}); + LuaeClass::Def bc("B", {}, {}, ac); + LuaeClass::create(L, ac); + LuaeClass::create(L, bc); + + Luae::getfield(L, LUA_REGISTRYINDEX, Luae::FieldClasses); + CPPUNIT_ASSERT_MESSAGE("Missing __luae_class", Luae::type(L, -1) == LUA_TTABLE); + + Luae::getfield(L, -1, "A"); + CPPUNIT_ASSERT_MESSAGE("Missing A definition", Luae::type(L, -1) == LUA_TTABLE); + + Luae::getfield(L, -2, "B"); + CPPUNIT_ASSERT_MESSAGE("Missing B definition", Luae::type(L, -1) == LUA_TTABLE); + + // Check for parent from B -> A + Luae::getfield(L, -1, "A"); + CPPUNIT_ASSERT_MESSAGE("Missing [A] = true", Luae::type(L, -1) == LUA_TBOOLEAN); +} + +void TestLuaeClass::testClass() +{ + auto name = [] (lua_State *L) -> int { + return Luae::push(L, "object"); + }; + auto validate = [] (lua_State *L) -> int { + return Luae::push(L, true); + }; + + LuaeClass::Methods methods { + { "name", name }, + { "validate", validate } + }; + + LuaeClass::Def object(Luae::TypeInfo::name, methods, {}); + + LuaeState L; + Luae::openlibs(L); + + try { + LuaeClass::create(L, object); + Luae::dofile(L, "class.lua"); + Luae::getglobal(L, "testObject"); + Luae::push(L, Object()); + Luae::pcall(L, 1, 1); + CPPUNIT_ASSERT_MESSAGE("o:validate() should return true", Luae::check(L, -1)); + } catch (const std::runtime_error &error) { + CPPUNIT_ASSERT_MESSAGE("unexpected exception: " + std::string(error.what()), false); + } +} + +void TestLuaeClass::testInheritance() +{ + auto base = [] (lua_State *L) -> int { + Luae::check(L, 1); + + return Luae::push(L, "base"); + }; + auto child = [] (lua_State *L) -> int { + Luae::check(L, 1); + + return Luae::push(L, "child"); + }; + + // Create base class + LuaeClass::Methods baseMethods = { + { "base", base } + }; + LuaeClass::Def baseDef(Luae::TypeInfo::name, baseMethods, {}); + + // Create child class + LuaeClass::Methods childMethods = { + { "child", child } + }; + LuaeClass::Def childDef(Luae::TypeInfo::name, childMethods, {}, baseDef); + + LuaeState L; + Luae::openlibs(L); + LuaeClass::create(L, baseDef); + LuaeClass::create(L, childDef); + + try { + Luae::dofile(L, "class.lua"); + Luae::getglobal(L, "testInheritance"); + Luae::push(L, Child()); + Luae::pcall(L, 1, 0); + } catch (const std::runtime_error &error) { + CPPUNIT_ASSERT_MESSAGE("unexpected exception: " + std::string(error.what()), false); + } + + // Do bad inheritance + try { + Luae::dofile(L, "class.lua"); + Luae::getglobal(L, "badInheritance"); + Luae::push(L, Base()); + Luae::push(L, Child()); + Luae::pcall(L, 2, 0); + + CPPUNIT_ASSERT_MESSAGE("expected exception", false); + } catch (const std::runtime_error &) { } +} + +int main() +{ + CppUnit::TextTestRunner runnerText; + + runnerText.addTest(TestLuaeClass::suite()); + + return runnerText.run("", false) == 1 ? 0 : 1; +} diff -r 927c4f3b8a88 -r 5e1001649d07 C++/Tests/Luae/TestLuaeClass.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/Tests/Luae/TestLuaeClass.h Sun Jun 22 16:19:09 2014 +0200 @@ -0,0 +1,41 @@ +/* + * TestLuaeClass.h -- test the LuaeClass class + * + * Copyright (c) 2013, 2014 David Demelier + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _TEST_LUAE_CLASS_H_ +#define _TEST_LUAE_CLASS_H_ + +#include +#include + +class TestLuaeClass : public CppUnit::TestFixture { +private: + CPPUNIT_TEST_SUITE(TestLuaeClass); + CPPUNIT_TEST(create); + CPPUNIT_TEST(createInheritance); + CPPUNIT_TEST(testClass); + CPPUNIT_TEST(testInheritance); + CPPUNIT_TEST_SUITE_END(); + +public: + void create(); + void createInheritance(); + void testClass(); + void testInheritance(); +}; + +#endif // !_TEST_LUAE_CLASS_H_ diff -r 927c4f3b8a88 -r 5e1001649d07 C++/Tests/Luae/scripts/class.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/Tests/Luae/scripts/class.lua Sun Jun 22 16:19:09 2014 +0200 @@ -0,0 +1,19 @@ +-- +-- Test class +-- + +function testObject(o) + assert(o:name() == "object", "expected o:name() == object") + + return o:validate() +end + +function testInheritance(o) + assert(o:base() == "base", "expected o:base() == base") + assert(o:child() == "child", "expected o:child() == child") +end + +function badInheritance(base, child) + -- We try to call child method on base + child.child(base) +end \ No newline at end of file