diff --git a/docs/source/usertypes.rst b/docs/source/usertypes.rst index e0cac45c..3428ec01 100644 --- a/docs/source/usertypes.rst +++ b/docs/source/usertypes.rst @@ -19,8 +19,10 @@ The examples folder also has a number of really great examples for you to see. T - If you need dynamic callbacks or runtime overridable functions, have a ``std::function`` member variable and get/set it on the usertype object - ``std::function`` works as a member variable or in passing as an argument / returning as a value (you can even use it with ``sol::property``) - You can also create an entirely dynamic object: see the `dynamic_object example`_ for more details -* (Advanced) You can override the iteration function for Lua 5.2 and above (LuaJIT does not have the capability) `as shown in the pairs example`_ * You can use :doc:`filters` to control dependencies and streamline return values, as well as apply custom behavior to a functions return +* You can work with special wrapper types such as ``std::unique_ptr``, ``std::shared_ptr``, and others by default + - Extend them using the :doc:`sol::unique_usertype\ traits` +* (Advanced) You can override the iteration function for Lua 5.2 and above (LuaJIT does not have the capability) `as shown in the pairs example`_ * Please note that the colon is necessary to "automatically" pass the ``this``/``self`` argument to Lua methods - ``obj:method_name()`` is how you call "member" methods in Lua - It is purely syntactic sugar that passes the object name as the first argument to the ``method_name`` function @@ -32,13 +34,13 @@ The examples folder also has a number of really great examples for you to see. T - You can retrieve a usertype from the Lua runtime at any time - Methods and properties will be added to the type only after you register the usertype with the Lua runtime - All methods and properties will appear on all userdata, even if that object was pushed before the usertype (all userdata will be updated) -* Types either copy once or move once into the memory location, if it is a value type. If it is a pointer, we store only the reference. - - This means retrieval of class types (not primitive types like strings or integers) by ``T&`` or ``T*`` allow you to modify the data in Lua directly. +* Types either copy once or move once into the memory location, if it is a value type. If it is a pointer, we store only the reference + - This means retrieval of class types (not primitive types like strings or integers) by ``T&`` or ``T*`` allow you to modify the data in Lua directly - Retrieve a plain ``T`` to get a copy - - Return types and passing arguments to ``sol::function``-types use perfect forwarding and reference semantics, which means no copies happen unless you specify a value explicitly. See :ref:`this note for details`. -* You can set ``index`` and ``new_index`` freely on any usertype you like to override the default "if a key is missing, find it / set it here" functionality of a specific object of a usertype. - - ``new_index`` and ``index`` will not be called if you try to manipulate the named usertype table directly. sol2's will be called to add that function to the usertype's function/variable lookup table. - - ``new_index`` and ``index`` will be called if you attempt to call a key that does not exist on an actual userdata object (the C++ object) itself. + - Return types and passing arguments to ``sol::function``-types use perfect forwarding and reference semantics, which means no copies happen unless you specify a value explicitly. See :ref:`this note for details` +* You can set ``index`` and ``new_index`` freely on any usertype you like to override the default "if a key is missing, find it / set it here" functionality of a specific object of a usertype + - ``new_index`` and ``index`` will not be called if you try to manipulate the named usertype table directly. sol2's will be called to add that function to the usertype's function/variable lookup table + - ``new_index`` and ``index`` will be called if you attempt to call a key that does not exist on an actual userdata object (the C++ object) itself - If you made a usertype named ``test``, this means ``t = test()``, with ``t.hi = 54`` will call your function, but ``test.hi = function () print ("hi"); end`` will instead set the key ``hi`` to to lookup that function for all ``test`` types * The first ``sizeof( void* )`` bytes is always a pointer to the typed C++ memory. What comes after is based on what you've pushed into the system according to :doc:`the memory specification for usertypes`. This is compatible with a number of systems other than just sol2, making it easy to interop with select other Lua systems. * Member methods, properties, variables and functions taking ``self&`` arguments modify data directly diff --git a/sol/call.hpp b/sol/call.hpp index c4ace21a..83ac99c7 100644 --- a/sol/call.hpp +++ b/sol/call.hpp @@ -302,8 +302,8 @@ namespace sol { }; template - struct agnostic_lua_call_wrapper { - static int call(lua_State* L, lua_r_CFunction f) { + struct agnostic_lua_call_wrapper { + static int call(lua_State* L, lua_CFunction_ref f) { return f(L); } }; diff --git a/sol/error_handler.hpp b/sol/error_handler.hpp index aa504d8d..6842f7da 100644 --- a/sol/error_handler.hpp +++ b/sol/error_handler.hpp @@ -64,12 +64,33 @@ namespace sol { } }; + template struct argument_handler { int operator()(lua_State* L, int index, type expected, type actual, const std::string& message) const noexcept(false) { return type_panic_string(L, index, expected, actual, message + " (bad argument to variable or function call)"); } }; + template + struct argument_handler> { + int operator()(lua_State* L, int index, type expected, type actual, const std::string& message) const noexcept(false) { + std::string addendum = " (bad argument to type expecting '"; + addendum += detail::demangle(); + addendum += "("; + int marker = 0; + auto action = [&addendum, &marker](const std::string& n) { + if (marker > 0) { + addendum += ", "; + } + addendum += n; + ++marker; + } + (void)detail::swallow{ int(), (action(detail::demangle()), int())... }; + addendum += ")')"; + return type_panic_string(L, index, expected, actual, message + addendum); + } + }; + // Specify this function as the handler for lua::check if you know there's nothing wrong inline int no_panic(lua_State*, int, type, type, const char* = nullptr) noexcept { return 0; diff --git a/sol/simple_usertype_metatable.hpp b/sol/simple_usertype_metatable.hpp index 4f94b36a..fe5d91d1 100644 --- a/sol/simple_usertype_metatable.hpp +++ b/sol/simple_usertype_metatable.hpp @@ -184,7 +184,7 @@ namespace sol { void* baseclasscast; bool mustindex; bool secondarymeta; - std::array properties; + std::array properties; template void insert(N&& n, object&& o) { @@ -415,6 +415,13 @@ namespace sol { auto& properties = umx.properties; auto sic = hasindex ? &usertype_detail::simple_index_call : &usertype_detail::simple_index_call; auto snic = hasnewindex ? &usertype_detail::simple_new_index_call : &usertype_detail::simple_new_index_call; + + lua_createtable(L, 0, 2); + stack_reference type_table(L, -1); + + stack::set_field(L, "name", detail::demangle(), type_table.stack_index()); + stack::set_field(L, "is", &usertype_detail::is_check, type_table.stack_index()); + auto register_kvp = [&](std::size_t meta_index, stack_reference& t, const std::string& first, object& second) { meta_function mf = meta_function::construct; for (std::size_t j = 1; j < properties.size(); ++j) { @@ -469,6 +476,8 @@ namespace sol { } luaL_newmetatable(L, metakey); stack_reference t(L, -1); + stack::set_field(L, meta_function::type, type_table, t.stack_index()); + for (auto& kvp : varmap.functions) { auto& first = std::get<0>(kvp); auto& second = std::get<1>(kvp); @@ -514,6 +523,7 @@ namespace sol { // for call constructor purposes and such lua_createtable(L, 0, 2 * static_cast(umx.secondarymeta) + static_cast(umx.callconstructfunc.valid())); stack_reference metabehind(L, -1); + stack::set_field(L, meta_function::type, type_table, metabehind.stack_index()); if (umx.callconstructfunc.valid()) { stack::set_field(L, sol::meta_function::call_function, umx.callconstructfunc, metabehind.stack_index()); } @@ -538,6 +548,7 @@ namespace sol { // Now for the shim-table that actually gets pushed luaL_newmetatable(L, &usertype_traits::user_metatable()[0]); stack_reference t(L, -1); + stack::set_field(L, meta_function::type, type_table, t.stack_index()); for (auto& kvp : varmap.functions) { auto& first = std::get<0>(kvp); auto& second = std::get<1>(kvp); @@ -546,6 +557,7 @@ namespace sol { { lua_createtable(L, 0, 2 + static_cast(umx.callconstructfunc.valid())); stack_reference metabehind(L, -1); + stack::set_field(L, meta_function::type, type_table, metabehind.stack_index()); if (umx.callconstructfunc.valid()) { stack::set_field(L, sol::meta_function::call_function, umx.callconstructfunc, metabehind.stack_index()); } @@ -570,6 +582,8 @@ namespace sol { metabehind.pop(); } + lua_remove(L, type_table.stack_index()); + // Don't pop the table when we're done; // return it return 1; diff --git a/sol/stack.hpp b/sol/stack.hpp index 47e5dcb7..13d13e56 100644 --- a/sol/stack.hpp +++ b/sol/stack.hpp @@ -112,8 +112,8 @@ namespace sol { inline decltype(auto) call(types, types ta, std::index_sequence tai, lua_State* L, int start, Fx&& fx, FxArgs&&... args) { #ifndef _MSC_VER static_assert(meta::all...>::value, "One of the arguments being bound is a move-only type, and it is not being taken by reference: this will break your code. Please take a reference and std::move it manually if this was your intention."); -#endif // This compiler make me so fucking sad - argument_handler handler{}; +#endif // This compiler make me so sad + argument_handler> handler{}; multi_check(L, start, handler); record tracking{}; return evaluator{}.eval(ta, tai, L, start, tracking, std::forward(fx), std::forward(args)...); @@ -124,7 +124,7 @@ namespace sol { #ifndef _MSC_VER static_assert(meta::all...>::value, "One of the arguments being bound is a move-only type, and it is not being taken by reference: this will break your code. Please take a reference and std::move it manually if this was your intention."); #endif // This compiler make me so fucking sad - argument_handler handler{}; + argument_handler> handler{}; multi_check(L, start, handler); record tracking{}; evaluator{}.eval(ta, tai, L, start, tracking, std::forward(fx), std::forward(args)...); diff --git a/sol/stack_push.hpp b/sol/stack_push.hpp index b16cc72a..e012392c 100644 --- a/sol/stack_push.hpp +++ b/sol/stack_push.hpp @@ -880,7 +880,18 @@ namespace sol { return std::visit(stack_detail::push_function(L), std::move(v)); } }; -#endif +#else + template <> + struct pusher { + static int push(lua_State* L, const std::string_view& sv) { + return stack::push(L, sv.data(), sv.length()); + } + + static int push(lua_State* L, const std::string_view& sv, std::size_t len) { + return stack::push(L, sv.data(), len); + } + }; +#endif // C++17 Support } // stack } // sol diff --git a/sol/state_view.hpp b/sol/state_view.hpp index ccd03084..e0fff2b5 100644 --- a/sol/state_view.hpp +++ b/sol/state_view.hpp @@ -30,19 +30,33 @@ namespace sol { enum class lib : char { + // print, assert, and other base functions base, + // require and other package functions package, + // coroutine functions and utilities coroutine, + // string library string, + // functionality from the OS os, + // all things math math, + // the table manipulator and observer functions table, + // the debug library debug, + // the bit library: different based on which you're using bit32, + // input/output library io, + // LuaJIT only ffi, + // LuaJIT only jit, + // library for handling utf8: new to Lua utf8, + // do not use count }; diff --git a/sol/types.hpp b/sol/types.hpp index 1cd2d059..551b540f 100644 --- a/sol/types.hpp +++ b/sol/types.hpp @@ -186,7 +186,7 @@ namespace sol { struct no_metatable_t {}; const no_metatable_t no_metatable = {}; - typedef std::remove_pointer_t lua_r_CFunction; + typedef std::remove_pointer_t lua_CFunction_ref; template struct unique_usertype_traits { @@ -617,13 +617,15 @@ namespace sol { bitwise_or, bitwise_xor, pairs, - next + next, + type, + type_info, }; typedef meta_function meta_method; - inline const std::array& meta_function_names() { - static const std::array names = { { + inline const std::array& meta_function_names() { + static const std::array names = { { "new", "__index", "__newindex", @@ -654,7 +656,9 @@ namespace sol { "__bxor", "__pairs", - "__next" + "__next", + "__type", + "__typeinfo" } }; return names; } diff --git a/sol/usertype_core.hpp b/sol/usertype_core.hpp index a32d3358..fcbe2bb2 100644 --- a/sol/usertype_core.hpp +++ b/sol/usertype_core.hpp @@ -44,6 +44,11 @@ namespace sol { } }; + template + int is_check(lua_State* L) { + return stack::push(L, stack::check(L, 1, &no_panic)); + } + template inline int member_default_to_string(std::true_type, lua_State* L) { decltype(auto) ts = stack::get(L, 1).to_string(); diff --git a/sol/usertype_metatable.hpp b/sol/usertype_metatable.hpp index e40cf01e..3901945b 100644 --- a/sol/usertype_metatable.hpp +++ b/sol/usertype_metatable.hpp @@ -379,7 +379,7 @@ namespace sol { struct usertype_metatable, Tn...> : usertype_metatable_core, usertype_detail::registrar { typedef std::make_index_sequence indices; typedef std::index_sequence half_indices; - typedef std::array regs_t; + typedef std::array regs_t; typedef std::tuple RawTuple; typedef std::tuple ...> Tuple; template @@ -394,7 +394,7 @@ namespace sol { void* baseclasscheck; void* baseclasscast; bool secondarymeta; - std::array properties; + std::array properties; template >> = meta::enabler> lua_CFunction make_func() const { @@ -676,6 +676,12 @@ namespace sol { } unique_table[lastreg - 1] = { value_table[lastreg - 1].name, detail::unique_destruct }; + lua_createtable(L, 0, 2); + stack_reference type_table(L, -1); + + stack::set_field(L, "name", detail::demangle(), type_table.stack_index()); + stack::set_field(L, "is", &usertype_detail::is_check, type_table.stack_index()); + // Now use um const bool& mustindex = umc.mustindex; for (std::size_t i = 0; i < 3; ++i) { @@ -699,6 +705,7 @@ namespace sol { } luaL_newmetatable(L, metakey); stack_reference t(L, -1); + stack::set_field(L, meta_function::type, type_table, t.stack_index()); int upvalues = 0; upvalues += stack::push(L, nullptr); upvalues += stack::push(L, make_light(um)); @@ -735,6 +742,9 @@ namespace sol { stack::set_field(L, meta_function::index, make_closure(umt_t::index_call, nullptr, make_light(um), make_light(umc)), metabehind.stack_index()); stack::set_field(L, meta_function::new_index, make_closure(umt_t::new_index_call, nullptr, make_light(um), make_light(umc)), metabehind.stack_index()); } + // type information needs to be present on the behind-tables too + stack::set_field(L, meta_function::type, type_table, metabehind.stack_index()); + stack::set_field(L, metatable_key, metabehind, t.stack_index()); metabehind.pop(); // We want to just leave the table @@ -745,6 +755,7 @@ namespace sol { // Now for the shim-table that actually gets assigned to the name luaL_newmetatable(L, &usertype_traits::user_metatable()[0]); stack_reference t(L, -1); + stack::set_field(L, meta_function::type, type_table, t.stack_index()); int upvalues = 0; upvalues += stack::push(L, nullptr); upvalues += stack::push(L, make_light(um)); @@ -758,11 +769,14 @@ namespace sol { stack::set_field(L, meta_function::index, make_closure(umt_t::index_call, nullptr, make_light(um), make_light(umc), nullptr, usertype_detail::toplevel_magic), metabehind.stack_index()); stack::set_field(L, meta_function::new_index, make_closure(umt_t::new_index_call, nullptr, make_light(um), make_light(umc), nullptr, usertype_detail::toplevel_magic), metabehind.stack_index()); - stack::set_field(L, metatable_key, metabehind, t.stack_index()); + // type information needs to be present on the behind-tables too + stack::set_field(L, meta_function::type, type_table, metabehind.stack_index()); metabehind.pop(); } + lua_remove(L, type_table.stack_index()); + return 1; } }; diff --git a/test_simple_usertypes.cpp b/test_simple_usertypes.cpp index 4ddb49b4..aa72aaf1 100644 --- a/test_simple_usertypes.cpp +++ b/test_simple_usertypes.cpp @@ -888,3 +888,35 @@ TEST_CASE("simple_usertype/indexing", "make sure simple usertypes can be indexed REQUIRE(val == 50); } } + +TEST_CASE("simple_usertype/basic type information", "check that we can query some basic type information") { + struct my_thing {}; + + sol::state lua; + lua.open_libraries(sol::lib::base); + + lua.new_usertype("my_thing"); + + lua.safe_script("obj = my_thing.new()"); + + lua.safe_script("assert(my_thing.__type.is(obj))"); + lua.safe_script("assert(not my_thing.__type.is(1))"); + lua.safe_script("assert(not my_thing.__type.is(\"not a thing\"))"); + lua.safe_script("print(my_thing.__type.name)"); + + lua.safe_script("assert(obj.__type.is(obj))"); + lua.safe_script("assert(not obj.__type.is(1))"); + lua.safe_script("assert(not obj.__type.is(\"not a thing\"))"); + lua.safe_script("print(obj.__type.name)"); + + lua.safe_script("assert(getmetatable(my_thing).__type.is(obj))"); + lua.safe_script("assert(not getmetatable(my_thing).__type.is(1))"); + lua.safe_script("assert(not getmetatable(my_thing).__type.is(\"not a thing\"))"); + lua.safe_script("print(getmetatable(my_thing).__type.name)"); + + lua.safe_script("assert(getmetatable(obj).__type.is(obj))"); + lua.safe_script("assert(not getmetatable(obj).__type.is(1))"); + lua.safe_script("assert(not getmetatable(obj).__type.is(\"not a thing\"))"); + lua.safe_script("print(getmetatable(obj).__type.name)"); +} + diff --git a/test_strings.cpp b/test_strings.cpp index 90c496b7..95c7eb0c 100644 --- a/test_strings.cpp +++ b/test_strings.cpp @@ -115,8 +115,23 @@ TEST_CASE("object/string-pushers", "test some basic string pushers with in_place sol::object ocs(lua, sol::in_place, "bark\0bark", 9); sol::object os(lua, sol::in_place_type, std::string("bark\0bark", 9), 8); - bool test1 = os.as() == std::string("bark\0bar", 8); - bool test2 = ocs.as() == std::string("bark\0bark", 9); + sol::object osv(lua, sol::in_place_type, std::string_view("woofwoof", 8), 8); + bool test1 = ocs.as() == std::string("bark\0bark", 9); + bool test2 = os.as() == std::string("bark\0bar", 8); + bool test3 = osv.as() == std::string("woofwoof", 8); + REQUIRE(ocs.get_type() == sol::type::string); + REQUIRE(ocs.is()); + REQUIRE(ocs.is()); + + REQUIRE(os.get_type() == sol::type::string); + REQUIRE(os.is()); + REQUIRE(os.is()); + + REQUIRE(osv.get_type() == sol::type::string); + REQUIRE(osv.is()); + REQUIRE(osv.is()); + REQUIRE(test1); REQUIRE(test2); + REQUIRE(test3); } diff --git a/test_usertypes.cpp b/test_usertypes.cpp index a08f1f90..2b16b569 100644 --- a/test_usertypes.cpp +++ b/test_usertypes.cpp @@ -1764,3 +1764,34 @@ TEST_CASE("usertype/noexcept-methods", "make sure noexcept functinos and methods REQUIRE(v1 == 0x61); REQUIRE(v2 == 0x62); } + +TEST_CASE("usertype/basic type information", "check that we can query some basic type information") { + struct my_thing {}; + + sol::state lua; + lua.open_libraries(sol::lib::base); + + lua.new_simple_usertype("my_thing"); + + lua.safe_script("obj = my_thing.new()"); + + lua.safe_script("assert(my_thing.__type.is(obj))"); + lua.safe_script("assert(not my_thing.__type.is(1))"); + lua.safe_script("assert(not my_thing.__type.is(\"not a thing\"))"); + lua.safe_script("print(my_thing.__type.name)"); + + lua.safe_script("assert(obj.__type.is(obj))"); + lua.safe_script("assert(not obj.__type.is(1))"); + lua.safe_script("assert(not obj.__type.is(\"not a thing\"))"); + lua.safe_script("print(obj.__type.name)"); + + lua.safe_script("assert(getmetatable(my_thing).__type.is(obj))"); + lua.safe_script("assert(not getmetatable(my_thing).__type.is(1))"); + lua.safe_script("assert(not getmetatable(my_thing).__type.is(\"not a thing\"))"); + lua.safe_script("print(getmetatable(my_thing).__type.name)"); + + lua.safe_script("assert(getmetatable(obj).__type.is(obj))"); + lua.safe_script("assert(not getmetatable(obj).__type.is(1))"); + lua.safe_script("assert(not getmetatable(obj).__type.is(\"not a thing\"))"); + lua.safe_script("print(getmetatable(obj).__type.name)"); +}