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
35#include <array>
36#include <cstddef>
37#include <meta>
38#include <string>
39#include <type_traits>
40#include <utility>
41
42#include <welder/welder.hpp> // welder::welder + the rod contract + driver
43#include <welder/rods/python/doc_style.hpp> // welder::rods::python::google_style
44#include <welder/rods/python/operators.hpp> // welder::rods::python::operator_dunder
45#include <welder/rods/python/trampoline.hpp> // trampoline_for / gate / coverage
46#include <welder/bind_traits.hpp>// param_types / param_names / aggregate_fields
47#include <welder/doc.hpp> // function_docstring
48
49#include <nanobind/nanobind.h>
50
51namespace welder::inline v0::rods::nanobind {
52
53// Inside `welder::rods::nanobind`, the unqualified name `nanobind` resolves to *this*
54// namespace, not the library. Alias the real one once — the way nanobind's own
55// docs spell it — and use `nb::` for every library reference below.
56namespace nb = ::nanobind;
57
76template <::welder::doc_style DocStyle = ::welder::rods::python::google_style>
77struct rod {
78 static constexpr lang language{lang::py};
79 using module_type = nb::module_;
80
85 template <class E> using enum_handle_type = nb::enum_<E>;
86
87 protected:
88 // --- implementation helpers (not part of the welder::rod contract) --
89
116 template <class T>
117 static constexpr bool _needs_registration =
118 std::is_enum_v<std::remove_cvref_t<T>> ||
119 nb::detail::is_base_caster_v<nb::detail::make_caster<T>>;
120
126 static consteval nb::rv_policy _rv_policy(::welder::rv_kind k) {
127 switch (k) {
128 case ::welder::rv_kind::automatic: return nb::rv_policy::automatic;
129 case ::welder::rv_kind::automatic_reference: return nb::rv_policy::automatic_reference;
130 case ::welder::rv_kind::take_ownership: return nb::rv_policy::take_ownership;
131 case ::welder::rv_kind::copy: return nb::rv_policy::copy;
132 case ::welder::rv_kind::move: return nb::rv_policy::move;
133 case ::welder::rv_kind::reference: return nb::rv_policy::reference;
134 case ::welder::rv_kind::reference_internal: return nb::rv_policy::reference_internal;
135 case ::welder::rv_kind::none: return nb::rv_policy::none;
136 }
137 return nb::rv_policy::automatic;
138 }
139
157 template <std::meta::info Fn, class Def, std::size_t... I, std::size_t... K>
158 static void _def_function(const char* name, Def def_into,
159 std::index_sequence<I...>, std::index_sequence<K...>) {
160 static constexpr auto names{::welder::detail::param_names<Fn>()};
161 static constexpr auto ka{::welder::detail::keep_alive_pairs<Fn>()};
163 constexpr nb::rv_policy rvp{_rv_policy(::welder::return_policy_of(Fn, language))};
166 if (doc.empty())
167 def_into(name, &[:Fn:], nb::arg(names[I])..., rvp,
168 nb::keep_alive<ka[K].nurse, ka[K].patient>()...);
169 else
170 def_into(name, &[:Fn:], doc.c_str(), nb::arg(names[I])..., rvp,
171 nb::keep_alive<ka[K].nurse, ka[K].patient>()...);
172 } else {
173 if (doc.empty())
174 def_into(name, &[:Fn:], rvp,
175 nb::keep_alive<ka[K].nurse, ka[K].patient>()...);
176 else
177 def_into(name, &[:Fn:], doc.c_str(), rvp,
178 nb::keep_alive<ka[K].nurse, ka[K].patient>()...);
179 }
180 }
181
184 template <std::meta::info Fn, class Def>
185 static void _def_function(const char* name, Def def_into) {
187 name, def_into,
188 std::make_index_sequence<std::meta::parameters_of(Fn).size()>{},
189 std::make_index_sequence<
191 }
192
200 template <std::meta::info Ctor, std::size_t... I>
201 static void _def_init(auto& cls, std::index_sequence<I...>) {
202 static constexpr auto params{::welder::detail::param_types<Ctor>()};
203 static constexpr auto names{::welder::detail::param_names<Ctor>()};
205 cls.def(nb::init<typename [:params[I]:]...>(), nb::arg(names[I])...);
206 else
207 cls.def(nb::init<typename [:params[I]:]...>());
208 }
209
221 template <class T, std::size_t... I>
222 static void _def_aggregate_init(auto& cls, std::index_sequence<I...>) {
223 static constexpr auto fields{::welder::detail::aggregate_fields<T>()};
224 cls.def(
225 "__init__",
226 [](T* self, typename [:std::meta::type_of(fields[I]):]... args) {
227 new (self) T{std::move(args)...};
228 },
229 nb::arg(std::define_static_string(
230 std::meta::identifier_of(fields[I])))...);
231 }
232
247 static void _install_live_properties(nb::module_& m, nb::dict props) {
248 auto builtins{nb::module_::import_("builtins")};
249 auto subclass{builtins.attr("type")(
250 nb::str("welder_live_module"),
251 nb::make_tuple(m.attr("__class__")), props)};
252 // Stamp the module's own name onto the dynamically-created class, which would
253 // otherwise carry `__module__ == "_frozen_importlib"` (the module-init frame).
254 // Keeps functions welded onto `m` after this swap (e.g. a `weld_function`
255 // following a mutable `weld_variable`) correctly attributed in stubs.
256 subclass.attr("__module__") = m.attr("__name__");
257 m.attr("__class__") = subclass;
258 }
259
275 template <class T, class Trampoline, auto Bases, std::size_t... I>
276 static auto _make_class(nb::handle scope, const char* name, const char* doc,
277 std::index_sequence<I...>) {
278 if constexpr (std::is_void_v<Trampoline>) {
279 if (doc)
280 return nb::class_<T, typename [:Bases[I]:]...>(scope, name, doc);
281 return nb::class_<T, typename [:Bases[I]:]...>(scope, name);
282 } else {
283 // The trampoline is an extra `nb::class_` template argument (nanobind
284 // accepts base and trampoline in either order); Python subclasses then
285 // instantiate it, so their overrides capture C++ virtual calls.
286 if (doc)
287 return nb::class_<T, Trampoline, typename [:Bases[I]:]...>(scope, name, doc);
288 return nb::class_<T, Trampoline, typename [:Bases[I]:]...>(scope, name);
289 }
290 }
291
296 template <class T, auto Bases, std::size_t... I>
297 static auto _make_class_at(nb::handle scope, const char* name,
298 const char* doc, std::index_sequence<I...> seq) {
299 namespace py = ::welder::rods::python;
300 if constexpr (py::has_virtual_methods(^^T)) {
301 // Resolve the trampoline: an explicit `trampoline_for<T>` wins; otherwise
302 // scan T's namespace for a `[[=trampoline]]`-annotated subclass.
303 constexpr auto scanned{py::scanned_trampoline_of(^^T)};
304 static_assert(
305 py::trampoline_for<T> != std::meta::info{} || !scanned.ambiguous,
306 "welder: more than one [[=welder::rods::python::trampoline]] class in "
307 "this namespace derives from T; disambiguate by specializing "
308 "welder::rods::python::trampoline_for<T>.");
309 constexpr std::meta::info tramp{py::trampoline_for<T> != std::meta::info{}
310 ? py::trampoline_for<T>
311 : scanned.type};
312 if constexpr (tramp != std::meta::info{}) {
313 using Trampoline = [:tramp:];
314 static_assert(
315 py::trampoline_covers(^^T, ^^Trampoline),
316 "welder: the trampoline registered for this type does not "
317 "override all of its virtual methods; every virtual needs an "
318 "override forwarding to Python (see WELDER_PY_OVERRIDE).");
319 return _make_class<T, Trampoline, Bases>(scope, name, doc, seq);
320 } else {
321 static_assert(
322 py::bound_flat(^^T),
323 "welder: this welded type has virtual methods but no trampoline "
324 "is registered, so a Python subclass could not override them. "
325 "Register one — a [[=welder::rods::python::trampoline]] subclass "
326 "in T's namespace, or a welder::rods::python::trampoline_for<T> "
327 "specialization — or annotate T with "
328 "[[=welder::rods::python::bind_flat]] to bind it non-overridably.");
329 return _make_class<T, void, Bases>(scope, name, doc, seq);
330 }
331 } else {
332 return _make_class<T, void, Bases>(scope, name, doc, seq);
333 }
334 }
335
336 public:
337 // --- caster oracle + emission primitives (the welder::rod contract) --
338
342 template <class T>
344
347 static consteval const char* special_method_name(std::meta::info op_fn) {
348 return ::welder::rods::python::operator_dunder(op_fn);
349 }
350
351 // --- class binding ------------------------------------------------------
352
357 template <class T>
360
370 template <class T, auto Bases, std::size_t... I>
371 static auto make_class(module_type& m, const char* name, const char* doc,
372 std::index_sequence<I...> seq) {
373 return _make_class_at<T, Bases>(m, name, doc, seq);
374 }
375
381 template <class T, auto Bases, std::size_t... I>
382 static auto make_nested_class(module_type&, auto& outer_cls, const char* name,
383 const char* doc, std::index_sequence<I...> seq) {
384 return _make_class_at<T, Bases>(outer_cls, name, doc, seq);
385 }
386
393 template <class T>
395 std::declval<module_type&>(), nullptr, nullptr, std::index_sequence<>{}));
396
402 template <class T, auto Ctors, bool HasDefault, bool Aggregate>
403 static void add_constructors(auto& cls) {
404 if constexpr (HasDefault)
405 cls.def(nb::init<>());
406 template for (constexpr auto ctor : std::define_static_array(Ctors)) {
407 _def_init<ctor>(cls, std::make_index_sequence<
408 std::meta::parameters_of(ctor).size()>{});
409 }
410 if constexpr (Aggregate) {
411 constexpr auto fields{::welder::detail::aggregate_fields<T>()};
412 _def_aggregate_init<T>(cls, std::make_index_sequence<fields.size()>{});
413 }
414 }
415
425 template <std::meta::info Mem, class Style = ::welder::naming::none>
426 static void add_field(auto& cls) {
427 constexpr const char* name{
429 constexpr const char* doc{::welder::doc_of<Mem>()};
430 if constexpr (!std::meta::is_public(Mem)) {
431 // A protected member (admitted under policy::weld_protected) binds
432 // as a property over welder::detail::field_access — gcc-16 rejects
433 // the dependent `&[:Mem:]` for protected data (see field_access).
434 // Same semantics as def_rw: reference_internal getter.
436 if constexpr (std::meta::is_const_type(std::meta::type_of(Mem))) {
437 if constexpr (doc)
438 cls.def_prop_ro(name, &fa::get,
439 nb::rv_policy::reference_internal, doc);
440 else
441 cls.def_prop_ro(name, &fa::get,
442 nb::rv_policy::reference_internal);
443 } else {
444 if constexpr (doc)
445 cls.def_prop_rw(name, &fa::get, &fa::set,
446 nb::rv_policy::reference_internal, doc);
447 else
448 cls.def_prop_rw(name, &fa::get, &fa::set,
449 nb::rv_policy::reference_internal);
450 }
451 } else if constexpr (std::meta::is_const_type(std::meta::type_of(Mem))) {
452 // const member: read-only (def_rw's setter would not compile).
453 if constexpr (doc)
454 cls.def_ro(name, &[:Mem:], doc);
455 else
456 cls.def_ro(name, &[:Mem:]);
457 } else {
458 if constexpr (doc)
459 cls.def_rw(name, &[:Mem:], doc);
460 else
461 cls.def_rw(name, &[:Mem:]);
462 }
463 }
464
467 template <auto Fns, class Style = ::welder::naming::none>
468 static void add_method(auto& cls) {
469 constexpr const char* name{
471 template for (constexpr auto fn : std::define_static_array(Fns)) {
472 _def_function<fn>(name, [&cls](auto&&... a) {
473 cls.def(std::forward<decltype(a)>(a)...);
474 });
475 }
476 }
477
479 template <auto Fns, class Style = ::welder::naming::none>
480 static void add_static_method(auto& cls) {
481 constexpr const char* name{
482 ::welder::name_of<Fns[0], language, Style,
484 template for (constexpr auto fn : std::define_static_array(Fns)) {
485 _def_function<fn>(name, [&cls](auto&&... a) {
486 cls.def_static(std::forward<decltype(a)>(a)...);
487 });
488 }
489 }
490
493 template <auto Fns>
494 static void add_operator(auto& cls) {
495 constexpr const char* name{::welder::rods::python::operator_dunder(Fns[0])};
496 template for (constexpr auto fn : std::define_static_array(Fns)) {
497 _def_function<fn>(name, [&cls](auto&&... a) {
498 cls.def(std::forward<decltype(a)>(a)...);
499 });
500 }
501 }
502
503 // --- enum binding -------------------------------------------------------
504
511 template <class E>
512 static auto make_enum(module_type& m, const char* name, const char* doc) {
513 // A non-null doc is the enum docstring (a bare const char* extra); nullptr
514 // is branched out rather than passed.
515 if (doc)
516 return nb::enum_<E>(m, name, doc, nb::is_arithmetic());
517 return nb::enum_<E>(m, name, nb::is_arithmetic());
518 }
519
524 template <class E>
525 static auto make_nested_enum(module_type&, auto& outer_cls, const char* name,
526 const char* doc) {
527 if (doc)
528 return nb::enum_<E>(outer_cls, name, doc, nb::is_arithmetic());
529 return nb::enum_<E>(outer_cls, name, nb::is_arithmetic());
530 }
531
533 template <std::meta::info Enum, class Style = ::welder::naming::none>
534 static void add_enumerator(auto& e) {
535 e.value(
537 [:Enum:]);
538 }
539
542 template <class E>
543 static void finish_enum(auto& e) {
544 // Mirror C++ scope semantics: an unscoped enum's enumerators are visible
545 // unqualified, so export them into the enclosing scope; a scoped enum's
546 // are reached as E.Value, so leave them scoped.
547 if constexpr (!std::is_scoped_enum_v<E>)
548 e.export_values();
549 }
550
551 // --- namespace / module binding -----------------------------------------
552
556 static nb::dict open_module(module_type&) { return nb::dict{}; }
557
562 static void set_module_doc(module_type& m, const char* doc) {
563 m.attr("__doc__") = doc;
564 }
565
573 template <auto Fns, class Style = ::welder::naming::none>
574 static nb::object add_function(module_type& m, const char* name = nullptr) {
575 const char* fn_name{::welder::name_of_or<Fns[0], language, Style,
577 template for (constexpr auto fn : std::define_static_array(Fns)) {
578 _def_function<fn>(fn_name, [&m](auto&&... a) {
579 m.def(std::forward<decltype(a)>(a)...);
580 });
581 }
582 return m.attr(fn_name);
583 }
584
591 template <std::meta::info Var, class Style = ::welder::naming::none>
592 static void add_variable(module_type& m, nb::dict& live,
593 const char* name_override = nullptr) {
594 const char* name{
596 name_override)};
597 if constexpr (std::meta::is_const_type(std::meta::type_of(Var))) {
598 m.attr(name) = [:Var:]; // immutable: a value snapshot at bind time
599 } else {
600 // Mutable: a live property over the C++ global. The descriptors take a
601 // leading `self` (the module) and ignore it.
602 auto property{nb::module_::import_("builtins").attr("property")};
603 live[name] = property(
604 nb::cpp_function([](nb::object) { return [:Var:]; }),
605 nb::cpp_function([](nb::object,
606 typename [:std::meta::type_of(Var):] v) {
607 [:Var:] = v;
608 }));
609 }
610 }
611
613 static module_type add_submodule(module_type& m, const char* name) {
614 return m.def_submodule(name);
615 }
616
618 static void close_module(module_type& m, nb::dict& live) {
619 if (live.size() != 0)
621 }
622};
623
624static_assert(::welder::rod<rod<>>,
625 "welder::rods::nanobind::rod<> must satisfy welder::rod");
626
627} // namespace welder::rods::nanobind
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
Language-agnostic documentation layer: read [[=welder::doc(...)]] annotations off reflected entities ...
Docstring styles shared by welder's Python backends.
consteval auto aggregate_fields()
The fields an aggregate is initialized from: its non-static data members in declaration order (all pu...
consteval auto param_types()
A function's parameter types, as a static array of reflections.
consteval bool all_params_named()
Whether every parameter of Fn carries an identifier.
consteval auto param_names()
A function's parameter names, in order.
consteval auto keep_alive_pairs()
The keep_alive dependencies declared on Fn, in declaration order.
consteval const char * operator_dunder(std::meta::info f)
The Python special-method ("dunder") name for a member operator, or nullptr if welder does not expose...
Definition operators.hpp:39
consteval std::meta::info construction_type_of()
The type welder constructs when binding T: its registered/annotated trampoline if one exists,...
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
@ py
Python (via the pybind11 and nanobind backends).
Definition lang.hpp:43
rv_kind
How a bound callable's returned object is owned/converted in the target language — welder's backend-n...
@ static_method
a static member function → transform_static_method.
Definition naming.hpp:244
@ function
a free function → transform_function.
Definition naming.hpp:245
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
std::string function_docstring()
The complete docstring for function Fn under Style.
Definition doc.hpp:276
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 const char * doc_of()
The doc text on Ent (a class, namespace, function, or parameter), or nullptr.
Definition doc.hpp:138
consteval rv_kind return_policy_of(std::meta::info fn, lang L)
The return-value policy declared on callable fn for language L.
Definition reflect.hpp:229
The C++-operator → Python special-method ("dunder") map shared by welder's Python backends.
Splice-based accessors for data member Mem — the pointer-to-member-free route the rods bind a protect...
nb::module_ module_type
nanobind's module handle.
Definition rod.hpp:79
static void _def_aggregate_init(auto &cls, std::index_sequence< I... >)
Synthesize a field constructor for a baseless aggregate T.
Definition rod.hpp:222
static consteval nb::rv_policy _rv_policy(::welder::rv_kind k)
Map welder's neutral welder::rv_kind to nanobind's rv_policy.
Definition rod.hpp:126
static consteval const char * special_method_name(std::meta::info op_fn)
Map a member operator to its Python dunder (nullptr = not exposed).
Definition rod.hpp:347
static nb::object add_function(module_type &m, const char *name=nullptr)
Bind free-function overload group Fns as one module-level function (name from Fns[0]; one chained ....
Definition rod.hpp:574
nb::enum_< E > enum_handle_type
The enum handle make_enum yields — exactly its return type.
Definition rod.hpp:85
static auto make_nested_class(module_type &, auto &outer_cls, const char *name, const char *doc, std::index_sequence< I... > seq)
Create the nb::class_ for a nested member type T, registered under its enclosing type's class handle ...
Definition rod.hpp:382
static constexpr lang language
welder::lang::py.
Definition rod.hpp:78
static void add_constructors(auto &cls)
Bind the default constructor.
Definition rod.hpp:403
static void set_module_doc(module_type &m, const char *doc)
Set the (sub)module docstring.
Definition rod.hpp:562
static void add_enumerator(auto &e)
Add enumerator Enum to the enum handle.
Definition rod.hpp:534
static void _def_function(const char *name, Def def_into, std::index_sequence< I... >, std::index_sequence< K... >)
Register the function/method reflected by Fn onto a nanobind target.
Definition rod.hpp:158
static auto _make_class(nb::handle scope, const char *name, const char *doc, std::index_sequence< I... >)
Construct nb::class_<T, NativeBases...> from a reflected base-type array.
Definition rod.hpp:276
static void add_method(auto &cls)
Bind method overload group Fns (name from Fns[0]; nanobind chains one .def per overload and dispatche...
Definition rod.hpp:468
static void add_variable(module_type &m, nb::dict &live, const char *name_override=nullptr)
Bind namespace variable Var as a module attribute.
Definition rod.hpp:592
static void _def_function(const char *name, Def def_into)
Convenience overload: derive the parameter and keep_alive index sequences from Fn.
Definition rod.hpp:185
static void close_module(module_type &m, nb::dict &live)
Close the session: apply any accumulated live properties.
Definition rod.hpp:618
static void _install_live_properties(nb::module_ &m, nb::dict props)
Give module m live get/set semantics for the names in props.
Definition rod.hpp:247
static void _def_init(auto &cls, std::index_sequence< I... >)
Register nb::init<P0, P1, …>() for constructor Ctor.
Definition rod.hpp:201
static auto _make_class_at(nb::handle scope, const char *name, const char *doc, std::index_sequence< I... > seq)
The trampoline-aware class factory over an arbitrary registration scope — the shared body of make_cla...
Definition rod.hpp:297
static void add_static_method(auto &cls)
Bind static-method overload group Fns.
Definition rod.hpp:480
static void add_operator(auto &cls)
Bind operator overload group Fns under its Python dunder (one operator + arity per group,...
Definition rod.hpp:494
static auto make_nested_enum(module_type &, auto &outer_cls, const char *name, const char *doc)
Create the nb::enum_<E> for a nested member enum, scoped to its enclosing type's class handle — Pytho...
Definition rod.hpp:525
static void finish_enum(auto &e)
Finalize enum E: export an unscoped enum's values into the enclosing scope.
Definition rod.hpp:543
static constexpr bool has_native_caster
caster_oracle: T is convertible without welder registering a class for it iff nanobind does not fall ...
Definition rod.hpp:343
decltype(make_class< T, std::array< std::meta::info, 0 >{}>( std::declval< module_type & >(), nullptr, nullptr, std::index_sequence<>{})) class_handle_type
The class handle make_class yields for T — exactly its return type for a base-less T (so it captures ...
Definition rod.hpp:394
static module_type add_submodule(module_type &m, const char *name)
Create a submodule named name under m.
Definition rod.hpp:613
static void add_field(auto &cls)
Bind data member Mem as an attribute.
Definition rod.hpp:426
static auto make_enum(module_type &m, const char *name, const char *doc)
Create the nb::enum_<E> handle (a non-null doc becomes its docstring).
Definition rod.hpp:512
static nb::dict open_module(module_type &)
Open a per-module session: a dict accumulating live (mutable-variable) properties; _install_live_prop...
Definition rod.hpp:556
static auto make_class(module_type &m, const char *name, const char *doc, std::index_sequence< I... > seq)
Create the nb::class_<T, Bases…> handle, weaving in a trampoline when T is a welded virtual type with...
Definition rod.hpp:371
static constexpr bool _needs_registration
Whether nanobind can only convert T via runtime class registration.
Definition rod.hpp:117
[:::welder::rods::python::construction_type_of< T >() :] construction_type
The type welder constructs when binding T — its registered trampoline if one exists,...
Definition rod.hpp:358
Virtual-function overriding support shared by welder's Python backends.
welder's binding entry point: the welder::welder struct.