Skip to content
Merged

Dev #384

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
14 changes: 4 additions & 10 deletions .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,13 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-2025]
ruby: ['3.1', '3.2', '3.3', '3.4']
ruby: ['3.2', '3.3', '3.4', '4.0']
include:
- os: ubuntu-22.04
ruby: '3.1'
exclude:
# There's something wrong with this setup in GHA such that
# it gets weird linking errors, however I'm unable to reproduce
# locally so I think it's an infra fluke on GitHub's side.
- os: macos-latest
ruby: '3.1'
ruby: '3.2'
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
Expand All @@ -35,7 +29,7 @@ jobs:
- name: Build and test
run: rake test
- name: Mkmf.log
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
if: always()
with:
name: mkmf-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.ruby }}
Expand Down
15 changes: 8 additions & 7 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
# Changelog

## 4.10.0 (2026-02-05)
## 4.10.0 (2026-02-07)

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]`)
* Ruby 4.0 support
* Support incomplete types (PIMPL/opaque handle patterns). Rice now uses `typeid(T*)` for forward-declared types that are never fully defined.
* Support `noexcept` functions, static members, and static member functions
* Support for `Buffer<void*>` and `Pointer<void*>`
* Add `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 `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`.
* Support verifying arrays of non-fundamental types (e.g., `MyClass[2]`)
* Delegate method calls for smart pointers to their wrapped objects via method_missing?

Internal:
Expand Down
2 changes: 1 addition & 1 deletion FindRuby.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ Finding Ruby and specifying the minimum required version:
find_package(Ruby 3.2)
#]=======================================================================]

cmake_policy(GET CMP0185 _Ruby_CMP0185)
#cmake_policy(GET CMP0185 _Ruby_CMP0185)

if(NOT _Ruby_CMP0185 STREQUAL "NEW")
# Backwards compatibility
Expand Down
6 changes: 5 additions & 1 deletion rice/Data_Object.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -514,10 +514,14 @@ namespace Rice::detail
{
return Convertible::Exact;
}
else if (Data_Type<Pointer_T>::is_descendant(value))
else if (Data_Type<Pointer_T>::is_descendant(value) && isBuffer)
{
return Convertible::Exact;
}
else if (Data_Type<Pointer_T>::is_descendant(value) && !isBuffer)
{
return Convertible::Exact * 0.99;
}
[[fallthrough]];
default:
return Convertible::None;
Expand Down
33 changes: 19 additions & 14 deletions rice/detail/Parameter.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -39,22 +39,27 @@ namespace Rice::detail
// Check with FromRuby if the VALUE is convertible to C++
double result = this->fromRuby_.is_convertible(value);

// If this is an exact match check if the const-ness of the value and the parameter match.
// One caveat - procs are also RUBY_T_DATA so don't check if this is a function type
if (result == Convertible::Exact && rb_type(value) == RUBY_T_DATA && !std::is_function_v<std::remove_pointer_t<T>>)
// TODO this is ugly and hacky and probably doesn't belong here.
// Some Ruby objects like Proc and Set (in Ruby 4+) are also RUBY_T_DATA so we have to check for them
if (result == Convertible::Exact && rb_type(value) == RUBY_T_DATA)
{
bool isConst = WrapperBase::isConst(value);

// Do not send a const value to a non-const parameter
if (isConst && !is_const_any_v<T>)
{
result = Convertible::None;
}
// It is ok to send a non-const value to a const parameter but
// prefer non-const to non-const by slightly decreasing the score
else if (!isConst && is_const_any_v<T>)
bool isBuffer = dynamic_cast<ArgBuffer*>(this->arg()) ? true : false;
if ((!isBuffer && Data_Type<detail::intrinsic_type<T>>::is_descendant(value)) ||
(isBuffer && Data_Type<Pointer<detail::intrinsic_type<T>>>::is_descendant(value)))
{
result = Convertible::ConstMismatch;
bool isConst = WrapperBase::isConst(value);

// Do not send a const value to a non-const parameter
if (isConst && !is_const_any_v<T>)
{
result = Convertible::None;
}
// It is ok to send a non-const value to a const parameter but
// prefer non-const to non-const by slightly decreasing the score
else if (!isConst && is_const_any_v<T>)
{
result = Convertible::ConstMismatch;
}
}
}

Expand Down
18 changes: 1 addition & 17 deletions rice/detail/ruby.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#pragma GCC diagnostic ignored "-Wunknown-pragmas"
#endif

#include <ruby/version.h>
#include <ruby.h>
#include <ruby/encoding.h>
#include <ruby/thread.h>
Expand Down Expand Up @@ -58,22 +59,5 @@ extern "C" typedef VALUE (*RUBY_VALUE_FUNC)(VALUE);
extern "C" typedef VALUE (*RUBY_METHOD_FUNC)(ANYARGS);
#endif

// This is a terrible hack for Ruby 3.1 and maybe earlier to avoid crashes when test_Attribute unit cases
// are run. If ruby_options is called to initialize the interpeter (previously it was not), when
// the attribute unit tests intentionally cause exceptions to happen, the exception is correctly processed.
// However any calls back to Ruby, for example to get the exception message, crash because the ruby
// execution context tag has been set to null. This does not happen in newer versions of Ruby. It is
// unknown if this happens in real life or just the test caes.
// Should be removed when Rice no longer supports Ruby 3.1
#if RUBY_API_VERSION_MAJOR == 3 && RUBY_API_VERSION_MINOR < 2
constexpr bool oldRuby = true;
#elif RUBY_API_VERSION_MAJOR < 3
constexpr bool oldRuby = true;
#else
constexpr bool oldRuby = false;
#endif



#endif // Rice__detail__ruby__hpp_

7 changes: 4 additions & 3 deletions rice/rice.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,16 @@
#include "detail/NativeRegistry.hpp"
#include "detail/Registries.hpp"

#include "Buffer.hpp"
#include "Pointer.hpp"
#include "Reference.hpp"

// To / From Ruby
#include "Arg.ipp"
#include "detail/Parameter.ipp"
#include "NoGVL.hpp"
#include "Return.ipp"
#include "Constructor.hpp"
#include "Buffer.hpp"
#include "Pointer.hpp"
#include "Reference.hpp"
#include "Buffer.ipp"
#include "Pointer.ipp"
#include "detail/Types.ipp"
Expand Down
81 changes: 75 additions & 6 deletions rice/stl/set.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -312,8 +312,17 @@ namespace Rice
switch (rb_type(value))
{
case RUBY_T_DATA:
{
#if RUBY_API_VERSION_MAJOR >= 4
if (detail::protect(rb_obj_is_instance_of, value, rb_cSet))
{
return Convertible::Exact;
}
#endif
return Data_Type<std::set<T>>::is_descendant(value) ? Convertible::Exact : Convertible::None;
break;
}
#if RUBY_API_VERSION_MAJOR < 4
case RUBY_T_OBJECT:
{
Object object(value);
Expand All @@ -322,6 +331,7 @@ namespace Rice
return Convertible::Exact;
}
}
#endif
default:
return Convertible::None;
}
Expand All @@ -333,9 +343,19 @@ namespace Rice
{
case RUBY_T_DATA:
{
// This is a wrapped self (hopefully!)
return *detail::unwrap<std::set<T>>(value, Data_Type<std::set<T>>::ruby_data_type(), false);
#if RUBY_API_VERSION_MAJOR >= 4
if (detail::protect(rb_obj_is_instance_of, value, rb_cSet))
{
return toSet<T>(value);
}
#endif

if (Data_Type<std::set<T>>::is_descendant(value))
{
return *detail::unwrap<std::set<T>>(value, Data_Type<std::set<T>>::ruby_data_type(), false);
}
}
#if RUBY_API_VERSION_MAJOR < 4
case RUBY_T_OBJECT:
{
Object object(value);
Expand All @@ -346,6 +366,7 @@ namespace Rice
throw Exception(rb_eTypeError, "wrong argument type %s (expected %s)",
detail::protect(rb_obj_classname, value), "std::set");
}
#endif
default:
{
throw Exception(rb_eTypeError, "wrong argument type %s (expected %s)",
Expand Down Expand Up @@ -376,8 +397,15 @@ namespace Rice
switch (rb_type(value))
{
case RUBY_T_DATA:
#if RUBY_API_VERSION_MAJOR >= 4
if (detail::protect(rb_obj_is_instance_of, value, rb_cSet))
{
return Convertible::Exact;
}
#endif
return Data_Type<std::set<T>>::is_descendant(value) ? Convertible::Exact : Convertible::None;
break;
#if RUBY_API_VERSION_MAJOR < 4
case RUBY_T_OBJECT:
{
Object object(value);
Expand All @@ -386,6 +414,7 @@ namespace Rice
return Convertible::Exact;
}
}
#endif
default:
return Convertible::None;
}
Expand All @@ -397,9 +426,24 @@ namespace Rice
{
case RUBY_T_DATA:
{
// This is a wrapped self (hopefully!)
return *detail::unwrap<std::set<T>>(value, Data_Type<std::set<T>>::ruby_data_type(), false);
#if RUBY_API_VERSION_MAJOR >= 4
if (detail::protect(rb_obj_is_instance_of, value, rb_cSet))
{
// If this an Ruby array and the vector type is copyable
if constexpr (std::is_default_constructible_v<T>)
{
this->converted_ = toSet<T>(value);
return this->converted_;
}
}
#endif

if (Data_Type<std::set<T>>::is_descendant(value))
{
return *detail::unwrap<std::set<T>>(value, Data_Type<std::set<T>>::ruby_data_type(), false);
}
}
#if RUBY_API_VERSION_MAJOR < 4
case RUBY_T_OBJECT:
{
Object object(value);
Expand All @@ -415,6 +459,7 @@ namespace Rice
throw Exception(rb_eTypeError, "wrong argument type %s (expected %s)",
detail::protect(rb_obj_classname, value), "std::set");
}
#endif
default:
{
throw Exception(rb_eTypeError, "wrong argument type %s (expected %s)",
Expand Down Expand Up @@ -445,11 +490,18 @@ namespace Rice
switch (rb_type(value))
{
case RUBY_T_DATA:
#if RUBY_API_VERSION_MAJOR >= 4
if (detail::protect(rb_obj_is_instance_of, value, rb_cSet))
{
return Convertible::Exact;
}
#endif
return Data_Type<std::set<T>>::is_descendant(value) ? Convertible::Exact : Convertible::None;
break;
case RUBY_T_NIL:
return Convertible::Exact;
break;
#if RUBY_API_VERSION_MAJOR < 4
case RUBY_T_OBJECT:
{
Object object(value);
Expand All @@ -458,6 +510,7 @@ namespace Rice
return Convertible::Exact;
}
}
#endif
default:
return Convertible::None;
}
Expand All @@ -469,9 +522,24 @@ namespace Rice
{
case RUBY_T_DATA:
{
// This is a wrapped self (hopefully!)
return detail::unwrap<std::set<T>>(value, Data_Type<std::set<T>>::ruby_data_type(), false);
#if RUBY_API_VERSION_MAJOR >= 4
if (detail::protect(rb_obj_is_instance_of, value, rb_cSet))
{
// If this an Ruby array and the vector type is copyable
if constexpr (std::is_default_constructible_v<T>)
{
this->converted_ = toSet<T>(value);
return &this->converted_;
}
}
#endif

if (Data_Type<std::set<T>>::is_descendant(value))
{
return detail::unwrap<std::set<T>>(value, Data_Type<std::set<T>>::ruby_data_type(), false);
}
}
#if RUBY_API_VERSION_MAJOR < 4
case RUBY_T_OBJECT:
{
Object object(value);
Expand All @@ -487,6 +555,7 @@ namespace Rice
throw Exception(rb_eTypeError, "wrong argument type %s (expected %s)",
detail::protect(rb_obj_classname, value), "std::set");
}
#endif
default:
{
throw Exception(rb_eTypeError, "wrong argument type %s (expected %s)",
Expand Down
9 changes: 4 additions & 5 deletions test/embed_ruby.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#include <rice/rice.hpp>
#include <ruby/version.h>

void embed_ruby()
{
Expand All @@ -18,10 +17,10 @@ void embed_ruby()
ruby_init();
ruby_init_loadpath();

#if RUBY_API_VERSION_MAJOR == 3 && RUBY_API_VERSION_MINOR >= 1
// Force the prelude / builtins
const char* opts[] = { "ruby", "-e;" };
ruby_options(2, (char**)opts);
#if (RUBY_API_VERSION_MAJOR == 3 && RUBY_API_VERSION_MINOR >= 1) || (RUBY_API_VERSION_MAJOR >= 4)
// Force the prelude / builtins. In versions over 3.1 this is mandatory for Symbols, Arrays, and Hash methods to work
const char* options[] = { "ruby", "-e;" };
ruby_options(2, (char**)options);
#endif

// Enable GC stress to help catch GC-related bugs
Expand Down
Loading