changeset 230:5e1001649d07

Finalize LuaeClass and type cast
author David Demelier <markand@malikania.fr>
date Sun, 22 Jun 2014 16:19:09 +0200
parents 927c4f3b8a88
children 2f1d820e6e33
files C++/Luae.h C++/LuaeClass.cpp C++/LuaeClass.h C++/Tests/Luae/CMakeLists.txt C++/Tests/Luae/TestLuaeClass.cpp C++/Tests/Luae/TestLuaeClass.h C++/Tests/Luae/scripts/class.lua
diffstat 7 files changed, 490 insertions(+), 183 deletions(-) [+]
line wrap: on
line diff
--- 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 <typename T>
 	struct IsCustom {
 		static const bool value = TypeInfo<T>::isCustom;
@@ -204,6 +226,7 @@
 		static const bool value = TypeInfo<T>::isUserdata;
 	};
 
+	/* Helpers */
 	template <bool Cond, typename Type = void>
 	using EnableIf		= typename std::enable_if<Cond, Type>::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 <size_t N>
-	static void push(lua_State *L, const char (&s)[N])
+	static int push(lua_State *L, const char (&s)[N])
 	{
-		push<const char *>(L, s);
+		return push<const char *>(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 <class T>
 	static inline T toType(lua_State *L, int idx, const char *meta)
 	{
-		return reinterpret_cast<T>(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<T>(luaL_checkudata(L, idx, meta));
+
+		return reinterpret_cast<T>(lua_touserdata(L, idx));
 	}
 };
 
--- 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<Def>(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   |   <reference>   |
+	 *                                     +-----------------+
+	 */
+	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
--- 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 <memory>
+#include <string>
+#include <vector>
+
 #include <lua.hpp>
 
 /**
@@ -33,36 +37,48 @@
 	/**
 	 * Methods for a class.
 	 */
-	using Methods	= std::vector<luaL_Reg>;
-
-	/**
-	 * Smart pointer for Luae objects.
-	 */
-	template <typename T>
-	using Ptr	= std::shared_ptr<T>;
+	using Methods = std::vector<luaL_Reg>;
 
 	/**
 	 * @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<Def>	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 <typename T>
-	static void pushShared(lua_State *L, Ptr<T> 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<T>(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 <typename T>
-	static Ptr<T> getShared(lua_State *L, int index, const char *meta)
-	{
-		testShared(L, index, meta);
-		
-		return *static_cast<Ptr<T> *>(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 <typename T>
-	static int deleteShared(lua_State *L, int index)
-	{
-		static_cast<Ptr<T> *>(lua_touserdata(L, index))->~shared_ptr<T>();
-
-		return 0;
-	}
 };
 
 #endif // !_LUAE_CLASS_H_
--- 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 $<TARGET_FILE_DIR:luae>
-)
\ 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 $<TARGET_FILE_DIR:${target}>
+	)
+endforeach ()
\ No newline at end of file
--- /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 <markand@malikania.fr>
+ *
+ * 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 <cppunit/TextTestRunner.h>
+
+#include <Luae.h>
+#include <LuaeClass.h>
+#include <LuaeState.h>
+
+#include "TestLuaeClass.h"
+
+/* ---------------------------------------------------------
+ * Fake class for TestLuaeClass::create()
+ * --------------------------------------------------------- */
+
+class Object {};
+
+template <>
+struct Luae::TypeInfo<Object> : public Luae::TypeUserdata {
+	static constexpr const char *name = "Object";
+};
+
+/* ---------------------------------------------------------
+ * Fake classes for TestLuaeClass::testInheritance()
+ * --------------------------------------------------------- */
+
+class Base {};
+class Child {};
+
+template <>
+struct Luae::TypeInfo<Base> : public Luae::TypeUserdata {
+	static constexpr const char *name = "Base";
+};
+
+template <>
+struct Luae::TypeInfo<Child> : 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<Object>::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<bool>(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<Base>(L, 1);
+
+		return Luae::push(L, "base");
+	};
+	auto child = [] (lua_State *L) -> int {
+		Luae::check<Child>(L, 1);
+
+		return Luae::push(L, "child");
+	};
+
+	// Create base class
+	LuaeClass::Methods baseMethods = {
+		{ "base",	base	}
+	};
+	LuaeClass::Def baseDef(Luae::TypeInfo<Base>::name, baseMethods, {});
+
+	// Create child class
+	LuaeClass::Methods childMethods = {
+		{ "child",	child	}
+	};
+	LuaeClass::Def childDef(Luae::TypeInfo<Child>::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;
+}
--- /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 <markand@malikania.fr>
+ *
+ * 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 <cppunit/extensions/HelperMacros.h>
+#include <cppunit/TestFixture.h>
+
+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_
--- /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