diff --git a/Source/LuaBridge/detail/Invoke.h b/Source/LuaBridge/detail/Invoke.h index 41c98c50..6a4415e5 100644 --- a/Source/LuaBridge/detail/Invoke.h +++ b/Source/LuaBridge/detail/Invoke.h @@ -103,6 +103,29 @@ class LuaResult return LuaRef(m_L); } +#if LUABRIDGE_HAS_EXCEPTIONS + /** + * @brief + */ + void raiseException() const + { + if (wasOk()) + return; + + if (std::holds_alternative(m_data)) + { + const auto& message = std::get(m_data); + lua_pushlstring(m_L, message.c_str(), message.size()); + } + else + { + lua_pushlstring(m_L, m_ec.message().c_str(), m_ec.message().size()); + } + + throw LuaException::fromStack(m_L, m_ec); + } +#endif + private: template friend LuaResult call(const LuaRef&, Args&&...); @@ -253,7 +276,17 @@ template template LuaResult LuaRefBase::callWithHandler(F&& errorHandler, Args&&... args) const { - return luabridge::callWithHandler(*this, std::forward(errorHandler), std::forward(args)...); + if constexpr (! std::is_convertible_v) + { + return luabridge::callWithHandler(*this, std::forward(errorHandler), std::forward(args)...); + } + else + { + if (errorHandler) + return luabridge::callWithHandler(*this, std::forward(errorHandler), std::forward(args)...); + } + + return luabridge::call(*this, std::forward(args)...); } } // namespace luabridge diff --git a/Source/LuaBridge/detail/LuaException.h b/Source/LuaBridge/detail/LuaException.h index 32b3891c..626b5156 100644 --- a/Source/LuaBridge/detail/LuaException.h +++ b/Source/LuaBridge/detail/LuaException.h @@ -55,7 +55,7 @@ class LuaException : public std::exception LUABRIDGE_ASSERT(areExceptionsEnabled(L)); #if LUABRIDGE_HAS_EXCEPTIONS - throw LuaException(L, code, FromLua{}); + throw LuaException::fromStack(L, code); #else unused(L, code); @@ -103,6 +103,19 @@ class LuaException : public std::exception #endif } + //============================================================================================= + /** + * @brief Construct a LuaException from stack. + * + * @return A Lua exception from stack. + */ + static LuaException fromStack(lua_State* L, std::error_code code = makeErrorCode(ErrorCode::LuaFunctionCallFailed)) + { + auto exception = LuaException(L, code); + exception.whatFromStack(); + return exception; + } + //============================================================================================= /** * @brief Retrieve the lua_State associated with the exception. @@ -112,15 +125,6 @@ class LuaException : public std::exception lua_State* state() const { return m_L; } private: - struct FromLua {}; - - LuaException(lua_State* L, std::error_code code, FromLua) - : m_L(L) - , m_code(code) - { - whatFromStack(); - } - void whatFromStack() { std::stringstream ss; @@ -141,7 +145,7 @@ class LuaException : public std::exception static int panicHandlerCallback(lua_State* L) { #if LUABRIDGE_HAS_EXCEPTIONS - throw LuaException(L, makeErrorCode(ErrorCode::LuaFunctionCallFailed), FromLua{}); + throw LuaException::fromStack(L); #else unused(L); diff --git a/Tests/Source/LuaRefTests.cpp b/Tests/Source/LuaRefTests.cpp index 62d4cbf4..df4cd448 100644 --- a/Tests/Source/LuaRefTests.cpp +++ b/Tests/Source/LuaRefTests.cpp @@ -510,18 +510,39 @@ TEST_F(LuaRefTests, Callable) EXPECT_EQ(200, obj["i"].unsafe_cast()); } -TEST_F(LuaRefTests, CallableWithHandler) +#if LUABRIDGE_HAS_EXCEPTIONS +TEST_F(LuaRefTests, CallableWithThrowingHandler) +{ + runLua("function f(x) error('we failed ' .. x) end"); + auto f = luabridge::getGlobal(L, "f"); + EXPECT_TRUE(f.isCallable()); + + bool calledHandler = false; + auto handler = [&](lua_State*) -> int + { + calledHandler = true; + return 0; + }; + + EXPECT_ANY_THROW(f.callWithHandler(handler, "badly").raiseException()); + EXPECT_TRUE(calledHandler); +} +#endif + +TEST_F(LuaRefTests, CallableWithAndWithoutHandler) { runLua("function f(x) error('we failed ' .. x) end"); auto f = luabridge::getGlobal(L, "f"); EXPECT_TRUE(f.isCallable()); + // Call without #if LUABRIDGE_HAS_EXCEPTIONS EXPECT_ANY_THROW(f.call("badly")); #else EXPECT_FALSE(f.call("badly")); #endif + // Call with bool calledHandler = false; std::string errorMessage; auto handler = [&](lua_State*) -> int @@ -540,6 +561,66 @@ TEST_F(LuaRefTests, CallableWithHandler) EXPECT_TRUE(errorMessage.find("we failed badly") != std::string::npos); } +TEST_F(LuaRefTests, CallableWithStdFunction) +{ + runLua("function f(x) error('we failed ' .. x) end"); + auto f = luabridge::getGlobal(L, "f"); + EXPECT_TRUE(f.isCallable()); + + bool calledHandler = false; + std::string errorMessage; + auto handler = [&](lua_State*) -> int + { + calledHandler = true; + + if (auto msg = lua_tostring(L, 1)) + errorMessage = msg; + + return 0; + }; + + std::function pHandler = handler; + + EXPECT_FALSE(f.callWithHandler(pHandler, "badly")); + EXPECT_TRUE(calledHandler); + EXPECT_TRUE(errorMessage.find("we failed badly") != std::string::npos); +} + +TEST_F(LuaRefTests, CallableWithNullifiedStdFunction) +{ + runLua("function f(x) error('we failed ' .. x) end"); + auto f = luabridge::getGlobal(L, "f"); + EXPECT_TRUE(f.isCallable()); + + std::function pHandler = nullptr; + EXPECT_FALSE(f.callWithHandler(pHandler, "badly")); +} + +TEST_F(LuaRefTests, CallableWithCFunction) +{ + runLua("function f(x) error('we failed ' .. x) end"); + auto f = luabridge::getGlobal(L, "f"); + EXPECT_TRUE(f.isCallable()); + + lua_CFunction pHandler = +[](lua_State* L) { return 0; }; + EXPECT_FALSE(f.callWithHandler(pHandler, "badly")); +} + +TEST_F(LuaRefTests, CallableWithNullCFunction) +{ + runLua("function f(x) error('we failed ' .. x) end"); + auto f = luabridge::getGlobal(L, "f"); + EXPECT_TRUE(f.isCallable()); + + lua_CFunction pHandler = nullptr; + +#if LUABRIDGE_HAS_EXCEPTIONS + EXPECT_ANY_THROW(f.callWithHandler(pHandler, "badly")); +#else + EXPECT_FALSE(f.callWithHandler(pHandler, "badly")); +#endif +} + TEST_F(LuaRefTests, Pop) { lua_pushstring(L, "hello");