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
107 changes: 107 additions & 0 deletions Source/LuaBridge/detail/CFunctions.h
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,83 @@ inline void push_class_or_const_table(lua_State* L, int index)
}
}

//=================================================================================================
/**
* @brief Look up a static function or property through an object's class metatable.
*/
inline std::optional<int> try_call_instance_static_index(lua_State* L, int classMetatableIndex)
{
lua_rawgetp_x(L, classMetatableIndex, getStaticKey()); // Stack: ..., static table (st) | nil
if (! lua_istable(L, -1))
{
lua_pop(L, 1);
return std::nullopt;
}

lua_pushvalue(L, 2); // Stack: ..., st, field name
lua_rawget(L, -2); // Stack: ..., st, value | nil
if (! lua_isnil(L, -1))
{
lua_remove(L, -2); // Stack: ..., value
return 1;
}

lua_pop(L, 1); // Stack: ..., st
lua_rawgetp_x(L, -1, getPropgetKey()); // Stack: ..., st, propget table (pg) | nil
if (! lua_istable(L, -1))
{
lua_pop(L, 2);
return std::nullopt;
}

lua_pushvalue(L, 2); // Stack: ..., st, pg, field name
lua_rawget(L, -2); // Stack: ..., st, pg, getter | nil
if (! lua_iscfunction(L, -1))
{
lua_pop(L, 3);
return std::nullopt;
}

lua_remove(L, -2); // Stack: ..., st, getter
lua_remove(L, -2); // Stack: ..., getter
lua_call(L, 0, 1); // Stack: ..., value
return 1;
}

/**
* @brief Call a static property setter through an object's class metatable.
*/
inline std::optional<int> try_call_instance_static_newindex(lua_State* L, int classMetatableIndex)
{
lua_rawgetp_x(L, classMetatableIndex, getStaticKey()); // Stack: ..., static table (st) | nil
if (! lua_istable(L, -1))
{
lua_pop(L, 1);
return std::nullopt;
}

lua_rawgetp_x(L, -1, getPropsetKey()); // Stack: ..., st, propset table (ps) | nil
if (! lua_istable(L, -1))
{
lua_pop(L, 2);
return std::nullopt;
}

lua_pushvalue(L, 2); // Stack: ..., st, ps, field name
lua_rawget(L, -2); // Stack: ..., st, ps, setter | nil
if (! lua_iscfunction(L, -1))
{
lua_pop(L, 3);
return std::nullopt;
}

lua_remove(L, -2); // Stack: ..., st, setter
lua_remove(L, -2); // Stack: ..., setter
lua_pushvalue(L, 3); // Stack: ..., setter, new value
lua_call(L, 1, 0);
return 0;
}

//=================================================================================================
/**
* @brief __index metamethod for a namespace or class static and non-static members.
Expand Down Expand Up @@ -607,6 +684,12 @@ inline int index_metamethod(lua_State* L)
LUABRIDGE_ASSERT(lua_isnil(L, -1)); // Stack: mt, nil
lua_pop(L, 1); // Stack: mt

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

// It may mean that the field may be in const table and it's constness violation.

// Search flattened parent list in declaration-order DFS.
Expand Down Expand Up @@ -750,6 +833,10 @@ inline int index_metamethod_simple(lua_State* L)
return 1;

lua_pop(L, 1);

if (auto result = try_call_instance_static_index(L, lua_upvalueindex(2)))
return *result;

lua_pushnil(L);
return 1;
}
Expand Down Expand Up @@ -1267,6 +1354,19 @@ inline int newindex_metamethod(lua_State* L)

lua_pop(L, 1); // Stack: mt

// 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 Expand Up @@ -1348,6 +1448,13 @@ inline int newindex_metamethod_simple(lua_State* L)
return 0;
}

lua_pop(L, 1);
lua_getmetatable(L, 1); // Stack: mt
LUABRIDGE_ASSERT(lua_istable(L, -1));
if (auto result = try_call_instance_static_newindex(L, -1))
return *result;
lua_pop(L, 1);

luaL_error(L, "no writable member '%s'", key);
}
}
Expand Down
10 changes: 4 additions & 6 deletions Tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -473,18 +473,16 @@ add_test_app (LuaBridgeTests55LuaC 505 "${LUABRIDGE_TEST_LUA55_C_FILES}" 1 "${LU
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 "" "")

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")

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 "" "libravi")
endif ()
#add_test_app (LuaBridgeTestsRaviNoexcept "RAVI" "${LUABRIDGE_TEST_RAVI_FILES}" 0 "" "libravi")

if (LUABRIDGE_COVERAGE)
setup_coverage_single_target ()
Expand Down
83 changes: 83 additions & 0 deletions Tests/Source/ClassTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2333,6 +2333,89 @@ TEST_F(ClassStaticProperties, SubsequentRegistration)
ASSERT_EQ(20, Int::staticData);
}

TEST_F(ClassStaticProperties, InstanceAccess)
{
using Int = Class<int, EmptyBase>;
Int instance;

Int::staticData = 10;

luabridge::getGlobalNamespace(L)
.beginClass<Int>("Int")
.addStaticPropertyReadWrite("staticData", &Int::staticData)
.addFunction("getData", &Int::getData)
.endClass()
.addVariable("instance", &instance);

// Write via class name (always works)
runLua("Int.staticData = 55");
ASSERT_EQ(55, Int::staticData);

Int::staticData = 10;

// Read via instance
runLua("result = instance.staticData");
ASSERT_TRUE(result().isNumber());
ASSERT_EQ(10, result<int>());

// Write via instance
runLua("instance.staticData = 42");
ASSERT_EQ(42, Int::staticData);
}

TEST_F(ClassStaticProperties, ReadOnlyViaInstance)
{
using Int = Class<int, EmptyBase>;

Int::staticData = 10;
Int instance;

luabridge::getGlobalNamespace(L)
.beginClass<Int>("Int")
.addStaticProperty("staticData", &Int::staticData)
.endClass()
.addVariable("instance", &instance);

runLua("result = instance.staticData");
ASSERT_TRUE(result().isNumber());
ASSERT_EQ(10, result<int>());

// Setting a read-only static property via instance should fail
#if LUABRIDGE_HAS_EXCEPTIONS
ASSERT_THROW(runLua("instance.staticData = 20"), std::exception);
#else
ASSERT_FALSE(runLua("instance.staticData = 20"));
#endif

ASSERT_EQ(10, Int::staticData);
}

TEST_F(ClassStaticProperties, StaticFunctionViaInstance)
{
using Int = Class<int, EmptyBase>;

int value = 10;
Int instance;

luabridge::getGlobalNamespace(L)
.beginClass<Int>("Int")
.addStaticFunction("getValue", [&value] { return value; })
.addStaticFunction("setValue", [&value](int x) { value = x; })
.endClass()
.addVariable("instance", &instance);

runLua("result = instance.getValue()");
ASSERT_TRUE(result().isNumber());
ASSERT_EQ(10, result<int>());

runLua("instance.setValue(99)");
ASSERT_EQ(99, value);

runLua("result = instance.getValue()");
ASSERT_TRUE(result().isNumber());
ASSERT_EQ(99, result<int>());
}

struct ClassMetaMethods : ClassTests
{
};
Expand Down
Loading