// https://github.com/kunitoki/LuaBridge3 // Copyright 2020, kunitoki // Copyright 2019, Dmitry Tarakanov // Copyright 2012, Vinnie Falco // SPDX-License-Identifier: MIT #pragma once #include "Config.h" #include "ClassInfo.h" #include "Errors.h" #include "FuncTraits.h" #include "LuaHelpers.h" #include "Options.h" #include "Stack.h" #include "TypeTraits.h" #include "Userdata.h" #include #include #include #include #include #include #include #include namespace luabridge { class LuaRef; namespace detail { //================================================================================================= /** * @brief Extract exactly what Stack::get() produces. * * Allows that implicit conversions (e.g. std::reference_wrapper → T&) happen correctly at the call site. */ template using stack_value_t = remove_cvref_t< decltype(*std::declval::get(std::declval(), 0))>())>; //================================================================================================= /** * @brief Trivially-destructible storage for one decoded function argument. * * Longjmp-safe: the raw byte array and the construction flag are trivially * destructible, so raise_lua_error (longjmp) while ArgStorage objects sit on * the C++ stack does not skip any live C++ destructor. The contained T must * be managed explicitly via construct/destroy. */ template struct ArgStorage { using StoredType = stack_value_t; alignas(StoredType) std::byte data[sizeof(StoredType)]; bool constructed = false; StoredType* ptr() noexcept { return std::launder(reinterpret_cast(data)); } void destroy() noexcept { if (constructed) { std::destroy_at(ptr()); constructed = false; } } }; //================================================================================================= /** * @brief Decode one argument from the Lua stack into storage element I. * * Sets error_arg/error_msg on the first failure; subsequent calls are no-ops. */ template void decode_arg(lua_State* L, StorageTuple& storage, int& error_arg, const char*& error_msg) { using T = std::tuple_element_t; using StoredType = typename std::tuple_element_t::StoredType; if (error_arg) return; auto result = Stack::get(L, static_cast(I + Start)); if (! result) { error_arg = static_cast(I + 1); error_msg = result.error_cstr(); return; } ::new (std::get(storage).data) StoredType(std::move(*result)); std::get(storage).constructed = true; } //================================================================================================= /** * @brief Make argument lists extracting them from the lua state, starting at a stack index. * * Arguments are decoded sequentially left to right. On failure every already- * constructed argument is explicitly destroyed before raise_lua_error is called, * so longjmp never skips a live C++ destructor. * * @tparam ArgsPack Arguments pack to extract from the lua stack. * @tparam Start Start index where stack variables are located in the lua stack. */ template auto make_arguments_list_impl([[maybe_unused]] lua_State* L, std::index_sequence) { std::tuple>...> storage; int error_arg = 0; const char* error_msg = nullptr; (decode_arg(L, storage, error_arg, error_msg), ...); if (error_arg) { (std::get(storage).destroy(), ...); raise_lua_error(L, "Error decoding argument #%d: %s", error_arg, error_msg); } auto result = tupleize(std::move(*std::get(storage).ptr())...); (std::get(storage).destroy(), ...); return result; } template auto make_arguments_list(lua_State* L) { return make_arguments_list_impl(L, std::make_index_sequence>()); } //================================================================================================= /** * @brief Helpers for iterating through tuple arguments, pushing each argument to the lua stack. */ template auto push_arguments(lua_State*, std::tuple) -> std::enable_if_t> { return std::make_tuple(Result(), Index + 1); } template auto push_arguments(lua_State* L, std::tuple t) -> std::enable_if_t> { using T = std::tuple_element_t>; auto result = Stack::push(L, std::get(t)); if (! result) return std::make_tuple(result, Index + 1); return push_arguments(L, std::move(t)); } //================================================================================================= /** * @brief Helpers for iterating through tuple arguments, popping each argument from the lua stack. */ template auto pop_arguments(lua_State*, std::tuple&) -> std::enable_if_t { return sizeof...(Types); } template auto pop_arguments(lua_State* L, std::tuple& t) -> std::enable_if_t { using T = std::tuple_element_t>; std::get(t) = Stack::get(L, Start - Index); return pop_arguments(L, t); } //================================================================================================= /** * @brief Push a tuple on the stack. */ template Result push_tuple_impl(lua_State* L, const Tuple& value, std::index_sequence) { Result result; (void)((result = Stack>>::push(L, std::get(value)), bool(result)) && ...); return result; } template Result push_tuple(lua_State* L, const std::tuple& value) { return push_tuple_impl(L, value, std::index_sequence_for{}); } //================================================================================================= /** * @brief Check if a method name is a metamethod. */ template constexpr auto make_array(Args&&... args) { if constexpr (std::is_same_v) return std::array...>, sizeof...(Args)>{{ std::forward(args)... }}; else return std::array{{ std::forward(args)... }}; } inline bool is_metamethod(std::string_view method_name) { static constexpr auto metamethods = make_array( "__add", "__band", "__bnot", "__bor", "__bxor", "__call", "__close", "__concat", "__div", "__eq", "__gc", "__idiv", "__index", "__ipairs", "__le", "__len", "__lt", "__metatable", "__mod", "__mode", "__mul", "__name", "__newindex", "__pairs", "__pow", "__shl", "__shr", "__sub", "__tostring", "__unm" ); if (method_name.size() <= 2 || method_name[0] != '_' || method_name[1] != '_') return false; auto result = std::lower_bound(metamethods.begin(), metamethods.end(), method_name); return result != metamethods.end() && *result == method_name; } inline void rawset_super_method(lua_State* L, int tableIndex, const char* key) { LUABRIDGE_ASSERT(key != nullptr); tableIndex = lua_absindex(L, tableIndex); // Stack before: ..., value if (key[0] == '_') lua_pushliteral(L, "super"); // Stack: ..., value, "super" else lua_pushliteral(L, "super_"); // Stack: ..., value, "super_" lua_pushstring(L, key); // Stack: ..., value, prefix, key lua_concat(L, 2); // Stack: ..., value, super_key lua_insert(L, -2); // Stack: ..., super_key, value lua_rawset(L, tableIndex); // Pops key/value. Stack: ... } //================================================================================================= /** * @brief Make super method name. */ inline Options get_class_options(lua_State* L, int index) { LUABRIDGE_ASSERT(lua_istable(L, index)); // Stack: mt Options options = defaultOptions; lua_rawgetp_x(L, index, getClassOptionsKey()); // Stack: mt, ifb (may be nil) if (lua_isnumber(L, -1)) options = Options::fromUnderlying(lua_tointeger(L, -1)); lua_pop(L, 1); return options; } //================================================================================================= /** * @brief 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_x(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_x(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. * * Retrieves functions from metatables and properties from propget tables. Looks through the class hierarchy if inheritance is present. */ inline std::optional try_call_index_fallback(lua_State* L) { LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: mt lua_rawgetp_x(L, -1, getIndexFallbackKey()); // Stack: mt, ifb (may be nil) if (! lua_iscfunction(L, -1)) { lua_pop(L, 1); // Stack: mt return std::nullopt; } lua_pushvalue(L, 1); // Stack: mt, ifb, arg1 lua_pushvalue(L, 2); // Stack: mt, ifb, arg1, arg2 lua_call(L, 2, 1); // Stack: mt, ifbresult if (! lua_isnoneornil(L, -1)) { lua_remove(L, -2); // Stack: ifbresult return 1; } lua_pop(L, 1); // Stack: mt return std::nullopt; } inline std::optional try_call_static_index_fallback(lua_State* L) { LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: mt lua_rawgetp_x(L, -1, getStaticIndexFallbackKey()); // Stack: mt, ifb (may be nil) if (! lua_iscfunction(L, -1)) { lua_pop(L, 1); // Stack: mt return std::nullopt; } lua_pushvalue(L, 2); // Stack: mt, ifb, arg1 (key only, no self for static) lua_call(L, 1, 1); // Stack: mt, ifbresult if (! lua_isnoneornil(L, -1)) { lua_remove(L, -2); // Stack: ifbresult return 1; } lua_pop(L, 1); // Stack: mt 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_x(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 std::optional try_call_parent_index_fallback(lua_State* L, const char* key) { LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: mt if (key == nullptr) return std::nullopt; lua_rawgetp_x(L, -1, getParentKey()); // Stack: mt, parent list | nil if (! lua_istable(L, -1)) { lua_pop(L, 1); // Stack: mt return std::nullopt; } const int parentListIndex = lua_absindex(L, -1); const int parentCount = get_length(L, parentListIndex); for (int i = 1; i <= parentCount; ++i) { lua_rawgeti(L, parentListIndex, i); // Stack: mt, parent list, parent mt if (! lua_istable(L, -1)) { lua_pop(L, 1); continue; } const Options parentOptions = get_class_options(L, -1); // Stack: mt, parent list, parent mt if (parentOptions.test(extensibleClass | ~allowOverridingMethods)) { if (auto result = try_call_index_extensible(L, key)) { lua_remove(L, -2); // Stack: mt, result lua_remove(L, -2); // Stack: result return *result; } } lua_rawgetp_x(L, -1, getIndexFallbackKey()); // Stack: mt, parent list, parent mt, ifb | nil if (lua_iscfunction(L, -1)) { lua_pushvalue(L, 1); // Stack: mt, parent list, parent mt, ifb, arg1 lua_pushvalue(L, 2); // Stack: mt, parent list, parent mt, ifb, arg1, arg2 lua_call(L, 2, 1); // Stack: mt, parent list, parent mt, result if (! lua_isnoneornil(L, -1)) { lua_remove(L, -2); // Stack: mt, parent list, result lua_remove(L, -2); // Stack: mt, result lua_remove(L, -2); // Stack: result return 1; } lua_pop(L, 1); // Stack: mt, parent list, parent mt } else { lua_pop(L, 1); // Stack: mt, parent list, parent mt } lua_pop(L, 1); // Stack: mt, parent list } lua_pop(L, 1); // Stack: mt return std::nullopt; } template inline int index_metamethod(lua_State* L) { #if LUABRIDGE_SAFE_STACK_CHECKS luaL_checkstack(L, 6, detail::error_lua_stack_overflow); #endif LUABRIDGE_ASSERT(lua_istable(L, 1) || lua_isuserdata(L, 1)); // Stack (further not shown): table | userdata, name lua_getmetatable(L, 1); // Stack: class/const table (mt) LUABRIDGE_ASSERT(lua_istable(L, -1)); // Protect internal meta methods const char* key = lua_tostring(L, 2); if (key != nullptr && is_metamethod(key)) { lua_pushnil(L); return 1; } for (;;) { if constexpr (IsObject) { // Repeat the lookup in the index fallback if (auto result = try_call_index_fallback(L)) return *result; } else { // Repeat the lookup in the static index fallback if (auto result = try_call_static_index_fallback(L)) return *result; } // 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_isnil(L, -1)) // Stack: mt, field { lua_remove(L, -2); // Stack: field return 1; } 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_x(L, -1, getPropgetKey()); // Stack: mt, propget table (pg) LUABRIDGE_ASSERT(lua_istable(L, -1)); lua_pushvalue(L, 2); // Stack: mt, pg, field name lua_rawget(L, -2); // Stack: mt, pg, getter | nil lua_remove(L, -2); // Stack: mt, getter | nil if (lua_iscfunction(L, -1)) // Stack: mt, getter { lua_remove(L, -2); // Stack: getter lua_pushvalue(L, 1); // Stack: getter, table | userdata lua_call(L, 1, 1); // Stack: value return 1; } LUABRIDGE_ASSERT(lua_isnil(L, -1)); // Stack: mt, nil lua_pop(L, 1); // Stack: mt // It may mean that the field may be in const table and it's constness violation. // Search flattened parent list in declaration-order DFS. lua_rawgetp_x(L, -1, getParentKey()); // Stack: mt, parent list | nil if (lua_istable(L, -1)) { const int parentListIndex = lua_absindex(L, -1); const int parentCount = get_length(L, parentListIndex); for (int i = 1; i <= parentCount; ++i) { lua_rawgeti(L, parentListIndex, i); // Stack: mt, parent list, parent mt if (! lua_istable(L, -1)) { lua_pop(L, 1); continue; } lua_pushvalue(L, 2); // Stack: mt, parent list, parent mt, field name lua_rawget(L, -2); // Stack: mt, parent list, parent mt, field | nil if (! lua_isnil(L, -1)) { lua_remove(L, -2); // Stack: mt, parent list, field lua_remove(L, -2); // Stack: mt, field lua_remove(L, -2); // Stack: field return 1; } lua_pop(L, 1); // Stack: mt, parent list, parent mt lua_rawgetp_x(L, -1, getPropgetKey()); // Stack: mt, parent list, parent mt, pg | nil if (lua_istable(L, -1)) { lua_pushvalue(L, 2); // Stack: mt, parent list, parent mt, pg, field name lua_rawget(L, -2); // Stack: mt, parent list, parent mt, pg, getter | nil lua_remove(L, -2); // Stack: mt, parent list, parent mt, getter | nil if (lua_iscfunction(L, -1)) { lua_remove(L, -2); // Stack: mt, parent list, getter lua_remove(L, -2); // Stack: mt, getter lua_remove(L, -2); // Stack: getter lua_pushvalue(L, 1); // Stack: getter, table | userdata lua_call(L, 1, 1); // Stack: value return 1; } lua_pop(L, 1); // Stack: mt, parent list, parent mt } else { lua_pop(L, 1); // Stack: mt, parent list, parent mt } lua_pop(L, 1); // Stack: mt, parent list } } lua_pop(L, 2); // Stack: - break; } lua_getmetatable(L, 1); // Stack: class/const table (mt) LUABRIDGE_ASSERT(lua_istable(L, -1)); 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; } if (auto result = try_call_parent_index_fallback(L, key)) return *result; lua_pop(L, 1); // Stack: - lua_pushnil(L); return 1; // no return } template inline int index_metamethod_simple(lua_State* L) { #if LUABRIDGE_SAFE_STACK_CHECKS luaL_checkstack(L, 3, detail::error_lua_stack_overflow); #endif LUABRIDGE_ASSERT(lua_istable(L, 1) || lua_isuserdata(L, 1)); // Fast path for simple userdata objects: when registration provides propget and // method tables as closure upvalues, avoid metatable/pointer-key lookups. if constexpr (IsObject) { if (lua_isuserdata(L, 1)) { const char* key = lua_tostring(L, 2); const auto rawlookup = [L](int tableIndex) { lua_pushvalue(L, 2); lua_rawget(L, tableIndex); }; rawlookup(lua_upvalueindex(1)); // Stack: getter | nil if (lua_iscfunction(L, -1)) { lua_pushvalue(L, 1); // Stack: getter, self lua_call(L, 1, 1); // Stack: value return 1; } lua_pop(L, 1); if (key != nullptr && is_metamethod(key)) { lua_pushnil(L); return 1; } rawlookup(lua_upvalueindex(2)); // Stack: value | nil if (! lua_isnil(L, -1)) return 1; lua_pop(L, 1); lua_pushnil(L); return 1; } } else { // Fast path for simple static tables: capture propget/class/metatable as upvalues. if (lua_istable(L, 1)) { const char* key = lua_tostring(L, 2); const auto rawlookup = [L](int tableIndex) { lua_pushvalue(L, 2); lua_rawget(L, tableIndex); }; if (key != nullptr && is_metamethod(key)) { lua_pushnil(L); return 1; } if (lua_istable(L, lua_upvalueindex(2))) { rawlookup(lua_upvalueindex(2)); // Stack: value | nil if (! lua_isnil(L, -1)) return 1; lua_pop(L, 1); } rawlookup(lua_upvalueindex(3)); // Stack: value | nil if (! lua_isnil(L, -1)) return 1; lua_pop(L, 1); rawlookup(lua_upvalueindex(1)); // Stack: getter | nil if (lua_iscfunction(L, -1)) { lua_pushvalue(L, 1); // Stack: getter, self lua_call(L, 1, 1); // Stack: value return 1; } lua_pop(L, 1); lua_pushnil(L); return 1; } } lua_getmetatable(L, 1); // Stack: mt LUABRIDGE_ASSERT(lua_istable(L, -1)); const char* key = lua_tostring(L, 2); const auto rawlookup = [L]() { lua_pushvalue(L, 2); lua_rawget(L, -2); }; // For userdata instance property access, checking propget first avoids an always-miss lookup // in the class table for common property keys. if (lua_isuserdata(L, 1)) { lua_rawgetp_x(L, -1, getPropgetKey()); // Stack: mt, pg LUABRIDGE_ASSERT(lua_istable(L, -1)); rawlookup(); // Stack: mt, pg, getter | nil lua_remove(L, -2); // Stack: mt, getter | nil if (lua_iscfunction(L, -1)) { lua_remove(L, -2); // Stack: getter lua_pushvalue(L, 1); // Stack: getter, self lua_call(L, 1, 1); // Stack: value return 1; } lua_pop(L, 1); // Stack: mt } if (key != nullptr && is_metamethod(key)) { lua_pushnil(L); return 1; } 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)) { rawlookup(); // Stack: mt, self | cl | co, value | nil lua_remove(L, -2); // Stack: mt, value | nil if (! lua_isnil(L, -1)) { lua_remove(L, -2); // Stack: value return 1; } } lua_pop(L, 1); // Stack: mt } rawlookup(); // Stack: mt, value | nil if (! lua_isnil(L, -1)) { lua_remove(L, -2); // Stack: value return 1; } lua_pop(L, 1); // Stack: mt lua_rawgetp_x(L, -1, getPropgetKey()); // Stack: mt, pg LUABRIDGE_ASSERT(lua_istable(L, -1)); rawlookup(); // Stack: mt, pg, getter | nil lua_remove(L, -2); // Stack: mt, getter | nil if (lua_iscfunction(L, -1)) { lua_remove(L, -2); // Stack: getter lua_pushvalue(L, 1); // Stack: getter, self lua_call(L, 1, 1); // Stack: value return 1; } lua_pop(L, 2); // Stack: - lua_pushnil(L); return 1; } //================================================================================================= /** * @brief __newindex metamethod for non-static members. * * Retrieves properties from propset tables. */ inline std::optional try_call_newindex_fallback(lua_State* L) { LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: mt lua_rawgetp_x(L, -1, getNewIndexFallbackKey()); // Stack: mt, nifb (may be nil) if (! lua_iscfunction(L, -1)) { lua_pop(L, 1); // Stack: mt return std::nullopt; } 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_static_newindex_fallback(lua_State* L) { LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: mt lua_rawgetp_x(L, -1, getStaticNewIndexFallbackKey()); // Stack: mt, nifb (may be nil) if (! lua_iscfunction(L, -1)) { lua_pop(L, 1); // Stack: mt return std::nullopt; } lua_pushvalue(L, 2); // stack: mt, nifb, arg1 (key only, no self for static) lua_pushvalue(L, 3); // stack: mt, nifb, arg1, arg2 (value) lua_call(L, 2, 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 // For Lua function values (instance methods added from Lua), capture the originating class // table so the method is stored there rather than in a traversed-to parent class table. // This prevents e.g. `function Derived:init()` from polluting `Base`'s method table. // // For non-function values (static properties like `Class.prop = val`), the original // traversal-end behaviour is preserved: storing them in the nearest class table that // already has a matching key (or the topmost base if none exists). This keeps static // properties out of the derived class table, which is also used for instance extensible // method lookup, avoiding unintended shadowing of per-instance properties accessed via an // index-fallback metamethod registered on a non-extensible base class. const bool value_is_function = lua_isfunction(L, 3); if (value_is_function) { // Capture the original (most-derived) class table — always write here. push_class_or_const_table(L, -1); // Stack: mt, orig_ct | nil if (! lua_istable(L, -1)) // Stack: mt, nil { lua_pop(L, 1); // Stack: mt return std::nullopt; } // Stack: mt, orig_ct const int mtIndex = lua_absindex(L, -2); const int origClassTableIndex = lua_absindex(L, -1); const auto process_metatable = [L, key, origClassTableIndex](int candidateMtIndex) { push_class_or_const_table(L, candidateMtIndex); // Stack: ..., candidate_ct | nil if (! lua_istable(L, -1)) { lua_pop(L, 1); return false; } lua_pushvalue(L, 2); // Stack: ..., candidate_ct, field name lua_rawget(L, -2); // Stack: ..., candidate_ct, field | nil if (lua_isnil(L, -1)) { lua_pop(L, 2); // Stack: ... return false; } if (! lua_iscfunction(L, -1)) { lua_pop(L, 2); // Stack: ... return true; } const Options options = get_class_options(L, -2); if (! options.test(allowOverridingMethods)) raise_lua_error(L, "immutable member '%s'", key); rawset_super_method(L, origClassTableIndex, key); // Stack: ..., candidate_ct lua_pop(L, 1); // Stack: ... return true; }; (void) process_metatable(mtIndex); lua_rawgetp_x(L, mtIndex, getParentKey()); // Stack: mt, orig_ct, parent list | nil if (lua_istable(L, -1)) { const int parentListIndex = lua_absindex(L, -1); const int parentCount = get_length(L, parentListIndex); for (int i = 1; i <= parentCount; ++i) { lua_rawgeti(L, parentListIndex, i); // Stack: mt, orig_ct, parent list, parent mt if (lua_istable(L, -1)) { if (process_metatable(lua_absindex(L, -1))) { lua_pop(L, 1); // Stack: mt, orig_ct, parent list break; } } lua_pop(L, 1); // Stack: mt, orig_ct, parent list } } lua_pop(L, 1); // Stack: mt, orig_ct // Stack: mt, orig_ct — write to the original (most-derived) class table. lua_getmetatable(L, -1); // Stack: mt, orig_ct, orig_ct_mt lua_pushvalue(L, 3); // Stack: mt, orig_ct, orig_ct_mt, arg3 rawsetfield(L, -2, key); // Stack: mt, orig_ct, orig_ct_mt lua_pop(L, 2); // Stack: mt return 0; } // Non-function value (static property): use original traversal-end write location. const int rootMetatableIndex = lua_absindex(L, -1); lua_pushvalue(L, rootMetatableIndex); // Stack: mt, target mt const int targetMetatableIndex = lua_absindex(L, -1); const auto process_metatable = [L, key, targetMetatableIndex](int candidateMtIndex) { push_class_or_const_table(L, candidateMtIndex); // Stack: ..., candidate_ct | nil if (! lua_istable(L, -1)) { lua_pop(L, 1); return false; } lua_pushvalue(L, 2); // Stack: ..., candidate_ct, field name lua_rawget(L, -2); // Stack: ..., candidate_ct, field | nil if (lua_isnil(L, -1)) { lua_pop(L, 2); // Stack: ... return false; } lua_pushvalue(L, candidateMtIndex); lua_replace(L, targetMetatableIndex); if (! lua_iscfunction(L, -1)) { lua_pop(L, 2); // Stack: ... return true; } const Options options = get_class_options(L, -2); if (! options.test(allowOverridingMethods)) raise_lua_error(L, "immutable member '%s'", key); rawset_super_method(L, -2, key); // Stack: ..., candidate_ct lua_pop(L, 1); // Stack: ... return true; }; (void) process_metatable(rootMetatableIndex); lua_rawgetp_x(L, rootMetatableIndex, getParentKey()); // Stack: mt, target mt, parent list | nil if (lua_istable(L, -1)) { const int parentListIndex = lua_absindex(L, -1); const int parentCount = get_length(L, parentListIndex); for (int i = 1; i <= parentCount; ++i) { lua_rawgeti(L, parentListIndex, i); // Stack: mt, target mt, parent list, parent mt if (! lua_istable(L, -1)) { lua_pop(L, 1); continue; } // Preserve old traversal-end behavior when no member exists in the chain. lua_pushvalue(L, -1); lua_replace(L, targetMetatableIndex); if (process_metatable(lua_absindex(L, -1))) { lua_pop(L, 1); // Stack: mt, target mt, parent list break; } lua_pop(L, 1); // Stack: mt, target mt, parent list } } lua_pop(L, 1); // Stack: mt, target mt push_class_or_const_table(L, targetMetatableIndex); // Stack: mt, target mt, ct | nil if (! lua_istable(L, -1)) // Stack: mt, target mt, nil { lua_pop(L, 2); // Stack: mt return std::nullopt; } lua_getmetatable(L, -1); // Stack: mt, target mt, ct, ct_mt lua_pushvalue(L, 3); // Stack: mt, target mt, ct, ct_mt, arg3 rawsetfield(L, -2, key); // Stack: mt, target mt, ct, ct_mt lua_pop(L, 3); // Stack: mt return 0; } template inline std::optional try_call_parent_newindex(lua_State* L) { LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: mt lua_rawgetp_x(L, -1, getParentKey()); // Stack: mt, parent list | nil if (! lua_istable(L, -1)) { lua_pop(L, 1); // Stack: mt return std::nullopt; } const int parentListIndex = lua_absindex(L, -1); const int parentCount = get_length(L, parentListIndex); for (int i = 1; i <= parentCount; ++i) { lua_rawgeti(L, parentListIndex, i); // Stack: mt, parent list, parent mt if (! lua_istable(L, -1)) { lua_pop(L, 1); continue; } lua_rawgetp_x(L, -1, getPropsetKey()); // Stack: mt, parent list, parent mt, ps | nil if (lua_istable(L, -1)) { lua_pushvalue(L, 2); // Stack: mt, parent list, parent mt, ps, field name lua_rawget(L, -2); // Stack: mt, parent list, parent mt, ps, setter | nil lua_remove(L, -2); // Stack: mt, parent list, parent mt, setter | nil if (lua_iscfunction(L, -1)) { lua_remove(L, -2); // Stack: mt, parent list, setter lua_remove(L, -2); // Stack: mt, setter lua_remove(L, -2); // Stack: setter if constexpr (IsObject) lua_pushvalue(L, 1); // Stack: setter, table | userdata lua_pushvalue(L, 3); // Stack: setter, table | userdata, new value lua_call(L, IsObject ? 2 : 1, 0); // Stack: - return 0; } lua_pop(L, 1); // Stack: mt, parent list, parent mt } else { lua_pop(L, 1); // Stack: mt, parent list, parent mt } lua_rawgetp_x(L, -1, getNewIndexFallbackKey()); // Stack: mt, parent list, parent mt, nifb | nil if (lua_iscfunction(L, -1)) { lua_pushvalue(L, 1); // Stack: mt, parent list, parent mt, nifb, arg1 lua_pushvalue(L, 2); // Stack: mt, parent list, parent mt, nifb, arg1, arg2 lua_pushvalue(L, 3); // Stack: mt, parent list, parent mt, nifb, arg1, arg2, arg3 lua_call(L, 3, 0); // Stack: mt, parent list, parent mt lua_pop(L, 3); // Stack: - return 0; } lua_pop(L, 2); // Stack: mt, parent list } lua_pop(L, 1); // Stack: mt return std::nullopt; } template inline int newindex_metamethod(lua_State* L) { #if LUABRIDGE_SAFE_STACK_CHECKS luaL_checkstack(L, 6, detail::error_lua_stack_overflow); #endif LUABRIDGE_ASSERT(lua_istable(L, 1) || lua_isuserdata(L, 1)); // Stack (further not shown): table | userdata, name, new value lua_getmetatable(L, 1); // Stack: metatable (mt) LUABRIDGE_ASSERT(lua_istable(L, -1)); const char* key = lua_tostring(L, 2); const Options options = get_class_options(L, -1); // Try in the property set table on the current class first. lua_rawgetp_x(L, -1, getPropsetKey()); // Stack: mt, propset table (ps) | nil if (! lua_istable(L, -1)) raise_lua_error(L, "no member named '%s'", key); lua_pushvalue(L, 2); // Stack: mt, ps, field name lua_rawget(L, -2); // Stack: mt, ps, setter | nil lua_remove(L, -2); // Stack: mt, setter | nil if (lua_iscfunction(L, -1)) // Stack: mt, setter { lua_remove(L, -2); // Stack: setter if constexpr (IsObject) lua_pushvalue(L, 1); // Stack: setter, table | userdata lua_pushvalue(L, 3); // Stack: setter, table | userdata, new value lua_call(L, IsObject ? 2 : 1, 0); // Stack: - return 0; } lua_pop(L, 1); // Stack: mt if constexpr (IsObject) { if (auto result = try_call_newindex_fallback(L)) return *result; } else { if (auto result = try_call_static_newindex_fallback(L)) return *result; if (options.test(extensibleClass)) { // For static extensible writes of plain values, store directly on the class table. // Function values still go through try_call_newindex_extensible to preserve super_* wiring. if (! lua_isfunction(L, 3)) { lua_pushvalue(L, 3); rawsetfield(L, 1, key); return 0; } if (auto result = try_call_newindex_extensible(L,key)) return *result; } } // Try in the propget key lua_rawgetp_x(L, -1, getPropsetKey()); // Stack: mt, propset table (ps) if (lua_istable(L, -1)) { lua_pushvalue(L, 2); // Stack: mt, ps, field name lua_rawget(L, -2); // Stack: mt, ps, setter | nil lua_remove(L, -2); // Stack: mt, setter | nil if (lua_iscfunction(L, -1)) // Stack: mt, setter { lua_remove(L, -2); // Stack: setter if constexpr (IsObject) lua_pushvalue(L, 1); // Stack: setter, table | userdata lua_pushvalue(L, 3); // Stack: setter, table | userdata, new value lua_call(L, IsObject ? 2 : 1, 0); // Stack: - return 0; } } lua_pop(L, 1); // Stack: mt if (auto result = try_call_parent_newindex(L)) return *result; if constexpr (IsObject) { // Parent metamethods should win; extensible storage is the final fallback. if (options.test(extensibleClass)) { if (auto result = try_call_newindex_extensible(L, key)) return *result; } } lua_pop(L, 1); // Stack: - raise_lua_error(L, "no writable member '%s'", key); return 0; } template inline int newindex_metamethod_simple(lua_State* L) { #if LUABRIDGE_SAFE_STACK_CHECKS luaL_checkstack(L, 3, detail::error_lua_stack_overflow); #endif LUABRIDGE_ASSERT(lua_istable(L, 1) || lua_isuserdata(L, 1)); // Fast path for simple userdata objects: propset table is captured as upvalue. if constexpr (IsObject) { if (lua_isuserdata(L, 1)) { const char* key = lua_tostring(L, 2); if (! lua_istable(L, lua_upvalueindex(1))) raise_lua_error(L, "no writable member '%s'", key); lua_pushvalue(L, 2); // Stack: key lua_rawget(L, lua_upvalueindex(1)); // Stack: setter | nil if (lua_iscfunction(L, -1)) { lua_pushvalue(L, 1); // Stack: setter, self lua_pushvalue(L, 3); // Stack: setter, self, value lua_call(L, 2, 0); return 0; } luaL_error(L, "no writable member '%s'", key); } } else { // Fast path for simple static tables: propset table is captured as upvalue. if (lua_istable(L, 1)) { const char* key = lua_tostring(L, 2); if (! lua_istable(L, lua_upvalueindex(1))) raise_lua_error(L, "no writable member '%s'", key); lua_pushvalue(L, 2); // Stack: key lua_rawget(L, lua_upvalueindex(1)); // Stack: setter | nil if (lua_iscfunction(L, -1)) { lua_pushvalue(L, 3); // Stack: setter, value lua_call(L, 1, 0); return 0; } raise_lua_error(L, "no writable member '%s'", key); } } lua_getmetatable(L, 1); // Stack: mt LUABRIDGE_ASSERT(lua_istable(L, -1)); const char* key = lua_tostring(L, 2); lua_rawgetp_x(L, -1, getPropsetKey()); // Stack: mt, ps | nil if (! lua_istable(L, -1)) raise_lua_error(L, "no member named '%s'", key); lua_pushvalue(L, 2); // Stack: mt, ps, key lua_rawget(L, -2); // Stack: mt, ps, setter | nil lua_remove(L, -2); // Stack: mt, setter | nil if (lua_iscfunction(L, -1)) { lua_remove(L, -2); // Stack: setter if constexpr (IsObject) lua_pushvalue(L, 1); // Stack: setter, self lua_pushvalue(L, 3); // Stack: setter, self, value lua_call(L, IsObject ? 2 : 1, 0); return 0; } raise_lua_error(L, "no writable member '%s'", key); return 0; } //================================================================================================= /** * @brief lua_CFunction to report an error writing to a read-only value. * * The name of the variable is in the first upvalue. */ inline int read_only_error(lua_State* L) { raise_lua_error(L, "'%s' is read-only", lua_tostring(L, lua_upvalueindex(1))); return 0; } //================================================================================================= /** * @brief __tostring metamethod for a class. */ template int tostring_metamethod(lua_State* L) { const void* ptr = lua_topointer(L, 1); lua_getmetatable(L, -1); // Stack: metatable (mt) lua_rawgetp_x(L, -1, getTypeKey()); // Stack: mt, classname (cn) lua_remove(L, -2); // Stack: cn std::stringstream ss; ss << ": 0x" << std::hex << reinterpret_cast(const_cast(ptr)); lua_pushstring(L, ss.str().c_str()); // Stack: cn, address string (astr) lua_concat(L, 2); // Stack: astr return 1; } //================================================================================================= /** * @brief __destruct metamethod for a class. */ template int destruct_metamethod(lua_State* L) { LUABRIDGE_ASSERT(lua_isuserdata(L, 1)); // Stack: userdata (ud) const auto top = lua_gettop(L); const int result = lua_getmetatable(L, 1); // Stack: ud, object metatable (ot) | nothing if (result == 0) return 0; LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: ud, ot lua_rawgetp_x(L, LUA_REGISTRYINDEX, detail::getClassRegistryKey()); // Stack: ud, ot, registry metatable (rt) | nil if (lua_istable(L, -1)) // Stack: ud, ot, rt { rawgetfield(L, -1, "__destruct"); // Stack: ud, ot, rt, ud, function | nil if (lua_isfunction(L, -1)) { lua_pushvalue(L, 1); // Stack: ud, ot, rt, function, ud lua_pcall(L, 1, 0, 0); // Stack: ud, ot, rt } } lua_settop(L, top); // Stack: ud return 0; } //================================================================================================= /** * @brief __gc metamethod for a class. */ template int gc_metamethod(lua_State* L) { destruct_metamethod(L); Userdata* ud = Userdata::getExact(L, 1); LUABRIDGE_ASSERT(ud); ud->~Userdata(); return 0; } //================================================================================================= template struct property_getter; /** * @brief lua_CFunction to get a variable. * * This is used for global variables or class static data members. The pointer to the data is in the first upvalue. */ template struct property_getter { static int call(lua_State* L) { LUABRIDGE_ASSERT(lua_islightuserdata(L, lua_upvalueindex(1))); T* ptr = static_cast(lua_touserdata(L, lua_upvalueindex(1))); LUABRIDGE_ASSERT(ptr != nullptr); auto result = Stack::push(L, *ptr); if (! result) raise_lua_error(L, "%s", result.error_cstr()); return 1; } }; /** * @brief lua_CFunction to get a class data member. * * The pointer-to-member is in the first upvalue. The class userdata object is at the top of the Lua stack. */ template struct property_getter { static int call(lua_State* L) { auto c = Userdata::get(L, 1, true); if (! c) raise_lua_error(L, "%s", c.error_cstr()); T C::** mp = static_cast(lua_touserdata(L, lua_upvalueindex(1))); Result result; #if LUABRIDGE_HAS_EXCEPTIONS try { #endif result = Stack::push(L, (*c)->**mp); #if LUABRIDGE_HAS_EXCEPTIONS } catch (const std::exception& e) { raise_lua_error(L, "%s", e.what()); } #endif if (! result) raise_lua_error(L, "%s", result.error_cstr()); return 1; } }; /** * @brief Helper function to push a property getter on a table at a specific index. */ inline void add_property_getter(lua_State* L, const char* name, int tableIndex) { #if LUABRIDGE_SAFE_STACK_CHECKS luaL_checkstack(L, 2, detail::error_lua_stack_overflow); #endif LUABRIDGE_ASSERT(name != nullptr); LUABRIDGE_ASSERT(lua_istable(L, tableIndex)); LUABRIDGE_ASSERT(lua_iscfunction(L, -1)); // Stack: getter lua_rawgetp_x(L, tableIndex, getPropgetKey()); // Stack: getter, propget table (pg) lua_pushvalue(L, -2); // Stack: getter, pg, getter rawsetfield(L, -2, name); // Stack: getter, pg lua_pop(L, 2); // Stack: - } //================================================================================================= template struct property_setter; /** * @brief lua_CFunction to set a variable. * * This is used for global variables or class static data members. The pointer to the data is in the first upvalue. */ template struct property_setter { static int call(lua_State* L) { LUABRIDGE_ASSERT(lua_islightuserdata(L, lua_upvalueindex(1))); T* ptr = static_cast(lua_touserdata(L, lua_upvalueindex(1))); LUABRIDGE_ASSERT(ptr != nullptr); auto result = Stack::get(L, 1); if (! result) raise_lua_error(L, "%s", result.error_cstr()); *ptr = std::move(*result); return 0; } }; /** * @brief lua_CFunction to set a class data member. * * The pointer-to-member is in the first upvalue. The class userdata object is at the top of the Lua stack. */ template struct property_setter { static int call(lua_State* L) { auto c = Userdata::get(L, 1, false); if (! c) raise_lua_error(L, "%s", c.error_cstr()); T C::** mp = static_cast(lua_touserdata(L, lua_upvalueindex(1))); #if LUABRIDGE_HAS_EXCEPTIONS try { #endif auto result = Stack::get(L, 2); if (! result) raise_lua_error(L, "%s", result.error_cstr()); (*c)->** mp = std::move(*result); #if LUABRIDGE_HAS_EXCEPTIONS } catch (const std::exception& e) { raise_lua_error(L, "%s", e.what()); } #endif return 0; } }; /** * @brief Helper function to push a property setter on a table at a specific index. */ inline void add_property_setter(lua_State* L, const char* name, int tableIndex) { #if LUABRIDGE_SAFE_STACK_CHECKS luaL_checkstack(L, 2, detail::error_lua_stack_overflow); #endif LUABRIDGE_ASSERT(name != nullptr); LUABRIDGE_ASSERT(lua_istable(L, tableIndex)); LUABRIDGE_ASSERT(lua_iscfunction(L, -1)); // Stack: setter lua_rawgetp_x(L, tableIndex, getPropsetKey()); // Stack: setter, propset table (ps) lua_pushvalue(L, -2); // Stack: setter, ps, setter rawsetfield(L, -2, name); // Stack: setter, ps lua_pop(L, 2); // Stack: - } //================================================================================================= /** * @brief Function generator. */ template decltype(auto) invoke_callable_from_stack(lua_State* L, F&& func) { return std::apply(std::forward(func), make_arguments_list(L)); } template decltype(auto) invoke_member_callable_from_stack(lua_State* L, T* ptr, F&& func) { return std::apply( std::forward(func), std::tuple_cat(std::tuple(ptr), make_arguments_list(L))); } template struct function { template static int call(lua_State* L, F&& func) { Result result; int numResults = 1; #if LUABRIDGE_HAS_EXCEPTIONS try { #endif if constexpr (detail::is_tuple_v) { numResults = static_cast(std::tuple_size_v); result = detail::push_tuple(L, invoke_callable_from_stack(L, std::forward(func))); } else { result = Stack::push(L, invoke_callable_from_stack(L, std::forward(func))); } #if LUABRIDGE_HAS_EXCEPTIONS } catch (const std::exception& e) { raise_lua_error(L, "%s", e.what()); } #endif if (! result) raise_lua_error(L, "%s", result.error_cstr()); return numResults; } template static int call(lua_State* L, T* ptr, F&& func) { Result result; int numResults = 1; #if LUABRIDGE_HAS_EXCEPTIONS try { #endif if constexpr (detail::is_tuple_v) { numResults = static_cast(std::tuple_size_v); result = detail::push_tuple(L, invoke_member_callable_from_stack(L, ptr, std::forward(func))); } else { result = Stack::push(L, invoke_member_callable_from_stack(L, ptr, std::forward(func))); } #if LUABRIDGE_HAS_EXCEPTIONS } catch (const std::exception& e) { raise_lua_error(L, "%s", e.what()); } #endif if (! result) raise_lua_error(L, "%s", result.error_cstr()); return numResults; } }; template struct function, ArgsPack, Start> : function { template static int call(lua_State* L, F&& func) { #if LUABRIDGE_HAS_EXCEPTIONS try { #endif invoke_callable_from_stack(L, std::forward(func)); #if LUABRIDGE_HAS_EXCEPTIONS } catch (const std::exception& e) { raise_lua_error(L, "%s", e.what()); } #endif return 0; } template static int call(lua_State* L, T* ptr, F&& func) { #if LUABRIDGE_HAS_EXCEPTIONS try { #endif invoke_member_callable_from_stack(L, ptr, std::forward(func)); #if LUABRIDGE_HAS_EXCEPTIONS } catch (const std::exception& e) { raise_lua_error(L, "%s", e.what()); } #endif return 0; } }; template struct function { template static int call(lua_State* L, F&& func) { #if LUABRIDGE_HAS_EXCEPTIONS try { #endif invoke_callable_from_stack(L, std::forward(func)); #if LUABRIDGE_HAS_EXCEPTIONS } catch (const std::exception& e) { raise_lua_error(L, "%s", e.what()); } #endif return 0; } template static int call(lua_State* L, T* ptr, F&& func) { #if LUABRIDGE_HAS_EXCEPTIONS try { #endif invoke_member_callable_from_stack(L, ptr, std::forward(func)); #if LUABRIDGE_HAS_EXCEPTIONS } catch (const std::exception& e) { raise_lua_error(L, "%s", e.what()); } #endif return 0; } }; template TypeResult get_member_object(lua_State* L, bool canBeConst) { const int selfIndex = lua_absindex(L, 1); if (lua_getmetatable(L, selfIndex)) { if (lua_type(L, lua_upvalueindex(2)) == LUA_TTABLE && lua_rawequal(L, -1, lua_upvalueindex(2))) { lua_pop(L, 1); return Userdata::getExactPointer(L, selfIndex); } if (canBeConst && lua_type(L, lua_upvalueindex(3)) == LUA_TTABLE && lua_rawequal(L, -1, lua_upvalueindex(3))) { lua_pop(L, 1); return Userdata::getExactPointer(L, selfIndex); } lua_pop(L, 1); } return Userdata::get(L, selfIndex, canBeConst); } //================================================================================================= /** * @brief lua_CFunction to call a class member function with a return value. * * The member function pointer is in the first upvalue. The class userdata object is at the top of the Lua stack. */ template int invoke_member_function(lua_State* L) { using FnTraits = function_traits; LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(1))); auto ptr = get_member_object(L, false); if (! ptr) raise_lua_error(L, "%s", ptr.error_cstr()); const F& func = *static_cast(lua_touserdata(L, lua_upvalueindex(1))); LUABRIDGE_ASSERT(func != nullptr); return function::call(L, *ptr, func); } template int invoke_const_member_function(lua_State* L) { using FnTraits = function_traits; LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(1))); auto ptr = get_member_object(L, true); if (! ptr) raise_lua_error(L, "%s", ptr.error_cstr()); const F& func = *static_cast(lua_touserdata(L, lua_upvalueindex(1))); LUABRIDGE_ASSERT(func != nullptr); return function::call(L, *ptr, func); } //================================================================================================= /** * @brief lua_CFunction to call a class member lua_CFunction. * * The member function pointer is in the first upvalue. The object userdata ('this') value is at top ot the Lua stack. */ template int invoke_member_cfunction(lua_State* L) { using F = int (T::*)(lua_State* L); LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(1))); auto t = Userdata::get(L, 1, false); if (! t) raise_lua_error(L, "%s", t.error_cstr()); const F& func = *static_cast(lua_touserdata(L, lua_upvalueindex(1))); LUABRIDGE_ASSERT(func != nullptr); #if LUABRIDGE_HAS_EXCEPTIONS try { #endif return ((*t)->*func)(L); #if LUABRIDGE_HAS_EXCEPTIONS } catch (const std::exception& e) { raise_lua_error(L, "%s", e.what()); } return 0; #endif } template int invoke_const_member_cfunction(lua_State* L) { using F = int (T::*)(lua_State * L) const; LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(1))); auto t = Userdata::get(L, 1, true); if (! t) raise_lua_error(L, "%s", t.error_cstr()); const F& func = *static_cast(lua_touserdata(L, lua_upvalueindex(1))); LUABRIDGE_ASSERT(func != nullptr); #if LUABRIDGE_HAS_EXCEPTIONS try { #endif return ((*t)->*func)(L); #if LUABRIDGE_HAS_EXCEPTIONS } catch (const std::exception& e) { raise_lua_error(L, "%s", e.what()); } return 0; #endif } //================================================================================================= /** * @brief lua_CFunction to call on a object via function pointer. * * The proxy function pointer (lightuserdata) is in the first upvalue. The class userdata object is at the top of the Lua stack. */ template int invoke_proxy_function(lua_State* L) { using FnTraits = function_traits; LUABRIDGE_ASSERT(lua_islightuserdata(L, lua_upvalueindex(1))); auto func = reinterpret_cast(lua_touserdata(L, lua_upvalueindex(1))); LUABRIDGE_ASSERT(func != nullptr); return function::call(L, func); } //================================================================================================= /** * @brief lua_CFunction to call on a object via functor (lambda wrapped in a std::function). * * The proxy std::function (lightuserdata) is in the first upvalue. The class userdata object is at the top of the Lua stack. */ template int invoke_proxy_functor(lua_State* L) { using FnTraits = function_traits>; LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(1))); auto& func = *align>(lua_touserdata(L, lua_upvalueindex(1))); return function::call(L, func); } //================================================================================================= /** * @brief lua_CFunction to call safely by trapping exceptions */ #if LUABRIDGE_SAFE_LUA_C_EXCEPTION_HANDLING && LUABRIDGE_HAS_EXCEPTIONS inline int invoke_safe_cfunction(lua_State* L) { LUABRIDGE_ASSERT(lua_iscfunction(L, lua_upvalueindex(1))); auto func = lua_tocfunction(L, lua_upvalueindex(1)); try { return func(L); } catch (const std::exception& e) { raise_lua_error(L, "%s", e.what()); } return 0; } #endif //================================================================================================= /** * @brief lua_CFunction to call on a object constructor via functor (lambda wrapped in a std::function). * * The proxy std::function (lightuserdata) is in the first upvalue. The class userdata object will be pushed at the top of the Lua stack. */ template int invoke_proxy_constructor(lua_State* L) { using FnTraits = function_traits; LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(1))); auto& func = *align(lua_touserdata(L, lua_upvalueindex(1))); function::call(L, func); return 1; } //================================================================================================= /** * @brief lua_CFunction to call on a object constructor via functor (lambda wrapped in a std::function). * * The proxy std::function (lightuserdata) is in the first upvalue. The class userdata object will be pushed at the top of the Lua stack. */ template int invoke_proxy_destructor(lua_State* L) { using FnTraits = function_traits; LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(1))); auto& func = *align(lua_touserdata(L, lua_upvalueindex(1))); function::call(L, func); return 1; } //================================================================================================= /** * @brief C++ storage for a single overload entry: arity and optional type checker. * * Stored inside an OverloadSet which is kept as a Lua full userdata (auto-GC'd). */ struct OverloadEntry { using TypeChecker = bool (*)(lua_State*, int start); int arity; // -1 for variadic (lua_CFunction): always attempt TypeChecker checker; // nullptr for variadic: skip type pre-checking }; /** * @brief C++ storage for all overloads of a function. * * Stored as a Lua full userdata so it is GC'd automatically when the closure is collected. * The actual function closures are stored separately in a flat Lua table (upvalue 2). */ struct OverloadSet { std::vector entries; }; /** * @brief Check a single argument type, skipping lua_State* (auto-injected, not on the Lua stack). */ template bool overload_check_one_arg(lua_State* L, int& idx) { if constexpr (std::is_pointer_v && std::is_same_v>, lua_State>) { return true; // lua_State* is auto-injected by LuaBridge, not a Lua-visible argument } else { return Stack::isInstance(L, idx++); } } template bool overload_check_args_impl(lua_State* L, int start, std::index_sequence) { [[maybe_unused]] int idx = start; return (overload_check_one_arg>(L, idx) && ...); } template bool overload_check_args(lua_State* L, int start) { return overload_check_args_impl(L, start, std::make_index_sequence>{}); } /** * @brief Type checker instantiable as an OverloadEntry::TypeChecker function pointer. * * Checks that the Lua stack arguments starting at @p start match the types in @tparam ArgsPack, * using Stack::isInstance without raising errors. lua_State* arguments are skipped. */ template bool overload_type_checker(lua_State* L, int start) { return overload_check_args(L, start); } //================================================================================================= /** * @brief lua_CFunction to resolve an invocation between several overloads. * * upvalue[1] = OverloadSet full userdata — C++ vector of {arity, type_checker} per overload. * upvalue[2] = flat Lua table {[1]=func1, [2]=func2, ...} — the actual function closures. * * Dispatch: * 1. Arity check in C++ (no Lua call). * 2. Type check via Stack::isInstance in C++ (no pcall) — skips clearly mismatched overloads. * 3. Only calls lua_pcall for type-matched candidates, eliminating failed pcalls for type mismatches. */ template inline int try_overload_functions(lua_State* L) { const int nargs = lua_gettop(L); const int effective_args = nargs - (Member ? 1 : 0); const int start_arg = Member ? 2 : 1; LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(1))); auto* overload_set = align(lua_touserdata(L, lua_upvalueindex(1))); // push flat functions table (upvalue 2) lua_pushvalue(L, lua_upvalueindex(2)); LUABRIDGE_ASSERT(lua_istable(L, -1)); const int idx_funcs = nargs + 1; // create table to hold error messages lua_createtable(L, static_cast(overload_set->entries.size()), 0); const int idx_errors = nargs + 2; int nerrors = 0; for (int i = 0; i < static_cast(overload_set->entries.size()); ++i) { const auto& entry = overload_set->entries[i]; // fast arity check (C++, no Lua calls) if (entry.arity >= 0 && entry.arity != effective_args) { lua_pushfstring(L, "Skipped overload #%d with unmatched arity of %d instead of %d", i, entry.arity, effective_args); lua_rawseti(L, idx_errors, ++nerrors); continue; } // fast type check (C++, no pcall) — avoids expensive pcall for clearly mismatched types if (entry.checker != nullptr && !entry.checker(L, start_arg)) { lua_pushfstring(L, "Skipped overload #%d with unmatched argument types", i); lua_rawseti(L, idx_errors, ++nerrors); continue; } // O(1) function lookup from flat table lua_rawgeti(L, idx_funcs, i + 1); LUABRIDGE_ASSERT(lua_isfunction(L, -1)); // push arguments for (int j = 1; j <= nargs; ++j) lua_pushvalue(L, j); // call f, this pops the function and its args, pushes result(s) const int err = lua_pcall(L, nargs, LUA_MULTRET, 0); if (err == LUABRIDGE_LUA_OK) { // 2 extra items on stack below results: idx_funcs, idx_errors return lua_gettop(L) - nargs - 2; } else if (err == LUA_ERRRUN) { // store error message and try next overload lua_rawseti(L, idx_errors, ++nerrors); } else { lua_error_x(L); // critical error: rethrow } } lua_Debug debug; lua_getstack_info_x(L, 0, "n", &debug); lua_pushfstring(L, "All %d overloads of %s returned an error:", nerrors, debug.name); // Concatenate error messages of each overload for (int i = 1; i <= nerrors; ++i) { lua_pushfstring(L, "\n%d: ", i); lua_rawgeti(L, idx_errors, i); } lua_concat(L, nerrors * 2 + 1); const char* message = lua_tostring(L, -1); raise_lua_error(L, "%s", message ? message : ""); } //================================================================================================= // Lua CFunction inline void push_function(lua_State* L, lua_CFunction fp, const char* debugname) { #if LUABRIDGE_SAFE_LUA_C_EXCEPTION_HANDLING && LUABRIDGE_HAS_EXCEPTIONS lua_pushcfunction_x(L, fp, debugname); lua_pushcclosure_x(L, invoke_safe_cfunction, debugname, 1); #else lua_pushcfunction_x(L, fp, debugname); #endif } // Generic function pointer template inline void push_function(lua_State* L, ReturnType (*fp)(Params...), const char* debugname) { using FnType = decltype(fp); lua_pushlightuserdata(L, reinterpret_cast(fp)); lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); } template inline void push_function(lua_State* L, ReturnType (*fp)(Params...) noexcept, const char* debugname) { using FnType = decltype(fp); lua_pushlightuserdata(L, reinterpret_cast(fp)); lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); } // Callable object (lambdas) template && !std::is_pointer_v && !std::is_member_function_pointer_v>> inline void push_function(lua_State* L, F&& f, const char* debugname) { lua_newuserdata_aligned(L, std::forward(f)); lua_pushcclosure_x(L, &invoke_proxy_functor, debugname, 1); } //================================================================================================= // Lua CFunction template void push_member_function(lua_State* L, lua_CFunction fp, const char* debugname) { #if LUABRIDGE_SAFE_LUA_C_EXCEPTION_HANDLING && LUABRIDGE_HAS_EXCEPTIONS lua_pushcfunction_x(L, fp, debugname); lua_pushcclosure_x(L, invoke_safe_cfunction, debugname, 1); #else lua_pushcfunction_x(L, fp, debugname); #endif } // Generic function pointer template void push_member_function(lua_State* L, ReturnType (*fp)(T*, Params...), const char* debugname) { using FnType = decltype(fp); lua_pushlightuserdata(L, reinterpret_cast(fp)); lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); } template void push_member_function(lua_State* L, ReturnType (*fp)(T&, Params...), const char* debugname) { using FnType = decltype(fp); lua_pushlightuserdata(L, reinterpret_cast(fp)); lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); } template void push_member_function(lua_State* L, ReturnType (*fp)(T*, Params...) noexcept, const char* debugname) { using FnType = decltype(fp); lua_pushlightuserdata(L, reinterpret_cast(fp)); lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); } template void push_member_function(lua_State* L, ReturnType (*fp)(T&, Params...) noexcept, const char* debugname) { using FnType = decltype(fp); lua_pushlightuserdata(L, reinterpret_cast(fp)); lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); } template void push_member_function(lua_State* L, ReturnType (*fp)(const T*, Params...), const char* debugname) { using FnType = decltype(fp); lua_pushlightuserdata(L, reinterpret_cast(fp)); lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); } template void push_member_function(lua_State* L, ReturnType (*fp)(const T&, Params...), const char* debugname) { using FnType = decltype(fp); lua_pushlightuserdata(L, reinterpret_cast(fp)); lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); } template void push_member_function(lua_State* L, ReturnType (*fp)(const T*, Params...) noexcept, const char* debugname) { using FnType = decltype(fp); lua_pushlightuserdata(L, reinterpret_cast(fp)); lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); } template void push_member_function(lua_State* L, ReturnType (*fp)(const T&, Params...) noexcept, const char* debugname) { using FnType = decltype(fp); lua_pushlightuserdata(L, reinterpret_cast(fp)); lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); } // Callable object (lambdas) template && std::is_object_v && !std::is_pointer_v && !std::is_member_function_pointer_v>> void push_member_function(lua_State* L, F&& f, const char* debugname) { static_assert(std::is_same_v>>>); lua_newuserdata_aligned(L, std::forward(f)); lua_pushcclosure_x(L, &invoke_proxy_functor, debugname, 1); } // Non const member function pointer template void push_member_function(lua_State* L, ReturnType (U::*mfp)(Params...), const char* debugname) { static_assert(std::is_same_v || std::is_base_of_v); using F = decltype(mfp); new (lua_newuserdata_x(L, sizeof(F))) F(mfp); lua_rawgetp_x(L, LUA_REGISTRYINDEX, detail::getClassRegistryKey()); lua_pushcclosure_x(L, &invoke_member_function, debugname, 2); } template void push_member_function(lua_State* L, ReturnType (U::*mfp)(Params...) noexcept, const char* debugname) { static_assert(std::is_same_v || std::is_base_of_v); using F = decltype(mfp); new (lua_newuserdata_x(L, sizeof(F))) F(mfp); lua_rawgetp_x(L, LUA_REGISTRYINDEX, detail::getClassRegistryKey()); lua_pushcclosure_x(L, &invoke_member_function, debugname, 2); } // Const member function pointer template void push_member_function(lua_State* L, ReturnType (U::*mfp)(Params...) const, const char* debugname) { static_assert(std::is_same_v || std::is_base_of_v); using F = decltype(mfp); new (lua_newuserdata_x(L, sizeof(F))) F(mfp); lua_rawgetp_x(L, LUA_REGISTRYINDEX, detail::getClassRegistryKey()); lua_rawgetp_x(L, LUA_REGISTRYINDEX, detail::getConstRegistryKey()); lua_pushcclosure_x(L, &detail::invoke_const_member_function, debugname, 3); } template void push_member_function(lua_State* L, ReturnType (U::*mfp)(Params...) const noexcept, const char* debugname) { static_assert(std::is_same_v || std::is_base_of_v); using F = decltype(mfp); new (lua_newuserdata_x(L, sizeof(F))) F(mfp); lua_rawgetp_x(L, LUA_REGISTRYINDEX, detail::getClassRegistryKey()); lua_rawgetp_x(L, LUA_REGISTRYINDEX, detail::getConstRegistryKey()); lua_pushcclosure_x(L, &detail::invoke_const_member_function, debugname, 3); } // Non const member Lua CFunction pointer template void push_member_function(lua_State* L, int (U::*mfp)(lua_State*), const char* debugname) { static_assert(std::is_same_v || std::is_base_of_v); using F = decltype(mfp); new (lua_newuserdata_x(L, sizeof(F))) F(mfp); lua_pushcclosure_x(L, &invoke_member_cfunction, debugname, 1); } // Const member Lua CFunction pointer template void push_member_function(lua_State* L, int (U::*mfp)(lua_State*) const, const char* debugname) { static_assert(std::is_same_v || std::is_base_of_v); using F = decltype(mfp); new (lua_newuserdata_x(L, sizeof(F))) F(mfp); lua_pushcclosure_x(L, &invoke_const_member_cfunction, debugname, 1); } //================================================================================================= /** * @brief */ template || !detail::is_callable_v)>> void push_property_getter(lua_State* L, const T* value, const char* debugname) { lua_pushlightuserdata(L, reinterpret_cast(const_cast(value))); lua_pushcclosure_x(L, &property_getter::call, debugname, 1); } template void push_property_getter(lua_State* L, T (*getter)(), const char* debugname) { lua_pushlightuserdata(L, reinterpret_cast(getter)); lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); } template void push_property_getter(lua_State* L, T (*getter)() noexcept, const char* debugname) { lua_pushlightuserdata(L, reinterpret_cast(getter)); lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); } template >> void push_property_getter(lua_State* L, T getter, const char* debugname) { if constexpr (std::is_same_v) { lua_pushcfunction_x(L, getter, debugname); } else { lua_newuserdata_aligned(L, std::move(getter)); lua_pushcclosure_x(L, &invoke_proxy_functor, debugname, 1); } } //================================================================================================= /** * @brief */ template void push_class_property_getter(lua_State* L, T (U::*value), const char* debugname) { using MemberValue = decltype(value); new (lua_newuserdata_x(L, sizeof(MemberValue))) MemberValue(value); lua_pushcclosure_x(L, &property_getter::call, debugname, 1); } template void push_class_property_getter(lua_State* L, T (C::*getter)() const, const char* debugname) { using GetType = decltype(getter); new (lua_newuserdata_x(L, sizeof(GetType))) GetType(getter); lua_pushcclosure_x(L, &invoke_const_member_function, debugname, 1); } template void push_class_property_getter(lua_State* L, T (C::*getter)() const noexcept, const char* debugname) { using GetType = decltype(getter); new (lua_newuserdata_x(L, sizeof(GetType))) GetType(getter); lua_pushcclosure_x(L, &invoke_const_member_function, debugname, 1); } template void push_class_property_getter(lua_State* L, T (C::*getter)(lua_State*) const, const char* debugname) { using GetType = decltype(getter); new (lua_newuserdata_x(L, sizeof(GetType))) GetType(getter); lua_pushcclosure_x(L, &invoke_const_member_function, debugname, 1); } template void push_class_property_getter(lua_State* L, T (C::*getter)(lua_State*) const noexcept, const char* debugname) { using GetType = decltype(getter); new (lua_newuserdata_x(L, sizeof(GetType))) GetType(getter); lua_pushcclosure_x(L, &invoke_const_member_function, debugname, 1); } template void push_class_property_getter(lua_State* L, T (*getter)(const C*), const char* debugname) { lua_pushlightuserdata(L, reinterpret_cast(getter)); lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); } template void push_class_property_getter(lua_State* L, T (*getter)(const C&), const char* debugname) { lua_pushlightuserdata(L, reinterpret_cast(getter)); lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); } template void push_class_property_getter(lua_State* L, T (*getter)(const C*, lua_State*), const char* debugname) { lua_pushlightuserdata(L, reinterpret_cast(getter)); lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); } template void push_class_property_getter(lua_State* L, T (*getter)(const C&, lua_State*), const char* debugname) { lua_pushlightuserdata(L, reinterpret_cast(getter)); lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); } template void push_class_property_getter(lua_State* L, T (*getter)(const C*) noexcept, const char* debugname) { lua_pushlightuserdata(L, reinterpret_cast(getter)); lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); } template void push_class_property_getter(lua_State* L, T (*getter)(const C&) noexcept, const char* debugname) { lua_pushlightuserdata(L, reinterpret_cast(getter)); lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); } template void push_class_property_getter(lua_State* L, T (*getter)(const C*, lua_State*) noexcept, const char* debugname) { lua_pushlightuserdata(L, reinterpret_cast(getter)); lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); } template void push_class_property_getter(lua_State* L, T (*getter)(const C&, lua_State*) noexcept, const char* debugname) { lua_pushlightuserdata(L, reinterpret_cast(getter)); lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); } template void push_class_property_getter(lua_State* L, lua_CFunction getter, const char* debugname) { lua_pushcfunction_x(L, getter, debugname); } template && detail::is_callable_v>> void push_class_property_getter(lua_State* L, T getter, const char* debugname) { using FirstArg = detail::function_argument_t<0, T>; static_assert(std::is_same_v>>, C>); lua_newuserdata_aligned(L, std::move(getter)); lua_pushcclosure_x(L, &invoke_proxy_functor, debugname, 1); } //================================================================================================= /** * @brief */ template || !detail::is_callable_v)>> void push_property_setter(lua_State* L, T* value, const char* debugname) { lua_pushlightuserdata(L, reinterpret_cast(value)); lua_pushcclosure_x(L, &property_setter::call, debugname, 1); } template void push_property_setter(lua_State* L, void (*setter)(T), const char* debugname) { lua_pushlightuserdata(L, reinterpret_cast(setter)); lua_pushcclosure_x(L, &detail::invoke_proxy_function, debugname, 1); } template void push_property_setter(lua_State* L, void (*setter)(T) noexcept, const char* debugname) { lua_pushlightuserdata(L, reinterpret_cast(setter)); lua_pushcclosure_x(L, &detail::invoke_proxy_function, debugname, 1); } template >> void push_property_setter(lua_State* L, T setter, const char* debugname) { if constexpr (std::is_same_v) { lua_pushcfunction_x(L, setter, debugname); } else { lua_newuserdata_aligned(L, std::move(setter)); lua_pushcclosure_x(L, &invoke_proxy_functor, debugname, 1); } } //================================================================================================= /** * @brief */ template void push_class_property_setter(lua_State* L, T U::*value, const char* debugname) { using MemberValue = decltype(value); new (lua_newuserdata_x(L, sizeof(MemberValue))) MemberValue(value); lua_pushcclosure_x(L, &property_setter::call, debugname, 1); } template void push_class_property_setter(lua_State* L, void (C::*setter)(T), const char* debugname) { using SetType = decltype(setter); new (lua_newuserdata_x(L, sizeof(SetType))) SetType(setter); lua_pushcclosure_x(L, &invoke_member_function, debugname, 1); } template void push_class_property_setter(lua_State* L, void (C::*setter)(T) noexcept, const char* debugname) { using SetType = decltype(setter); new (lua_newuserdata_x(L, sizeof(SetType))) SetType(setter); lua_pushcclosure_x(L, &invoke_member_function, debugname, 1); } template void push_class_property_setter(lua_State* L, void (C::*setter)(T, lua_State*), const char* debugname) { using SetType = decltype(setter); new (lua_newuserdata_x(L, sizeof(SetType))) SetType(setter); lua_pushcclosure_x(L, &invoke_member_function, debugname, 1); } template void push_class_property_setter(lua_State* L, void (C::*setter)(T, lua_State*) noexcept, const char* debugname) { using SetType = decltype(setter); new (lua_newuserdata_x(L, sizeof(SetType))) SetType(setter); lua_pushcclosure_x(L, &invoke_member_function, debugname, 1); } template void push_class_property_setter(lua_State* L, void (*setter)(C*, T), const char* debugname) { lua_pushlightuserdata( L, reinterpret_cast(setter)); lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); } template void push_class_property_setter(lua_State* L, void (*setter)(C&, T), const char* debugname) { lua_pushlightuserdata( L, reinterpret_cast(setter)); lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); } template void push_class_property_setter(lua_State* L, void (*setter)(C*, T, lua_State*), const char* debugname) { lua_pushlightuserdata( L, reinterpret_cast(setter)); lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); } template void push_class_property_setter(lua_State* L, void (*setter)(C&, T, lua_State*), const char* debugname) { lua_pushlightuserdata( L, reinterpret_cast(setter)); lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); } template void push_class_property_setter(lua_State* L, void (*setter)(C*, T) noexcept, const char* debugname) { lua_pushlightuserdata( L, reinterpret_cast(setter)); lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); } template void push_class_property_setter(lua_State* L, void (*setter)(C&, T) noexcept, const char* debugname) { lua_pushlightuserdata( L, reinterpret_cast(setter)); lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); } template void push_class_property_setter(lua_State* L, void (*setter)(C*, T, lua_State*) noexcept, const char* debugname) { lua_pushlightuserdata( L, reinterpret_cast(setter)); lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); } template void push_class_property_setter(lua_State* L, void (*setter)(C&, T, lua_State*) noexcept, const char* debugname) { lua_pushlightuserdata( L, reinterpret_cast(setter)); lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); } template void push_class_property_setter(lua_State* L, lua_CFunction setter, const char* debugname) { lua_pushcfunction_x(L, setter, debugname); } template && detail::is_callable_v>> void push_class_property_setter(lua_State* L, T setter, const char* debugname) { using FirstArg = detail::function_argument_t<0, T>; static_assert(std::is_same_v>, C>); lua_newuserdata_aligned(L, std::move(setter)); lua_pushcclosure_x(L, &invoke_proxy_functor, debugname, 1); } //================================================================================================= /** * @brief */ inline void push_property_readonly(lua_State* L, const char* debugname) { lua_pushstring(L, debugname); lua_pushcclosure_x(L, &detail::read_only_error, debugname, 1); } //================================================================================================= /** * @brief Constructor generators. * * These templates call operator new with the contents of a type/value list passed to the constructor. Two versions of call() are provided. * One performs a regular new, the other performs a placement new. */ template struct constructor { static T* construct(const Args& args) { auto alloc = [](auto&&... args) { return new T(std::forward(args)...); }; return std::apply(alloc, args); } static T* construct(void* ptr, const Args& args) { auto alloc = [ptr](auto&&... args) { return new (ptr) T(std::forward(args)...); }; return std::apply(alloc, args); } }; //================================================================================================= /** * @brief Placement constructor generators. */ template struct placement_constructor { template static T* construct(void* ptr, F&& func, const Args& args) { auto alloc = [ptr, func = std::forward(func)](auto&&... args) { return func(ptr, std::forward(args)...); }; return std::apply(alloc, args); } }; //================================================================================================= /** * @brief Container allocator generators. */ template struct container_constructor { template static C construct(F&& func, const Args& args) { auto alloc = [func = std::forward(func)](auto&&... args) { return func(std::forward(args)...); }; return std::apply(alloc, args); } }; //================================================================================================= /** * @brief External allocator generators. */ template struct external_constructor { template static T* construct(F&& func, const Args& args) { auto alloc = [func = std::forward(func)](auto&&... args) { return func(std::forward(args)...); }; return std::apply(alloc, args); } }; //================================================================================================= /** * @brief lua_CFunction to construct a class object wrapped in a container. */ template int constructor_container_proxy(lua_State* L) { using T = typename ContainerTraits::Type; T* object = nullptr; #if LUABRIDGE_HAS_EXCEPTIONS try { #endif object = constructor::construct(make_arguments_list(L)); #if LUABRIDGE_HAS_EXCEPTIONS } catch (const std::exception& e) { raise_lua_error(L, "%s", e.what()); } #endif auto result = UserdataSharedHelper::push(L, object); if (! result) raise_lua_error(L, "%s", result.error_cstr()); return 1; } /** * @brief lua_CFunction to construct a class object in-place in the userdata. */ template int constructor_placement_proxy(lua_State* L) { auto args = make_arguments_list(L); std::error_code ec; auto* value = UserdataValue::place(L, ec); if (! value) raise_lua_error(L, "%s", detail::ErrorCategory::errorString(ec.value())); #if LUABRIDGE_HAS_EXCEPTIONS try { #endif constructor::construct(value->getObject(), std::move(args)); #if LUABRIDGE_HAS_EXCEPTIONS } catch (const std::exception& e) { raise_lua_error(L, "%s", e.what()); } #endif value->commit(); return 1; } //================================================================================================= /** * @brief Constructor forwarder. */ template struct constructor_forwarder { explicit constructor_forwarder(F f) : m_func(std::move(f)) { } T* operator()(lua_State* L) { using FnTraits = function_traits; using FnArgs = remove_first_type_t; auto args = make_arguments_list(L); std::error_code ec; auto* value = UserdataValue::place(L, ec); if (! value) raise_lua_error(L, "%s", detail::ErrorCategory::errorString(ec.value())); T* object = nullptr; #if LUABRIDGE_HAS_EXCEPTIONS try { #endif object = placement_constructor::construct(value->getObject(), m_func, std::move(args)); #if LUABRIDGE_HAS_EXCEPTIONS } catch (const std::exception& e) { raise_lua_error(L, "%s", e.what()); } #endif value->commit(); return object; } private: F m_func; }; //================================================================================================= /** * @brief Constructor forwarder. */ template struct destructor_forwarder { explicit destructor_forwarder(F f) : m_func(std::move(f)) { } void operator()(lua_State* L) { auto value = Userdata::get(L, -1, false); if (! value) raise_lua_error(L, "%s", value.error_cstr()); std::invoke(m_func, *value); } private: F m_func; }; //================================================================================================= /** * @brief Constructor forwarder. */ template struct factory_forwarder { explicit factory_forwarder(Alloc alloc, Dealloc dealloc) : m_alloc(std::move(alloc)) , m_dealloc(std::move(dealloc)) { } T* operator()(lua_State* L) { using FnTraits = function_traits; using FnArgs = typename FnTraits::argument_types; T* object = nullptr; #if LUABRIDGE_HAS_EXCEPTIONS try { #endif object = external_constructor::construct(m_alloc, make_arguments_list(L)); #if LUABRIDGE_HAS_EXCEPTIONS } catch (const std::exception& e) { raise_lua_error(L, "%s", e.what()); } #endif std::error_code ec; auto* value = UserdataValueExternal::place(L, object, m_dealloc, ec); if (! value) raise_lua_error(L, "%s", detail::ErrorCategory::errorString(ec.value())); return object; } private: Alloc m_alloc; Dealloc m_dealloc; }; //================================================================================================= /** * @brief Container forwarder. */ template struct container_forwarder { explicit container_forwarder(F f) : m_func(std::move(f)) { } C operator()(lua_State* L) { using FnTraits = function_traits; using FnArgs = typename FnTraits::argument_types; alignas(C) std::byte object_storage[sizeof(C)]; C* object = nullptr; #if LUABRIDGE_HAS_EXCEPTIONS try { #endif object = ::new (object_storage) C( container_constructor::construct(m_func, make_arguments_list(L))); #if LUABRIDGE_HAS_EXCEPTIONS } catch (const std::exception& e) { if (object != nullptr) std::destroy_at(object); raise_lua_error(L, "%s", e.what()); } #endif LUABRIDGE_ASSERT(object != nullptr); Result result; #if LUABRIDGE_HAS_EXCEPTIONS try { #endif result = UserdataSharedHelper::push(L, *object); #if LUABRIDGE_HAS_EXCEPTIONS } catch (const std::exception& e) { std::destroy_at(object); raise_lua_error(L, "%s", e.what()); } #endif if (! result) { std::destroy_at(object); raise_lua_error(L, "%s", result.error_cstr()); } C ret = std::move(*object); std::destroy_at(object); return ret; } private: F m_func; }; } // namespace detail } // namespace luabridge