Skip to content

Commit 3813eb5

Browse files
authored
Allow reversed proxy metamethods (#261)
1 parent 335bdae commit 3813eb5

4 files changed

Lines changed: 285 additions & 2 deletions

File tree

‎Source/LuaBridge/detail/CFunctions.h‎

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,37 @@ inline bool is_metamethod(std::string_view method_name)
254254
return result != metamethods.end() && *result == method_name;
255255
}
256256

257+
/**
258+
* @brief Check if a method name is a binary operator metamethod.
259+
*/
260+
inline bool is_binary_operator_metamethod(std::string_view method_name)
261+
{
262+
static constexpr auto metamethods = make_array<std::string_view>(
263+
"__add",
264+
"__band",
265+
"__bor",
266+
"__bxor",
267+
"__concat",
268+
"__div",
269+
"__eq",
270+
"__idiv",
271+
"__le",
272+
"__lt",
273+
"__mod",
274+
"__mul",
275+
"__pow",
276+
"__shl",
277+
"__shr",
278+
"__sub"
279+
);
280+
281+
if (method_name.size() <= 2 || method_name[0] != '_' || method_name[1] != '_')
282+
return false;
283+
284+
auto result = std::lower_bound(metamethods.begin(), metamethods.end(), method_name);
285+
return result != metamethods.end() && *result == method_name;
286+
}
287+
257288
inline void rawset_super_method(lua_State* L, int tableIndex, const char* key)
258289
{
259290
LUABRIDGE_ASSERT(key != nullptr);
@@ -2251,6 +2282,18 @@ bool overload_type_checker(lua_State* L, int start)
22512282
return overload_check_args<ArgsPack>(L, start);
22522283
}
22532284

2285+
/**
2286+
* @brief Type checker for reversed proxy functions (binary operators with swapped operands).
2287+
*
2288+
* Checks all arguments from absolute stack index 1, ignoring @p start, because the class object
2289+
* is not the first operand on the stack (eg. `3 * vec`).
2290+
*/
2291+
template <class ArgsPack>
2292+
bool reversed_overload_type_checker(lua_State* L, int)
2293+
{
2294+
return overload_check_args<ArgsPack>(L, 1);
2295+
}
2296+
22542297
//=================================================================================================
22552298
/**
22562299
* @brief lua_CFunction to resolve an invocation between several overloads.
@@ -2479,7 +2522,8 @@ template <class T, class F, class = std::enable_if<
24792522
!std::is_member_function_pointer_v<F>>>
24802523
void push_member_function(lua_State* L, F&& f, const char* debugname)
24812524
{
2482-
static_assert(std::is_same_v<T, remove_cvref_t<std::remove_pointer_t<function_argument_or_void_t<0, F>>>>);
2525+
static_assert(std::is_same_v<T, remove_cvref_t<std::remove_pointer_t<function_argument_or_void_t<0, F>>>> ||
2526+
is_reversed_proxy_function_v<T, F>);
24832527

24842528
lua_newuserdata_aligned<F>(L, std::forward<F>(f));
24852529
lua_pushcclosure_x(L, &invoke_proxy_functor<F>, debugname, 1);

‎Source/LuaBridge/detail/FuncTraits.h‎

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,28 @@ inline static constexpr bool is_const_proxy_function_v =
541541
is_proxy_member_function_v<T, F> &&
542542
std::is_const_v<std::remove_reference_t<std::remove_pointer_t<function_argument_or_void_t<0, F>>>>;
543543

544+
//=================================================================================================
545+
/**
546+
* @brief A constexpr check for reversed proxy member functions (binary operators).
547+
*
548+
* A reversed proxy function is a binary callable where T is the second argument instead of the
549+
* first, as needed by binary operator metamethods invoked with swapped operands (eg. `3 * vec`).
550+
*
551+
* @tparam T Type where the callable should be able to operate.
552+
* @tparam F Callable object.
553+
*/
554+
template <class T, class F>
555+
inline static constexpr bool is_reversed_proxy_function_v =
556+
!std::is_member_function_pointer_v<F> &&
557+
!is_proxy_member_function_v<T, F> &&
558+
function_arity_v<F> == 2 &&
559+
std::is_same_v<T, remove_cvref_t<std::remove_pointer_t<function_argument_or_void_t<1, F>>>>;
560+
561+
template <class T, class F>
562+
inline static constexpr bool is_const_reversed_proxy_function_v =
563+
is_reversed_proxy_function_v<T, F> &&
564+
std::is_const_v<std::remove_reference_t<std::remove_pointer_t<function_argument_or_void_t<1, F>>>>;
565+
544566
//=================================================================================================
545567
/**
546568
* @brief An integral constant expression that gives the number of arguments excluding one type (usually used with lua_State*) accepted by the callable object.
@@ -593,7 +615,8 @@ inline static constexpr std::size_t member_function_arity_excluding_v = member_f
593615
template <class T, class F>
594616
static constexpr bool is_const_function =
595617
detail::is_const_member_function_pointer_v<F> ||
596-
(detail::function_arity_v<F> > 0 && detail::is_const_proxy_function_v<T, F>);
618+
(detail::function_arity_v<F> > 0 && detail::is_const_proxy_function_v<T, F>) ||
619+
detail::is_const_reversed_proxy_function_v<T, F>;
597620

598621
template <class T, class... Fs>
599622
inline static constexpr std::size_t const_functions_count = (0 + ... + (is_const_function<T, Fs> ? 1 : 0));

‎Source/LuaBridge/detail/Namespace.h‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -950,6 +950,15 @@ class Namespace : public detail::Registrar
950950
#endif
951951
}
952952

953+
if constexpr ((detail::is_reversed_proxy_function_v<T, Functions> || ...))
954+
{
955+
if (!detail::is_binary_operator_metamethod(name))
956+
{
957+
throw_or_assert<std::logic_error>("reversed functions are only allowed on binary operator metamethods");
958+
return *this;
959+
}
960+
}
961+
953962
if constexpr (sizeof...(Functions) == 1)
954963
{
955964
([&]
@@ -995,6 +1004,12 @@ class Namespace : public detail::Registrar
9951004
entry.arity = static_cast<int>(detail::member_function_arity_excluding_v<T, Functions, lua_State*>);
9961005
entry.checker = &detail::overload_type_checker<ArgsPack>;
9971006
}
1007+
else if constexpr (detail::is_reversed_proxy_function_v<T, Functions>)
1008+
{
1009+
using ArgsPack = detail::function_arguments_t<Functions>;
1010+
entry.arity = static_cast<int>(detail::member_function_arity_excluding_v<T, Functions, lua_State*>) - 1;
1011+
entry.checker = &detail::reversed_overload_type_checker<ArgsPack>;
1012+
}
9981013
else
9991014
{
10001015
using ArgsPack = detail::function_arguments_t<Functions>;
@@ -1052,6 +1067,12 @@ class Namespace : public detail::Registrar
10521067
entry.arity = static_cast<int>(detail::member_function_arity_excluding_v<T, Functions, lua_State*>);
10531068
entry.checker = &detail::overload_type_checker<ArgsPack>;
10541069
}
1070+
else if constexpr (detail::is_reversed_proxy_function_v<T, Functions>)
1071+
{
1072+
using ArgsPack = detail::function_arguments_t<Functions>;
1073+
entry.arity = static_cast<int>(detail::member_function_arity_excluding_v<T, Functions, lua_State*>) - 1;
1074+
entry.checker = &detail::reversed_overload_type_checker<ArgsPack>;
1075+
}
10551076
else
10561077
{
10571078
using ArgsPack = detail::function_arguments_t<Functions>;

‎Tests/Source/ClassTests.cpp‎

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,67 @@ class Bar
169169
Foo<T> foo;
170170
};
171171

172+
class Vec2
173+
{
174+
public:
175+
Vec2(float x, float y)
176+
: x(x), y(y)
177+
{
178+
}
179+
180+
friend Vec2 operator*(const Vec2& vec, float scalar)
181+
{
182+
return Vec2(vec.x * scalar, vec.y * scalar);
183+
}
184+
185+
friend Vec2 operator*(float scalar, const Vec2& vec)
186+
{
187+
return Vec2(vec.x * scalar, vec.y * scalar);
188+
}
189+
190+
friend Vec2 operator+(const Vec2& vec, float scalar)
191+
{
192+
return Vec2(vec.x + scalar, vec.y + scalar);
193+
}
194+
195+
friend Vec2 operator+(float scalar, const Vec2& vec)
196+
{
197+
return Vec2(scalar + vec.x, scalar + vec.y);
198+
}
199+
200+
friend Vec2 operator-(const Vec2& vec, float scalar)
201+
{
202+
return Vec2(vec.x - scalar, vec.y - scalar);
203+
}
204+
205+
friend Vec2 operator-(float scalar, const Vec2& vec)
206+
{
207+
return Vec2(scalar - vec.x, scalar - vec.y);
208+
}
209+
210+
friend Vec2 operator/(const Vec2& vec, float scalar)
211+
{
212+
return Vec2(vec.x / scalar, vec.y / scalar);
213+
}
214+
215+
friend Vec2 operator/(float scalar, const Vec2& vec)
216+
{
217+
return Vec2(scalar / vec.x, scalar / vec.y);
218+
}
219+
220+
friend bool operator==(const Vec2& lhs, const Vec2& rhs)
221+
{
222+
return lhs.x == rhs.x && lhs.y == rhs.y;
223+
}
224+
225+
float getX() const { return x; }
226+
float getY() const { return y; }
227+
228+
private:
229+
float x;
230+
float y;
231+
};
232+
172233
} // namespace
173234

174235
TEST_F(ClassTests, Assignment)
@@ -2572,6 +2633,122 @@ TEST_F(ClassMetaMethods, __mul)
25722633
ASSERT_EQ(10, result<Int>().data);
25732634
}
25742635

2636+
TEST_F(ClassMetaMethods, ReversedAddOperator)
2637+
{
2638+
luabridge::getGlobalNamespace(L)
2639+
.beginClass<Vec2>("Vec2")
2640+
.addConstructor<void (*)(float, float)>()
2641+
.addFunction("__add",
2642+
[](const Vec2& vec, float scalar) { return vec + scalar; },
2643+
[](float scalar, const Vec2& vec) { return scalar + vec; })
2644+
.endClass();
2645+
2646+
runLua("result = Vec2 (1, 2) + 3");
2647+
ASSERT_TRUE(result().isUserdata());
2648+
ASSERT_EQ(Vec2(4, 5), result<Vec2>());
2649+
2650+
runLua("result = 3 + Vec2 (1, 2)");
2651+
ASSERT_TRUE(result().isUserdata());
2652+
ASSERT_EQ(Vec2(4, 5), result<Vec2>());
2653+
}
2654+
2655+
TEST_F(ClassMetaMethods, ReversedSubOperator)
2656+
{
2657+
luabridge::getGlobalNamespace(L)
2658+
.beginClass<Vec2>("Vec2")
2659+
.addConstructor<void (*)(float, float)>()
2660+
.addFunction("__sub",
2661+
[](const Vec2& vec, float scalar) { return vec - scalar; },
2662+
[](float scalar, const Vec2& vec) { return scalar - vec; })
2663+
.endClass();
2664+
2665+
runLua("result = Vec2 (5, 7) - 2");
2666+
ASSERT_TRUE(result().isUserdata());
2667+
ASSERT_EQ(Vec2(3, 5), result<Vec2>());
2668+
2669+
runLua("result = 10 - Vec2 (1, 2)");
2670+
ASSERT_TRUE(result().isUserdata());
2671+
ASSERT_EQ(Vec2(9, 8), result<Vec2>());
2672+
}
2673+
2674+
TEST_F(ClassMetaMethods, ReversedMulOperator)
2675+
{
2676+
luabridge::getGlobalNamespace(L)
2677+
.beginClass<Vec2>("Vec2")
2678+
.addConstructor<void (*)(float, float)>()
2679+
.addFunction("__mul",
2680+
[](const Vec2& vec, float scalar) { return vec * scalar; },
2681+
[](float scalar, const Vec2& vec) { return scalar * vec; })
2682+
.endClass();
2683+
2684+
runLua("result = Vec2 (1, 2) * 3");
2685+
ASSERT_TRUE(result().isUserdata());
2686+
ASSERT_EQ(Vec2(3, 6), result<Vec2>());
2687+
2688+
runLua("result = 3 * Vec2 (1, 2)");
2689+
ASSERT_TRUE(result().isUserdata());
2690+
ASSERT_EQ(Vec2(3, 6), result<Vec2>());
2691+
}
2692+
2693+
TEST_F(ClassMetaMethods, ReversedDivOperator)
2694+
{
2695+
luabridge::getGlobalNamespace(L)
2696+
.beginClass<Vec2>("Vec2")
2697+
.addConstructor<void (*)(float, float)>()
2698+
.addFunction("__div",
2699+
[](const Vec2& vec, float scalar) { return vec / scalar; },
2700+
[](float scalar, const Vec2& vec) { return scalar / vec; })
2701+
.endClass();
2702+
2703+
runLua("result = Vec2 (6, 8) / 2");
2704+
ASSERT_TRUE(result().isUserdata());
2705+
ASSERT_EQ(Vec2(3, 4), result<Vec2>());
2706+
2707+
runLua("result = 12 / Vec2 (2, 4)");
2708+
ASSERT_TRUE(result().isUserdata());
2709+
ASSERT_EQ(Vec2(6, 3), result<Vec2>());
2710+
}
2711+
2712+
TEST_F(ClassMetaMethods, ReversedOperatorOnConstInstance)
2713+
{
2714+
luabridge::getGlobalNamespace(L)
2715+
.beginClass<Vec2>("Vec2")
2716+
.addConstructor<void (*)(float, float)>()
2717+
.addFunction("__mul",
2718+
[](const Vec2& vec, float scalar) { return vec * scalar; },
2719+
[](float scalar, const Vec2& vec) { return scalar * vec; })
2720+
.endClass();
2721+
2722+
const Vec2 vec(1, 2);
2723+
luabridge::setGlobal(L, &vec, "v");
2724+
2725+
runLua("result = v * 3");
2726+
ASSERT_TRUE(result().isUserdata());
2727+
ASSERT_EQ(Vec2(3, 6), result<Vec2>());
2728+
2729+
runLua("result = 3 * v");
2730+
ASSERT_TRUE(result().isUserdata());
2731+
ASSERT_EQ(Vec2(3, 6), result<Vec2>());
2732+
}
2733+
2734+
TEST_F(ClassMetaMethods, ReversedOperatorOnly)
2735+
{
2736+
luabridge::getGlobalNamespace(L)
2737+
.beginClass<Vec2>("Vec2")
2738+
.addConstructor<void (*)(float, float)>()
2739+
.addFunction("__mul",
2740+
[](float scalar, const Vec2& vec) { return scalar * vec; })
2741+
.endClass();
2742+
2743+
runLua("result = 3 * Vec2 (1, 2)");
2744+
ASSERT_TRUE(result().isUserdata());
2745+
ASSERT_EQ(Vec2(3, 6), result<Vec2>());
2746+
2747+
#if LUABRIDGE_HAS_EXCEPTIONS
2748+
ASSERT_ANY_THROW(runLua("result = Vec2 (1, 2) * 3"));
2749+
#endif
2750+
}
2751+
25752752
TEST_F(ClassMetaMethods, __div)
25762753
{
25772754
typedef Class<int, EmptyBase> Int;
@@ -2776,6 +2953,24 @@ TEST_F(ClassMetaMethods, __gcForbidden)
27762953
.endClass(),
27772954
std::exception);
27782955
}
2956+
2957+
TEST_F(ClassMetaMethods, ReversedFunctionsForbiddenOutsideBinaryOperators)
2958+
{
2959+
ASSERT_THROW(luabridge::getGlobalNamespace(L)
2960+
.beginClass<Vec2>("Vec2")
2961+
.addFunction("mul",
2962+
[](const Vec2& vec, float scalar) { return vec * scalar; },
2963+
[](float scalar, const Vec2& vec) { return scalar * vec; })
2964+
.endClass(),
2965+
std::exception);
2966+
2967+
ASSERT_THROW(luabridge::getGlobalNamespace(L)
2968+
.beginClass<Vec2>("Vec2")
2969+
.addFunction("__tostring",
2970+
[](float scalar, const Vec2& vec) { return scalar * vec; })
2971+
.endClass(),
2972+
std::exception);
2973+
}
27792974
#endif
27802975

27812976
TEST_F(ClassMetaMethods, MetamethodsShouldNotBePartOfClassInstances)

0 commit comments

Comments
 (0)