welder 0.1.0
Bindings for annotated C++ types, from C++26 reflection
Loading...
Searching...
No Matches
rod.hpp
Go to the documentation of this file.
1#pragma once
67#include <array>
68#include <cstddef>
69#include <functional>
70#include <meta>
71#include <type_traits>
72#include <utility>
73#include <vector>
74
75#include <welder/welder.hpp> // welder::welder + rod contract + driver
76#include <welder/rods/lua/sol2/metamethods.hpp> // operator -> sol2 metamethod map
77#include <welder/bind_traits.hpp> // aggregate_* helpers
78
79#include <sol/sol.hpp>
80
81namespace welder::inline v0::rods::sol2 {
82
83// Note: the namespace is `welder::rods::sol2`; the library namespace is `::sol`. They do
84// not collide, so `sol::` below refers to the library without an alias (unlike the
85// Python backends, whose namespace shadowed the library's).
86
89template <class T, class... A>
90using ctor_sig = T(A...);
91
103struct rod {
104 static constexpr lang language{lang::lua};
105 using module_type = ::sol::table;
106
116 struct session {
117 ::sol::table getters{};
118 ::sol::table setters{};
119 bool has_live{false};
120 };
121
122 protected:
123 // --- implementation helpers (not part of the welder::rod contract) --
124
150 template <class T>
151 static constexpr bool _needs_registration =
152 std::is_enum_v<std::remove_cvref_t<T>> ||
153 ::sol::lua_type_of<std::remove_cvref_t<T>>::value == ::sol::type::userdata;
154
163 template <class E>
165 ::sol::table values;
166 std::function<void(const char*, lua_Integer)>
168 bool scoped;
169 };
170
187 template <class T, auto Ctors, bool HasDefault, bool Aggregate>
188 static consteval std::vector<std::meta::info> _ctor_signatures() {
189 std::vector<std::meta::info> sigs;
190 auto sig = [](std::vector<std::meta::info> targs) {
191 return std::meta::dealias(std::meta::substitute(^^ctor_sig, targs));
192 };
193 if (HasDefault)
194 sigs.push_back(sig({^^T}));
195 for (auto c : Ctors) {
196 std::vector<std::meta::info> targs{^^T};
197 for (auto p : std::meta::parameters_of(c))
198 targs.push_back(std::meta::type_of(p));
199 sigs.push_back(sig(targs));
200 }
201 if (Aggregate) {
202 std::vector<std::meta::info> targs{^^T};
204 targs.push_back(std::meta::type_of(fld));
205 sigs.push_back(sig(targs));
206 }
207 return sigs;
208 }
209
211 template <class T, auto Ctors, bool HasDefault, bool Aggregate>
212 static consteval auto _ctor_sigs_array() {
213 constexpr std::size_t n{
215 std::array<std::meta::info, n> out{};
216 // Guard the fill: std::array<T, 0>::operator[] is not consteval, so it must
217 // not be instantiated for a type with no exposed constructors.
218 if constexpr (n != 0) {
220 for (std::size_t i{0}; i < n; ++i)
221 out[i] = v[i];
222 }
223 return out;
224 }
225
232 template <class T, auto Sigs, std::size_t... I>
233 static void _set_constructors(::sol::usertype<T>& ut,
234 std::index_sequence<I...>) {
235 // Expose both the call form `T(…)` and the idiomatic `T.new(…)`.
236 ut[::sol::call_constructor] = ::sol::constructors<typename [:Sigs[I]:]...>();
237 ut["new"] = ::sol::constructors<typename [:Sigs[I]:]...>();
238 }
239
240
252 template <class T, auto Bases, std::size_t... I>
253 static auto _make_usertype(::sol::table& m, const char* name,
254 std::index_sequence<I...>) {
255 // sol::no_constructor suppresses sol2's implicit constructor; the real set
256 // is installed afterwards by add_constructors (the driver's one call).
257 if constexpr (sizeof...(I) == 0) {
258 return ::sol::usertype<T>{m.new_usertype<T>(name, ::sol::no_constructor)};
259 } else {
260 return ::sol::usertype<T>{m.new_usertype<T>(
261 name, ::sol::no_constructor, ::sol::base_classes,
262 ::sol::bases<typename [:Bases[I]:]...>())};
263 }
264 }
265
281 static consteval void _collect_welded_bases(
282 std::meta::info type, lang L, std::vector<std::meta::info>& out) {
283 for (auto base : ::welder::public_bases(type)) {
284 const bool welded{::welder::welded_for(base, L)};
285 if (welded) {
286 bool seen{false};
287 for (auto e : out)
288 if (e == base) {
289 seen = true;
290 break;
291 }
292 if (seen)
293 continue;
294 out.push_back(base);
295 }
296 _collect_welded_bases(base, L, out); // descend (into welded bases too)
297 }
298 }
299
303 template <class T>
304 static consteval auto _welded_bases_array() {
305 constexpr std::size_t n{[] {
306 std::vector<std::meta::info> v;
308 return v.size();
309 }()};
310 std::array<std::meta::info, n> out{};
311 // Guard the fill: std::array<T, 0>::operator[] is not consteval.
312 if constexpr (n != 0) {
313 std::vector<std::meta::info> v;
315 for (std::size_t i{0}; i < n; ++i)
316 out[i] = v[i];
317 }
318 return out;
319 }
320
332 template <auto Grp, class Target, std::size_t... I>
333 static void _register_named(Target& t, const char* name,
334 std::index_sequence<I...>) {
335 // sol2 owns a returned object structurally (a value → a Lua-owned
336 // copy/move; a pointer/reference → a non-owning view), so a
337 // [[=welder::return_policy]] has no runtime effect here — but a
338 // self-contradictory one (a reference to a returned temporary) is still
339 // rejected, uniformly with the Python rods.
341 if constexpr (sizeof...(I) == 1)
342 t[name] = &[:Grp[0]:];
343 else
344 t[name] = ::sol::overload(&[:Grp[I]:]...);
345 }
346
353 template <auto Grp, class Target, std::size_t... I>
354 static void _register_operator(Target& t, std::index_sequence<I...>) {
356 constexpr auto slot{operator_mm(Grp[0]).fn};
357 if constexpr (sizeof...(I) == 1)
358 t[slot] = &[:Grp[0]:];
359 else
360 t[slot] = ::sol::overload(&[:Grp[I]:]...);
361 }
362
363 // --- live namespace-variable proxy --------------------------------------
364
368 static ::sol::object _prev_index(::sol::this_state ts, const ::sol::object& prev,
369 const ::sol::table& self, const ::sol::object& key) {
370 if (prev.get_type() == ::sol::type::function)
371 return ::sol::object{prev.as<::sol::function>()(self, key)};
372 if (prev.get_type() == ::sol::type::table)
373 return ::sol::object{prev.as<::sol::table>()[key]};
374 return ::sol::make_object(ts, ::sol::lua_nil);
375 }
376
380 static void _prev_newindex(const ::sol::object& prev, ::sol::table self,
381 const ::sol::object& key, const ::sol::object& value) {
382 if (prev.get_type() == ::sol::type::function) {
383 prev.as<::sol::function>()(self, key, value);
384 return;
385 }
386 if (prev.get_type() == ::sol::type::table) {
387 prev.as<::sol::table>()[key] = value;
388 return;
389 }
390 self.raw_set(key, value);
391 }
392
403 static void _install_live_variables(module_type& m, ::sol::table getters,
404 ::sol::table setters) {
405 lua_State* L{m.lua_state()};
406 ::sol::state_view lua{L};
407 // Fetch any existing metatable via the raw C API: reading it through a
408 // checked sol::table would fail SOL_ALL_SAFETIES_ON when there is none (nil
409 // is not a table). A pre-existing metatable (a prior live-variable install
410 // onto the same table) is reused and chained; otherwise a fresh one is made.
411 m.push(); // [module]
412 const bool had_mt{lua_getmetatable(L, -1) != 0}; // [module, mt?]
413 ::sol::table mt{had_mt ? ::sol::stack::pop<::sol::table>(L)
414 : lua.create_table()};
415 lua_pop(L, 1); // pop [module]
416 // Capture any metamethods to chain (a state-bound nil when absent, so the
417 // fallback helpers can safely query get_type()).
418 ::sol::object prev_index{mt["__index"]};
419 ::sol::object prev_newindex{mt["__newindex"]};
420
421 mt["__index"] = [getters, prev_index](::sol::this_state ts, ::sol::table self,
422 ::sol::object key) -> ::sol::object {
423 ::sol::object g{getters[key]};
424 if (g.get_type() == ::sol::type::function)
425 return ::sol::object{g.as<::sol::function>()()}; // live read of the C++ global
426 return _prev_index(ts, prev_index, self, key);
427 };
428 mt["__newindex"] = [setters, prev_newindex](::sol::table self, ::sol::object key,
429 ::sol::object value) {
430 ::sol::object s{setters[key]};
431 if (s.get_type() == ::sol::type::function) {
432 s.as<::sol::function>()(value); // live write through to the C++ global
433 return; // no rawset: the key stays live
434 }
435 _prev_newindex(prev_newindex, self, key, value);
436 };
437 m[::sol::metatable_key] = mt;
438 }
439
440 public:
441 // --- caster oracle + emission primitives (the welder::rod contract) --
442
447 template <class T> using class_handle_type = ::sol::usertype<T>;
448 template <class E> using enum_handle_type = _enum_binding<E>;
449
454 template <class T>
456
459 static consteval const char* special_method_name(std::meta::info op_fn) {
460 return operator_mm(op_fn).name;
461 }
462
463 // --- class binding ------------------------------------------------------
464
473 template <class T, auto Bases, std::size_t... I>
474 static auto make_class(module_type& m, const char* name, const char* /*doc*/,
475 std::index_sequence<I...> /*seq*/) {
476 constexpr auto bases{_welded_bases_array<T>()};
478 m, name, std::make_index_sequence<bases.size()>{});
479 }
480
491 template <class T, auto Bases, std::size_t... I>
492 static auto make_nested_class(module_type& m, auto& outer, const char* name,
493 const char* doc, std::index_sequence<I...> seq) {
494 auto ut{make_class<T, Bases>(m, name, doc, seq)};
495 outer[name] = ut; // Outer.Inner = the usertype (a lua reference
496 // value registers as a static entry)
497 m[name] = ::sol::lua_nil; // drop the temporary module-scope key
498 return ut;
499 }
500
505 template <class T, auto Ctors, bool HasDefault, bool Aggregate>
506 static void add_constructors(::sol::usertype<T>& ut) {
508 if constexpr (sigs.size() != 0)
509 _set_constructors<T, sigs>(ut, std::make_index_sequence<sigs.size()>{});
510 }
511
518 template <std::meta::info Mem, class Style = ::welder::naming::none>
519 static void add_field(auto& ut) {
520 constexpr const char* name{
522 if constexpr (!std::meta::is_public(Mem)) {
523 // A protected member (admitted under policy::weld_protected) binds
524 // as a sol::property over welder::detail::field_access — gcc-16
525 // rejects the dependent `&[:Mem:]` for protected data (see
526 // field_access).
528 if constexpr (std::meta::is_const_type(std::meta::type_of(Mem)))
529 ut[name] = ::sol::readonly_property(&fa::get);
530 else
531 ut[name] = ::sol::property(&fa::get, &fa::set);
532 } else if constexpr (std::meta::is_const_type(std::meta::type_of(Mem))) {
533 ut[name] = ::sol::readonly(&[:Mem:]);
534 } else {
535 ut[name] = &[:Mem:];
536 }
537 }
538
542 template <auto Fns, class Style = ::welder::naming::none>
543 static void add_method(auto& ut) {
545 ut,
547 std::make_index_sequence<Fns.size()>{});
548 }
549
552 template <auto Fns, class Style = ::welder::naming::none>
553 static void add_static_method(auto& ut) {
555 ut,
556 ::welder::name_of<Fns[0], language, Style,
558 std::make_index_sequence<Fns.size()>{});
559 }
560
564 template <auto Fns>
565 static void add_operator(auto& ut) {
566 _register_operator<Fns>(ut, std::make_index_sequence<Fns.size()>{});
567 }
568
569 // --- enum binding -------------------------------------------------------
570
573 template <class E>
574 static auto make_enum(module_type& m, const char* name, const char* /*doc*/) {
575 ::sol::table values{::sol::state_view{m.lua_state()}.create_table()};
576 m[name] = values;
577 return _enum_binding<E>{
578 values, [m](const char* n, lua_Integer v) mutable { m[n] = v; },
579 std::is_scoped_enum_v<E>};
580 }
581
587 template <class E>
588 static auto make_nested_enum(module_type& m, auto& outer, const char* name,
589 const char* /*doc*/) {
590 ::sol::table values{::sol::state_view{m.lua_state()}.create_table()};
591 outer[name] = values; // a reference value registers as a static entry
592 return _enum_binding<E>{
593 values,
594 // sol::var marks the plain integer as a STATIC variable — a bare
595 // value assignment would be wrapped as a bound function.
596 [outer](const char* n, lua_Integer v) mutable {
597 outer[n] = ::sol::var(v);
598 },
599 std::is_scoped_enum_v<E>};
600 }
601
607 template <std::meta::info Enum, class Style = ::welder::naming::none>
608 static void add_enumerator(auto& e) {
609 constexpr const char* name{
611 const lua_Integer value{static_cast<lua_Integer>(std::to_underlying([:Enum:]))};
612 e.values[name] = value;
613 if (!e.scoped)
614 e.mirror(name, value);
615 }
616
619 template <class /*E*/>
620 static void finish_enum(auto&) {}
621
622 // --- namespace / module binding -----------------------------------------
623
627 static session open_module(module_type&) { return {}; }
628
631 static void set_module_doc(module_type&, const char*) {}
632
640 template <auto Fns, class Style = ::welder::naming::none>
641 static sol::object add_function(module_type& m, const char* name = nullptr) {
642 const char* fn_name{::welder::name_of_or<
643 Fns[0], language, Style, ::welder::ent_kind::function>(name)};
644 _register_named<Fns>(m, fn_name, std::make_index_sequence<Fns.size()>{});
645 return m.get<sol::object>(fn_name);
646 }
647
660 template <std::meta::info Var, class Style = ::welder::naming::none>
661 static void add_variable(module_type& m, session& s, const char* name = nullptr) {
662 const char* key{::welder::name_of_or<Var, language, Style,
664 if constexpr (std::meta::is_const_type(std::meta::type_of(Var))) {
665 m[key] = [:Var:]; // immutable: a value snapshot at load time
666 } else {
667 if (!s.has_live) {
668 ::sol::state_view lua{m.lua_state()};
669 s.getters = lua.create_table();
670 s.setters = lua.create_table();
671 s.has_live = true;
672 }
673 s.getters[key] = [] { return [:Var:]; };
674 s.setters[key] = [](typename [:std::meta::type_of(Var):] v) {
675 [:Var:] = v;
676 };
677 }
678 }
679
681 static module_type add_submodule(module_type& m, const char* name) {
682 ::sol::table sub{::sol::state_view{m.lua_state()}.create_table()};
683 m[name] = sub;
684 return sub;
685 }
686
689 static void close_module(module_type& m, session& s) {
690 if (s.has_live)
692 }
693};
694
695static_assert(::welder::rod<rod>,
696 "welder::rods::sol2::rod must satisfy welder::rod");
697
698} // namespace welder::rods::sol2
Backend-agnostic selection layer: the reflection predicates and selectors that decide what participat...
The contract a rod (a welder backend, welder::rods::…::rod) must satisfy to plug into the generic dri...
Definition concepts.hpp:206
consteval auto aggregate_fields()
The fields an aggregate is initialized from: its non-static data members in declaration order (all pu...
consteval metamethod operator_mm(std::meta::info f)
Map a member operator to its sol2 metamethod ({…, nullptr} = not exposed).
T(A...) ctor_sig
The alias T(A...) — a constructor call signature, built by substitute (a namespace-scope alias so it ...
Definition rod.hpp:90
consteval bool welded_for(std::meta::info type, lang L)
Is type welded for language L — i.e.
Definition reflect.hpp:26
consteval detail::doc_spec< N > doc(const char(&s)[N])
Attach a docstring to a namespace, class, function, or function parameter.
lang
The target languages welder ships rods for — but not the whole value space.
Definition lang.hpp:42
@ lua
Lua (via the sol2 and LuaBridge3 backends).
Definition lang.hpp:44
@ static_method
a static member function → transform_static_method.
Definition naming.hpp:244
@ function
a free function → transform_function.
Definition naming.hpp:245
@ variable
a namespace variable → transform_variable.
Definition naming.hpp:247
@ method
a member function → transform_method.
Definition naming.hpp:243
consteval void validate_return_policy()
Reject a return_policy on Fn (for language L) that contradicts Fn's return type.
Definition reflect.hpp:253
constexpr const char * name_of_or(const char *override_)
Resolve a bound name with a call-site override: override_ wins verbatim, nullptr falls back to name_o...
Definition naming.hpp:343
consteval const char * name_of()
The final bound name of Ent (a K-kind entity) for language L under name style Style.
Definition naming.hpp:294
consteval std::vector< std::meta::info > public_bases(std::meta::info type)
The types of the public base classes of type.
Definition reflect.hpp:307
The C++-operator → sol2 metamethod map, factored out of the sol2 backend.
Splice-based accessors for data member Mem — the pointer-to-member-free route the rods bind a protect...
::sol::meta_function fn
The sol2 metamethod slot.
const char * name
Its __name, or nullptr if not exposed.
A welded enum's binding: the name→value table, how to mirror a name onto the enclosing scope,...
Definition rod.hpp:164
std::function< void(const char *, lua_Integer)> mirror
Write one name onto the enclosing scope.
Definition rod.hpp:167
bool scoped
true for enum class.
Definition rod.hpp:168
::sol::table values
The E = { Name = value, … } table.
Definition rod.hpp:165
Per-module session: the deferred state for live namespace variables.
Definition rod.hpp:116
::sol::table getters
name → fun(): value (live reads).
Definition rod.hpp:117
bool has_live
any mutable variable registered?
Definition rod.hpp:119
::sol::table setters
name → fun(value) (live writes).
Definition rod.hpp:118
static void add_field(auto &ut)
Bind data member Mem as a usertype property.
Definition rod.hpp:519
static module_type add_submodule(module_type &m, const char *name)
Create a submodule table named name under m.
Definition rod.hpp:681
static consteval void _collect_welded_bases(std::meta::info type, lang L, std::vector< std::meta::info > &out)
Collect all welded ancestors of type (transitively), deduplicated.
Definition rod.hpp:281
static void set_module_doc(module_type &, const char *)
No runtime module docstring in Lua (its home is a generated stub).
Definition rod.hpp:631
::sol::usertype< T > class_handle_type
The class / enum handles the per-class / per-enum hooks operate on — exactly what make_class / make_e...
Definition rod.hpp:447
static auto make_class(module_type &m, const char *name, const char *, std::index_sequence< I... >)
Create the sol::usertype<T> handle (constructors are installed by the driver's subsequent add_constru...
Definition rod.hpp:474
static void _prev_newindex(const ::sol::object &prev, ::sol::table self, const ::sol::object &key, const ::sol::object &value)
Route a missing-key write on the module to a captured previous __newindex (function or table form); w...
Definition rod.hpp:380
static void add_static_method(auto &ut)
Bind static-method overload group Fns as a class-table function (T.name(…)), grouped as in add_method...
Definition rod.hpp:553
static void close_module(module_type &m, session &s)
Close the session: install the live-variable metatable proxy, if any mutable variable was welded onto...
Definition rod.hpp:689
static consteval const char * special_method_name(std::meta::info op_fn)
Map a member operator to its Lua metamethod name (nullptr = not exposed).
Definition rod.hpp:459
static void add_enumerator(auto &e)
Add enumerator Enum (as its underlying integer).
Definition rod.hpp:608
static auto make_nested_enum(module_type &m, auto &outer, const char *name, const char *)
Create the Name = value table for a nested member enum, placed on the enclosing type's usertype (modu...
Definition rod.hpp:588
static auto make_enum(module_type &m, const char *name, const char *)
Create the enum's Name = value table on the module (doc ignored).
Definition rod.hpp:574
static auto make_nested_class(module_type &m, auto &outer, const char *name, const char *doc, std::index_sequence< I... > seq)
Create the sol::usertype<T> for a nested member type, placed on the enclosing type's usertype table (...
Definition rod.hpp:492
static void _register_operator(Target &t, std::index_sequence< I... >)
Register operator group Grp under its Lua metamethod slot (a single callable, or a sol::overload(…) f...
Definition rod.hpp:354
_enum_binding< E > enum_handle_type
Definition rod.hpp:448
static void _install_live_variables(module_type &m, ::sol::table getters, ::sol::table setters)
Install (or extend) the module table's metatable so absent keys in getters / setters read and write t...
Definition rod.hpp:403
static sol::object add_function(module_type &m, const char *name=nullptr)
Bind free-function overload group Fns as one module-level function (a single callable,...
Definition rod.hpp:641
static consteval auto _ctor_sigs_array()
_ctor_signatures<T, …>() as a fixed-size, splice-ready static array.
Definition rod.hpp:212
static consteval std::vector< std::meta::info > _ctor_signatures()
The set of sol::constructors<…> signatures to expose for T, as function- type reflections T(A....
Definition rod.hpp:188
static consteval auto _welded_bases_array()
_collect_welded_bases for T as a fixed-size, splice-ready static array.
Definition rod.hpp:304
static void finish_enum(auto &)
No whole-enum finalizer needed (unscoped export is done per-enumerator).
Definition rod.hpp:620
static void add_constructors(::sol::usertype< T > &ut)
Install T's whole constructor set — exactly what sol2 wants (one sol::constructors<…> assignment cove...
Definition rod.hpp:506
static void add_variable(module_type &m, session &s, const char *name=nullptr)
Bind namespace variable Var onto the module.
Definition rod.hpp:661
static void _set_constructors(::sol::usertype< T > &ut, std::index_sequence< I... >)
Register T's constructor set on the usertype from the reflected signatures.
Definition rod.hpp:233
static auto _make_usertype(::sol::table &m, const char *name, std::index_sequence< I... >)
Create m.new_usertype<T, Bases…>(name) with sol2's base-class linkage.
Definition rod.hpp:253
static session open_module(module_type &)
Open a per-module session.
Definition rod.hpp:627
static constexpr lang language
welder::lang::lua.
Definition rod.hpp:104
static constexpr bool has_native_caster
caster_oracle: T converts without welder registering a usertype iff sol2 does not classify it as need...
Definition rod.hpp:455
::sol::object _prev_index(::sol::this_state ts, const ::sol::object &prev, const ::sol::table &self, const ::sol::object &key)
Route a missing-key read on the module to a captured previous __index (function or table form),...
Definition rod.hpp:368
::sol::table module_type
A Lua module is a table.
Definition rod.hpp:105
static void _register_named(Target &t, const char *name, std::index_sequence< I... >)
Register overload group Grp on target t under the (already name-styled) name — a single callable when...
Definition rod.hpp:333
static void add_operator(auto &ut)
Bind operator overload group Fns under its Lua metamethod (one operator + arity per group; each overl...
Definition rod.hpp:565
static constexpr bool _needs_registration
Whether sol2 can only convert T via runtime usertype registration.
Definition rod.hpp:151
static void add_method(auto &ut)
Bind method overload group Fns as one method (obj:name(…)) — a single callable when unique,...
Definition rod.hpp:543
welder's binding entry point: the welder::welder struct.