Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 25 additions & 4 deletions Distribution/LuaBridge/LuaBridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -3705,6 +3705,7 @@ class LuaException : public std::exception
LuaException(lua_State* L, std::error_code code)
: m_L(L)
, m_code(code)
, m_what(code.message())
{
}

Expand Down Expand Up @@ -4730,7 +4731,7 @@ struct UserdataGetter
};

template <class T>
struct UserdataGetter<T, std::void_t<T (*)()>>
struct UserdataGetter<T, std::enable_if_t<!is_move_only_function_v<T>, std::void_t<T (*)()>>>
{
using ReturnType = TypeResult<T>;

Expand All @@ -4744,6 +4745,26 @@ struct UserdataGetter<T, std::void_t<T (*)()>>
}
};

#if LUABRIDGE_HAS_CXX23_MOVE_ONLY_FUNCTION
template <class T>
struct UserdataGetter<T, std::enable_if_t<is_move_only_function_v<T>>>
{
using ReturnType = TypeResult<T>;

static ReturnType get(lua_State* L, int index)
{
auto result = Userdata::get<T>(L, index, true);
if (! result)
return result.error();

if (*result == nullptr)
return getNilBadArgError<T>(L, index);

return std::move(**result);
}
};
#endif

}

template <class T, class = void>
Expand Down Expand Up @@ -8237,7 +8258,7 @@ inline std::optional<int> try_call_newindex_extensible(lua_State* L, const char*

const int mtIndex = lua_absindex(L, -2);
const int origClassTableIndex = lua_absindex(L, -1);
const auto process_metatable = [L, key, origClassTableIndex](int candidateMtIndex)
const auto process_metatable = [=](int candidateMtIndex)
{
push_class_or_const_table(L, candidateMtIndex);
if (! lua_istable(L, -1))
Expand Down Expand Up @@ -8307,7 +8328,7 @@ inline std::optional<int> try_call_newindex_extensible(lua_State* L, const char*
lua_pushvalue(L, rootMetatableIndex);
const int targetMetatableIndex = lua_absindex(L, -1);

const auto process_metatable = [L, key, targetMetatableIndex](int candidateMtIndex)
const auto process_metatable = [=](int candidateMtIndex)
{
push_class_or_const_table(L, candidateMtIndex);
if (! lua_istable(L, -1))
Expand Down Expand Up @@ -12576,7 +12597,7 @@ class Namespace : public detail::Registrar
LUABRIDGE_ASSERT(lua_istable(L, visitedIndex));
LUABRIDGE_ASSERT(lua_istable(L, baseMetatableIndex));

const auto appendUnique = [L, parentsIndex, visitedIndex](int metatableIndex)
const auto appendUnique = [=](int metatableIndex)
{
metatableIndex = lua_absindex(L, metatableIndex);

Expand Down
17 changes: 15 additions & 2 deletions Source/LuaBridge/detail/CFunctions.h
Original file line number Diff line number Diff line change
Expand Up @@ -1049,7 +1049,7 @@ inline std::optional<int> try_call_newindex_extensible(lua_State* L, const char*

const int mtIndex = lua_absindex(L, -2);
const int origClassTableIndex = lua_absindex(L, -1);
const auto process_metatable = [L, key, origClassTableIndex](int candidateMtIndex)
const auto process_metatable = [=](int candidateMtIndex)
{
push_class_or_const_table(L, candidateMtIndex); // Stack: ..., candidate_ct | nil
if (! lua_istable(L, -1))
Expand Down Expand Up @@ -1121,7 +1121,7 @@ inline std::optional<int> try_call_newindex_extensible(lua_State* L, const char*
lua_pushvalue(L, rootMetatableIndex); // Stack: mt, target mt
const int targetMetatableIndex = lua_absindex(L, -1);

const auto process_metatable = [L, key, targetMetatableIndex](int candidateMtIndex)
const auto process_metatable = [=](int candidateMtIndex)
{
push_class_or_const_table(L, candidateMtIndex); // Stack: ..., candidate_ct | nil
if (! lua_istable(L, -1))
Expand Down Expand Up @@ -1367,6 +1367,19 @@ inline int newindex_metamethod(lua_State* L)
return *result;
}

// Before consulting any __newindex fallback, scan the entire parent hierarchy for a
// matching property setter. This ensures that a registered property anywhere in the
// inheritance chain always takes priority over a __newindex fallback defined at a
// narrower scope (e.g. an intermediate or leaf class).
if (auto result = try_call_parent_newindex_setters<IsObject>(L))
return *result;

if constexpr (IsObject)
{
if (auto result = try_call_instance_static_newindex(L, -1))
return *result;
}

// Before consulting any __newindex fallback, scan the entire parent hierarchy for a
// matching property setter. This ensures that a registered property anywhere in the
// inheritance chain always takes priority over a __newindex fallback defined at a
Expand Down
5 changes: 2 additions & 3 deletions Source/LuaBridge/detail/LuaException.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,12 @@ class LuaException : public std::exception
public:
//=============================================================================================
/**
* @brief Construct a LuaException after a lua_pcall().
*
* Assumes the error string is on top of the stack, but provides a generic error message otherwise.
* @brief Construct a LuaException from a LuaBridge error code.
*/
LuaException(lua_State* L, std::error_code code)
: m_L(L)
, m_code(code)
, m_what(code.message())
{
}

Expand Down
18 changes: 18 additions & 0 deletions Source/LuaBridge/detail/LuaRef.h
Original file line number Diff line number Diff line change
Expand Up @@ -1829,6 +1829,24 @@ class LuaRef : public LuaRefBase<LuaRef, LuaRef>
lua_pop(m_L, 1);
}

//=============================================================================================
/**
* @brief Get the Lua pointer of the referenced value.
*/
const void* getPointer() const
{
#if LUABRIDGE_SAFE_STACK_CHECKS
if (! lua_checkstack(m_L, 1))
return nullptr;
#endif

lua_rawgeti(m_L, LUA_REGISTRYINDEX, m_ref);
const void* ptr = lua_topointer(m_L, -1);
lua_pop(m_L, 1);

return ptr;
}

//=============================================================================================
/**
* @brief Get the unique hash of a LuaRef.
Expand Down
2 changes: 1 addition & 1 deletion Source/LuaBridge/detail/Namespace.h
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ class Namespace : public detail::Registrar
LUABRIDGE_ASSERT(lua_istable(L, visitedIndex));
LUABRIDGE_ASSERT(lua_istable(L, baseMetatableIndex));

const auto appendUnique = [L, parentsIndex, visitedIndex](int metatableIndex)
const auto appendUnique = [=](int metatableIndex)
{
metatableIndex = lua_absindex(L, metatableIndex);

Expand Down
23 changes: 22 additions & 1 deletion Source/LuaBridge/detail/Userdata.h
Original file line number Diff line number Diff line change
Expand Up @@ -1013,8 +1013,9 @@ struct UserdataGetter
}
};

// Specialization for default-constructible types (but exclude move-only functions)
template <class T>
struct UserdataGetter<T, std::void_t<T (*)()>>
struct UserdataGetter<T, std::enable_if_t<!is_move_only_function_v<T>, std::void_t<T (*)()>>>
{
using ReturnType = TypeResult<T>;

Expand All @@ -1028,6 +1029,26 @@ struct UserdataGetter<T, std::void_t<T (*)()>>
}
};

#if LUABRIDGE_HAS_CXX23_MOVE_ONLY_FUNCTION
template <class T>
struct UserdataGetter<T, std::enable_if_t<is_move_only_function_v<T>>>
{
using ReturnType = TypeResult<T>;

static ReturnType get(lua_State* L, int index)
{
auto result = Userdata::get<T>(L, index, true);
if (! result)
return result.error();

if (*result == nullptr)
return getNilBadArgError<T>(L, index);

return std::move(**result);
}
};
#endif

} // namespace detail

//=================================================================================================
Expand Down
4 changes: 4 additions & 0 deletions Tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,10 @@ macro (add_test_app LUABRIDGE_TEST_NAME LUA_VERSION LUABRIDGE_TEST_LUA_LIBRARY_F
${LUABRIDGE_TEST_LUA_LIBRARY_FILES}
)

if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
target_compile_options (${LUABRIDGE_TEST_NAME} PRIVATE -Wno-psabi)
endif ()

target_compile_definitions (${LUABRIDGE_TEST_NAME} PRIVATE
${LUABRIDGE_DEFINES})

Expand Down
7 changes: 6 additions & 1 deletion Tests/Source/FlatMapTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,14 @@
namespace {
struct Unregistered
{
bool operator==(const Unregistered& other) const
{
return this == std::addressof(other);
}

bool operator<(const Unregistered& other) const
{
return true;
return this < std::addressof(other);
}
};

Expand Down
2 changes: 1 addition & 1 deletion Tests/Source/FlatSetTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ struct Unregistered
{
bool operator<(const Unregistered& other) const
{
return true;
return false;
}
};

Expand Down
44 changes: 44 additions & 0 deletions Tests/Source/LuaRefTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2140,3 +2140,47 @@ TEST_F(LuaRefTests, MoveToNilRef)
EXPECT_EQ(threadTop, lua_gettop(thread));
EXPECT_TRUE(nilRef.isNil());
}

namespace {
struct Event
{
explicit Event(lua_State* L)
: onClick(L)
{
}

luabridge::LuaRef onClick;
};
} // namespace

TEST_F(LuaRefTests, PreservesLuaTableMutations)
{
luabridge::getGlobalNamespace(L)
.beginClass<Event>("Event")
.addConstructor<void (*)(lua_State*)>()
.addPropertyReadWrite("OnClick", &Event::onClick)
.endClass();

runLua(R"(
local event = Event()
event.OnClick = {}

table.insert(event.OnClick, function() return 100 end)
table.insert(event.OnClick, function() return 200 end)

result = event
)");

auto* event = result<Event*>();
ASSERT_NE(nullptr, event);
ASSERT_TRUE(event->onClick.isTable());
ASSERT_EQ(2, event->onClick.length());

auto firstResult = event->onClick[1].call<int>();
ASSERT_TRUE(firstResult);
EXPECT_EQ(100, *firstResult);

auto secondResult = event->onClick[2].call<int>();
ASSERT_TRUE(secondResult);
EXPECT_EQ(200, *secondResult);
}
42 changes: 41 additions & 1 deletion Tests/Source/MoveOnlyFunctionTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@

#include <functional>

namespace {
class TestClass
{
public:
void method1(std::function<void()> fn) { fn(); }
void method2(std::move_only_function<void()> fn) { fn(); }
};
} // namespace

struct MoveOnlyFunctionTests : TestBase
{
};
Expand Down Expand Up @@ -48,7 +57,7 @@ TEST_F(MoveOnlyFunctionTests, RegisterAndCall)
std::move_only_function<int(int)> fn = [](int x) { return x * 2; };

luabridge::getGlobalNamespace(L)
.addFunction("double_it", std::function<int(int)>([](int x) { return x * 2; }));
.addFunction("double_it", std::move(fn));

runLua("result = double_it(21)");
EXPECT_EQ(42, result<int>());
Expand All @@ -60,4 +69,35 @@ TEST_F(MoveOnlyFunctionTests, HasFunctionTraits)
EXPECT_TRUE((luabridge::detail::has_function_traits_v<F>));
}

TEST_F(MoveOnlyFunctionTests, RegisterAsArgument)
{
using Function1 = std::function<void()>;
using Function2 = std::move_only_function<void()>;

luabridge::getGlobalNamespace(L)
.beginClass<TestClass>("TestClass")
.addFunction("Method1", &TestClass::method1)
.addFunction("Method2", &TestClass::method2)
.endClass()
.beginClass<Function1>("Function1")
.endClass()
.beginClass<Function2>("Function2")
.endClass();

TestClass object;
luabridge::setGlobal(L, &object, "object");

bool called1 = false;
Function1 fn1 = [&called1] { called1 = true; };
luabridge::setGlobal(L, &fn1, "fn1");
runLua("object:Method1(fn1)");
EXPECT_TRUE(called1);

bool called2 = false;
Function2 fn2 = [&called2] { called2 = true; };
luabridge::setGlobal(L, &fn2, "fn2");
runLua("object:Method2(fn2)");
EXPECT_TRUE(called2);
}

#endif // LUABRIDGE_HAS_CXX23_MOVE_ONLY_FUNCTION
13 changes: 11 additions & 2 deletions Tests/Source/StackTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2895,8 +2895,17 @@ TEST_F(StackTests, SetGlobalFailure)
{
struct Unregistered2 {};

// setGlobal should return false when push fails (non-exception mode only)
#if !LUABRIDGE_HAS_EXCEPTIONS
#if LUABRIDGE_HAS_EXCEPTIONS
try
{
luabridge::setGlobal(L, Unregistered2{}, "test_var");
FAIL() << "Expected an exception";
}
catch (const luabridge::LuaException& e)
{
EXPECT_STREQ("The class is not registered in LuaBridge", e.what());
}
#else
bool ok = luabridge::setGlobal(L, Unregistered2{}, "test_var");
EXPECT_FALSE(ok);
#endif
Expand Down
8 changes: 8 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ test1 CXX="17":
sanitize TYPE="address" CXX="17":
cmake -G Xcode -B Build{{CXX}} -DLUABRIDGE_SANITIZE={{TYPE}} .

gcc CXX="17":
cmake -G "Unix Makefiles" \
-DCMAKE_C_COMPILER=/opt/homebrew/bin/gcc-15 \
-DCMAKE_CXX_COMPILER=/opt/homebrew/bin/g++-15 \
-B BuildGCC{{CXX}} -DLUABRIDGE_BENCHMARKS=ON -DCMAKE_CXX_STANDARD={{CXX}} .
cmake --build BuildGCC{{CXX}} --config Debug --target LuaBridgeTests54 -j8
./BuildGCC{{CXX}}/Tests/LuaBridgeTests54 --gtest_filter=*

benchmark CXX="17":
@just generate {{CXX}}
cmake --build Build{{CXX}} --config Release --target LuaBridge3Benchmark -j8
Expand Down
Loading