From 153d1fe76d68d16f53da2f68cc5cd91824ba6388 Mon Sep 17 00:00:00 2001 From: cfis Date: Wed, 31 Dec 2025 17:58:41 -0800 Subject: [PATCH 1/4] Update API and expose to Ruby. --- lib/rice/native_registry.rb | 13 ++++-------- rice/detail/Forwards.ipp | 36 ++++++++++++++++++-------------- rice/detail/NativeRegistry.hpp | 2 +- rice/detail/NativeRegistry.ipp | 6 +++--- rice/rice_api/NativeRegistry.ipp | 15 ++++++++++++- 5 files changed, 42 insertions(+), 30 deletions(-) diff --git a/lib/rice/native_registry.rb b/lib/rice/native_registry.rb index 3323c87c..114abcc8 100644 --- a/lib/rice/native_registry.rb +++ b/lib/rice/native_registry.rb @@ -1,21 +1,16 @@ module Rice class NativeRegistry def native_attributes(klass) - self.native_by_kind(klass, [Rice::NativeKind::AttributeReader, Rice::NativeKind::AttributeWriter]) + self.lookup_by_kind(klass, Rice::NativeKind::AttributeReader) + + self.lookup_by_kind(klass, Rice::NativeKind::AttributeWriter) end def native_methods(klass) - self.native_by_kind(klass, [Rice::NativeKind::Method]) + self.lookup_by_kind(klass, Rice::NativeKind::Method) end def native_functions(klass) - self.native_by_kind(klass, [Rice::NativeKind::Function]) - end - - def native_by_kind(klass, kinds) - self.lookup(klass).find_all do |native| - kinds.include?(native.kind) - end + self.lookup_by_kind(klass, Rice::NativeKind::Function) end end end \ No newline at end of file diff --git a/rice/detail/Forwards.ipp b/rice/detail/Forwards.ipp index 24429b80..b30216cc 100644 --- a/rice/detail/Forwards.ipp +++ b/rice/detail/Forwards.ipp @@ -8,14 +8,17 @@ namespace Rice::detail // Get wrapper class's method names to avoid conflicts std::set wrapperMethodSet; - std::vector wrapperMethods = Registries::instance.natives.lookup(wrapper_klass, NativeKind::Method); - wrapperMethodSet.insert(wrapperMethods.begin(), wrapperMethods.end()); - std::vector wrapperReaders = Registries::instance.natives.lookup(wrapper_klass, NativeKind::AttributeReader); - wrapperMethodSet.insert(wrapperReaders.begin(), wrapperReaders.end()); - std::vector wrapperWriters = Registries::instance.natives.lookup(wrapper_klass, NativeKind::AttributeWriter); - for (const std::string& writer : wrapperWriters) + for (Native* native : Registries::instance.natives.lookup(wrapper_klass, NativeKind::Method)) { - wrapperMethodSet.insert(writer + "="); + wrapperMethodSet.insert(native->name()); + } + for (Native* native : Registries::instance.natives.lookup(wrapper_klass, NativeKind::AttributeReader)) + { + wrapperMethodSet.insert(native->name()); + } + for (Native* native : Registries::instance.natives.lookup(wrapper_klass, NativeKind::AttributeWriter)) + { + wrapperMethodSet.insert(native->name() + "="); } // Get wrapped class's method names from the registry, including ancestor classes @@ -23,16 +26,17 @@ namespace Rice::detail Class klass(wrapped_klass); while (klass.value() != rb_cObject && klass.value() != Qnil) { - std::vector methods = Registries::instance.natives.lookup(klass.value(), NativeKind::Method); - wrappedMethodSet.insert(methods.begin(), methods.end()); - - std::vector readers = Registries::instance.natives.lookup(klass.value(), NativeKind::AttributeReader); - wrappedMethodSet.insert(readers.begin(), readers.end()); - - std::vector writers = Registries::instance.natives.lookup(klass.value(), NativeKind::AttributeWriter); - for (const std::string& writer : writers) + for (Native* native : Registries::instance.natives.lookup(klass.value(), NativeKind::Method)) + { + wrappedMethodSet.insert(native->name()); + } + for (Native* native : Registries::instance.natives.lookup(klass.value(), NativeKind::AttributeReader)) + { + wrappedMethodSet.insert(native->name()); + } + for (Native* native : Registries::instance.natives.lookup(klass.value(), NativeKind::AttributeWriter)) { - wrappedMethodSet.insert(writer + "="); + wrappedMethodSet.insert(native->name() + "="); } klass = klass.superclass(); diff --git a/rice/detail/NativeRegistry.hpp b/rice/detail/NativeRegistry.hpp index 5ba30268..b792930a 100644 --- a/rice/detail/NativeRegistry.hpp +++ b/rice/detail/NativeRegistry.hpp @@ -35,7 +35,7 @@ namespace Rice::detail const std::vector lookup(VALUE klass); const std::vector>& lookup(VALUE klass, ID methodId); - std::vector lookup(VALUE klass, NativeKind kind); + std::vector lookup(VALUE klass, NativeKind kind); private: // Key - Ruby klass/method diff --git a/rice/detail/NativeRegistry.ipp b/rice/detail/NativeRegistry.ipp index 2aff108c..18b02bb8 100644 --- a/rice/detail/NativeRegistry.ipp +++ b/rice/detail/NativeRegistry.ipp @@ -73,9 +73,9 @@ namespace Rice::detail return this->natives_[key]; } - inline std::vector NativeRegistry::lookup(VALUE klass, NativeKind kind) + inline std::vector NativeRegistry::lookup(VALUE klass, NativeKind kind) { - std::vector result; + std::vector result; if (rb_type(klass) == T_ICLASS) { @@ -93,7 +93,7 @@ namespace Rice::detail { if (native->kind() == kind) { - result.push_back(native->name()); + result.push_back(native.get()); } } } diff --git a/rice/rice_api/NativeRegistry.ipp b/rice/rice_api/NativeRegistry.ipp index 17674583..d227e5bf 100644 --- a/rice/rice_api/NativeRegistry.ipp +++ b/rice/rice_api/NativeRegistry.ipp @@ -17,5 +17,18 @@ inline void Init_Native_Registry() } return result; - }, Arg("klass").setValue()); + }, Arg("klass").setValue()). + + define_method("lookup_by_kind", [](detail::NativeRegistry& self, VALUE klass, detail::NativeKind kind) -> Array + { + Array result; + + const std::vector natives = self.lookup(klass, kind); + for (detail::Native* native : natives) + { + result.push(native, false); + } + + return result; + }, Arg("klass").setValue(), Arg("kind")); } From 7405609b347e5e3f8f18ed3a192128767a04ae32 Mon Sep 17 00:00:00 2001 From: cfis Date: Wed, 31 Dec 2025 21:57:10 -0800 Subject: [PATCH 2/4] Improved rbs and api doc generation. --- bin/rice-doc.rb | 2 ++ lib/rice/rbs.rb | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/bin/rice-doc.rb b/bin/rice-doc.rb index 559acbb0..c2f9f530 100644 --- a/bin/rice-doc.rb +++ b/bin/rice-doc.rb @@ -114,6 +114,8 @@ def parse_args FileUtils.mkdir_p(config.output) end +# Add the extension directory the path in case it ships with extra libraries +ENV["PATH"] = "#{File.dirname(config.extension)}#{File::PATH_SEPARATOR}#{ENV["PATH"]}" # Load the extension require config.extension diff --git a/lib/rice/rbs.rb b/lib/rice/rbs.rb index 2fa2799e..ad2114e7 100644 --- a/lib/rice/rbs.rb +++ b/lib/rice/rbs.rb @@ -12,7 +12,11 @@ def initialize(extension, output) def generate STDOUT << "Writing rbs files to #{@output}" << "\n" + + # Add the extension directory the path in case it ships with extra libraries + ENV["PATH"] = "#{File.dirname(self.extension)}#{File::PATH_SEPARATOR}#{ENV["PATH"]}" require self.extension + types = Registries.instance.types types.klasses.each do |klass| process_class(klass) From d831e4a1cdf31f82a7010715c4fa3ffa887c73ca Mon Sep 17 00:00:00 2001 From: cfis Date: Wed, 31 Dec 2025 21:57:26 -0800 Subject: [PATCH 3/4] Prep 4.9 release. --- CHANGELOG.md | 7 +++++++ lib/rice/version.rb | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71dfb1d2..88e9774a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 4.9.0 (2025-12-31 +This release revamps smart pointer support for `std::shared_ptr` and `std::unique_ptr`. + +Rice now always creates wrapper classes for smart pointers under the `Std` module (e.g., `Std::SharedPtr≺MyClass≻`, `Std::UniquePtr≺MyClass≻`). These wrapper classes expose methods like `empty?`, `get`, `swap`, and for shared_ptr, `use_count`. Methods defined on the managed type are automatically forwarded to the wrapper class using Ruby's `Forwardable` module. + +This change is backwards compatible for Ruby code but not C++ code. If you implemented your own Smart Pointer wrapper then please read the Smart Pointer documentation for more information. + ## 4.8.0 (2025-12-29) This release focuses on making Rice easier to use: diff --git a/lib/rice/version.rb b/lib/rice/version.rb index 9ae07e83..e1181ad2 100644 --- a/lib/rice/version.rb +++ b/lib/rice/version.rb @@ -1,3 +1,3 @@ module Rice - VERSION = "4.8.0" + VERSION = "4.9.0" end From 39780563db100507b4bbffeacaec12c6f29bbce6 Mon Sep 17 00:00:00 2001 From: cfis Date: Wed, 31 Dec 2025 21:57:49 -0800 Subject: [PATCH 4/4] Update headers. --- include/rice/api.hpp | 15 +- include/rice/rice.hpp | 288 +++++++++++++++++++++++++++++------- include/rice/stl.hpp | 331 +++++++++++++++++++++--------------------- 3 files changed, 417 insertions(+), 217 deletions(-) diff --git a/include/rice/api.hpp b/include/rice/api.hpp index a6d60157..a276cf92 100644 --- a/include/rice/api.hpp +++ b/include/rice/api.hpp @@ -79,7 +79,20 @@ inline void Init_Native_Registry() } return result; - }, Arg("klass").setValue()); + }, Arg("klass").setValue()). + + define_method("lookup_by_kind", [](detail::NativeRegistry& self, VALUE klass, detail::NativeKind kind) -> Array + { + Array result; + + const std::vector natives = self.lookup(klass, kind); + for (detail::Native* native : natives) + { + result.push(native, false); + } + + return result; + }, Arg("klass").setValue(), Arg("kind")); } // ========= TypeRegistry.hpp ========= diff --git a/include/rice/rice.hpp b/include/rice/rice.hpp index d8a8e39e..eb31c429 100644 --- a/include/rice/rice.hpp +++ b/include/rice/rice.hpp @@ -600,9 +600,9 @@ namespace Rice::detail class WrapperBase { public: - WrapperBase() = default; + WrapperBase(rb_data_type_t* rb_data_type); virtual ~WrapperBase() = default; - virtual void* get() = 0; + virtual void* get(rb_data_type_t* requestedType) = 0; bool isConst(); void ruby_mark(); @@ -610,6 +610,7 @@ namespace Rice::detail void setOwner(bool value); protected: + rb_data_type_t* rb_data_type_; bool isOwner_ = false; bool isConst_ = false; @@ -624,10 +625,10 @@ namespace Rice::detail class Wrapper : public WrapperBase { public: - Wrapper(T& data); - Wrapper(T&& data); + Wrapper(rb_data_type_t* rb_data_type, T& data); + Wrapper(rb_data_type_t* rb_data_type, T&& data); ~Wrapper(); - void* get() override; + void* get(rb_data_type_t* requestedType) override; private: T data_; @@ -637,9 +638,9 @@ namespace Rice::detail class Wrapper : public WrapperBase { public: - Wrapper(T& data); + Wrapper(rb_data_type_t* rb_data_type, T& data); ~Wrapper(); - void* get() override; + void* get(rb_data_type_t* requestedType) override; private: T& data_; @@ -649,9 +650,9 @@ namespace Rice::detail class Wrapper : public WrapperBase { public: - Wrapper(T* data, bool isOwner); + Wrapper(rb_data_type_t* rb_data_type, T* data, bool isOwner); ~Wrapper(); - void* get() override; + void* get(rb_data_type_t* requestedType) override; private: T* data_ = nullptr; @@ -661,9 +662,9 @@ namespace Rice::detail class Wrapper : public WrapperBase { public: - Wrapper(T** data, bool isOwner); + Wrapper(rb_data_type_t* rb_data_type, T** data, bool isOwner); ~Wrapper(); - void* get() override; + void* get(rb_data_type_t* requestedType) override; private: T** data_ = nullptr; @@ -1609,6 +1610,7 @@ namespace Rice namespace Rice { class Class; + class Module; class String; class Array; @@ -1716,6 +1718,11 @@ namespace Rice */ bool is_a(Object klass) const; + //! Extend the object with a module. + /*! \param mod the module to extend with. + */ + void extend(Module const& mod); + //! Determine if the objects responds to a method. /*! \param id the name of the method * \return true if the objects responds to the method, false @@ -2697,6 +2704,11 @@ namespace Rice */ const std::string base_name() const; + //! Return the superclass of this class + /*! \return Class. + */ + Class superclass() const; + // Include these methods to call methods from Module but return // an instance of the current classes. This is an alternative to // using CRTP. @@ -3796,6 +3808,7 @@ namespace Rice::detail const std::vector lookup(VALUE klass); const std::vector>& lookup(VALUE klass, ID methodId); + std::vector lookup(VALUE klass, NativeKind kind); private: // Key - Ruby klass/method @@ -8985,6 +8998,35 @@ namespace Rice::detail // Lookup items for method return this->natives_[key]; } + + inline std::vector NativeRegistry::lookup(VALUE klass, NativeKind kind) + { + std::vector result; + + if (rb_type(klass) == T_ICLASS) + { + klass = detail::protect(rb_class_of, klass); + } + + for (auto& pair : this->natives_) + { + const std::pair& key = pair.first; + + if (klass == key.first) + { + const std::vector>& natives = pair.second; + for (auto& native : natives) + { + if (native->kind() == kind) + { + result.push_back(native.get()); + } + } + } + } + + return result; + } } // ========= Registries.ipp ========= @@ -9207,6 +9249,10 @@ namespace Rice::detail std::regex equalRegex(R"(,\s*std::equal_to)"); removeGroup(base, equalRegex); + // Remove default_delete (std::unique_ptr) + std::regex defaultDeleteRegex(R"(,\s*std::default_delete)"); + removeGroup(base, defaultDeleteRegex); + // Remove spaces before pointers std::regex ptrRegex = std::regex(R"(\s+\*)"); base = std::regex_replace(base, ptrRegex, "*"); @@ -9531,6 +9577,10 @@ namespace Rice::detail namespace Rice::detail { + inline WrapperBase::WrapperBase(rb_data_type_t* rb_data_type) : rb_data_type_(rb_data_type) + { + } + inline bool WrapperBase::isConst() { return this->isConst_; @@ -9556,31 +9606,40 @@ namespace Rice::detail // ---- Wrapper ----- template - inline Wrapper::Wrapper(T& data): data_(data) + inline Wrapper::Wrapper(rb_data_type_t* rb_data_type, T& data) : WrapperBase(rb_data_type), data_(data) { this->isConst_ = std::is_const_v>; } template - inline Wrapper::Wrapper(T&& data) : data_(std::move(data)) + inline Wrapper::Wrapper(rb_data_type_t* rb_data_type, T&& data) : WrapperBase(rb_data_type), data_(std::move(data)) { } template inline Wrapper::~Wrapper() { - Registries::instance.instances.remove(this->get()); + Registries::instance.instances.remove(this->get(this->rb_data_type_)); } template - inline void* Wrapper::Wrapper::get() + inline void* Wrapper::get(rb_data_type_t* requestedType) { - return (void*)&this->data_; + if (rb_typeddata_inherited_p(this->rb_data_type_, requestedType)) + { + return (void*)&this->data_; + } + else + { + throw Exception(rb_eTypeError, "wrong argument type %s (expected %s)", + this->rb_data_type_->wrap_struct_name, + requestedType->wrap_struct_name); + } } // ---- Wrapper& ----- template - inline Wrapper::Wrapper(T& data): data_(data) + inline Wrapper::Wrapper(rb_data_type_t* rb_data_type, T& data) : WrapperBase(rb_data_type), data_(data) { this->isConst_ = std::is_const_v>; } @@ -9588,18 +9647,27 @@ namespace Rice::detail template inline Wrapper::~Wrapper() { - Registries::instance.instances.remove(this->get()); + Registries::instance.instances.remove(this->get(this->rb_data_type_)); } template - inline void* Wrapper::get() + inline void* Wrapper::get(rb_data_type_t* requestedType) { - return (void*)&this->data_; + if (rb_typeddata_inherited_p(this->rb_data_type_, requestedType)) + { + return (void*)&this->data_; + } + else + { + throw Exception(rb_eTypeError, "wrong argument type %s (expected %s)", + this->rb_data_type_->wrap_struct_name, + requestedType->wrap_struct_name); + } } // ---- Wrapper* ----- template - inline Wrapper::Wrapper(T* data, bool isOwner) : data_(data) + inline Wrapper::Wrapper(rb_data_type_t* rb_data_type, T* data, bool isOwner) : WrapperBase(rb_data_type), data_(data) { this->isOwner_ = isOwner; this->isConst_ = std::is_const_v>; @@ -9608,7 +9676,8 @@ namespace Rice::detail template inline Wrapper::~Wrapper() { - Registries::instance.instances.remove(this->get()); + Registries::instance.instances.remove(this->get(this->rb_data_type_)); + if constexpr (std::is_destructible_v) { if (this->isOwner_) @@ -9619,14 +9688,23 @@ namespace Rice::detail } template - inline void* Wrapper::get() + inline void* Wrapper::get(rb_data_type_t* requestedType) { - return (void*)this->data_; + if (rb_typeddata_inherited_p(this->rb_data_type_, requestedType)) + { + return (void*)this->data_; + } + else + { + throw Exception(rb_eTypeError, "wrong argument type %s (expected %s)", + this->rb_data_type_->wrap_struct_name, + requestedType->wrap_struct_name); + } } // ---- Wrapper** ----- template - inline Wrapper::Wrapper(T** data, bool isOwner) : data_(data) + inline Wrapper::Wrapper(rb_data_type_t* rb_data_type, T** data, bool isOwner) : WrapperBase(rb_data_type), data_(data) { this->isOwner_ = isOwner; this->isConst_ = std::is_const_v>>; @@ -9635,7 +9713,8 @@ namespace Rice::detail template inline Wrapper::~Wrapper() { - Registries::instance.instances.remove(this->get()); + Registries::instance.instances.remove(this->get(this->rb_data_type_)); + if constexpr (std::is_destructible_v) { if (this->isOwner_) @@ -9646,9 +9725,18 @@ namespace Rice::detail } template - inline void* Wrapper::get() + inline void* Wrapper::get(rb_data_type_t* requestedType) { - return (void*)this->data_; + if (rb_typeddata_inherited_p(this->rb_data_type_, requestedType)) + { + return (void*)this->data_; + } + else + { + throw Exception(rb_eTypeError, "wrong argument type %s (expected %s)", + this->rb_data_type_->wrap_struct_name, + requestedType->wrap_struct_name); + } } // ---- Helper Functions ------- @@ -9665,7 +9753,7 @@ namespace Rice::detail // If Ruby is not the owner then wrap the reference if (!isOwner) { - wrapper = new Wrapper(data); + wrapper = new Wrapper(rb_data_type, data); result = TypedData_Wrap_Struct(klass, rb_data_type, wrapper); } @@ -9674,12 +9762,12 @@ namespace Rice::detail { if constexpr (std::is_copy_constructible_v) { - wrapper = new Wrapper(data); + wrapper = new Wrapper(rb_data_type, data); result = TypedData_Wrap_Struct(klass, rb_data_type, wrapper); } else { - wrapper = new Wrapper(std::move(data)); + wrapper = new Wrapper(rb_data_type, std::move(data)); result = TypedData_Wrap_Struct(klass, rb_data_type, wrapper); } } @@ -9687,14 +9775,14 @@ namespace Rice::detail // Ruby is the owner so copy data else if constexpr (std::is_copy_constructible_v) { - wrapper = new Wrapper(data); + wrapper = new Wrapper(rb_data_type, data); result = TypedData_Wrap_Struct(klass, rb_data_type, wrapper); } // Ruby is the owner so move data else if constexpr (std::is_move_constructible_v) { - wrapper = new Wrapper(std::move(data)); + wrapper = new Wrapper(rb_data_type, std::move(data)); result = TypedData_Wrap_Struct(klass, rb_data_type, wrapper); } @@ -9706,7 +9794,7 @@ namespace Rice::detail throw std::runtime_error(message); } - Registries::instance.instances.add(wrapper->get(), result); + Registries::instance.instances.add(wrapper->get(rb_data_type), result); return result; }; @@ -9719,38 +9807,40 @@ namespace Rice::detail if (result != Qnil) return result; - WrapperBase* wrapper = new Wrapper(data, isOwner); + WrapperBase* wrapper = new Wrapper(rb_data_type, data, isOwner); result = TypedData_Wrap_Struct(klass, rb_data_type, wrapper); - Registries::instance.instances.add(wrapper->get(), result); + Registries::instance.instances.add(wrapper->get(rb_data_type), result); return result; }; template inline T* unwrap(VALUE value, rb_data_type_t* rb_data_type, bool takeOwnership) { - if (rb_type(value) != RUBY_T_DATA) + if (!RTYPEDDATA_P(value)) { std::string message = "The Ruby object does not wrap a C++ object. It is actually a " + std::string(detail::protect(rb_obj_classname, value)) + "."; throw std::runtime_error(message); } - WrapperBase* wrapper = getWrapper(value, rb_data_type); + WrapperBase* wrapper = static_cast(RTYPEDDATA_DATA(value)); if (wrapper == nullptr) { - std::string message = "Wrapped C++ object is nil. Did you override " + - std::string(detail::protect(rb_obj_classname, value)) + + std::string message = "Wrapped C++ object is nil. Did you override " + + std::string(detail::protect(rb_obj_classname, value)) + "#initialize and forget to call super?"; throw std::runtime_error(message); } if (takeOwnership) + { wrapper->setOwner(false); + } - return static_cast(wrapper->get()); + return static_cast(wrapper->get(rb_data_type)); } template @@ -9772,7 +9862,8 @@ namespace Rice::detail if (!RTYPEDDATA_P(value)) { throw Exception(rb_eTypeError, "wrong argument type %s (expected %s)", - detail::protect(rb_obj_classname, value), "wrapped C++ object"); + detail::protect(rb_obj_classname, value), + "wrapped C++ object"); } return static_cast(RTYPEDDATA_DATA(value)); @@ -9786,21 +9877,21 @@ namespace Rice::detail inline void wrapConstructed(VALUE value, rb_data_type_t* rb_data_type, T* data) { using Wrapper_T = Wrapper; - + Wrapper_T* wrapper = nullptr; TypedData_Get_Struct(value, Wrapper_T, rb_data_type, wrapper); if (wrapper) { - Registries::instance.instances.remove(wrapper->get()); + Registries::instance.instances.remove(wrapper->get(rb_data_type)); delete wrapper; } - wrapper = new Wrapper_T(data, true); + wrapper = new Wrapper_T(rb_data_type, data, true); RTYPEDDATA_DATA(value) = wrapper; Registries::instance.instances.add(data, value); } -} // namespace +} // ========= Native.ipp ========= namespace Rice::detail @@ -12119,6 +12210,11 @@ namespace Rice return RB_TEST(result); } + inline void Object::extend(Module const& mod) + { + detail::protect(rb_extend_object, this->value(), mod.value()); + } + inline bool Object::respond_to(Identifier id) const { return bool(rb_respond_to(this->value(), id.id())); @@ -13513,6 +13609,11 @@ namespace Rice return result; } + inline Class Class::superclass() const + { + return detail::protect(rb_class_superclass, this->value()); + } + inline Class define_class_under(Object parent, Identifier id, const Class& superclass) { VALUE klass = detail::protect(rb_define_class_id_under, parent.value(), id, superclass.value()); @@ -14067,17 +14168,22 @@ namespace Rice template inline void ruby_mark_internal(detail::WrapperBase* wrapper) { - // Tell the wrapper to mark the objects its keeping alive - wrapper->ruby_mark(); + detail::cpp_protect([&] + { + // Tell the wrapper to mark the objects its keeping alive + wrapper->ruby_mark(); - // Get the underlying data and call custom mark function (if any) - T* data = static_cast(wrapper->get()); - ruby_mark(data); + // Get the underlying data and call custom mark function (if any) + // Use the wrapper's stored rb_data_type to avoid type mismatch + T* data = static_cast(wrapper->get(Data_Type::ruby_data_type())); + ruby_mark(data); + }); } template inline void ruby_free_internal(detail::WrapperBase* wrapper) { + // Destructors are noexcept so we cannot use cpp_protect here delete wrapper; } @@ -15662,6 +15768,86 @@ namespace Rice } } +// Dependent on Module, Array, Symbol - used by stl smart pointers + +// ========= Forwards.hpp ========= + +namespace Rice::detail +{ + // Setup method forwarding from a wrapper class to its wrapped type using Ruby's Forwardable. + // This allows calling methods on the wrapper that get delegated to the wrapped object via + // a "get" method that returns the wrapped object. + // + // Parameters: + // wrapper_klass - The Ruby class to add forwarding to (e.g., SharedPtr_MyClass) + // wrapped_klass - The Ruby class whose methods should be forwarded (e.g., MyClass) + void define_forwarding(VALUE wrapper_klass, VALUE wrapped_klass); +} + + +// --------- Forwards.ipp --------- +namespace Rice::detail +{ + inline void define_forwarding(VALUE wrapper_klass, VALUE wrapped_klass) + { + protect(rb_require, "forwardable"); + Object forwardable = Object(rb_cObject).const_get("Forwardable"); + Object(wrapper_klass).extend(forwardable.value()); + + // Get wrapper class's method names to avoid conflicts + std::set wrapperMethodSet; + for (Native* native : Registries::instance.natives.lookup(wrapper_klass, NativeKind::Method)) + { + wrapperMethodSet.insert(native->name()); + } + for (Native* native : Registries::instance.natives.lookup(wrapper_klass, NativeKind::AttributeReader)) + { + wrapperMethodSet.insert(native->name()); + } + for (Native* native : Registries::instance.natives.lookup(wrapper_klass, NativeKind::AttributeWriter)) + { + wrapperMethodSet.insert(native->name() + "="); + } + + // Get wrapped class's method names from the registry, including ancestor classes + std::set wrappedMethodSet; + Class klass(wrapped_klass); + while (klass.value() != rb_cObject && klass.value() != Qnil) + { + for (Native* native : Registries::instance.natives.lookup(klass.value(), NativeKind::Method)) + { + wrappedMethodSet.insert(native->name()); + } + for (Native* native : Registries::instance.natives.lookup(klass.value(), NativeKind::AttributeReader)) + { + wrappedMethodSet.insert(native->name()); + } + for (Native* native : Registries::instance.natives.lookup(klass.value(), NativeKind::AttributeWriter)) + { + wrappedMethodSet.insert(native->name() + "="); + } + + klass = klass.superclass(); + } + + // Build the arguments array for def_delegators: [:get, :method1, :method2, ...] + // Skip methods that are already defined on the wrapper class + Array args; + args.push(Symbol("get")); + for (const std::string& method : wrappedMethodSet) + { + if (wrapperMethodSet.find(method) == wrapperMethodSet.end()) + { + args.push(Symbol(method)); + } + } + + // Call def_delegators(*args) + Object(wrapper_klass).vcall("def_delegators", args); + } +} + + // For now include libc support - maybe should be separate header file someday // ========= file.hpp ========= diff --git a/include/rice/stl.hpp b/include/rice/stl.hpp index 2c835721..90db6e3e 100644 --- a/include/rice/stl.hpp +++ b/include/rice/stl.hpp @@ -2687,11 +2687,26 @@ namespace Rice Data_Type> define_shared_ptr(std::string klassName = ""); } +namespace Rice::detail +{ + template + class Wrapper> : public WrapperBase + { + public: + Wrapper(rb_data_type_t* rb_data_type, const std::shared_ptr& data); + ~Wrapper(); + void* get(rb_data_type_t* requestedType) override; + + private: + std::shared_ptr data_; + rb_data_type_t* inner_rb_data_type_; + }; +} + // --------- shared_ptr.ipp --------- #include -// --------- Enable creation of std::shared_ptr from Ruby --------- namespace Rice { template @@ -2713,74 +2728,98 @@ namespace Rice } Identifier id(klassName); - Data_Type_T result = define_class_under(rb_mStd, id); + Data_Type_T result = define_class_under>(rb_mStd, id). + define_method("get", &SharedPtr_T::get). + define_method("swap", &SharedPtr_T::swap). + define_method("use_count", &SharedPtr_T::use_count). + define_method("empty?", [](SharedPtr_T& self)->bool + { + return !self; + }); - // std::shared_ptr cannot be constructed from void* because void is incomplete - // and the deleter cannot be determined. So skip the constructor for void. - // std::shared_ptr (array types) also skip constructor - arrays need special handling. - if constexpr (!std::is_void_v && !std::is_array_v) + if constexpr (!std::is_void_v) { result.define_constructor(Constructor(), Arg("value").takeOwnership()); } - result. - define_method("get", &SharedPtr_T::get). - define_method("use_count", &SharedPtr_T::use_count). - define_method("empty?", &SharedPtr_T::operator bool); + // Setup delegation to forward T's methods via get (only for non-fundamental, non-void types) + if constexpr (!std::is_void_v && !std::is_fundamental_v) + { + detail::define_forwarding(result.klass(), Data_Type::klass()); + } return result; } } -// --------- Type/To_Ruby/From_Ruby --------- +// --------- Wrapper --------- namespace Rice::detail { template - struct Type> + Wrapper>::Wrapper(rb_data_type_t* rb_data_type, const std::shared_ptr& data) + : WrapperBase(rb_data_type), data_(data) { - static bool verify() + using Intrinsic_T = intrinsic_type; + + if constexpr (std::is_fundamental_v) { - if constexpr (std::is_fundamental_v) - { - Type>::verify(); - Type>::verify(); - } - else - { - if (!Type>::verify()) - { - return false; - } - } + inner_rb_data_type_ = Data_Type>::ruby_data_type(); + } + else + { + inner_rb_data_type_ = Data_Type::ruby_data_type(); + } + } - define_shared_ptr(); + template + Wrapper>::~Wrapper() + { + Registries::instance.instances.remove(this->get(this->rb_data_type_)); + } - return true; + template + void* Wrapper>::get(rb_data_type_t* requestedType) + { + if (rb_typeddata_inherited_p(this->rb_data_type_, requestedType)) + { + return &this->data_; } - }; + else if (rb_typeddata_inherited_p(this->inner_rb_data_type_, requestedType)) + { + return this->data_.get(); + } + else + { + throw Exception(rb_eTypeError, "wrong argument type (expected %s)", + requestedType->wrap_struct_name); + } + } +} - // Specialization for array types std::shared_ptr +// --------- Type --------- +namespace Rice::detail +{ template - struct Type> + struct Type> { static bool verify() { + bool result = true; if constexpr (std::is_fundamental_v) { - Type>::verify(); - Type>::verify(); + result = result && Type>::verify(); } else { - if (!Type>::verify()) - { - return false; - } + result = result && Type::verify(); } - define_shared_ptr(); + if (result) + { + define_shared_ptr(); + } - return true; + return result; } }; } @@ -3301,19 +3340,25 @@ namespace Rice::detail // ========= unique_ptr.hpp ========= +namespace Rice +{ + template + Data_Type> define_unique_ptr(std::string klassName = ""); +} + namespace Rice::detail { template class Wrapper> : public WrapperBase { public: - Wrapper(std::unique_ptr&& data); + Wrapper(rb_data_type_t* rb_data_type, std::unique_ptr&& data); ~Wrapper(); - void* get() override; - std::unique_ptr& data(); + void* get(rb_data_type_t* requestedType) override; private: std::unique_ptr data_; + rb_data_type_t* inner_rb_data_type_; }; } @@ -3321,136 +3366,124 @@ namespace Rice::detail // --------- unique_ptr.ipp --------- #include -namespace Rice::detail +namespace Rice { template - inline Wrapper>::Wrapper(std::unique_ptr&& data) - : data_(std::move(data)) + Data_Type> define_unique_ptr(std::string klassName) { - } - - template - inline Wrapper>::~Wrapper() - { - Registries::instance.instances.remove(this->get()); - } + using UniquePtr_T = std::unique_ptr; + using Data_Type_T = Data_Type; - template - inline void* Wrapper>::get() - { - return (void*)this->data_.get(); - } - - template - inline std::unique_ptr& Wrapper>::data() - { - return data_; - } - - template - class To_Ruby> - { - public: - To_Ruby() = default; - - explicit To_Ruby(Arg* arg) : arg_(arg) + if (klassName.empty()) { + detail::TypeMapper typeMapper; + klassName = typeMapper.rubyName(); } - VALUE convert(std::unique_ptr& data) + Module rb_mStd = define_module("Std"); + if (Data_Type_T::check_defined(klassName, rb_mStd)) { - std::pair rubyTypeInfo = detail::Registries::instance.types.figureType(*data); - return detail::wrap>(rubyTypeInfo.first, rubyTypeInfo.second, data, true); + return Data_Type_T(); } - VALUE convert(std::unique_ptr&& data) + Identifier id(klassName); + Data_Type_T result = define_class_under>(rb_mStd, id). + define_method("get", &UniquePtr_T::get). + define_method("release", &UniquePtr_T::release). + define_method("reset", &UniquePtr_T::reset). + define_method("swap", &UniquePtr_T::swap). + define_method("empty?", [](UniquePtr_T& self)->bool + { + return !self; + }); + + // Setup delegation to forward T's methods via get (only for non-fundamental, non-void types) + if constexpr (!std::is_void_v && !std::is_fundamental_v) { - std::pair rubyTypeInfo = detail::Registries::instance.types.figureType(*data); - return detail::wrap>(rubyTypeInfo.first, rubyTypeInfo.second, data, true); + detail::define_forwarding(result.klass(), Data_Type::klass()); } - private: - Arg* arg_ = nullptr; - }; + return result; + } +} - template - class To_Ruby&> +// --------- Wrapper --------- +namespace Rice::detail +{ + template + Wrapper>::Wrapper(rb_data_type_t* rb_data_type, std::unique_ptr&& data) + : WrapperBase(rb_data_type), data_(std::move(data)) { - public: - To_Ruby() = default; + using Intrinsic_T = intrinsic_type; - explicit To_Ruby(Arg* arg) : arg_(arg) + if constexpr (std::is_fundamental_v) { + inner_rb_data_type_ = Data_Type>::ruby_data_type(); } - - VALUE convert(std::unique_ptr& data) + else { - std::pair rubyTypeInfo = detail::Registries::instance.types.figureType(*data); - return detail::wrap>(rubyTypeInfo.first, rubyTypeInfo.second, data, true); + inner_rb_data_type_ = Data_Type::ruby_data_type(); } + } - private: - Arg* arg_ = nullptr; - }; + template + Wrapper>::~Wrapper() + { + Registries::instance.instances.remove(this->get(this->rb_data_type_)); + } - template - class From_Ruby> + template + void* Wrapper>::get(rb_data_type_t* requestedType) { - public: - Wrapper>* is_same_smart_ptr(VALUE value) + if (rb_typeddata_inherited_p(this->rb_data_type_, requestedType)) { - WrapperBase* wrapper = detail::getWrapper(value, Data_Type::ruby_data_type()); - return dynamic_cast>*>(wrapper); + return &this->data_; } - - From_Ruby() = default; - - explicit From_Ruby(Arg* arg) : arg_(arg) + else if (rb_typeddata_inherited_p(this->inner_rb_data_type_, requestedType)) { + return this->data_.get(); } + else + { + throw Exception(rb_eTypeError, "wrong argument type (expected %s)", + requestedType->wrap_struct_name); + } + } - double is_convertible(VALUE value) +} + +// --------- Type --------- +namespace Rice::detail +{ + template + struct Type> + { + static bool verify() { - if (!is_same_smart_ptr(value)) + bool result = true; + if constexpr (std::is_fundamental_v) { - return Convertible::None; + result = result && Type>::verify(); } - - switch (rb_type(value)) + else { - case RUBY_T_DATA: - return Convertible::Exact; - break; - default: - return Convertible::None; + result = result && Type::verify(); } - } - std::unique_ptr convert(VALUE value) - { - Wrapper>* wrapper = is_same_smart_ptr(value); - if (!wrapper) + if (result) { - std::string message = "Invalid smart pointer wrapper"; - throw std::runtime_error(message.c_str()); + define_unique_ptr(); } - return std::move(wrapper->data()); - } - private: - Arg* arg_ = nullptr; + return result; + } }; + // --------- From_Ruby --------- template - class From_Ruby&> + class From_Ruby> { public: - Wrapper>* is_same_smart_ptr(VALUE value) - { - WrapperBase* wrapper = detail::getWrapper(value, Data_Type::ruby_data_type()); - return dynamic_cast>*>(wrapper); - } - From_Ruby() = default; explicit From_Ruby(Arg* arg) : arg_(arg) @@ -3459,11 +3492,6 @@ namespace Rice::detail double is_convertible(VALUE value) { - if (!is_same_smart_ptr(value)) - { - return Convertible::None; - } - switch (rb_type(value)) { case RUBY_T_DATA: @@ -3474,43 +3502,16 @@ namespace Rice::detail } } - std::unique_ptr& convert(VALUE value) + std::unique_ptr convert(VALUE value) { - Wrapper>* wrapper = is_same_smart_ptr(value); - if (!wrapper) - { - std::string message = "Invalid smart pointer wrapper"; - throw std::runtime_error(message.c_str()); - } - return wrapper->data(); + std::unique_ptr* result = detail::unwrap>(value, Data_Type>::ruby_data_type(), this->arg_ && this->arg_->isOwner()); + // The reason we need this overriden From_Ruby is to do this std::move. + return std::move(*result); } private: Arg* arg_ = nullptr; }; - - template - struct Type> - { - static bool verify() - { - if constexpr (std::is_fundamental_v) - { - return Type>::verify(); - return Type>::verify(); - } - else - { - return Type::verify(); - } - } - - static VALUE rubyKlass() - { - TypeMapper typeMapper; - return typeMapper.rubyKlass(); - } - }; }