Skip to content
Merged

Dev #383

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
# Changelog

## 4.9.2 (unreleased)
## 4.10.0 (2026-02-05)

Enhancements:
* Add support for incomplete types (PIMPL/opaque handle patterns). Rice now uses `typeid(T*)` for forward-declared types that are never fully defined.
* Add support for `noexcept` functions, static members, and static member functions
* Add support for `Buffer<void*>` and `Pointer<void*>`
* Add support for `std::function`. Ruby procs, lambdas, and blocks can be wrapped in `std::function` objects and passed to C++ methods. C++ functions returning `std::function` are automatically wrapped.
* Add support for `std::ostream`, `std::ostringstream`, and `std::ofstream`. Ruby can write to C++ streams and pass them to C++ functions. Standard streams are exposed as `Std::COUT` and `Std::CERR`.
* Add support for verifying arrays of non-fundamental types (e.g., `MyClass[2]`)
* Delegate method calls for smart pointers to their wrapped objects via method_missing?

Internal:
* Refactor type handling by merging `TypeMapper` into `TypeDetail` and simplifying class hierarchy
* Greatly simplify define_attr

Incompatible Changes:
* `Address_Registration_Guard` has been replaced by `Pin`. If you are using `Address_Registration_Guard`
Expand Down
15 changes: 12 additions & 3 deletions docs/architecture/registries.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,19 @@ Maps C++ types to Ruby classes. The TypeRegistry serves two important purposes:
When a C++ method returns a base class pointer that actually points to a derived class, Rice uses RTTI (`typeid`) to look up the derived type in the registry and wrap it as the correct Ruby class. For example:

```cpp
class Base { virtual ~Base() = default; };
class Derived : public Base {};
class Base
{
virtual ~Base() = default;
};

class Derived : public Base
{
};

Base* create() { return new Derived(); } // Returns Derived* as Base*
Base* create()
{
return new Derived();
} // Returns Derived* as Base*
```

When `create()` is called from Ruby, Rice uses `typeid(*result)` to discover the object is actually a `Derived`, looks it up in the TypeRegistry, and wraps it as `Rb::Derived` instead of `Rb::Base`.
Expand Down
2 changes: 1 addition & 1 deletion lib/rice/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module Rice
VERSION = "4.9.2"
VERSION = "4.10.0"
end
16 changes: 15 additions & 1 deletion rice/Data_Type.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,28 @@ namespace Rice
{
dataType.define_singleton_method("cpp_class", [](VALUE) -> VALUE
{
Return returnInfo;
returnInfo.takeOwnership();

detail::TypeDetail<T> typeDetail;
std::string cppClassName = typeDetail.simplifiedName();

return detail::To_Ruby<char*>(&returnInfo).convert(cppClassName.c_str());
}, Arg("klass").setValue(), Return().setValue());
}
else
{
VALUE klass_value = klass.value();
dataType.define_singleton_method("cpp_class", [klass_value](VALUE) -> VALUE
{
Return returnInfo;
returnInfo.takeOwnership();

Rice::String cppClassName = detail::protect(rb_class_path, klass_value);

return detail::To_Ruby<char*>(&returnInfo).convert(cppClassName.c_str());
}, Arg("klass").setValue(), Return().setValue());
}

return dataType;
}

Expand Down
1 change: 1 addition & 0 deletions rice/detail/Type.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ namespace Rice::detail
struct Type<T[N]>
{
static bool verify();
static VALUE rubyKlass();
};

template<typename T>
Expand Down
19 changes: 7 additions & 12 deletions rice/detail/Type.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ namespace Rice::detail
return Type<T>::verify();
}

template <typename T, int N>
inline VALUE Type<T[N]>::rubyKlass()
{
detail::TypeDetail<Pointer<T>> typeDetail;
return typeDetail.rubyKlass();
}

template<typename T>
void verifyType()
{
Expand All @@ -58,16 +65,4 @@ namespace Rice::detail
std::make_index_sequence<std::tuple_size_v<Tuple_T>> indexes;
verifyTypesImpl<Tuple_T>(indexes);
}

// ---------- RubyKlass ------------
// Helper template to see if the method rubyKlass is defined on a Type specialization
template<typename, typename = std::void_t<>>
struct has_ruby_klass : std::false_type
{
};

template<typename T>
struct has_ruby_klass<T, std::void_t<decltype(T::rubyKlass())>> : std::true_type
{
};
}
6 changes: 2 additions & 4 deletions rice/stl/shared_ptr.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,8 @@ namespace Rice::detail
result = result && Type<T>::verify();
}

if (result)
{
define_shared_ptr<T>();
}
// We ALWAYS need to define the std::shared_ptr<T>, even if T is not bound, because it could be bound after this call
define_shared_ptr<T>();

return result;
}
Expand Down
6 changes: 2 additions & 4 deletions rice/stl/unique_ptr.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,8 @@ namespace Rice::detail
result = result && Type<T>::verify();
}

if (result)
{
define_unique_ptr<T>();
}
// We ALWAYS need to define the std::unique_ptr<T>, even if T is not bound, because it could be bound after this call
define_unique_ptr<T>();

return result;
}
Expand Down
11 changes: 11 additions & 0 deletions rice/traits/rice_traits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,17 @@ namespace Rice
template<typename T>
constexpr bool is_wrapped_v = is_wrapped<T>::value;

// ---------- RubyKlass ------------
template<typename, typename = std::void_t<>>
struct has_ruby_klass : std::false_type
{
};

template<typename T>
struct has_ruby_klass<T, std::void_t<decltype(T::rubyKlass())>> : std::true_type
{
};

// -- Tuple Helpers ---
template<typename T>
struct tuple_shift;
Expand Down
10 changes: 10 additions & 0 deletions test/test_Type.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,16 @@ TESTCASE(RubyKlass)
expected = stdModule.const_get("OFStream");
actual = typeDetail34.rubyKlass();
ASSERT_EQUAL(expected.value(), actual);

detail::TypeDetail<std::string[2]> typeDetail35;
actual = typeDetail35.rubyKlass();
ASSERT_EQUAL(rb_cString, actual);

define_pointer<std::vector<int>>();
detail::TypeDetail<std::vector<int>[2]> typeDetail36;
expected = riceModule.const_get("Pointer≺vector≺int≻≻");
actual = typeDetail36.rubyKlass();
ASSERT_EQUAL(expected.value(), actual);
}

TESTCASE(MakeRubyClass)
Expand Down