2121#include < cstdint>
2222#include < optional>
2323#include < string>
24+ #include < vector>
2425
2526namespace luabridge {
2627
@@ -947,6 +948,7 @@ inline int newindex_metamethod_simple(lua_State* L)
947948 }
948949
949950 luaL_error (L, " no writable member '%s'" , key);
951+ return 0 ;
950952}
951953
952954// =================================================================================================
@@ -958,7 +960,6 @@ inline int newindex_metamethod_simple(lua_State* L)
958960inline int read_only_error (lua_State* L)
959961{
960962 raise_lua_error (L, " '%s' is read-only" , lua_tostring (L, lua_upvalueindex (1 )));
961-
962963 return 0 ;
963964}
964965
@@ -1551,67 +1552,140 @@ int invoke_proxy_destructor(lua_State* L)
15511552 return 1 ;
15521553}
15531554
1555+ // =================================================================================================
1556+ /* *
1557+ * @brief C++ storage for a single overload entry: arity and optional type checker.
1558+ *
1559+ * Stored inside an OverloadSet which is kept as a Lua full userdata (auto-GC'd).
1560+ */
1561+ struct OverloadEntry
1562+ {
1563+ using TypeChecker = bool (*)(lua_State*, int start);
1564+
1565+ int arity; // -1 for variadic (lua_CFunction): always attempt
1566+ TypeChecker checker; // nullptr for variadic: skip type pre-checking
1567+ };
1568+
1569+ /* *
1570+ * @brief C++ storage for all overloads of a function.
1571+ *
1572+ * Stored as a Lua full userdata so it is GC'd automatically when the closure is collected.
1573+ * The actual function closures are stored separately in a flat Lua table (upvalue 2).
1574+ */
1575+ struct OverloadSet
1576+ {
1577+ std::vector<OverloadEntry> entries;
1578+ };
1579+
1580+ /* *
1581+ * @brief Check a single argument type, skipping lua_State* (auto-injected, not on the Lua stack).
1582+ */
1583+ template <class T >
1584+ bool overload_check_one_arg (lua_State* L, int & idx)
1585+ {
1586+ if constexpr (std::is_pointer_v<T> &&
1587+ std::is_same_v<std::remove_const_t <std::remove_pointer_t <T>>, lua_State>)
1588+ {
1589+ return true ; // lua_State* is auto-injected by LuaBridge, not a Lua-visible argument
1590+ }
1591+ else
1592+ {
1593+ return Stack<T>::isInstance (L, idx++);
1594+ }
1595+ }
1596+
1597+ template <class ArgsPack , std::size_t ... I>
1598+ bool overload_check_args_impl (lua_State* L, int start, std::index_sequence<I...>)
1599+ {
1600+ int idx = start;
1601+ return (overload_check_one_arg<std::tuple_element_t <I, ArgsPack>>(L, idx) && ...);
1602+ }
1603+
1604+ template <class ArgsPack >
1605+ bool overload_check_args (lua_State* L, int start)
1606+ {
1607+ return overload_check_args_impl<ArgsPack>(L, start,
1608+ std::make_index_sequence<std::tuple_size_v<ArgsPack>>{});
1609+ }
1610+
1611+ /* *
1612+ * @brief Type checker instantiable as an OverloadEntry::TypeChecker function pointer.
1613+ *
1614+ * Checks that the Lua stack arguments starting at @p start match the types in @tparam ArgsPack,
1615+ * using Stack<T>::isInstance without raising errors. lua_State* arguments are skipped.
1616+ */
1617+ template <class ArgsPack >
1618+ bool overload_type_checker (lua_State* L, int start)
1619+ {
1620+ return overload_check_args<ArgsPack>(L, start);
1621+ }
1622+
15541623// =================================================================================================
15551624/* *
15561625 * @brief lua_CFunction to resolve an invocation between several overloads.
15571626 *
1558- * The list of overloads is in the first upvalue. The arguments of the function call are at the top of the Lua stack.
1627+ * upvalue[1] = OverloadSet full userdata — C++ vector of {arity, type_checker} per overload.
1628+ * upvalue[2] = flat Lua table {[1]=func1, [2]=func2, ...} — the actual function closures.
1629+ *
1630+ * Dispatch:
1631+ * 1. Arity check in C++ (no Lua call).
1632+ * 2. Type check via Stack<T>::isInstance in C++ (no pcall) — skips clearly mismatched overloads.
1633+ * 3. Only calls lua_pcall for type-matched candidates, eliminating failed pcalls for type mismatches.
15591634 */
15601635template <bool Member>
15611636inline int try_overload_functions (lua_State* L)
15621637{
15631638 const int nargs = lua_gettop (L);
15641639 const int effective_args = nargs - (Member ? 1 : 0 );
1640+ const int start_arg = Member ? 2 : 1 ;
1641+
1642+ LUABRIDGE_ASSERT (isfulluserdata (L, lua_upvalueindex (1 )));
1643+ auto * overload_set = align<OverloadSet>(lua_touserdata (L, lua_upvalueindex (1 )));
15651644
1566- // get the list of overloads
1567- lua_pushvalue (L, lua_upvalueindex (1 ));
1645+ // push flat functions table (upvalue 2)
1646+ lua_pushvalue (L, lua_upvalueindex (2 ));
15681647 LUABRIDGE_ASSERT (lua_istable (L, -1 ));
1569- const int idx_overloads = nargs + 1 ;
1570- const int num_overloads = get_length (L, idx_overloads);
1648+ const int idx_funcs = nargs + 1 ;
15711649
15721650 // create table to hold error messages
1573- lua_createtable (L, num_overloads , 0 );
1651+ lua_createtable (L, static_cast < int >(overload_set-> entries . size ()) , 0 );
15741652 const int idx_errors = nargs + 2 ;
15751653 int nerrors = 0 ;
15761654
1577- // iterate through table, snippet taken from Lua docs
1578- lua_pushnil (L); // first key
1579- while (lua_next (L, idx_overloads) != 0 )
1655+ for (int i = 0 ; i < static_cast <int >(overload_set->entries .size ()); ++i)
15801656 {
1581- LUABRIDGE_ASSERT (lua_istable (L, -1 ));
1582-
1583- // check matching arity
1584- lua_rawgeti (L, -1 , 1 );
1585- LUABRIDGE_ASSERT (lua_isnumber (L, -1 ));
1657+ const auto & entry = overload_set->entries [i];
15861658
1587- const int overload_arity = static_cast < int >( lua_tointeger (L, - 1 ));
1588- if (overload_arity >= 0 && overload_arity != effective_args)
1659+ // fast arity check (C++, no Lua calls)
1660+ if (entry. arity >= 0 && entry. arity != effective_args)
15891661 {
1590- // store error message and try next overload
1591- lua_pushfstring (L, " Skipped overload #%d with unmatched arity of %d instead of %d" , nerrors, overload_arity, effective_args);
1662+ lua_pushfstring (L, " Skipped overload #%d with unmatched arity of %d instead of %d" , i, entry.arity , effective_args);
15921663 lua_rawseti (L, idx_errors, ++nerrors);
1593-
1594- lua_pop (L, 2 ); // pop arity, value (table)
15951664 continue ;
15961665 }
15971666
1598- lua_pop (L, 1 ); // pop arity
1667+ // fast type check (C++, no pcall) — avoids expensive pcall for clearly mismatched types
1668+ if (entry.checker != nullptr && !entry.checker (L, start_arg))
1669+ {
1670+ lua_pushfstring (L, " Skipped overload #%d with unmatched argument types" , i);
1671+ lua_rawseti (L, idx_errors, ++nerrors);
1672+ continue ;
1673+ }
15991674
1600- // push function
1601- lua_pushnumber (L, 2 );
1602- lua_gettable (L, -2 );
1675+ // O(1) function lookup from flat table
1676+ lua_rawgeti (L, idx_funcs, i + 1 );
16031677 LUABRIDGE_ASSERT (lua_isfunction (L, -1 ));
16041678
16051679 // push arguments
1606- for (int i = 1 ; i <= nargs; ++i )
1607- lua_pushvalue (L, i );
1680+ for (int j = 1 ; j <= nargs; ++j )
1681+ lua_pushvalue (L, j );
16081682
16091683 // call f, this pops the function and its args, pushes result(s)
16101684 const int err = lua_pcall (L, nargs, LUA_MULTRET , 0 );
16111685 if (err == LUABRIDGE_LUA_OK )
16121686 {
1613- // calculate number of return values and return
1614- return lua_gettop (L) - nargs - 4 ; // 4: overloads, errors, key, table
1687+ // 2 extra items on stack below results: idx_funcs, idx_errors
1688+ return lua_gettop (L) - nargs - 2 ;
16151689 }
16161690 else if (err == LUA_ERRRUN )
16171691 {
@@ -1620,10 +1694,8 @@ inline int try_overload_functions(lua_State* L)
16201694 }
16211695 else
16221696 {
1623- return lua_error_x (L); // critical error: rethrow
1697+ return lua_error_x (L); // critical error: rethrow
16241698 }
1625-
1626- lua_pop (L, 1 ); // pop value (table)
16271699 }
16281700
16291701 lua_Debug debug;
0 commit comments