From d6a93cdbab4b5fc3251a6e02a584070ad286dfbf Mon Sep 17 00:00:00 2001 From: kunitoki Date: Tue, 7 Apr 2026 12:40:54 +0200 Subject: [PATCH 01/14] Inspection first check in --- CMakeLists.txt | 4 +- Distribution/LuaBridge/LuaBridge.h | 2474 +++++++++++++++++++++-- Images/benchmarks.png | Bin 170105 -> 168214 bytes Source/CMakeLists.txt | 1 + Source/LuaBridge/Inspect.h | 1440 +++++++++++++ Source/LuaBridge/LuaBridge.h | 1 + Source/LuaBridge/detail/CFunctions.h | 6 + Source/LuaBridge/detail/FunctionHints.h | 176 ++ Source/LuaBridge/detail/LuaHelpers.h | 102 +- Source/LuaBridge/detail/Namespace.h | 239 ++- Tests/CMakeLists.txt | 5 + Tests/Source/InspectTests.cpp | 565 ++++++ amalgamate.py | 8 +- justfile | 3 +- 14 files changed, 4769 insertions(+), 255 deletions(-) create mode 100644 Source/LuaBridge/Inspect.h create mode 100644 Source/LuaBridge/detail/FunctionHints.h create mode 100644 Tests/Source/InspectTests.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a79065db..1cf6acc8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,13 +13,13 @@ find_program (LCOV_EXECUTABLE lcov) find_program (GENHTML_EXECUTABLE genhtml) cmake_dependent_option (LUABRIDGE_TESTING "Build tests" ON "CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR" OFF) +cmake_dependent_option (LUABRIDGE_SANITIZE "Sanitizer to enable (address, undefined, thread)" OFF "LUABRIDGE_TESTING" OFF) cmake_dependent_option (LUABRIDGE_COVERAGE "Enable coverage" OFF "LUABRIDGE_TESTING;FIND_EXECUTABLE;LCOV_EXECUTABLE;GENHTML_EXECUTABLE" OFF) cmake_dependent_option (LUABRIDGE_BENCHMARKS "Build benchmark executable" OFF "CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR" OFF) -set (LUABRIDGE_SANITIZE "" CACHE STRING "Sanitizer to enable (address, undefined, thread)") add_subdirectory (Source) -if (LUABRIDGE_TESTING) +if (PROJECT_IS_TOP_LEVEL AND LUABRIDGE_TESTING) include (CTest) enable_testing() set (gtest_force_shared_crt ON CACHE BOOL "Use /MD and /MDd" FORCE) diff --git a/Distribution/LuaBridge/LuaBridge.h b/Distribution/LuaBridge/LuaBridge.h index b21f8723..a2d13260 100644 --- a/Distribution/LuaBridge/LuaBridge.h +++ b/Distribution/LuaBridge/LuaBridge.h @@ -10,6 +10,10 @@ #include #include #include +#if defined(__has_include) && __has_include() +#include +#endif + #include #include #include @@ -42,6 +46,26 @@ #error LuaBridge 3 requires a compliant C++17 compiler, or C++17 has not been enabled ! #endif +#if defined(LUAU_FASTMATH_BEGIN) +#define LUABRIDGE_ON_LUAU 1 +#elif defined(LUAJIT_VERSION) +#define LUABRIDGE_ON_LUAJIT 1 +#elif defined(RAVI_OPTION_STRING2) +#define LUABRIDGE_ON_RAVI 1 +#elif defined(LUA_VERSION_NUM) +#define LUABRIDGE_ON_LUA 1 +#else +#error "Lua headers must be included prior to LuaBridge ones" +#endif + +#if !defined(LUABRIDGE_HAS_CXX20_COROUTINES) +#if !defined(LUABRIDGE_DISABLE_CXX20_COROUTINES) && (__cplusplus >= 202002L || (defined(_MSC_VER) && _HAS_CXX20)) && !(LUABRIDGE_ON_LUAU || LUABRIDGE_ON_LUAJIT || LUABRIDGE_ON_RAVI || LUA_VERSION_NUM < 502) +#define LUABRIDGE_HAS_CXX20_COROUTINES 1 +#else +#define LUABRIDGE_HAS_CXX20_COROUTINES 0 +#endif +#endif + #if !defined(LUABRIDGE_HAS_EXCEPTIONS) #if defined(_MSC_VER) #if _CPPUNWIND || _HAS_EXCEPTIONS @@ -78,18 +102,6 @@ #define LUABRIDGE_NO_SANITIZE(x) #endif -#if defined(LUAU_FASTMATH_BEGIN) -#define LUABRIDGE_ON_LUAU 1 -#elif defined(LUAJIT_VERSION) -#define LUABRIDGE_ON_LUAJIT 1 -#elif defined(RAVI_OPTION_STRING2) -#define LUABRIDGE_ON_RAVI 1 -#elif defined(LUA_VERSION_NUM) -#define LUABRIDGE_ON_LUA 1 -#else -#error "Lua headers must be included prior to LuaBridge ones" -#endif - #if defined(__OBJC__) #define LUABRIDGE_ON_OBJECTIVE_C 1 #endif @@ -1021,6 +1033,41 @@ void* lua_newuserdata_aligned(lua_State* L, Args&&... args) return pointer; } +inline int lua_resume_x(lua_State* L, lua_State* from, int nargs, int* nresults = nullptr) +{ +#if LUABRIDGE_ON_LUAJIT || LUA_VERSION_NUM == 501 + unused(from); + int status = lua_resume(L, nargs); + if (nresults) + *nresults = lua_gettop(L); + return status; +#elif LUABRIDGE_ON_LUAU || LUABRIDGE_ON_RAVI || LUA_VERSION_NUM < 504 + int status = lua_resume(L, from, nargs); + if (nresults) + *nresults = lua_gettop(L); + return status; +#else + int nr = 0; + int status = lua_resume(L, from, nargs, &nr); + if (nresults) + *nresults = nr; + return status; +#endif +} + +inline bool lua_isyieldable_x(lua_State* L) +{ +#if LUABRIDGE_ON_LUAJIT || LUA_VERSION_NUM == 501 || LUABRIDGE_ON_LUAU + unused(L); + return false; +#elif LUA_VERSION_NUM < 503 + unused(L); + return true; +#else + return lua_isyieldable(L) != 0; +#endif +} + [[noreturn]] inline void raise_lua_error(lua_State* L, const char* fmt, ...) { va_list argp; @@ -1179,7 +1226,11 @@ enum class ErrorCode InvalidTypeCast, - InvalidTableSizeInCast + InvalidTableSizeInCast, + + CoroutineYieldFromNonCoroutine, + + CoroutineAlreadyDone }; namespace detail { @@ -1220,6 +1271,12 @@ struct ErrorCategory : std::error_category case ErrorCode::InvalidTableSizeInCast: return "The lua table has different size than expected"; + case ErrorCode::CoroutineYieldFromNonCoroutine: + return "Cannot yield from a non-coroutine Lua state"; + + case ErrorCode::CoroutineAlreadyDone: + return "The Lua coroutine has already finished execution"; + default: return "Unknown error"; } @@ -6008,78 +6065,6 @@ inline void dumpState(lua_State* L, unsigned maxDepth = 1, std::ostream& stream // End File: Source/LuaBridge/Dump.h -// Begin File: Source/LuaBridge/List.h - -namespace luabridge { - -template -struct Stack> -{ - using Type = std::list; - - [[nodiscard]] static Result push(lua_State* L, const Type& list) - { -#if LUABRIDGE_SAFE_STACK_CHECKS - if (! lua_checkstack(L, 3)) - return makeErrorCode(ErrorCode::LuaStackOverflow); -#endif - - StackRestore stackRestore(L); - - lua_createtable(L, static_cast(list.size()), 0); - - auto it = list.cbegin(); - for (lua_Integer tableIndex = 1; it != list.cend(); ++tableIndex, ++it) - { - lua_pushinteger(L, tableIndex); - - auto result = Stack::push(L, *it); - if (! result) - return result; - - lua_settable(L, -3); - } - - stackRestore.reset(); - return {}; - } - - [[nodiscard]] static TypeResult get(lua_State* L, int index) - { - if (!lua_istable(L, index)) - return makeErrorCode(ErrorCode::InvalidTypeCast); - - const StackRestore stackRestore(L); - - Type list; - - int absIndex = lua_absindex(L, index); - lua_pushnil(L); - - while (lua_next(L, absIndex) != 0) - { - auto item = Stack::get(L, -1); - if (! item) - return makeErrorCode(ErrorCode::InvalidTypeCast); - - list.emplace_back(*item); - lua_pop(L, 1); - } - - return list; - } - - [[nodiscard]] static bool isInstance(lua_State* L, int index) - { - return lua_istable(L, index); - } -}; - -} - - -// End File: Source/LuaBridge/List.h - // Begin File: Source/LuaBridge/detail/FlagSet.h namespace luabridge { @@ -7928,6 +7913,12 @@ struct OverloadEntry int arity; TypeChecker checker; + +#if defined(LUABRIDGE_ENABLE_REFLECT) + std::string returnType; + std::vector paramTypes; + std::vector paramHints; +#endif }; struct OverloadSet @@ -8822,137 +8813,1985 @@ struct container_forwarder // End File: Source/LuaBridge/detail/CFunctions.h -// Begin File: Source/LuaBridge/detail/Enum.h +// Begin File: Source/LuaBridge/Inspect.h namespace luabridge { -template -struct Enum -{ - static_assert(std::is_enum_v); +struct NamespaceInspectInfo; +struct ClassInspectInfo; +class InspectVisitor; - using Type = std::underlying_type_t; +void accept(const NamespaceInspectInfo& ns, InspectVisitor& v); +void accept(const ClassInspectInfo& cls, InspectVisitor& v); - [[nodiscard]] static Result push(lua_State* L, T value) - { - return Stack::push(L, static_cast(value)); - } +enum class MemberKind +{ + Method, + StaticMethod, + Property, + ReadOnlyProperty, + StaticProperty, + StaticReadOnlyProperty, + Constructor, + Metamethod, +}; - [[nodiscard]] static TypeResult get(lua_State* L, int index) - { - const auto result = Stack::get(L, index); - if (! result) - return result.error(); +struct ParamInfo +{ + std::string typeName; + std::string hint; +}; - if constexpr (sizeof...(Values) > 0) - { - constexpr Type values[] = { static_cast(Values)... }; - for (std::size_t i = 0; i < sizeof...(Values); ++i) - { - if (values[i] == *result) - return static_cast(*result); - } +struct OverloadInfo +{ + std::string returnType; + std::vector params; + bool isConst = false; +}; - return makeErrorCode(ErrorCode::InvalidTypeCast); - } - else - { - return static_cast(*result); - } - } +struct MemberInfo +{ + std::string name; + MemberKind kind = MemberKind::Method; + std::vector overloads; +}; - [[nodiscard]] static bool isInstance(lua_State* L, int index) - { - return lua_type(L, index) == LUA_TNUMBER; - } +struct ClassInspectInfo +{ + std::string name; + std::vector baseClasses; + std::vector members; + + void accept(InspectVisitor& v) const; }; -} +struct NamespaceInspectInfo +{ + std::string name; + std::vector freeMembers; + std::vector classes; + std::vector subNamespaces; + void accept(InspectVisitor& v) const; +}; -// End File: Source/LuaBridge/detail/Enum.h +class InspectVisitor +{ +public: + virtual ~InspectVisitor() = default; -// Begin File: Source/LuaBridge/detail/Globals.h + virtual void beginNamespace(const NamespaceInspectInfo& +) {} + virtual void endNamespace(const NamespaceInspectInfo& +) {} + virtual void visitFreeMember(const NamespaceInspectInfo& +, const MemberInfo& +) {} -namespace luabridge { + virtual void beginClass(const ClassInspectInfo& +) {} + virtual void endClass(const ClassInspectInfo& +) {} + virtual void visitMember(const ClassInspectInfo& +, const MemberInfo& +) {} +}; -template -TypeResult getGlobal(lua_State* L, const char* name) +inline void accept(const NamespaceInspectInfo& ns, InspectVisitor& v) { - lua_getglobal(L, name); + v.beginNamespace(ns); + for (const auto& m : ns.freeMembers) + v.visitFreeMember(ns, m); + for (const auto& cls : ns.classes) + accept(cls, v); + for (const auto& sub : ns.subNamespaces) + accept(sub, v); + v.endNamespace(ns); +} - auto result = luabridge::Stack::get(L, -1); +inline void accept(const ClassInspectInfo& cls, InspectVisitor& v) +{ + v.beginClass(cls); + for (const auto& m : cls.members) + v.visitMember(cls, m); + v.endClass(cls); +} - lua_pop(L, 1); +inline void ClassInspectInfo::accept(InspectVisitor& v) const { luabridge::accept(*this, v); } +inline void NamespaceInspectInfo::accept(InspectVisitor& v) const { luabridge::accept(*this, v); } - return result; -} +namespace detail { -template -bool setGlobal(lua_State* L, T&& t, const char* name) +inline std::set collectTableKeys(lua_State* L, int tableIdx) { - if (auto result = push(L, std::forward(t))) + std::set keys; + tableIdx = lua_absindex(L, tableIdx); + lua_pushnil(L); + while (lua_next(L, tableIdx)) { - lua_setglobal(L, name); - return true; + if (lua_type(L, -2) == LUA_TSTRING) + keys.insert(lua_tostring(L, -2)); + lua_pop(L, 1); } + return keys; +} - return false; +inline std::string stripConst(const char* name) +{ + std::string s = name ? name : ""; + if (s.substr(0, 6) == "const ") + s = s.substr(6); + return s; } -} +inline std::vector getOverloadInfos(lua_State* L, int funcIdx) +{ + funcIdx = lua_absindex(L, funcIdx); + if (!lua_isfunction(L, funcIdx)) + return { OverloadInfo{} }; -// End File: Source/LuaBridge/detail/Globals.h + const char* uv1name = lua_getupvalue(L, funcIdx, 1); + if (uv1name == nullptr) + { + + return { OverloadInfo{} }; + } -// Begin File: Source/LuaBridge/detail/LuaRef.h + bool isOverloadSet = isfulluserdata(L, -1); + lua_pop(L, 1); -namespace luabridge { + if (!isOverloadSet) + { + + return { OverloadInfo{} }; + } -template -class LuaFunction; + const char* uv2name = lua_getupvalue(L, funcIdx, 2); + bool hasTable = (uv2name != nullptr) && lua_istable(L, -1); + if (uv2name != nullptr) + lua_pop(L, 1); -struct LuaNil -{ -}; + if (!hasTable) + return { OverloadInfo{} }; -template <> -struct Stack -{ - [[nodiscard]] static Result push(lua_State* L, const LuaNil&) + lua_getupvalue(L, funcIdx, 1); + auto* overload_set = align(lua_touserdata(L, -1)); + lua_pop(L, 1); + + if (!overload_set || overload_set->entries.empty()) + return { OverloadInfo{} }; + + std::vector result; + result.reserve(overload_set->entries.size()); + + for (const auto& entry : overload_set->entries) { -#if LUABRIDGE_SAFE_STACK_CHECKS - if (! lua_checkstack(L, 1)) - return makeErrorCode(ErrorCode::LuaStackOverflow); + OverloadInfo info; + +#if defined(LUABRIDGE_ENABLE_REFLECT) + info.returnType = entry.returnType; + for (std::size_t i = 0; i < entry.paramTypes.size(); ++i) + { + ParamInfo p; + p.typeName = entry.paramTypes[i]; + if (i < entry.paramHints.size()) + p.hint = entry.paramHints[i]; + info.params.push_back(std::move(p)); + } +#else + + if (entry.arity >= 0) + { + for (int i = 0; i < entry.arity; ++i) + info.params.push_back(ParamInfo{}); + } #endif - lua_pushnil(L); - return {}; + result.push_back(std::move(info)); } - [[nodiscard]] static bool isInstance(lua_State* L, int index) - { - return lua_type(L, index) == LUA_TNIL; - } -}; + return result; +} -template -class LuaRefBase +inline bool isClassStaticTable(lua_State* L, int tableIdx) { -protected: - friend struct Stack; + tableIdx = lua_absindex(L, tableIdx); - struct FromStack - { - }; + if (!lua_getmetatable(L, tableIdx)) + return false; - LuaRefBase(lua_State* L) noexcept - : m_L(L) + lua_rawgetp(L, -1, getClassKey()); + bool result = lua_istable(L, -1); + lua_pop(L, 1); + + if (!result) { - LUABRIDGE_ASSERT(L != nullptr); + lua_pop(L, 1); + return false; } - int createRef() const + return true; +} + +inline ClassInspectInfo inspectClassFromStaticTable(lua_State* L, int stIdx) +{ + stIdx = lua_absindex(L, stIdx); + + int mtIdx = lua_absindex(L, -1); + + lua_rawgetp(L, mtIdx, getClassKey()); + int clIdx = lua_absindex(L, -1); + + ClassInspectInfo cls; + + lua_rawgetp(L, clIdx, getTypeKey()); + if (lua_isstring(L, -1)) + cls.name = stripConst(lua_tostring(L, -1)); + lua_pop(L, 1); + + lua_rawgetp(L, clIdx, getParentKey()); + if (lua_istable(L, -1)) + { + int parIdx = lua_absindex(L, -1); + int len = static_cast(luaL_len(L, parIdx)); + for (int i = 1; i <= len; ++i) + { + lua_rawgeti(L, parIdx, i); + lua_rawgetp(L, -1, getTypeKey()); + if (lua_isstring(L, -1)) + { + std::string baseName = stripConst(lua_tostring(L, -1)); + if (!baseName.empty() && baseName != cls.name) + cls.baseClasses.push_back(std::move(baseName)); + } + lua_pop(L, 2); + } + } + lua_pop(L, 1); + + std::set instPropget; + lua_rawgetp(L, clIdx, getPropgetKey()); + if (lua_istable(L, -1)) + instPropget = collectTableKeys(L, -1); + lua_pop(L, 1); + + std::set instPropsetReal; + lua_rawgetp(L, clIdx, getPropsetKey()); + if (lua_istable(L, -1)) + { + int psIdx = lua_absindex(L, -1); + for (const auto& k : instPropget) + { + lua_getfield(L, psIdx, k.c_str()); + if (lua_isfunction(L, -1)) + { + lua_CFunction fn = lua_tocfunction(L, -1); + if (fn != &detail::read_only_error) + instPropsetReal.insert(k); + } + lua_pop(L, 1); + } + } + lua_pop(L, 1); + + for (const auto& propName : instPropget) + { + MemberInfo m; + m.name = propName; + m.kind = instPropsetReal.count(propName) ? MemberKind::Property : MemberKind::ReadOnlyProperty; + m.overloads.push_back(OverloadInfo{}); + cls.members.push_back(std::move(m)); + } + + static const std::set skipKeys{ "__index", "__newindex", "__metatable" }; + + lua_pushnil(L); + while (lua_next(L, clIdx)) + { + if (lua_type(L, -2) == LUA_TSTRING) + { + std::string key = lua_tostring(L, -2); + if (!skipKeys.count(key) && !instPropget.count(key)) + { + if (lua_isfunction(L, -1) || lua_isuserdata(L, -1)) + { + MemberInfo m; + m.name = key; + m.kind = (key.size() >= 2 && key[0] == '_' && key[1] == '_') + ? MemberKind::Metamethod + : MemberKind::Method; + m.overloads = getOverloadInfos(L, -1); + cls.members.push_back(std::move(m)); + } + } + } + lua_pop(L, 1); + } + + std::set stPropget; + lua_rawgetp(L, mtIdx, getPropgetKey()); + if (lua_istable(L, -1)) + stPropget = collectTableKeys(L, -1); + lua_pop(L, 1); + + std::set stPropsetReal; + lua_rawgetp(L, mtIdx, getPropsetKey()); + if (lua_istable(L, -1)) + { + int psIdx = lua_absindex(L, -1); + for (const auto& k : stPropget) + { + lua_getfield(L, psIdx, k.c_str()); + if (lua_isfunction(L, -1)) + { + lua_CFunction fn = lua_tocfunction(L, -1); + if (fn != &detail::read_only_error) + stPropsetReal.insert(k); + } + lua_pop(L, 1); + } + } + lua_pop(L, 1); + + for (const auto& propName : stPropget) + { + MemberInfo m; + m.name = propName; + m.kind = stPropsetReal.count(propName) ? MemberKind::StaticProperty : MemberKind::StaticReadOnlyProperty; + m.overloads.push_back(OverloadInfo{}); + cls.members.push_back(std::move(m)); + } + + static const std::set staticSkipKeys{ "__index", "__newindex", "__metatable" }; + + lua_rawgetp(L, mtIdx, getClassKey()); + lua_pop(L, 1); + + lua_pushnil(L); + while (lua_next(L, mtIdx)) + { + if (lua_type(L, -2) == LUA_TSTRING) + { + std::string key = lua_tostring(L, -2); + if (!staticSkipKeys.count(key) && !stPropget.count(key)) + { + if (lua_isfunction(L, -1) || lua_isuserdata(L, -1)) + { + MemberInfo m; + m.name = key; + if (key == "__call") + m.kind = MemberKind::Constructor; + else if (key.size() >= 2 && key[0] == '_' && key[1] == '_') + m.kind = MemberKind::Metamethod; + else + m.kind = MemberKind::StaticMethod; + m.overloads = getOverloadInfos(L, -1); + cls.members.push_back(std::move(m)); + } + } + } + lua_pop(L, 1); + } + + lua_pop(L, 2); + return cls; +} + +inline NamespaceInspectInfo inspectNamespaceTable(lua_State* L, int nsIdx, std::string name); + +inline NamespaceInspectInfo inspectNamespaceTable(lua_State* L, int nsIdx, std::string name) +{ + nsIdx = lua_absindex(L, nsIdx); + NamespaceInspectInfo info; + info.name = std::move(name); + + std::set nsPropget; + lua_rawgetp(L, nsIdx, getPropgetKey()); + if (lua_istable(L, -1)) + nsPropget = collectTableKeys(L, -1); + lua_pop(L, 1); + + int nsPropsetTableIdx = 0; + lua_rawgetp(L, nsIdx, getPropsetKey()); + if (lua_istable(L, -1)) + nsPropsetTableIdx = lua_absindex(L, -1); + + for (const auto& propName : nsPropget) + { + MemberInfo m; + m.name = propName; + + bool isReadOnly = true; + if (nsPropsetTableIdx) + { + lua_getfield(L, nsPropsetTableIdx, propName.c_str()); + if (lua_isfunction(L, -1)) + { + lua_CFunction fn = lua_tocfunction(L, -1); + isReadOnly = (fn == &detail::read_only_error); + } + lua_pop(L, 1); + } + + m.kind = isReadOnly ? MemberKind::ReadOnlyProperty : MemberKind::Property; + m.overloads.push_back(OverloadInfo{}); + info.freeMembers.push_back(std::move(m)); + } + + lua_pop(L, 1); + + static const std::set nsSkipKeys{ "__index", "__newindex", "__metatable", "__gc" }; + + lua_pushnil(L); + while (lua_next(L, nsIdx)) + { + if (lua_type(L, -2) != LUA_TSTRING) + { + lua_pop(L, 1); + continue; + } + + std::string key = lua_tostring(L, -2); + + if (nsSkipKeys.count(key) || nsPropget.count(key)) + { + lua_pop(L, 1); + continue; + } + + if (lua_istable(L, -1)) + { + int valIdx = lua_absindex(L, -1); + + if (isClassStaticTable(L, valIdx)) + { + + auto cls = inspectClassFromStaticTable(L, valIdx); + + info.classes.push_back(std::move(cls)); + } + else + { + auto sub = inspectNamespaceTable(L, valIdx, key); + info.subNamespaces.push_back(std::move(sub)); + } + } + else if (lua_isfunction(L, -1)) + { + if (!nsPropget.count(key)) + { + MemberInfo m; + m.name = key; + m.kind = MemberKind::Method; + m.overloads = getOverloadInfos(L, -1); + info.freeMembers.push_back(std::move(m)); + } + } + + lua_pop(L, 1); + } + + return info; +} + +} + +template +[[nodiscard]] ClassInspectInfo inspect(lua_State* L) +{ + + lua_rawgetp(L, LUA_REGISTRYINDEX, detail::getClassRegistryKey()); + if (!lua_istable(L, -1)) + { + lua_pop(L, 1); + return {}; + } + + lua_rawgetp(L, -1, detail::getStaticKey()); + if (!lua_istable(L, -1)) + { + lua_pop(L, 2); + return {}; + } + + lua_rawgetp(L, -1, detail::getClassKey()); + const bool isClass = lua_istable(L, -1); + lua_pop(L, 1); + + if (!isClass) + { + lua_pop(L, 2); + return {}; + } + + ClassInspectInfo result = detail::inspectClassFromStaticTable(L, lua_gettop(L)); + + lua_pop(L, 1); + return result; +} + +[[nodiscard]] inline NamespaceInspectInfo inspectNamespace(lua_State* L, const char* namespaceName = nullptr) +{ + if (namespaceName == nullptr || namespaceName[0] == '\0') + { + lua_pushglobaltable(L); + } + else + { + + std::string path = namespaceName; + std::string first; + std::string::size_type dot = path.find('.'); + if (dot == std::string::npos) + first = path; + else + first = path.substr(0, dot); + + lua_getglobal(L, first.c_str()); + if (!lua_istable(L, -1)) + { + lua_pop(L, 1); + return {}; + } + + while (dot != std::string::npos) + { + path = path.substr(dot + 1); + dot = path.find('.'); + std::string component = (dot == std::string::npos) ? path : path.substr(0, dot); + lua_getfield(L, -1, component.c_str()); + lua_remove(L, -2); + if (!lua_istable(L, -1)) + { + lua_pop(L, 1); + return {}; + } + } + } + + std::string label; + if (namespaceName && namespaceName[0]) + { + std::string_view sv(namespaceName); + auto dot = sv.rfind('.'); + label = std::string(dot == std::string_view::npos ? sv : sv.substr(dot + 1)); + } + else + { + label = "_G"; + } + auto result = detail::inspectNamespaceTable(L, -1, label); + lua_pop(L, 1); + return result; +} + +inline void inspectAccept(lua_State* L, const char* namespaceName, InspectVisitor& visitor) +{ + auto ns = inspectNamespace(L, namespaceName); + accept(ns, visitor); +} + +class ConsoleVisitor : public InspectVisitor +{ +public: + explicit ConsoleVisitor(std::ostream& out = std::cerr) + : out_(out) + { + } + + void beginNamespace(const NamespaceInspectInfo& ns) override + { + indent(); + out_ << "namespace " << ns.name << " {\n"; + ++depth_; + } + + void endNamespace(const NamespaceInspectInfo& +) override + { + --depth_; + indent(); + out_ << "}\n"; + } + + void visitFreeMember(const NamespaceInspectInfo& +, const MemberInfo& m) override + { + emitMember(m, +""); + } + + void beginClass(const ClassInspectInfo& cls) override + { + indent(); + out_ << "class " << cls.name; + if (!cls.baseClasses.empty()) + { + out_ << " extends "; + for (std::size_t i = 0; i < cls.baseClasses.size(); ++i) + { + if (i) out_ << ", "; + out_ << cls.baseClasses[i]; + } + } + out_ << " {\n"; + ++depth_; + } + + void endClass(const ClassInspectInfo& +) override + { + --depth_; + indent(); + out_ << "}\n"; + } + + void visitMember(const ClassInspectInfo& cls, const MemberInfo& m) override + { + emitMember(m, cls.name); + } + +private: + void indent() const + { + for (int i = 0; i < depth_; ++i) + out_ << " "; + } + + static std::string paramStr(const OverloadInfo& ov) + { + std::string s; + for (std::size_t i = 0; i < ov.params.size(); ++i) + { + if (i) s += ", "; + const auto& p = ov.params[i]; + s += p.hint.empty() ? ("p" + std::to_string(i + 1)) : p.hint; + s += ": "; + s += p.typeName.empty() ? "any" : p.typeName; + } + return s; + } + + static std::string retStr(const OverloadInfo& ov, const std::string& constructorClass = "") + { + if (!constructorClass.empty()) + return constructorClass; + return ov.returnType.empty() ? "any" : ov.returnType; + } + + void emitMember(const MemberInfo& m, const std::string& className) const + { + switch (m.kind) + { + case MemberKind::Property: + indent(); out_ << m.name << ": any;\n"; break; + case MemberKind::ReadOnlyProperty: + indent(); out_ << "readonly " << m.name << ": any;\n"; break; + case MemberKind::StaticProperty: + indent(); out_ << "static " << m.name << ": any;\n"; break; + case MemberKind::StaticReadOnlyProperty: + indent(); out_ << "static readonly " << m.name << ": any;\n"; break; + case MemberKind::Metamethod: + + break; + case MemberKind::Constructor: + for (const auto& ov : m.overloads) + { + indent(); + out_ << "constructor(" << paramStr(ov) << ");\n"; + } + break; + case MemberKind::Method: + case MemberKind::StaticMethod: + { + bool isStatic = (m.kind == MemberKind::StaticMethod); + for (std::size_t i = 0; i < m.overloads.size(); ++i) + { + indent(); + if (isStatic) out_ << "static "; + out_ << m.name << "(" << paramStr(m.overloads[i]) << "): " + << retStr(m.overloads[i]) << ";\n"; + } + break; + } + default: + break; + } + (void)className; + } + + std::ostream& out_; + int depth_ = 0; +}; + +class LuaLSVisitor : public InspectVisitor +{ +public: + explicit LuaLSVisitor(std::ostream& out) + : out_(out) + { + } + + void beginNamespace(const NamespaceInspectInfo& ns) override + { + ns_ = ns.name == "_G" ? "" : ns.name; + } + + void endNamespace(const NamespaceInspectInfo& +) override {} + + void visitFreeMember(const NamespaceInspectInfo& ns, const MemberInfo& m) override + { + if (m.kind != MemberKind::Method && m.kind != MemberKind::StaticMethod) + return; + + for (const auto& ov : m.overloads) + { + emitParams(ov.params); + if (!ov.returnType.empty() && ov.returnType != "void") + out_ << "---@return " << luaType(ov.returnType) << "\n"; + } + std::string qual = ns_.empty() ? "" : (ns_ + "."); + out_ << "function " << qual << m.name << "("; + if (!m.overloads.empty()) + out_ << paramNames(m.overloads[0].params); + out_ << ") end\n\n"; + } + + void beginClass(const ClassInspectInfo& cls) override + { + curClass_ = cls.name; + out_ << "---@class " << qualifiedName(cls.name); + if (!cls.baseClasses.empty()) + { + out_ << " : " << cls.baseClasses[0]; + for (std::size_t i = 1; i < cls.baseClasses.size(); ++i) + out_ << ", " << cls.baseClasses[i]; + } + out_ << "\n"; + + } + + void endClass(const ClassInspectInfo& +) override + { + out_ << "local " << curClass_ << " = {}\n\n"; + curClass_.clear(); + } + + void visitMember(const ClassInspectInfo& cls, const MemberInfo& m) override + { + switch (m.kind) + { + case MemberKind::Property: + out_ << "---@field " << m.name << " any\n"; + break; + case MemberKind::ReadOnlyProperty: + out_ << "---@field " << m.name << " any # readonly\n"; + break; + case MemberKind::StaticProperty: + out_ << "---@field " << m.name << " any\n"; + break; + case MemberKind::StaticReadOnlyProperty: + out_ << "---@field " << m.name << " any # readonly\n"; + break; + case MemberKind::Constructor: + { + out_ << "\n"; + for (std::size_t i = 0; i < m.overloads.size(); ++i) + { + const auto& ov = m.overloads[i]; + if (i == 0) + { + emitParams(ov.params); + out_ << "---@return " << cls.name << "\n"; + out_ << "function " << cls.name << ".new(" << paramNames(ov.params) << ") end\n"; + } + else + { + out_ << "---@overload fun(" << overloadParams(ov.params) << "): " << cls.name << "\n"; + } + } + break; + } + case MemberKind::Method: + { + out_ << "\n"; + for (std::size_t i = 0; i < m.overloads.size(); ++i) + { + const auto& ov = m.overloads[i]; + if (i == 0) + { + emitParams(ov.params, +true); + if (!ov.returnType.empty() && ov.returnType != "void") + out_ << "---@return " << luaType(ov.returnType) << "\n"; + out_ << "function " << cls.name << ":" << m.name + << "(" << paramNames(ov.params) << ") end\n"; + } + else + { + out_ << "---@overload fun(self: " << cls.name << ", " << overloadParams(ov.params) << ")" + << (ov.returnType.empty() ? "" : (": " + luaType(ov.returnType))) << "\n"; + } + } + break; + } + case MemberKind::StaticMethod: + { + out_ << "\n"; + for (std::size_t i = 0; i < m.overloads.size(); ++i) + { + const auto& ov = m.overloads[i]; + if (i == 0) + { + emitParams(ov.params); + if (!ov.returnType.empty() && ov.returnType != "void") + out_ << "---@return " << luaType(ov.returnType) << "\n"; + out_ << "function " << cls.name << "." << m.name + << "(" << paramNames(ov.params) << ") end\n"; + } + else + { + out_ << "---@overload fun(" << overloadParams(ov.params) << ")" + << (ov.returnType.empty() ? "" : (": " + luaType(ov.returnType))) << "\n"; + } + } + break; + } + default: + break; + } + } + +private: + + static std::string luaType(const std::string& cppType) + { + if (cppType == "void") return "nil"; + if (cppType == "bool") return "boolean"; + if (cppType == "int" || cppType == "long" || cppType == "short" || + cppType == "unsigned int" || cppType == "unsigned long" || + cppType == "unsigned short" || cppType == "int64_t" || cppType == "uint64_t" || + cppType == "int32_t" || cppType == "uint32_t" || cppType == "size_t") + return "integer"; + if (cppType == "float" || cppType == "double") return "number"; + if (cppType == "std::string" || cppType == "const char *" || cppType == "const char*") + return "string"; + return cppType.empty() ? "any" : cppType; + } + + std::string qualifiedName(const std::string& name) const + { + return ns_.empty() ? name : (ns_ + "." + name); + } + + static std::string paramNames(const std::vector& params) + { + std::string s; + for (std::size_t i = 0; i < params.size(); ++i) + { + if (i) s += ", "; + s += params[i].hint.empty() ? ("p" + std::to_string(i + 1)) : params[i].hint; + } + return s; + } + + static std::string overloadParams(const std::vector& params) + { + std::string s; + for (std::size_t i = 0; i < params.size(); ++i) + { + if (i) s += ", "; + const auto& p = params[i]; + std::string pname = p.hint.empty() ? ("p" + std::to_string(i + 1)) : p.hint; + s += pname + ": " + luaType(p.typeName.empty() ? "any" : p.typeName); + } + return s; + } + + void emitParams(const std::vector& params, bool hasSelf = false) const + { + if (hasSelf) + out_ << "---@param self " << curClass_ << "\n"; + for (std::size_t i = 0; i < params.size(); ++i) + { + const auto& p = params[i]; + std::string pname = p.hint.empty() ? ("p" + std::to_string(i + 1)) : p.hint; + std::string ptype = p.typeName.empty() ? "any" : luaType(p.typeName); + out_ << "---@param " << pname << " " << ptype << "\n"; + } + } + + std::ostream& out_; + std::string ns_; + std::string curClass_; +}; + +class LuaProxyVisitor : public InspectVisitor +{ +public: + explicit LuaProxyVisitor(std::ostream& out) + : out_(out) + { + } + + void beginNamespace(const NamespaceInspectInfo& ns) override + { + if (ns.name != "_G") + out_ << "local " << ns.name << " = {}\n\n"; + } + + void endNamespace(const NamespaceInspectInfo& ns) override + { + if (ns.name != "_G") + out_ << "return " << ns.name << "\n"; + } + + void visitFreeMember(const NamespaceInspectInfo& ns, const MemberInfo& m) override + { + if (m.kind != MemberKind::Method && m.kind != MemberKind::StaticMethod) + return; + std::string qual = (ns.name == "_G") ? "" : (ns.name + "."); + if (!m.overloads.empty()) + { + out_ << "function " << qual << m.name + << "(" << paramNames(m.overloads[0].params) << ") end\n"; + } + } + + void beginClass(const ClassInspectInfo& cls) override + { + curClass_ = cls.name; + out_ << cls.name << " = {}\n"; + out_ << cls.name << ".__index = " << cls.name << "\n\n"; + } + + void endClass(const ClassInspectInfo& +) override + { + curClass_.clear(); + out_ << "\n"; + } + + void visitMember(const ClassInspectInfo& +, const MemberInfo& m) override + { + if (m.overloads.empty()) + return; + + switch (m.kind) + { + case MemberKind::Constructor: + out_ << "function " << curClass_ << ".new(" + << paramNames(m.overloads[0].params) << ")\n" + << " return setmetatable({}, " << curClass_ << ")\n" + << "end\n\n"; + break; + case MemberKind::Method: + out_ << "function " << curClass_ << ":" << m.name + << "(" << paramNames(m.overloads[0].params) << ") end\n"; + break; + case MemberKind::StaticMethod: + out_ << "function " << curClass_ << "." << m.name + << "(" << paramNames(m.overloads[0].params) << ") end\n"; + break; + case MemberKind::Property: + case MemberKind::ReadOnlyProperty: + case MemberKind::StaticProperty: + case MemberKind::StaticReadOnlyProperty: + + break; + default: + break; + } + } + +private: + static std::string paramNames(const std::vector& params) + { + std::string s; + for (std::size_t i = 0; i < params.size(); ++i) + { + if (i) s += ", "; + s += params[i].hint.empty() ? ("p" + std::to_string(i + 1)) : params[i].hint; + } + return s; + } + + std::ostream& out_; + std::string curClass_; +}; + +class JsonVisitor : public InspectVisitor +{ +public: + explicit JsonVisitor(std::ostream& out) + : out_(out) + { + } + + void beginNamespace(const NamespaceInspectInfo& ns) override + { + if (depth_ == 0) out_ << "{\n"; + indent(); out_ << "\"name\": \"" << escape(ns.name) << "\",\n"; + indent(); out_ << "\"freeMembers\": [],\n"; + indent(); out_ << "\"classes\": [\n"; + ++depth_; + firstClass_ = true; + } + + void endNamespace(const NamespaceInspectInfo& +) override + { + --depth_; + out_ << "\n"; + indent(); out_ << "]\n"; + if (depth_ == 0) out_ << "}\n"; + } + + void beginClass(const ClassInspectInfo& cls) override + { + if (!firstClass_) out_ << ",\n"; + firstClass_ = false; + indent(); out_ << "{\n"; + ++depth_; + indent(); out_ << "\"name\": \"" << escape(cls.name) << "\",\n"; + indent(); out_ << "\"bases\": ["; + for (std::size_t i = 0; i < cls.baseClasses.size(); ++i) + { + if (i) out_ << ", "; + out_ << "\"" << escape(cls.baseClasses[i]) << "\""; + } + out_ << "],\n"; + indent(); out_ << "\"members\": [\n"; + ++depth_; + firstMember_ = true; + } + + void endClass(const ClassInspectInfo& +) override + { + --depth_; + out_ << "\n"; + indent(); out_ << "]\n"; + --depth_; + indent(); out_ << "}"; + } + + void visitMember(const ClassInspectInfo& +, const MemberInfo& m) override + { + if (!firstMember_) out_ << ",\n"; + firstMember_ = false; + indent(); + out_ << "{ \"name\": \"" << escape(m.name) << "\"" + << ", \"kind\": \"" << kindStr(m.kind) << "\"" + << ", \"overloads\": " << m.overloads.size() + << " }"; + } + +private: + void indent() const + { + for (int i = 0; i < depth_; ++i) + out_ << " "; + } + + static std::string escape(const std::string& s) + { + std::string out; + for (char c : s) + { + if (c == '"') out += "\\\""; + else if (c == '\\') out += "\\\\"; + else out += c; + } + return out; + } + + static const char* kindStr(MemberKind k) + { + switch (k) + { + case MemberKind::Method: return "method"; + case MemberKind::StaticMethod: return "static_method"; + case MemberKind::Property: return "property"; + case MemberKind::ReadOnlyProperty: return "readonly_property"; + case MemberKind::StaticProperty: return "static_property"; + case MemberKind::StaticReadOnlyProperty: return "static_readonly_property"; + case MemberKind::Constructor: return "constructor"; + case MemberKind::Metamethod: return "metamethod"; + default: return "unknown"; + } + } + + std::ostream& out_; + int depth_ = 0; + bool firstClass_ = true; + bool firstMember_ = true; +}; + +class LuaTableVisitor : public InspectVisitor +{ +public: + explicit LuaTableVisitor(lua_State* L) + : L_(L) + { + } + + void beginNamespace(const NamespaceInspectInfo& ns) override + { + lua_newtable(L_); + lua_pushstring(L_, ns.name.c_str()); + lua_setfield(L_, -2, "name"); + + lua_newtable(L_); + lua_setfield(L_, -2, "freeMembers"); + freeMemberIdx_ = 1; + + lua_newtable(L_); + lua_setfield(L_, -2, "classes"); + classIdx_ = 1; + + lua_newtable(L_); + lua_setfield(L_, -2, "subNamespaces"); + subNsIdx_ = 1; + } + + void endNamespace(const NamespaceInspectInfo& +) override + { + + } + + void visitFreeMember(const NamespaceInspectInfo& +, const MemberInfo& m) override + { + lua_getfield(L_, -1, "freeMembers"); + pushMemberInfo(m); + lua_rawseti(L_, -2, freeMemberIdx_++); + lua_pop(L_, 1); + } + + void beginClass(const ClassInspectInfo& cls) override + { + lua_newtable(L_); + lua_pushstring(L_, cls.name.c_str()); + lua_setfield(L_, -2, "name"); + + lua_newtable(L_); + for (std::size_t i = 0; i < cls.baseClasses.size(); ++i) + { + lua_pushstring(L_, cls.baseClasses[i].c_str()); + lua_rawseti(L_, -2, static_cast(i + 1)); + } + lua_setfield(L_, -2, "bases"); + + lua_newtable(L_); + lua_setfield(L_, -2, "members"); + memberIdx_ = 1; + } + + void endClass(const ClassInspectInfo& +) override + { + + lua_getfield(L_, -2, "classes"); + lua_pushvalue(L_, -2); + lua_rawseti(L_, -2, classIdx_++); + lua_pop(L_, 2); + } + + void visitMember(const ClassInspectInfo& +, const MemberInfo& m) override + { + lua_getfield(L_, -1, "members"); + pushMemberInfo(m); + lua_rawseti(L_, -2, memberIdx_++); + lua_pop(L_, 1); + } + +private: + void pushMemberInfo(const MemberInfo& m) + { + lua_newtable(L_); + lua_pushstring(L_, m.name.c_str()); + lua_setfield(L_, -2, "name"); + lua_pushstring(L_, kindStr(m.kind)); + lua_setfield(L_, -2, "kind"); + lua_pushinteger(L_, static_cast(m.overloads.size())); + lua_setfield(L_, -2, "overloads"); + + lua_newtable(L_); + for (std::size_t i = 0; i < m.overloads.size(); ++i) + { + const auto& ov = m.overloads[i]; + lua_newtable(L_); + lua_pushstring(L_, ov.returnType.c_str()); + lua_setfield(L_, -2, "returnType"); + + lua_newtable(L_); + for (std::size_t j = 0; j < ov.params.size(); ++j) + { + lua_newtable(L_); + lua_pushstring(L_, ov.params[j].typeName.c_str()); + lua_setfield(L_, -2, "type"); + lua_pushstring(L_, ov.params[j].hint.c_str()); + lua_setfield(L_, -2, "hint"); + lua_rawseti(L_, -2, static_cast(j + 1)); + } + lua_setfield(L_, -2, "params"); + lua_rawseti(L_, -2, static_cast(i + 1)); + } + lua_setfield(L_, -2, "overloadDetails"); + } + + static const char* kindStr(MemberKind k) + { + switch (k) + { + case MemberKind::Method: return "method"; + case MemberKind::StaticMethod: return "static_method"; + case MemberKind::Property: return "property"; + case MemberKind::ReadOnlyProperty: return "readonly_property"; + case MemberKind::StaticProperty: return "static_property"; + case MemberKind::StaticReadOnlyProperty: return "static_readonly_property"; + case MemberKind::Constructor: return "constructor"; + case MemberKind::Metamethod: return "metamethod"; + default: return "unknown"; + } + } + + lua_State* L_; + int freeMemberIdx_ = 1; + int classIdx_ = 1; + int subNsIdx_ = 1; + int memberIdx_ = 1; +}; + +inline void inspectPrint(lua_State* L, const char* namespaceName = nullptr, std::ostream& stream = std::cerr) +{ + auto ns = inspectNamespace(L, namespaceName); + ConsoleVisitor v(stream); + accept(ns, v); +} + +inline void inspectToLua(lua_State* L, const char* namespaceName = nullptr) +{ + auto ns = inspectNamespace(L, namespaceName); + LuaTableVisitor v(L); + accept(ns, v); +} + +} + + +// End File: Source/LuaBridge/Inspect.h + +// Begin File: Source/LuaBridge/List.h + +namespace luabridge { + +template +struct Stack> +{ + using Type = std::list; + + [[nodiscard]] static Result push(lua_State* L, const Type& list) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 3)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + StackRestore stackRestore(L); + + lua_createtable(L, static_cast(list.size()), 0); + + auto it = list.cbegin(); + for (lua_Integer tableIndex = 1; it != list.cend(); ++tableIndex, ++it) + { + lua_pushinteger(L, tableIndex); + + auto result = Stack::push(L, *it); + if (! result) + return result; + + lua_settable(L, -3); + } + + stackRestore.reset(); + return {}; + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + if (!lua_istable(L, index)) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + const StackRestore stackRestore(L); + + Type list; + + int absIndex = lua_absindex(L, index); + lua_pushnil(L); + + while (lua_next(L, absIndex) != 0) + { + auto item = Stack::get(L, -1); + if (! item) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + list.emplace_back(*item); + lua_pop(L, 1); + } + + return list; + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return lua_istable(L, index); + } +}; + +} + + +// End File: Source/LuaBridge/List.h + +// Begin File: Source/LuaBridge/detail/Coroutine.h + +#if LUABRIDGE_HAS_CXX20_COROUTINES + +#if LUABRIDGE_ON_LUAJIT || LUA_VERSION_NUM == 501 || LUABRIDGE_ON_LUAU +#ifndef LUABRIDGE_DISABLE_COROUTINE_INTEGRATION +#error "C++20 coroutine integration requires Lua 5.2+ with lua_yieldk support. \ +Define LUABRIDGE_DISABLE_COROUTINE_INTEGRATION to suppress this error." +#endif +#else + +namespace luabridge { + +template +struct CppCoroutine +{ + struct promise_type + { + lua_State* L = nullptr; + int nresults = 0; + bool is_done = false; + std::exception_ptr exception; + + std::suspend_always initial_suspend() noexcept { return {}; } + std::suspend_always final_suspend() noexcept { return {}; } + + void unhandled_exception() noexcept + { + exception = std::current_exception(); + } + + std::suspend_always yield_value(const R& value) + { + nresults = 0; + if (L) + { + auto result = Stack::push(L, value); + if (result) + nresults = 1; + else + exception = std::make_exception_ptr(std::system_error(result.error())); + } + return {}; + } + + std::suspend_always yield_value(R&& value) + { + nresults = 0; + if (L) + { + auto result = Stack::push(L, std::move(value)); + if (result) + nresults = 1; + else + exception = std::make_exception_ptr(std::system_error(result.error())); + } + return {}; + } + + void return_value(const R& value) + { + nresults = 0; + if (L) + { + auto result = Stack::push(L, value); + if (result) + nresults = 1; + else + exception = std::make_exception_ptr(std::system_error(result.error())); + } + is_done = true; + } + + void return_value(R&& value) + { + nresults = 0; + if (L) + { + auto result = Stack::push(L, std::move(value)); + if (result) + nresults = 1; + else + exception = std::make_exception_ptr(std::system_error(result.error())); + } + is_done = true; + } + + CppCoroutine get_return_object() + { + return CppCoroutine{ std::coroutine_handle::from_promise(*this) }; + } + }; + + std::coroutine_handle handle; + + explicit CppCoroutine(std::coroutine_handle h) noexcept + : handle(h) + { + } + + CppCoroutine(CppCoroutine&& other) noexcept + : handle(std::exchange(other.handle, {})) + { + } + + CppCoroutine(const CppCoroutine&) = delete; + CppCoroutine& operator=(const CppCoroutine&) = delete; + ~CppCoroutine() = default; +}; + +template <> +struct CppCoroutine +{ + struct promise_type + { + lua_State* L = nullptr; + int nresults = 0; + bool is_done = false; + std::exception_ptr exception; + + std::suspend_always initial_suspend() noexcept { return {}; } + std::suspend_always final_suspend() noexcept { return {}; } + + void unhandled_exception() noexcept + { + exception = std::current_exception(); + } + + void return_void() + { + nresults = 0; + is_done = true; + } + + CppCoroutine get_return_object() + { + return CppCoroutine{ std::coroutine_handle::from_promise(*this) }; + } + }; + + std::coroutine_handle handle; + + explicit CppCoroutine(std::coroutine_handle h) noexcept + : handle(h) + { + } + + CppCoroutine(CppCoroutine&& other) noexcept + : handle(std::exchange(other.handle, {})) + { + } + + CppCoroutine(const CppCoroutine&) = delete; + CppCoroutine& operator=(const CppCoroutine&) = delete; + ~CppCoroutine() = default; +}; + +class LuaCoroutine +{ +public: + LuaCoroutine(lua_State* thread, lua_State* from = nullptr) noexcept + : m_thread(thread) + , m_from(from) + { + } + + bool await_ready() noexcept + { + m_status = lua_resume_x(m_thread, m_from, 0, &m_nresults); + return true; + } + + void await_suspend(std::coroutine_handle<>) noexcept + { + + } + + std::pair await_resume() noexcept + { + return { m_status, m_nresults }; + } + +private: + lua_State* m_thread; + lua_State* m_from; + int m_status = LUABRIDGE_LUA_OK; + int m_nresults = 0; +}; + +namespace detail { + +template +struct is_cpp_coroutine : std::false_type +{ +}; + +template +struct is_cpp_coroutine> : std::true_type +{ +}; + +template +struct is_cpp_coroutine_factory : std::false_type +{ +}; + +template +struct is_cpp_coroutine_factory>::result_type>> + : is_cpp_coroutine>::result_type> +{ +}; + +template +inline constexpr bool is_cpp_coroutine_factory_v = is_cpp_coroutine_factory::value; + +template +struct CppCoroutineFrame +{ + using HandleType = std::coroutine_handle; + + HandleType handle; + + explicit CppCoroutineFrame(HandleType h) noexcept + : handle(h) + { + } + + CppCoroutineFrame(const CppCoroutineFrame&) = delete; + CppCoroutineFrame& operator=(const CppCoroutineFrame&) = delete; + + ~CppCoroutineFrame() + { + if (handle && !handle.done()) + handle.destroy(); + } +}; + +template int coroutine_continuation_body(lua_State* L, int frame_abs_idx); + +#if LUA_VERSION_NUM < 503 + +template +int coroutine_continuation(lua_State* L) +{ + int frame_abs_idx = 0; + lua_getctx(L, &frame_abs_idx); + return coroutine_continuation_body(L, frame_abs_idx); +} + +template +int do_yield(lua_State* L, int nresults, int frame_abs_idx) +{ + return lua_yieldk(L, nresults, frame_abs_idx, &coroutine_continuation); +} +#else + +template +int coroutine_continuation(lua_State* L, int +, lua_KContext ctx) +{ + return coroutine_continuation_body(L, static_cast(ctx)); +} + +template +int do_yield(lua_State* L, int nresults, int frame_abs_idx) +{ + return lua_yieldk(L, nresults, static_cast(frame_abs_idx), &coroutine_continuation); +} +#endif + +[[noreturn]] inline void raise_from_exception(lua_State* L, int frame_abs_idx, std::exception_ptr ex) +{ + lua_settop(L, frame_abs_idx - 1); + +#if LUABRIDGE_HAS_EXCEPTIONS + try + { + std::rethrow_exception(ex); + } + catch (const std::exception& e) + { + raise_lua_error(L, "%s", e.what()); + } + catch (...) + { +#endif + + raise_lua_error(L, "unknown exception in C++ coroutine"); + +#if LUABRIDGE_HAS_EXCEPTIONS + } +#endif +} + +template +int coroutine_continuation_body(lua_State* L, int frame_abs_idx) +{ + using CoroType = typename function_traits>::result_type; + using FrameType = CppCoroutineFrame; + + lua_settop(L, frame_abs_idx); + + auto* frame = align(lua_touserdata(L, frame_abs_idx)); + + frame->handle.resume(); + + auto& promise = frame->handle.promise(); + + if (promise.exception) + raise_from_exception(L, frame_abs_idx, promise.exception); + + if (promise.is_done) + { + if (promise.nresults == 1) + lua_replace(L, frame_abs_idx); + else + lua_settop(L, frame_abs_idx - 1); + return promise.nresults; + } + + return do_yield(L, promise.nresults, frame_abs_idx); +} + +template +int invoke_coroutine_entry(lua_State* L) +{ + using FnTraits = function_traits>; + using ArgsPack = typename FnTraits::argument_types; + using CoroType = typename FnTraits::result_type; + using FrameType = CppCoroutineFrame; + + LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(1))); + auto& factory = *align(lua_touserdata(L, lua_upvalueindex(1))); + + auto coro = invoke_callable_from_stack(L, factory); + + lua_newuserdata_aligned(L, std::move(coro.handle)); + coro.handle = {}; + + int frame_abs_idx = lua_gettop(L); + auto* frame = align(lua_touserdata(L, frame_abs_idx)); + + frame->handle.promise().L = L; + + frame->handle.resume(); + + auto& promise = frame->handle.promise(); + + if (promise.exception) + raise_from_exception(L, frame_abs_idx, promise.exception); + + if (promise.is_done) + { + if (promise.nresults == 1) + lua_replace(L, frame_abs_idx); + else + lua_settop(L, frame_abs_idx - 1); + return promise.nresults; + } + + return do_yield(L, promise.nresults, frame_abs_idx); +} + +template >> +inline void push_coroutine_function(lua_State* L, F&& f, const char* debugname) +{ + using FDecay = std::decay_t; + lua_newuserdata_aligned(L, std::forward(f)); + lua_pushcclosure_x(L, &invoke_coroutine_entry, debugname, 1); +} + +} +} + +#endif +#endif + + +// End File: Source/LuaBridge/detail/Coroutine.h + +// Begin File: Source/LuaBridge/detail/Enum.h + +namespace luabridge { + +template +struct Enum +{ + static_assert(std::is_enum_v); + + using Type = std::underlying_type_t; + + [[nodiscard]] static Result push(lua_State* L, T value) + { + return Stack::push(L, static_cast(value)); + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + const auto result = Stack::get(L, index); + if (! result) + return result.error(); + + if constexpr (sizeof...(Values) > 0) + { + constexpr Type values[] = { static_cast(Values)... }; + for (std::size_t i = 0; i < sizeof...(Values); ++i) + { + if (values[i] == *result) + return static_cast(*result); + } + + return makeErrorCode(ErrorCode::InvalidTypeCast); + } + else + { + return static_cast(*result); + } + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return lua_type(L, index) == LUA_TNUMBER; + } +}; + +} + + +// End File: Source/LuaBridge/detail/Enum.h + +// Begin File: Source/LuaBridge/detail/FunctionHints.h + +namespace luabridge { + +template +struct FunctionWithHints +{ + using func_type = F; + + F func; + std::vector hints; +}; + +template +[[nodiscard]] auto withHints(F&& func, Names&&... paramNames) -> FunctionWithHints> +{ + return { std::forward(func), { std::string(std::forward(paramNames))... } }; +} + +namespace detail { + +template +struct unwrap_fn_type +{ + using type = T; +}; + +template +struct unwrap_fn_type> +{ + using type = F; +}; + +template +using unwrap_fn_type_t = typename unwrap_fn_type::type; + +template +struct is_function_with_hints : std::false_type +{ +}; + +template +struct is_function_with_hints> : std::true_type +{ +}; + +template +inline static constexpr bool is_function_with_hints_v = is_function_with_hints::value; + +template +struct function_traits> : function_traits +{ +}; + +template +struct is_callable> +{ + static constexpr bool value = is_callable_v; +}; + +template +struct is_const_member_function_pointer> +{ + static constexpr bool value = is_const_member_function_pointer_v; +}; + +template +[[nodiscard]] decltype(auto) get_underlying(F&& f) noexcept +{ + return std::forward(f); +} + +template +[[nodiscard]] F&& get_underlying(FunctionWithHints&& f) noexcept +{ + return std::move(f.func); +} + +template +[[nodiscard]] F& get_underlying(FunctionWithHints& f) noexcept +{ + return f.func; +} + +#if defined(LUABRIDGE_ENABLE_REFLECT) + +template +[[nodiscard]] std::vector reflect_param_type_names() +{ + std::vector result; + + [&](std::index_sequence) + { + ( + [&] + { + using ParamT = std::tuple_element_t; + + constexpr bool isLuaState = + std::is_pointer_v && + std::is_same_v>, lua_State>; + + if constexpr (!isLuaState) + result.push_back(std::string(typeName>())); + }(), + ...); + }(std::make_index_sequence>{}); + + return result; +} + +#endif + +} +} + + +// End File: Source/LuaBridge/detail/FunctionHints.h + +// Begin File: Source/LuaBridge/detail/Globals.h + +namespace luabridge { + +template +TypeResult getGlobal(lua_State* L, const char* name) +{ + lua_getglobal(L, name); + + auto result = luabridge::Stack::get(L, -1); + + lua_pop(L, 1); + + return result; +} + +template +bool setGlobal(lua_State* L, T&& t, const char* name) +{ + if (auto result = push(L, std::forward(t))) + { + lua_setglobal(L, name); + return true; + } + + return false; +} + +} + + +// End File: Source/LuaBridge/detail/Globals.h + +// Begin File: Source/LuaBridge/detail/LuaRef.h + +namespace luabridge { + +template +class LuaFunction; + +struct LuaNil +{ +}; + +template <> +struct Stack +{ + [[nodiscard]] static Result push(lua_State* L, const LuaNil&) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + lua_pushnil(L); + return {}; + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return lua_type(L, index) == LUA_TNIL; + } +}; + +template +class LuaRefBase +{ +protected: + friend struct Stack; + + struct FromStack + { + }; + + LuaRefBase(lua_State* L) noexcept + : m_L(L) + { + LUABRIDGE_ASSERT(L != nullptr); + } + + int createRef() const { impl().push(m_L); @@ -11014,11 +12853,44 @@ class Namespace : public detail::Registrar if constexpr (sizeof...(Functions) == 1) { +#if defined(LUABRIDGE_ENABLE_REFLECT) + + ([&] + { + auto* overload_set_unaligned = lua_newuserdata_aligned(L); + auto* overload_set = align(overload_set_unaligned); + + detail::OverloadEntry entry; + if constexpr (!detail::is_any_cfunction_pointer_v) + { + using ArgsPack = detail::function_arguments_t; + entry.arity = static_cast(detail::function_arity_excluding_v); + entry.checker = &detail::overload_type_checker; + entry.returnType = std::string(detail::typeName>()); + entry.paramTypes = detail::reflect_param_type_names(); + if constexpr (detail::is_function_with_hints_v) + entry.paramHints = functions.hints; + } + else + { + entry.arity = -1; + entry.checker = nullptr; + } + overload_set->entries.push_back(std::move(entry)); + + lua_createtable(L, 1, 0); + detail::push_function(L, detail::get_underlying(std::move(functions)), name); + lua_rawseti(L, -2, 1); + lua_pushcclosure_x(L, &detail::try_overload_functions, name, 2); + + } (), ...); +#else ([&] { - detail::push_function(L, std::move(functions), name); + detail::push_function(L, detail::get_underlying(std::move(functions)), name); } (), ...); +#endif } else { @@ -11039,6 +12911,12 @@ class Namespace : public detail::Registrar using ArgsPack = detail::function_arguments_t; entry.arity = static_cast(detail::function_arity_excluding_v); entry.checker = &detail::overload_type_checker; +#if defined(LUABRIDGE_ENABLE_REFLECT) + entry.returnType = std::string(detail::typeName>()); + entry.paramTypes = detail::reflect_param_type_names(); + if constexpr (detail::is_function_with_hints_v) + entry.paramHints = functions.hints; +#endif } overload_set->entries.push_back(entry); @@ -11050,7 +12928,7 @@ class Namespace : public detail::Registrar ([&] { - detail::push_function(L, std::move(functions), name); + detail::push_function(L, detail::get_underlying(std::move(functions)), name); lua_rawseti(L, -2, idx++); } (), ...); @@ -11177,11 +13055,55 @@ class Namespace : public detail::Registrar if constexpr (sizeof...(Functions) == 1) { +#if defined(LUABRIDGE_ENABLE_REFLECT) + + ([&] + { + auto* overload_set_unaligned = lua_newuserdata_aligned(L); + auto* overload_set = align(overload_set_unaligned); + + detail::OverloadEntry entry; + if constexpr (!detail::is_any_cfunction_pointer_v) + { + if constexpr (detail::is_proxy_member_function_v) + { + using ArgsPack = detail::remove_first_type_t>; + entry.arity = static_cast(detail::member_function_arity_excluding_v); + entry.checker = &detail::overload_type_checker; + entry.returnType = std::string(detail::typeName>()); + entry.paramTypes = detail::reflect_param_type_names(); + } + else + { + using ArgsPack = detail::function_arguments_t; + entry.arity = static_cast(detail::member_function_arity_excluding_v); + entry.checker = &detail::overload_type_checker; + entry.returnType = std::string(detail::typeName>()); + entry.paramTypes = detail::reflect_param_type_names(); + } + if constexpr (detail::is_function_with_hints_v) + entry.paramHints = functions.hints; + } + else + { + entry.arity = -1; + entry.checker = nullptr; + } + overload_set->entries.push_back(std::move(entry)); + + lua_createtable(L, 1, 0); + detail::push_member_function(L, detail::get_underlying(std::move(functions)), name); + lua_rawseti(L, -2, 1); + lua_pushcclosure_x(L, &detail::try_overload_functions, name, 2); + + } (), ...); +#else ([&] { - detail::push_member_function(L, std::move(functions), name); + detail::push_member_function(L, detail::get_underlying(std::move(functions)), name); } (), ...); +#endif if constexpr (detail::const_functions_count == 1) { @@ -11219,12 +13141,24 @@ class Namespace : public detail::Registrar using ArgsPack = detail::remove_first_type_t>; entry.arity = static_cast(detail::member_function_arity_excluding_v); entry.checker = &detail::overload_type_checker; +#if defined(LUABRIDGE_ENABLE_REFLECT) + entry.returnType = std::string(detail::typeName>()); + entry.paramTypes = detail::reflect_param_type_names(); + if constexpr (detail::is_function_with_hints_v) + entry.paramHints = functions.hints; +#endif } else { using ArgsPack = detail::function_arguments_t; entry.arity = static_cast(detail::member_function_arity_excluding_v); entry.checker = &detail::overload_type_checker; +#if defined(LUABRIDGE_ENABLE_REFLECT) + entry.returnType = std::string(detail::typeName>()); + entry.paramTypes = detail::reflect_param_type_names(); + if constexpr (detail::is_function_with_hints_v) + entry.paramHints = functions.hints; +#endif } overload_set_const->entries.push_back(entry); @@ -11241,7 +13175,7 @@ class Namespace : public detail::Registrar if (!detail::is_const_function) return; - detail::push_member_function(L, std::move(functions), name); + detail::push_member_function(L, detail::get_underlying(std::move(functions)), name); lua_rawseti(L, -2, idx++); } (), ...); @@ -11274,12 +13208,24 @@ class Namespace : public detail::Registrar using ArgsPack = detail::remove_first_type_t>; entry.arity = static_cast(detail::member_function_arity_excluding_v); entry.checker = &detail::overload_type_checker; +#if defined(LUABRIDGE_ENABLE_REFLECT) + entry.returnType = std::string(detail::typeName>()); + entry.paramTypes = detail::reflect_param_type_names(); + if constexpr (detail::is_function_with_hints_v) + entry.paramHints = functions.hints; +#endif } else { using ArgsPack = detail::function_arguments_t; entry.arity = static_cast(detail::member_function_arity_excluding_v); entry.checker = &detail::overload_type_checker; +#if defined(LUABRIDGE_ENABLE_REFLECT) + entry.returnType = std::string(detail::typeName>()); + entry.paramTypes = detail::reflect_param_type_names(); + if constexpr (detail::is_function_with_hints_v) + entry.paramHints = functions.hints; +#endif } overload_set_nonconst->entries.push_back(entry); @@ -11296,7 +13242,7 @@ class Namespace : public detail::Registrar if (detail::is_const_function) return; - detail::push_member_function(L, std::move(functions), name); + detail::push_member_function(L, detail::get_underlying(std::move(functions)), name); lua_rawseti(L, -2, idx++); } (), ...); @@ -11309,6 +13255,47 @@ class Namespace : public detail::Registrar return *this; } +#if LUABRIDGE_HAS_CXX20_COROUTINES + + template + auto addStaticCoroutine(const char* name, F factory) + -> std::enable_if_t, Class&> + { + LUABRIDGE_ASSERT(name != nullptr); + assertStackState(); + + detail::push_coroutine_function(L, std::move(factory), name); + rawsetfield(L, -2, name); + + return *this; + } + + template + auto addCoroutine(const char* name, F factory) + -> std::enable_if_t< + detail::is_cpp_coroutine_factory_v && detail::is_proxy_member_function_v, + Class&> + { + LUABRIDGE_ASSERT(name != nullptr); + assertStackState(); + + detail::push_coroutine_function(L, std::move(factory), name); + + if constexpr (detail::is_const_function) + { + lua_pushvalue(L, -1); + rawsetfield(L, -4, name); + rawsetfield(L, -4, name); + } + else + { + rawsetfield(L, -3, name); + } + + return *this; + } +#endif + template auto addConstructor() -> std::enable_if_t<(sizeof...(Functions) > 0), Class&> @@ -11317,11 +13304,35 @@ class Namespace : public detail::Registrar if constexpr (sizeof...(Functions) == 1) { +#if defined(LUABRIDGE_ENABLE_REFLECT) + + ([&] + { + using ArgsPack = detail::function_arguments_t; + + auto* overload_set_unaligned = lua_newuserdata_aligned(L); + auto* overload_set = align(overload_set_unaligned); + + detail::OverloadEntry entry; + entry.arity = static_cast(detail::function_arity_excluding_v); + entry.checker = &detail::overload_type_checker; + entry.returnType = std::string(detail::typeName()); + entry.paramTypes = detail::reflect_param_type_names(); + overload_set->entries.push_back(std::move(entry)); + + lua_createtable(L, 1, 0); + lua_pushcclosure_x(L, &detail::constructor_placement_proxy, className, 0); + lua_rawseti(L, -2, 1); + lua_pushcclosure_x(L, &detail::try_overload_functions, className, 2); + + } (), ...); +#else ([&] { lua_pushcclosure_x(L, &detail::constructor_placement_proxy>, className, 0); } (), ...); +#endif } else { @@ -11335,6 +13346,10 @@ class Namespace : public detail::Registrar detail::OverloadEntry entry; entry.arity = static_cast(detail::function_arity_excluding_v); entry.checker = &detail::overload_type_checker; +#if defined(LUABRIDGE_ENABLE_REFLECT) + entry.returnType = std::string(detail::typeName()); + entry.paramTypes = detail::reflect_param_type_names(); +#endif overload_set->entries.push_back(entry); } (), ...); @@ -11371,11 +13386,36 @@ class Namespace : public detail::Registrar { ([&] { - using F = detail::constructor_forwarder; + using InnerF = detail::unwrap_fn_type_t; + using F = detail::constructor_forwarder; - lua_newuserdata_aligned(L, F(std::move(functions))); - lua_pushcclosure_x(L, &detail::invoke_proxy_constructor, className, 1); +#if defined(LUABRIDGE_ENABLE_REFLECT) + + auto* overload_set_unaligned = lua_newuserdata_aligned(L); + auto* overload_set = align(overload_set_unaligned); + + { + + using ArgsPack = detail::remove_first_type_t>; + detail::OverloadEntry entry; + entry.arity = static_cast(detail::function_arity_excluding_v) - 1; + entry.checker = &detail::overload_type_checker; + entry.returnType = std::string(detail::typeName()); + entry.paramTypes = detail::reflect_param_type_names(); + if constexpr (detail::is_function_with_hints_v) + entry.paramHints = functions.hints; + overload_set->entries.push_back(std::move(entry)); + } + lua_createtable(L, 1, 0); + lua_newuserdata_aligned(L, F(detail::get_underlying(std::move(functions)))); + lua_pushcclosure_x(L, &detail::invoke_proxy_constructor, className, 1); + lua_rawseti(L, -2, 1); + lua_pushcclosure_x(L, &detail::try_overload_functions, className, 2); +#else + lua_newuserdata_aligned(L, F(detail::get_underlying(std::move(functions)))); + lua_pushcclosure_x(L, &detail::invoke_proxy_constructor, className, 1); +#endif } (), ...); } else @@ -11386,8 +13426,10 @@ class Namespace : public detail::Registrar ([&] { + using InnerF = detail::unwrap_fn_type_t; + detail::OverloadEntry entry; - if constexpr (detail::is_any_cfunction_pointer_v) + if constexpr (detail::is_any_cfunction_pointer_v) { entry.arity = -1; entry.checker = nullptr; @@ -11395,9 +13437,15 @@ class Namespace : public detail::Registrar else { - using ArgsPack = detail::remove_first_type_t>; - entry.arity = static_cast(detail::function_arity_excluding_v) - 1; + using ArgsPack = detail::remove_first_type_t>; + entry.arity = static_cast(detail::function_arity_excluding_v) - 1; entry.checker = &detail::overload_type_checker; +#if defined(LUABRIDGE_ENABLE_REFLECT) + entry.returnType = std::string(detail::typeName()); + entry.paramTypes = detail::reflect_param_type_names(); + if constexpr (detail::is_function_with_hints_v) + entry.paramHints = functions.hints; +#endif } overload_set->entries.push_back(entry); @@ -11409,9 +13457,10 @@ class Namespace : public detail::Registrar ([&] { - using F = detail::constructor_forwarder; + using InnerF = detail::unwrap_fn_type_t; + using F = detail::constructor_forwarder; - lua_newuserdata_aligned(L, F(std::move(functions))); + lua_newuserdata_aligned(L, F(detail::get_underlying(std::move(functions)))); lua_pushcclosure_x(L, &detail::invoke_proxy_constructor, className, 1); lua_rawseti(L, -2, idx++); @@ -11926,11 +13975,44 @@ class Namespace : public detail::Registrar if constexpr (sizeof...(Functions) == 1) { +#if defined(LUABRIDGE_ENABLE_REFLECT) + + ([&] + { + auto* overload_set_unaligned = lua_newuserdata_aligned(L); + auto* overload_set = align(overload_set_unaligned); + + detail::OverloadEntry entry; + if constexpr (!detail::is_any_cfunction_pointer_v) + { + using ArgsPack = detail::function_arguments_t; + entry.arity = static_cast(detail::function_arity_excluding_v); + entry.checker = &detail::overload_type_checker; + entry.returnType = std::string(detail::typeName>()); + entry.paramTypes = detail::reflect_param_type_names(); + if constexpr (detail::is_function_with_hints_v) + entry.paramHints = functions.hints; + } + else + { + entry.arity = -1; + entry.checker = nullptr; + } + overload_set->entries.push_back(std::move(entry)); + + lua_createtable(L, 1, 0); + detail::push_function(L, detail::get_underlying(std::move(functions)), name); + lua_rawseti(L, -2, 1); + lua_pushcclosure_x(L, &detail::try_overload_functions, name, 2); + + } (), ...); +#else ([&] { - detail::push_function(L, std::move(functions), name); + detail::push_function(L, detail::get_underlying(std::move(functions)), name); } (), ...); +#endif } else { @@ -11951,6 +14033,12 @@ class Namespace : public detail::Registrar using ArgsPack = detail::function_arguments_t; entry.arity = static_cast(detail::function_arity_excluding_v); entry.checker = &detail::overload_type_checker; +#if defined(LUABRIDGE_ENABLE_REFLECT) + entry.returnType = std::string(detail::typeName>()); + entry.paramTypes = detail::reflect_param_type_names(); + if constexpr (detail::is_function_with_hints_v) + entry.paramHints = functions.hints; +#endif } overload_set->entries.push_back(entry); @@ -11962,7 +14050,7 @@ class Namespace : public detail::Registrar ([&] { - detail::push_function(L, std::move(functions), name); + detail::push_function(L, detail::get_underlying(std::move(functions)), name); lua_rawseti(L, -2, idx++); } (), ...); @@ -11975,6 +14063,22 @@ class Namespace : public detail::Registrar return *this; } +#if LUABRIDGE_HAS_CXX20_COROUTINES + + template + auto addCoroutine(const char* name, F factory) + -> std::enable_if_t, Namespace&> + { + LUABRIDGE_ASSERT(name != nullptr); + LUABRIDGE_ASSERT(lua_istable(L, -1)); + + detail::push_coroutine_function(L, std::move(factory), name); + rawsetfield(L, -2, name); + + return *this; + } +#endif + Table beginTable(const char* name) { assertIsActive(); diff --git a/Images/benchmarks.png b/Images/benchmarks.png index f015a46a20e74f8aa656d0cac374147d7256844c..50009c8bad9921ffc1021db4bc3ea3e68b9f2e90 100644 GIT binary patch literal 168214 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

v0Rq4367qy{pR;v-e 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

fTQIxtiDF zHRGuP)!+dOoF;`2YXz2;mU@BtaG=Pag{7Of?#7Kg(QZ*ysN%AFQzoPGy_;EOy_nMB zt_)MXbO%g^K5Pc*^;;>KQng>|Ej%rM2Mde94Q4v~xyAudIjxgvQZ(L4E!B0R2In+~ z-M)Q$|I+xKWE^v1W2*4S^!6chkC`E^nYh6S4X?m3k(Or+o=|}C$%;DRs3BtduCSvQ z3^=CAUs_YH?>zkTnsD~(oiEDP|3|(%iDqmP*=f}v +#include +#include +#include +#include + +namespace luabridge { + +//================================================================================================= +// Forward declarations + +struct NamespaceInspectInfo; +struct ClassInspectInfo; +class InspectVisitor; + +void accept(const NamespaceInspectInfo& ns, InspectVisitor& v); +void accept(const ClassInspectInfo& cls, InspectVisitor& v); + +//================================================================================================= +/** + * @brief Kind of a registered member. + */ +enum class MemberKind +{ + Method, + StaticMethod, + Property, + ReadOnlyProperty, + StaticProperty, + StaticReadOnlyProperty, + Constructor, + Metamethod, +}; + +//================================================================================================= +/** + * @brief Type and optional name hint for one Lua-visible parameter of an overload. + */ +struct ParamInfo +{ + std::string typeName; ///< C++ type name (e.g. "float", "int", "MyClass"). Empty if unavailable. + std::string hint; ///< Optional user-provided name (e.g. "damage"). Empty if not provided. +}; + +//================================================================================================= +/** + * @brief Type information for one overload of a registered function or constructor. + */ +struct OverloadInfo +{ + std::string returnType; ///< C++ return type name. Empty if unavailable. + std::vector params; ///< One entry per Lua-visible parameter. + bool isConst = false; ///< True when this is a const member function. +}; + +//================================================================================================= +/** + * @brief Information about one registered member (method, property, constructor, …). + */ +struct MemberInfo +{ + std::string name; + MemberKind kind = MemberKind::Method; + std::vector overloads; ///< At least 1 entry; may contain empty OverloadInfo. +}; + +//================================================================================================= +/** + * @brief Inspection result for one registered class. + */ +struct ClassInspectInfo +{ + std::string name; + std::vector baseClasses; ///< Names of all registered ancestor classes. + std::vector members; + + void accept(InspectVisitor& v) const; +}; + +//================================================================================================= +/** + * @brief Inspection result for a namespace (may contain classes and sub-namespaces). + */ +struct NamespaceInspectInfo +{ + std::string name; + std::vector freeMembers; ///< Free functions and namespace-level properties. + std::vector classes; + std::vector subNamespaces; + + void accept(InspectVisitor& v) const; +}; + +//================================================================================================= +/** + * @brief Visitor interface for traversing an inspection result tree. + * + * Override the methods you care about. Default implementations are no-ops so you only + * need to override what you need. + */ +class InspectVisitor +{ +public: + virtual ~InspectVisitor() = default; + + virtual void beginNamespace(const NamespaceInspectInfo& /*ns*/) {} + virtual void endNamespace(const NamespaceInspectInfo& /*ns*/) {} + virtual void visitFreeMember(const NamespaceInspectInfo& /*ns*/, const MemberInfo& /*m*/) {} + + virtual void beginClass(const ClassInspectInfo& /*cls*/) {} + virtual void endClass(const ClassInspectInfo& /*cls*/) {} + virtual void visitMember(const ClassInspectInfo& /*cls*/, const MemberInfo& /*m*/) {} +}; + +//================================================================================================= +/** + * @brief Traverse a namespace inspection result, calling visitor callbacks in order. + */ +inline void accept(const NamespaceInspectInfo& ns, InspectVisitor& v) +{ + v.beginNamespace(ns); + for (const auto& m : ns.freeMembers) + v.visitFreeMember(ns, m); + for (const auto& cls : ns.classes) + accept(cls, v); + for (const auto& sub : ns.subNamespaces) + accept(sub, v); + v.endNamespace(ns); +} + +inline void accept(const ClassInspectInfo& cls, InspectVisitor& v) +{ + v.beginClass(cls); + for (const auto& m : cls.members) + v.visitMember(cls, m); + v.endClass(cls); +} + +inline void ClassInspectInfo::accept(InspectVisitor& v) const { luabridge::accept(*this, v); } +inline void NamespaceInspectInfo::accept(InspectVisitor& v) const { luabridge::accept(*this, v); } + +//================================================================================================= +// Internal helpers + +namespace detail { + +// Read all string keys from a Lua table at tableIdx into a set +inline std::set collectTableKeys(lua_State* L, int tableIdx) +{ + std::set keys; + tableIdx = lua_absindex(L, tableIdx); + lua_pushnil(L); + while (lua_next(L, tableIdx)) + { + if (lua_type(L, -2) == LUA_TSTRING) + keys.insert(lua_tostring(L, -2)); + lua_pop(L, 1); // pop value, keep key + } + return keys; +} + +// Strip a "const " prefix from a type name string +inline std::string stripConst(const char* name) +{ + std::string s = name ? name : ""; + if (s.substr(0, 6) == "const ") + s = s.substr(6); + return s; +} + +// Return the overload infos for a function/closure sitting at funcIdx. +// If there's no OverloadSet upvalue (bare cfunction), returns 1 entry with empty type strings. +inline std::vector getOverloadInfos(lua_State* L, int funcIdx) +{ + funcIdx = lua_absindex(L, funcIdx); + + if (!lua_isfunction(L, funcIdx)) + return { OverloadInfo{} }; + + // Check upvalue 1 for an OverloadSet userdata + const char* uv1name = lua_getupvalue(L, funcIdx, 1); + if (uv1name == nullptr) + { + // No upvalues — bare cfunction (single function, no metadata available) + return { OverloadInfo{} }; + } + + bool isOverloadSet = isfulluserdata(L, -1); + lua_pop(L, 1); // pop upvalue 1 + + if (!isOverloadSet) + { + // upvalue 1 is not a full userdata (e.g. lightuserdata function pointer) + return { OverloadInfo{} }; + } + + // Confirm upvalue 2 is a table (the flat function table) + const char* uv2name = lua_getupvalue(L, funcIdx, 2); + bool hasTable = (uv2name != nullptr) && lua_istable(L, -1); + if (uv2name != nullptr) + lua_pop(L, 1); // pop upvalue 2 + + if (!hasTable) + return { OverloadInfo{} }; + + // Re-fetch upvalue 1 to read the OverloadSet + lua_getupvalue(L, funcIdx, 1); + auto* overload_set = align(lua_touserdata(L, -1)); + lua_pop(L, 1); + + if (!overload_set || overload_set->entries.empty()) + return { OverloadInfo{} }; + + std::vector result; + result.reserve(overload_set->entries.size()); + + for (const auto& entry : overload_set->entries) + { + OverloadInfo info; + +#if defined(LUABRIDGE_ENABLE_REFLECT) + info.returnType = entry.returnType; + for (std::size_t i = 0; i < entry.paramTypes.size(); ++i) + { + ParamInfo p; + p.typeName = entry.paramTypes[i]; + if (i < entry.paramHints.size()) + p.hint = entry.paramHints[i]; + info.params.push_back(std::move(p)); + } +#else + // Without LUABRIDGE_ENABLE_REFLECT: emit synthetic params based on arity only + if (entry.arity >= 0) + { + for (int i = 0; i < entry.arity; ++i) + info.params.push_back(ParamInfo{}); + } +#endif + + result.push_back(std::move(info)); + } + + return result; +} + +// Returns true if the table at tableIdx is a class static table (has a metatable with getClassKey). +// Also leaves the class metatable (mt) on the stack top if true (caller must pop it). +// Leaves nothing extra on the stack if false. +inline bool isClassStaticTable(lua_State* L, int tableIdx) +{ + tableIdx = lua_absindex(L, tableIdx); + + if (!lua_getmetatable(L, tableIdx)) // always works from C regardless of __metatable + return false; + + lua_rawgetp(L, -1, getClassKey()); // mt[getClassKey()] = cl ? + bool result = lua_istable(L, -1); + lua_pop(L, 1); // pop cl / nil + + if (!result) + { + lua_pop(L, 1); // pop mt + return false; + } + + // Leave mt on stack (caller needs it) + return true; +} + +// Inspect a class from its static table (st at stIdx). +// Assumes isClassStaticTable(L, stIdx) returned true and left mt on the stack. +inline ClassInspectInfo inspectClassFromStaticTable(lua_State* L, int stIdx) +{ + stIdx = lua_absindex(L, stIdx); + + // mt is on top of the stack (left there by isClassStaticTable) + int mtIdx = lua_absindex(L, -1); + + // Get the class table cl = mt[getClassKey()] + lua_rawgetp(L, mtIdx, getClassKey()); + int clIdx = lua_absindex(L, -1); + + ClassInspectInfo cls; + + // 1. Class name + lua_rawgetp(L, clIdx, getTypeKey()); + if (lua_isstring(L, -1)) + cls.name = stripConst(lua_tostring(L, -1)); + lua_pop(L, 1); + + // 2. Base classes from parent list + lua_rawgetp(L, clIdx, getParentKey()); + if (lua_istable(L, -1)) + { + int parIdx = lua_absindex(L, -1); + int len = static_cast(luaL_len(L, parIdx)); + for (int i = 1; i <= len; ++i) + { + lua_rawgeti(L, parIdx, i); + lua_rawgetp(L, -1, getTypeKey()); + if (lua_isstring(L, -1)) + { + std::string baseName = stripConst(lua_tostring(L, -1)); + if (!baseName.empty() && baseName != cls.name) + cls.baseClasses.push_back(std::move(baseName)); + } + lua_pop(L, 2); // pop typename + parent mt + } + } + lua_pop(L, 1); // pop parent list / nil + + // 3. Collect instance property names + std::set instPropget; + lua_rawgetp(L, clIdx, getPropgetKey()); + if (lua_istable(L, -1)) + instPropget = collectTableKeys(L, -1); + lua_pop(L, 1); + + // Collect instance property names that have a real (non-readonly-sentinel) setter + std::set instPropsetReal; + lua_rawgetp(L, clIdx, getPropsetKey()); + if (lua_istable(L, -1)) + { + int psIdx = lua_absindex(L, -1); + for (const auto& k : instPropget) + { + lua_getfield(L, psIdx, k.c_str()); + if (lua_isfunction(L, -1)) + { + lua_CFunction fn = lua_tocfunction(L, -1); + if (fn != &detail::read_only_error) + instPropsetReal.insert(k); + } + lua_pop(L, 1); + } + } + lua_pop(L, 1); + + // 4. Emit instance properties + for (const auto& propName : instPropget) + { + MemberInfo m; + m.name = propName; + m.kind = instPropsetReal.count(propName) ? MemberKind::Property : MemberKind::ReadOnlyProperty; + m.overloads.push_back(OverloadInfo{}); + cls.members.push_back(std::move(m)); + } + + // 5. Iterate class table for instance methods / metamethods + static const std::set skipKeys{ "__index", "__newindex", "__metatable" }; + + lua_pushnil(L); + while (lua_next(L, clIdx)) + { + if (lua_type(L, -2) == LUA_TSTRING) + { + std::string key = lua_tostring(L, -2); + if (!skipKeys.count(key) && !instPropget.count(key)) + { + if (lua_isfunction(L, -1) || lua_isuserdata(L, -1)) + { + MemberInfo m; + m.name = key; + m.kind = (key.size() >= 2 && key[0] == '_' && key[1] == '_') + ? MemberKind::Metamethod + : MemberKind::Method; + m.overloads = getOverloadInfos(L, -1); + cls.members.push_back(std::move(m)); + } + } + } + lua_pop(L, 1); // pop value + } + + // 6. Collect static property names + std::set stPropget; + lua_rawgetp(L, mtIdx, getPropgetKey()); + if (lua_istable(L, -1)) + stPropget = collectTableKeys(L, -1); + lua_pop(L, 1); + + // Collect static property names that have a real (non-readonly-sentinel) setter + std::set stPropsetReal; + lua_rawgetp(L, mtIdx, getPropsetKey()); + if (lua_istable(L, -1)) + { + int psIdx = lua_absindex(L, -1); + for (const auto& k : stPropget) + { + lua_getfield(L, psIdx, k.c_str()); + if (lua_isfunction(L, -1)) + { + lua_CFunction fn = lua_tocfunction(L, -1); + if (fn != &detail::read_only_error) + stPropsetReal.insert(k); + } + lua_pop(L, 1); + } + } + lua_pop(L, 1); + + // 7. Emit static properties + for (const auto& propName : stPropget) + { + MemberInfo m; + m.name = propName; + m.kind = stPropsetReal.count(propName) ? MemberKind::StaticProperty : MemberKind::StaticReadOnlyProperty; + m.overloads.push_back(OverloadInfo{}); + cls.members.push_back(std::move(m)); + } + + // 8. Iterate mt for static methods + constructor + static const std::set staticSkipKeys{ "__index", "__newindex", "__metatable" }; + + lua_rawgetp(L, mtIdx, getClassKey()); // just to see mt's string keys, iterate mt directly + lua_pop(L, 1); + + lua_pushnil(L); + while (lua_next(L, mtIdx)) + { + if (lua_type(L, -2) == LUA_TSTRING) + { + std::string key = lua_tostring(L, -2); + if (!staticSkipKeys.count(key) && !stPropget.count(key)) + { + if (lua_isfunction(L, -1) || lua_isuserdata(L, -1)) + { + MemberInfo m; + m.name = key; + if (key == "__call") + m.kind = MemberKind::Constructor; + else if (key.size() >= 2 && key[0] == '_' && key[1] == '_') + m.kind = MemberKind::Metamethod; + else + m.kind = MemberKind::StaticMethod; + m.overloads = getOverloadInfos(L, -1); + cls.members.push_back(std::move(m)); + } + } + } + lua_pop(L, 1); + } + + lua_pop(L, 2); // pop cl, mt + return cls; +} + +// Inspect a namespace table at nsIdx +inline NamespaceInspectInfo inspectNamespaceTable(lua_State* L, int nsIdx, std::string name); + +inline NamespaceInspectInfo inspectNamespaceTable(lua_State* L, int nsIdx, std::string name) +{ + nsIdx = lua_absindex(L, nsIdx); + NamespaceInspectInfo info; + info.name = std::move(name); + + // Namespace-level properties (stored directly on the namespace table via getPropgetKey) + std::set nsPropget; + lua_rawgetp(L, nsIdx, getPropgetKey()); + if (lua_istable(L, -1)) + nsPropget = collectTableKeys(L, -1); + lua_pop(L, 1); + + // Get the propset table so we can check each setter individually + int nsPropsetTableIdx = 0; + lua_rawgetp(L, nsIdx, getPropsetKey()); + if (lua_istable(L, -1)) + nsPropsetTableIdx = lua_absindex(L, -1); + // leave propset table on stack; we pop it after the loop + + for (const auto& propName : nsPropget) + { + MemberInfo m; + m.name = propName; + + // A getter-only namespace property still gets a read_only_error sentinel in propset. + // Detect the sentinel by comparing the setter's underlying C function pointer. + bool isReadOnly = true; + if (nsPropsetTableIdx) + { + lua_getfield(L, nsPropsetTableIdx, propName.c_str()); + if (lua_isfunction(L, -1)) + { + lua_CFunction fn = lua_tocfunction(L, -1); + isReadOnly = (fn == &detail::read_only_error); + } + lua_pop(L, 1); + } + + m.kind = isReadOnly ? MemberKind::ReadOnlyProperty : MemberKind::Property; + m.overloads.push_back(OverloadInfo{}); + info.freeMembers.push_back(std::move(m)); + } + + lua_pop(L, 1); // pop propset table (or nil) + + // Iterate namespace table entries + static const std::set nsSkipKeys{ "__index", "__newindex", "__metatable", "__gc" }; + + lua_pushnil(L); + while (lua_next(L, nsIdx)) + { + if (lua_type(L, -2) != LUA_TSTRING) + { + lua_pop(L, 1); + continue; + } + + std::string key = lua_tostring(L, -2); + + if (nsSkipKeys.count(key) || nsPropget.count(key)) + { + lua_pop(L, 1); + continue; + } + + if (lua_istable(L, -1)) + { + int valIdx = lua_absindex(L, -1); + + if (isClassStaticTable(L, valIdx)) + { + // mt is now on top of the stack (left by isClassStaticTable) + auto cls = inspectClassFromStaticTable(L, valIdx); + // inspectClassFromStaticTable pops mt and cl + info.classes.push_back(std::move(cls)); + } + else + { + auto sub = inspectNamespaceTable(L, valIdx, key); + info.subNamespaces.push_back(std::move(sub)); + } + } + else if (lua_isfunction(L, -1)) + { + if (!nsPropget.count(key)) + { + MemberInfo m; + m.name = key; + m.kind = MemberKind::Method; + m.overloads = getOverloadInfos(L, -1); + info.freeMembers.push_back(std::move(m)); + } + } + + lua_pop(L, 1); // pop value + } + + return info; +} + +} // namespace detail + +//================================================================================================= +/** + * @brief Inspect a single registered class by its C++ type. + * + * Uses the compile-time registry key — no namespace traversal required. + * + * @returns Inspection result, or an empty ClassInspectInfo if T is not registered. + */ +template +[[nodiscard]] ClassInspectInfo inspect(lua_State* L) +{ + // Load the class table (cl) directly from the Lua registry + lua_rawgetp(L, LUA_REGISTRYINDEX, detail::getClassRegistryKey()); + if (!lua_istable(L, -1)) + { + lua_pop(L, 1); + return {}; + } + // Stack: ..., cl + + // cl[getStaticKey()] = st (the internal static table that acts as a metatable) + // NOTE: this is NOT the user-visible ns["ClassName"] table; it is the metatable of that table. + // isClassStaticTable() cannot be used here because st itself has no metatable. + lua_rawgetp(L, -1, detail::getStaticKey()); + if (!lua_istable(L, -1)) + { + lua_pop(L, 2); + return {}; + } + // Stack: ..., cl, st + + // Verify st actually belongs to a registered class: st[getClassKey()] must be a table + lua_rawgetp(L, -1, detail::getClassKey()); + const bool isClass = lua_istable(L, -1); + lua_pop(L, 1); + + if (!isClass) + { + lua_pop(L, 2); + return {}; + } + // Stack: ..., cl, st (st is at top — inspectClassFromStaticTable reads it as "mt") + + ClassInspectInfo result = detail::inspectClassFromStaticTable(L, lua_gettop(L)); + // inspectClassFromStaticTable pops st (used as mt) and the cl it fetched from st[getClassKey()] + // Stack: ..., cl + + lua_pop(L, 1); // pop cl + return result; +} + +//================================================================================================= +/** + * @brief Inspect a namespace by name. + * + * @param namespaceName The global name of the namespace (e.g. "MyNS"), or nullptr / "" for the + * global namespace. Supports dotted paths ("Outer.Inner"). + * + * @returns Inspection result, or an empty NamespaceInspectInfo if the name is not found. + */ +[[nodiscard]] inline NamespaceInspectInfo inspectNamespace(lua_State* L, const char* namespaceName = nullptr) +{ + if (namespaceName == nullptr || namespaceName[0] == '\0') + { + lua_pushglobaltable(L); + } + else + { + // Support dotted paths by navigating component by component + std::string path = namespaceName; + std::string first; + std::string::size_type dot = path.find('.'); + if (dot == std::string::npos) + first = path; + else + first = path.substr(0, dot); + + lua_getglobal(L, first.c_str()); + if (!lua_istable(L, -1)) + { + lua_pop(L, 1); + return {}; + } + + while (dot != std::string::npos) + { + path = path.substr(dot + 1); + dot = path.find('.'); + std::string component = (dot == std::string::npos) ? path : path.substr(0, dot); + lua_getfield(L, -1, component.c_str()); + lua_remove(L, -2); // remove previous table + if (!lua_istable(L, -1)) + { + lua_pop(L, 1); + return {}; + } + } + } + + // Use only the leaf component of a dotted path as the display name + std::string label; + if (namespaceName && namespaceName[0]) + { + std::string_view sv(namespaceName); + auto dot = sv.rfind('.'); + label = std::string(dot == std::string_view::npos ? sv : sv.substr(dot + 1)); + } + else + { + label = "_G"; + } + auto result = detail::inspectNamespaceTable(L, -1, label); + lua_pop(L, 1); + return result; +} + +//================================================================================================= +/** + * @brief Collect inspection data for a namespace and drive a visitor over it. + */ +inline void inspectAccept(lua_State* L, const char* namespaceName, InspectVisitor& visitor) +{ + auto ns = inspectNamespace(L, namespaceName); + accept(ns, visitor); +} + +//================================================================================================= +// Built-in visitors + +//================================================================================================= +/** + * @brief Visitor that emits TypeScript-style pseudo-code to an ostream. + * + * Useful for quick debugging and documentation. + * + * Example output: + * @code + * namespace MyNS { + * class Foo extends Base { + * constructor(); + * method(p1: float): void; + * readonly value: int; + * static create(): Foo; + * } + * } + * @endcode + */ +class ConsoleVisitor : public InspectVisitor +{ +public: + explicit ConsoleVisitor(std::ostream& out = std::cerr) + : out_(out) + { + } + + void beginNamespace(const NamespaceInspectInfo& ns) override + { + indent(); + out_ << "namespace " << ns.name << " {\n"; + ++depth_; + } + + void endNamespace(const NamespaceInspectInfo& /*ns*/) override + { + --depth_; + indent(); + out_ << "}\n"; + } + + void visitFreeMember(const NamespaceInspectInfo& /*ns*/, const MemberInfo& m) override + { + emitMember(m, /*className=*/""); + } + + void beginClass(const ClassInspectInfo& cls) override + { + indent(); + out_ << "class " << cls.name; + if (!cls.baseClasses.empty()) + { + out_ << " extends "; + for (std::size_t i = 0; i < cls.baseClasses.size(); ++i) + { + if (i) out_ << ", "; + out_ << cls.baseClasses[i]; + } + } + out_ << " {\n"; + ++depth_; + } + + void endClass(const ClassInspectInfo& /*cls*/) override + { + --depth_; + indent(); + out_ << "}\n"; + } + + void visitMember(const ClassInspectInfo& cls, const MemberInfo& m) override + { + emitMember(m, cls.name); + } + +private: + void indent() const + { + for (int i = 0; i < depth_; ++i) + out_ << " "; + } + + static std::string paramStr(const OverloadInfo& ov) + { + std::string s; + for (std::size_t i = 0; i < ov.params.size(); ++i) + { + if (i) s += ", "; + const auto& p = ov.params[i]; + s += p.hint.empty() ? ("p" + std::to_string(i + 1)) : p.hint; + s += ": "; + s += p.typeName.empty() ? "any" : p.typeName; + } + return s; + } + + static std::string retStr(const OverloadInfo& ov, const std::string& constructorClass = "") + { + if (!constructorClass.empty()) + return constructorClass; + return ov.returnType.empty() ? "any" : ov.returnType; + } + + void emitMember(const MemberInfo& m, const std::string& className) const + { + switch (m.kind) + { + case MemberKind::Property: + indent(); out_ << m.name << ": any;\n"; break; + case MemberKind::ReadOnlyProperty: + indent(); out_ << "readonly " << m.name << ": any;\n"; break; + case MemberKind::StaticProperty: + indent(); out_ << "static " << m.name << ": any;\n"; break; + case MemberKind::StaticReadOnlyProperty: + indent(); out_ << "static readonly " << m.name << ": any;\n"; break; + case MemberKind::Metamethod: + // Skip metamethods in TypeScript-style output + break; + case MemberKind::Constructor: + for (const auto& ov : m.overloads) + { + indent(); + out_ << "constructor(" << paramStr(ov) << ");\n"; + } + break; + case MemberKind::Method: + case MemberKind::StaticMethod: + { + bool isStatic = (m.kind == MemberKind::StaticMethod); + for (std::size_t i = 0; i < m.overloads.size(); ++i) + { + indent(); + if (isStatic) out_ << "static "; + out_ << m.name << "(" << paramStr(m.overloads[i]) << "): " + << retStr(m.overloads[i]) << ";\n"; + } + break; + } + default: + break; + } + (void)className; + } + + std::ostream& out_; + int depth_ = 0; +}; + +//================================================================================================= +/** + * @brief Visitor that emits LuaLS (Lua Language Server) / LuaCATS annotation stubs. + * + * Suitable for generating `.lua` type stub files consumed by lua-language-server. + * + * @see https://luals.github.io/wiki/annotations/ + */ +class LuaLSVisitor : public InspectVisitor +{ +public: + explicit LuaLSVisitor(std::ostream& out) + : out_(out) + { + } + + void beginNamespace(const NamespaceInspectInfo& ns) override + { + ns_ = ns.name == "_G" ? "" : ns.name; + } + + void endNamespace(const NamespaceInspectInfo& /*ns*/) override {} + + void visitFreeMember(const NamespaceInspectInfo& ns, const MemberInfo& m) override + { + if (m.kind != MemberKind::Method && m.kind != MemberKind::StaticMethod) + return; // properties not directly emittable as LuaLS annotations easily + + for (const auto& ov : m.overloads) + { + emitParams(ov.params); + if (!ov.returnType.empty() && ov.returnType != "void") + out_ << "---@return " << luaType(ov.returnType) << "\n"; + } + std::string qual = ns_.empty() ? "" : (ns_ + "."); + out_ << "function " << qual << m.name << "("; + if (!m.overloads.empty()) + out_ << paramNames(m.overloads[0].params); + out_ << ") end\n\n"; + } + + void beginClass(const ClassInspectInfo& cls) override + { + curClass_ = cls.name; + out_ << "---@class " << qualifiedName(cls.name); + if (!cls.baseClasses.empty()) + { + out_ << " : " << cls.baseClasses[0]; + for (std::size_t i = 1; i < cls.baseClasses.size(); ++i) + out_ << ", " << cls.baseClasses[i]; + } + out_ << "\n"; + + // Emit fields for properties in the class body annotation + // (will be added in visitMember below, but need the class annotation open) + } + + void endClass(const ClassInspectInfo& /*cls*/) override + { + out_ << "local " << curClass_ << " = {}\n\n"; + curClass_.clear(); + } + + void visitMember(const ClassInspectInfo& cls, const MemberInfo& m) override + { + switch (m.kind) + { + case MemberKind::Property: + out_ << "---@field " << m.name << " any\n"; + break; + case MemberKind::ReadOnlyProperty: + out_ << "---@field " << m.name << " any # readonly\n"; + break; + case MemberKind::StaticProperty: + out_ << "---@field " << m.name << " any\n"; + break; + case MemberKind::StaticReadOnlyProperty: + out_ << "---@field " << m.name << " any # readonly\n"; + break; + case MemberKind::Constructor: + { + out_ << "\n"; + for (std::size_t i = 0; i < m.overloads.size(); ++i) + { + const auto& ov = m.overloads[i]; + if (i == 0) + { + emitParams(ov.params); + out_ << "---@return " << cls.name << "\n"; + out_ << "function " << cls.name << ".new(" << paramNames(ov.params) << ") end\n"; + } + else + { + out_ << "---@overload fun(" << overloadParams(ov.params) << "): " << cls.name << "\n"; + } + } + break; + } + case MemberKind::Method: + { + out_ << "\n"; + for (std::size_t i = 0; i < m.overloads.size(); ++i) + { + const auto& ov = m.overloads[i]; + if (i == 0) + { + emitParams(ov.params, /*hasSelf=*/true); + if (!ov.returnType.empty() && ov.returnType != "void") + out_ << "---@return " << luaType(ov.returnType) << "\n"; + out_ << "function " << cls.name << ":" << m.name + << "(" << paramNames(ov.params) << ") end\n"; + } + else + { + out_ << "---@overload fun(self: " << cls.name << ", " << overloadParams(ov.params) << ")" + << (ov.returnType.empty() ? "" : (": " + luaType(ov.returnType))) << "\n"; + } + } + break; + } + case MemberKind::StaticMethod: + { + out_ << "\n"; + for (std::size_t i = 0; i < m.overloads.size(); ++i) + { + const auto& ov = m.overloads[i]; + if (i == 0) + { + emitParams(ov.params); + if (!ov.returnType.empty() && ov.returnType != "void") + out_ << "---@return " << luaType(ov.returnType) << "\n"; + out_ << "function " << cls.name << "." << m.name + << "(" << paramNames(ov.params) << ") end\n"; + } + else + { + out_ << "---@overload fun(" << overloadParams(ov.params) << ")" + << (ov.returnType.empty() ? "" : (": " + luaType(ov.returnType))) << "\n"; + } + } + break; + } + default: + break; + } + } + +private: + // Minimal C++ → Lua type name mapping + static std::string luaType(const std::string& cppType) + { + if (cppType == "void") return "nil"; + if (cppType == "bool") return "boolean"; + if (cppType == "int" || cppType == "long" || cppType == "short" || + cppType == "unsigned int" || cppType == "unsigned long" || + cppType == "unsigned short" || cppType == "int64_t" || cppType == "uint64_t" || + cppType == "int32_t" || cppType == "uint32_t" || cppType == "size_t") + return "integer"; + if (cppType == "float" || cppType == "double") return "number"; + if (cppType == "std::string" || cppType == "const char *" || cppType == "const char*") + return "string"; + return cppType.empty() ? "any" : cppType; + } + + std::string qualifiedName(const std::string& name) const + { + return ns_.empty() ? name : (ns_ + "." + name); + } + + static std::string paramNames(const std::vector& params) + { + std::string s; + for (std::size_t i = 0; i < params.size(); ++i) + { + if (i) s += ", "; + s += params[i].hint.empty() ? ("p" + std::to_string(i + 1)) : params[i].hint; + } + return s; + } + + static std::string overloadParams(const std::vector& params) + { + std::string s; + for (std::size_t i = 0; i < params.size(); ++i) + { + if (i) s += ", "; + const auto& p = params[i]; + std::string pname = p.hint.empty() ? ("p" + std::to_string(i + 1)) : p.hint; + s += pname + ": " + luaType(p.typeName.empty() ? "any" : p.typeName); + } + return s; + } + + void emitParams(const std::vector& params, bool hasSelf = false) const + { + if (hasSelf) + out_ << "---@param self " << curClass_ << "\n"; + for (std::size_t i = 0; i < params.size(); ++i) + { + const auto& p = params[i]; + std::string pname = p.hint.empty() ? ("p" + std::to_string(i + 1)) : p.hint; + std::string ptype = p.typeName.empty() ? "any" : luaType(p.typeName); + out_ << "---@param " << pname << " " << ptype << "\n"; + } + } + + std::ostream& out_; + std::string ns_; + std::string curClass_; +}; + +//================================================================================================= +/** + * @brief Visitor that emits a real Lua stub file (require()-able, mockable in tests). + * + * Each class is emitted as a module-local table with stub methods. + * Namespace-level functions are emitted as module-level functions. + */ +class LuaProxyVisitor : public InspectVisitor +{ +public: + explicit LuaProxyVisitor(std::ostream& out) + : out_(out) + { + } + + void beginNamespace(const NamespaceInspectInfo& ns) override + { + if (ns.name != "_G") + out_ << "local " << ns.name << " = {}\n\n"; + } + + void endNamespace(const NamespaceInspectInfo& ns) override + { + if (ns.name != "_G") + out_ << "return " << ns.name << "\n"; + } + + void visitFreeMember(const NamespaceInspectInfo& ns, const MemberInfo& m) override + { + if (m.kind != MemberKind::Method && m.kind != MemberKind::StaticMethod) + return; + std::string qual = (ns.name == "_G") ? "" : (ns.name + "."); + if (!m.overloads.empty()) + { + out_ << "function " << qual << m.name + << "(" << paramNames(m.overloads[0].params) << ") end\n"; + } + } + + void beginClass(const ClassInspectInfo& cls) override + { + curClass_ = cls.name; + out_ << cls.name << " = {}\n"; + out_ << cls.name << ".__index = " << cls.name << "\n\n"; + } + + void endClass(const ClassInspectInfo& /*cls*/) override + { + curClass_.clear(); + out_ << "\n"; + } + + void visitMember(const ClassInspectInfo& /*cls*/, const MemberInfo& m) override + { + if (m.overloads.empty()) + return; + + switch (m.kind) + { + case MemberKind::Constructor: + out_ << "function " << curClass_ << ".new(" + << paramNames(m.overloads[0].params) << ")\n" + << " return setmetatable({}, " << curClass_ << ")\n" + << "end\n\n"; + break; + case MemberKind::Method: + out_ << "function " << curClass_ << ":" << m.name + << "(" << paramNames(m.overloads[0].params) << ") end\n"; + break; + case MemberKind::StaticMethod: + out_ << "function " << curClass_ << "." << m.name + << "(" << paramNames(m.overloads[0].params) << ") end\n"; + break; + case MemberKind::Property: + case MemberKind::ReadOnlyProperty: + case MemberKind::StaticProperty: + case MemberKind::StaticReadOnlyProperty: + // Properties are part of the table, no explicit function needed + break; + default: + break; + } + } + +private: + static std::string paramNames(const std::vector& params) + { + std::string s; + for (std::size_t i = 0; i < params.size(); ++i) + { + if (i) s += ", "; + s += params[i].hint.empty() ? ("p" + std::to_string(i + 1)) : params[i].hint; + } + return s; + } + + std::ostream& out_; + std::string curClass_; +}; + +//================================================================================================= +/** + * @brief Visitor that emits a JSON representation of the inspection tree. + */ +class JsonVisitor : public InspectVisitor +{ +public: + explicit JsonVisitor(std::ostream& out) + : out_(out) + { + } + + void beginNamespace(const NamespaceInspectInfo& ns) override + { + if (depth_ == 0) out_ << "{\n"; + indent(); out_ << "\"name\": \"" << escape(ns.name) << "\",\n"; + indent(); out_ << "\"freeMembers\": [],\n"; + indent(); out_ << "\"classes\": [\n"; + ++depth_; + firstClass_ = true; + } + + void endNamespace(const NamespaceInspectInfo& /*ns*/) override + { + --depth_; + out_ << "\n"; + indent(); out_ << "]\n"; + if (depth_ == 0) out_ << "}\n"; + } + + void beginClass(const ClassInspectInfo& cls) override + { + if (!firstClass_) out_ << ",\n"; + firstClass_ = false; + indent(); out_ << "{\n"; + ++depth_; + indent(); out_ << "\"name\": \"" << escape(cls.name) << "\",\n"; + indent(); out_ << "\"bases\": ["; + for (std::size_t i = 0; i < cls.baseClasses.size(); ++i) + { + if (i) out_ << ", "; + out_ << "\"" << escape(cls.baseClasses[i]) << "\""; + } + out_ << "],\n"; + indent(); out_ << "\"members\": [\n"; + ++depth_; + firstMember_ = true; + } + + void endClass(const ClassInspectInfo& /*cls*/) override + { + --depth_; + out_ << "\n"; + indent(); out_ << "]\n"; + --depth_; + indent(); out_ << "}"; + } + + void visitMember(const ClassInspectInfo& /*cls*/, const MemberInfo& m) override + { + if (!firstMember_) out_ << ",\n"; + firstMember_ = false; + indent(); + out_ << "{ \"name\": \"" << escape(m.name) << "\"" + << ", \"kind\": \"" << kindStr(m.kind) << "\"" + << ", \"overloads\": " << m.overloads.size() + << " }"; + } + +private: + void indent() const + { + for (int i = 0; i < depth_; ++i) + out_ << " "; + } + + static std::string escape(const std::string& s) + { + std::string out; + for (char c : s) + { + if (c == '"') out += "\\\""; + else if (c == '\\') out += "\\\\"; + else out += c; + } + return out; + } + + static const char* kindStr(MemberKind k) + { + switch (k) + { + case MemberKind::Method: return "method"; + case MemberKind::StaticMethod: return "static_method"; + case MemberKind::Property: return "property"; + case MemberKind::ReadOnlyProperty: return "readonly_property"; + case MemberKind::StaticProperty: return "static_property"; + case MemberKind::StaticReadOnlyProperty: return "static_readonly_property"; + case MemberKind::Constructor: return "constructor"; + case MemberKind::Metamethod: return "metamethod"; + default: return "unknown"; + } + } + + std::ostream& out_; + int depth_ = 0; + bool firstClass_ = true; + bool firstMember_ = true; +}; + +//================================================================================================= +/** + * @brief Visitor that builds a structured Lua table on the Lua stack. + * + * After calling accept() with this visitor, the stack top is a table with structure: + * @code + * { + * name = "MyNS", + * freeMembers = { {name=…, kind=…, overloads=…}, … }, + * classes = { + * { name=…, bases={…}, members={ {name=…, kind=…, overloads=N}, … } }, + * … + * }, + * subNamespaces = { … } + * } + * @endcode + */ +class LuaTableVisitor : public InspectVisitor +{ +public: + explicit LuaTableVisitor(lua_State* L) + : L_(L) + { + } + + void beginNamespace(const NamespaceInspectInfo& ns) override + { + lua_newtable(L_); // the namespace table + lua_pushstring(L_, ns.name.c_str()); + lua_setfield(L_, -2, "name"); + + lua_newtable(L_); + lua_setfield(L_, -2, "freeMembers"); + freeMemberIdx_ = 1; + + lua_newtable(L_); + lua_setfield(L_, -2, "classes"); + classIdx_ = 1; + + lua_newtable(L_); + lua_setfield(L_, -2, "subNamespaces"); + subNsIdx_ = 1; + } + + void endNamespace(const NamespaceInspectInfo& /*ns*/) override + { + // namespace table stays on stack as the result + } + + void visitFreeMember(const NamespaceInspectInfo& /*ns*/, const MemberInfo& m) override + { + lua_getfield(L_, -1, "freeMembers"); + pushMemberInfo(m); + lua_rawseti(L_, -2, freeMemberIdx_++); + lua_pop(L_, 1); + } + + void beginClass(const ClassInspectInfo& cls) override + { + lua_newtable(L_); + lua_pushstring(L_, cls.name.c_str()); + lua_setfield(L_, -2, "name"); + + lua_newtable(L_); + for (std::size_t i = 0; i < cls.baseClasses.size(); ++i) + { + lua_pushstring(L_, cls.baseClasses[i].c_str()); + lua_rawseti(L_, -2, static_cast(i + 1)); + } + lua_setfield(L_, -2, "bases"); + + lua_newtable(L_); + lua_setfield(L_, -2, "members"); + memberIdx_ = 1; + } + + void endClass(const ClassInspectInfo& /*cls*/) override + { + // class table is on top; store it in the namespace's "classes" array + lua_getfield(L_, -2, "classes"); + lua_pushvalue(L_, -2); + lua_rawseti(L_, -2, classIdx_++); + lua_pop(L_, 2); // pop classes table + class table + } + + void visitMember(const ClassInspectInfo& /*cls*/, const MemberInfo& m) override + { + lua_getfield(L_, -1, "members"); + pushMemberInfo(m); + lua_rawseti(L_, -2, memberIdx_++); + lua_pop(L_, 1); + } + +private: + void pushMemberInfo(const MemberInfo& m) + { + lua_newtable(L_); + lua_pushstring(L_, m.name.c_str()); + lua_setfield(L_, -2, "name"); + lua_pushstring(L_, kindStr(m.kind)); + lua_setfield(L_, -2, "kind"); + lua_pushinteger(L_, static_cast(m.overloads.size())); + lua_setfield(L_, -2, "overloads"); + + // Emit overload details when type info is available + lua_newtable(L_); + for (std::size_t i = 0; i < m.overloads.size(); ++i) + { + const auto& ov = m.overloads[i]; + lua_newtable(L_); + lua_pushstring(L_, ov.returnType.c_str()); + lua_setfield(L_, -2, "returnType"); + + lua_newtable(L_); + for (std::size_t j = 0; j < ov.params.size(); ++j) + { + lua_newtable(L_); + lua_pushstring(L_, ov.params[j].typeName.c_str()); + lua_setfield(L_, -2, "type"); + lua_pushstring(L_, ov.params[j].hint.c_str()); + lua_setfield(L_, -2, "hint"); + lua_rawseti(L_, -2, static_cast(j + 1)); + } + lua_setfield(L_, -2, "params"); + lua_rawseti(L_, -2, static_cast(i + 1)); + } + lua_setfield(L_, -2, "overloadDetails"); + } + + static const char* kindStr(MemberKind k) + { + switch (k) + { + case MemberKind::Method: return "method"; + case MemberKind::StaticMethod: return "static_method"; + case MemberKind::Property: return "property"; + case MemberKind::ReadOnlyProperty: return "readonly_property"; + case MemberKind::StaticProperty: return "static_property"; + case MemberKind::StaticReadOnlyProperty: return "static_readonly_property"; + case MemberKind::Constructor: return "constructor"; + case MemberKind::Metamethod: return "metamethod"; + default: return "unknown"; + } + } + + lua_State* L_; + int freeMemberIdx_ = 1; + int classIdx_ = 1; + int subNsIdx_ = 1; + int memberIdx_ = 1; +}; + +//================================================================================================= +/** + * @brief Print a TypeScript-style inspection of a namespace to an ostream. + * + * Convenience wrapper around ConsoleVisitor. + */ +inline void inspectPrint(lua_State* L, const char* namespaceName = nullptr, std::ostream& stream = std::cerr) +{ + auto ns = inspectNamespace(L, namespaceName); + ConsoleVisitor v(stream); + accept(ns, v); +} + +//================================================================================================= +/** + * @brief Push a structured Lua table describing a namespace onto the Lua stack. + * + * Convenience wrapper around LuaTableVisitor. + * Pushes exactly 1 value (the result table). + */ +inline void inspectToLua(lua_State* L, const char* namespaceName = nullptr) +{ + auto ns = inspectNamespace(L, namespaceName); + LuaTableVisitor v(L); + accept(ns, v); +} + +} // namespace luabridge diff --git a/Source/LuaBridge/LuaBridge.h b/Source/LuaBridge/LuaBridge.h index ede78604..b64370e4 100644 --- a/Source/LuaBridge/LuaBridge.h +++ b/Source/LuaBridge/LuaBridge.h @@ -24,6 +24,7 @@ #include "detail/Expected.h" #include "detail/FlagSet.h" #include "detail/FuncTraits.h" +#include "detail/FunctionHints.h" #include "detail/Globals.h" #include "detail/Invoke.h" #include "detail/Iterator.h" diff --git a/Source/LuaBridge/detail/CFunctions.h b/Source/LuaBridge/detail/CFunctions.h index b8621190..0621d1f4 100644 --- a/Source/LuaBridge/detail/CFunctions.h +++ b/Source/LuaBridge/detail/CFunctions.h @@ -1897,6 +1897,12 @@ struct OverloadEntry int arity; // -1 for variadic (lua_CFunction): always attempt TypeChecker checker; // nullptr for variadic: skip type pre-checking + +#if defined(LUABRIDGE_ENABLE_REFLECT) + std::string returnType; ///< C++ return type name (from detail::typeName) + std::vector paramTypes; ///< C++ parameter type names (excluding lua_State*) + std::vector paramHints; ///< Optional user-provided parameter names (from withHints) +#endif }; /** diff --git a/Source/LuaBridge/detail/FunctionHints.h b/Source/LuaBridge/detail/FunctionHints.h new file mode 100644 index 00000000..98c15510 --- /dev/null +++ b/Source/LuaBridge/detail/FunctionHints.h @@ -0,0 +1,176 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2024, kunitoki +// SPDX-License-Identifier: MIT + +#pragma once + +#include "ClassInfo.h" +#include "FuncTraits.h" + +#include +#include +#include + +namespace luabridge { + +//================================================================================================= +/** + * @brief A wrapper that attaches Lua parameter name hints to a function or callable. + * + * Use luabridge::withHints() to create one. Transparently forwards all function trait queries + * to the wrapped function type, so it can be used anywhere a plain function is accepted. + * + * @code + * .addFunction("attack", luabridge::withHints(&Enemy::attack, "target", "damage")) + * @endcode + */ +template +struct FunctionWithHints +{ + using func_type = F; + + F func; + std::vector hints; ///< Optional parameter name hints, one per Lua-visible parameter +}; + +/** + * @brief Attach Lua parameter name hints to a function for use with addFunction / addStaticFunction. + * + * @param func The function, member function pointer, or lambda to wrap. + * @param paramNames Parameter names in the same order as the Lua-visible parameters. + * + * @returns A FunctionWithHints wrapper accepted by all addFunction / addStaticFunction overloads. + */ +template +[[nodiscard]] auto withHints(F&& func, Names&&... paramNames) -> FunctionWithHints> +{ + return { std::forward(func), { std::string(std::forward(paramNames))... } }; +} + +namespace detail { + +//================================================================================================= +// Trait: safely unwrap FunctionWithHints to F, identity otherwise. +// Unlike conditional_t, typename T::func_type, T>, +// this trait never instantiates the ::func_type member for non-FunctionWithHints types. + +template +struct unwrap_fn_type +{ + using type = T; +}; + +template +struct unwrap_fn_type> +{ + using type = F; +}; + +template +using unwrap_fn_type_t = typename unwrap_fn_type::type; + +//================================================================================================= +// Trait: detect FunctionWithHints + +template +struct is_function_with_hints : std::false_type +{ +}; + +template +struct is_function_with_hints> : std::true_type +{ +}; + +template +inline static constexpr bool is_function_with_hints_v = is_function_with_hints::value; + +//================================================================================================= +// Make function_traits> delegate to function_traits +// This makes function_result_t<>, function_arguments_t<>, function_arity_v<>, etc. all work +// transparently through a FunctionWithHints wrapper. + +template +struct function_traits> : function_traits +{ +}; + +//================================================================================================= +// Make is_callable> true when F is callable + +template +struct is_callable> +{ + static constexpr bool value = is_callable_v; +}; + +//================================================================================================= +// Make const-member-pointer detection transparent through FunctionWithHints + +template +struct is_const_member_function_pointer> +{ + static constexpr bool value = is_const_member_function_pointer_v; +}; + +//================================================================================================= +// Helper: unwrap FunctionWithHints to its inner callable, or pass through unchanged. + +template +[[nodiscard]] decltype(auto) get_underlying(F&& f) noexcept +{ + return std::forward(f); +} + +template +[[nodiscard]] F&& get_underlying(FunctionWithHints&& f) noexcept +{ + return std::move(f.func); +} + +template +[[nodiscard]] F& get_underlying(FunctionWithHints& f) noexcept +{ + return f.func; +} + +//================================================================================================= +// Reflection helpers — only compiled when LUABRIDGE_ENABLE_REFLECT is defined. + +#if defined(LUABRIDGE_ENABLE_REFLECT) + +/** + * @brief Build a vector of C++ type name strings from a tuple of types. + * + * lua_State* parameters are filtered out (they are auto-injected by LuaBridge, not + * visible from Lua). + */ +template +[[nodiscard]] std::vector reflect_param_type_names() +{ + std::vector result; + + [&](std::index_sequence) + { + ( + [&] + { + using ParamT = std::tuple_element_t; + + constexpr bool isLuaState = + std::is_pointer_v && + std::is_same_v>, lua_State>; + + if constexpr (!isLuaState) + result.push_back(std::string(typeName>())); + }(), + ...); + }(std::make_index_sequence>{}); + + return result; +} + +#endif // LUABRIDGE_ENABLE_REFLECT + +} // namespace detail +} // namespace luabridge diff --git a/Source/LuaBridge/detail/LuaHelpers.h b/Source/LuaBridge/detail/LuaHelpers.h index daab583b..099aed52 100644 --- a/Source/LuaBridge/detail/LuaHelpers.h +++ b/Source/LuaBridge/detail/LuaHelpers.h @@ -564,6 +564,57 @@ void* lua_newuserdata_aligned(lua_State* L, Args&&... args) return pointer; } +/** + * @brief Portable wrapper for lua_resume that normalises calling convention differences + * across Lua 5.1/LuaJIT (no from, no nresults), 5.2-5.3 (from but no nresults), and 5.4+ (from + nresults). + * + * @param L The coroutine thread to resume. + * @param from The thread doing the resuming (may be nullptr on older Lua). + * @param nargs Number of arguments on L's stack to pass to the resumed function. + * @param nresults Output: number of values on L's stack after resume (yielded or returned). + * For Lua 5.4+, filled directly by lua_resume. For older versions, computed via lua_gettop. + * @returns LUA_OK, LUA_YIELD, or an error code. + */ +inline int lua_resume_x(lua_State* L, lua_State* from, int nargs, int* nresults = nullptr) +{ +#if LUABRIDGE_ON_LUAJIT || LUA_VERSION_NUM == 501 + unused(from); + int status = lua_resume(L, nargs); + if (nresults) + *nresults = lua_gettop(L); + return status; +#elif LUABRIDGE_ON_LUAU || LUABRIDGE_ON_RAVI || LUA_VERSION_NUM < 504 + int status = lua_resume(L, from, nargs); + if (nresults) + *nresults = lua_gettop(L); + return status; +#else + int nr = 0; + int status = lua_resume(L, from, nargs, &nr); + if (nresults) + *nresults = nr; + return status; +#endif +} + +/** + * @brief Returns true if the currently running C function can yield via lua_yieldk. + * + * Returns false on Lua 5.1, LuaJIT, and Luau where lua_yieldk is unavailable. + */ +inline bool lua_isyieldable_x(lua_State* L) +{ +#if LUABRIDGE_ON_LUAJIT || LUA_VERSION_NUM == 501 || LUABRIDGE_ON_LUAU + unused(L); + return false; +#elif LUA_VERSION_NUM < 503 + unused(L); + return true; // lua_yieldk exists in 5.2; assume yieldable when reached +#else + return lua_isyieldable(L) != 0; +#endif +} + /** * @brief Safe error able to walk backwards for error reporting correctly. */ @@ -702,55 +753,4 @@ bool is_floating_point_representable_by(lua_State* L, int index) return isValid ? is_floating_point_representable_by(value) : false; } -/** - * @brief Portable wrapper for lua_resume that normalises calling convention differences - * across Lua 5.1/LuaJIT (no from, no nresults), 5.2-5.3 (from but no nresults), and 5.4+ (from + nresults). - * - * @param L The coroutine thread to resume. - * @param from The thread doing the resuming (may be nullptr on older Lua). - * @param nargs Number of arguments on L's stack to pass to the resumed function. - * @param nresults Output: number of values on L's stack after resume (yielded or returned). - * For Lua 5.4+, filled directly by lua_resume. For older versions, computed via lua_gettop. - * @returns LUA_OK, LUA_YIELD, or an error code. - */ -inline int lua_resume_x(lua_State* L, lua_State* from, int nargs, int* nresults = nullptr) -{ -#if LUABRIDGE_ON_LUAJIT || LUA_VERSION_NUM == 501 - unused(from); - int status = lua_resume(L, nargs); - if (nresults) - *nresults = lua_gettop(L); - return status; -#elif LUABRIDGE_ON_LUAU || LUABRIDGE_ON_RAVI || LUA_VERSION_NUM < 504 - int status = lua_resume(L, from, nargs); - if (nresults) - *nresults = lua_gettop(L); - return status; -#else - int nr = 0; - int status = lua_resume(L, from, nargs, &nr); - if (nresults) - *nresults = nr; - return status; -#endif -} - -/** - * @brief Returns true if the currently running C function can yield via lua_yieldk. - * - * Returns false on Lua 5.1, LuaJIT, and Luau where lua_yieldk is unavailable. - */ -inline bool lua_isyieldable_x(lua_State* L) -{ -#if LUABRIDGE_ON_LUAJIT || LUA_VERSION_NUM == 501 || LUABRIDGE_ON_LUAU - unused(L); - return false; -#elif LUA_VERSION_NUM < 503 - unused(L); - return true; // lua_yieldk exists in 5.2; assume yieldable when reached -#else - return lua_isyieldable(L) != 0; -#endif -} - } // namespace luabridge diff --git a/Source/LuaBridge/detail/Namespace.h b/Source/LuaBridge/detail/Namespace.h index 3dc59f6b..e0271c66 100644 --- a/Source/LuaBridge/detail/Namespace.h +++ b/Source/LuaBridge/detail/Namespace.h @@ -745,11 +745,44 @@ class Namespace : public detail::Registrar if constexpr (sizeof...(Functions) == 1) { +#if defined(LUABRIDGE_ENABLE_REFLECT) + // Wrap in 1-entry OverloadSet so metadata is accessible at runtime. ([&] { - detail::push_function(L, std::move(functions), name); + auto* overload_set_unaligned = lua_newuserdata_aligned(L); + auto* overload_set = align(overload_set_unaligned); + + detail::OverloadEntry entry; + if constexpr (!detail::is_any_cfunction_pointer_v) + { + using ArgsPack = detail::function_arguments_t; + entry.arity = static_cast(detail::function_arity_excluding_v); + entry.checker = &detail::overload_type_checker; + entry.returnType = std::string(detail::typeName>()); + entry.paramTypes = detail::reflect_param_type_names(); + if constexpr (detail::is_function_with_hints_v) + entry.paramHints = functions.hints; + } + else + { + entry.arity = -1; + entry.checker = nullptr; + } + overload_set->entries.push_back(std::move(entry)); + + lua_createtable(L, 1, 0); + detail::push_function(L, detail::get_underlying(std::move(functions)), name); + lua_rawseti(L, -2, 1); + lua_pushcclosure_x(L, &detail::try_overload_functions, name, 2); } (), ...); +#else + ([&] + { + detail::push_function(L, detail::get_underlying(std::move(functions)), name); + + } (), ...); +#endif } else { @@ -770,6 +803,12 @@ class Namespace : public detail::Registrar using ArgsPack = detail::function_arguments_t; entry.arity = static_cast(detail::function_arity_excluding_v); entry.checker = &detail::overload_type_checker; +#if defined(LUABRIDGE_ENABLE_REFLECT) + entry.returnType = std::string(detail::typeName>()); + entry.paramTypes = detail::reflect_param_type_names(); + if constexpr (detail::is_function_with_hints_v) + entry.paramHints = functions.hints; +#endif } overload_set->entries.push_back(entry); @@ -782,7 +821,7 @@ class Namespace : public detail::Registrar ([&] { - detail::push_function(L, std::move(functions), name); + detail::push_function(L, detail::get_underlying(std::move(functions)), name); lua_rawseti(L, -2, idx++); } (), ...); @@ -934,11 +973,56 @@ class Namespace : public detail::Registrar if constexpr (sizeof...(Functions) == 1) { +#if defined(LUABRIDGE_ENABLE_REFLECT) + // When reflection is enabled, wrap single functions in a 1-entry OverloadSet so + // metadata (return type, param types, hints) is uniformly accessible at runtime. + ([&] + { + auto* overload_set_unaligned = lua_newuserdata_aligned(L); + auto* overload_set = align(overload_set_unaligned); + + detail::OverloadEntry entry; + if constexpr (!detail::is_any_cfunction_pointer_v) + { + if constexpr (detail::is_proxy_member_function_v) + { + using ArgsPack = detail::remove_first_type_t>; + entry.arity = static_cast(detail::member_function_arity_excluding_v); + entry.checker = &detail::overload_type_checker; + entry.returnType = std::string(detail::typeName>()); + entry.paramTypes = detail::reflect_param_type_names(); + } + else + { + using ArgsPack = detail::function_arguments_t; + entry.arity = static_cast(detail::member_function_arity_excluding_v); + entry.checker = &detail::overload_type_checker; + entry.returnType = std::string(detail::typeName>()); + entry.paramTypes = detail::reflect_param_type_names(); + } + if constexpr (detail::is_function_with_hints_v) + entry.paramHints = functions.hints; + } + else + { + entry.arity = -1; + entry.checker = nullptr; + } + overload_set->entries.push_back(std::move(entry)); + + lua_createtable(L, 1, 0); + detail::push_member_function(L, detail::get_underlying(std::move(functions)), name); + lua_rawseti(L, -2, 1); + lua_pushcclosure_x(L, &detail::try_overload_functions, name, 2); + + } (), ...); +#else ([&] { - detail::push_member_function(L, std::move(functions), name); + detail::push_member_function(L, detail::get_underlying(std::move(functions)), name); } (), ...); +#endif if constexpr (detail::const_functions_count == 1) { @@ -976,12 +1060,24 @@ class Namespace : public detail::Registrar using ArgsPack = detail::remove_first_type_t>; entry.arity = static_cast(detail::member_function_arity_excluding_v); entry.checker = &detail::overload_type_checker; +#if defined(LUABRIDGE_ENABLE_REFLECT) + entry.returnType = std::string(detail::typeName>()); + entry.paramTypes = detail::reflect_param_type_names(); + if constexpr (detail::is_function_with_hints_v) + entry.paramHints = functions.hints; +#endif } else { using ArgsPack = detail::function_arguments_t; entry.arity = static_cast(detail::member_function_arity_excluding_v); entry.checker = &detail::overload_type_checker; +#if defined(LUABRIDGE_ENABLE_REFLECT) + entry.returnType = std::string(detail::typeName>()); + entry.paramTypes = detail::reflect_param_type_names(); + if constexpr (detail::is_function_with_hints_v) + entry.paramHints = functions.hints; +#endif } overload_set_const->entries.push_back(entry); @@ -999,7 +1095,7 @@ class Namespace : public detail::Registrar if (!detail::is_const_function) return; - detail::push_member_function(L, std::move(functions), name); + detail::push_member_function(L, detail::get_underlying(std::move(functions)), name); lua_rawseti(L, -2, idx++); } (), ...); @@ -1033,12 +1129,24 @@ class Namespace : public detail::Registrar using ArgsPack = detail::remove_first_type_t>; entry.arity = static_cast(detail::member_function_arity_excluding_v); entry.checker = &detail::overload_type_checker; +#if defined(LUABRIDGE_ENABLE_REFLECT) + entry.returnType = std::string(detail::typeName>()); + entry.paramTypes = detail::reflect_param_type_names(); + if constexpr (detail::is_function_with_hints_v) + entry.paramHints = functions.hints; +#endif } else { using ArgsPack = detail::function_arguments_t; entry.arity = static_cast(detail::member_function_arity_excluding_v); entry.checker = &detail::overload_type_checker; +#if defined(LUABRIDGE_ENABLE_REFLECT) + entry.returnType = std::string(detail::typeName>()); + entry.paramTypes = detail::reflect_param_type_names(); + if constexpr (detail::is_function_with_hints_v) + entry.paramHints = functions.hints; +#endif } overload_set_nonconst->entries.push_back(entry); @@ -1056,7 +1164,7 @@ class Namespace : public detail::Registrar if (detail::is_const_function) return; - detail::push_member_function(L, std::move(functions), name); + detail::push_member_function(L, detail::get_underlying(std::move(functions)), name); lua_rawseti(L, -2, idx++); } (), ...); @@ -1158,11 +1266,35 @@ class Namespace : public detail::Registrar if constexpr (sizeof...(Functions) == 1) { +#if defined(LUABRIDGE_ENABLE_REFLECT) + // Wrap in 1-entry OverloadSet so constructor metadata is accessible at runtime. + ([&] + { + using ArgsPack = detail::function_arguments_t; + + auto* overload_set_unaligned = lua_newuserdata_aligned(L); + auto* overload_set = align(overload_set_unaligned); + + detail::OverloadEntry entry; + entry.arity = static_cast(detail::function_arity_excluding_v); + entry.checker = &detail::overload_type_checker; + entry.returnType = std::string(detail::typeName()); + entry.paramTypes = detail::reflect_param_type_names(); + overload_set->entries.push_back(std::move(entry)); + + lua_createtable(L, 1, 0); + lua_pushcclosure_x(L, &detail::constructor_placement_proxy, className, 0); + lua_rawseti(L, -2, 1); + lua_pushcclosure_x(L, &detail::try_overload_functions, className, 2); + + } (), ...); +#else ([&] { lua_pushcclosure_x(L, &detail::constructor_placement_proxy>, className, 0); } (), ...); +#endif } else { @@ -1176,6 +1308,10 @@ class Namespace : public detail::Registrar detail::OverloadEntry entry; entry.arity = static_cast(detail::function_arity_excluding_v); entry.checker = &detail::overload_type_checker; +#if defined(LUABRIDGE_ENABLE_REFLECT) + entry.returnType = std::string(detail::typeName()); + entry.paramTypes = detail::reflect_param_type_names(); +#endif overload_set->entries.push_back(entry); } (), ...); @@ -1222,11 +1358,36 @@ class Namespace : public detail::Registrar { ([&] { - using F = detail::constructor_forwarder; + using InnerF = detail::unwrap_fn_type_t; + using F = detail::constructor_forwarder; - lua_newuserdata_aligned(L, F(std::move(functions))); // Stack: co, cl, st, upvalue - lua_pushcclosure_x(L, &detail::invoke_proxy_constructor, className, 1); // Stack: co, cl, st, function +#if defined(LUABRIDGE_ENABLE_REFLECT) + // Wrap in 1-entry OverloadSet so constructor metadata is accessible at runtime. + auto* overload_set_unaligned = lua_newuserdata_aligned(L); + auto* overload_set = align(overload_set_unaligned); + + { + // skip void* first arg (placement new destination, not a Lua argument) + using ArgsPack = detail::remove_first_type_t>; + detail::OverloadEntry entry; + entry.arity = static_cast(detail::function_arity_excluding_v) - 1; + entry.checker = &detail::overload_type_checker; + entry.returnType = std::string(detail::typeName()); + entry.paramTypes = detail::reflect_param_type_names(); + if constexpr (detail::is_function_with_hints_v) + entry.paramHints = functions.hints; + overload_set->entries.push_back(std::move(entry)); + } + lua_createtable(L, 1, 0); + lua_newuserdata_aligned(L, F(detail::get_underlying(std::move(functions)))); + lua_pushcclosure_x(L, &detail::invoke_proxy_constructor, className, 1); + lua_rawseti(L, -2, 1); + lua_pushcclosure_x(L, &detail::try_overload_functions, className, 2); +#else + lua_newuserdata_aligned(L, F(detail::get_underlying(std::move(functions)))); // Stack: co, cl, st, upvalue + lua_pushcclosure_x(L, &detail::invoke_proxy_constructor, className, 1); // Stack: co, cl, st, function +#endif } (), ...); } else @@ -1237,8 +1398,10 @@ class Namespace : public detail::Registrar ([&] { + using InnerF = detail::unwrap_fn_type_t; + detail::OverloadEntry entry; - if constexpr (detail::is_any_cfunction_pointer_v) + if constexpr (detail::is_any_cfunction_pointer_v) { entry.arity = -1; entry.checker = nullptr; @@ -1246,9 +1409,15 @@ class Namespace : public detail::Registrar else { // skip void* first arg (placement new destination, not a Lua argument) - using ArgsPack = detail::remove_first_type_t>; - entry.arity = static_cast(detail::function_arity_excluding_v) - 1; + using ArgsPack = detail::remove_first_type_t>; + entry.arity = static_cast(detail::function_arity_excluding_v) - 1; entry.checker = &detail::overload_type_checker; +#if defined(LUABRIDGE_ENABLE_REFLECT) + entry.returnType = std::string(detail::typeName()); + entry.paramTypes = detail::reflect_param_type_names(); + if constexpr (detail::is_function_with_hints_v) + entry.paramHints = functions.hints; +#endif } overload_set->entries.push_back(entry); @@ -1261,9 +1430,10 @@ class Namespace : public detail::Registrar ([&] { - using F = detail::constructor_forwarder; + using InnerF = detail::unwrap_fn_type_t; + using F = detail::constructor_forwarder; - lua_newuserdata_aligned(L, F(std::move(functions))); + lua_newuserdata_aligned(L, F(detail::get_underlying(std::move(functions)))); lua_pushcclosure_x(L, &detail::invoke_proxy_constructor, className, 1); lua_rawseti(L, -2, idx++); @@ -1922,11 +2092,44 @@ class Namespace : public detail::Registrar if constexpr (sizeof...(Functions) == 1) { +#if defined(LUABRIDGE_ENABLE_REFLECT) + // Wrap in 1-entry OverloadSet so metadata is accessible at runtime. ([&] { - detail::push_function(L, std::move(functions), name); + auto* overload_set_unaligned = lua_newuserdata_aligned(L); + auto* overload_set = align(overload_set_unaligned); + + detail::OverloadEntry entry; + if constexpr (!detail::is_any_cfunction_pointer_v) + { + using ArgsPack = detail::function_arguments_t; + entry.arity = static_cast(detail::function_arity_excluding_v); + entry.checker = &detail::overload_type_checker; + entry.returnType = std::string(detail::typeName>()); + entry.paramTypes = detail::reflect_param_type_names(); + if constexpr (detail::is_function_with_hints_v) + entry.paramHints = functions.hints; + } + else + { + entry.arity = -1; + entry.checker = nullptr; + } + overload_set->entries.push_back(std::move(entry)); + + lua_createtable(L, 1, 0); + detail::push_function(L, detail::get_underlying(std::move(functions)), name); + lua_rawseti(L, -2, 1); + lua_pushcclosure_x(L, &detail::try_overload_functions, name, 2); } (), ...); +#else + ([&] + { + detail::push_function(L, detail::get_underlying(std::move(functions)), name); + + } (), ...); +#endif } else { @@ -1947,6 +2150,12 @@ class Namespace : public detail::Registrar using ArgsPack = detail::function_arguments_t; entry.arity = static_cast(detail::function_arity_excluding_v); entry.checker = &detail::overload_type_checker; +#if defined(LUABRIDGE_ENABLE_REFLECT) + entry.returnType = std::string(detail::typeName>()); + entry.paramTypes = detail::reflect_param_type_names(); + if constexpr (detail::is_function_with_hints_v) + entry.paramHints = functions.hints; +#endif } overload_set->entries.push_back(entry); @@ -1959,7 +2168,7 @@ class Namespace : public detail::Registrar ([&] { - detail::push_function(L, std::move(functions), name); + detail::push_function(L, detail::get_underlying(std::move(functions)), name); lua_rawseti(L, -2, idx++); } (), ...); diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 414d3a9a..42fa651e 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -33,6 +33,7 @@ set (LUABRIDGE_TEST_SOURCE_FILES Source/ClassTests.cpp Source/CoroutineTests.cpp Source/DumpTests.cpp + Source/InspectTests.cpp Source/EnumTests.cpp Source/ExceptionTests.cpp Source/FlagSetTests.cpp @@ -285,6 +286,7 @@ macro (add_test_app LUABRIDGE_TEST_NAME LUA_VERSION LUABRIDGE_TEST_LUA_LIBRARY_F target_compile_definitions (${LUABRIDGE_TESTLIB_NAME} PRIVATE LUABRIDGE_TEST_SHARED_EXPORT=1 + LUABRIDGE_ENABLE_REFLECT=1 ${LUABRIDGE_DEFINES}) target_include_directories (${LUABRIDGE_TESTLIB_NAME} PRIVATE @@ -386,10 +388,13 @@ macro (add_test_app LUABRIDGE_TEST_NAME LUA_VERSION LUABRIDGE_TEST_LUA_LIBRARY_F set (LUABRIDGE_TEST_SHARED_LIBRARY "lib${LUABRIDGE_TESTLIB_NAME}.so") endif () target_compile_definitions (${LUABRIDGE_TEST_NAME} PRIVATE + LUABRIDGE_ENABLE_REFLECT=1 LUABRIDGE_TEST_SHARED_LIBRARY="${LUABRIDGE_TEST_SHARED_LIBRARY}" LUABRIDGE_TEST_SHARED_EXPORT=0) endif () + target_compile_definitions (${LUABRIDGE_TEST_NAME} PRIVATE LUABRIDGE_ENABLE_REFLECT=1) + if (NOT ${LUABRIDGE_EXCEPTIONS}) target_compile_definitions (${LUABRIDGE_TEST_NAME} PRIVATE LUA_USE_LONGJMP=1) if (APPLE) diff --git a/Tests/Source/InspectTests.cpp b/Tests/Source/InspectTests.cpp new file mode 100644 index 00000000..7d4ac746 --- /dev/null +++ b/Tests/Source/InspectTests.cpp @@ -0,0 +1,565 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2024, kunitoki +// SPDX-License-Identifier: MIT + +#include "TestBase.h" + +#include "LuaBridge/LuaBridge.h" + +#include +#include + +struct InspectTests : TestBase +{ +}; + +//================================================================================================= +// Helpers + +namespace { + +const luabridge::MemberInfo* findMember(const luabridge::ClassInspectInfo& cls, const std::string& name) +{ + for (const auto& m : cls.members) + if (m.name == name) return &m; + return nullptr; +} + +const luabridge::MemberInfo* findFreeMember(const luabridge::NamespaceInspectInfo& ns, const std::string& name) +{ + for (const auto& m : ns.freeMembers) + if (m.name == name) return &m; + return nullptr; +} + +const luabridge::ClassInspectInfo* findClass(const luabridge::NamespaceInspectInfo& ns, const std::string& name) +{ + for (const auto& c : ns.classes) + if (c.name == name) return &c; + return nullptr; +} + +const luabridge::NamespaceInspectInfo* findSubNamespace(const luabridge::NamespaceInspectInfo& ns, const std::string& name) +{ + for (const auto& s : ns.subNamespaces) + if (s.name == name) return &s; + return nullptr; +} + +bool hasBase(const luabridge::ClassInspectInfo& cls, const std::string& name) +{ + return std::find(cls.baseClasses.begin(), cls.baseClasses.end(), name) != cls.baseClasses.end(); +} + +} // anonymous namespace + +//================================================================================================= +// inspect() — single class tests + +TEST_F(InspectTests, InspectUnregisteredClass) +{ + struct Unregistered {}; + auto info = luabridge::inspect(L); + EXPECT_TRUE(info.name.empty()); + EXPECT_TRUE(info.members.empty()); + EXPECT_TRUE(info.baseClasses.empty()); +} + +TEST_F(InspectTests, InspectSimpleClass) +{ + struct Foo + { + int x = 0; + int getX() const { return x; } + void setX(int v) { x = v; } + void doSomething() {} + static int staticFunc() { return 42; } + }; + + luabridge::getGlobalNamespace(L) + .beginClass("Foo") + .addConstructor() + .addProperty("x", &Foo::getX, &Foo::setX) + .addFunction("doSomething", &Foo::doSomething) + .addStaticFunction("staticFunc", &Foo::staticFunc) + .endClass(); + + auto info = luabridge::inspect(L); + EXPECT_EQ("Foo", info.name); + + auto* prop = findMember(info, "x"); + ASSERT_NE(nullptr, prop); + EXPECT_EQ(luabridge::MemberKind::Property, prop->kind); + + auto* method = findMember(info, "doSomething"); + ASSERT_NE(nullptr, method); + EXPECT_EQ(luabridge::MemberKind::Method, method->kind); + EXPECT_EQ(1u, method->overloads.size()); + + auto* staticFn = findMember(info, "staticFunc"); + ASSERT_NE(nullptr, staticFn); + EXPECT_EQ(luabridge::MemberKind::StaticMethod, staticFn->kind); + + auto* ctor = findMember(info, "__call"); + ASSERT_NE(nullptr, ctor); + EXPECT_EQ(luabridge::MemberKind::Constructor, ctor->kind); +} + +TEST_F(InspectTests, InspectReadOnlyProperty) +{ + struct Bar { int getValue() const { return 1; } }; + luabridge::getGlobalNamespace(L) + .beginClass("Bar") + .addProperty("value", &Bar::getValue) + .endClass(); + + auto info = luabridge::inspect(L); + auto* prop = findMember(info, "value"); + ASSERT_NE(nullptr, prop); + EXPECT_EQ(luabridge::MemberKind::ReadOnlyProperty, prop->kind); +} + +TEST_F(InspectTests, InspectOverloadedMethod) +{ + struct Baz {}; + luabridge::getGlobalNamespace(L) + .beginClass("Baz") + .addFunction("method", + [](Baz&, int) {}, + [](Baz&, float) {}) + .endClass(); + + auto info = luabridge::inspect(L); + auto* method = findMember(info, "method"); + ASSERT_NE(nullptr, method); + EXPECT_EQ(luabridge::MemberKind::Method, method->kind); + EXPECT_EQ(2u, method->overloads.size()); +} + +TEST_F(InspectTests, InspectDerivedClass) +{ + struct Base { int baseMethod() const { return 1; } }; + struct Derived : Base { int derivedMethod() const { return 2; } }; + + luabridge::getGlobalNamespace(L) + .beginClass("Base") + .addFunction("baseMethod", &Base::baseMethod) + .endClass() + .deriveClass("Derived") + .addFunction("derivedMethod", &Derived::derivedMethod) + .endClass(); + + auto info = luabridge::inspect(L); + EXPECT_EQ("Derived", info.name); + EXPECT_FALSE(info.baseClasses.empty()); + EXPECT_TRUE(hasBase(info, "Base")); +} + +TEST_F(InspectTests, InspectMetamethods) +{ + struct Vec + { + Vec operator+(const Vec& /*rhs*/) const { return {}; } + }; + luabridge::getGlobalNamespace(L) + .beginClass("Vec") + .addFunction("__add", &Vec::operator+) + .endClass(); + + auto info = luabridge::inspect(L); + auto* add = findMember(info, "__add"); + ASSERT_NE(nullptr, add); + EXPECT_EQ(luabridge::MemberKind::Metamethod, add->kind); +} + +TEST_F(InspectTests, InspectStaticReadOnlyProperty) +{ + struct WithStatic {}; + static int sVal = 42; + luabridge::getGlobalNamespace(L) + .beginClass("WithStatic") + .addStaticProperty("ROProp", +[]() { return sVal; }) + .addStaticProperty("RWProp", +[]() { return sVal; }, +[](int v) { sVal = v; }) + .endClass(); + + auto info = luabridge::inspect(L); + + auto* ro = findMember(info, "ROProp"); + ASSERT_NE(nullptr, ro); + EXPECT_EQ(luabridge::MemberKind::StaticReadOnlyProperty, ro->kind); + + auto* rw = findMember(info, "RWProp"); + ASSERT_NE(nullptr, rw); + EXPECT_EQ(luabridge::MemberKind::StaticProperty, rw->kind); +} + +//================================================================================================= +// inspectNamespace() tests + +TEST_F(InspectTests, InspectNamespace) +{ + struct Widget { void draw() {} }; + luabridge::getGlobalNamespace(L) + .beginNamespace("UI") + .beginClass("Widget") + .addFunction("draw", &Widget::draw) + .endClass() + .addFunction("ping", +[]() -> int { return 1; }) + .endNamespace(); + + auto ns = luabridge::inspectNamespace(L, "UI"); + EXPECT_EQ("UI", ns.name); + + auto* w = findClass(ns, "Widget"); + ASSERT_NE(nullptr, w); + EXPECT_EQ("Widget", w->name); + + auto* ping = findFreeMember(ns, "ping"); + ASSERT_NE(nullptr, ping); + EXPECT_EQ(luabridge::MemberKind::Method, ping->kind); +} + +TEST_F(InspectTests, InspectSubNamespace) +{ + luabridge::getGlobalNamespace(L) + .beginNamespace("Outer") + .beginNamespace("Inner") + .addFunction("innerFunc", +[]() {}) + .endNamespace() + .endNamespace(); + + auto ns = luabridge::inspectNamespace(L, "Outer"); + EXPECT_EQ("Outer", ns.name); + + auto* inner = findSubNamespace(ns, "Inner"); + ASSERT_NE(nullptr, inner); + EXPECT_EQ("Inner", inner->name); + + auto* fn = findFreeMember(*inner, "innerFunc"); + ASSERT_NE(nullptr, fn); +} + +TEST_F(InspectTests, InspectEmptyNamespace) +{ + luabridge::getGlobalNamespace(L) + .beginNamespace("Empty") + .endNamespace(); + + auto ns = luabridge::inspectNamespace(L, "Empty"); + EXPECT_EQ("Empty", ns.name); + EXPECT_TRUE(ns.classes.empty()); + EXPECT_TRUE(ns.freeMembers.empty()); + EXPECT_TRUE(ns.subNamespaces.empty()); +} + +TEST_F(InspectTests, InspectNonexistentNamespace) +{ + auto ns = luabridge::inspectNamespace(L, "DoesNotExist"); + EXPECT_TRUE(ns.name.empty()); +} + +TEST_F(InspectTests, InspectDottedNamespacePath) +{ + luabridge::getGlobalNamespace(L) + .beginNamespace("A") + .beginNamespace("B") + .addFunction("deep", +[]() {}) + .endNamespace() + .endNamespace(); + + auto ns = luabridge::inspectNamespace(L, "A.B"); + EXPECT_EQ("B", ns.name); + EXPECT_NE(nullptr, findFreeMember(ns, "deep")); +} + +TEST_F(InspectTests, InspectNamespaceProperty) +{ + static int nsVal = 0; + luabridge::getGlobalNamespace(L) + .beginNamespace("NsProp") + .addProperty("roProp", +[]() { return nsVal; }) + .addProperty("rwProp", +[]() { return nsVal; }, +[](int v) { nsVal = v; }) + .endNamespace(); + + auto ns = luabridge::inspectNamespace(L, "NsProp"); + auto* ro = findFreeMember(ns, "roProp"); + ASSERT_NE(nullptr, ro); + EXPECT_EQ(luabridge::MemberKind::ReadOnlyProperty, ro->kind); + + auto* rw = findFreeMember(ns, "rwProp"); + ASSERT_NE(nullptr, rw); + EXPECT_EQ(luabridge::MemberKind::Property, rw->kind); +} + +//================================================================================================= +// Visitor tests + +TEST_F(InspectTests, ConsoleVisitorProducesOutput) +{ + struct Cls {}; + luabridge::getGlobalNamespace(L) + .beginNamespace("ConsTest") + .beginClass("Cls") + .addConstructor() + .endClass() + .endNamespace(); + + std::stringstream ss; + luabridge::inspectPrint(L, "ConsTest", ss); + EXPECT_FALSE(ss.str().empty()); + EXPECT_NE(std::string::npos, ss.str().find("ConsTest")); + EXPECT_NE(std::string::npos, ss.str().find("Cls")); +} + +TEST_F(InspectTests, LuaTableVisitorProducesTable) +{ + struct Cls { void m() {} }; + luabridge::getGlobalNamespace(L) + .beginNamespace("TblTest") + .beginClass("Cls") + .addFunction("m", &Cls::m) + .endClass() + .endNamespace(); + + int topBefore = lua_gettop(L); + luabridge::inspectToLua(L, "TblTest"); + + ASSERT_EQ(topBefore + 1, lua_gettop(L)); + ASSERT_TRUE(lua_istable(L, -1)); + + lua_getfield(L, -1, "name"); + EXPECT_STREQ("TblTest", lua_tostring(L, -1)); + lua_pop(L, 1); + + lua_getfield(L, -1, "classes"); + EXPECT_TRUE(lua_istable(L, -1)); + EXPECT_EQ(1, (int)lua_rawlen(L, -1)); + lua_pop(L, 1); + + lua_pop(L, 1); // pop result table + EXPECT_EQ(topBefore, lua_gettop(L)); +} + +TEST_F(InspectTests, LuaLSVisitorProducesAnnotations) +{ + struct Cls { int getValue() const { return 0; } }; + luabridge::getGlobalNamespace(L) + .beginNamespace("LuaLS") + .beginClass("Cls") + .addProperty("value", &Cls::getValue) + .endClass() + .endNamespace(); + + auto ns = luabridge::inspectNamespace(L, "LuaLS"); + std::stringstream ss; + luabridge::LuaLSVisitor v(ss); + luabridge::accept(ns, v); + + const auto result = ss.str(); + EXPECT_FALSE(result.empty()); + EXPECT_NE(std::string::npos, result.find("@class")); + EXPECT_NE(std::string::npos, result.find("Cls")); +} + +TEST_F(InspectTests, LuaProxyVisitorProducesStubs) +{ + struct Cls { void method() {} }; + luabridge::getGlobalNamespace(L) + .beginNamespace("Proxy") + .beginClass("Cls") + .addConstructor() + .addFunction("method", &Cls::method) + .endClass() + .endNamespace(); + + auto ns = luabridge::inspectNamespace(L, "Proxy"); + std::stringstream ss; + luabridge::LuaProxyVisitor v(ss); + luabridge::accept(ns, v); + + const auto result = ss.str(); + EXPECT_FALSE(result.empty()); + EXPECT_NE(std::string::npos, result.find("Cls")); + EXPECT_NE(std::string::npos, result.find("method")); +} + +TEST_F(InspectTests, JsonVisitorProducesJson) +{ + struct Cls {}; + luabridge::getGlobalNamespace(L) + .beginNamespace("Json") + .beginClass("Cls").endClass() + .endNamespace(); + + auto ns = luabridge::inspectNamespace(L, "Json"); + std::stringstream ss; + luabridge::JsonVisitor v(ss); + luabridge::accept(ns, v); + + const auto result = ss.str(); + EXPECT_FALSE(result.empty()); + EXPECT_NE(std::string::npos, result.find("\"name\"")); + EXPECT_NE(std::string::npos, result.find("Json")); +} + +//================================================================================================= +// inspect() stack balance test + +TEST_F(InspectTests, InspectDoesNotLeakStack) +{ + struct Foo { void m() {} }; + luabridge::getGlobalNamespace(L) + .beginClass("Foo") + .addFunction("m", &Foo::m) + .endClass(); + + int top = lua_gettop(L); + auto info = luabridge::inspect(L); + EXPECT_EQ(top, lua_gettop(L)); + EXPECT_EQ("Foo", info.name); +} + +TEST_F(InspectTests, InspectNamespaceDoesNotLeakStack) +{ + luabridge::getGlobalNamespace(L) + .beginNamespace("StackTest") + .addFunction("f", +[]() {}) + .endNamespace(); + + int top = lua_gettop(L); + auto ns = luabridge::inspectNamespace(L, "StackTest"); + EXPECT_EQ(top, lua_gettop(L)); + EXPECT_EQ("StackTest", ns.name); +} + +//================================================================================================= +// inspectAccept() helper + +TEST_F(InspectTests, InspectAcceptDrivesVisitor) +{ + struct Cls { void m() {} }; + luabridge::getGlobalNamespace(L) + .beginNamespace("AccTest") + .beginClass("Cls") + .addFunction("m", &Cls::m) + .endClass() + .endNamespace(); + + struct CountingVisitor : luabridge::InspectVisitor + { + int classCount = 0; + int memberCount = 0; + void beginClass(const luabridge::ClassInspectInfo&) override { ++classCount; } + void visitMember(const luabridge::ClassInspectInfo&, const luabridge::MemberInfo&) override { ++memberCount; } + }; + + CountingVisitor cv; + luabridge::inspectAccept(L, "AccTest", cv); + EXPECT_EQ(1, cv.classCount); + EXPECT_GE(cv.memberCount, 1); +} + +//================================================================================================= +// withHints() API test (tests FunctionWithHints detection) + +TEST_F(InspectTests, WithHintsIsAcceptedByAddFunction) +{ + struct Actor { void attack(float /*damage*/) {} }; + + // This test verifies that withHints compiles and the function is registered correctly. + EXPECT_NO_THROW( + luabridge::getGlobalNamespace(L) + .beginClass("Actor") + .addFunction("attack", luabridge::withHints(&Actor::attack, "damage")) + .endClass() + ); + + // Verify the function is callable from Lua + auto info = luabridge::inspect(L); + auto* m = findMember(info, "attack"); + ASSERT_NE(nullptr, m); + EXPECT_EQ(luabridge::MemberKind::Method, m->kind); +} + +TEST_F(InspectTests, WithHintsOverloadsCompile) +{ + struct Shooter {}; + + EXPECT_NO_THROW( + luabridge::getGlobalNamespace(L) + .beginClass("Shooter") + .addFunction("fire", + luabridge::withHints([](Shooter&, int /*count*/) {}, "count"), + luabridge::withHints([](Shooter&, float /*angle*/, int /*count*/) {}, "angle", "count")) + .endClass() + ); + + auto info = luabridge::inspect(L); + auto* m = findMember(info, "fire"); + ASSERT_NE(nullptr, m); + EXPECT_EQ(2u, m->overloads.size()); +} + +#if defined(LUABRIDGE_ENABLE_REFLECT) + +TEST_F(InspectTests, ReflectTypeInfoPopulated) +{ + struct Enemy { void takeDamage(float /*amount*/, int /*count*/) {} }; + + luabridge::getGlobalNamespace(L) + .beginClass("Enemy") + .addFunction("takeDamage", luabridge::withHints(&Enemy::takeDamage, "amount", "count")) + .endClass(); + + auto info = luabridge::inspect(L); + auto* m = findMember(info, "takeDamage"); + ASSERT_NE(nullptr, m); + ASSERT_EQ(1u, m->overloads.size()); + + const auto& ov = m->overloads[0]; + ASSERT_EQ(2u, ov.params.size()); + + // Type names should be non-empty when LUABRIDGE_ENABLE_REFLECT is active + EXPECT_FALSE(ov.params[0].typeName.empty()); + EXPECT_FALSE(ov.params[1].typeName.empty()); + + // Hints should be populated from withHints + EXPECT_EQ("amount", ov.params[0].hint); + EXPECT_EQ("count", ov.params[1].hint); +} + +TEST_F(InspectTests, ReflectConstructorTypeInfo) +{ + struct Widget {}; + + luabridge::getGlobalNamespace(L) + .beginClass("Widget") + .addConstructor() + .endClass(); + + auto info = luabridge::inspect(L); + auto* ctor = findMember(info, "__call"); + ASSERT_NE(nullptr, ctor); + ASSERT_EQ(1u, ctor->overloads.size()); + + EXPECT_EQ(2u, ctor->overloads[0].params.size()); +} + +TEST_F(InspectTests, ReflectFreeFunction) +{ + luabridge::getGlobalNamespace(L) + .beginNamespace("ReflectNS") + .addFunction("add", + luabridge::withHints(+[](int a, int b) -> int { return a + b; }, "a", "b")) + .endNamespace(); + + auto ns = luabridge::inspectNamespace(L, "ReflectNS"); + auto* fn = findFreeMember(ns, "add"); + ASSERT_NE(nullptr, fn); + ASSERT_EQ(1u, fn->overloads.size()); + ASSERT_EQ(2u, fn->overloads[0].params.size()); + EXPECT_EQ("a", fn->overloads[0].params[0].hint); + EXPECT_EQ("b", fn->overloads[0].params[1].hint); +} + +#endif // LUABRIDGE_ENABLE_REFLECT diff --git a/amalgamate.py b/amalgamate.py index e12f99b5..1a23a815 100644 --- a/amalgamate.py +++ b/amalgamate.py @@ -196,8 +196,14 @@ def WriteAlgamationFiles(self): headerAmalgamation.write(f"// clang-format off\n\n") headerAmalgamation.write(f"#pragma once\n\n") + guarded_includes = set(["coroutine", "span"]) for header in sorted(list(self.systemHeaders)): - headerAmalgamation.write(f"#include <{header}>\n") + if header in guarded_includes: + headerAmalgamation.write(f"#if defined(__has_include) && __has_include(<{header}>)\n") + headerAmalgamation.write(f"#include <{header}>\n") + headerAmalgamation.write(f"#endif\n\n") + else: + headerAmalgamation.write(f"#include <{header}>\n") headerAmalgamation.write("\n") self.AmalgamateQueue(self.headerQueue, headerAmalgamation) diff --git a/justfile b/justfile index 9a84bf48..ae502c91 100644 --- a/justfile +++ b/justfile @@ -22,7 +22,8 @@ plot: uv run --with-requirements Benchmarks/requirements.txt Benchmarks/plot_benchmarks.py --input Build/*.json --output Images/benchmarks.png clean: - rm -rf Build + -rm -rf Build/ + rm -rf Build/ amalgamate: uv run amalgamate.py From a2b0a3245ca5b4741d18193b2dc470e73421faf9 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Tue, 7 Apr 2026 16:41:00 +0200 Subject: [PATCH 02/14] Fix inspection --- .github/workflows/build_asan.yml | 4 + .github/workflows/build_linux.yml | 4 + .github/workflows/build_macos.yml | 4 + .github/workflows/build_tsan.yml_ | 4 + .github/workflows/build_ubsan.yml | 4 + .github/workflows/build_windows.yml | 4 + .github/workflows/coverage.yml | 4 + Distribution/LuaBridge/LuaBridge.h | 496 +++++++++++++++--------- Source/LuaBridge/Inspect.h | 110 +++--- Source/LuaBridge/detail/CFunctions.h | 218 +++++++++-- Source/LuaBridge/detail/FunctionHints.h | 38 +- Source/LuaBridge/detail/Namespace.h | 175 ++++----- Tests/CMakeLists.txt | 164 ++++---- Tests/Source/InspectTests.cpp | 33 +- 14 files changed, 774 insertions(+), 488 deletions(-) diff --git a/.github/workflows/build_asan.yml b/.github/workflows/build_asan.yml index 82af7a4e..24f7db7e 100644 --- a/.github/workflows/build_asan.yml +++ b/.github/workflows/build_asan.yml @@ -53,8 +53,10 @@ jobs: run: | cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ LuaBridgeTests${{ matrix.lua.suffix }} \ + LuaBridgeTests${{ matrix.lua.suffix }}Reflect \ LuaBridgeTests${{ matrix.lua.suffix }}LuaC \ LuaBridgeTests${{ matrix.lua.suffix }}Noexcept \ + LuaBridgeTests${{ matrix.lua.suffix }}ReflectNoexcept \ LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept - name: Test Lua ${{ matrix.lua.version }} @@ -63,6 +65,8 @@ jobs: ASAN_OPTIONS: detect_leaks=0:detect_odr_violation=0 run: | ./LuaBridgeTests${{ matrix.lua.suffix }} + ./LuaBridgeTests${{ matrix.lua.suffix }}Reflect ./LuaBridgeTests${{ matrix.lua.suffix }}LuaC ./LuaBridgeTests${{ matrix.lua.suffix }}Noexcept + ./LuaBridgeTests${{ matrix.lua.suffix }}ReflectNoexcept ./LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept diff --git a/.github/workflows/build_linux.yml b/.github/workflows/build_linux.yml index 06430c9c..75b2aeec 100644 --- a/.github/workflows/build_linux.yml +++ b/.github/workflows/build_linux.yml @@ -53,16 +53,20 @@ jobs: run: | cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ LuaBridgeTests${{ matrix.lua.suffix }} \ + LuaBridgeTests${{ matrix.lua.suffix }}Reflect \ LuaBridgeTests${{ matrix.lua.suffix }}LuaC \ LuaBridgeTests${{ matrix.lua.suffix }}Noexcept \ + LuaBridgeTests${{ matrix.lua.suffix }}ReflectNoexcept \ LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept - name: Test Lua ${{ matrix.lua.version }} working-directory: ${{runner.workspace}}/build/Tests run: | ./LuaBridgeTests${{ matrix.lua.suffix }} + ./LuaBridgeTests${{ matrix.lua.suffix }}Reflect ./LuaBridgeTests${{ matrix.lua.suffix }}LuaC ./LuaBridgeTests${{ matrix.lua.suffix }}Noexcept + ./LuaBridgeTests${{ matrix.lua.suffix }}ReflectNoexcept ./LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept luajit: diff --git a/.github/workflows/build_macos.yml b/.github/workflows/build_macos.yml index 0ff61011..9d0962f4 100644 --- a/.github/workflows/build_macos.yml +++ b/.github/workflows/build_macos.yml @@ -50,16 +50,20 @@ jobs: run: | cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ LuaBridgeTests${{ matrix.lua.suffix }} \ + LuaBridgeTests${{ matrix.lua.suffix }}Reflect \ LuaBridgeTests${{ matrix.lua.suffix }}LuaC \ LuaBridgeTests${{ matrix.lua.suffix }}Noexcept \ + LuaBridgeTests${{ matrix.lua.suffix }}ReflectNoexcept \ LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept - name: Test Lua ${{ matrix.lua.version }} working-directory: ${{runner.workspace}}/build/Tests run: | ./LuaBridgeTests${{ matrix.lua.suffix }} + ./LuaBridgeTests${{ matrix.lua.suffix }}Reflect ./LuaBridgeTests${{ matrix.lua.suffix }}LuaC ./LuaBridgeTests${{ matrix.lua.suffix }}Noexcept + ./LuaBridgeTests${{ matrix.lua.suffix }}ReflectNoexcept ./LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept luajit: diff --git a/.github/workflows/build_tsan.yml_ b/.github/workflows/build_tsan.yml_ index f0dbed88..549e64e2 100644 --- a/.github/workflows/build_tsan.yml_ +++ b/.github/workflows/build_tsan.yml_ @@ -53,8 +53,10 @@ jobs: run: | cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ LuaBridgeTests${{ matrix.lua.suffix }} \ + LuaBridgeTests${{ matrix.lua.suffix }}Reflect \ LuaBridgeTests${{ matrix.lua.suffix }}LuaC \ LuaBridgeTests${{ matrix.lua.suffix }}Noexcept \ + LuaBridgeTests${{ matrix.lua.suffix }}ReflectNoexcept \ LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept - name: Test Lua ${{ matrix.lua.version }} @@ -63,6 +65,8 @@ jobs: TSAN_OPTIONS: halt_on_error=1:print_stacktrace=1 run: | ./LuaBridgeTests${{ matrix.lua.suffix }} + ./LuaBridgeTests${{ matrix.lua.suffix }}Reflect ./LuaBridgeTests${{ matrix.lua.suffix }}LuaC ./LuaBridgeTests${{ matrix.lua.suffix }}Noexcept + ./LuaBridgeTests${{ matrix.lua.suffix }}ReflectNoexcept ./LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept diff --git a/.github/workflows/build_ubsan.yml b/.github/workflows/build_ubsan.yml index 88691cf4..e826304e 100644 --- a/.github/workflows/build_ubsan.yml +++ b/.github/workflows/build_ubsan.yml @@ -53,8 +53,10 @@ jobs: run: | cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ LuaBridgeTests${{ matrix.lua.suffix }} \ + LuaBridgeTests${{ matrix.lua.suffix }}Reflect \ LuaBridgeTests${{ matrix.lua.suffix }}LuaC \ LuaBridgeTests${{ matrix.lua.suffix }}Noexcept \ + LuaBridgeTests${{ matrix.lua.suffix }}ReflectNoexcept \ LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept - name: Test Lua ${{ matrix.lua.version }} @@ -63,6 +65,8 @@ jobs: UBSAN_OPTIONS: halt_on_error=1:print_stacktrace=1 run: | ./LuaBridgeTests${{ matrix.lua.suffix }} + ./LuaBridgeTests${{ matrix.lua.suffix }}Reflect ./LuaBridgeTests${{ matrix.lua.suffix }}LuaC ./LuaBridgeTests${{ matrix.lua.suffix }}Noexcept + ./LuaBridgeTests${{ matrix.lua.suffix }}ReflectNoexcept ./LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept diff --git a/.github/workflows/build_windows.yml b/.github/workflows/build_windows.yml index a67bb15b..f7f3762e 100644 --- a/.github/workflows/build_windows.yml +++ b/.github/workflows/build_windows.yml @@ -52,8 +52,10 @@ jobs: run: | cmake --build . --config $BUILD_TYPE --parallel 4 --target \ LuaBridgeTests${{ matrix.lua.suffix }} \ + LuaBridgeTests${{ matrix.lua.suffix }}Reflect \ LuaBridgeTests${{ matrix.lua.suffix }}LuaC \ LuaBridgeTests${{ matrix.lua.suffix }}Noexcept \ + LuaBridgeTests${{ matrix.lua.suffix }}ReflectNoexcept \ LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept - name: Test Lua ${{ matrix.lua.version }} @@ -61,8 +63,10 @@ jobs: shell: bash run: | ./LuaBridgeTests${{ matrix.lua.suffix }}.exe + ./LuaBridgeTests${{ matrix.lua.suffix }}Reflect.exe ./LuaBridgeTests${{ matrix.lua.suffix }}LuaC.exe ./LuaBridgeTests${{ matrix.lua.suffix }}Noexcept.exe + ./LuaBridgeTests${{ matrix.lua.suffix }}ReflectNoexcept.exe ./LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept.exe luajit: diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index a558dfd9..273a60fc 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -55,16 +55,20 @@ jobs: run: | cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ LuaBridgeTests${{ matrix.lua.suffix }} \ + LuaBridgeTests${{ matrix.lua.suffix }}Reflect \ LuaBridgeTests${{ matrix.lua.suffix }}LuaC \ LuaBridgeTests${{ matrix.lua.suffix }}Noexcept \ + LuaBridgeTests${{ matrix.lua.suffix }}ReflectNoexcept \ LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept - name: Test Lua ${{ matrix.lua.version }} working-directory: ${{runner.workspace}}/build/Tests run: | ./LuaBridgeTests${{ matrix.lua.suffix }} + ./LuaBridgeTests${{ matrix.lua.suffix }}Reflect ./LuaBridgeTests${{ matrix.lua.suffix }}LuaC ./LuaBridgeTests${{ matrix.lua.suffix }}Noexcept + ./LuaBridgeTests${{ matrix.lua.suffix }}ReflectNoexcept ./LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept - name: Coverage Lua ${{ matrix.lua.version }} diff --git a/Distribution/LuaBridge/LuaBridge.h b/Distribution/LuaBridge/LuaBridge.h index a2d13260..dfb90ee7 100644 --- a/Distribution/LuaBridge/LuaBridge.h +++ b/Distribution/LuaBridge/LuaBridge.h @@ -7738,35 +7738,35 @@ struct function } }; -template +template int invoke_member_function(lua_State* L) { using FnTraits = function_traits; - LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(1))); + LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(UV))); auto ptr = Userdata::get(L, 1, false); if (! ptr) raise_lua_error(L, "%s", ptr.error_cstr()); - const F& func = *static_cast(lua_touserdata(L, lua_upvalueindex(1))); + const F& func = *static_cast(lua_touserdata(L, lua_upvalueindex(UV))); LUABRIDGE_ASSERT(func != nullptr); return function::call(L, *ptr, func); } -template +template int invoke_const_member_function(lua_State* L) { using FnTraits = function_traits; - LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(1))); + LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(UV))); auto ptr = Userdata::get(L, 1, true); if (! ptr) raise_lua_error(L, "%s", ptr.error_cstr()); - const F& func = *static_cast(lua_touserdata(L, lua_upvalueindex(1))); + const F& func = *static_cast(lua_touserdata(L, lua_upvalueindex(UV))); LUABRIDGE_ASSERT(func != nullptr); return function::call(L, *ptr, func); @@ -7834,27 +7834,27 @@ int invoke_const_member_cfunction(lua_State* L) #endif } -template +template int invoke_proxy_function(lua_State* L) { using FnTraits = function_traits; - LUABRIDGE_ASSERT(lua_islightuserdata(L, lua_upvalueindex(1))); + LUABRIDGE_ASSERT(lua_islightuserdata(L, lua_upvalueindex(UV))); - auto func = reinterpret_cast(lua_touserdata(L, lua_upvalueindex(1))); + auto func = reinterpret_cast(lua_touserdata(L, lua_upvalueindex(UV))); LUABRIDGE_ASSERT(func != nullptr); return function::call(L, func); } -template +template int invoke_proxy_functor(lua_State* L) { using FnTraits = function_traits>; - LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(1))); + LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(UV))); - auto& func = *align>(lua_touserdata(L, lua_upvalueindex(1))); + auto& func = *align>(lua_touserdata(L, lua_upvalueindex(UV))); return function::call(L, func); } @@ -7879,14 +7879,14 @@ inline int invoke_safe_cfunction(lua_State* L) } #endif -template +template int invoke_proxy_constructor(lua_State* L) { using FnTraits = function_traits; - LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(1))); + LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(UV))); - auto& func = *align(lua_touserdata(L, lua_upvalueindex(1))); + auto& func = *align(lua_touserdata(L, lua_upvalueindex(UV))); function::call(L, func); @@ -7914,7 +7914,7 @@ struct OverloadEntry int arity; TypeChecker checker; -#if defined(LUABRIDGE_ENABLE_REFLECT) +#if LUABRIDGE_ENABLE_REFLECT std::string returnType; std::vector paramTypes; std::vector paramHints; @@ -7923,6 +7923,8 @@ struct OverloadEntry struct OverloadSet { + static constexpr uint32_t kMagic = 0x4C425246u; + uint32_t magic = kMagic; std::vector entries; }; @@ -8068,6 +8070,29 @@ inline void push_function(lua_State* L, F&& f, const char* debugname) lua_pushcclosure_x(L, &invoke_proxy_functor, debugname, 1); } +template +inline void push_function_reflect(lua_State* L, ReturnType (*fp)(Params...), const char* debugname) +{ + using FnType = ReturnType (*)(Params...); + lua_pushlightuserdata(L, reinterpret_cast(fp)); + lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 2); +} + +template +inline void push_function_reflect(lua_State* L, ReturnType (*fp)(Params...) noexcept, const char* debugname) +{ + using FnType = ReturnType (*)(Params...) noexcept; + lua_pushlightuserdata(L, reinterpret_cast(fp)); + lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 2); +} + +template && !std::is_pointer_v && !std::is_member_function_pointer_v>> +inline void push_function_reflect(lua_State* L, F&& f, const char* debugname) +{ + lua_newuserdata_aligned(L, std::forward(f)); + lua_pushcclosure_x(L, &invoke_proxy_functor, debugname, 2); +} + template void push_member_function(lua_State* L, lua_CFunction fp, const char* debugname) { @@ -8230,6 +8255,118 @@ void push_member_function(lua_State* L, int (U::*mfp)(lua_State*) const, const c lua_pushcclosure_x(L, &invoke_const_member_cfunction, debugname, 1); } +template +void push_member_function_reflect(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, 2); +} + +template +void push_member_function_reflect(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, 2); +} + +template +void push_member_function_reflect(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, 2); +} + +template +void push_member_function_reflect(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, 2); +} + +template +void push_member_function_reflect(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, 2); +} + +template +void push_member_function_reflect(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, 2); +} + +template +void push_member_function_reflect(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, 2); +} + +template +void push_member_function_reflect(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, 2); +} + +template +void push_member_function_reflect(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_pushcclosure_x(L, &invoke_member_function, debugname, 2); +} + +template +void push_member_function_reflect(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_pushcclosure_x(L, &invoke_member_function, debugname, 2); +} + +template +void push_member_function_reflect(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_pushcclosure_x(L, &invoke_const_member_function, debugname, 2); +} + +template +void push_member_function_reflect(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_pushcclosure_x(L, &invoke_const_member_function, debugname, 2); +} + +template && + std::is_object_v && + !std::is_pointer_v && + !std::is_member_function_pointer_v>> +void push_member_function_reflect(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, 2); +} + template || !detail::is_callable_v)>> void push_property_getter(lua_State* L, const T* value, const char* debugname) @@ -8551,16 +8688,30 @@ struct constructor { static T* construct(const Args& args) { - auto alloc = [](auto&&... args) { return new T(std::forward(args)...); }; - - return std::apply(alloc, args); + if constexpr (std::is_aggregate_v) + { + auto alloc = [](auto&&... args) { return new T{std::forward(args)...}; }; + return std::apply(alloc, args); + } + else + { + 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); + if constexpr (std::is_aggregate_v) + { + auto alloc = [ptr](auto&&... args) { return new (ptr) T{std::forward(args)...}; }; + return std::apply(alloc, args); + } + else + { + auto alloc = [ptr](auto&&... args) { return new (ptr) T(std::forward(args)...); }; + return std::apply(alloc, args); + } } }; @@ -8958,26 +9109,14 @@ inline std::vector getOverloadInfos(lua_State* L, int funcIdx) return { OverloadInfo{} }; } - bool isOverloadSet = isfulluserdata(L, -1); - lua_pop(L, 1); - - if (!isOverloadSet) + OverloadSet* overload_set = nullptr; + if (isfulluserdata(L, -1)) { - - return { OverloadInfo{} }; + auto* candidate = align(lua_touserdata(L, -1)); + if (candidate && candidate->magic == OverloadSet::kMagic) + overload_set = candidate; } - - const char* uv2name = lua_getupvalue(L, funcIdx, 2); - bool hasTable = (uv2name != nullptr) && lua_istable(L, -1); - if (uv2name != nullptr) - lua_pop(L, 1); - - if (!hasTable) - return { OverloadInfo{} }; - - lua_getupvalue(L, funcIdx, 1); - auto* overload_set = align(lua_touserdata(L, -1)); - lua_pop(L, 1); + lua_pop(L, 1); if (!overload_set || overload_set->entries.empty()) return { OverloadInfo{} }; @@ -8989,7 +9128,7 @@ inline std::vector getOverloadInfos(lua_State* L, int funcIdx) { OverloadInfo info; -#if defined(LUABRIDGE_ENABLE_REFLECT) +#if LUABRIDGE_ENABLE_REFLECT info.returnType = entry.returnType; for (std::size_t i = 0; i < entry.paramTypes.size(); ++i) { @@ -9021,7 +9160,7 @@ inline bool isClassStaticTable(lua_State* L, int tableIdx) if (!lua_getmetatable(L, tableIdx)) return false; - lua_rawgetp(L, -1, getClassKey()); + lua_rawgetp_x(L, -1, getClassKey()); bool result = lua_istable(L, -1); lua_pop(L, 1); @@ -9040,25 +9179,25 @@ inline ClassInspectInfo inspectClassFromStaticTable(lua_State* L, int stIdx) int mtIdx = lua_absindex(L, -1); - lua_rawgetp(L, mtIdx, getClassKey()); + lua_rawgetp_x(L, mtIdx, getClassKey()); int clIdx = lua_absindex(L, -1); ClassInspectInfo cls; - lua_rawgetp(L, clIdx, getTypeKey()); + lua_rawgetp_x(L, clIdx, getTypeKey()); if (lua_isstring(L, -1)) cls.name = stripConst(lua_tostring(L, -1)); lua_pop(L, 1); - lua_rawgetp(L, clIdx, getParentKey()); + lua_rawgetp_x(L, clIdx, getParentKey()); if (lua_istable(L, -1)) { int parIdx = lua_absindex(L, -1); - int len = static_cast(luaL_len(L, parIdx)); + int len = get_length(L, parIdx); for (int i = 1; i <= len; ++i) { lua_rawgeti(L, parIdx, i); - lua_rawgetp(L, -1, getTypeKey()); + lua_rawgetp_x(L, -1, getTypeKey()); if (lua_isstring(L, -1)) { std::string baseName = stripConst(lua_tostring(L, -1)); @@ -9071,13 +9210,13 @@ inline ClassInspectInfo inspectClassFromStaticTable(lua_State* L, int stIdx) lua_pop(L, 1); std::set instPropget; - lua_rawgetp(L, clIdx, getPropgetKey()); + lua_rawgetp_x(L, clIdx, getPropgetKey()); if (lua_istable(L, -1)) instPropget = collectTableKeys(L, -1); lua_pop(L, 1); std::set instPropsetReal; - lua_rawgetp(L, clIdx, getPropsetKey()); + lua_rawgetp_x(L, clIdx, getPropsetKey()); if (lua_istable(L, -1)) { int psIdx = lua_absindex(L, -1); @@ -9130,13 +9269,13 @@ inline ClassInspectInfo inspectClassFromStaticTable(lua_State* L, int stIdx) } std::set stPropget; - lua_rawgetp(L, mtIdx, getPropgetKey()); + lua_rawgetp_x(L, mtIdx, getPropgetKey()); if (lua_istable(L, -1)) stPropget = collectTableKeys(L, -1); lua_pop(L, 1); std::set stPropsetReal; - lua_rawgetp(L, mtIdx, getPropsetKey()); + lua_rawgetp_x(L, mtIdx, getPropsetKey()); if (lua_istable(L, -1)) { int psIdx = lua_absindex(L, -1); @@ -9165,7 +9304,7 @@ inline ClassInspectInfo inspectClassFromStaticTable(lua_State* L, int stIdx) static const std::set staticSkipKeys{ "__index", "__newindex", "__metatable" }; - lua_rawgetp(L, mtIdx, getClassKey()); + lua_rawgetp_x(L, mtIdx, getClassKey()); lua_pop(L, 1); lua_pushnil(L); @@ -9207,13 +9346,13 @@ inline NamespaceInspectInfo inspectNamespaceTable(lua_State* L, int nsIdx, std:: info.name = std::move(name); std::set nsPropget; - lua_rawgetp(L, nsIdx, getPropgetKey()); + lua_rawgetp_x(L, nsIdx, getPropgetKey()); if (lua_istable(L, -1)) nsPropget = collectTableKeys(L, -1); lua_pop(L, 1); int nsPropsetTableIdx = 0; - lua_rawgetp(L, nsIdx, getPropsetKey()); + lua_rawgetp_x(L, nsIdx, getPropsetKey()); if (lua_istable(L, -1)) nsPropsetTableIdx = lua_absindex(L, -1); @@ -9301,21 +9440,21 @@ template [[nodiscard]] ClassInspectInfo inspect(lua_State* L) { - lua_rawgetp(L, LUA_REGISTRYINDEX, detail::getClassRegistryKey()); + lua_rawgetp_x(L, LUA_REGISTRYINDEX, detail::getClassRegistryKey()); if (!lua_istable(L, -1)) { lua_pop(L, 1); return {}; } - lua_rawgetp(L, -1, detail::getStaticKey()); + lua_rawgetp_x(L, -1, detail::getStaticKey()); if (!lua_istable(L, -1)) { lua_pop(L, 2); return {}; } - lua_rawgetp(L, -1, detail::getClassKey()); + lua_rawgetp_x(L, -1, detail::getClassKey()); const bool isClass = lua_istable(L, -1); lua_pop(L, 1); @@ -9335,7 +9474,7 @@ template { if (namespaceName == nullptr || namespaceName[0] == '\0') { - lua_pushglobaltable(L); + lua_getglobal(L, "_G"); } else { @@ -9407,19 +9546,16 @@ class ConsoleVisitor : public InspectVisitor ++depth_; } - void endNamespace(const NamespaceInspectInfo& -) override + void endNamespace([[maybe_unused]] const NamespaceInspectInfo& ns) override { --depth_; indent(); out_ << "}\n"; } - void visitFreeMember(const NamespaceInspectInfo& -, const MemberInfo& m) override + void visitFreeMember([[maybe_unused]] const NamespaceInspectInfo& ns, const MemberInfo& m) override { - emitMember(m, -""); + emitMember(m, ""); } void beginClass(const ClassInspectInfo& cls) override @@ -9439,8 +9575,7 @@ class ConsoleVisitor : public InspectVisitor ++depth_; } - void endClass(const ClassInspectInfo& -) override + void endClass([[maybe_unused]] const ClassInspectInfo& cls) override { --depth_; indent(); @@ -9480,7 +9615,7 @@ class ConsoleVisitor : public InspectVisitor return ov.returnType.empty() ? "any" : ov.returnType; } - void emitMember(const MemberInfo& m, const std::string& className) const + void emitMember(const MemberInfo& m, [[maybe_unused]] const std::string& className) const { switch (m.kind) { @@ -9510,15 +9645,13 @@ class ConsoleVisitor : public InspectVisitor { indent(); if (isStatic) out_ << "static "; - out_ << m.name << "(" << paramStr(m.overloads[i]) << "): " - << retStr(m.overloads[i]) << ";\n"; + out_ << m.name << "(" << paramStr(m.overloads[i]) << "): " << retStr(m.overloads[i]) << ";\n"; } break; } default: break; } - (void)className; } std::ostream& out_; @@ -9538,8 +9671,7 @@ class LuaLSVisitor : public InspectVisitor ns_ = ns.name == "_G" ? "" : ns.name; } - void endNamespace(const NamespaceInspectInfo& -) override {} + void endNamespace([[maybe_unused]] const NamespaceInspectInfo& ns) override {} void visitFreeMember(const NamespaceInspectInfo& ns, const MemberInfo& m) override { @@ -9552,6 +9684,7 @@ class LuaLSVisitor : public InspectVisitor if (!ov.returnType.empty() && ov.returnType != "void") out_ << "---@return " << luaType(ov.returnType) << "\n"; } + std::string qual = ns_.empty() ? "" : (ns_ + "."); out_ << "function " << qual << m.name << "("; if (!m.overloads.empty()) @@ -9573,8 +9706,7 @@ class LuaLSVisitor : public InspectVisitor } - void endClass(const ClassInspectInfo& -) override + void endClass([[maybe_unused]] const ClassInspectInfo& cls) override { out_ << "local " << curClass_ << " = {}\n\n"; curClass_.clear(); @@ -9768,15 +9900,13 @@ class LuaProxyVisitor : public InspectVisitor out_ << cls.name << ".__index = " << cls.name << "\n\n"; } - void endClass(const ClassInspectInfo& -) override + void endClass([[maybe_unused]] const ClassInspectInfo& cls) override { curClass_.clear(); out_ << "\n"; } - void visitMember(const ClassInspectInfo& -, const MemberInfo& m) override + void visitMember([[maybe_unused]] const ClassInspectInfo& cls, const MemberInfo& m) override { if (m.overloads.empty()) return; @@ -9842,8 +9972,7 @@ class JsonVisitor : public InspectVisitor firstClass_ = true; } - void endNamespace(const NamespaceInspectInfo& -) override + void endNamespace([[maybe_unused]] const NamespaceInspectInfo& ns) override { --depth_; out_ << "\n"; @@ -9870,8 +9999,7 @@ class JsonVisitor : public InspectVisitor firstMember_ = true; } - void endClass(const ClassInspectInfo& -) override + void endClass([[maybe_unused]] const ClassInspectInfo& cls) override { --depth_; out_ << "\n"; @@ -9880,8 +10008,7 @@ class JsonVisitor : public InspectVisitor indent(); out_ << "}"; } - void visitMember(const ClassInspectInfo& -, const MemberInfo& m) override + void visitMember([[maybe_unused]] const ClassInspectInfo& cls, const MemberInfo& m) override { if (!firstMember_) out_ << ",\n"; firstMember_ = false; @@ -9960,14 +10087,12 @@ class LuaTableVisitor : public InspectVisitor subNsIdx_ = 1; } - void endNamespace(const NamespaceInspectInfo& -) override + void endNamespace([[maybe_unused]] const NamespaceInspectInfo& ns) override { } - void visitFreeMember(const NamespaceInspectInfo& -, const MemberInfo& m) override + void visitFreeMember([[maybe_unused]] const NamespaceInspectInfo& ns, const MemberInfo& m) override { lua_getfield(L_, -1, "freeMembers"); pushMemberInfo(m); @@ -9994,8 +10119,7 @@ class LuaTableVisitor : public InspectVisitor memberIdx_ = 1; } - void endClass(const ClassInspectInfo& -) override + void endClass([[maybe_unused]] const ClassInspectInfo& cls) override { lua_getfield(L_, -2, "classes"); @@ -10004,8 +10128,7 @@ class LuaTableVisitor : public InspectVisitor lua_pop(L_, 2); } - void visitMember(const ClassInspectInfo& -, const MemberInfo& m) override + void visitMember([[maybe_unused]] const ClassInspectInfo& cls, const MemberInfo& m) override { lua_getfield(L_, -1, "members"); pushMemberInfo(m); @@ -10678,28 +10801,30 @@ template #if defined(LUABRIDGE_ENABLE_REFLECT) -template -[[nodiscard]] std::vector reflect_param_type_names() +template +void reflect_param_type_names_impl(std::vector& result, std::index_sequence) { - std::vector result; - - [&](std::index_sequence) - { - ( - [&] - { - using ParamT = std::tuple_element_t; + + ( + [&]() + { + using ParamT = std::tuple_element_t; - constexpr bool isLuaState = - std::is_pointer_v && - std::is_same_v>, lua_State>; + constexpr bool isLuaState = + std::is_pointer_v && + std::is_same_v>, lua_State>; - if constexpr (!isLuaState) - result.push_back(std::string(typeName>())); - }(), - ...); - }(std::make_index_sequence>{}); + if constexpr (!isLuaState) + result.push_back(std::string(typeName>())); + }(), + ...); +} +template +[[nodiscard]] std::vector reflect_param_type_names() +{ + std::vector result; + reflect_param_type_names_impl(result, std::make_index_sequence>{}); return result; } @@ -12853,35 +12978,31 @@ class Namespace : public detail::Registrar if constexpr (sizeof...(Functions) == 1) { -#if defined(LUABRIDGE_ENABLE_REFLECT) +#if LUABRIDGE_ENABLE_REFLECT ([&] { - auto* overload_set_unaligned = lua_newuserdata_aligned(L); - auto* overload_set = align(overload_set_unaligned); - - detail::OverloadEntry entry; if constexpr (!detail::is_any_cfunction_pointer_v) { + auto* overload_set_unaligned = lua_newuserdata_aligned(L); + auto* overload_set = align(overload_set_unaligned); + + detail::OverloadEntry entry; using ArgsPack = detail::function_arguments_t; - entry.arity = static_cast(detail::function_arity_excluding_v); - entry.checker = &detail::overload_type_checker; - entry.returnType = std::string(detail::typeName>()); + entry.arity = -1; + entry.checker = nullptr; + entry.returnType = std::string(detail::typeName>>()); entry.paramTypes = detail::reflect_param_type_names(); if constexpr (detail::is_function_with_hints_v) entry.paramHints = functions.hints; + overload_set->entries.push_back(std::move(entry)); + + detail::push_function_reflect(L, detail::get_underlying(std::move(functions)), name); } else { - entry.arity = -1; - entry.checker = nullptr; + detail::push_function(L, detail::get_underlying(std::move(functions)), name); } - overload_set->entries.push_back(std::move(entry)); - - lua_createtable(L, 1, 0); - detail::push_function(L, detail::get_underlying(std::move(functions)), name); - lua_rawseti(L, -2, 1); - lua_pushcclosure_x(L, &detail::try_overload_functions, name, 2); } (), ...); #else @@ -12911,8 +13032,8 @@ class Namespace : public detail::Registrar using ArgsPack = detail::function_arguments_t; entry.arity = static_cast(detail::function_arity_excluding_v); entry.checker = &detail::overload_type_checker; -#if defined(LUABRIDGE_ENABLE_REFLECT) - entry.returnType = std::string(detail::typeName>()); +#if LUABRIDGE_ENABLE_REFLECT + entry.returnType = std::string(detail::typeName>>()); entry.paramTypes = detail::reflect_param_type_names(); if constexpr (detail::is_function_with_hints_v) entry.paramHints = functions.hints; @@ -13055,46 +13176,43 @@ class Namespace : public detail::Registrar if constexpr (sizeof...(Functions) == 1) { -#if defined(LUABRIDGE_ENABLE_REFLECT) +#if LUABRIDGE_ENABLE_REFLECT ([&] { - auto* overload_set_unaligned = lua_newuserdata_aligned(L); - auto* overload_set = align(overload_set_unaligned); - - detail::OverloadEntry entry; if constexpr (!detail::is_any_cfunction_pointer_v) { + auto* overload_set_unaligned = lua_newuserdata_aligned(L); + auto* overload_set = align(overload_set_unaligned); + + detail::OverloadEntry entry; if constexpr (detail::is_proxy_member_function_v) { using ArgsPack = detail::remove_first_type_t>; - entry.arity = static_cast(detail::member_function_arity_excluding_v); - entry.checker = &detail::overload_type_checker; - entry.returnType = std::string(detail::typeName>()); + entry.arity = -1; + entry.checker = nullptr; + entry.returnType = std::string(detail::typeName>>()); entry.paramTypes = detail::reflect_param_type_names(); } else { using ArgsPack = detail::function_arguments_t; - entry.arity = static_cast(detail::member_function_arity_excluding_v); - entry.checker = &detail::overload_type_checker; - entry.returnType = std::string(detail::typeName>()); + entry.arity = -1; + entry.checker = nullptr; + entry.returnType = std::string(detail::typeName>>()); entry.paramTypes = detail::reflect_param_type_names(); } if constexpr (detail::is_function_with_hints_v) entry.paramHints = functions.hints; + overload_set->entries.push_back(std::move(entry)); + + detail::push_member_function_reflect(L, detail::get_underlying(std::move(functions)), name); } else { - entry.arity = -1; - entry.checker = nullptr; + + detail::push_member_function(L, detail::get_underlying(std::move(functions)), name); } - overload_set->entries.push_back(std::move(entry)); - - lua_createtable(L, 1, 0); - detail::push_member_function(L, detail::get_underlying(std::move(functions)), name); - lua_rawseti(L, -2, 1); - lua_pushcclosure_x(L, &detail::try_overload_functions, name, 2); } (), ...); #else @@ -13141,8 +13259,8 @@ class Namespace : public detail::Registrar using ArgsPack = detail::remove_first_type_t>; entry.arity = static_cast(detail::member_function_arity_excluding_v); entry.checker = &detail::overload_type_checker; -#if defined(LUABRIDGE_ENABLE_REFLECT) - entry.returnType = std::string(detail::typeName>()); +#if LUABRIDGE_ENABLE_REFLECT + entry.returnType = std::string(detail::typeName>>()); entry.paramTypes = detail::reflect_param_type_names(); if constexpr (detail::is_function_with_hints_v) entry.paramHints = functions.hints; @@ -13153,8 +13271,8 @@ class Namespace : public detail::Registrar using ArgsPack = detail::function_arguments_t; entry.arity = static_cast(detail::member_function_arity_excluding_v); entry.checker = &detail::overload_type_checker; -#if defined(LUABRIDGE_ENABLE_REFLECT) - entry.returnType = std::string(detail::typeName>()); +#if LUABRIDGE_ENABLE_REFLECT + entry.returnType = std::string(detail::typeName>>()); entry.paramTypes = detail::reflect_param_type_names(); if constexpr (detail::is_function_with_hints_v) entry.paramHints = functions.hints; @@ -13208,8 +13326,8 @@ class Namespace : public detail::Registrar using ArgsPack = detail::remove_first_type_t>; entry.arity = static_cast(detail::member_function_arity_excluding_v); entry.checker = &detail::overload_type_checker; -#if defined(LUABRIDGE_ENABLE_REFLECT) - entry.returnType = std::string(detail::typeName>()); +#if LUABRIDGE_ENABLE_REFLECT + entry.returnType = std::string(detail::typeName>>()); entry.paramTypes = detail::reflect_param_type_names(); if constexpr (detail::is_function_with_hints_v) entry.paramHints = functions.hints; @@ -13220,8 +13338,8 @@ class Namespace : public detail::Registrar using ArgsPack = detail::function_arguments_t; entry.arity = static_cast(detail::member_function_arity_excluding_v); entry.checker = &detail::overload_type_checker; -#if defined(LUABRIDGE_ENABLE_REFLECT) - entry.returnType = std::string(detail::typeName>()); +#if LUABRIDGE_ENABLE_REFLECT + entry.returnType = std::string(detail::typeName>>()); entry.paramTypes = detail::reflect_param_type_names(); if constexpr (detail::is_function_with_hints_v) entry.paramHints = functions.hints; @@ -13304,7 +13422,7 @@ class Namespace : public detail::Registrar if constexpr (sizeof...(Functions) == 1) { -#if defined(LUABRIDGE_ENABLE_REFLECT) +#if LUABRIDGE_ENABLE_REFLECT ([&] { @@ -13314,16 +13432,13 @@ class Namespace : public detail::Registrar auto* overload_set = align(overload_set_unaligned); detail::OverloadEntry entry; - entry.arity = static_cast(detail::function_arity_excluding_v); - entry.checker = &detail::overload_type_checker; + entry.arity = -1; + entry.checker = nullptr; entry.returnType = std::string(detail::typeName()); entry.paramTypes = detail::reflect_param_type_names(); overload_set->entries.push_back(std::move(entry)); - lua_createtable(L, 1, 0); - lua_pushcclosure_x(L, &detail::constructor_placement_proxy, className, 0); - lua_rawseti(L, -2, 1); - lua_pushcclosure_x(L, &detail::try_overload_functions, className, 2); + lua_pushcclosure_x(L, &detail::constructor_placement_proxy, className, 1); } (), ...); #else @@ -13346,7 +13461,7 @@ class Namespace : public detail::Registrar detail::OverloadEntry entry; entry.arity = static_cast(detail::function_arity_excluding_v); entry.checker = &detail::overload_type_checker; -#if defined(LUABRIDGE_ENABLE_REFLECT) +#if LUABRIDGE_ENABLE_REFLECT entry.returnType = std::string(detail::typeName()); entry.paramTypes = detail::reflect_param_type_names(); #endif @@ -13389,29 +13504,25 @@ class Namespace : public detail::Registrar using InnerF = detail::unwrap_fn_type_t; using F = detail::constructor_forwarder; -#if defined(LUABRIDGE_ENABLE_REFLECT) +#if LUABRIDGE_ENABLE_REFLECT - auto* overload_set_unaligned = lua_newuserdata_aligned(L); - auto* overload_set = align(overload_set_unaligned); - { + auto* overload_set_unaligned = lua_newuserdata_aligned(L); + auto* overload_set = align(overload_set_unaligned); using ArgsPack = detail::remove_first_type_t>; detail::OverloadEntry entry; - entry.arity = static_cast(detail::function_arity_excluding_v) - 1; - entry.checker = &detail::overload_type_checker; + entry.arity = -1; + entry.checker = nullptr; entry.returnType = std::string(detail::typeName()); entry.paramTypes = detail::reflect_param_type_names(); if constexpr (detail::is_function_with_hints_v) entry.paramHints = functions.hints; overload_set->entries.push_back(std::move(entry)); } - - lua_createtable(L, 1, 0); - lua_newuserdata_aligned(L, F(detail::get_underlying(std::move(functions)))); - lua_pushcclosure_x(L, &detail::invoke_proxy_constructor, className, 1); - lua_rawseti(L, -2, 1); - lua_pushcclosure_x(L, &detail::try_overload_functions, className, 2); + + lua_newuserdata_aligned(L, F(detail::get_underlying(std::move(functions)))); + lua_pushcclosure_x(L, &detail::invoke_proxy_constructor, className, 2); #else lua_newuserdata_aligned(L, F(detail::get_underlying(std::move(functions)))); lua_pushcclosure_x(L, &detail::invoke_proxy_constructor, className, 1); @@ -13440,7 +13551,7 @@ class Namespace : public detail::Registrar using ArgsPack = detail::remove_first_type_t>; entry.arity = static_cast(detail::function_arity_excluding_v) - 1; entry.checker = &detail::overload_type_checker; -#if defined(LUABRIDGE_ENABLE_REFLECT) +#if LUABRIDGE_ENABLE_REFLECT entry.returnType = std::string(detail::typeName()); entry.paramTypes = detail::reflect_param_type_names(); if constexpr (detail::is_function_with_hints_v) @@ -13975,35 +14086,32 @@ class Namespace : public detail::Registrar if constexpr (sizeof...(Functions) == 1) { -#if defined(LUABRIDGE_ENABLE_REFLECT) +#if LUABRIDGE_ENABLE_REFLECT ([&] { - auto* overload_set_unaligned = lua_newuserdata_aligned(L); - auto* overload_set = align(overload_set_unaligned); - - detail::OverloadEntry entry; if constexpr (!detail::is_any_cfunction_pointer_v) { + auto* overload_set_unaligned = lua_newuserdata_aligned(L); + auto* overload_set = align(overload_set_unaligned); + + detail::OverloadEntry entry; using ArgsPack = detail::function_arguments_t; - entry.arity = static_cast(detail::function_arity_excluding_v); - entry.checker = &detail::overload_type_checker; - entry.returnType = std::string(detail::typeName>()); + entry.arity = -1; + entry.checker = nullptr; + entry.returnType = std::string(detail::typeName>>()); entry.paramTypes = detail::reflect_param_type_names(); if constexpr (detail::is_function_with_hints_v) entry.paramHints = functions.hints; + overload_set->entries.push_back(std::move(entry)); + + detail::push_function_reflect(L, detail::get_underlying(std::move(functions)), name); } else { - entry.arity = -1; - entry.checker = nullptr; + + detail::push_function(L, detail::get_underlying(std::move(functions)), name); } - overload_set->entries.push_back(std::move(entry)); - - lua_createtable(L, 1, 0); - detail::push_function(L, detail::get_underlying(std::move(functions)), name); - lua_rawseti(L, -2, 1); - lua_pushcclosure_x(L, &detail::try_overload_functions, name, 2); } (), ...); #else @@ -14033,8 +14141,8 @@ class Namespace : public detail::Registrar using ArgsPack = detail::function_arguments_t; entry.arity = static_cast(detail::function_arity_excluding_v); entry.checker = &detail::overload_type_checker; -#if defined(LUABRIDGE_ENABLE_REFLECT) - entry.returnType = std::string(detail::typeName>()); +#if LUABRIDGE_ENABLE_REFLECT + entry.returnType = std::string(detail::typeName>>()); entry.paramTypes = detail::reflect_param_type_names(); if constexpr (detail::is_function_with_hints_v) entry.paramHints = functions.hints; diff --git a/Source/LuaBridge/Inspect.h b/Source/LuaBridge/Inspect.h index d8a4a8a5..1dc75ac1 100644 --- a/Source/LuaBridge/Inspect.h +++ b/Source/LuaBridge/Inspect.h @@ -187,36 +187,25 @@ inline std::vector getOverloadInfos(lua_State* L, int funcIdx) if (!lua_isfunction(L, funcIdx)) return { OverloadInfo{} }; - // Check upvalue 1 for an OverloadSet userdata + // Check upvalue 1 for an OverloadSet userdata identified by its magic cookie. + // This handles two layouts: + // Multi-overload: upvalue[1]=OverloadSet, upvalue[2]=table of functions + // Single-overload REFLECT: upvalue[1]=OverloadSet, upvalue[2]=function data directly const char* uv1name = lua_getupvalue(L, funcIdx, 1); if (uv1name == nullptr) { - // No upvalues — bare cfunction (single function, no metadata available) + // No upvalues — bare cfunction, no metadata available return { OverloadInfo{} }; } - bool isOverloadSet = isfulluserdata(L, -1); - lua_pop(L, 1); // pop upvalue 1 - - if (!isOverloadSet) + OverloadSet* overload_set = nullptr; + if (isfulluserdata(L, -1)) { - // upvalue 1 is not a full userdata (e.g. lightuserdata function pointer) - return { OverloadInfo{} }; + auto* candidate = align(lua_touserdata(L, -1)); + if (candidate && candidate->magic == OverloadSet::kMagic) + overload_set = candidate; } - - // Confirm upvalue 2 is a table (the flat function table) - const char* uv2name = lua_getupvalue(L, funcIdx, 2); - bool hasTable = (uv2name != nullptr) && lua_istable(L, -1); - if (uv2name != nullptr) - lua_pop(L, 1); // pop upvalue 2 - - if (!hasTable) - return { OverloadInfo{} }; - - // Re-fetch upvalue 1 to read the OverloadSet - lua_getupvalue(L, funcIdx, 1); - auto* overload_set = align(lua_touserdata(L, -1)); - lua_pop(L, 1); + lua_pop(L, 1); // pop upvalue 1 if (!overload_set || overload_set->entries.empty()) return { OverloadInfo{} }; @@ -228,7 +217,7 @@ inline std::vector getOverloadInfos(lua_State* L, int funcIdx) { OverloadInfo info; -#if defined(LUABRIDGE_ENABLE_REFLECT) +#if LUABRIDGE_ENABLE_REFLECT info.returnType = entry.returnType; for (std::size_t i = 0; i < entry.paramTypes.size(); ++i) { @@ -263,7 +252,7 @@ inline bool isClassStaticTable(lua_State* L, int tableIdx) if (!lua_getmetatable(L, tableIdx)) // always works from C regardless of __metatable return false; - lua_rawgetp(L, -1, getClassKey()); // mt[getClassKey()] = cl ? + lua_rawgetp_x(L, -1, getClassKey()); // mt[getClassKey()] = cl ? bool result = lua_istable(L, -1); lua_pop(L, 1); // pop cl / nil @@ -287,27 +276,27 @@ inline ClassInspectInfo inspectClassFromStaticTable(lua_State* L, int stIdx) int mtIdx = lua_absindex(L, -1); // Get the class table cl = mt[getClassKey()] - lua_rawgetp(L, mtIdx, getClassKey()); + lua_rawgetp_x(L, mtIdx, getClassKey()); int clIdx = lua_absindex(L, -1); ClassInspectInfo cls; // 1. Class name - lua_rawgetp(L, clIdx, getTypeKey()); + lua_rawgetp_x(L, clIdx, getTypeKey()); if (lua_isstring(L, -1)) cls.name = stripConst(lua_tostring(L, -1)); lua_pop(L, 1); // 2. Base classes from parent list - lua_rawgetp(L, clIdx, getParentKey()); + lua_rawgetp_x(L, clIdx, getParentKey()); if (lua_istable(L, -1)) { int parIdx = lua_absindex(L, -1); - int len = static_cast(luaL_len(L, parIdx)); + int len = get_length(L, parIdx); for (int i = 1; i <= len; ++i) { lua_rawgeti(L, parIdx, i); - lua_rawgetp(L, -1, getTypeKey()); + lua_rawgetp_x(L, -1, getTypeKey()); if (lua_isstring(L, -1)) { std::string baseName = stripConst(lua_tostring(L, -1)); @@ -321,14 +310,14 @@ inline ClassInspectInfo inspectClassFromStaticTable(lua_State* L, int stIdx) // 3. Collect instance property names std::set instPropget; - lua_rawgetp(L, clIdx, getPropgetKey()); + lua_rawgetp_x(L, clIdx, getPropgetKey()); if (lua_istable(L, -1)) instPropget = collectTableKeys(L, -1); lua_pop(L, 1); // Collect instance property names that have a real (non-readonly-sentinel) setter std::set instPropsetReal; - lua_rawgetp(L, clIdx, getPropsetKey()); + lua_rawgetp_x(L, clIdx, getPropsetKey()); if (lua_istable(L, -1)) { int psIdx = lua_absindex(L, -1); @@ -384,14 +373,14 @@ inline ClassInspectInfo inspectClassFromStaticTable(lua_State* L, int stIdx) // 6. Collect static property names std::set stPropget; - lua_rawgetp(L, mtIdx, getPropgetKey()); + lua_rawgetp_x(L, mtIdx, getPropgetKey()); if (lua_istable(L, -1)) stPropget = collectTableKeys(L, -1); lua_pop(L, 1); // Collect static property names that have a real (non-readonly-sentinel) setter std::set stPropsetReal; - lua_rawgetp(L, mtIdx, getPropsetKey()); + lua_rawgetp_x(L, mtIdx, getPropsetKey()); if (lua_istable(L, -1)) { int psIdx = lua_absindex(L, -1); @@ -422,7 +411,7 @@ inline ClassInspectInfo inspectClassFromStaticTable(lua_State* L, int stIdx) // 8. Iterate mt for static methods + constructor static const std::set staticSkipKeys{ "__index", "__newindex", "__metatable" }; - lua_rawgetp(L, mtIdx, getClassKey()); // just to see mt's string keys, iterate mt directly + lua_rawgetp_x(L, mtIdx, getClassKey()); // just to see mt's string keys, iterate mt directly lua_pop(L, 1); lua_pushnil(L); @@ -466,14 +455,14 @@ inline NamespaceInspectInfo inspectNamespaceTable(lua_State* L, int nsIdx, std:: // Namespace-level properties (stored directly on the namespace table via getPropgetKey) std::set nsPropget; - lua_rawgetp(L, nsIdx, getPropgetKey()); + lua_rawgetp_x(L, nsIdx, getPropgetKey()); if (lua_istable(L, -1)) nsPropget = collectTableKeys(L, -1); lua_pop(L, 1); // Get the propset table so we can check each setter individually int nsPropsetTableIdx = 0; - lua_rawgetp(L, nsIdx, getPropsetKey()); + lua_rawgetp_x(L, nsIdx, getPropsetKey()); if (lua_istable(L, -1)) nsPropsetTableIdx = lua_absindex(L, -1); // leave propset table on stack; we pop it after the loop @@ -573,7 +562,7 @@ template [[nodiscard]] ClassInspectInfo inspect(lua_State* L) { // Load the class table (cl) directly from the Lua registry - lua_rawgetp(L, LUA_REGISTRYINDEX, detail::getClassRegistryKey()); + lua_rawgetp_x(L, LUA_REGISTRYINDEX, detail::getClassRegistryKey()); if (!lua_istable(L, -1)) { lua_pop(L, 1); @@ -584,7 +573,7 @@ template // cl[getStaticKey()] = st (the internal static table that acts as a metatable) // NOTE: this is NOT the user-visible ns["ClassName"] table; it is the metatable of that table. // isClassStaticTable() cannot be used here because st itself has no metatable. - lua_rawgetp(L, -1, detail::getStaticKey()); + lua_rawgetp_x(L, -1, detail::getStaticKey()); if (!lua_istable(L, -1)) { lua_pop(L, 2); @@ -593,7 +582,7 @@ template // Stack: ..., cl, st // Verify st actually belongs to a registered class: st[getClassKey()] must be a table - lua_rawgetp(L, -1, detail::getClassKey()); + lua_rawgetp_x(L, -1, detail::getClassKey()); const bool isClass = lua_istable(L, -1); lua_pop(L, 1); @@ -607,7 +596,6 @@ template ClassInspectInfo result = detail::inspectClassFromStaticTable(L, lua_gettop(L)); // inspectClassFromStaticTable pops st (used as mt) and the cl it fetched from st[getClassKey()] // Stack: ..., cl - lua_pop(L, 1); // pop cl return result; } @@ -625,7 +613,7 @@ template { if (namespaceName == nullptr || namespaceName[0] == '\0') { - lua_pushglobaltable(L); + lua_getglobal(L, "_G"); } else { @@ -687,9 +675,6 @@ inline void inspectAccept(lua_State* L, const char* namespaceName, InspectVisito accept(ns, visitor); } -//================================================================================================= -// Built-in visitors - //================================================================================================= /** * @brief Visitor that emits TypeScript-style pseudo-code to an ostream. @@ -723,16 +708,16 @@ class ConsoleVisitor : public InspectVisitor ++depth_; } - void endNamespace(const NamespaceInspectInfo& /*ns*/) override + void endNamespace([[maybe_unused]] const NamespaceInspectInfo& ns) override { --depth_; indent(); out_ << "}\n"; } - void visitFreeMember(const NamespaceInspectInfo& /*ns*/, const MemberInfo& m) override + void visitFreeMember([[maybe_unused]] const NamespaceInspectInfo& ns, const MemberInfo& m) override { - emitMember(m, /*className=*/""); + emitMember(m, ""); } void beginClass(const ClassInspectInfo& cls) override @@ -752,7 +737,7 @@ class ConsoleVisitor : public InspectVisitor ++depth_; } - void endClass(const ClassInspectInfo& /*cls*/) override + void endClass([[maybe_unused]] const ClassInspectInfo& cls) override { --depth_; indent(); @@ -792,7 +777,7 @@ class ConsoleVisitor : public InspectVisitor return ov.returnType.empty() ? "any" : ov.returnType; } - void emitMember(const MemberInfo& m, const std::string& className) const + void emitMember(const MemberInfo& m, [[maybe_unused]] const std::string& className) const { switch (m.kind) { @@ -822,15 +807,13 @@ class ConsoleVisitor : public InspectVisitor { indent(); if (isStatic) out_ << "static "; - out_ << m.name << "(" << paramStr(m.overloads[i]) << "): " - << retStr(m.overloads[i]) << ";\n"; + out_ << m.name << "(" << paramStr(m.overloads[i]) << "): " << retStr(m.overloads[i]) << ";\n"; } break; } default: break; } - (void)className; } std::ostream& out_; @@ -858,7 +841,7 @@ class LuaLSVisitor : public InspectVisitor ns_ = ns.name == "_G" ? "" : ns.name; } - void endNamespace(const NamespaceInspectInfo& /*ns*/) override {} + void endNamespace([[maybe_unused]] const NamespaceInspectInfo& ns) override {} void visitFreeMember(const NamespaceInspectInfo& ns, const MemberInfo& m) override { @@ -871,6 +854,7 @@ class LuaLSVisitor : public InspectVisitor if (!ov.returnType.empty() && ov.returnType != "void") out_ << "---@return " << luaType(ov.returnType) << "\n"; } + std::string qual = ns_.empty() ? "" : (ns_ + "."); out_ << "function " << qual << m.name << "("; if (!m.overloads.empty()) @@ -894,7 +878,7 @@ class LuaLSVisitor : public InspectVisitor // (will be added in visitMember below, but need the class annotation open) } - void endClass(const ClassInspectInfo& /*cls*/) override + void endClass([[maybe_unused]] const ClassInspectInfo& cls) override { out_ << "local " << curClass_ << " = {}\n\n"; curClass_.clear(); @@ -1094,13 +1078,13 @@ class LuaProxyVisitor : public InspectVisitor out_ << cls.name << ".__index = " << cls.name << "\n\n"; } - void endClass(const ClassInspectInfo& /*cls*/) override + void endClass([[maybe_unused]] const ClassInspectInfo& cls) override { curClass_.clear(); out_ << "\n"; } - void visitMember(const ClassInspectInfo& /*cls*/, const MemberInfo& m) override + void visitMember([[maybe_unused]] const ClassInspectInfo& cls, const MemberInfo& m) override { if (m.overloads.empty()) return; @@ -1170,7 +1154,7 @@ class JsonVisitor : public InspectVisitor firstClass_ = true; } - void endNamespace(const NamespaceInspectInfo& /*ns*/) override + void endNamespace([[maybe_unused]] const NamespaceInspectInfo& ns) override { --depth_; out_ << "\n"; @@ -1197,7 +1181,7 @@ class JsonVisitor : public InspectVisitor firstMember_ = true; } - void endClass(const ClassInspectInfo& /*cls*/) override + void endClass([[maybe_unused]] const ClassInspectInfo& cls) override { --depth_; out_ << "\n"; @@ -1206,7 +1190,7 @@ class JsonVisitor : public InspectVisitor indent(); out_ << "}"; } - void visitMember(const ClassInspectInfo& /*cls*/, const MemberInfo& m) override + void visitMember([[maybe_unused]] const ClassInspectInfo& cls, const MemberInfo& m) override { if (!firstMember_) out_ << ",\n"; firstMember_ = false; @@ -1302,12 +1286,12 @@ class LuaTableVisitor : public InspectVisitor subNsIdx_ = 1; } - void endNamespace(const NamespaceInspectInfo& /*ns*/) override + void endNamespace([[maybe_unused]] const NamespaceInspectInfo& ns) override { // namespace table stays on stack as the result } - void visitFreeMember(const NamespaceInspectInfo& /*ns*/, const MemberInfo& m) override + void visitFreeMember([[maybe_unused]] const NamespaceInspectInfo& ns, const MemberInfo& m) override { lua_getfield(L_, -1, "freeMembers"); pushMemberInfo(m); @@ -1334,7 +1318,7 @@ class LuaTableVisitor : public InspectVisitor memberIdx_ = 1; } - void endClass(const ClassInspectInfo& /*cls*/) override + void endClass([[maybe_unused]] const ClassInspectInfo& cls) override { // class table is on top; store it in the namespace's "classes" array lua_getfield(L_, -2, "classes"); @@ -1343,7 +1327,7 @@ class LuaTableVisitor : public InspectVisitor lua_pop(L_, 2); // pop classes table + class table } - void visitMember(const ClassInspectInfo& /*cls*/, const MemberInfo& m) override + void visitMember([[maybe_unused]] const ClassInspectInfo& cls, const MemberInfo& m) override { lua_getfield(L_, -1, "members"); pushMemberInfo(m); diff --git a/Source/LuaBridge/detail/CFunctions.h b/Source/LuaBridge/detail/CFunctions.h index 0621d1f4..dfbb2610 100644 --- a/Source/LuaBridge/detail/CFunctions.h +++ b/Source/LuaBridge/detail/CFunctions.h @@ -1682,35 +1682,35 @@ struct function * * The member function pointer is in the first upvalue. The class userdata object is at the top of the Lua stack. */ -template +template int invoke_member_function(lua_State* L) { using FnTraits = function_traits; - LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(1))); + LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(UV))); auto ptr = Userdata::get(L, 1, false); if (! ptr) raise_lua_error(L, "%s", ptr.error_cstr()); - const F& func = *static_cast(lua_touserdata(L, lua_upvalueindex(1))); + const F& func = *static_cast(lua_touserdata(L, lua_upvalueindex(UV))); LUABRIDGE_ASSERT(func != nullptr); return function::call(L, *ptr, func); } -template +template int invoke_const_member_function(lua_State* L) { using FnTraits = function_traits; - LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(1))); + LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(UV))); auto ptr = Userdata::get(L, 1, true); if (! ptr) raise_lua_error(L, "%s", ptr.error_cstr()); - const F& func = *static_cast(lua_touserdata(L, lua_upvalueindex(1))); + const F& func = *static_cast(lua_touserdata(L, lua_upvalueindex(UV))); LUABRIDGE_ASSERT(func != nullptr); return function::call(L, *ptr, func); @@ -1790,14 +1790,14 @@ int invoke_const_member_cfunction(lua_State* L) * * The proxy function pointer (lightuserdata) is in the first upvalue. The class userdata object is at the top of the Lua stack. */ -template +template int invoke_proxy_function(lua_State* L) { using FnTraits = function_traits; - LUABRIDGE_ASSERT(lua_islightuserdata(L, lua_upvalueindex(1))); + LUABRIDGE_ASSERT(lua_islightuserdata(L, lua_upvalueindex(UV))); - auto func = reinterpret_cast(lua_touserdata(L, lua_upvalueindex(1))); + auto func = reinterpret_cast(lua_touserdata(L, lua_upvalueindex(UV))); LUABRIDGE_ASSERT(func != nullptr); return function::call(L, func); @@ -1809,14 +1809,14 @@ int invoke_proxy_function(lua_State* L) * * The proxy std::function (lightuserdata) is in the first upvalue. The class userdata object is at the top of the Lua stack. */ -template +template int invoke_proxy_functor(lua_State* L) { using FnTraits = function_traits>; - LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(1))); + LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(UV))); - auto& func = *align>(lua_touserdata(L, lua_upvalueindex(1))); + auto& func = *align>(lua_touserdata(L, lua_upvalueindex(UV))); return function::call(L, func); } @@ -1851,14 +1851,14 @@ inline int invoke_safe_cfunction(lua_State* L) * * 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 +template int invoke_proxy_constructor(lua_State* L) { using FnTraits = function_traits; - LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(1))); + LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(UV))); - auto& func = *align(lua_touserdata(L, lua_upvalueindex(1))); + auto& func = *align(lua_touserdata(L, lua_upvalueindex(UV))); function::call(L, func); @@ -1898,7 +1898,7 @@ struct OverloadEntry int arity; // -1 for variadic (lua_CFunction): always attempt TypeChecker checker; // nullptr for variadic: skip type pre-checking -#if defined(LUABRIDGE_ENABLE_REFLECT) +#if LUABRIDGE_ENABLE_REFLECT std::string returnType; ///< C++ return type name (from detail::typeName) std::vector paramTypes; ///< C++ parameter type names (excluding lua_State*) std::vector paramHints; ///< Optional user-provided parameter names (from withHints) @@ -1909,10 +1909,16 @@ struct OverloadEntry * @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). + * For multi-overload dispatch: the actual function closures are in a flat Lua table (upvalue 2). + * For single-overload REFLECT: the OverloadSet is upvalue[1] of the actual function closure directly. + * + * The magic field allows getOverloadInfos() to reliably distinguish this from other full-userdata + * upvalues (e.g. functor objects stored in invoke_proxy_functor closures). */ struct OverloadSet { + static constexpr uint32_t kMagic = 0x4C425246u; // 'LBRF' — LuaBridge ReFLect sentinel + uint32_t magic = kMagic; std::vector entries; }; @@ -1971,6 +1977,8 @@ bool overload_type_checker(lua_State* L, int start) * 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) { @@ -2092,6 +2100,35 @@ inline void push_function(lua_State* L, F&& f, const char* debugname) lua_pushcclosure_x(L, &invoke_proxy_functor, debugname, 1); } +//================================================================================================= +// REFLECT variants of push_function: OverloadSet is already at stack top (upvalue[1]); +// these push function data as upvalue[2] and create a 2-upvalue closure. + +// Generic function pointer (reflect) +template +inline void push_function_reflect(lua_State* L, ReturnType (*fp)(Params...), const char* debugname) +{ + using FnType = ReturnType (*)(Params...); + lua_pushlightuserdata(L, reinterpret_cast(fp)); + lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 2); +} + +template +inline void push_function_reflect(lua_State* L, ReturnType (*fp)(Params...) noexcept, const char* debugname) +{ + using FnType = ReturnType (*)(Params...) noexcept; + lua_pushlightuserdata(L, reinterpret_cast(fp)); + lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 2); +} + +// Callable object / lambda (reflect) +template && !std::is_pointer_v && !std::is_member_function_pointer_v>> +inline void push_function_reflect(lua_State* L, F&& f, const char* debugname) +{ + lua_newuserdata_aligned(L, std::forward(f)); + lua_pushcclosure_x(L, &invoke_proxy_functor, debugname, 2); +} + //================================================================================================= // Lua CFunction template @@ -2262,6 +2299,127 @@ void push_member_function(lua_State* L, int (U::*mfp)(lua_State*) const, const c lua_pushcclosure_x(L, &invoke_const_member_cfunction, debugname, 1); } +//================================================================================================= +// REFLECT variants of push_member_function: OverloadSet is already at stack top (upvalue[1]); +// these push function data as upvalue[2] and create a 2-upvalue closure. +// Only non-cfunction types are handled here; is_any_cfunction_pointer_v types skip OverloadSet. + +// Generic function pointer (proxy-style, first arg is T* or T&) +template +void push_member_function_reflect(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, 2); +} + +template +void push_member_function_reflect(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, 2); +} + +template +void push_member_function_reflect(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, 2); +} + +template +void push_member_function_reflect(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, 2); +} + +template +void push_member_function_reflect(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, 2); +} + +template +void push_member_function_reflect(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, 2); +} + +template +void push_member_function_reflect(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, 2); +} + +template +void push_member_function_reflect(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, 2); +} + +// True member function pointers (non-const) +template +void push_member_function_reflect(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_pushcclosure_x(L, &invoke_member_function, debugname, 2); +} + +template +void push_member_function_reflect(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_pushcclosure_x(L, &invoke_member_function, debugname, 2); +} + +// True member function pointers (const) +template +void push_member_function_reflect(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_pushcclosure_x(L, &invoke_const_member_function, debugname, 2); +} + +template +void push_member_function_reflect(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_pushcclosure_x(L, &invoke_const_member_function, debugname, 2); +} + +// Callable object / lambda (reflect) +template && + std::is_object_v && + !std::is_pointer_v && + !std::is_member_function_pointer_v>> +void push_member_function_reflect(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, 2); +} + //================================================================================================= /** * @brief @@ -2610,16 +2768,30 @@ struct constructor { static T* construct(const Args& args) { - auto alloc = [](auto&&... args) { return new T(std::forward(args)...); }; - - return std::apply(alloc, args); + if constexpr (std::is_aggregate_v) + { + auto alloc = [](auto&&... args) { return new T{std::forward(args)...}; }; + return std::apply(alloc, args); + } + else + { + 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); + if constexpr (std::is_aggregate_v) + { + auto alloc = [ptr](auto&&... args) { return new (ptr) T{std::forward(args)...}; }; + return std::apply(alloc, args); + } + else + { + auto alloc = [ptr](auto&&... args) { return new (ptr) T(std::forward(args)...); }; + return std::apply(alloc, args); + } } }; diff --git a/Source/LuaBridge/detail/FunctionHints.h b/Source/LuaBridge/detail/FunctionHints.h index 98c15510..1ab29d2e 100644 --- a/Source/LuaBridge/detail/FunctionHints.h +++ b/Source/LuaBridge/detail/FunctionHints.h @@ -145,28 +145,30 @@ template * lua_State* parameters are filtered out (they are auto-injected by LuaBridge, not * visible from Lua). */ +template +void reflect_param_type_names_impl(std::vector& result, std::index_sequence) +{ + // Fold expression over comma operator (C++17): invoke a lambda for each index + ( + [&]() + { + using ParamT = std::tuple_element_t; + + constexpr bool isLuaState = + std::is_pointer_v && + std::is_same_v>, lua_State>; + + if constexpr (!isLuaState) + result.push_back(std::string(typeName>())); + }(), + ...); +} + template [[nodiscard]] std::vector reflect_param_type_names() { std::vector result; - - [&](std::index_sequence) - { - ( - [&] - { - using ParamT = std::tuple_element_t; - - constexpr bool isLuaState = - std::is_pointer_v && - std::is_same_v>, lua_State>; - - if constexpr (!isLuaState) - result.push_back(std::string(typeName>())); - }(), - ...); - }(std::make_index_sequence>{}); - + reflect_param_type_names_impl(result, std::make_index_sequence>{}); return result; } diff --git a/Source/LuaBridge/detail/Namespace.h b/Source/LuaBridge/detail/Namespace.h index e0271c66..0039f3e4 100644 --- a/Source/LuaBridge/detail/Namespace.h +++ b/Source/LuaBridge/detail/Namespace.h @@ -745,35 +745,32 @@ class Namespace : public detail::Registrar if constexpr (sizeof...(Functions) == 1) { -#if defined(LUABRIDGE_ENABLE_REFLECT) - // Wrap in 1-entry OverloadSet so metadata is accessible at runtime. +#if LUABRIDGE_ENABLE_REFLECT + // Embed 1-entry OverloadSet as upvalue[1] of the actual function closure so + // metadata is accessible at runtime without a wrapper. ([&] { - auto* overload_set_unaligned = lua_newuserdata_aligned(L); - auto* overload_set = align(overload_set_unaligned); - - detail::OverloadEntry entry; if constexpr (!detail::is_any_cfunction_pointer_v) { + auto* overload_set_unaligned = lua_newuserdata_aligned(L); + auto* overload_set = align(overload_set_unaligned); + + detail::OverloadEntry entry; using ArgsPack = detail::function_arguments_t; - entry.arity = static_cast(detail::function_arity_excluding_v); - entry.checker = &detail::overload_type_checker; - entry.returnType = std::string(detail::typeName>()); + entry.arity = -1; + entry.checker = nullptr; + entry.returnType = std::string(detail::typeName>>()); entry.paramTypes = detail::reflect_param_type_names(); if constexpr (detail::is_function_with_hints_v) entry.paramHints = functions.hints; + overload_set->entries.push_back(std::move(entry)); + + detail::push_function_reflect(L, detail::get_underlying(std::move(functions)), name); } else { - entry.arity = -1; - entry.checker = nullptr; + detail::push_function(L, detail::get_underlying(std::move(functions)), name); } - overload_set->entries.push_back(std::move(entry)); - - lua_createtable(L, 1, 0); - detail::push_function(L, detail::get_underlying(std::move(functions)), name); - lua_rawseti(L, -2, 1); - lua_pushcclosure_x(L, &detail::try_overload_functions, name, 2); } (), ...); #else @@ -803,8 +800,8 @@ class Namespace : public detail::Registrar using ArgsPack = detail::function_arguments_t; entry.arity = static_cast(detail::function_arity_excluding_v); entry.checker = &detail::overload_type_checker; -#if defined(LUABRIDGE_ENABLE_REFLECT) - entry.returnType = std::string(detail::typeName>()); +#if LUABRIDGE_ENABLE_REFLECT + entry.returnType = std::string(detail::typeName>>()); entry.paramTypes = detail::reflect_param_type_names(); if constexpr (detail::is_function_with_hints_v) entry.paramHints = functions.hints; @@ -973,47 +970,47 @@ class Namespace : public detail::Registrar if constexpr (sizeof...(Functions) == 1) { -#if defined(LUABRIDGE_ENABLE_REFLECT) - // When reflection is enabled, wrap single functions in a 1-entry OverloadSet so - // metadata (return type, param types, hints) is uniformly accessible at runtime. +#if LUABRIDGE_ENABLE_REFLECT + // Embed 1-entry OverloadSet as upvalue[1] of the actual function closure so + // metadata is accessible at runtime without a wrapper (avoiding C-to-C call chains + // that break luaL_argerror function-name resolution). ([&] { - auto* overload_set_unaligned = lua_newuserdata_aligned(L); - auto* overload_set = align(overload_set_unaligned); - - detail::OverloadEntry entry; if constexpr (!detail::is_any_cfunction_pointer_v) { + auto* overload_set_unaligned = lua_newuserdata_aligned(L); + auto* overload_set = align(overload_set_unaligned); + + detail::OverloadEntry entry; if constexpr (detail::is_proxy_member_function_v) { using ArgsPack = detail::remove_first_type_t>; - entry.arity = static_cast(detail::member_function_arity_excluding_v); - entry.checker = &detail::overload_type_checker; - entry.returnType = std::string(detail::typeName>()); + entry.arity = -1; + entry.checker = nullptr; + entry.returnType = std::string(detail::typeName>>()); entry.paramTypes = detail::reflect_param_type_names(); } else { using ArgsPack = detail::function_arguments_t; - entry.arity = static_cast(detail::member_function_arity_excluding_v); - entry.checker = &detail::overload_type_checker; - entry.returnType = std::string(detail::typeName>()); + entry.arity = -1; + entry.checker = nullptr; + entry.returnType = std::string(detail::typeName>>()); entry.paramTypes = detail::reflect_param_type_names(); } if constexpr (detail::is_function_with_hints_v) entry.paramHints = functions.hints; + overload_set->entries.push_back(std::move(entry)); + + // OverloadSet is now at stack top (upvalue[1]). + // push_member_function_reflect pushes function data as upvalue[2]. + detail::push_member_function_reflect(L, detail::get_underlying(std::move(functions)), name); } else { - entry.arity = -1; - entry.checker = nullptr; + // Bare cfunction: no type metadata to embed; register directly. + detail::push_member_function(L, detail::get_underlying(std::move(functions)), name); } - overload_set->entries.push_back(std::move(entry)); - - lua_createtable(L, 1, 0); - detail::push_member_function(L, detail::get_underlying(std::move(functions)), name); - lua_rawseti(L, -2, 1); - lua_pushcclosure_x(L, &detail::try_overload_functions, name, 2); } (), ...); #else @@ -1060,8 +1057,8 @@ class Namespace : public detail::Registrar using ArgsPack = detail::remove_first_type_t>; entry.arity = static_cast(detail::member_function_arity_excluding_v); entry.checker = &detail::overload_type_checker; -#if defined(LUABRIDGE_ENABLE_REFLECT) - entry.returnType = std::string(detail::typeName>()); +#if LUABRIDGE_ENABLE_REFLECT + entry.returnType = std::string(detail::typeName>>()); entry.paramTypes = detail::reflect_param_type_names(); if constexpr (detail::is_function_with_hints_v) entry.paramHints = functions.hints; @@ -1072,8 +1069,8 @@ class Namespace : public detail::Registrar using ArgsPack = detail::function_arguments_t; entry.arity = static_cast(detail::member_function_arity_excluding_v); entry.checker = &detail::overload_type_checker; -#if defined(LUABRIDGE_ENABLE_REFLECT) - entry.returnType = std::string(detail::typeName>()); +#if LUABRIDGE_ENABLE_REFLECT + entry.returnType = std::string(detail::typeName>>()); entry.paramTypes = detail::reflect_param_type_names(); if constexpr (detail::is_function_with_hints_v) entry.paramHints = functions.hints; @@ -1129,8 +1126,8 @@ class Namespace : public detail::Registrar using ArgsPack = detail::remove_first_type_t>; entry.arity = static_cast(detail::member_function_arity_excluding_v); entry.checker = &detail::overload_type_checker; -#if defined(LUABRIDGE_ENABLE_REFLECT) - entry.returnType = std::string(detail::typeName>()); +#if LUABRIDGE_ENABLE_REFLECT + entry.returnType = std::string(detail::typeName>>()); entry.paramTypes = detail::reflect_param_type_names(); if constexpr (detail::is_function_with_hints_v) entry.paramHints = functions.hints; @@ -1141,8 +1138,8 @@ class Namespace : public detail::Registrar using ArgsPack = detail::function_arguments_t; entry.arity = static_cast(detail::member_function_arity_excluding_v); entry.checker = &detail::overload_type_checker; -#if defined(LUABRIDGE_ENABLE_REFLECT) - entry.returnType = std::string(detail::typeName>()); +#if LUABRIDGE_ENABLE_REFLECT + entry.returnType = std::string(detail::typeName>>()); entry.paramTypes = detail::reflect_param_type_names(); if constexpr (detail::is_function_with_hints_v) entry.paramHints = functions.hints; @@ -1266,8 +1263,10 @@ class Namespace : public detail::Registrar if constexpr (sizeof...(Functions) == 1) { -#if defined(LUABRIDGE_ENABLE_REFLECT) - // Wrap in 1-entry OverloadSet so constructor metadata is accessible at runtime. +#if LUABRIDGE_ENABLE_REFLECT + // Embed 1-entry OverloadSet as upvalue[1] of the constructor closure. + // constructor_placement_proxy uses no upvalues internally, so upvalue[1] + // is only there for getOverloadInfos() to read metadata. ([&] { using ArgsPack = detail::function_arguments_t; @@ -1276,16 +1275,14 @@ class Namespace : public detail::Registrar auto* overload_set = align(overload_set_unaligned); detail::OverloadEntry entry; - entry.arity = static_cast(detail::function_arity_excluding_v); - entry.checker = &detail::overload_type_checker; + entry.arity = -1; + entry.checker = nullptr; entry.returnType = std::string(detail::typeName()); entry.paramTypes = detail::reflect_param_type_names(); overload_set->entries.push_back(std::move(entry)); - lua_createtable(L, 1, 0); - lua_pushcclosure_x(L, &detail::constructor_placement_proxy, className, 0); - lua_rawseti(L, -2, 1); - lua_pushcclosure_x(L, &detail::try_overload_functions, className, 2); + // OverloadSet is at stack top (upvalue[1]); constructor_placement_proxy ignores it. + lua_pushcclosure_x(L, &detail::constructor_placement_proxy, className, 1); } (), ...); #else @@ -1308,7 +1305,7 @@ class Namespace : public detail::Registrar detail::OverloadEntry entry; entry.arity = static_cast(detail::function_arity_excluding_v); entry.checker = &detail::overload_type_checker; -#if defined(LUABRIDGE_ENABLE_REFLECT) +#if LUABRIDGE_ENABLE_REFLECT entry.returnType = std::string(detail::typeName()); entry.paramTypes = detail::reflect_param_type_names(); #endif @@ -1361,29 +1358,26 @@ class Namespace : public detail::Registrar using InnerF = detail::unwrap_fn_type_t; using F = detail::constructor_forwarder; -#if defined(LUABRIDGE_ENABLE_REFLECT) - // Wrap in 1-entry OverloadSet so constructor metadata is accessible at runtime. - auto* overload_set_unaligned = lua_newuserdata_aligned(L); - auto* overload_set = align(overload_set_unaligned); - +#if LUABRIDGE_ENABLE_REFLECT + // Embed 1-entry OverloadSet as upvalue[1] of the constructor closure so + // metadata is accessible at runtime without a wrapper. { + auto* overload_set_unaligned = lua_newuserdata_aligned(L); + auto* overload_set = align(overload_set_unaligned); // skip void* first arg (placement new destination, not a Lua argument) using ArgsPack = detail::remove_first_type_t>; detail::OverloadEntry entry; - entry.arity = static_cast(detail::function_arity_excluding_v) - 1; - entry.checker = &detail::overload_type_checker; + entry.arity = -1; + entry.checker = nullptr; entry.returnType = std::string(detail::typeName()); entry.paramTypes = detail::reflect_param_type_names(); if constexpr (detail::is_function_with_hints_v) entry.paramHints = functions.hints; overload_set->entries.push_back(std::move(entry)); } - - lua_createtable(L, 1, 0); - lua_newuserdata_aligned(L, F(detail::get_underlying(std::move(functions)))); - lua_pushcclosure_x(L, &detail::invoke_proxy_constructor, className, 1); - lua_rawseti(L, -2, 1); - lua_pushcclosure_x(L, &detail::try_overload_functions, className, 2); + // OverloadSet is now at stack top (upvalue[1]). + lua_newuserdata_aligned(L, F(detail::get_underlying(std::move(functions)))); // upvalue[2] + lua_pushcclosure_x(L, &detail::invoke_proxy_constructor, className, 2); #else lua_newuserdata_aligned(L, F(detail::get_underlying(std::move(functions)))); // Stack: co, cl, st, upvalue lua_pushcclosure_x(L, &detail::invoke_proxy_constructor, className, 1); // Stack: co, cl, st, function @@ -1412,7 +1406,7 @@ class Namespace : public detail::Registrar using ArgsPack = detail::remove_first_type_t>; entry.arity = static_cast(detail::function_arity_excluding_v) - 1; entry.checker = &detail::overload_type_checker; -#if defined(LUABRIDGE_ENABLE_REFLECT) +#if LUABRIDGE_ENABLE_REFLECT entry.returnType = std::string(detail::typeName()); entry.paramTypes = detail::reflect_param_type_names(); if constexpr (detail::is_function_with_hints_v) @@ -2092,35 +2086,36 @@ class Namespace : public detail::Registrar if constexpr (sizeof...(Functions) == 1) { -#if defined(LUABRIDGE_ENABLE_REFLECT) - // Wrap in 1-entry OverloadSet so metadata is accessible at runtime. +#if LUABRIDGE_ENABLE_REFLECT + // Embed 1-entry OverloadSet as upvalue[1] of the actual function closure so that + // metadata is accessible at runtime without a wrapper (avoiding C-to-C call chains + // that break luaL_argerror function-name resolution). ([&] { - auto* overload_set_unaligned = lua_newuserdata_aligned(L); - auto* overload_set = align(overload_set_unaligned); - - detail::OverloadEntry entry; if constexpr (!detail::is_any_cfunction_pointer_v) { + auto* overload_set_unaligned = lua_newuserdata_aligned(L); + auto* overload_set = align(overload_set_unaligned); + + detail::OverloadEntry entry; using ArgsPack = detail::function_arguments_t; - entry.arity = static_cast(detail::function_arity_excluding_v); - entry.checker = &detail::overload_type_checker; - entry.returnType = std::string(detail::typeName>()); + entry.arity = -1; // single overload: arity check disabled + entry.checker = nullptr; // single overload: no type dispatch needed + entry.returnType = std::string(detail::typeName>>()); entry.paramTypes = detail::reflect_param_type_names(); if constexpr (detail::is_function_with_hints_v) entry.paramHints = functions.hints; + overload_set->entries.push_back(std::move(entry)); + + // OverloadSet is now at stack top (upvalue[1]). + // push_function_reflect pushes function data as upvalue[2] and creates the closure. + detail::push_function_reflect(L, detail::get_underlying(std::move(functions)), name); } else { - entry.arity = -1; - entry.checker = nullptr; + // Bare cfunction: no type metadata to embed; register directly. + detail::push_function(L, detail::get_underlying(std::move(functions)), name); } - overload_set->entries.push_back(std::move(entry)); - - lua_createtable(L, 1, 0); - detail::push_function(L, detail::get_underlying(std::move(functions)), name); - lua_rawseti(L, -2, 1); - lua_pushcclosure_x(L, &detail::try_overload_functions, name, 2); } (), ...); #else @@ -2150,8 +2145,8 @@ class Namespace : public detail::Registrar using ArgsPack = detail::function_arguments_t; entry.arity = static_cast(detail::function_arity_excluding_v); entry.checker = &detail::overload_type_checker; -#if defined(LUABRIDGE_ENABLE_REFLECT) - entry.returnType = std::string(detail::typeName>()); +#if LUABRIDGE_ENABLE_REFLECT + entry.returnType = std::string(detail::typeName>>()); entry.paramTypes = detail::reflect_param_type_names(); if constexpr (detail::is_function_with_hints_v) entry.paramHints = functions.hints; diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 42fa651e..3650aa56 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -71,7 +71,14 @@ source_group ("Source Files" FILES ${LUABRIDGE_TEST_SOURCE_FILES}) # ====================================================== Lua C -set (LUABRIDGE_LUA_C_DEFINES "LUABRIDGE_SAFE_LUA_C_EXCEPTION_HANDLING=1") +set (LUABRIDGE_LUA_DEFAULT_DEFINES) + +set (LUABRIDGE_LUA_CPP_DEFINES + ${LUABRIDGE_LUA_DEFAULT_DEFINES}) + +set (LUABRIDGE_LUA_C_DEFINES + ${LUABRIDGE_LUA_DEFAULT_DEFINES} + -DLUABRIDGE_SAFE_LUA_C_EXCEPTION_HANDLING=1) # ====================================================== Lua 5.1 @@ -199,63 +206,45 @@ function (setup_target_for_coverage TARGET_NAME SOURCE_LOCATION SOURCE_PACKAGE) endfunction () function (setup_coverage_single_target) + set (LUABRIDGE_COVERAGE_TARGETS) + + foreach (LUA_VERSION_SUFFIX IN ITEMS 51 52 53 54 55) + foreach (TEST_VARIANT IN ITEMS "" "Noexcept" "Reflect" "ReflectNoexcept" "LuaC" "LuaCNoexcept") + list (APPEND LUABRIDGE_COVERAGE_TARGETS "LuaBridgeTests${LUA_VERSION_SUFFIX}${TEST_VARIANT}") + endforeach () + endforeach () + + list (APPEND LUABRIDGE_COVERAGE_TARGETS + LuaBridgeTestsLuau + LuaBridgeTestsLuauReflect) + + if (NOT EMSCRIPTEN) + list (APPEND LUABRIDGE_COVERAGE_TARGETS + LuaBridgeTestsLuaJIT + LuaBridgeTestsLuaJITNoexcept + LuaBridgeTestsLuaJITReflect + LuaBridgeTestsLuaJITReflectNoexcept + LuaBridgeTestsRavi + LuaBridgeTestsRaviReflect) + endif () + + set (LUABRIDGE_COVERAGE_INFO_FILES) + set (LUABRIDGE_COVERAGE_MERGE_ARGS) + foreach (COVERAGE_TARGET IN LISTS LUABRIDGE_COVERAGE_TARGETS) + list (APPEND LUABRIDGE_COVERAGE_INFO_FILES "coverage/${COVERAGE_TARGET}.info") + list (APPEND LUABRIDGE_COVERAGE_MERGE_ARGS -a "coverage/${COVERAGE_TARGET}.info") + endforeach () + add_custom_target (LuaBridgeTestsCoverage COMMAND ${CMAKE_COMMAND} -E make_directory coverage_html COMMAND ${CMAKE_COMMAND} -E rm -Rf coverage_html/* COMMAND ${CMAKE_COMMAND} -E rm -f coverage/Merged.info COMMAND ${LCOV_EXECUTABLE} - -a "coverage/LuaBridgeTests51.info" - -a "coverage/LuaBridgeTests51LuaC.info" - -a "coverage/LuaBridgeTests51Noexcept.info" - -a "coverage/LuaBridgeTests51LuaCNoexcept.info" - -a "coverage/LuaBridgeTests52.info" - -a "coverage/LuaBridgeTests52LuaC.info" - -a "coverage/LuaBridgeTests52Noexcept.info" - -a "coverage/LuaBridgeTests52LuaCNoexcept.info" - -a "coverage/LuaBridgeTests53.info" - -a "coverage/LuaBridgeTests53LuaC.info" - -a "coverage/LuaBridgeTests53Noexcept.info" - -a "coverage/LuaBridgeTests53LuaCNoexcept.info" - -a "coverage/LuaBridgeTests54.info" - -a "coverage/LuaBridgeTests54LuaC.info" - -a "coverage/LuaBridgeTests54Noexcept.info" - -a "coverage/LuaBridgeTests54LuaCNoexcept.info" - -a "coverage/LuaBridgeTests55.info" - -a "coverage/LuaBridgeTests55LuaC.info" - -a "coverage/LuaBridgeTests55Noexcept.info" - -a "coverage/LuaBridgeTests55LuaCNoexcept.info" - -a "coverage/LuaBridgeTestsLuaJIT.info" - -a "coverage/LuaBridgeTestsLuaJITNoexcept.info" - -a "coverage/LuaBridgeTestsLuau.info" - -a "coverage/LuaBridgeTestsRavi.info" + ${LUABRIDGE_COVERAGE_MERGE_ARGS} -o "coverage/Merged.info" COMMAND ${GENHTML_EXECUTABLE} --rc branch_coverage=1 "coverage/Merged.info" -o "coverage_html" - DEPENDS - "coverage/LuaBridgeTests51.info" - "coverage/LuaBridgeTests51LuaC.info" - "coverage/LuaBridgeTests51Noexcept.info" - "coverage/LuaBridgeTests51LuaCNoexcept.info" - "coverage/LuaBridgeTests52.info" - "coverage/LuaBridgeTests52LuaC.info" - "coverage/LuaBridgeTests52Noexcept.info" - "coverage/LuaBridgeTests52LuaCNoexcept.info" - "coverage/LuaBridgeTests53.info" - "coverage/LuaBridgeTests53LuaC.info" - "coverage/LuaBridgeTests53Noexcept.info" - "coverage/LuaBridgeTests53LuaCNoexcept.info" - "coverage/LuaBridgeTests54.info" - "coverage/LuaBridgeTests54LuaC.info" - "coverage/LuaBridgeTests54Noexcept.info" - "coverage/LuaBridgeTests54LuaCNoexcept.info" - "coverage/LuaBridgeTests55.info" - "coverage/LuaBridgeTests55LuaC.info" - "coverage/LuaBridgeTests55Noexcept.info" - "coverage/LuaBridgeTests55LuaCNoexcept.info" - "coverage/LuaBridgeTestsLuaJIT.info" - "coverage/LuaBridgeTestsLuaJITNoexcept.info" - "coverage/LuaBridgeTestsLuau.info" - "coverage/LuaBridgeTestsRavi.info" + DEPENDS ${LUABRIDGE_COVERAGE_INFO_FILES} WORKING_DIRECTORY ${CMAKE_BINARY_DIR} VERBATIM) @@ -286,7 +275,6 @@ macro (add_test_app LUABRIDGE_TEST_NAME LUA_VERSION LUABRIDGE_TEST_LUA_LIBRARY_F target_compile_definitions (${LUABRIDGE_TESTLIB_NAME} PRIVATE LUABRIDGE_TEST_SHARED_EXPORT=1 - LUABRIDGE_ENABLE_REFLECT=1 ${LUABRIDGE_DEFINES}) target_include_directories (${LUABRIDGE_TESTLIB_NAME} PRIVATE @@ -350,16 +338,18 @@ macro (add_test_app LUABRIDGE_TEST_NAME LUA_VERSION LUABRIDGE_TEST_LUA_LIBRARY_F Source) if (${LUA_VERSION} STREQUAL "LUAU") - target_include_directories (${LUABRIDGE_TEST_NAME} PRIVATE "${LUABRIDGE_LUAU_LOCATION}/VM/include") - target_include_directories (${LUABRIDGE_TEST_NAME} PRIVATE "${LUABRIDGE_LUAU_LOCATION}/Ast/include") - target_include_directories (${LUABRIDGE_TEST_NAME} PRIVATE "${LUABRIDGE_LUAU_LOCATION}/Compiler/include") - target_include_directories (${LUABRIDGE_TEST_NAME} PRIVATE "${LUABRIDGE_LUAU_LOCATION}/Common/include") + target_include_directories (${LUABRIDGE_TEST_NAME} PRIVATE + "${LUABRIDGE_LUAU_LOCATION}/VM/include" + "${LUABRIDGE_LUAU_LOCATION}/Ast/include" + "${LUABRIDGE_LUAU_LOCATION}/Compiler/include" + "${LUABRIDGE_LUAU_LOCATION}/Common/include") target_compile_definitions (${LUABRIDGE_TEST_NAME} PRIVATE LUABRIDGE_TEST_LUAU=1) if (LUABRIDGE_BUILD_SHARED_TESTS) - target_include_directories (${LUABRIDGE_TESTLIB_NAME} PRIVATE "${LUABRIDGE_LUAU_LOCATION}/VM/include") - target_include_directories (${LUABRIDGE_TESTLIB_NAME} PRIVATE "${LUABRIDGE_LUAU_LOCATION}/Ast/include") - target_include_directories (${LUABRIDGE_TESTLIB_NAME} PRIVATE "${LUABRIDGE_LUAU_LOCATION}/Compiler/include") - target_include_directories (${LUABRIDGE_TESTLIB_NAME} PRIVATE "${LUABRIDGE_LUAU_LOCATION}/Common/include") + target_include_directories (${LUABRIDGE_TESTLIB_NAME} PRIVATE + "${LUABRIDGE_LUAU_LOCATION}/VM/include" + "${LUABRIDGE_LUAU_LOCATION}/Ast/include" + "${LUABRIDGE_LUAU_LOCATION}/Compiler/include" + "${LUABRIDGE_LUAU_LOCATION}/Common/include") target_compile_definitions (${LUABRIDGE_TESTLIB_NAME} PRIVATE LUABRIDGE_TEST_LUAU=1) endif () elseif (${LUA_VERSION} STREQUAL "LUAJIT") @@ -388,13 +378,11 @@ macro (add_test_app LUABRIDGE_TEST_NAME LUA_VERSION LUABRIDGE_TEST_LUA_LIBRARY_F set (LUABRIDGE_TEST_SHARED_LIBRARY "lib${LUABRIDGE_TESTLIB_NAME}.so") endif () target_compile_definitions (${LUABRIDGE_TEST_NAME} PRIVATE - LUABRIDGE_ENABLE_REFLECT=1 LUABRIDGE_TEST_SHARED_LIBRARY="${LUABRIDGE_TEST_SHARED_LIBRARY}" - LUABRIDGE_TEST_SHARED_EXPORT=0) + LUABRIDGE_TEST_SHARED_EXPORT=0 + ${LUABRIDGE_DEFINES}) endif () - target_compile_definitions (${LUABRIDGE_TEST_NAME} PRIVATE LUABRIDGE_ENABLE_REFLECT=1) - if (NOT ${LUABRIDGE_EXCEPTIONS}) target_compile_definitions (${LUABRIDGE_TEST_NAME} PRIVATE LUA_USE_LONGJMP=1) if (APPLE) @@ -438,43 +426,57 @@ endmacro (add_test_app) # ====================================================== Real Unit Tests -add_test_app (LuaBridgeTests51 501 "${LUABRIDGE_TEST_LUA51_FILES}" 1 "" "") +add_test_app (LuaBridgeTests51 501 "${LUABRIDGE_TEST_LUA51_FILES}" 1 "${LUABRIDGE_LUA_CPP_DEFINES}" "") +add_test_app (LuaBridgeTests51Noexcept 501 "${LUABRIDGE_TEST_LUA51_FILES}" 0 "${LUABRIDGE_LUA_CPP_DEFINES}" "") +add_test_app (LuaBridgeTests51Reflect 501 "${LUABRIDGE_TEST_LUA51_FILES}" 1 "-DLUABRIDGE_ENABLE_REFLECT=1;${LUABRIDGE_LUA_CPP_DEFINES}" "") +add_test_app (LuaBridgeTests51ReflectNoexcept 501 "${LUABRIDGE_TEST_LUA51_FILES}" 0 "-DLUABRIDGE_ENABLE_REFLECT=1;${LUABRIDGE_LUA_CPP_DEFINES}" "") add_test_app (LuaBridgeTests51LuaC 501 "${LUABRIDGE_TEST_LUA51_C_FILES}" 1 "${LUABRIDGE_LUA_C_DEFINES}" "") -add_test_app (LuaBridgeTests51Noexcept 501 "${LUABRIDGE_TEST_LUA51_FILES}" 0 "" "") add_test_app (LuaBridgeTests51LuaCNoexcept 501 "${LUABRIDGE_TEST_LUA51_C_FILES}" 0 "${LUABRIDGE_LUA_C_DEFINES}" "") -add_test_app (LuaBridgeTests52 502 "${LUABRIDGE_TEST_LUA52_FILES}" 1 "" "") +add_test_app (LuaBridgeTests52 502 "${LUABRIDGE_TEST_LUA52_FILES}" 1 "${LUABRIDGE_LUA_CPP_DEFINES}" "") +add_test_app (LuaBridgeTests52Noexcept 502 "${LUABRIDGE_TEST_LUA52_FILES}" 0 "${LUABRIDGE_LUA_CPP_DEFINES}" "") +add_test_app (LuaBridgeTests52Reflect 502 "${LUABRIDGE_TEST_LUA52_FILES}" 1 "-DLUABRIDGE_ENABLE_REFLECT=1;${LUABRIDGE_LUA_CPP_DEFINES}" "") +add_test_app (LuaBridgeTests52ReflectNoexcept 502 "${LUABRIDGE_TEST_LUA52_FILES}" 0 "-DLUABRIDGE_ENABLE_REFLECT=1;${LUABRIDGE_LUA_CPP_DEFINES}" "") add_test_app (LuaBridgeTests52LuaC 502 "${LUABRIDGE_TEST_LUA52_C_FILES}" 1 "${LUABRIDGE_LUA_C_DEFINES}" "") -add_test_app (LuaBridgeTests52Noexcept 502 "${LUABRIDGE_TEST_LUA52_FILES}" 0 "" "") add_test_app (LuaBridgeTests52LuaCNoexcept 502 "${LUABRIDGE_TEST_LUA52_C_FILES}" 0 "${LUABRIDGE_LUA_C_DEFINES}" "") -add_test_app (LuaBridgeTests53 503 "${LUABRIDGE_TEST_LUA53_FILES}" 1 "" "") +add_test_app (LuaBridgeTests53 503 "${LUABRIDGE_TEST_LUA53_FILES}" 1 "${LUABRIDGE_LUA_CPP_DEFINES}" "") +add_test_app (LuaBridgeTests53Noexcept 503 "${LUABRIDGE_TEST_LUA53_FILES}" 0 "${LUABRIDGE_LUA_CPP_DEFINES}" "") +add_test_app (LuaBridgeTests53Reflect 503 "${LUABRIDGE_TEST_LUA53_FILES}" 1 "-DLUABRIDGE_ENABLE_REFLECT=1;${LUABRIDGE_LUA_CPP_DEFINES}" "") +add_test_app (LuaBridgeTests53ReflectNoexcept 503 "${LUABRIDGE_TEST_LUA53_FILES}" 0 "-DLUABRIDGE_ENABLE_REFLECT=1;${LUABRIDGE_LUA_CPP_DEFINES}" "") add_test_app (LuaBridgeTests53LuaC 503 "${LUABRIDGE_TEST_LUA53_C_FILES}" 1 "${LUABRIDGE_LUA_C_DEFINES}" "") -add_test_app (LuaBridgeTests53Noexcept 503 "${LUABRIDGE_TEST_LUA53_FILES}" 0 "" "") add_test_app (LuaBridgeTests53LuaCNoexcept 503 "${LUABRIDGE_TEST_LUA53_C_FILES}" 0 "${LUABRIDGE_LUA_C_DEFINES}" "") -add_test_app (LuaBridgeTests54 504 "${LUABRIDGE_TEST_LUA54_FILES}" 1 "" "") +add_test_app (LuaBridgeTests54 504 "${LUABRIDGE_TEST_LUA54_FILES}" 1 "${LUABRIDGE_LUA_CPP_DEFINES}" "") +add_test_app (LuaBridgeTests54Noexcept 504 "${LUABRIDGE_TEST_LUA54_FILES}" 0 "${LUABRIDGE_LUA_CPP_DEFINES}" "") +add_test_app (LuaBridgeTests54Reflect 504 "${LUABRIDGE_TEST_LUA54_FILES}" 1 "-DLUABRIDGE_ENABLE_REFLECT=1;${LUABRIDGE_LUA_CPP_DEFINES}" "") +add_test_app (LuaBridgeTests54ReflectNoexcept 504 "${LUABRIDGE_TEST_LUA54_FILES}" 0 "-DLUABRIDGE_ENABLE_REFLECT=1;${LUABRIDGE_LUA_CPP_DEFINES}" "") add_test_app (LuaBridgeTests54LuaC 504 "${LUABRIDGE_TEST_LUA54_C_FILES}" 1 "${LUABRIDGE_LUA_C_DEFINES}" "") -add_test_app (LuaBridgeTests54Noexcept 504 "${LUABRIDGE_TEST_LUA54_FILES}" 0 "" "") add_test_app (LuaBridgeTests54LuaCNoexcept 504 "${LUABRIDGE_TEST_LUA54_C_FILES}" 0 "${LUABRIDGE_LUA_C_DEFINES}" "") -add_test_app (LuaBridgeTests55 505 "${LUABRIDGE_TEST_LUA55_FILES}" 1 "" "") +add_test_app (LuaBridgeTests55 505 "${LUABRIDGE_TEST_LUA55_FILES}" 1 "${LUABRIDGE_LUA_CPP_DEFINES}" "") +add_test_app (LuaBridgeTests55Noexcept 505 "${LUABRIDGE_TEST_LUA55_FILES}" 0 "${LUABRIDGE_LUA_CPP_DEFINES}" "") +add_test_app (LuaBridgeTests55Reflect 505 "${LUABRIDGE_TEST_LUA55_FILES}" 1 "-DLUABRIDGE_ENABLE_REFLECT=1;${LUABRIDGE_LUA_CPP_DEFINES}" "") +add_test_app (LuaBridgeTests55ReflectNoexcept 505 "${LUABRIDGE_TEST_LUA55_FILES}" 0 "-DLUABRIDGE_ENABLE_REFLECT=1;${LUABRIDGE_LUA_CPP_DEFINES}" "") add_test_app (LuaBridgeTests55LuaC 505 "${LUABRIDGE_TEST_LUA55_C_FILES}" 1 "${LUABRIDGE_LUA_C_DEFINES}" "") -add_test_app (LuaBridgeTests55Noexcept 505 "${LUABRIDGE_TEST_LUA55_FILES}" 0 "" "") add_test_app (LuaBridgeTests55LuaCNoexcept 505 "${LUABRIDGE_TEST_LUA55_C_FILES}" 0 "${LUABRIDGE_LUA_C_DEFINES}" "") -if (NOT EMSCRIPTEN) - add_test_app (LuaBridgeTestsLuaJIT "LUAJIT" "${LUABRIDGE_TEST_LUAJIT_FILES}" 1 "" "liblua-static") - add_test_app (LuaBridgeTestsLuaJITNoexcept "LUAJIT" "${LUABRIDGE_TEST_LUAJIT_FILES}" 0 "" "liblua-static") -endif () - -add_test_app (LuaBridgeTestsLuau "LUAU" "${LUABRIDGE_TEST_LUAU_FILES}" 1 "" "") -#add_test_app (LuaBridgeTestsLuauNoexcept "LUAU" "${LUABRIDGE_TEST_LUAU_FILES}" 0 "" "") +add_test_app (LuaBridgeTestsLuau "LUAU" "${LUABRIDGE_TEST_LUAU_FILES}" 1 "${LUABRIDGE_LUA_CPP_DEFINES}" "") +#add_test_app (LuaBridgeTestsLuauNoexcept "LUAU" "${LUABRIDGE_TEST_LUAU_FILES}" 0 "${LUABRIDGE_LUA_CPP_DEFINES}" "") +add_test_app (LuaBridgeTestsLuauReflect "LUAU" "${LUABRIDGE_TEST_LUAU_FILES}" 1 "-DLUABRIDGE_ENABLE_REFLECT=1;${LUABRIDGE_LUA_CPP_DEFINES}" "") +#add_test_app (LuaBridgeTestsLuauReflectNoexcept "LUAU" "${LUABRIDGE_TEST_LUAU_FILES}" 0 "-DLUABRIDGE_ENABLE_REFLECT=1;${LUABRIDGE_LUA_CPP_DEFINES}" "") if (NOT EMSCRIPTEN) + add_test_app (LuaBridgeTestsLuaJIT "LUAJIT" "${LUABRIDGE_TEST_LUAJIT_FILES}" 1 "${LUABRIDGE_LUA_CPP_DEFINES}" "liblua-static") + add_test_app (LuaBridgeTestsLuaJITNoexcept "LUAJIT" "${LUABRIDGE_TEST_LUAJIT_FILES}" 0 "${LUABRIDGE_LUA_CPP_DEFINES}" "liblua-static") + add_test_app (LuaBridgeTestsLuaJITReflect "LUAJIT" "${LUABRIDGE_TEST_LUAJIT_FILES}" 1 "-DLUABRIDGE_ENABLE_REFLECT=1;${LUABRIDGE_LUA_CPP_DEFINES}" "liblua-static") + add_test_app (LuaBridgeTestsLuaJITReflectNoexcept "LUAJIT" "${LUABRIDGE_TEST_LUAJIT_FILES}" 0 "-DLUABRIDGE_ENABLE_REFLECT=1;${LUABRIDGE_LUA_CPP_DEFINES}" "liblua-static") + add_test_app (LuaBridgeTestsRavi "RAVI" "${LUABRIDGE_TEST_RAVI_FILES}" 1 "${LUABRIDGE_LUA_C_DEFINES}" "libravi") + #add_test_app (LuaBridgeTestsRaviNoexcept "RAVI" "${LUABRIDGE_TEST_RAVI_FILES}" 0 "${LUABRIDGE_LUA_C_DEFINES}" "libravi") + add_test_app (LuaBridgeTestsRaviReflect "RAVI" "${LUABRIDGE_TEST_RAVI_FILES}" 1 "-DLUABRIDGE_ENABLE_REFLECT=1;${LUABRIDGE_LUA_C_DEFINES}" "libravi") + #add_test_app (LuaBridgeTestsRaviReflectNoexcept "RAVI" "${LUABRIDGE_TEST_RAVI_FILES}" 0 "-DLUABRIDGE_ENABLE_REFLECT=1;${LUABRIDGE_LUA_C_DEFINES}" "libravi") endif () -#add_test_app (LuaBridgeTestsRaviNoexcept "RAVI" "${LUABRIDGE_TEST_RAVI_FILES}" 0 "" "libravi") if (LUABRIDGE_COVERAGE) setup_coverage_single_target () diff --git a/Tests/Source/InspectTests.cpp b/Tests/Source/InspectTests.cpp index 7d4ac746..54f22674 100644 --- a/Tests/Source/InspectTests.cpp +++ b/Tests/Source/InspectTests.cpp @@ -5,6 +5,7 @@ #include "TestBase.h" #include "LuaBridge/LuaBridge.h" +#include "LuaBridge/Inspect.h" #include #include @@ -333,7 +334,7 @@ TEST_F(InspectTests, LuaTableVisitorProducesTable) lua_getfield(L, -1, "classes"); EXPECT_TRUE(lua_istable(L, -1)); - EXPECT_EQ(1, (int)lua_rawlen(L, -1)); + EXPECT_EQ(1, luabridge::get_length(L, -1)); lua_pop(L, 1); lua_pop(L, 1); // pop result table @@ -467,12 +468,10 @@ TEST_F(InspectTests, WithHintsIsAcceptedByAddFunction) struct Actor { void attack(float /*damage*/) {} }; // This test verifies that withHints compiles and the function is registered correctly. - EXPECT_NO_THROW( - luabridge::getGlobalNamespace(L) - .beginClass("Actor") - .addFunction("attack", luabridge::withHints(&Actor::attack, "damage")) - .endClass() - ); + luabridge::getGlobalNamespace(L) + .beginClass("Actor") + .addFunction("attack", luabridge::withHints(&Actor::attack, "damage")) + .endClass(); // Verify the function is callable from Lua auto info = luabridge::inspect(L); @@ -485,14 +484,12 @@ TEST_F(InspectTests, WithHintsOverloadsCompile) { struct Shooter {}; - EXPECT_NO_THROW( - luabridge::getGlobalNamespace(L) - .beginClass("Shooter") - .addFunction("fire", - luabridge::withHints([](Shooter&, int /*count*/) {}, "count"), - luabridge::withHints([](Shooter&, float /*angle*/, int /*count*/) {}, "angle", "count")) - .endClass() - ); + luabridge::getGlobalNamespace(L) + .beginClass("Shooter") + .addFunction("fire", + luabridge::withHints([](Shooter&, int /*count*/) {}, "count"), + luabridge::withHints([](Shooter&, float /*angle*/, int /*count*/) {}, "angle", "count")) + .endClass(); auto info = luabridge::inspect(L); auto* m = findMember(info, "fire"); @@ -500,8 +497,7 @@ TEST_F(InspectTests, WithHintsOverloadsCompile) EXPECT_EQ(2u, m->overloads.size()); } -#if defined(LUABRIDGE_ENABLE_REFLECT) - +#if LUABRIDGE_ENABLE_REFLECT TEST_F(InspectTests, ReflectTypeInfoPopulated) { struct Enemy { void takeDamage(float /*amount*/, int /*count*/) {} }; @@ -530,7 +526,7 @@ TEST_F(InspectTests, ReflectTypeInfoPopulated) TEST_F(InspectTests, ReflectConstructorTypeInfo) { - struct Widget {}; + struct Widget { int x; float y; }; luabridge::getGlobalNamespace(L) .beginClass("Widget") @@ -561,5 +557,4 @@ TEST_F(InspectTests, ReflectFreeFunction) EXPECT_EQ("a", fn->overloads[0].params[0].hint); EXPECT_EQ("b", fn->overloads[0].params[1].hint); } - #endif // LUABRIDGE_ENABLE_REFLECT From 0c1de9d7adcc7db25541cf78ae926acd822eff19 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Tue, 7 Apr 2026 17:50:26 +0200 Subject: [PATCH 03/14] Move tools and expand to more test runs --- .github/workflows/amalgamate.yml | 6 +++--- .github/workflows/build_linux.yml | 22 +++++++++++++++++----- .github/workflows/build_macos.yml | 22 +++++++++++++++++----- .github/workflows/build_windows.yml | 18 ++++++++++++++---- .github/workflows/coverage.yml | 24 ++++++++++++++++++------ amalgamate.py => Tools/amalgamate.py | 0 cobertura.py => Tools/cobertura.py | 0 justfile | 3 +-- 8 files changed, 70 insertions(+), 25 deletions(-) rename amalgamate.py => Tools/amalgamate.py (100%) rename cobertura.py => Tools/cobertura.py (100%) diff --git a/.github/workflows/amalgamate.yml b/.github/workflows/amalgamate.yml index 8b87bd95..0043c811 100644 --- a/.github/workflows/amalgamate.yml +++ b/.github/workflows/amalgamate.yml @@ -6,8 +6,8 @@ on: - master paths: - "**/workflows/amalgamate.yml" - - "**/Source/**" - - "**/amalgamate.py" + - "**/Source/**/*.h" + - "**/Tools/amalgamate.py" jobs: run: @@ -23,7 +23,7 @@ jobs: python-version: 3.9 - name: Create amalgamation file - run: python amalgamate.py + run: python Tools/amalgamate.py - name: Commit changes uses: EndBug/add-and-commit@v10 diff --git a/.github/workflows/build_linux.yml b/.github/workflows/build_linux.yml index 75b2aeec..ac051712 100644 --- a/.github/workflows/build_linux.yml +++ b/.github/workflows/build_linux.yml @@ -94,13 +94,17 @@ jobs: run: | cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ LuaBridgeTestsLuaJIT \ - LuaBridgeTestsLuaJITNoexcept + LuaBridgeTestsLuaJITReflect \ + LuaBridgeTestsLuaJITNoexcept \ + LuaBridgeTestsLuaJITReflectNoexcept - name: Test LuaJIT working-directory: ${{runner.workspace}}/build/Tests run: | ./LuaBridgeTestsLuaJIT + ./LuaBridgeTestsLuaJITReflect ./LuaBridgeTestsLuaJITNoexcept + ./LuaBridgeTestsLuaJITReflectNoexcept luau: runs-on: ubuntu-latest @@ -122,11 +126,15 @@ jobs: - name: Build Luau working-directory: ${{runner.workspace}}/build - run: cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target LuaBridgeTestsLuau + run: cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ + LuaBridgeTestsLuau \ + LuaBridgeTestsLuauReflect - name: Test Luau working-directory: ${{runner.workspace}}/build/Tests - run: ./LuaBridgeTestsLuau + run: | + ./LuaBridgeTestsLuau + ./LuaBridgeTestsLuauReflect ravi: runs-on: ubuntu-latest @@ -148,8 +156,12 @@ jobs: - name: Build Ravi working-directory: ${{runner.workspace}}/build - run: cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target LuaBridgeTestsRavi + run: cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ + LuaBridgeTestsRavi \ + LuaBridgeTestsRaviReflect - name: Test Ravi working-directory: ${{runner.workspace}}/build/Tests - run: ./LuaBridgeTestsRavi + run: | + ./LuaBridgeTestsRavi + ./LuaBridgeTestsRaviReflect diff --git a/.github/workflows/build_macos.yml b/.github/workflows/build_macos.yml index 9d0962f4..50cd8ce7 100644 --- a/.github/workflows/build_macos.yml +++ b/.github/workflows/build_macos.yml @@ -86,13 +86,17 @@ jobs: run: | cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ LuaBridgeTestsLuaJIT \ - LuaBridgeTestsLuaJITNoexcept + LuaBridgeTestsLuaJITReflect \ + LuaBridgeTestsLuaJITNoexcept \ + LuaBridgeTestsLuaJITReflectNoexcept - name: Test LuaJIT working-directory: ${{runner.workspace}}/build/Tests run: | ./LuaBridgeTestsLuaJIT + ./LuaBridgeTestsLuaJITReflect ./LuaBridgeTestsLuaJITNoexcept + ./LuaBridgeTestsLuaJITReflectNoexcept luau: runs-on: macos-latest @@ -111,11 +115,15 @@ jobs: - name: Build Luau working-directory: ${{runner.workspace}}/build - run: cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target LuaBridgeTestsLuau + run: cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ + LuaBridgeTestsLuau \ + LuaBridgeTestsLuauReflect - name: Test Luau working-directory: ${{runner.workspace}}/build/Tests - run: ./LuaBridgeTestsLuau + run: | + ./LuaBridgeTestsLuau + ./LuaBridgeTestsLuauReflect ravi: runs-on: macos-latest @@ -134,8 +142,12 @@ jobs: - name: Build Ravi working-directory: ${{runner.workspace}}/build - run: cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target LuaBridgeTestsRavi + run: cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ + LuaBridgeTestsRavi \ + LuaBridgeTestsRaviReflect - name: Test Ravi working-directory: ${{runner.workspace}}/build/Tests - run: ./LuaBridgeTestsRavi + run: | + ./LuaBridgeTestsRavi + ./LuaBridgeTestsRaviReflect diff --git a/.github/workflows/build_windows.yml b/.github/workflows/build_windows.yml index f7f3762e..3670bc0a 100644 --- a/.github/workflows/build_windows.yml +++ b/.github/workflows/build_windows.yml @@ -91,14 +91,18 @@ jobs: run: | cmake --build . --config $BUILD_TYPE --parallel 4 --target \ LuaBridgeTestsLuaJIT \ - LuaBridgeTestsLuaJITNoexcept + LuaBridgeTestsLuaJITReflect \ + LuaBridgeTestsLuaJITNoexcept \ + LuaBridgeTestsLuaJITReflectNoexcept - name: Test LuaJIT working-directory: ${{runner.workspace}}/build/Tests/Release shell: bash run: | ./LuaBridgeTestsLuaJIT.exe + ./LuaBridgeTestsLuaJITReflect.exe ./LuaBridgeTestsLuaJITNoexcept.exe + ./LuaBridgeTestsLuaJITReflectNoexcept.exe luau: runs-on: windows-latest @@ -119,12 +123,16 @@ jobs: - name: Build Luau working-directory: ${{runner.workspace}}/build shell: bash - run: cmake --build . --config $BUILD_TYPE --parallel 4 --target LuaBridgeTestsLuau + run: cmake --build . --config $BUILD_TYPE --parallel 4 --target \ + LuaBridgeTestsLuau \ + LuaBridgeTestsLuauReflect - name: Test Luau working-directory: ${{runner.workspace}}/build/Tests/Release shell: bash - run: ./LuaBridgeTestsLuau.exe + run: | + ./LuaBridgeTestsLuau.exe + ./LuaBridgeTestsLuauReflect.exe ravi: runs-on: windows-latest @@ -145,7 +153,9 @@ jobs: - name: Build Ravi working-directory: ${{runner.workspace}}/build shell: bash - run: cmake --build . --config $BUILD_TYPE --parallel 4 --target LuaBridgeTestsRavi + run: cmake --build . --config $BUILD_TYPE --parallel 4 --target \ + LuaBridgeTestsRavi \ + LuaBridgeTestsRaviReflect - name: Test Ravi working-directory: ${{runner.workspace}}/build/Tests/Release diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 273a60fc..5de1d5dc 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -110,13 +110,17 @@ jobs: run: | cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ LuaBridgeTestsLuaJIT \ - LuaBridgeTestsLuaJITNoexcept + LuaBridgeTestsLuaJITReflect \ + LuaBridgeTestsLuaJITNoexcept \ + LuaBridgeTestsLuaJITReflectNoexcept - name: Test LuaJIT working-directory: ${{runner.workspace}}/build/Tests run: | ./LuaBridgeTestsLuaJIT + ./LuaBridgeTestsLuaJITReflect ./LuaBridgeTestsLuaJITNoexcept + ./LuaBridgeTestsLuaJITReflectNoexcept - name: Coverage LuaJIT working-directory: ${{runner.workspace}}/build @@ -154,11 +158,15 @@ jobs: - name: Build Luau working-directory: ${{runner.workspace}}/build - run: cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target LuaBridgeTestsLuau + run: cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ + LuaBridgeTestsLuau \ + LuaBridgeTestsLuauReflect - name: Test Luau working-directory: ${{runner.workspace}}/build/Tests - run: ./LuaBridgeTestsLuau + run: | + ./LuaBridgeTestsLuau + ./LuaBridgeTestsLuauReflect - name: Coverage Luau working-directory: ${{runner.workspace}}/build @@ -196,11 +204,15 @@ jobs: - name: Build Ravi working-directory: ${{runner.workspace}}/build - run: cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target LuaBridgeTestsRavi + run: cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ + LuaBridgeTestsRavi \ + LuaBridgeTestsRaviReflect - name: Test Ravi working-directory: ${{runner.workspace}}/build/Tests - run: LD_PRELOAD=$(gcc -print-file-name=libasan.so) ./LuaBridgeTestsRavi + run: | + ./LuaBridgeTestsRavi + ./LuaBridgeTestsRaviReflect - name: Coverage Ravi working-directory: ${{runner.workspace}}/build @@ -303,7 +315,7 @@ jobs: #- name: Convert to Coverage TXT # working-directory: ${{runner.workspace}}/build - # run: python3 ${{runner.workspace}}/cobertura.py coverage/cobertura.info coverage/coverage.txt + # run: python3 ${{runner.workspace}}/Tools/cobertura.py coverage/cobertura.info coverage/coverage.txt - name: Upload Cobertura XML uses: actions/upload-artifact@v4 diff --git a/amalgamate.py b/Tools/amalgamate.py similarity index 100% rename from amalgamate.py rename to Tools/amalgamate.py diff --git a/cobertura.py b/Tools/cobertura.py similarity index 100% rename from cobertura.py rename to Tools/cobertura.py diff --git a/justfile b/justfile index ae502c91..67f15fae 100644 --- a/justfile +++ b/justfile @@ -26,5 +26,4 @@ clean: rm -rf Build/ amalgamate: - uv run amalgamate.py - + uv run Tools/amalgamate.py From 7e9ddf096128e606d42bb6b9ffbbd84af840eea8 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Tue, 7 Apr 2026 17:54:15 +0200 Subject: [PATCH 04/14] Fix github actions --- .github/workflows/build_linux.yml | 14 ++++++++------ .github/workflows/build_macos.yml | 14 ++++++++------ .github/workflows/build_windows.yml | 14 ++++++++------ .github/workflows/coverage.yml | 14 ++++++++------ 4 files changed, 32 insertions(+), 24 deletions(-) diff --git a/.github/workflows/build_linux.yml b/.github/workflows/build_linux.yml index ac051712..8e96d6dd 100644 --- a/.github/workflows/build_linux.yml +++ b/.github/workflows/build_linux.yml @@ -126,9 +126,10 @@ jobs: - name: Build Luau working-directory: ${{runner.workspace}}/build - run: cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ - LuaBridgeTestsLuau \ - LuaBridgeTestsLuauReflect + run: | + cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ + LuaBridgeTestsLuau \ + LuaBridgeTestsLuauReflect - name: Test Luau working-directory: ${{runner.workspace}}/build/Tests @@ -156,9 +157,10 @@ jobs: - name: Build Ravi working-directory: ${{runner.workspace}}/build - run: cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ - LuaBridgeTestsRavi \ - LuaBridgeTestsRaviReflect + run: | + cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ + LuaBridgeTestsRavi \ + LuaBridgeTestsRaviReflect - name: Test Ravi working-directory: ${{runner.workspace}}/build/Tests diff --git a/.github/workflows/build_macos.yml b/.github/workflows/build_macos.yml index 50cd8ce7..aab1980b 100644 --- a/.github/workflows/build_macos.yml +++ b/.github/workflows/build_macos.yml @@ -115,9 +115,10 @@ jobs: - name: Build Luau working-directory: ${{runner.workspace}}/build - run: cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ - LuaBridgeTestsLuau \ - LuaBridgeTestsLuauReflect + run: | + cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ + LuaBridgeTestsLuau \ + LuaBridgeTestsLuauReflect - name: Test Luau working-directory: ${{runner.workspace}}/build/Tests @@ -142,9 +143,10 @@ jobs: - name: Build Ravi working-directory: ${{runner.workspace}}/build - run: cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ - LuaBridgeTestsRavi \ - LuaBridgeTestsRaviReflect + run: | + cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ + LuaBridgeTestsRavi \ + LuaBridgeTestsRaviReflect - name: Test Ravi working-directory: ${{runner.workspace}}/build/Tests diff --git a/.github/workflows/build_windows.yml b/.github/workflows/build_windows.yml index 3670bc0a..55987944 100644 --- a/.github/workflows/build_windows.yml +++ b/.github/workflows/build_windows.yml @@ -123,9 +123,10 @@ jobs: - name: Build Luau working-directory: ${{runner.workspace}}/build shell: bash - run: cmake --build . --config $BUILD_TYPE --parallel 4 --target \ - LuaBridgeTestsLuau \ - LuaBridgeTestsLuauReflect + run: | + cmake --build . --config $BUILD_TYPE --parallel 4 --target \ + LuaBridgeTestsLuau \ + LuaBridgeTestsLuauReflect - name: Test Luau working-directory: ${{runner.workspace}}/build/Tests/Release @@ -153,9 +154,10 @@ jobs: - name: Build Ravi working-directory: ${{runner.workspace}}/build shell: bash - run: cmake --build . --config $BUILD_TYPE --parallel 4 --target \ - LuaBridgeTestsRavi \ - LuaBridgeTestsRaviReflect + run: | + cmake --build . --config $BUILD_TYPE --parallel 4 --target \ + LuaBridgeTestsRavi \ + LuaBridgeTestsRaviReflect - name: Test Ravi working-directory: ${{runner.workspace}}/build/Tests/Release diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 5de1d5dc..c47de62b 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -158,9 +158,10 @@ jobs: - name: Build Luau working-directory: ${{runner.workspace}}/build - run: cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ - LuaBridgeTestsLuau \ - LuaBridgeTestsLuauReflect + run: | + cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ + LuaBridgeTestsLuau \ + LuaBridgeTestsLuauReflect - name: Test Luau working-directory: ${{runner.workspace}}/build/Tests @@ -204,9 +205,10 @@ jobs: - name: Build Ravi working-directory: ${{runner.workspace}}/build - run: cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ - LuaBridgeTestsRavi \ - LuaBridgeTestsRaviReflect + run: | + cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ + LuaBridgeTestsRavi \ + LuaBridgeTestsRaviReflect - name: Test Ravi working-directory: ${{runner.workspace}}/build/Tests From c47b829116849b046fca155a80801a35d360f713 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Tue, 7 Apr 2026 17:59:08 +0200 Subject: [PATCH 05/14] Fix warnings --- .github/workflows/coverage.yml | 4 ++-- Tests/Lua/Luau.cpp | 6 ++++++ Tests/Lua/LuauSplit.cpp | 6 ++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index c47de62b..44acd1a2 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -213,8 +213,8 @@ jobs: - name: Test Ravi working-directory: ${{runner.workspace}}/build/Tests run: | - ./LuaBridgeTestsRavi - ./LuaBridgeTestsRaviReflect + LD_PRELOAD=$(gcc -print-file-name=libasan.so) ./LuaBridgeTestsRavi + LD_PRELOAD=$(gcc -print-file-name=libasan.so) ./LuaBridgeTestsRaviReflect - name: Coverage Ravi working-directory: ${{runner.workspace}}/build diff --git a/Tests/Lua/Luau.cpp b/Tests/Lua/Luau.cpp index 297cde0e..bc5f5580 100644 --- a/Tests/Lua/Luau.cpp +++ b/Tests/Lua/Luau.cpp @@ -36,6 +36,10 @@ #ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS #endif +#elif __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpragmas" +#pragma GCC diagnostic ignored "-Wunused-variable" #endif // Ast @@ -83,6 +87,8 @@ #if _MSC_VER #pragma pop_macro("_CRT_SECURE_NO_WARNINGS") +#elif __GNUC__ +#pragma GCC diagnostic pop #endif #else diff --git a/Tests/Lua/LuauSplit.cpp b/Tests/Lua/LuauSplit.cpp index 4fec0a70..6f7155a8 100644 --- a/Tests/Lua/LuauSplit.cpp +++ b/Tests/Lua/LuauSplit.cpp @@ -36,6 +36,10 @@ #ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS #endif +#elif __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpragmas" +#pragma GCC diagnostic ignored "-Wunused-variable" #endif // Compiler @@ -63,6 +67,8 @@ #if _MSC_VER #pragma pop_macro("_CRT_SECURE_NO_WARNINGS") +#elif __GNUC__ +#pragma GCC diagnostic pop #endif #else From 586f7bcc8c5e754bd0887d6bc0c417c6ad9bedcb Mon Sep 17 00:00:00 2001 From: kunitoki Date: Tue, 7 Apr 2026 19:38:56 +0200 Subject: [PATCH 06/14] More cleanups --- Source/LuaBridge/Inspect.h | 309 +++++++++++++-------------- Source/LuaBridge/detail/CFunctions.h | 32 +++ Source/LuaBridge/detail/ClassInfo.h | 11 + Source/LuaBridge/detail/FuncTraits.h | 30 +++ Source/LuaBridge/detail/Namespace.h | 18 ++ Tests/Source/InspectTests.cpp | 37 ++-- 6 files changed, 250 insertions(+), 187 deletions(-) diff --git a/Source/LuaBridge/Inspect.h b/Source/LuaBridge/Inspect.h index 1dc75ac1..4e0e7613 100644 --- a/Source/LuaBridge/Inspect.h +++ b/Source/LuaBridge/Inspect.h @@ -9,7 +9,9 @@ #include "detail/LuaHelpers.h" #include +#include #include +#include #include #include #include @@ -335,13 +337,33 @@ inline ClassInspectInfo inspectClassFromStaticTable(lua_State* L, int stIdx) } lua_pop(L, 1); + // Collect instance property type names (may be absent if not registered with type info) + std::map instPropTypes; + lua_rawgetp_x(L, clIdx, getPropTypeKey()); + if (lua_istable(L, -1)) + { + int ptIdx = lua_absindex(L, -1); + for (const auto& k : instPropget) + { + lua_getfield(L, ptIdx, k.c_str()); + if (lua_isstring(L, -1)) + instPropTypes[k] = lua_tostring(L, -1); + lua_pop(L, 1); + } + } + lua_pop(L, 1); + // 4. Emit instance properties for (const auto& propName : instPropget) { MemberInfo m; m.name = propName; m.kind = instPropsetReal.count(propName) ? MemberKind::Property : MemberKind::ReadOnlyProperty; - m.overloads.push_back(OverloadInfo{}); + OverloadInfo ov; + auto it = instPropTypes.find(propName); + if (it != instPropTypes.end()) + ov.returnType = it->second; + m.overloads.push_back(std::move(ov)); cls.members.push_back(std::move(m)); } @@ -398,13 +420,33 @@ inline ClassInspectInfo inspectClassFromStaticTable(lua_State* L, int stIdx) } lua_pop(L, 1); + // Collect static property type names + std::map stPropTypes; + lua_rawgetp_x(L, mtIdx, getPropTypeKey()); + if (lua_istable(L, -1)) + { + int ptIdx = lua_absindex(L, -1); + for (const auto& k : stPropget) + { + lua_getfield(L, ptIdx, k.c_str()); + if (lua_isstring(L, -1)) + stPropTypes[k] = lua_tostring(L, -1); + lua_pop(L, 1); + } + } + lua_pop(L, 1); + // 7. Emit static properties for (const auto& propName : stPropget) { MemberInfo m; m.name = propName; m.kind = stPropsetReal.count(propName) ? MemberKind::StaticProperty : MemberKind::StaticReadOnlyProperty; - m.overloads.push_back(OverloadInfo{}); + OverloadInfo ov; + auto it = stPropTypes.find(propName); + if (it != stPropTypes.end()) + ov.returnType = it->second; + m.overloads.push_back(std::move(ov)); cls.members.push_back(std::move(m)); } @@ -467,6 +509,22 @@ inline NamespaceInspectInfo inspectNamespaceTable(lua_State* L, int nsIdx, std:: nsPropsetTableIdx = lua_absindex(L, -1); // leave propset table on stack; we pop it after the loop + // Collect namespace property type names + std::map nsPropTypes; + lua_rawgetp_x(L, nsIdx, getPropTypeKey()); + if (lua_istable(L, -1)) + { + int ptIdx = lua_absindex(L, -1); + for (const auto& k : nsPropget) + { + lua_getfield(L, ptIdx, k.c_str()); + if (lua_isstring(L, -1)) + nsPropTypes[k] = lua_tostring(L, -1); + lua_pop(L, 1); + } + } + lua_pop(L, 1); // pop proptype table or nil + for (const auto& propName : nsPropget) { MemberInfo m; @@ -487,7 +545,11 @@ inline NamespaceInspectInfo inspectNamespaceTable(lua_State* L, int nsIdx, std:: } m.kind = isReadOnly ? MemberKind::ReadOnlyProperty : MemberKind::Property; - m.overloads.push_back(OverloadInfo{}); + OverloadInfo ov; + auto it = nsPropTypes.find(propName); + if (it != nsPropTypes.end()) + ov.returnType = it->second; + m.overloads.push_back(std::move(ov)); info.freeMembers.push_back(std::move(m)); } @@ -850,7 +912,7 @@ class LuaLSVisitor : public InspectVisitor for (const auto& ov : m.overloads) { - emitParams(ov.params); + emitParamsTo(out_, ov.params); if (!ov.returnType.empty() && ov.returnType != "void") out_ << "---@return " << luaType(ov.returnType) << "\n"; } @@ -873,96 +935,101 @@ class LuaLSVisitor : public InspectVisitor out_ << ", " << cls.baseClasses[i]; } out_ << "\n"; - - // Emit fields for properties in the class body annotation - // (will be added in visitMember below, but need the class annotation open) + methodBuf_.str({}); + methodBuf_.clear(); } void endClass([[maybe_unused]] const ClassInspectInfo& cls) override { - out_ << "local " << curClass_ << " = {}\n\n"; + out_ << "local " << curClass_ << " = {}\n"; + const std::string methods = methodBuf_.str(); + if (!methods.empty()) + out_ << "\n" << methods; + else + out_ << "\n"; curClass_.clear(); } void visitMember(const ClassInspectInfo& cls, const MemberInfo& m) override { + auto propType = [&]() -> std::string { + if (!m.overloads.empty() && !m.overloads[0].returnType.empty()) + return luaType(m.overloads[0].returnType); + return "any"; + }; + switch (m.kind) { case MemberKind::Property: - out_ << "---@field " << m.name << " any\n"; + out_ << "---@field " << m.name << " " << propType() << "\n"; break; + case MemberKind::ReadOnlyProperty: - out_ << "---@field " << m.name << " any # readonly\n"; + out_ << "---@field " << m.name << " " << propType() << " # readonly\n"; break; + case MemberKind::StaticProperty: - out_ << "---@field " << m.name << " any\n"; + out_ << "---@field " << m.name << " " << propType() << "\n"; break; + case MemberKind::StaticReadOnlyProperty: - out_ << "---@field " << m.name << " any # readonly\n"; + out_ << "---@field " << m.name << " " << propType() << " # readonly\n"; break; + case MemberKind::Constructor: { - out_ << "\n"; - for (std::size_t i = 0; i < m.overloads.size(); ++i) - { - const auto& ov = m.overloads[i]; - if (i == 0) - { - emitParams(ov.params); - out_ << "---@return " << cls.name << "\n"; - out_ << "function " << cls.name << ".new(" << paramNames(ov.params) << ") end\n"; - } - else - { - out_ << "---@overload fun(" << overloadParams(ov.params) << "): " << cls.name << "\n"; - } - } + // Represent the constructor as @overload annotations before the local declaration. + // LuaBridge constructors are called as ClassName(args), not ClassName.new(args). + std::string qname = qualifiedName(cls.name); + for (const auto& ov : m.overloads) + out_ << "---@overload fun(" << overloadParams(ov.params) << "): " << qname << "\n"; break; } + case MemberKind::Method: { - out_ << "\n"; + std::string qname = qualifiedName(cls.name); for (std::size_t i = 0; i < m.overloads.size(); ++i) { const auto& ov = m.overloads[i]; if (i == 0) { - emitParams(ov.params, /*hasSelf=*/true); + emitParamsTo(methodBuf_, ov.params, /*hasSelf=*/true, qname); if (!ov.returnType.empty() && ov.returnType != "void") - out_ << "---@return " << luaType(ov.returnType) << "\n"; - out_ << "function " << cls.name << ":" << m.name - << "(" << paramNames(ov.params) << ") end\n"; + methodBuf_ << "---@return " << luaType(ov.returnType) << "\n"; + methodBuf_ << "function " << qname << ":" << m.name << "(" << paramNames(ov.params) << ") end\n\n"; } else { - out_ << "---@overload fun(self: " << cls.name << ", " << overloadParams(ov.params) << ")" - << (ov.returnType.empty() ? "" : (": " + luaType(ov.returnType))) << "\n"; + methodBuf_ << "---@overload fun(self: " << qname << ", " << overloadParams(ov.params) << ")" + << (ov.returnType.empty() ? "" : (": " + luaType(ov.returnType))) << "\n"; } } break; } + case MemberKind::StaticMethod: { - out_ << "\n"; + std::string qname = qualifiedName(cls.name); for (std::size_t i = 0; i < m.overloads.size(); ++i) { const auto& ov = m.overloads[i]; if (i == 0) { - emitParams(ov.params); + emitParamsTo(methodBuf_, ov.params); if (!ov.returnType.empty() && ov.returnType != "void") - out_ << "---@return " << luaType(ov.returnType) << "\n"; - out_ << "function " << cls.name << "." << m.name - << "(" << paramNames(ov.params) << ") end\n"; + methodBuf_ << "---@return " << luaType(ov.returnType) << "\n"; + methodBuf_ << "function " << qname << "." << m.name << "(" << paramNames(ov.params) << ") end\n\n"; } else { - out_ << "---@overload fun(" << overloadParams(ov.params) << ")" - << (ov.returnType.empty() ? "" : (": " + luaType(ov.returnType))) << "\n"; + methodBuf_ << "---@overload fun(" << overloadParams(ov.params) << ")" + << (ov.returnType.empty() ? "" : (": " + luaType(ov.returnType))) << "\n"; } } break; } + default: break; } @@ -973,15 +1040,20 @@ class LuaLSVisitor : public InspectVisitor static std::string luaType(const std::string& cppType) { if (cppType == "void") return "nil"; + if (cppType == "bool") return "boolean"; + if (cppType == "int" || cppType == "long" || cppType == "short" || cppType == "unsigned int" || cppType == "unsigned long" || cppType == "unsigned short" || cppType == "int64_t" || cppType == "uint64_t" || cppType == "int32_t" || cppType == "uint32_t" || cppType == "size_t") return "integer"; + if (cppType == "float" || cppType == "double") return "number"; + if (cppType == "std::string" || cppType == "const char *" || cppType == "const char*") return "string"; + return cppType.empty() ? "any" : cppType; } @@ -1007,6 +1079,7 @@ class LuaLSVisitor : public InspectVisitor for (std::size_t i = 0; i < params.size(); ++i) { if (i) s += ", "; + const auto& p = params[i]; std::string pname = p.hint.empty() ? ("p" + std::to_string(i + 1)) : p.hint; s += pname + ": " + luaType(p.typeName.empty() ? "any" : p.typeName); @@ -1014,20 +1087,23 @@ class LuaLSVisitor : public InspectVisitor return s; } - void emitParams(const std::vector& params, bool hasSelf = false) const + static void emitParamsTo(std::ostream& os, const std::vector& params, + bool hasSelf = false, const std::string& selfType = {}) { if (hasSelf) - out_ << "---@param self " << curClass_ << "\n"; + os << "---@param self " << selfType << "\n"; + for (std::size_t i = 0; i < params.size(); ++i) { const auto& p = params[i]; std::string pname = p.hint.empty() ? ("p" + std::to_string(i + 1)) : p.hint; std::string ptype = p.typeName.empty() ? "any" : luaType(p.typeName); - out_ << "---@param " << pname << " " << ptype << "\n"; + os << "---@param " << pname << " " << ptype << "\n"; } } std::ostream& out_; + std::ostringstream methodBuf_; std::string ns_; std::string curClass_; }; @@ -1049,14 +1125,16 @@ class LuaProxyVisitor : public InspectVisitor void beginNamespace(const NamespaceInspectInfo& ns) override { - if (ns.name != "_G") - out_ << "local " << ns.name << " = {}\n\n"; + curNs_ = (ns.name == "_G") ? "" : ns.name; + if (!curNs_.empty()) + out_ << "local " << curNs_ << " = {}\n\n"; } void endNamespace(const NamespaceInspectInfo& ns) override { if (ns.name != "_G") out_ << "return " << ns.name << "\n"; + curNs_.clear(); } void visitFreeMember(const NamespaceInspectInfo& ns, const MemberInfo& m) override @@ -1074,8 +1152,9 @@ class LuaProxyVisitor : public InspectVisitor void beginClass(const ClassInspectInfo& cls) override { curClass_ = cls.name; - out_ << cls.name << " = {}\n"; - out_ << cls.name << ".__index = " << cls.name << "\n\n"; + std::string qname = curNs_.empty() ? cls.name : (curNs_ + "." + cls.name); + out_ << qname << " = {}\n"; + out_ << qname << ".__index = " << qname << "\n\n"; } void endClass([[maybe_unused]] const ClassInspectInfo& cls) override @@ -1089,28 +1168,35 @@ class LuaProxyVisitor : public InspectVisitor if (m.overloads.empty()) return; + std::string qname = curNs_.empty() ? curClass_ : (curNs_ + "." + curClass_); + switch (m.kind) { case MemberKind::Constructor: - out_ << "function " << curClass_ << ".new(" - << paramNames(m.overloads[0].params) << ")\n" - << " return setmetatable({}, " << curClass_ << ")\n" - << "end\n\n"; + // LuaBridge constructors are called as ClassName(args) via __call metamethod. + out_ << "setmetatable(" << qname << ", {__call = function(t"; + if (!m.overloads[0].params.empty()) + out_ << ", " << paramNames(m.overloads[0].params); + out_ << ")\n return setmetatable({}, t)\nend})\n\n"; break; + case MemberKind::Method: - out_ << "function " << curClass_ << ":" << m.name - << "(" << paramNames(m.overloads[0].params) << ") end\n"; + out_ << "function " << qname << ":" << m.name + << "(" << paramNames(m.overloads[0].params) << ") end\n\n"; break; + case MemberKind::StaticMethod: - out_ << "function " << curClass_ << "." << m.name - << "(" << paramNames(m.overloads[0].params) << ") end\n"; + out_ << "function " << qname << "." << m.name + << "(" << paramNames(m.overloads[0].params) << ") end\n\n"; break; + case MemberKind::Property: case MemberKind::ReadOnlyProperty: case MemberKind::StaticProperty: case MemberKind::StaticReadOnlyProperty: // Properties are part of the table, no explicit function needed break; + default: break; } @@ -1129,119 +1215,10 @@ class LuaProxyVisitor : public InspectVisitor } std::ostream& out_; + std::string curNs_; std::string curClass_; }; -//================================================================================================= -/** - * @brief Visitor that emits a JSON representation of the inspection tree. - */ -class JsonVisitor : public InspectVisitor -{ -public: - explicit JsonVisitor(std::ostream& out) - : out_(out) - { - } - - void beginNamespace(const NamespaceInspectInfo& ns) override - { - if (depth_ == 0) out_ << "{\n"; - indent(); out_ << "\"name\": \"" << escape(ns.name) << "\",\n"; - indent(); out_ << "\"freeMembers\": [],\n"; - indent(); out_ << "\"classes\": [\n"; - ++depth_; - firstClass_ = true; - } - - void endNamespace([[maybe_unused]] const NamespaceInspectInfo& ns) override - { - --depth_; - out_ << "\n"; - indent(); out_ << "]\n"; - if (depth_ == 0) out_ << "}\n"; - } - - void beginClass(const ClassInspectInfo& cls) override - { - if (!firstClass_) out_ << ",\n"; - firstClass_ = false; - indent(); out_ << "{\n"; - ++depth_; - indent(); out_ << "\"name\": \"" << escape(cls.name) << "\",\n"; - indent(); out_ << "\"bases\": ["; - for (std::size_t i = 0; i < cls.baseClasses.size(); ++i) - { - if (i) out_ << ", "; - out_ << "\"" << escape(cls.baseClasses[i]) << "\""; - } - out_ << "],\n"; - indent(); out_ << "\"members\": [\n"; - ++depth_; - firstMember_ = true; - } - - void endClass([[maybe_unused]] const ClassInspectInfo& cls) override - { - --depth_; - out_ << "\n"; - indent(); out_ << "]\n"; - --depth_; - indent(); out_ << "}"; - } - - void visitMember([[maybe_unused]] const ClassInspectInfo& cls, const MemberInfo& m) override - { - if (!firstMember_) out_ << ",\n"; - firstMember_ = false; - indent(); - out_ << "{ \"name\": \"" << escape(m.name) << "\"" - << ", \"kind\": \"" << kindStr(m.kind) << "\"" - << ", \"overloads\": " << m.overloads.size() - << " }"; - } - -private: - void indent() const - { - for (int i = 0; i < depth_; ++i) - out_ << " "; - } - - static std::string escape(const std::string& s) - { - std::string out; - for (char c : s) - { - if (c == '"') out += "\\\""; - else if (c == '\\') out += "\\\\"; - else out += c; - } - return out; - } - - static const char* kindStr(MemberKind k) - { - switch (k) - { - case MemberKind::Method: return "method"; - case MemberKind::StaticMethod: return "static_method"; - case MemberKind::Property: return "property"; - case MemberKind::ReadOnlyProperty: return "readonly_property"; - case MemberKind::StaticProperty: return "static_property"; - case MemberKind::StaticReadOnlyProperty: return "static_readonly_property"; - case MemberKind::Constructor: return "constructor"; - case MemberKind::Metamethod: return "metamethod"; - default: return "unknown"; - } - } - - std::ostream& out_; - int depth_ = 0; - bool firstClass_ = true; - bool firstMember_ = true; -}; - //================================================================================================= /** * @brief Visitor that builds a structured Lua table on the Lua stack. diff --git a/Source/LuaBridge/detail/CFunctions.h b/Source/LuaBridge/detail/CFunctions.h index dfbb2610..0bd474f8 100644 --- a/Source/LuaBridge/detail/CFunctions.h +++ b/Source/LuaBridge/detail/CFunctions.h @@ -1391,6 +1391,38 @@ inline void add_property_getter(lua_State* L, const char* name, int tableIndex) lua_pop(L, 2); // Stack: - } +//================================================================================================= +/** + * @brief Store a property's C++ type name string in a side-table for inspection/tooling. + * + * Creates the side-table (keyed by getPropTypeKey()) on demand inside the table at tableIndex. + * A no-op when typeName is null or empty. + */ +inline void add_property_type(lua_State* L, const char* name, const char* typeName, int tableIndex) +{ +#if LUABRIDGE_SAFE_STACK_CHECKS + luaL_checkstack(L, 3, detail::error_lua_stack_overflow); +#endif + + if (!typeName || typeName[0] == '\0') + return; + + tableIndex = lua_absindex(L, tableIndex); + + lua_rawgetp_x(L, tableIndex, getPropTypeKey()); // Stack: ..., proptype (or nil) + if (lua_isnil(L, -1)) + { + lua_pop(L, 1); + lua_createtable(L, 0, 4); + lua_pushvalue(L, -1); + lua_rawsetp_x(L, tableIndex, getPropTypeKey()); + } + // Stack: ..., proptype + lua_pushstring(L, typeName); + rawsetfield(L, -2, name); + lua_pop(L, 1); // pop proptype +} + //================================================================================================= template diff --git a/Source/LuaBridge/detail/ClassInfo.h b/Source/LuaBridge/detail/ClassInfo.h index ad90f244..603037d5 100644 --- a/Source/LuaBridge/detail/ClassInfo.h +++ b/Source/LuaBridge/detail/ClassInfo.h @@ -146,6 +146,17 @@ template ().find_first_of('.')> return reinterpret_cast(0xdad); } +//================================================================================================= +/** + * @brief The key of a property-type table in a class/namespace metatable. + * + * Maps property names to their C++ return type strings (for inspection/IDE tooling). + */ +[[nodiscard]] inline const void* getPropTypeKey() noexcept +{ + return reinterpret_cast(0x9e7); +} + //================================================================================================= /** * @brief The key of a cast offset table in a derived class metatable. diff --git a/Source/LuaBridge/detail/FuncTraits.h b/Source/LuaBridge/detail/FuncTraits.h index c121bc48..c87af0d4 100644 --- a/Source/LuaBridge/detail/FuncTraits.h +++ b/Source/LuaBridge/detail/FuncTraits.h @@ -547,5 +547,35 @@ struct remove_first_type> template using remove_first_type_t = typename remove_first_type::type; +//================================================================================================= +/** + * @brief Deduces the value type of a property getter. + * + * Handles member object pointers (T C::*), callable getters (member functions, free + * functions, lambdas), and falls back to void for anything else. + */ +template +struct getter_return_type +{ + using type = void; +}; + +// Member object pointer: T C::* (not a function member pointer) +template +struct getter_return_type>> +{ + using type = std::remove_const_t; +}; + +// Callable getter (member function pointer, free function pointer, lambda/functor) +template +struct getter_return_type>> +{ + using type = remove_cvref_t>; +}; + +template +using getter_return_t = typename getter_return_type::type; + } // namespace detail } // namespace luabridge diff --git a/Source/LuaBridge/detail/Namespace.h b/Source/LuaBridge/detail/Namespace.h index 0039f3e4..cdc0f60a 100644 --- a/Source/LuaBridge/detail/Namespace.h +++ b/Source/LuaBridge/detail/Namespace.h @@ -709,6 +709,9 @@ class Namespace : public detail::Registrar detail::push_property_readonly(L, name); // Stack: co, cl, st, function detail::add_property_setter(L, name, -2); // Stack: co, cl, st + using RetType = detail::getter_return_t; + detail::add_property_type(L, name, std::string(detail::typeName()).c_str(), -1); // st + return *this; } @@ -724,6 +727,9 @@ class Namespace : public detail::Registrar detail::push_property_setter(L, std::move(set), name); // Stack: co, cl, st, function detail::add_property_setter(L, name, -2); // Stack: co, cl, st + using RetType = detail::getter_return_t; + detail::add_property_type(L, name, std::string(detail::typeName()).c_str(), -1); // st + return *this; } @@ -926,6 +932,9 @@ class Namespace : public detail::Registrar detail::push_property_readonly(L, name); // Stack: co, cl, st, function detail::add_property_setter(L, name, -3); // Stack: co, cl, st + using RetType = detail::getter_return_t; + detail::add_property_type(L, name, std::string(detail::typeName()).c_str(), -2); // cl + return *this; } @@ -943,6 +952,9 @@ class Namespace : public detail::Registrar detail::push_class_property_setter(L, std::move(setter), name); // Stack: co, cl, st, setter detail::add_property_setter(L, name, -3); // Stack: co, cl, st + using RetType = detail::getter_return_t; + detail::add_property_type(L, name, std::string(detail::typeName()).c_str(), -2); // cl + return *this; } @@ -2034,6 +2046,9 @@ class Namespace : public detail::Registrar detail::push_property_readonly(L, name); // Stack: ns, function detail::add_property_setter(L, name, -2); // Stack: ns + using RetType = detail::getter_return_t; + detail::add_property_type(L, name, std::string(detail::typeName()).c_str(), -1); // ns + return *this; } @@ -2065,6 +2080,9 @@ class Namespace : public detail::Registrar detail::push_property_setter(L, std::move(setter), name); // Stack: ns, setter detail::add_property_setter(L, name, -2); // Stack: ns + using RetType = detail::getter_return_t; + detail::add_property_type(L, name, std::string(detail::typeName()).c_str(), -1); // ns + return *this; } diff --git a/Tests/Source/InspectTests.cpp b/Tests/Source/InspectTests.cpp index 54f22674..37d0eae2 100644 --- a/Tests/Source/InspectTests.cpp +++ b/Tests/Source/InspectTests.cpp @@ -347,7 +347,10 @@ TEST_F(InspectTests, LuaLSVisitorProducesAnnotations) luabridge::getGlobalNamespace(L) .beginNamespace("LuaLS") .beginClass("Cls") + .addConstructor() .addProperty("value", &Cls::getValue) + .addFunction("xyz", [](const Cls& self, int a) { return a + 1; }) + .addStaticFunction("abc", [](bool, float, int) { return "42"; }) .endClass() .endNamespace(); @@ -358,8 +361,19 @@ TEST_F(InspectTests, LuaLSVisitorProducesAnnotations) const auto result = ss.str(); EXPECT_FALSE(result.empty()); - EXPECT_NE(std::string::npos, result.find("@class")); - EXPECT_NE(std::string::npos, result.find("Cls")); + EXPECT_NE(std::string::npos, result.find("---@class LuaLS.Cls")); + EXPECT_NE(std::string::npos, result.find("---@field value integer # readonly")); + EXPECT_NE(std::string::npos, result.find("---@overload fun(): LuaLS.Cls")); + EXPECT_NE(std::string::npos, result.find("local Cls = {}")); + EXPECT_NE(std::string::npos, result.find("---@param self LuaLS.Cls")); + EXPECT_NE(std::string::npos, result.find("---@param p1 integer")); + EXPECT_NE(std::string::npos, result.find("---@return integer")); + EXPECT_NE(std::string::npos, result.find("function LuaLS.Cls:xyz(p1) end")); + EXPECT_NE(std::string::npos, result.find("---@param p1 boolean")); + EXPECT_NE(std::string::npos, result.find("---@param p2 number")); + EXPECT_NE(std::string::npos, result.find("---@param p3 integer")); + EXPECT_NE(std::string::npos, result.find("---@return string")); + EXPECT_NE(std::string::npos, result.find("function LuaLS.Cls.abc(p1, p2, p3) end")); } TEST_F(InspectTests, LuaProxyVisitorProducesStubs) @@ -384,25 +398,6 @@ TEST_F(InspectTests, LuaProxyVisitorProducesStubs) EXPECT_NE(std::string::npos, result.find("method")); } -TEST_F(InspectTests, JsonVisitorProducesJson) -{ - struct Cls {}; - luabridge::getGlobalNamespace(L) - .beginNamespace("Json") - .beginClass("Cls").endClass() - .endNamespace(); - - auto ns = luabridge::inspectNamespace(L, "Json"); - std::stringstream ss; - luabridge::JsonVisitor v(ss); - luabridge::accept(ns, v); - - const auto result = ss.str(); - EXPECT_FALSE(result.empty()); - EXPECT_NE(std::string::npos, result.find("\"name\"")); - EXPECT_NE(std::string::npos, result.find("Json")); -} - //================================================================================================= // inspect() stack balance test From ac7916866407f8eb65f09d383d5d94860e781de1 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Wed, 8 Apr 2026 00:20:22 +0200 Subject: [PATCH 07/14] Disable inspection when no REFLECT is defined --- Source/LuaBridge/Inspect.h | 29 ++++++++++++++++++++++++----- Tests/Source/InspectTests.cpp | 7 +++++-- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/Source/LuaBridge/Inspect.h b/Source/LuaBridge/Inspect.h index 4e0e7613..29b4d5b5 100644 --- a/Source/LuaBridge/Inspect.h +++ b/Source/LuaBridge/Inspect.h @@ -4,6 +4,10 @@ #pragma once +#if ! LUABRIDGE_ENABLE_REFLECT +#error "This header is only for use when LUABRIDGE_ENABLE_REFLECT is active." +#endif + #include "detail/CFunctions.h" #include "detail/ClassInfo.h" #include "detail/LuaHelpers.h" @@ -844,16 +848,29 @@ class ConsoleVisitor : public InspectVisitor switch (m.kind) { case MemberKind::Property: - indent(); out_ << m.name << ": any;\n"; break; + indent(); + out_ << m.name << ": any;\n"; + break; + case MemberKind::ReadOnlyProperty: - indent(); out_ << "readonly " << m.name << ": any;\n"; break; + indent(); + out_ << "readonly " << m.name << ": any;\n"; + break; + case MemberKind::StaticProperty: - indent(); out_ << "static " << m.name << ": any;\n"; break; + indent(); + out_ << "static " << m.name << ": any;\n"; + break; + case MemberKind::StaticReadOnlyProperty: - indent(); out_ << "static readonly " << m.name << ": any;\n"; break; + indent(); + out_ << "static readonly " << m.name << ": any;\n"; + break; + case MemberKind::Metamethod: // Skip metamethods in TypeScript-style output break; + case MemberKind::Constructor: for (const auto& ov : m.overloads) { @@ -861,10 +878,11 @@ class ConsoleVisitor : public InspectVisitor out_ << "constructor(" << paramStr(ov) << ");\n"; } break; + case MemberKind::Method: case MemberKind::StaticMethod: { - bool isStatic = (m.kind == MemberKind::StaticMethod); + const bool isStatic = (m.kind == MemberKind::StaticMethod); for (std::size_t i = 0; i < m.overloads.size(); ++i) { indent(); @@ -873,6 +891,7 @@ class ConsoleVisitor : public InspectVisitor } break; } + default: break; } diff --git a/Tests/Source/InspectTests.cpp b/Tests/Source/InspectTests.cpp index 37d0eae2..268f0a9f 100644 --- a/Tests/Source/InspectTests.cpp +++ b/Tests/Source/InspectTests.cpp @@ -1,10 +1,12 @@ // https://github.com/kunitoki/LuaBridge3 -// Copyright 2024, kunitoki +// Copyright 2026, kunitoki // SPDX-License-Identifier: MIT #include "TestBase.h" #include "LuaBridge/LuaBridge.h" + +#if LUABRIDGE_ENABLE_REFLECT #include "LuaBridge/Inspect.h" #include @@ -360,6 +362,7 @@ TEST_F(InspectTests, LuaLSVisitorProducesAnnotations) luabridge::accept(ns, v); const auto result = ss.str(); + std::cout << result << std::endl; EXPECT_FALSE(result.empty()); EXPECT_NE(std::string::npos, result.find("---@class LuaLS.Cls")); EXPECT_NE(std::string::npos, result.find("---@field value integer # readonly")); @@ -492,7 +495,6 @@ TEST_F(InspectTests, WithHintsOverloadsCompile) EXPECT_EQ(2u, m->overloads.size()); } -#if LUABRIDGE_ENABLE_REFLECT TEST_F(InspectTests, ReflectTypeInfoPopulated) { struct Enemy { void takeDamage(float /*amount*/, int /*count*/) {} }; @@ -552,4 +554,5 @@ TEST_F(InspectTests, ReflectFreeFunction) EXPECT_EQ("a", fn->overloads[0].params[0].hint); EXPECT_EQ("b", fn->overloads[0].params[1].hint); } + #endif // LUABRIDGE_ENABLE_REFLECT From 0da524f1755ffb72f1c108365152068be65ccd80 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Wed, 8 Apr 2026 00:29:35 +0200 Subject: [PATCH 08/14] More docs --- Manual.md | 299 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 299 insertions(+) diff --git a/Manual.md b/Manual.md index e9f74774..51a710a6 100644 --- a/Manual.md +++ b/Manual.md @@ -33,6 +33,7 @@ Contents * [2.4 - Property Member Proxies](#24---property-member-proxies) * [2.5 - Function Member Proxies](#25---function-member-proxies) * [2.5.1 - Function Overloading](#251---function-overloading) + * [2.5.2 - Parameter Name Hints](#252---parameter-name-hints) * [2.6 - Constructors](#26---constructors) * [2.6.1 - Constructor Proxies](#261---constructor-proxies) * [2.6.2 - Constructor Factories](#262---constructor-factories) @@ -51,6 +52,13 @@ Contents * [2.9.4 - LuaCoroutine - Awaiting a Lua Thread from C++](#294---luacoroutine----awaiting-a-lua-thread-from-c) * [2.9.5 - Limitations](#295---limitations) +* [2.10 - Inspecting Registrations](#210---inspecting-registrations) + + * [2.10.1 - inspect\ and inspectNamespace](#2101---inspectT-and-inspectnamespace) + * [2.10.2 - Visitor API](#2102---visitor-api) + * [2.10.3 - ConsoleVisitor](#2103---consolevisitor) + * [2.10.4 - Enabling Full Type Information (LUABRIDGE_ENABLE_REFLECT)](#2104---enabling-full-type-information-luabridge_enable_reflect) + * [3 - Passing Objects](#3---passing-objects) * [3.1 - C++ Lifetime](#31---c-lifetime) @@ -84,6 +92,7 @@ Contents * [6.3 - LUABRIDGE_SAFE_LUA_C_EXCEPTION_HANDLING](#63---luabridge-safe-c-exception-handling) * [6.4 - LUABRIDGE_RAISE_UNREGISTERED_CLASS_USAGE](#64---luabridge-raise-unregistered-class-usage) * [6.5 - LUABRIDGE_HAS_CXX20_COROUTINES / LUABRIDGE_DISABLE_CXX20_COROUTINES](#65---luabridge-has-cxx20-coroutines--luabridge-disable-cxx20-coroutines) + * [6.6 - LUABRIDGE_ENABLE_REFLECT](#66---luabridge-enable-reflect) * [Appendix - API Reference](#appendix---api-reference) @@ -659,6 +668,40 @@ It's possible to mix lambdas, function pointers and member functions in overload Special attention needs to be given to the order (priority) of the overloads, based on the number and type of the arguments. Better to place first the overloads that can be called more frequently, and putting "stronger" types first: for example when having an overload taking an `int` and an overload taking `float`, as lua is not able to distinguish between them properly (until lua 5.4) the first overload will always be called. +### 2.5.2 - Parameter Name Hints + +When `LUABRIDGE_ENABLE_REFLECT` is defined (see [6.6](#66---luabridge-enable-reflect)), LuaBridge captures the C++ type names of function parameters at compile time. You can additionally attach human-readable **parameter name hints** to any function using `luabridge::withHints`. These names are stored alongside the function and become available through the [Inspection API](#210---inspecting-registrations). + +```cpp +#define LUABRIDGE_ENABLE_REFLECT +#include +#include + +struct Enemy +{ + void attack(Enemy* target, float damage); + void move(float x, float y, float z); +}; + +luabridge::getGlobalNamespace(L) + .beginNamespace("game") + .beginClass("Enemy") + .addFunction("attack", luabridge::withHints(&Enemy::attack, "target", "damage")) + .addFunction("move", luabridge::withHints(&Enemy::move, "x", "y", "z")) + .endClass() + .endNamespace(); +``` + +`withHints` is transparent: it forwards all function-trait queries to the wrapped function, so it can be used anywhere a plain function pointer is accepted — including overloaded registrations: + +```cpp +.addFunction("rotate", + luabridge::withHints(luabridge::overload(&Vec::rotate), "degrees"), + luabridge::withHints(luabridge::overload(&Vec::rotate), "quaternion")) +``` + +When `LUABRIDGE_ENABLE_REFLECT` is **not** defined, `withHints` is still accepted by the compiler and correctly strips the wrapper — the hints are simply ignored at runtime so there is no overhead. + 2.6 - Constructors ------------------ @@ -1427,6 +1470,134 @@ print(f()) -- -1 (done) * **Multi-value yield:** `co_yield` sends exactly one value per suspension. Use `std::tuple` or a struct if multiple values are needed. * **Thread safety:** Coroutine frames must be driven from a single OS thread. +2.10 - Inspecting Registrations +================================ + +LuaBridge3 can introspect its own registration tables at runtime, making it straightforward to generate documentation, build IDE auto-complete databases, produce TypeScript type declarations, or validate that a binding matches an expected schema. + +The inspection API lives in the **optional** header ``, which must be included separately and **requires** `LUABRIDGE_ENABLE_REFLECT` to be defined (see [6.6](#66---luabridge-enable-reflect)). + +```cpp +#define LUABRIDGE_ENABLE_REFLECT +#include +#include +``` + +### 2.10.1 - inspect\ and inspectNamespace + +Two top-level free functions collect inspection data without traversing any Lua tables from user code: + +**`inspect(L)`** — returns a `ClassInspectInfo` for the single registered class `T`. Returns an empty struct if `T` has not been registered. + +```cpp +auto info = luabridge::inspect(L); +std::cout << info.name << "\n"; // "Enemy" +for (const auto& m : info.members) + std::cout << " " << m.name << "\n"; +``` + +**`inspectNamespace(L, name)`** — returns a `NamespaceInspectInfo` for the given namespace. Pass `nullptr` or an empty string to inspect the global namespace `_G`. Dotted paths are supported. + +```cpp +auto ns = luabridge::inspectNamespace(L, "game"); // top-level namespace +auto sub = luabridge::inspectNamespace(L, "game.util"); // nested namespace +auto g = luabridge::inspectNamespace(L); // global namespace (_G) +``` + +The returned structures are plain data — they are independent copies that do not hold references to the Lua state: + +| Type | Fields | +|------|--------| +| `NamespaceInspectInfo` | `name`, `freeMembers`, `classes`, `subNamespaces` | +| `ClassInspectInfo` | `name`, `baseClasses`, `members` | +| `MemberInfo` | `name`, `kind` (`MemberKind`), `overloads` | +| `OverloadInfo` | `returnType`, `params` (`vector`), `isConst` | +| `ParamInfo` | `typeName`, `hint` | + +`MemberKind` is an enum with the following values: + +```cpp +enum class MemberKind +{ + Method, + StaticMethod, + Property, + ReadOnlyProperty, + StaticProperty, + StaticReadOnlyProperty, + Constructor, + Metamethod, +}; +``` + +`returnType` and `typeName` are populated only when `LUABRIDGE_ENABLE_REFLECT` is defined. `hint` is populated when the function was registered with `luabridge::withHints` (see [2.5.2](#252---parameter-name-hints)). + +### 2.10.2 - Visitor API + +For namespace-wide traversal, a visitor-pattern API is provided. Derive from `InspectVisitor` and override only the callbacks you need: + +```cpp +class InspectVisitor +{ +public: + virtual void beginNamespace(const NamespaceInspectInfo& ns) {} + virtual void endNamespace (const NamespaceInspectInfo& ns) {} + virtual void visitFreeMember(const NamespaceInspectInfo& ns, const MemberInfo& m) {} + + virtual void beginClass(const ClassInspectInfo& cls) {} + virtual void endClass (const ClassInspectInfo& cls) {} + virtual void visitMember(const ClassInspectInfo& cls, const MemberInfo& m) {} +}; +``` + +Drive a visitor over a pre-collected `NamespaceInspectInfo` using `accept`, or use the convenience wrapper `inspectAccept` that collects and visits in one call: + +```cpp +// Option A: collect once, visit multiple times +auto ns = luabridge::inspectNamespace(L, "game"); +MyVisitor v; +luabridge::accept(ns, v); + +// Option B: collect and visit in a single call +luabridge::inspectAccept(L, "game", v); +``` + +The traversal order for `accept` is: +1. `beginNamespace` +2. `visitFreeMember` for each free function / namespace-level property +3. Recursive traversal of each class (begin → members → end) +4. Recursive traversal of each sub-namespace +5. `endNamespace` + +### 2.10.3 - ConsoleVisitor + +LuaBridge provides a ready-made visitor, `ConsoleVisitor`, that prints a TypeScript-style declaration to any `std::ostream` (defaults to `std::cerr`): + +```cpp +luabridge::ConsoleVisitor printer(std::cout); +luabridge::inspectAccept(L, "game", printer); +``` + +Example output for a `game` namespace containing an `Enemy` class: + +``` +namespace game { + class Enemy { + constructor(p1: any); + attack(target: Enemy, damage: float): void; + move(x: float, y: float, z: float): void; + readonly hp: int; + static create(p1: float, p2: float): Enemy; + } +} +``` + +Type names and parameter names are filled in when `LUABRIDGE_ENABLE_REFLECT` is active and `withHints` was used during registration. Without reflection the output uses `any` for unknown types and `p1`, `p2`, … as placeholder parameter names. + +### 2.10.4 - Enabling Full Type Information (LUABRIDGE_ENABLE_REFLECT) + +See [6.6 - LUABRIDGE_ENABLE_REFLECT](#66---luabridge-enable-reflect) for the compile-time flag that activates C++ type-name capture. When the flag is off, the inspection API still works — it just reports empty strings for type names. The `withHints` parameter name hints work regardless of the flag. + 3 - Passing Objects =================== @@ -2171,6 +2342,28 @@ You can also override the detection result explicitly: Attempting to use coroutine integration on Lua 5.1, LuaJIT, or Luau will emit a compile-time `#error` unless `LUABRIDGE_DISABLE_COROUTINE_INTEGRATION` is also defined. +6.6 - LUABRIDGE_ENABLE_REFLECT +------------------------------- + +**Default: not defined (disabled)** + +When defined, LuaBridge captures the C++ type names of function parameters and return types at compile time using `typeid`. This information is stored alongside the function in the Lua registry and is retrieved by the [Inspection API](#210---inspecting-registrations) to populate `OverloadInfo::returnType` and `ParamInfo::typeName`. + +Enable reflection by defining the macro **before** including any LuaBridge header: + +```cpp +#define LUABRIDGE_ENABLE_REFLECT +#include +#include // optional: only needed when using the inspection API +``` + +> **Note:** `Inspect.h` unconditionally requires `LUABRIDGE_ENABLE_REFLECT` and will emit a `#error` if the macro is not defined. You may use `withHints` (see [2.5.2](#252---parameter-name-hints)) without including `Inspect.h`. + +When `LUABRIDGE_ENABLE_REFLECT` is not defined: +* `withHints` parameter name hints are still stored and available through inspection. +* `OverloadInfo::returnType` and `ParamInfo::typeName` are empty strings. +* The `Inspect.h` header cannot be included. + Appendix - API Reference ======================== @@ -2610,3 +2803,109 @@ int lua_resume_x(lua_State* L, lua_State* from, int nargs, int* nresults = nullp /// Returns true if the current C function can yield via lua_yieldk. bool lua_isyieldable_x(lua_State* L); ``` + +Registration Inspection (requires `LUABRIDGE_ENABLE_REFLECT`, ``) +--------------------------------------------------------------------------------------- + +```cpp +/// Kind of a registered member. +enum class MemberKind +{ + Method, + StaticMethod, + Property, + ReadOnlyProperty, + StaticProperty, + StaticReadOnlyProperty, + Constructor, + Metamethod, +}; + +/// Type and optional name hint for one Lua-visible parameter of an overload. +struct ParamInfo +{ + std::string typeName; ///< C++ type name (e.g. "float", "int", "MyClass"). Empty if unavailable. + std::string hint; ///< Optional user-provided name (e.g. "damage"). Empty if not provided. +}; + +/// Type information for one overload of a registered function or constructor. +struct OverloadInfo +{ + std::string returnType; ///< C++ return type name. Empty if unavailable. + std::vector params; ///< One entry per Lua-visible parameter. + bool isConst = false; ///< True when this is a const member function. +}; + +/// Information about one registered member (method, property, constructor, …). +struct MemberInfo +{ + std::string name; + MemberKind kind = MemberKind::Method; + std::vector overloads; ///< At least 1 entry; may contain empty OverloadInfo. +}; + +/// Inspection result for one registered class. +struct ClassInspectInfo +{ + std::string name; + std::vector baseClasses; ///< Names of all registered ancestor classes. + std::vector members; + + void accept(InspectVisitor& v) const; +}; + +/// Inspection result for a namespace (may contain classes and sub-namespaces). +struct NamespaceInspectInfo +{ + std::string name; + std::vector freeMembers; ///< Free functions and namespace-level properties. + std::vector classes; + std::vector subNamespaces; + + void accept(InspectVisitor& v) const; +}; + +/// Visitor interface for traversing an inspection result tree. +class InspectVisitor +{ +public: + virtual void beginNamespace(const NamespaceInspectInfo& ns) {} + virtual void endNamespace (const NamespaceInspectInfo& ns) {} + virtual void visitFreeMember(const NamespaceInspectInfo& ns, const MemberInfo& m) {} + + virtual void beginClass(const ClassInspectInfo& cls) {} + virtual void endClass (const ClassInspectInfo& cls) {} + virtual void visitMember(const ClassInspectInfo& cls, const MemberInfo& m) {} +}; + +/// Built-in visitor that emits TypeScript-style pseudo-declarations to an ostream. +class ConsoleVisitor : public InspectVisitor +{ +public: + explicit ConsoleVisitor(std::ostream& out = std::cerr); + // overrides all six InspectVisitor methods +}; + +/// Inspect a single registered class by its C++ type. +/// Returns an empty ClassInspectInfo if T is not registered. +template +[[nodiscard]] ClassInspectInfo inspect(lua_State* L); + +/// Inspect a namespace by name. Pass nullptr or "" for the global namespace. +/// Supports dotted paths (e.g. "Outer.Inner"). +/// Returns an empty NamespaceInspectInfo if the name is not found. +[[nodiscard]] NamespaceInspectInfo inspectNamespace(lua_State* L, const char* namespaceName = nullptr); + +/// Traverse a NamespaceInspectInfo with a visitor. +void accept(const NamespaceInspectInfo& ns, InspectVisitor& v); +void accept(const ClassInspectInfo& cls, InspectVisitor& v); + +/// Collect inspection data for a namespace and drive a visitor over it (convenience wrapper). +void inspectAccept(lua_State* L, const char* namespaceName, InspectVisitor& visitor); + +/// Attach Lua parameter name hints to a function for use with addFunction / addStaticFunction. +/// Works with and without LUABRIDGE_ENABLE_REFLECT. Hints are stored at runtime; type capture +/// requires the macro. +template +[[nodiscard]] FunctionWithHints> withHints(F&& func, Names&&... paramNames); +``` From 572d45e6e46d1835ef71d4d9684e7ab501dedeac Mon Sep 17 00:00:00 2001 From: kunitoki Date: Wed, 8 Apr 2026 00:48:25 +0200 Subject: [PATCH 09/14] Better manual --- Manual.md | 1033 +++++++++++++++++++++++++++-------------------------- 1 file changed, 519 insertions(+), 514 deletions(-) diff --git a/Manual.md b/Manual.md index 51a710a6..913e867c 100644 --- a/Manual.md +++ b/Manual.md @@ -45,19 +45,6 @@ Contents * [2.8 - Lua Stack](#28---lua-stack) * [2.8.1 - Enums](#281---enums) * [2.8.2 - lua_State](#282---lua_state) - * [2.9 - C++20 Coroutine Integration](#29---c20-coroutine-integration) - * [2.9.1 - CppCoroutine\ - Generators callable from Lua](#291---cppcoroutiner----generators-callable-from-lua) - * [2.9.2 - Accepting Arguments](#292---accepting-arguments) - * [2.9.3 - Class Coroutines - Static and Member](#293---class-coroutines----static-and-member) - * [2.9.4 - LuaCoroutine - Awaiting a Lua Thread from C++](#294---luacoroutine----awaiting-a-lua-thread-from-c) - * [2.9.5 - Limitations](#295---limitations) - -* [2.10 - Inspecting Registrations](#210---inspecting-registrations) - - * [2.10.1 - inspect\ and inspectNamespace](#2101---inspectT-and-inspectnamespace) - * [2.10.2 - Visitor API](#2102---visitor-api) - * [2.10.3 - ConsoleVisitor](#2103---consolevisitor) - * [2.10.4 - Enabling Full Type Information (LUABRIDGE_ENABLE_REFLECT)](#2104---enabling-full-type-information-luabridge_enable_reflect) * [3 - Passing Objects](#3---passing-objects) @@ -82,17 +69,31 @@ Contents * [4.3.2 - Class LuaException](#432---class-luaexception) * [4.3.3 - Calling with Error Handlers](#433---calling-with-error-handlers) * [4.4 - Wrapping C++ Callables](#44---wrapping-c-callables) + * [4.4.1 - LuaFunction\](#441---luafunctionsignature) + * [4.5 - C++20 Coroutine Integration](#45---c20-coroutine-integration) + * [4.5.1 - CppCoroutine\ - Generators callable from Lua](#451---cppcoroutiner----generators-callable-from-lua) + * [4.5.2 - Accepting Arguments](#452---accepting-arguments) + * [4.5.3 - Class Coroutines - Static and Member](#453---class-coroutines----static-and-member) + * [4.5.4 - LuaCoroutine - Awaiting a Lua Thread from C++](#454---luacoroutine----awaiting-a-lua-thread-from-c) + * [4.5.5 - Limitations](#455---limitations) * [5 - Security](#5---security) -* [6 - Configuration](#6---configuration) +* [6 - Inspecting Registrations](#6---inspecting-registrations) + + * [6.1 - inspect\ and inspectNamespace](#61---inspectT-and-inspectnamespace) + * [6.2 - Visitor API](#62---visitor-api) + * [6.3 - ConsoleVisitor](#63---consolevisitor) + * [6.4 - Enabling Full Type Information (LUABRIDGE_ENABLE_REFLECT)](#64---enabling-full-type-information-luabridge_enable_reflect) + +* [7 - Configuration](#7---configuration) - * [6.1 - LUABRIDGE_SAFE_STACK_CHECKS](#61---luabridge-safe-stack-checks) - * [6.2 - LUABRIDGE_STRICT_STACK_CONVERSIONS](#62---luabridge-strict-stack-conversions) - * [6.3 - LUABRIDGE_SAFE_LUA_C_EXCEPTION_HANDLING](#63---luabridge-safe-c-exception-handling) - * [6.4 - LUABRIDGE_RAISE_UNREGISTERED_CLASS_USAGE](#64---luabridge-raise-unregistered-class-usage) - * [6.5 - LUABRIDGE_HAS_CXX20_COROUTINES / LUABRIDGE_DISABLE_CXX20_COROUTINES](#65---luabridge-has-cxx20-coroutines--luabridge-disable-cxx20-coroutines) - * [6.6 - LUABRIDGE_ENABLE_REFLECT](#66---luabridge-enable-reflect) + * [7.1 - LUABRIDGE_SAFE_STACK_CHECKS](#71---luabridge-safe-stack-checks) + * [7.2 - LUABRIDGE_STRICT_STACK_CONVERSIONS](#72---luabridge-strict-stack-conversions) + * [7.3 - LUABRIDGE_SAFE_LUA_C_EXCEPTION_HANDLING](#73---luabridge-safe-c-exception-handling) + * [7.4 - LUABRIDGE_RAISE_UNREGISTERED_CLASS_USAGE](#74---luabridge-raise-unregistered-class-usage) + * [7.5 - LUABRIDGE_HAS_CXX20_COROUTINES / LUABRIDGE_DISABLE_CXX20_COROUTINES](#75---luabridge-has-cxx20-coroutines--luabridge-disable-cxx20-coroutines) + * [7.6 - LUABRIDGE_ENABLE_REFLECT](#76---luabridge-enable-reflect) * [Appendix - API Reference](#appendix---api-reference) @@ -670,7 +671,7 @@ Special attention needs to be given to the order (priority) of the overloads, ba ### 2.5.2 - Parameter Name Hints -When `LUABRIDGE_ENABLE_REFLECT` is defined (see [6.6](#66---luabridge-enable-reflect)), LuaBridge captures the C++ type names of function parameters at compile time. You can additionally attach human-readable **parameter name hints** to any function using `luabridge::withHints`. These names are stored alongside the function and become available through the [Inspection API](#210---inspecting-registrations). +When `LUABRIDGE_ENABLE_REFLECT` is defined (see [6.6](#76---luabridge-enable-reflect)), LuaBridge captures the C++ type names of function parameters at compile time. You can additionally attach human-readable **parameter name hints** to any function using `luabridge::withHints`. These names are stored alongside the function and become available through the [Inspection API](#6---inspecting-registrations). ```cpp #define LUABRIDGE_ENABLE_REFLECT @@ -1295,492 +1296,189 @@ When the script calls `useStateAndArgs`, it passes only the integer and string p The same is applicable for properties. -2.9 - C++20 Coroutine Integration ----------------------------------- +3 - Passing Objects +=================== -LuaBridge3 provides first-class interoperability between C++20 coroutines and Lua coroutines, available when compiling with C++20 or later and Lua 5.2+ (requires `lua_yieldk`). The feature is guarded by `LUABRIDGE_HAS_CXX20_COROUTINES`, which is detected automatically and can be suppressed with `LUABRIDGE_DISABLE_CXX20_COROUTINES`. +An object of a registered class `T` may be passed to Lua as: -> **Note:** C++20 coroutine integration is not supported on Lua 5.1, LuaJIT, or Luau (those targets lack a public `lua_yieldk` equivalent). +**`T`** -### 2.9.1 - CppCoroutine\ - Generators callable from Lua +Passed by value (a copy), with _Lua lifetime_. -`luabridge::CppCoroutine` is a coroutine return type that bridges C++20 coroutines with Lua's `coroutine.wrap` / `coroutine.resume` API. A function returning `CppCoroutine` can use `co_yield` to suspend and pass a value back to Lua, and `co_return` to finish and return a final value. +**`const T`** -Register via `Namespace::addCoroutine`: +Passed by value (a copy), with _Lua lifetime_. -```cpp -luabridge::getGlobalNamespace(L) - .addCoroutine("range", [](int from, int to) -> luabridge::CppCoroutine - { - for (int i = from; i <= to; ++i) - co_yield i; - co_return -1; // sentinel value when the range is exhausted - }); -``` +**`T*`** -From Lua, use `coroutine.wrap` to create a callable iterator: +Passed by reference, with _C++ lifetime_. -```lua -local gen = coroutine.wrap(range) -local v = gen(1, 5) -- first call passes arguments; yields 1 -while v ~= -1 do - print(v) -- 1, 2, 3, 4, 5 - v = gen() -- subsequent calls resume without arguments -end -``` +**`T&`** -`CppCoroutine` is also supported for coroutines that produce no values: +Passed by reference, with _C++ lifetime_. -```cpp -.addCoroutine("doWork", []() -> luabridge::CppCoroutine -{ - performStep1(); - co_return; -}); -``` +**`const T*`** -An abandoned coroutine (one that goes out of scope in Lua without being fully consumed) is automatically cleaned up by the Lua garbage collector - no manual resource management is needed. +Passed by const reference, with _C++ lifetime_. -### 2.9.2 - Accepting Arguments +**`const T&`** -The factory lambda receives the Lua call arguments on first invocation. A `lua_State*` parameter, if present, must be the **first** parameter and receives the running Lua thread: +Passed by const reference, with _C++ lifetime_. + +3.1 - C++ Lifetime +------------------ + +The creation and deletion of objects with _C++ lifetime_ is controlled by the C++ code. Lua does nothing when it garbage collects a reference to such an object. Specifically, the object's destructor is not called (since C++ owns it). Care must be taken to ensure that objects with C++ lifetime are not deleted while still being referenced by a `lua_State*`, or else undefined behavior results. In the previous examples, an instance of `A` can be passed to Lua with C++ lifetime, like this: ```cpp -.addCoroutine("adder", [](int a, int b) -> luabridge::CppCoroutine -{ - co_yield a + b; // first resume yields the sum - co_return a * b; // second resume returns the product -}); +A a; + +auto result = luabridge::push (L, &a); // pointer to 'a', C++ lifetime +lua_setglobal (L, "a"); + +auto result = luabridge::push (L, (const A*) &a); // pointer to 'a const', C++ lifetime +lua_setglobal (L, "ac"); + +auto result = luabridge::push (L, &a); // equivalent to push (L, (A const*) &a) +lua_setglobal (L, "ac2"); + +auto result = luabridge::push (L, new A); // compiles, but will leak memory +lua_setglobal (L, "ap"); ``` -```lua -local f = coroutine.wrap(adder) -print(f(3, 4)) -- 7 (yield: 3+4) -print(f()) -- 12 (return: 3*4) +3.2 - Lua Lifetime +------------------ + +When an object of a registered class is passed by value to Lua, it will have _Lua lifetime_. A copy of the passed object is constructed inside the userdata. When Lua has no more references to the object, it becomes eligible for garbage collection. When the userdata is collected, the destructor for the class will be called on the object. Care must be taken to ensure that objects with Lua lifetime are not accessed by C++ after they are garbage collected, or else undefined behavior results. An instance of `B` can be passed to Lua with Lua lifetime this way: + +```cpp +B b; + +auto result = luabridge::push (L, b); // Copy of b passed, Lua lifetime. +lua_setglobal (L, "b"); ``` -Multiple independent instances of the same coroutine factory can run concurrently - each call to `coroutine.wrap(name)` creates a separate C++ coroutine frame: +Given the previous code segments, these Lua statements are applicable: ```lua -local a = coroutine.wrap(adder) -local b = coroutine.wrap(adder) -a(1, 2) -- independent from b -b(10, 20) +print (test.A.staticData) -- Prints the static data member. +print (test.A.staticProperty) -- Prints the static property member. +test.A.staticFunc () -- Calls the static method. + +print (a.data) -- Prints the data member. +print (a.prop) -- Prints the property member. +a:func1 () -- Calls A::func1 (). +test.A.func1 (a) -- Equivalent to a:func1 (). +test.A.func1 ("hello") -- Error: "hello" is not a class A. +a:virtualFunc () -- Calls A::virtualFunc (). + +print (b.data) -- Prints B::dataMember. +print (b.prop) -- Prints inherited property member. +b:func1 () -- Calls B::func1 (). +b:func2 () -- Calls B::func2 (). +test.B.func2 (a) -- Error: a is not a class B. +test.A.func1 (b) -- Calls A::func1 (). +b:virtualFunc () -- Calls B::virtualFunc (). +test.B.virtualFunc (b) -- Calls B::virtualFunc (). +test.A.virtualFunc (b) -- Calls B::virtualFunc (). +test.B.virtualFunc (a) -- Error: a is not a class B. + +a = nil; collectgarbage () -- 'a' still exists in C++. +b = nil; collectgarbage () -- Lua calls ~B() on the copy of b. ``` -### 2.9.3 - Class Coroutines - Static and Member +When Lua script creates an object of class type using a registered constructor, the resulting value will have Lua lifetime. After Lua no longer references the object, it becomes eligible for garbage collection. You can still pass these to C++, either by reference or by value. If passed by reference, the usual warnings apply about accessing the reference later, after it has been garbage collected. -Coroutines can be attached directly to a registered class using `addStaticCoroutine` and `addCoroutine`. +3.3 - Pointers, References, and Pass by Value +--------------------------------------------- -**Static coroutines** behave identically to namespace-level coroutines but live in the class's static table. The factory requires no object argument: +When C++ objects are passed from Lua back to C++ as arguments to functions, or set as data members, LuaBridge does its best to automate the conversion. Using the previous definitions, the following functions may be registered to Lua: ```cpp -luabridge::getGlobalNamespace(L) - .beginClass("Counter") - .addStaticCoroutine("range", [](int from, int count) -> luabridge::CppCoroutine - { - for (int i = 0; i < count; ++i) - co_yield from + i; - co_return -1; - }) - .endClass(); +void func0 (A a); +void func1 (A* a); +void func2 (A const* a); +void func3 (A& a); +void func4 (A const& a); ``` +Executing this Lua code will have the prescribed effect: + ```lua -local f = coroutine.wrap(Counter.range) -print(f(5, 3)) -- 5 (first yield) -print(f()) -- 6 -print(f()) -- 7 -print(f()) -- -1 (done) +func0 (a) -- Passes a copy of a, using A's copy constructor. +func1 (a) -- Passes a pointer to a. +func2 (a) -- Passes a pointer to a const a. +func3 (a) -- Passes a reference to a. +func4 (a) -- Passes a reference to a const a. ``` -**Member coroutines** bind a coroutine factory to individual class instances. The factory's **first argument must be `T*` or `const T*`** - LuaBridge passes the Lua object as that argument automatically: +In the example above, all functions can read the data members and property members of `a`, or call const member functions of `a`. Only `func0`, `func1`, and `func3` can modify the data members and data properties, or call non-const member functions of `a`. + +The usual C++ inheritance and pointer assignment rules apply. Given: ```cpp -.beginClass("Counter") - .addCoroutine("generate", [](Counter* obj, int n) -> luabridge::CppCoroutine - { - for (int i = 0; i < n; ++i) - { - co_yield obj->value; - obj->increment(); - } - co_return -1; - }) -.endClass(); +void func5 (B b); +void func6 (B* b); ``` +These Lua statements hold: + ```lua -local obj = Counter() -local f = coroutine.wrap(Counter.generate) -print(f(obj, 3)) -- 0 (obj.value before first increment) -print(f()) -- 1 -print(f()) -- 2 -print(f()) -- -1 (done) +func5 (b) -- Passes a copy of b, using B's copy constructor. +func6 (b) -- Passes a pointer to b. +func6 (a) -- Error: Pointer to B expected. +func1 (b) -- Okay, b is a subclass of a. ``` -**Const vs non-const:** a factory that takes `const T*` as its first argument is registered as a const method - accessible on both const and non-const objects (it appears in both the const and non-const class tables). A factory taking `T*` is registered as a non-const method and is accessible on non-const objects only. +When a pointer or pointer to const is passed to Lua and the pointer is null (zero), LuaBridge will pass Lua a `nil` instead. When Lua passes a `nil` to C++ where a pointer is expected, a null (zero) is passed instead. Attempting to pass a null pointer to a C++ function expecting a reference results in `lua_error` being called. -```cpp -// Accessible on const and non-const objects: -.addCoroutine("peek", [](const Counter* obj) -> luabridge::CppCoroutine -{ - co_yield obj->value; - co_return obj->value * 2; -}) +3.4 - Shared Lifetime +--------------------- -// Accessible on non-const objects only: -.addCoroutine("pop", [](Counter* obj) -> luabridge::CppCoroutine -{ - co_yield obj->value--; - co_return obj->value; -}) -``` +LuaBridge supports a _shared lifetime_ model: dynamically allocated and reference counted objects whose ownership is shared by both Lua and C++. The object remains in existence until there are no remaining C++ or Lua references, and Lua performs its usual garbage collection cycle. A container is recognized by a specialization of the `ContainerTraits` template class. LuaBridge will automatically recognize when a data type is a container when the corresponding specialization is present. Two styles of containers come with LuaBridge, including the necessary specializations. -### 2.9.4 - LuaCoroutine - Awaiting a Lua Thread from C++ +### 3.4.1 - User-defined Containers -`luabridge::LuaCoroutine` is an awaitable that can be used inside a `CppCoroutine` body to resume a child Lua thread synchronously. It runs the child thread to its first yield or return and gives back the status and the number of values the child left on its stack: +If you have your own container, you must provide a specialization of `luabridge::ContainerTraits` in the `luabridge` namespace for your type before it will be recognized by LuaBridge (or else the code will not compile): ```cpp -.addCoroutine("driver", [](lua_State* L) -> luabridge::CppCoroutine -{ - // Spawn a child Lua thread and anchor it in the registry so the GC - // doesn't collect it while we hold a pointer to it. - lua_State* child = lua_newthread(L); - int ref = luaL_ref(L, LUA_REGISTRYINDEX); // pops thread from L's stack +namespace luabridge { - lua_getglobal(child, "luaGenerator"); +template +struct ContainerTraits> +{ + using Type = T; - // Resume the child synchronously; suspends this C++ coroutine until done. - auto [status, nresults] = co_await luabridge::LuaCoroutine{ child, L }; + static CustomContainer construct (T* c) + { + return c; + } - int value = (nresults > 0) ? static_cast(lua_tointeger(child, -nresults)) : 0; + static T* get (const CustomContainer& c) + { + return c.getPointerToObject (); + } +}; - luaL_unref(L, LUA_REGISTRYINDEX, ref); - co_return value; -}); +} // namespace luabridge ``` -`LuaCoroutine` always completes synchronously (no external event loop is required). The `status` field contains `LUA_YIELD` if the child yielded or `LUA_OK` if it returned normally. - -### 2.9.5 - Limitations - -* **Lua version:** Requires Lua 5.2+ (`lua_yieldk`). Not supported on Lua 5.1, LuaJIT, or Luau. -* **C++ version:** Requires C++20 (``). Non-coroutine features continue to work under C++17. -* **Multi-value yield:** `co_yield` sends exactly one value per suspension. Use `std::tuple` or a struct if multiple values are needed. -* **Thread safety:** Coroutine frames must be driven from a single OS thread. +Containers must be safely constructible from raw pointers to objects that are already referenced by other instances of the container (such as is the case for the provided containers or for example `boost::intrusive_ptr` but not `std::shared_ptr` or `boost::shared_ptr`). -2.10 - Inspecting Registrations -================================ +### 3.4.2 - shared_ptr As Container -LuaBridge3 can introspect its own registration tables at runtime, making it straightforward to generate documentation, build IDE auto-complete databases, produce TypeScript type declarations, or validate that a binding matches an expected schema. +Standard containers like `std::shared_ptr` or `boost::shared_ptr` will work in LuaBridge3, but they require special care. This is because of type erasure; when the object goes from C++ to Lua and back to C++, constructing a new shared_ptr from the raw pointer will create another reference count and result in undefined behavior, unless it could intrusively reconstruct the container from a raw pointer. -The inspection API lives in the **optional** header ``, which must be included separately and **requires** `LUABRIDGE_ENABLE_REFLECT` to be defined (see [6.6](#66---luabridge-enable-reflect)). +To overcome this issue classes that should be managed by `shared_ptr` have to provide a way to correctly reconstruct a `shared_ptr` which can be done only if type hold it is deriving publicly from `std::enable_shared_from_this` or `boost::enable_shared_from_this`. No additional specialization of traits is needed in this case. ```cpp -#define LUABRIDGE_ENABLE_REFLECT -#include -#include -``` - -### 2.10.1 - inspect\ and inspectNamespace - -Two top-level free functions collect inspection data without traversing any Lua tables from user code: +struct A : public std::enable_shared_from_this +{ + A () { } + A (int) { } -**`inspect(L)`** — returns a `ClassInspectInfo` for the single registered class `T`. Returns an empty struct if `T` has not been registered. - -```cpp -auto info = luabridge::inspect(L); -std::cout << info.name << "\n"; // "Enemy" -for (const auto& m : info.members) - std::cout << " " << m.name << "\n"; -``` - -**`inspectNamespace(L, name)`** — returns a `NamespaceInspectInfo` for the given namespace. Pass `nullptr` or an empty string to inspect the global namespace `_G`. Dotted paths are supported. - -```cpp -auto ns = luabridge::inspectNamespace(L, "game"); // top-level namespace -auto sub = luabridge::inspectNamespace(L, "game.util"); // nested namespace -auto g = luabridge::inspectNamespace(L); // global namespace (_G) -``` - -The returned structures are plain data — they are independent copies that do not hold references to the Lua state: - -| Type | Fields | -|------|--------| -| `NamespaceInspectInfo` | `name`, `freeMembers`, `classes`, `subNamespaces` | -| `ClassInspectInfo` | `name`, `baseClasses`, `members` | -| `MemberInfo` | `name`, `kind` (`MemberKind`), `overloads` | -| `OverloadInfo` | `returnType`, `params` (`vector`), `isConst` | -| `ParamInfo` | `typeName`, `hint` | - -`MemberKind` is an enum with the following values: - -```cpp -enum class MemberKind -{ - Method, - StaticMethod, - Property, - ReadOnlyProperty, - StaticProperty, - StaticReadOnlyProperty, - Constructor, - Metamethod, -}; -``` - -`returnType` and `typeName` are populated only when `LUABRIDGE_ENABLE_REFLECT` is defined. `hint` is populated when the function was registered with `luabridge::withHints` (see [2.5.2](#252---parameter-name-hints)). - -### 2.10.2 - Visitor API - -For namespace-wide traversal, a visitor-pattern API is provided. Derive from `InspectVisitor` and override only the callbacks you need: - -```cpp -class InspectVisitor -{ -public: - virtual void beginNamespace(const NamespaceInspectInfo& ns) {} - virtual void endNamespace (const NamespaceInspectInfo& ns) {} - virtual void visitFreeMember(const NamespaceInspectInfo& ns, const MemberInfo& m) {} - - virtual void beginClass(const ClassInspectInfo& cls) {} - virtual void endClass (const ClassInspectInfo& cls) {} - virtual void visitMember(const ClassInspectInfo& cls, const MemberInfo& m) {} -}; -``` - -Drive a visitor over a pre-collected `NamespaceInspectInfo` using `accept`, or use the convenience wrapper `inspectAccept` that collects and visits in one call: - -```cpp -// Option A: collect once, visit multiple times -auto ns = luabridge::inspectNamespace(L, "game"); -MyVisitor v; -luabridge::accept(ns, v); - -// Option B: collect and visit in a single call -luabridge::inspectAccept(L, "game", v); -``` - -The traversal order for `accept` is: -1. `beginNamespace` -2. `visitFreeMember` for each free function / namespace-level property -3. Recursive traversal of each class (begin → members → end) -4. Recursive traversal of each sub-namespace -5. `endNamespace` - -### 2.10.3 - ConsoleVisitor - -LuaBridge provides a ready-made visitor, `ConsoleVisitor`, that prints a TypeScript-style declaration to any `std::ostream` (defaults to `std::cerr`): - -```cpp -luabridge::ConsoleVisitor printer(std::cout); -luabridge::inspectAccept(L, "game", printer); -``` - -Example output for a `game` namespace containing an `Enemy` class: - -``` -namespace game { - class Enemy { - constructor(p1: any); - attack(target: Enemy, damage: float): void; - move(x: float, y: float, z: float): void; - readonly hp: int; - static create(p1: float, p2: float): Enemy; - } -} -``` - -Type names and parameter names are filled in when `LUABRIDGE_ENABLE_REFLECT` is active and `withHints` was used during registration. Without reflection the output uses `any` for unknown types and `p1`, `p2`, … as placeholder parameter names. - -### 2.10.4 - Enabling Full Type Information (LUABRIDGE_ENABLE_REFLECT) - -See [6.6 - LUABRIDGE_ENABLE_REFLECT](#66---luabridge-enable-reflect) for the compile-time flag that activates C++ type-name capture. When the flag is off, the inspection API still works — it just reports empty strings for type names. The `withHints` parameter name hints work regardless of the flag. - -3 - Passing Objects -=================== - -An object of a registered class `T` may be passed to Lua as: - -**`T`** - -Passed by value (a copy), with _Lua lifetime_. - -**`const T`** - -Passed by value (a copy), with _Lua lifetime_. - -**`T*`** - -Passed by reference, with _C++ lifetime_. - -**`T&`** - -Passed by reference, with _C++ lifetime_. - -**`const T*`** - -Passed by const reference, with _C++ lifetime_. - -**`const T&`** - -Passed by const reference, with _C++ lifetime_. - -3.1 - C++ Lifetime ------------------- - -The creation and deletion of objects with _C++ lifetime_ is controlled by the C++ code. Lua does nothing when it garbage collects a reference to such an object. Specifically, the object's destructor is not called (since C++ owns it). Care must be taken to ensure that objects with C++ lifetime are not deleted while still being referenced by a `lua_State*`, or else undefined behavior results. In the previous examples, an instance of `A` can be passed to Lua with C++ lifetime, like this: - -```cpp -A a; - -auto result = luabridge::push (L, &a); // pointer to 'a', C++ lifetime -lua_setglobal (L, "a"); - -auto result = luabridge::push (L, (const A*) &a); // pointer to 'a const', C++ lifetime -lua_setglobal (L, "ac"); - -auto result = luabridge::push (L, &a); // equivalent to push (L, (A const*) &a) -lua_setglobal (L, "ac2"); - -auto result = luabridge::push (L, new A); // compiles, but will leak memory -lua_setglobal (L, "ap"); -``` - -3.2 - Lua Lifetime ------------------- - -When an object of a registered class is passed by value to Lua, it will have _Lua lifetime_. A copy of the passed object is constructed inside the userdata. When Lua has no more references to the object, it becomes eligible for garbage collection. When the userdata is collected, the destructor for the class will be called on the object. Care must be taken to ensure that objects with Lua lifetime are not accessed by C++ after they are garbage collected, or else undefined behavior results. An instance of `B` can be passed to Lua with Lua lifetime this way: - -```cpp -B b; - -auto result = luabridge::push (L, b); // Copy of b passed, Lua lifetime. -lua_setglobal (L, "b"); -``` - -Given the previous code segments, these Lua statements are applicable: - -```lua -print (test.A.staticData) -- Prints the static data member. -print (test.A.staticProperty) -- Prints the static property member. -test.A.staticFunc () -- Calls the static method. - -print (a.data) -- Prints the data member. -print (a.prop) -- Prints the property member. -a:func1 () -- Calls A::func1 (). -test.A.func1 (a) -- Equivalent to a:func1 (). -test.A.func1 ("hello") -- Error: "hello" is not a class A. -a:virtualFunc () -- Calls A::virtualFunc (). - -print (b.data) -- Prints B::dataMember. -print (b.prop) -- Prints inherited property member. -b:func1 () -- Calls B::func1 (). -b:func2 () -- Calls B::func2 (). -test.B.func2 (a) -- Error: a is not a class B. -test.A.func1 (b) -- Calls A::func1 (). -b:virtualFunc () -- Calls B::virtualFunc (). -test.B.virtualFunc (b) -- Calls B::virtualFunc (). -test.A.virtualFunc (b) -- Calls B::virtualFunc (). -test.B.virtualFunc (a) -- Error: a is not a class B. - -a = nil; collectgarbage () -- 'a' still exists in C++. -b = nil; collectgarbage () -- Lua calls ~B() on the copy of b. -``` - -When Lua script creates an object of class type using a registered constructor, the resulting value will have Lua lifetime. After Lua no longer references the object, it becomes eligible for garbage collection. You can still pass these to C++, either by reference or by value. If passed by reference, the usual warnings apply about accessing the reference later, after it has been garbage collected. - -3.3 - Pointers, References, and Pass by Value ---------------------------------------------- - -When C++ objects are passed from Lua back to C++ as arguments to functions, or set as data members, LuaBridge does its best to automate the conversion. Using the previous definitions, the following functions may be registered to Lua: - -```cpp -void func0 (A a); -void func1 (A* a); -void func2 (A const* a); -void func3 (A& a); -void func4 (A const& a); -``` - -Executing this Lua code will have the prescribed effect: - -```lua -func0 (a) -- Passes a copy of a, using A's copy constructor. -func1 (a) -- Passes a pointer to a. -func2 (a) -- Passes a pointer to a const a. -func3 (a) -- Passes a reference to a. -func4 (a) -- Passes a reference to a const a. -``` - -In the example above, all functions can read the data members and property members of `a`, or call const member functions of `a`. Only `func0`, `func1`, and `func3` can modify the data members and data properties, or call non-const member functions of `a`. - -The usual C++ inheritance and pointer assignment rules apply. Given: - -```cpp -void func5 (B b); -void func6 (B* b); -``` - -These Lua statements hold: - -```lua -func5 (b) -- Passes a copy of b, using B's copy constructor. -func6 (b) -- Passes a pointer to b. -func6 (a) -- Error: Pointer to B expected. -func1 (b) -- Okay, b is a subclass of a. -``` - -When a pointer or pointer to const is passed to Lua and the pointer is null (zero), LuaBridge will pass Lua a `nil` instead. When Lua passes a `nil` to C++ where a pointer is expected, a null (zero) is passed instead. Attempting to pass a null pointer to a C++ function expecting a reference results in `lua_error` being called. - -3.4 - Shared Lifetime ---------------------- - -LuaBridge supports a _shared lifetime_ model: dynamically allocated and reference counted objects whose ownership is shared by both Lua and C++. The object remains in existence until there are no remaining C++ or Lua references, and Lua performs its usual garbage collection cycle. A container is recognized by a specialization of the `ContainerTraits` template class. LuaBridge will automatically recognize when a data type is a container when the corresponding specialization is present. Two styles of containers come with LuaBridge, including the necessary specializations. - -### 3.4.1 - User-defined Containers - -If you have your own container, you must provide a specialization of `luabridge::ContainerTraits` in the `luabridge` namespace for your type before it will be recognized by LuaBridge (or else the code will not compile): - -```cpp -namespace luabridge { - -template -struct ContainerTraits> -{ - using Type = T; - - static CustomContainer construct (T* c) - { - return c; - } - - static T* get (const CustomContainer& c) - { - return c.getPointerToObject (); - } -}; - -} // namespace luabridge -``` - -Containers must be safely constructible from raw pointers to objects that are already referenced by other instances of the container (such as is the case for the provided containers or for example `boost::intrusive_ptr` but not `std::shared_ptr` or `boost::shared_ptr`). - -### 3.4.2 - shared_ptr As Container - -Standard containers like `std::shared_ptr` or `boost::shared_ptr` will work in LuaBridge3, but they require special care. This is because of type erasure; when the object goes from C++ to Lua and back to C++, constructing a new shared_ptr from the raw pointer will create another reference count and result in undefined behavior, unless it could intrusively reconstruct the container from a raw pointer. - -To overcome this issue classes that should be managed by `shared_ptr` have to provide a way to correctly reconstruct a `shared_ptr` which can be done only if type hold it is deriving publicly from `std::enable_shared_from_this` or `boost::enable_shared_from_this`. No additional specialization of traits is needed in this case. - -```cpp -struct A : public std::enable_shared_from_this -{ - A () { } - A (int) { } - - void foo () { } -}; + void foo () { } +}; luabridge::getGlobalNamespace (L) .beginClass ("A") @@ -2106,91 +1804,266 @@ When compiling `LuaBridge3` with exceptions disabled, all references to try catc ### 4.3.2 - Class LuaException -When the application is compiled with exceptions and `luabridge::enableExceptions` function has been called, using `luabridge::call` or `LuaRef::operator()` will uses the C++ exception handling mechanism, throwing a `luabridge::LuaException` object in case an argument has a type that has not been registered (and cannot be pushed onto the lua stack) or the lua function generated an error: +When the application is compiled with exceptions and `luabridge::enableExceptions` function has been called, using `luabridge::call` or `LuaRef::operator()` will uses the C++ exception handling mechanism, throwing a `luabridge::LuaException` object in case an argument has a type that has not been registered (and cannot be pushed onto the lua stack) or the lua function generated an error: + +```lua +function fail () + error ("A problem occurred") +end +``` + +```cpp +luabridge::LuaRef f = luabridge::getGlobal (L, "fail"); + +try +{ + f (); +} +catch (const luabridge::LuaException& e) +{ + std::cerr << e.what (); +} +``` + +### 4.3.3 - Calling with Error Handlers + +By default, when a Lua error occurs the raw Lua error message is stored in the `TypeResult`. For more detailed diagnostics you can supply a custom message handler (equivalent to the `msgh` parameter of `lua_pcall`). The handler is a C++ callable that receives a `lua_State*`, may inspect the stack, and must return an `int`: + +```lua +function riskyOp () + error ("something went wrong") +end +``` + +```cpp +luabridge::LuaRef f = luabridge::getGlobal (L, "riskyOp"); + +auto handler = [] (lua_State* L) -> int { + // Augment the error message with a traceback + luaL_traceback (L, L, lua_tostring (L, 1), 1); + return 1; +}; + +auto result = f.callWithHandler (handler); +if (! result) + std::cerr << result.message (); // includes traceback +``` + +The same function is available as a free function: + +```cpp +auto result = luabridge::callWithHandler (f, handler, /* args... */); +``` + +4.4 - Wrapping C++ Callables +----------------------------- + +`luabridge::newFunction` (and its equivalent `LuaRef::newFunction`) wraps any C++ callable - a lambda, a function pointer, or a `std::function` - into a Lua function and returns it as a `LuaRef`. This is useful when you need to pass a C++ callback to Lua without going through the namespace/class registration API: + +```cpp +// Create a Lua function that squares its argument +luabridge::LuaRef square = luabridge::newFunction (L, [] (int x) { return x * x; }); + +// Store it in a Lua global +luabridge::setGlobal (L, square, "square"); +``` + +From Lua: +```lua +print (square (5)) -- 25 +``` + +You can also store such a function in a table or pass it as a callback argument. + +### 4.4.1 - LuaFunction\ + +When you have a `LuaRef` that you know will always be called with fixed argument and return types, `LuaFunction` provides a strongly-typed wrapper that avoids repeating the template arguments at every call site: + +```cpp +// Retrieve a Lua function and wrap it with a known signature +auto add = luabridge::getGlobal (L, "add").callable(); + +auto result = add (3, 4); // TypeResult +if (result) + std::cout << *result; // 7 +``` + +`LuaFunction` supports the same `call`, `callWithHandler`, and `isValid` interface as a `LuaRef`. The wrapped `LuaRef` is accessible via `ref()`. + +4.5 - C++20 Coroutine Integration +---------------------------------- + +LuaBridge3 provides first-class interoperability between C++20 coroutines and Lua coroutines, available when compiling with C++20 or later and Lua 5.2+ (requires `lua_yieldk`). The feature is guarded by `LUABRIDGE_HAS_CXX20_COROUTINES`, which is detected automatically and can be suppressed with `LUABRIDGE_DISABLE_CXX20_COROUTINES`. + +> **Note:** C++20 coroutine integration is not supported on Lua 5.1, LuaJIT, or Luau (those targets lack a public `lua_yieldk` equivalent). + +### 4.5.1 - CppCoroutine\ - Generators callable from Lua + +`luabridge::CppCoroutine` is a coroutine return type that bridges C++20 coroutines with Lua's `coroutine.wrap` / `coroutine.resume` API. A function returning `CppCoroutine` can use `co_yield` to suspend and pass a value back to Lua, and `co_return` to finish and return a final value. + +Register via `Namespace::addCoroutine`: + +```cpp +luabridge::getGlobalNamespace(L) + .addCoroutine("range", [](int from, int to) -> luabridge::CppCoroutine + { + for (int i = from; i <= to; ++i) + co_yield i; + co_return -1; // sentinel value when the range is exhausted + }); +``` + +From Lua, use `coroutine.wrap` to create a callable iterator: + +```lua +local gen = coroutine.wrap(range) +local v = gen(1, 5) -- first call passes arguments; yields 1 +while v ~= -1 do + print(v) -- 1, 2, 3, 4, 5 + v = gen() -- subsequent calls resume without arguments +end +``` + +`CppCoroutine` is also supported for coroutines that produce no values: + +```cpp +.addCoroutine("doWork", []() -> luabridge::CppCoroutine +{ + performStep1(); + co_return; +}); +``` + +An abandoned coroutine (one that goes out of scope in Lua without being fully consumed) is automatically cleaned up by the Lua garbage collector - no manual resource management is needed. + +### 4.5.2 - Accepting Arguments + +The factory lambda receives the Lua call arguments on first invocation. A `lua_State*` parameter, if present, must be the **first** parameter and receives the running Lua thread: + +```cpp +.addCoroutine("adder", [](int a, int b) -> luabridge::CppCoroutine +{ + co_yield a + b; // first resume yields the sum + co_return a * b; // second resume returns the product +}); +``` + +```lua +local f = coroutine.wrap(adder) +print(f(3, 4)) -- 7 (yield: 3+4) +print(f()) -- 12 (return: 3*4) +``` + +Multiple independent instances of the same coroutine factory can run concurrently - each call to `coroutine.wrap(name)` creates a separate C++ coroutine frame: ```lua -function fail () - error ("A problem occurred") -end +local a = coroutine.wrap(adder) +local b = coroutine.wrap(adder) +a(1, 2) -- independent from b +b(10, 20) ``` -```cpp -luabridge::LuaRef f = luabridge::getGlobal (L, "fail"); +### 4.5.3 - Class Coroutines - Static and Member -try -{ - f (); -} -catch (const luabridge::LuaException& e) -{ - std::cerr << e.what (); -} -``` +Coroutines can be attached directly to a registered class using `addStaticCoroutine` and `addCoroutine`. -### 4.3.3 - Calling with Error Handlers +**Static coroutines** behave identically to namespace-level coroutines but live in the class's static table. The factory requires no object argument: -By default, when a Lua error occurs the raw Lua error message is stored in the `TypeResult`. For more detailed diagnostics you can supply a custom message handler (equivalent to the `msgh` parameter of `lua_pcall`). The handler is a C++ callable that receives a `lua_State*`, may inspect the stack, and must return an `int`: +```cpp +luabridge::getGlobalNamespace(L) + .beginClass("Counter") + .addStaticCoroutine("range", [](int from, int count) -> luabridge::CppCoroutine + { + for (int i = 0; i < count; ++i) + co_yield from + i; + co_return -1; + }) + .endClass(); +``` ```lua -function riskyOp () - error ("something went wrong") -end +local f = coroutine.wrap(Counter.range) +print(f(5, 3)) -- 5 (first yield) +print(f()) -- 6 +print(f()) -- 7 +print(f()) -- -1 (done) ``` -```cpp -luabridge::LuaRef f = luabridge::getGlobal (L, "riskyOp"); +**Member coroutines** bind a coroutine factory to individual class instances. The factory's **first argument must be `T*` or `const T*`** - LuaBridge passes the Lua object as that argument automatically: -auto handler = [] (lua_State* L) -> int { - // Augment the error message with a traceback - luaL_traceback (L, L, lua_tostring (L, 1), 1); - return 1; -}; +```cpp +.beginClass("Counter") + .addCoroutine("generate", [](Counter* obj, int n) -> luabridge::CppCoroutine + { + for (int i = 0; i < n; ++i) + { + co_yield obj->value; + obj->increment(); + } + co_return -1; + }) +.endClass(); +``` -auto result = f.callWithHandler (handler); -if (! result) - std::cerr << result.message (); // includes traceback +```lua +local obj = Counter() +local f = coroutine.wrap(Counter.generate) +print(f(obj, 3)) -- 0 (obj.value before first increment) +print(f()) -- 1 +print(f()) -- 2 +print(f()) -- -1 (done) ``` -The same function is available as a free function: +**Const vs non-const:** a factory that takes `const T*` as its first argument is registered as a const method - accessible on both const and non-const objects (it appears in both the const and non-const class tables). A factory taking `T*` is registered as a non-const method and is accessible on non-const objects only. ```cpp -auto result = luabridge::callWithHandler (f, handler, /* args... */); +// Accessible on const and non-const objects: +.addCoroutine("peek", [](const Counter* obj) -> luabridge::CppCoroutine +{ + co_yield obj->value; + co_return obj->value * 2; +}) + +// Accessible on non-const objects only: +.addCoroutine("pop", [](Counter* obj) -> luabridge::CppCoroutine +{ + co_yield obj->value--; + co_return obj->value; +}) ``` -4.4 - Wrapping C++ Callables ------------------------------ +### 4.5.4 - LuaCoroutine - Awaiting a Lua Thread from C++ -`luabridge::newFunction` (and its equivalent `LuaRef::newFunction`) wraps any C++ callable - a lambda, a function pointer, or a `std::function` - into a Lua function and returns it as a `LuaRef`. This is useful when you need to pass a C++ callback to Lua without going through the namespace/class registration API: +`luabridge::LuaCoroutine` is an awaitable that can be used inside a `CppCoroutine` body to resume a child Lua thread synchronously. It runs the child thread to its first yield or return and gives back the status and the number of values the child left on its stack: ```cpp -// Create a Lua function that squares its argument -luabridge::LuaRef square = luabridge::newFunction (L, [] (int x) { return x * x; }); - -// Store it in a Lua global -luabridge::setGlobal (L, square, "square"); -``` +.addCoroutine("driver", [](lua_State* L) -> luabridge::CppCoroutine +{ + // Spawn a child Lua thread and anchor it in the registry so the GC + // doesn't collect it while we hold a pointer to it. + lua_State* child = lua_newthread(L); + int ref = luaL_ref(L, LUA_REGISTRYINDEX); // pops thread from L's stack -From Lua: -```lua -print (square (5)) -- 25 -``` + lua_getglobal(child, "luaGenerator"); -You can also store such a function in a table or pass it as a callback argument. + // Resume the child synchronously; suspends this C++ coroutine until done. + auto [status, nresults] = co_await luabridge::LuaCoroutine{ child, L }; -### 4.4.1 - LuaFunction\ + int value = (nresults > 0) ? static_cast(lua_tointeger(child, -nresults)) : 0; -When you have a `LuaRef` that you know will always be called with fixed argument and return types, `LuaFunction` provides a strongly-typed wrapper that avoids repeating the template arguments at every call site: + luaL_unref(L, LUA_REGISTRYINDEX, ref); + co_return value; +}); +``` -```cpp -// Retrieve a Lua function and wrap it with a known signature -auto add = luabridge::getGlobal (L, "add").callable(); +`LuaCoroutine` always completes synchronously (no external event loop is required). The `status` field contains `LUA_YIELD` if the child yielded or `LUA_OK` if it returned normally. -auto result = add (3, 4); // TypeResult -if (result) - std::cout << *result; // 7 -``` +### 4.5.5 - Limitations -`LuaFunction` supports the same `call`, `callWithHandler`, and `isValid` interface as a `LuaRef`. The wrapped `LuaRef` is accessible via `ref()`. +* **Lua version:** Requires Lua 5.2+ (`lua_yieldk`). Not supported on Lua 5.1, LuaJIT, or Luau. +* **C++ version:** Requires C++20 (``). Non-coroutine features continue to work under C++17. +* **Multi-value yield:** `co_yield` sends exactly one value per suspension. Use `std::tuple` or a struct if multiple values are needed. +* **Thread safety:** Coroutine frames must be driven from a single OS thread. 5 - Security ============ @@ -2231,12 +2104,144 @@ luabridge::getGlobalNamespace (L) .endNamespace () ``` -6 - Configuration +6 - Inspecting Registrations +============================ + +LuaBridge3 can introspect its own registration tables at runtime, making it straightforward to generate documentation, build IDE auto-complete databases, produce TypeScript type declarations, or validate that a binding matches an expected schema. + +The inspection API lives in the **optional** header ``, which must be included separately and **requires** `LUABRIDGE_ENABLE_REFLECT` to be defined (see [7.6](#76---luabridge-enable-reflect)). + +```cpp +#define LUABRIDGE_ENABLE_REFLECT +#include +#include +``` + +6.1 - inspect\ and inspectNamespace +---------------------------------------- + +Two top-level free functions collect inspection data without traversing any Lua tables from user code: + +**`inspect(L)`** — returns a `ClassInspectInfo` for the single registered class `T`. Returns an empty struct if `T` has not been registered. + +```cpp +auto info = luabridge::inspect(L); +std::cout << info.name << "\n"; // "Enemy" +for (const auto& m : info.members) + std::cout << " " << m.name << "\n"; +``` + +**`inspectNamespace(L, name)`** — returns a `NamespaceInspectInfo` for the given namespace. Pass `nullptr` or an empty string to inspect the global namespace `_G`. Dotted paths are supported. + +```cpp +auto ns = luabridge::inspectNamespace(L, "game"); // top-level namespace +auto sub = luabridge::inspectNamespace(L, "game.util"); // nested namespace +auto g = luabridge::inspectNamespace(L); // global namespace (_G) +``` + +The returned structures are plain data — they are independent copies that do not hold references to the Lua state: + +| Type | Fields | +|------|--------| +| `NamespaceInspectInfo` | `name`, `freeMembers`, `classes`, `subNamespaces` | +| `ClassInspectInfo` | `name`, `baseClasses`, `members` | +| `MemberInfo` | `name`, `kind` (`MemberKind`), `overloads` | +| `OverloadInfo` | `returnType`, `params` (`vector`), `isConst` | +| `ParamInfo` | `typeName`, `hint` | + +`MemberKind` is an enum with the following values: + +```cpp +enum class MemberKind +{ + Method, + StaticMethod, + Property, + ReadOnlyProperty, + StaticProperty, + StaticReadOnlyProperty, + Constructor, + Metamethod, +}; +``` + +`returnType` and `typeName` are populated only when `LUABRIDGE_ENABLE_REFLECT` is defined. `hint` is populated when the function was registered with `luabridge::withHints` (see [2.5.2](#252---parameter-name-hints)). + +6.2 - Visitor API +------------------ + +For namespace-wide traversal, a visitor-pattern API is provided. Derive from `InspectVisitor` and override only the callbacks you need: + +```cpp +class InspectVisitor +{ +public: + virtual void beginNamespace(const NamespaceInspectInfo& ns) {} + virtual void endNamespace (const NamespaceInspectInfo& ns) {} + virtual void visitFreeMember(const NamespaceInspectInfo& ns, const MemberInfo& m) {} + + virtual void beginClass(const ClassInspectInfo& cls) {} + virtual void endClass (const ClassInspectInfo& cls) {} + virtual void visitMember(const ClassInspectInfo& cls, const MemberInfo& m) {} +}; +``` + +Drive a visitor over a pre-collected `NamespaceInspectInfo` using `accept`, or use the convenience wrapper `inspectAccept` that collects and visits in one call: + +```cpp +// Option A: collect once, visit multiple times +auto ns = luabridge::inspectNamespace(L, "game"); +MyVisitor v; +luabridge::accept(ns, v); + +// Option B: collect and visit in a single call +luabridge::inspectAccept(L, "game", v); +``` + +The traversal order for `accept` is: +1. `beginNamespace` +2. `visitFreeMember` for each free function / namespace-level property +3. Recursive traversal of each class (begin → members → end) +4. Recursive traversal of each sub-namespace +5. `endNamespace` + +6.3 - ConsoleVisitor +--------------------- + +LuaBridge provides a ready-made visitor, `ConsoleVisitor`, that prints a TypeScript-style declaration to any `std::ostream` (defaults to `std::cerr`): + +```cpp +luabridge::ConsoleVisitor printer(std::cout); +luabridge::inspectAccept(L, "game", printer); +``` + +Example output for a `game` namespace containing an `Enemy` class: + +``` +namespace game { + class Enemy { + constructor(p1: any); + attack(target: Enemy, damage: float): void; + move(x: float, y: float, z: float): void; + readonly hp: int; + static create(p1: float, p2: float): Enemy; + } +} +``` + +Type names and parameter names are filled in when `LUABRIDGE_ENABLE_REFLECT` is active and `withHints` was used during registration. Without reflection the output uses `any` for unknown types and `p1`, `p2`, … as placeholder parameter names. + +6.4 - Enabling Full Type Information (LUABRIDGE_ENABLE_REFLECT) +---------------------------------------------------------------- + +See [7.6 - LUABRIDGE_ENABLE_REFLECT](#76---luabridge-enable-reflect) for the compile-time flag that activates C++ type-name capture. When the flag is off, the inspection API still works — it just reports empty strings for type names. The `withHints` parameter name hints work regardless of the flag. + +7 - Configuration ================= LuaBridge3 exposes several compile-time configuration macros. Each macro can be overridden by defining it **before** including any LuaBridge header, or by passing it as a compiler flag (e.g. `-DLUABRIDGE_SAFE_STACK_CHECKS=0`). -6.1 - LUABRIDGE_SAFE_STACK_CHECKS +7.1 - LUABRIDGE_SAFE_STACK_CHECKS ---------------------------------- **Default: `1` (enabled)** @@ -2250,7 +2255,7 @@ Disable this flag only when you are certain that the Lua stack will never overfl #include ``` -6.2 - LUABRIDGE_STRICT_STACK_CONVERSIONS +7.2 - LUABRIDGE_STRICT_STACK_CONVERSIONS ----------------------------------------- **Default: `0` (disabled)** @@ -2288,7 +2293,7 @@ lua_pushboolean (L, 1); auto r = luabridge::Stack::get (L, -1); // ok: true ``` -6.3 - LUABRIDGE_SAFE_LUA_C_EXCEPTION_HANDLING +7.3 - LUABRIDGE_SAFE_LUA_C_EXCEPTION_HANDLING ----------------------------------------------- **Default: `0` (disabled). Only meaningful when `LUABRIDGE_HAS_EXCEPTIONS` is `1`.** @@ -2304,7 +2309,7 @@ Enable this flag only if you are compiling Lua as C (not as C++), have exception > **Warning:** Enabling this flag introduces a small performance overhead on every registered CFunction call through the library. -6.4 - LUABRIDGE_RAISE_UNREGISTERED_CLASS_USAGE +7.4 - LUABRIDGE_RAISE_UNREGISTERED_CLASS_USAGE ------------------------------------------------ **Default: `1` when exceptions are enabled, `0` otherwise.** @@ -2318,7 +2323,7 @@ Override the default when you need fine-grained control: #include ``` -6.5 - LUABRIDGE_HAS_CXX20_COROUTINES / LUABRIDGE_DISABLE_CXX20_COROUTINES +7.5 - LUABRIDGE_HAS_CXX20_COROUTINES / LUABRIDGE_DISABLE_CXX20_COROUTINES -------------------------------------------------------------------------- **`LUABRIDGE_HAS_CXX20_COROUTINES` - auto-detected, override allowed** @@ -2342,12 +2347,12 @@ You can also override the detection result explicitly: Attempting to use coroutine integration on Lua 5.1, LuaJIT, or Luau will emit a compile-time `#error` unless `LUABRIDGE_DISABLE_COROUTINE_INTEGRATION` is also defined. -6.6 - LUABRIDGE_ENABLE_REFLECT +7.6 - LUABRIDGE_ENABLE_REFLECT ------------------------------- **Default: not defined (disabled)** -When defined, LuaBridge captures the C++ type names of function parameters and return types at compile time using `typeid`. This information is stored alongside the function in the Lua registry and is retrieved by the [Inspection API](#210---inspecting-registrations) to populate `OverloadInfo::returnType` and `ParamInfo::typeName`. +When defined, LuaBridge captures the C++ type names of function parameters and return types at compile time using `typeid`. This information is stored alongside the function in the Lua registry and is retrieved by the [Inspection API](#6---inspecting-registrations) to populate `OverloadInfo::returnType` and `ParamInfo::typeName`. Enable reflection by defining the macro **before** including any LuaBridge header: From 7f92ff6c3c4b3c9cfbc21722661bf3256a0d4e32 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Wed, 8 Apr 2026 01:03:33 +0200 Subject: [PATCH 10/14] Update Manual.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Manual.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Manual.md b/Manual.md index 913e867c..31787e0e 100644 --- a/Manual.md +++ b/Manual.md @@ -671,7 +671,7 @@ Special attention needs to be given to the order (priority) of the overloads, ba ### 2.5.2 - Parameter Name Hints -When `LUABRIDGE_ENABLE_REFLECT` is defined (see [6.6](#76---luabridge-enable-reflect)), LuaBridge captures the C++ type names of function parameters at compile time. You can additionally attach human-readable **parameter name hints** to any function using `luabridge::withHints`. These names are stored alongside the function and become available through the [Inspection API](#6---inspecting-registrations). +When `LUABRIDGE_ENABLE_REFLECT` is defined (see [7.6](#76---luabridge-enable-reflect)), LuaBridge captures the C++ type names of function parameters at compile time. You can additionally attach human-readable **parameter name hints** to any function using `luabridge::withHints`. These names are stored alongside the function and become available through the [Inspection API](#6---inspecting-registrations). ```cpp #define LUABRIDGE_ENABLE_REFLECT From 1e458c218ec87d833e0d00c7de5d9e55955889da Mon Sep 17 00:00:00 2001 From: kunitoki Date: Wed, 8 Apr 2026 01:03:46 +0200 Subject: [PATCH 11/14] Update Manual.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Manual.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Manual.md b/Manual.md index 31787e0e..8b0e7fd4 100644 --- a/Manual.md +++ b/Manual.md @@ -2365,7 +2365,7 @@ Enable reflection by defining the macro **before** including any LuaBridge heade > **Note:** `Inspect.h` unconditionally requires `LUABRIDGE_ENABLE_REFLECT` and will emit a `#error` if the macro is not defined. You may use `withHints` (see [2.5.2](#252---parameter-name-hints)) without including `Inspect.h`. When `LUABRIDGE_ENABLE_REFLECT` is not defined: -* `withHints` parameter name hints are still stored and available through inspection. +* `withHints` parameter name hints are ignored and are not available through inspection. * `OverloadInfo::returnType` and `ParamInfo::typeName` are empty strings. * The `Inspect.h` header cannot be included. From 9d2876486d2644e87a2fb00d4e5e76e2120bcc9d Mon Sep 17 00:00:00 2001 From: kunitoki Date: Wed, 8 Apr 2026 01:04:21 +0200 Subject: [PATCH 12/14] Fix amalgama --- CMakeLists.txt | 2 +- Distribution/LuaBridge/LuaBridge.h | 397 +++++++++++++++++------------ 2 files changed, 232 insertions(+), 167 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1cf6acc8..dc1f1114 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,9 +13,9 @@ find_program (LCOV_EXECUTABLE lcov) find_program (GENHTML_EXECUTABLE genhtml) cmake_dependent_option (LUABRIDGE_TESTING "Build tests" ON "CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR" OFF) -cmake_dependent_option (LUABRIDGE_SANITIZE "Sanitizer to enable (address, undefined, thread)" OFF "LUABRIDGE_TESTING" OFF) cmake_dependent_option (LUABRIDGE_COVERAGE "Enable coverage" OFF "LUABRIDGE_TESTING;FIND_EXECUTABLE;LCOV_EXECUTABLE;GENHTML_EXECUTABLE" OFF) cmake_dependent_option (LUABRIDGE_BENCHMARKS "Build benchmark executable" OFF "CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR" OFF) +set (LUABRIDGE_SANITIZE "" CACHE STRING "Sanitizer to enable (address, undefined, thread)") add_subdirectory (Source) diff --git a/Distribution/LuaBridge/LuaBridge.h b/Distribution/LuaBridge/LuaBridge.h index dfb90ee7..7761c840 100644 --- a/Distribution/LuaBridge/LuaBridge.h +++ b/Distribution/LuaBridge/LuaBridge.h @@ -542,6 +542,27 @@ struct remove_first_type> template using remove_first_type_t = typename remove_first_type::type; +template +struct getter_return_type +{ + using type = void; +}; + +template +struct getter_return_type>> +{ + using type = std::remove_const_t; +}; + +template +struct getter_return_type>> +{ + using type = remove_cvref_t>; +}; + +template +using getter_return_t = typename getter_return_type::type; + } } @@ -3263,6 +3284,11 @@ template ().find_first_of('.')> return reinterpret_cast(0xdad); } +[[nodiscard]] inline const void* getPropTypeKey() noexcept +{ + return reinterpret_cast(0x9e7); +} + [[nodiscard]] inline const void* getCastTableKey() noexcept { return reinterpret_cast(0xca57); @@ -7472,6 +7498,31 @@ inline void add_property_getter(lua_State* L, const char* name, int tableIndex) lua_pop(L, 2); } +inline void add_property_type(lua_State* L, const char* name, const char* typeName, int tableIndex) +{ +#if LUABRIDGE_SAFE_STACK_CHECKS + luaL_checkstack(L, 3, detail::error_lua_stack_overflow); +#endif + + if (!typeName || typeName[0] == '\0') + return; + + tableIndex = lua_absindex(L, tableIndex); + + lua_rawgetp_x(L, tableIndex, getPropTypeKey()); + if (lua_isnil(L, -1)) + { + lua_pop(L, 1); + lua_createtable(L, 0, 4); + lua_pushvalue(L, -1); + lua_rawsetp_x(L, tableIndex, getPropTypeKey()); + } + + lua_pushstring(L, typeName); + rawsetfield(L, -2, name); + lua_pop(L, 1); +} + template struct property_setter; @@ -8966,6 +9017,10 @@ struct container_forwarder // Begin File: Source/LuaBridge/Inspect.h +#if ! LUABRIDGE_ENABLE_REFLECT +#error "This header is only for use when LUABRIDGE_ENABLE_REFLECT is active." +#endif + namespace luabridge { struct NamespaceInspectInfo; @@ -9234,12 +9289,31 @@ inline ClassInspectInfo inspectClassFromStaticTable(lua_State* L, int stIdx) } lua_pop(L, 1); + std::map instPropTypes; + lua_rawgetp_x(L, clIdx, getPropTypeKey()); + if (lua_istable(L, -1)) + { + int ptIdx = lua_absindex(L, -1); + for (const auto& k : instPropget) + { + lua_getfield(L, ptIdx, k.c_str()); + if (lua_isstring(L, -1)) + instPropTypes[k] = lua_tostring(L, -1); + lua_pop(L, 1); + } + } + lua_pop(L, 1); + for (const auto& propName : instPropget) { MemberInfo m; m.name = propName; m.kind = instPropsetReal.count(propName) ? MemberKind::Property : MemberKind::ReadOnlyProperty; - m.overloads.push_back(OverloadInfo{}); + OverloadInfo ov; + auto it = instPropTypes.find(propName); + if (it != instPropTypes.end()) + ov.returnType = it->second; + m.overloads.push_back(std::move(ov)); cls.members.push_back(std::move(m)); } @@ -9293,12 +9367,31 @@ inline ClassInspectInfo inspectClassFromStaticTable(lua_State* L, int stIdx) } lua_pop(L, 1); + std::map stPropTypes; + lua_rawgetp_x(L, mtIdx, getPropTypeKey()); + if (lua_istable(L, -1)) + { + int ptIdx = lua_absindex(L, -1); + for (const auto& k : stPropget) + { + lua_getfield(L, ptIdx, k.c_str()); + if (lua_isstring(L, -1)) + stPropTypes[k] = lua_tostring(L, -1); + lua_pop(L, 1); + } + } + lua_pop(L, 1); + for (const auto& propName : stPropget) { MemberInfo m; m.name = propName; m.kind = stPropsetReal.count(propName) ? MemberKind::StaticProperty : MemberKind::StaticReadOnlyProperty; - m.overloads.push_back(OverloadInfo{}); + OverloadInfo ov; + auto it = stPropTypes.find(propName); + if (it != stPropTypes.end()) + ov.returnType = it->second; + m.overloads.push_back(std::move(ov)); cls.members.push_back(std::move(m)); } @@ -9356,6 +9449,21 @@ inline NamespaceInspectInfo inspectNamespaceTable(lua_State* L, int nsIdx, std:: if (lua_istable(L, -1)) nsPropsetTableIdx = lua_absindex(L, -1); + std::map nsPropTypes; + lua_rawgetp_x(L, nsIdx, getPropTypeKey()); + if (lua_istable(L, -1)) + { + int ptIdx = lua_absindex(L, -1); + for (const auto& k : nsPropget) + { + lua_getfield(L, ptIdx, k.c_str()); + if (lua_isstring(L, -1)) + nsPropTypes[k] = lua_tostring(L, -1); + lua_pop(L, 1); + } + } + lua_pop(L, 1); + for (const auto& propName : nsPropget) { MemberInfo m; @@ -9374,7 +9482,11 @@ inline NamespaceInspectInfo inspectNamespaceTable(lua_State* L, int nsIdx, std:: } m.kind = isReadOnly ? MemberKind::ReadOnlyProperty : MemberKind::Property; - m.overloads.push_back(OverloadInfo{}); + OverloadInfo ov; + auto it = nsPropTypes.find(propName); + if (it != nsPropTypes.end()) + ov.returnType = it->second; + m.overloads.push_back(std::move(ov)); info.freeMembers.push_back(std::move(m)); } @@ -9620,16 +9732,29 @@ class ConsoleVisitor : public InspectVisitor switch (m.kind) { case MemberKind::Property: - indent(); out_ << m.name << ": any;\n"; break; + indent(); + out_ << m.name << ": any;\n"; + break; + case MemberKind::ReadOnlyProperty: - indent(); out_ << "readonly " << m.name << ": any;\n"; break; + indent(); + out_ << "readonly " << m.name << ": any;\n"; + break; + case MemberKind::StaticProperty: - indent(); out_ << "static " << m.name << ": any;\n"; break; + indent(); + out_ << "static " << m.name << ": any;\n"; + break; + case MemberKind::StaticReadOnlyProperty: - indent(); out_ << "static readonly " << m.name << ": any;\n"; break; + indent(); + out_ << "static readonly " << m.name << ": any;\n"; + break; + case MemberKind::Metamethod: break; + case MemberKind::Constructor: for (const auto& ov : m.overloads) { @@ -9637,10 +9762,11 @@ class ConsoleVisitor : public InspectVisitor out_ << "constructor(" << paramStr(ov) << ");\n"; } break; + case MemberKind::Method: case MemberKind::StaticMethod: { - bool isStatic = (m.kind == MemberKind::StaticMethod); + const bool isStatic = (m.kind == MemberKind::StaticMethod); for (std::size_t i = 0; i < m.overloads.size(); ++i) { indent(); @@ -9649,6 +9775,7 @@ class ConsoleVisitor : public InspectVisitor } break; } + default: break; } @@ -9680,7 +9807,7 @@ class LuaLSVisitor : public InspectVisitor for (const auto& ov : m.overloads) { - emitParams(ov.params); + emitParamsTo(out_, ov.params); if (!ov.returnType.empty() && ov.returnType != "void") out_ << "---@return " << luaType(ov.returnType) << "\n"; } @@ -9703,95 +9830,101 @@ class LuaLSVisitor : public InspectVisitor out_ << ", " << cls.baseClasses[i]; } out_ << "\n"; - + methodBuf_.str({}); + methodBuf_.clear(); } void endClass([[maybe_unused]] const ClassInspectInfo& cls) override { - out_ << "local " << curClass_ << " = {}\n\n"; + out_ << "local " << curClass_ << " = {}\n"; + const std::string methods = methodBuf_.str(); + if (!methods.empty()) + out_ << "\n" << methods; + else + out_ << "\n"; curClass_.clear(); } void visitMember(const ClassInspectInfo& cls, const MemberInfo& m) override { + auto propType = [&]() -> std::string { + if (!m.overloads.empty() && !m.overloads[0].returnType.empty()) + return luaType(m.overloads[0].returnType); + return "any"; + }; + switch (m.kind) { case MemberKind::Property: - out_ << "---@field " << m.name << " any\n"; + out_ << "---@field " << m.name << " " << propType() << "\n"; break; + case MemberKind::ReadOnlyProperty: - out_ << "---@field " << m.name << " any # readonly\n"; + out_ << "---@field " << m.name << " " << propType() << " # readonly\n"; break; + case MemberKind::StaticProperty: - out_ << "---@field " << m.name << " any\n"; + out_ << "---@field " << m.name << " " << propType() << "\n"; break; + case MemberKind::StaticReadOnlyProperty: - out_ << "---@field " << m.name << " any # readonly\n"; + out_ << "---@field " << m.name << " " << propType() << " # readonly\n"; break; + case MemberKind::Constructor: { - out_ << "\n"; - for (std::size_t i = 0; i < m.overloads.size(); ++i) - { - const auto& ov = m.overloads[i]; - if (i == 0) - { - emitParams(ov.params); - out_ << "---@return " << cls.name << "\n"; - out_ << "function " << cls.name << ".new(" << paramNames(ov.params) << ") end\n"; - } - else - { - out_ << "---@overload fun(" << overloadParams(ov.params) << "): " << cls.name << "\n"; - } - } + + std::string qname = qualifiedName(cls.name); + for (const auto& ov : m.overloads) + out_ << "---@overload fun(" << overloadParams(ov.params) << "): " << qname << "\n"; break; } + case MemberKind::Method: { - out_ << "\n"; + std::string qname = qualifiedName(cls.name); for (std::size_t i = 0; i < m.overloads.size(); ++i) { const auto& ov = m.overloads[i]; if (i == 0) { - emitParams(ov.params, -true); + emitParamsTo(methodBuf_, ov.params, +true, qname); if (!ov.returnType.empty() && ov.returnType != "void") - out_ << "---@return " << luaType(ov.returnType) << "\n"; - out_ << "function " << cls.name << ":" << m.name - << "(" << paramNames(ov.params) << ") end\n"; + methodBuf_ << "---@return " << luaType(ov.returnType) << "\n"; + methodBuf_ << "function " << qname << ":" << m.name << "(" << paramNames(ov.params) << ") end\n\n"; } else { - out_ << "---@overload fun(self: " << cls.name << ", " << overloadParams(ov.params) << ")" - << (ov.returnType.empty() ? "" : (": " + luaType(ov.returnType))) << "\n"; + methodBuf_ << "---@overload fun(self: " << qname << ", " << overloadParams(ov.params) << ")" + << (ov.returnType.empty() ? "" : (": " + luaType(ov.returnType))) << "\n"; } } break; } + case MemberKind::StaticMethod: { - out_ << "\n"; + std::string qname = qualifiedName(cls.name); for (std::size_t i = 0; i < m.overloads.size(); ++i) { const auto& ov = m.overloads[i]; if (i == 0) { - emitParams(ov.params); + emitParamsTo(methodBuf_, ov.params); if (!ov.returnType.empty() && ov.returnType != "void") - out_ << "---@return " << luaType(ov.returnType) << "\n"; - out_ << "function " << cls.name << "." << m.name - << "(" << paramNames(ov.params) << ") end\n"; + methodBuf_ << "---@return " << luaType(ov.returnType) << "\n"; + methodBuf_ << "function " << qname << "." << m.name << "(" << paramNames(ov.params) << ") end\n\n"; } else { - out_ << "---@overload fun(" << overloadParams(ov.params) << ")" - << (ov.returnType.empty() ? "" : (": " + luaType(ov.returnType))) << "\n"; + methodBuf_ << "---@overload fun(" << overloadParams(ov.params) << ")" + << (ov.returnType.empty() ? "" : (": " + luaType(ov.returnType))) << "\n"; } } break; } + default: break; } @@ -9802,15 +9935,20 @@ true); static std::string luaType(const std::string& cppType) { if (cppType == "void") return "nil"; + if (cppType == "bool") return "boolean"; + if (cppType == "int" || cppType == "long" || cppType == "short" || cppType == "unsigned int" || cppType == "unsigned long" || cppType == "unsigned short" || cppType == "int64_t" || cppType == "uint64_t" || cppType == "int32_t" || cppType == "uint32_t" || cppType == "size_t") return "integer"; + if (cppType == "float" || cppType == "double") return "number"; + if (cppType == "std::string" || cppType == "const char *" || cppType == "const char*") return "string"; + return cppType.empty() ? "any" : cppType; } @@ -9836,6 +9974,7 @@ true); for (std::size_t i = 0; i < params.size(); ++i) { if (i) s += ", "; + const auto& p = params[i]; std::string pname = p.hint.empty() ? ("p" + std::to_string(i + 1)) : p.hint; s += pname + ": " + luaType(p.typeName.empty() ? "any" : p.typeName); @@ -9843,20 +9982,23 @@ true); return s; } - void emitParams(const std::vector& params, bool hasSelf = false) const + static void emitParamsTo(std::ostream& os, const std::vector& params, + bool hasSelf = false, const std::string& selfType = {}) { if (hasSelf) - out_ << "---@param self " << curClass_ << "\n"; + os << "---@param self " << selfType << "\n"; + for (std::size_t i = 0; i < params.size(); ++i) { const auto& p = params[i]; std::string pname = p.hint.empty() ? ("p" + std::to_string(i + 1)) : p.hint; std::string ptype = p.typeName.empty() ? "any" : luaType(p.typeName); - out_ << "---@param " << pname << " " << ptype << "\n"; + os << "---@param " << pname << " " << ptype << "\n"; } } std::ostream& out_; + std::ostringstream methodBuf_; std::string ns_; std::string curClass_; }; @@ -9871,14 +10013,16 @@ class LuaProxyVisitor : public InspectVisitor void beginNamespace(const NamespaceInspectInfo& ns) override { - if (ns.name != "_G") - out_ << "local " << ns.name << " = {}\n\n"; + curNs_ = (ns.name == "_G") ? "" : ns.name; + if (!curNs_.empty()) + out_ << "local " << curNs_ << " = {}\n\n"; } void endNamespace(const NamespaceInspectInfo& ns) override { if (ns.name != "_G") out_ << "return " << ns.name << "\n"; + curNs_.clear(); } void visitFreeMember(const NamespaceInspectInfo& ns, const MemberInfo& m) override @@ -9896,8 +10040,9 @@ class LuaProxyVisitor : public InspectVisitor void beginClass(const ClassInspectInfo& cls) override { curClass_ = cls.name; - out_ << cls.name << " = {}\n"; - out_ << cls.name << ".__index = " << cls.name << "\n\n"; + std::string qname = curNs_.empty() ? cls.name : (curNs_ + "." + cls.name); + out_ << qname << " = {}\n"; + out_ << qname << ".__index = " << qname << "\n\n"; } void endClass([[maybe_unused]] const ClassInspectInfo& cls) override @@ -9911,28 +10056,35 @@ class LuaProxyVisitor : public InspectVisitor if (m.overloads.empty()) return; + std::string qname = curNs_.empty() ? curClass_ : (curNs_ + "." + curClass_); + switch (m.kind) { case MemberKind::Constructor: - out_ << "function " << curClass_ << ".new(" - << paramNames(m.overloads[0].params) << ")\n" - << " return setmetatable({}, " << curClass_ << ")\n" - << "end\n\n"; + + out_ << "setmetatable(" << qname << ", {__call = function(t"; + if (!m.overloads[0].params.empty()) + out_ << ", " << paramNames(m.overloads[0].params); + out_ << ")\n return setmetatable({}, t)\nend})\n\n"; break; + case MemberKind::Method: - out_ << "function " << curClass_ << ":" << m.name - << "(" << paramNames(m.overloads[0].params) << ") end\n"; + out_ << "function " << qname << ":" << m.name + << "(" << paramNames(m.overloads[0].params) << ") end\n\n"; break; + case MemberKind::StaticMethod: - out_ << "function " << curClass_ << "." << m.name - << "(" << paramNames(m.overloads[0].params) << ") end\n"; + out_ << "function " << qname << "." << m.name + << "(" << paramNames(m.overloads[0].params) << ") end\n\n"; break; + case MemberKind::Property: case MemberKind::ReadOnlyProperty: case MemberKind::StaticProperty: case MemberKind::StaticReadOnlyProperty: break; + default: break; } @@ -9951,115 +10103,10 @@ class LuaProxyVisitor : public InspectVisitor } std::ostream& out_; + std::string curNs_; std::string curClass_; }; -class JsonVisitor : public InspectVisitor -{ -public: - explicit JsonVisitor(std::ostream& out) - : out_(out) - { - } - - void beginNamespace(const NamespaceInspectInfo& ns) override - { - if (depth_ == 0) out_ << "{\n"; - indent(); out_ << "\"name\": \"" << escape(ns.name) << "\",\n"; - indent(); out_ << "\"freeMembers\": [],\n"; - indent(); out_ << "\"classes\": [\n"; - ++depth_; - firstClass_ = true; - } - - void endNamespace([[maybe_unused]] const NamespaceInspectInfo& ns) override - { - --depth_; - out_ << "\n"; - indent(); out_ << "]\n"; - if (depth_ == 0) out_ << "}\n"; - } - - void beginClass(const ClassInspectInfo& cls) override - { - if (!firstClass_) out_ << ",\n"; - firstClass_ = false; - indent(); out_ << "{\n"; - ++depth_; - indent(); out_ << "\"name\": \"" << escape(cls.name) << "\",\n"; - indent(); out_ << "\"bases\": ["; - for (std::size_t i = 0; i < cls.baseClasses.size(); ++i) - { - if (i) out_ << ", "; - out_ << "\"" << escape(cls.baseClasses[i]) << "\""; - } - out_ << "],\n"; - indent(); out_ << "\"members\": [\n"; - ++depth_; - firstMember_ = true; - } - - void endClass([[maybe_unused]] const ClassInspectInfo& cls) override - { - --depth_; - out_ << "\n"; - indent(); out_ << "]\n"; - --depth_; - indent(); out_ << "}"; - } - - void visitMember([[maybe_unused]] const ClassInspectInfo& cls, const MemberInfo& m) override - { - if (!firstMember_) out_ << ",\n"; - firstMember_ = false; - indent(); - out_ << "{ \"name\": \"" << escape(m.name) << "\"" - << ", \"kind\": \"" << kindStr(m.kind) << "\"" - << ", \"overloads\": " << m.overloads.size() - << " }"; - } - -private: - void indent() const - { - for (int i = 0; i < depth_; ++i) - out_ << " "; - } - - static std::string escape(const std::string& s) - { - std::string out; - for (char c : s) - { - if (c == '"') out += "\\\""; - else if (c == '\\') out += "\\\\"; - else out += c; - } - return out; - } - - static const char* kindStr(MemberKind k) - { - switch (k) - { - case MemberKind::Method: return "method"; - case MemberKind::StaticMethod: return "static_method"; - case MemberKind::Property: return "property"; - case MemberKind::ReadOnlyProperty: return "readonly_property"; - case MemberKind::StaticProperty: return "static_property"; - case MemberKind::StaticReadOnlyProperty: return "static_readonly_property"; - case MemberKind::Constructor: return "constructor"; - case MemberKind::Metamethod: return "metamethod"; - default: return "unknown"; - } - } - - std::ostream& out_; - int depth_ = 0; - bool firstClass_ = true; - bool firstMember_ = true; -}; - class LuaTableVisitor : public InspectVisitor { public: @@ -12951,6 +12998,9 @@ class Namespace : public detail::Registrar detail::push_property_readonly(L, name); detail::add_property_setter(L, name, -2); + using RetType = detail::getter_return_t; + detail::add_property_type(L, name, std::string(detail::typeName()).c_str(), -1); + return *this; } @@ -12966,6 +13016,9 @@ class Namespace : public detail::Registrar detail::push_property_setter(L, std::move(set), name); detail::add_property_setter(L, name, -2); + using RetType = detail::getter_return_t; + detail::add_property_type(L, name, std::string(detail::typeName()).c_str(), -1); + return *this; } @@ -13141,6 +13194,9 @@ class Namespace : public detail::Registrar detail::push_property_readonly(L, name); detail::add_property_setter(L, name, -3); + using RetType = detail::getter_return_t; + detail::add_property_type(L, name, std::string(detail::typeName()).c_str(), -2); + return *this; } @@ -13158,6 +13214,9 @@ class Namespace : public detail::Registrar detail::push_class_property_setter(L, std::move(setter), name); detail::add_property_setter(L, name, -3); + using RetType = detail::getter_return_t; + detail::add_property_type(L, name, std::string(detail::typeName()).c_str(), -2); + return *this; } @@ -14052,6 +14111,9 @@ class Namespace : public detail::Registrar detail::push_property_readonly(L, name); detail::add_property_setter(L, name, -2); + using RetType = detail::getter_return_t; + detail::add_property_type(L, name, std::string(detail::typeName()).c_str(), -1); + return *this; } @@ -14074,6 +14136,9 @@ class Namespace : public detail::Registrar detail::push_property_setter(L, std::move(setter), name); detail::add_property_setter(L, name, -2); + using RetType = detail::getter_return_t; + detail::add_property_type(L, name, std::string(detail::typeName()).c_str(), -1); + return *this; } From e03527920ec85b1a60326a5eb98011f12ff15593 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Apr 2026 23:11:44 +0000 Subject: [PATCH 13/14] fix: LuaTableVisitor nested namespace handling with state stack Replace flat freeMemberIdx_/classIdx_/subNsIdx_ member variables with a std::vector stack in LuaTableVisitor, and implement endNamespace() to properly insert each sub-namespace table into the parent's subNamespaces array and pop it from the Lua stack. This fixes both the incorrect result structure and the Lua stack leak that occurred when inspecting namespaces with sub-namespaces. Also adds LuaTableVisitorHandlesNestedNamespaces test that verifies the correct output structure and confirms the Lua stack is balanced after use. Apply same fix to Distribution/LuaBridge/LuaBridge.h amalgam. Agent-Logs-Url: https://github.com/kunitoki/LuaBridge3/sessions/e82019ab-faa5-48bb-8798-f4cdadde82f2 Co-authored-by: kunitoki <707032+kunitoki@users.noreply.github.com> --- Distribution/LuaBridge/LuaBridge.h | 33 +++++++++++----- Source/LuaBridge/Inspect.h | 33 +++++++++++----- Tests/Source/InspectTests.cpp | 60 ++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 18 deletions(-) diff --git a/Distribution/LuaBridge/LuaBridge.h b/Distribution/LuaBridge/LuaBridge.h index 7761c840..7b789144 100644 --- a/Distribution/LuaBridge/LuaBridge.h +++ b/Distribution/LuaBridge/LuaBridge.h @@ -10123,27 +10123,37 @@ class LuaTableVisitor : public InspectVisitor lua_newtable(L_); lua_setfield(L_, -2, "freeMembers"); - freeMemberIdx_ = 1; lua_newtable(L_); lua_setfield(L_, -2, "classes"); - classIdx_ = 1; lua_newtable(L_); lua_setfield(L_, -2, "subNamespaces"); - subNsIdx_ = 1; + + nsStack_.push_back({}); } void endNamespace([[maybe_unused]] const NamespaceInspectInfo& ns) override { - + nsStack_.pop_back(); + + if (!nsStack_.empty()) + { + // Sub-namespace table is on top; store it in the parent's "subNamespaces" array. + // Stack: [..., parent_ns_table, sub_ns_table] + lua_getfield(L_, -2, "subNamespaces"); // [..., parent_ns_table, sub_ns_table, subNS_array] + lua_pushvalue(L_, -2); // [..., parent_ns_table, sub_ns_table, subNS_array, sub_ns_table] + lua_rawseti(L_, -2, nsStack_.back().subNsIdx++); // store sub_ns_table into subNS_array + lua_pop(L_, 2); // pop subNS_array + sub_ns_table + // Stack: [..., parent_ns_table] + } } void visitFreeMember([[maybe_unused]] const NamespaceInspectInfo& ns, const MemberInfo& m) override { lua_getfield(L_, -1, "freeMembers"); pushMemberInfo(m); - lua_rawseti(L_, -2, freeMemberIdx_++); + lua_rawseti(L_, -2, nsStack_.back().freeMemberIdx++); lua_pop(L_, 1); } @@ -10171,7 +10181,7 @@ class LuaTableVisitor : public InspectVisitor lua_getfield(L_, -2, "classes"); lua_pushvalue(L_, -2); - lua_rawseti(L_, -2, classIdx_++); + lua_rawseti(L_, -2, nsStack_.back().classIdx++); lua_pop(L_, 2); } @@ -10184,6 +10194,13 @@ class LuaTableVisitor : public InspectVisitor } private: + struct NsState + { + int freeMemberIdx = 1; + int classIdx = 1; + int subNsIdx = 1; + }; + void pushMemberInfo(const MemberInfo& m) { lua_newtable(L_); @@ -10235,9 +10252,7 @@ class LuaTableVisitor : public InspectVisitor } lua_State* L_; - int freeMemberIdx_ = 1; - int classIdx_ = 1; - int subNsIdx_ = 1; + std::vector nsStack_; int memberIdx_ = 1; }; diff --git a/Source/LuaBridge/Inspect.h b/Source/LuaBridge/Inspect.h index 29b4d5b5..75bf145f 100644 --- a/Source/LuaBridge/Inspect.h +++ b/Source/LuaBridge/Inspect.h @@ -1271,27 +1271,37 @@ class LuaTableVisitor : public InspectVisitor lua_newtable(L_); lua_setfield(L_, -2, "freeMembers"); - freeMemberIdx_ = 1; lua_newtable(L_); lua_setfield(L_, -2, "classes"); - classIdx_ = 1; lua_newtable(L_); lua_setfield(L_, -2, "subNamespaces"); - subNsIdx_ = 1; + + nsStack_.push_back({}); } void endNamespace([[maybe_unused]] const NamespaceInspectInfo& ns) override { - // namespace table stays on stack as the result + nsStack_.pop_back(); + + if (!nsStack_.empty()) + { + // Sub-namespace table is on top; store it in the parent's "subNamespaces" array. + // Stack: [..., parent_ns_table, sub_ns_table] + lua_getfield(L_, -2, "subNamespaces"); // [..., parent_ns_table, sub_ns_table, subNS_array] + lua_pushvalue(L_, -2); // [..., parent_ns_table, sub_ns_table, subNS_array, sub_ns_table] + lua_rawseti(L_, -2, nsStack_.back().subNsIdx++); // store sub_ns_table into subNS_array + lua_pop(L_, 2); // pop subNS_array + sub_ns_table + // Stack: [..., parent_ns_table] + } } void visitFreeMember([[maybe_unused]] const NamespaceInspectInfo& ns, const MemberInfo& m) override { lua_getfield(L_, -1, "freeMembers"); pushMemberInfo(m); - lua_rawseti(L_, -2, freeMemberIdx_++); + lua_rawseti(L_, -2, nsStack_.back().freeMemberIdx++); lua_pop(L_, 1); } @@ -1319,7 +1329,7 @@ class LuaTableVisitor : public InspectVisitor // class table is on top; store it in the namespace's "classes" array lua_getfield(L_, -2, "classes"); lua_pushvalue(L_, -2); - lua_rawseti(L_, -2, classIdx_++); + lua_rawseti(L_, -2, nsStack_.back().classIdx++); lua_pop(L_, 2); // pop classes table + class table } @@ -1332,6 +1342,13 @@ class LuaTableVisitor : public InspectVisitor } private: + struct NsState + { + int freeMemberIdx = 1; + int classIdx = 1; + int subNsIdx = 1; + }; + void pushMemberInfo(const MemberInfo& m) { lua_newtable(L_); @@ -1384,9 +1401,7 @@ class LuaTableVisitor : public InspectVisitor } lua_State* L_; - int freeMemberIdx_ = 1; - int classIdx_ = 1; - int subNsIdx_ = 1; + std::vector nsStack_; int memberIdx_ = 1; }; diff --git a/Tests/Source/InspectTests.cpp b/Tests/Source/InspectTests.cpp index 268f0a9f..c3fc9049 100644 --- a/Tests/Source/InspectTests.cpp +++ b/Tests/Source/InspectTests.cpp @@ -343,6 +343,66 @@ TEST_F(InspectTests, LuaTableVisitorProducesTable) EXPECT_EQ(topBefore, lua_gettop(L)); } +TEST_F(InspectTests, LuaTableVisitorHandlesNestedNamespaces) +{ + struct OuterCls { void outerMethod() {} }; + struct InnerCls { void innerMethod() {} }; + + luabridge::getGlobalNamespace(L) + .beginNamespace("Outer") + .beginClass("OuterCls") + .addFunction("outerMethod", &OuterCls::outerMethod) + .endClass() + .beginNamespace("Inner") + .beginClass("InnerCls") + .addFunction("innerMethod", &InnerCls::innerMethod) + .endClass() + .endNamespace() + .endNamespace(); + + int topBefore = lua_gettop(L); + luabridge::inspectToLua(L, "Outer"); + + // Stack must grow by exactly 1 + ASSERT_EQ(topBefore + 1, lua_gettop(L)); + ASSERT_TRUE(lua_istable(L, -1)); + + // Outer namespace name + lua_getfield(L, -1, "name"); + EXPECT_STREQ("Outer", lua_tostring(L, -1)); + lua_pop(L, 1); + + // Outer classes array contains OuterCls + lua_getfield(L, -1, "classes"); + EXPECT_TRUE(lua_istable(L, -1)); + EXPECT_EQ(1, luabridge::get_length(L, -1)); + lua_pop(L, 1); + + // subNamespaces array contains the Inner namespace + lua_getfield(L, -1, "subNamespaces"); + EXPECT_TRUE(lua_istable(L, -1)); + EXPECT_EQ(1, luabridge::get_length(L, -1)); + + // Inspect the Inner sub-namespace table + lua_rawgeti(L, -1, 1); + ASSERT_TRUE(lua_istable(L, -1)); + + lua_getfield(L, -1, "name"); + EXPECT_STREQ("Inner", lua_tostring(L, -1)); + lua_pop(L, 1); + + lua_getfield(L, -1, "classes"); + EXPECT_TRUE(lua_istable(L, -1)); + EXPECT_EQ(1, luabridge::get_length(L, -1)); + lua_pop(L, 1); + + lua_pop(L, 1); // pop Inner table + lua_pop(L, 1); // pop subNamespaces table + + lua_pop(L, 1); // pop result table + EXPECT_EQ(topBefore, lua_gettop(L)); +} + TEST_F(InspectTests, LuaLSVisitorProducesAnnotations) { struct Cls { int getValue() const { return 0; } }; From c0c14d7be044c2ece373d6ca93f5444b5206829e Mon Sep 17 00:00:00 2001 From: kunitoki Date: Wed, 8 Apr 2026 01:59:22 +0200 Subject: [PATCH 14/14] Updated headers and manual --- Distribution/LuaBridge/LuaBridge.h | 178 +++---------------------- Manual.md | 23 +++- Source/LuaBridge/Inspect.h | 203 +++-------------------------- Tests/Source/InspectTests.cpp | 89 ------------- 4 files changed, 53 insertions(+), 440 deletions(-) diff --git a/Distribution/LuaBridge/LuaBridge.h b/Distribution/LuaBridge/LuaBridge.h index 7b789144..bcd226c7 100644 --- a/Distribution/LuaBridge/LuaBridge.h +++ b/Distribution/LuaBridge/LuaBridge.h @@ -9017,10 +9017,6 @@ struct container_forwarder // Begin File: Source/LuaBridge/Inspect.h -#if ! LUABRIDGE_ENABLE_REFLECT -#error "This header is only for use when LUABRIDGE_ENABLE_REFLECT is active." -#endif - namespace luabridge { struct NamespaceInspectInfo; @@ -9551,6 +9547,7 @@ inline NamespaceInspectInfo inspectNamespaceTable(lua_State* L, int nsIdx, std:: template [[nodiscard]] ClassInspectInfo inspect(lua_State* L) { +#if LUABRIDGE_ENABLE_REFLECT lua_rawgetp_x(L, LUA_REGISTRYINDEX, detail::getClassRegistryKey()); if (!lua_istable(L, -1)) @@ -9580,10 +9577,15 @@ template lua_pop(L, 1); return result; +#else + unused(L); + return {}; +#endif } [[nodiscard]] inline NamespaceInspectInfo inspectNamespace(lua_State* L, const char* namespaceName = nullptr) { +#if LUABRIDGE_ENABLE_REFLECT if (namespaceName == nullptr || namespaceName[0] == '\0') { lua_getglobal(L, "_G"); @@ -9635,12 +9637,20 @@ template auto result = detail::inspectNamespaceTable(L, -1, label); lua_pop(L, 1); return result; +#else + unused(L, namespaceName); + return {}; +#endif } inline void inspectAccept(lua_State* L, const char* namespaceName, InspectVisitor& visitor) { +#if LUABRIDGE_ENABLE_REFLECT auto ns = inspectNamespace(L, namespaceName); accept(ns, visitor); +#else + unused(L, namespaceName, visitor); +#endif } class ConsoleVisitor : public InspectVisitor @@ -10107,167 +10117,15 @@ class LuaProxyVisitor : public InspectVisitor std::string curClass_; }; -class LuaTableVisitor : public InspectVisitor -{ -public: - explicit LuaTableVisitor(lua_State* L) - : L_(L) - { - } - - void beginNamespace(const NamespaceInspectInfo& ns) override - { - lua_newtable(L_); - lua_pushstring(L_, ns.name.c_str()); - lua_setfield(L_, -2, "name"); - - lua_newtable(L_); - lua_setfield(L_, -2, "freeMembers"); - - lua_newtable(L_); - lua_setfield(L_, -2, "classes"); - - lua_newtable(L_); - lua_setfield(L_, -2, "subNamespaces"); - - nsStack_.push_back({}); - } - - void endNamespace([[maybe_unused]] const NamespaceInspectInfo& ns) override - { - nsStack_.pop_back(); - - if (!nsStack_.empty()) - { - // Sub-namespace table is on top; store it in the parent's "subNamespaces" array. - // Stack: [..., parent_ns_table, sub_ns_table] - lua_getfield(L_, -2, "subNamespaces"); // [..., parent_ns_table, sub_ns_table, subNS_array] - lua_pushvalue(L_, -2); // [..., parent_ns_table, sub_ns_table, subNS_array, sub_ns_table] - lua_rawseti(L_, -2, nsStack_.back().subNsIdx++); // store sub_ns_table into subNS_array - lua_pop(L_, 2); // pop subNS_array + sub_ns_table - // Stack: [..., parent_ns_table] - } - } - - void visitFreeMember([[maybe_unused]] const NamespaceInspectInfo& ns, const MemberInfo& m) override - { - lua_getfield(L_, -1, "freeMembers"); - pushMemberInfo(m); - lua_rawseti(L_, -2, nsStack_.back().freeMemberIdx++); - lua_pop(L_, 1); - } - - void beginClass(const ClassInspectInfo& cls) override - { - lua_newtable(L_); - lua_pushstring(L_, cls.name.c_str()); - lua_setfield(L_, -2, "name"); - - lua_newtable(L_); - for (std::size_t i = 0; i < cls.baseClasses.size(); ++i) - { - lua_pushstring(L_, cls.baseClasses[i].c_str()); - lua_rawseti(L_, -2, static_cast(i + 1)); - } - lua_setfield(L_, -2, "bases"); - - lua_newtable(L_); - lua_setfield(L_, -2, "members"); - memberIdx_ = 1; - } - - void endClass([[maybe_unused]] const ClassInspectInfo& cls) override - { - - lua_getfield(L_, -2, "classes"); - lua_pushvalue(L_, -2); - lua_rawseti(L_, -2, nsStack_.back().classIdx++); - lua_pop(L_, 2); - } - - void visitMember([[maybe_unused]] const ClassInspectInfo& cls, const MemberInfo& m) override - { - lua_getfield(L_, -1, "members"); - pushMemberInfo(m); - lua_rawseti(L_, -2, memberIdx_++); - lua_pop(L_, 1); - } - -private: - struct NsState - { - int freeMemberIdx = 1; - int classIdx = 1; - int subNsIdx = 1; - }; - - void pushMemberInfo(const MemberInfo& m) - { - lua_newtable(L_); - lua_pushstring(L_, m.name.c_str()); - lua_setfield(L_, -2, "name"); - lua_pushstring(L_, kindStr(m.kind)); - lua_setfield(L_, -2, "kind"); - lua_pushinteger(L_, static_cast(m.overloads.size())); - lua_setfield(L_, -2, "overloads"); - - lua_newtable(L_); - for (std::size_t i = 0; i < m.overloads.size(); ++i) - { - const auto& ov = m.overloads[i]; - lua_newtable(L_); - lua_pushstring(L_, ov.returnType.c_str()); - lua_setfield(L_, -2, "returnType"); - - lua_newtable(L_); - for (std::size_t j = 0; j < ov.params.size(); ++j) - { - lua_newtable(L_); - lua_pushstring(L_, ov.params[j].typeName.c_str()); - lua_setfield(L_, -2, "type"); - lua_pushstring(L_, ov.params[j].hint.c_str()); - lua_setfield(L_, -2, "hint"); - lua_rawseti(L_, -2, static_cast(j + 1)); - } - lua_setfield(L_, -2, "params"); - lua_rawseti(L_, -2, static_cast(i + 1)); - } - lua_setfield(L_, -2, "overloadDetails"); - } - - static const char* kindStr(MemberKind k) - { - switch (k) - { - case MemberKind::Method: return "method"; - case MemberKind::StaticMethod: return "static_method"; - case MemberKind::Property: return "property"; - case MemberKind::ReadOnlyProperty: return "readonly_property"; - case MemberKind::StaticProperty: return "static_property"; - case MemberKind::StaticReadOnlyProperty: return "static_readonly_property"; - case MemberKind::Constructor: return "constructor"; - case MemberKind::Metamethod: return "metamethod"; - default: return "unknown"; - } - } - - lua_State* L_; - std::vector nsStack_; - int memberIdx_ = 1; -}; - inline void inspectPrint(lua_State* L, const char* namespaceName = nullptr, std::ostream& stream = std::cerr) { +#if LUABRIDGE_ENABLE_REFLECT auto ns = inspectNamespace(L, namespaceName); ConsoleVisitor v(stream); accept(ns, v); -} - -inline void inspectToLua(lua_State* L, const char* namespaceName = nullptr) -{ - auto ns = inspectNamespace(L, namespaceName); - LuaTableVisitor v(L); - accept(ns, v); +#else + unused(L, namespaceName, stream); +#endif } } diff --git a/Manual.md b/Manual.md index 8b0e7fd4..ec640145 100644 --- a/Manual.md +++ b/Manual.md @@ -676,7 +676,7 @@ When `LUABRIDGE_ENABLE_REFLECT` is defined (see [7.6](#76---luabridge-enable-ref ```cpp #define LUABRIDGE_ENABLE_REFLECT #include -#include +#include // source headers only; omit when using the amalgama struct Enemy { @@ -2109,12 +2109,19 @@ luabridge::getGlobalNamespace (L) LuaBridge3 can introspect its own registration tables at runtime, making it straightforward to generate documentation, build IDE auto-complete databases, produce TypeScript type declarations, or validate that a binding matches an expected schema. -The inspection API lives in the **optional** header ``, which must be included separately and **requires** `LUABRIDGE_ENABLE_REFLECT` to be defined (see [7.6](#76---luabridge-enable-reflect)). +The inspection API lives in the **optional** header ``. When using the individual source headers, it must be included separately after ``. When using the single-file amalgama (`Distribution/LuaBridge/LuaBridge.h`), `Inspect.h` is already embedded and no separate include is needed. + +Define `LUABRIDGE_ENABLE_REFLECT` before any LuaBridge header to enable C++ type-name capture (see [7.6](#76---luabridge-enable-reflect)). The inspection API works without it — type names will simply be empty strings. ```cpp -#define LUABRIDGE_ENABLE_REFLECT +// Source headers: include Inspect.h separately (optional) +#define LUABRIDGE_ENABLE_REFLECT // optional: enables type-name capture #include #include + +// Amalgama: Inspect.h is already embedded — no separate include needed +#define LUABRIDGE_ENABLE_REFLECT // optional: enables type-name capture +#include ``` 6.1 - inspect\ and inspectNamespace @@ -2357,17 +2364,21 @@ When defined, LuaBridge captures the C++ type names of function parameters and r Enable reflection by defining the macro **before** including any LuaBridge header: ```cpp +// Source headers #define LUABRIDGE_ENABLE_REFLECT #include #include // optional: only needed when using the inspection API + +// Amalgama (Inspect.h is already embedded — no separate include needed) +#define LUABRIDGE_ENABLE_REFLECT +#include ``` -> **Note:** `Inspect.h` unconditionally requires `LUABRIDGE_ENABLE_REFLECT` and will emit a `#error` if the macro is not defined. You may use `withHints` (see [2.5.2](#252---parameter-name-hints)) without including `Inspect.h`. +> **Note:** `Inspect.h` does **not** require `LUABRIDGE_ENABLE_REFLECT` — it can always be included. Without the macro the inspection API still works; it just reports empty strings for type names. You may also use `withHints` (see [2.5.2](#252---parameter-name-hints)) without including `Inspect.h`. When `LUABRIDGE_ENABLE_REFLECT` is not defined: * `withHints` parameter name hints are ignored and are not available through inspection. * `OverloadInfo::returnType` and `ParamInfo::typeName` are empty strings. -* The `Inspect.h` header cannot be included. Appendix - API Reference ======================== @@ -2809,7 +2820,7 @@ int lua_resume_x(lua_State* L, lua_State* from, int nargs, int* nresults = nullp bool lua_isyieldable_x(lua_State* L); ``` -Registration Inspection (requires `LUABRIDGE_ENABLE_REFLECT`, ``) +Registration Inspection (``, enhanced by `LUABRIDGE_ENABLE_REFLECT`) --------------------------------------------------------------------------------------- ```cpp diff --git a/Source/LuaBridge/Inspect.h b/Source/LuaBridge/Inspect.h index 75bf145f..acf5013f 100644 --- a/Source/LuaBridge/Inspect.h +++ b/Source/LuaBridge/Inspect.h @@ -4,10 +4,6 @@ #pragma once -#if ! LUABRIDGE_ENABLE_REFLECT -#error "This header is only for use when LUABRIDGE_ENABLE_REFLECT is active." -#endif - #include "detail/CFunctions.h" #include "detail/ClassInfo.h" #include "detail/LuaHelpers.h" @@ -627,6 +623,7 @@ inline NamespaceInspectInfo inspectNamespaceTable(lua_State* L, int nsIdx, std:: template [[nodiscard]] ClassInspectInfo inspect(lua_State* L) { +#if LUABRIDGE_ENABLE_REFLECT // Load the class table (cl) directly from the Lua registry lua_rawgetp_x(L, LUA_REGISTRYINDEX, detail::getClassRegistryKey()); if (!lua_istable(L, -1)) @@ -664,6 +661,10 @@ template // Stack: ..., cl lua_pop(L, 1); // pop cl return result; +#else + unused(L); + return {}; +#endif } //================================================================================================= @@ -677,6 +678,7 @@ template */ [[nodiscard]] inline NamespaceInspectInfo inspectNamespace(lua_State* L, const char* namespaceName = nullptr) { +#if LUABRIDGE_ENABLE_REFLECT if (namespaceName == nullptr || namespaceName[0] == '\0') { lua_getglobal(L, "_G"); @@ -729,6 +731,10 @@ template auto result = detail::inspectNamespaceTable(L, -1, label); lua_pop(L, 1); return result; +#else + unused(L, namespaceName); + return {}; +#endif } //================================================================================================= @@ -737,8 +743,12 @@ template */ inline void inspectAccept(lua_State* L, const char* namespaceName, InspectVisitor& visitor) { +#if LUABRIDGE_ENABLE_REFLECT auto ns = inspectNamespace(L, namespaceName); accept(ns, visitor); +#else + unused(L, namespaceName, visitor); +#endif } //================================================================================================= @@ -1238,173 +1248,6 @@ class LuaProxyVisitor : public InspectVisitor std::string curClass_; }; -//================================================================================================= -/** - * @brief Visitor that builds a structured Lua table on the Lua stack. - * - * After calling accept() with this visitor, the stack top is a table with structure: - * @code - * { - * name = "MyNS", - * freeMembers = { {name=…, kind=…, overloads=…}, … }, - * classes = { - * { name=…, bases={…}, members={ {name=…, kind=…, overloads=N}, … } }, - * … - * }, - * subNamespaces = { … } - * } - * @endcode - */ -class LuaTableVisitor : public InspectVisitor -{ -public: - explicit LuaTableVisitor(lua_State* L) - : L_(L) - { - } - - void beginNamespace(const NamespaceInspectInfo& ns) override - { - lua_newtable(L_); // the namespace table - lua_pushstring(L_, ns.name.c_str()); - lua_setfield(L_, -2, "name"); - - lua_newtable(L_); - lua_setfield(L_, -2, "freeMembers"); - - lua_newtable(L_); - lua_setfield(L_, -2, "classes"); - - lua_newtable(L_); - lua_setfield(L_, -2, "subNamespaces"); - - nsStack_.push_back({}); - } - - void endNamespace([[maybe_unused]] const NamespaceInspectInfo& ns) override - { - nsStack_.pop_back(); - - if (!nsStack_.empty()) - { - // Sub-namespace table is on top; store it in the parent's "subNamespaces" array. - // Stack: [..., parent_ns_table, sub_ns_table] - lua_getfield(L_, -2, "subNamespaces"); // [..., parent_ns_table, sub_ns_table, subNS_array] - lua_pushvalue(L_, -2); // [..., parent_ns_table, sub_ns_table, subNS_array, sub_ns_table] - lua_rawseti(L_, -2, nsStack_.back().subNsIdx++); // store sub_ns_table into subNS_array - lua_pop(L_, 2); // pop subNS_array + sub_ns_table - // Stack: [..., parent_ns_table] - } - } - - void visitFreeMember([[maybe_unused]] const NamespaceInspectInfo& ns, const MemberInfo& m) override - { - lua_getfield(L_, -1, "freeMembers"); - pushMemberInfo(m); - lua_rawseti(L_, -2, nsStack_.back().freeMemberIdx++); - lua_pop(L_, 1); - } - - void beginClass(const ClassInspectInfo& cls) override - { - lua_newtable(L_); - lua_pushstring(L_, cls.name.c_str()); - lua_setfield(L_, -2, "name"); - - lua_newtable(L_); - for (std::size_t i = 0; i < cls.baseClasses.size(); ++i) - { - lua_pushstring(L_, cls.baseClasses[i].c_str()); - lua_rawseti(L_, -2, static_cast(i + 1)); - } - lua_setfield(L_, -2, "bases"); - - lua_newtable(L_); - lua_setfield(L_, -2, "members"); - memberIdx_ = 1; - } - - void endClass([[maybe_unused]] const ClassInspectInfo& cls) override - { - // class table is on top; store it in the namespace's "classes" array - lua_getfield(L_, -2, "classes"); - lua_pushvalue(L_, -2); - lua_rawseti(L_, -2, nsStack_.back().classIdx++); - lua_pop(L_, 2); // pop classes table + class table - } - - void visitMember([[maybe_unused]] const ClassInspectInfo& cls, const MemberInfo& m) override - { - lua_getfield(L_, -1, "members"); - pushMemberInfo(m); - lua_rawseti(L_, -2, memberIdx_++); - lua_pop(L_, 1); - } - -private: - struct NsState - { - int freeMemberIdx = 1; - int classIdx = 1; - int subNsIdx = 1; - }; - - void pushMemberInfo(const MemberInfo& m) - { - lua_newtable(L_); - lua_pushstring(L_, m.name.c_str()); - lua_setfield(L_, -2, "name"); - lua_pushstring(L_, kindStr(m.kind)); - lua_setfield(L_, -2, "kind"); - lua_pushinteger(L_, static_cast(m.overloads.size())); - lua_setfield(L_, -2, "overloads"); - - // Emit overload details when type info is available - lua_newtable(L_); - for (std::size_t i = 0; i < m.overloads.size(); ++i) - { - const auto& ov = m.overloads[i]; - lua_newtable(L_); - lua_pushstring(L_, ov.returnType.c_str()); - lua_setfield(L_, -2, "returnType"); - - lua_newtable(L_); - for (std::size_t j = 0; j < ov.params.size(); ++j) - { - lua_newtable(L_); - lua_pushstring(L_, ov.params[j].typeName.c_str()); - lua_setfield(L_, -2, "type"); - lua_pushstring(L_, ov.params[j].hint.c_str()); - lua_setfield(L_, -2, "hint"); - lua_rawseti(L_, -2, static_cast(j + 1)); - } - lua_setfield(L_, -2, "params"); - lua_rawseti(L_, -2, static_cast(i + 1)); - } - lua_setfield(L_, -2, "overloadDetails"); - } - - static const char* kindStr(MemberKind k) - { - switch (k) - { - case MemberKind::Method: return "method"; - case MemberKind::StaticMethod: return "static_method"; - case MemberKind::Property: return "property"; - case MemberKind::ReadOnlyProperty: return "readonly_property"; - case MemberKind::StaticProperty: return "static_property"; - case MemberKind::StaticReadOnlyProperty: return "static_readonly_property"; - case MemberKind::Constructor: return "constructor"; - case MemberKind::Metamethod: return "metamethod"; - default: return "unknown"; - } - } - - lua_State* L_; - std::vector nsStack_; - int memberIdx_ = 1; -}; - //================================================================================================= /** * @brief Print a TypeScript-style inspection of a namespace to an ostream. @@ -1413,23 +1256,13 @@ class LuaTableVisitor : public InspectVisitor */ inline void inspectPrint(lua_State* L, const char* namespaceName = nullptr, std::ostream& stream = std::cerr) { +#if LUABRIDGE_ENABLE_REFLECT auto ns = inspectNamespace(L, namespaceName); ConsoleVisitor v(stream); accept(ns, v); -} - -//================================================================================================= -/** - * @brief Push a structured Lua table describing a namespace onto the Lua stack. - * - * Convenience wrapper around LuaTableVisitor. - * Pushes exactly 1 value (the result table). - */ -inline void inspectToLua(lua_State* L, const char* namespaceName = nullptr) -{ - auto ns = inspectNamespace(L, namespaceName); - LuaTableVisitor v(L); - accept(ns, v); +#else + unused(L, namespaceName, stream); +#endif } } // namespace luabridge diff --git a/Tests/Source/InspectTests.cpp b/Tests/Source/InspectTests.cpp index c3fc9049..2dc469ef 100644 --- a/Tests/Source/InspectTests.cpp +++ b/Tests/Source/InspectTests.cpp @@ -314,95 +314,6 @@ TEST_F(InspectTests, ConsoleVisitorProducesOutput) EXPECT_NE(std::string::npos, ss.str().find("Cls")); } -TEST_F(InspectTests, LuaTableVisitorProducesTable) -{ - struct Cls { void m() {} }; - luabridge::getGlobalNamespace(L) - .beginNamespace("TblTest") - .beginClass("Cls") - .addFunction("m", &Cls::m) - .endClass() - .endNamespace(); - - int topBefore = lua_gettop(L); - luabridge::inspectToLua(L, "TblTest"); - - ASSERT_EQ(topBefore + 1, lua_gettop(L)); - ASSERT_TRUE(lua_istable(L, -1)); - - lua_getfield(L, -1, "name"); - EXPECT_STREQ("TblTest", lua_tostring(L, -1)); - lua_pop(L, 1); - - lua_getfield(L, -1, "classes"); - EXPECT_TRUE(lua_istable(L, -1)); - EXPECT_EQ(1, luabridge::get_length(L, -1)); - lua_pop(L, 1); - - lua_pop(L, 1); // pop result table - EXPECT_EQ(topBefore, lua_gettop(L)); -} - -TEST_F(InspectTests, LuaTableVisitorHandlesNestedNamespaces) -{ - struct OuterCls { void outerMethod() {} }; - struct InnerCls { void innerMethod() {} }; - - luabridge::getGlobalNamespace(L) - .beginNamespace("Outer") - .beginClass("OuterCls") - .addFunction("outerMethod", &OuterCls::outerMethod) - .endClass() - .beginNamespace("Inner") - .beginClass("InnerCls") - .addFunction("innerMethod", &InnerCls::innerMethod) - .endClass() - .endNamespace() - .endNamespace(); - - int topBefore = lua_gettop(L); - luabridge::inspectToLua(L, "Outer"); - - // Stack must grow by exactly 1 - ASSERT_EQ(topBefore + 1, lua_gettop(L)); - ASSERT_TRUE(lua_istable(L, -1)); - - // Outer namespace name - lua_getfield(L, -1, "name"); - EXPECT_STREQ("Outer", lua_tostring(L, -1)); - lua_pop(L, 1); - - // Outer classes array contains OuterCls - lua_getfield(L, -1, "classes"); - EXPECT_TRUE(lua_istable(L, -1)); - EXPECT_EQ(1, luabridge::get_length(L, -1)); - lua_pop(L, 1); - - // subNamespaces array contains the Inner namespace - lua_getfield(L, -1, "subNamespaces"); - EXPECT_TRUE(lua_istable(L, -1)); - EXPECT_EQ(1, luabridge::get_length(L, -1)); - - // Inspect the Inner sub-namespace table - lua_rawgeti(L, -1, 1); - ASSERT_TRUE(lua_istable(L, -1)); - - lua_getfield(L, -1, "name"); - EXPECT_STREQ("Inner", lua_tostring(L, -1)); - lua_pop(L, 1); - - lua_getfield(L, -1, "classes"); - EXPECT_TRUE(lua_istable(L, -1)); - EXPECT_EQ(1, luabridge::get_length(L, -1)); - lua_pop(L, 1); - - lua_pop(L, 1); // pop Inner table - lua_pop(L, 1); // pop subNamespaces table - - lua_pop(L, 1); // pop result table - EXPECT_EQ(topBefore, lua_gettop(L)); -} - TEST_F(InspectTests, LuaLSVisitorProducesAnnotations) { struct Cls { int getValue() const { return 0; } };