Skip to content
Merged

Dev #379

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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ Enhancements:
Internal:
* Refactor type handling by merging `TypeMapper` into `TypeDetail` and simplifying class hierarchy

Incomptatible Changes:
* Rice converts Ruby blocks to procs. Thus if you have a method that expects a block, you must add
a VALUE parameter and tell Rice it is a value. For example:

define_method("my_method", [](VALUE self, VALUE proc)
{
}, Arg("proc").setValue())

Think of this as similar to how you would capture a block in Ruby using the &block syntax.

## 4.9.1 (2026-01-04)
This release focuses on improving memory management for STL containers and attribute setters.

Expand Down
1 change: 1 addition & 0 deletions rice/Arg.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ namespace Rice
{
public:
ArgBuffer(std::string name);
using Arg::operator=; // Inherit the templated operator=
};
} // Rice

Expand Down
23 changes: 13 additions & 10 deletions rice/Buffer.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -944,19 +944,22 @@ namespace Rice
define_method("data", &Buffer_T::ptr, ReturnBuffer()).
define_method("release", &Buffer_T::release, ReturnBuffer());

if constexpr (!std::is_pointer_v<T> && !std::is_void_v<T> && !std::is_const_v<T> && std::is_copy_assignable_v<T>)
if constexpr (detail::is_complete_v<detail::intrinsic_type<T>>)
{
klass.define_method("[]=", [](Buffer_T& self, size_t index, T& value) -> void
if constexpr (!std::is_pointer_v<T> && !std::is_void_v<T> && !std::is_const_v<T> && std::is_copy_assignable_v<T>)
{
self[index] = value;
});
}
else if constexpr (std::is_pointer_v<T> && !std::is_const_v<std::remove_pointer_t<T>> && std::is_copy_assignable_v<std::remove_pointer_t<T>>)
{
klass.define_method("[]=", [](Buffer_T& self, size_t index, T value) -> void
klass.define_method("[]=", [](Buffer_T& self, size_t index, T& value) -> void
{
self[index] = value;
});
}
else if constexpr (std::is_pointer_v<T> && !std::is_const_v<std::remove_pointer_t<T>> && std::is_copy_assignable_v<std::remove_pointer_t<T>>)
{
*self[index] = *value;
});
klass.define_method("[]=", [](Buffer_T& self, size_t index, T value) -> void
{
*self[index] = *value;
});
}
}

return klass;
Expand Down
16 changes: 3 additions & 13 deletions rice/Data_Object.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -634,15 +634,13 @@ namespace Rice::detail

double is_convertible(VALUE value)
{
bool isBuffer = dynamic_cast<ArgBuffer*>(this->arg_) ? true : false;

switch (rb_type(value))
{
case RUBY_T_NIL:
return Convertible::Exact;
break;
case RUBY_T_DATA:
if (Data_Type<Pointer_T>::is_descendant(value) && isBuffer)
if (Data_Type<Pointer_T>::is_descendant(value))
{
return Convertible::Exact;
}
Expand All @@ -655,7 +653,6 @@ namespace Rice::detail
T** convert(VALUE value)
{
bool isOwner = this->arg_ && this->arg_->isOwner();
bool isBuffer = dynamic_cast<ArgBuffer*>(this->arg_) ? true : false;

switch (rb_type(value))
{
Expand All @@ -666,7 +663,7 @@ namespace Rice::detail
}
case RUBY_T_DATA:
{
if (Data_Type<Pointer_T>::is_descendant(value) && isBuffer)
if (Data_Type<Pointer_T>::is_descendant(value))
{
T** result = detail::unwrap<Intrinsic_T*>(value, Data_Type<Pointer_T>::ruby_data_type(), isOwner);
return result;
Expand All @@ -675,14 +672,7 @@ namespace Rice::detail
}
default:
{
if (isBuffer)
{
throw create_type_exception<Pointer_T>(value);
}
else
{
throw create_type_exception<T**>(value);
}
throw create_type_exception<Pointer_T>(value);
}
}
}
Expand Down
17 changes: 7 additions & 10 deletions rice/Data_Type.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,13 @@ namespace Rice
template<typename T>
inline void ruby_mark_internal(detail::WrapperBase* wrapper)
{
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)
// Use the wrapper's stored rb_data_type to avoid type mismatch
T* data = static_cast<T*>(wrapper->get(Data_Type<T>::ruby_data_type()));
ruby_mark<T>(data);
});
// Tell the wrapper to mark the objects its keeping alive
wrapper->ruby_mark();

// 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<T*>(wrapper->get(Data_Type<T>::ruby_data_type()));
ruby_mark<T>(data);
}

template<typename T>
Expand Down
7 changes: 6 additions & 1 deletion rice/detail/NativeIterator.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,12 @@ namespace Rice::detail
detail::To_Ruby<To_Ruby_T> toRuby;
for (; it != end; ++it)
{
protect(rb_yield, toRuby.convert(*it));
// Use auto&& to accept both reference- and value-returning iterators.
// - If *it is an lvalue (T&), auto&& deduces to T&, no copy made.
// - If *it is a prvalue (T), auto&& deduces to T&&, value binds to the temporary for the scope of this loop iteration.
// This also avoids MSVC C4239 when convert expects a non-const lvalue reference.
auto&& value = *it;
protect(rb_yield, toRuby.convert(value));
}

return self;
Expand Down
37 changes: 27 additions & 10 deletions rice/detail/Parameter.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,27 @@ namespace Rice::detail
template<typename T>
inline VALUE Parameter<T>::defaultValueRuby()
{
using To_Ruby_T = remove_cv_recursive_t<T>;

if (!this->arg()->hasDefaultValue())
{
throw std::runtime_error("No default value set for " + this->arg()->name);
}

if constexpr (!is_complete_v<intrinsic_type<T>>)
{
// Incomplete types can't have default values (std::any requires complete types)
// Only pointers to incomplete types can have
// default values (e.g., void* or Impl*), since the pointer itself is complete.
// References and values of incomplete types cannot be stored in std::any.
if constexpr (std::is_pointer_v<std::remove_reference_t<T>>)
{
T defaultValue = this->arg()->template defaultValue<T>();
return this->toRuby_.convert((To_Ruby_T)defaultValue);
}
else
{
throw std::runtime_error("Default value not allowed for incomple type. Parameter " + this->arg()->name);
}
}
else if constexpr (std::is_constructible_v<std::remove_cv_t<T>, std::remove_cv_t<std::remove_reference_t<T>>&>)
{
Expand All @@ -139,21 +157,20 @@ namespace Rice::detail
{
if constexpr (std::is_copy_constructible_v<typename detail::intrinsic_type<T>::value_type>)
{
if (this->arg()->hasDefaultValue())
{
T defaultValue = this->arg()->template defaultValue<T>();
return this->toRuby_.convert(defaultValue);
}
T defaultValue = this->arg()->template defaultValue<T>();
return this->toRuby_.convert(defaultValue);
}
}
else if (this->arg()->hasDefaultValue())
else
{
T defaultValue = this->arg()->template defaultValue<T>();
return this->toRuby_.convert((remove_cv_recursive_t<T>)defaultValue);
return this->toRuby_.convert((To_Ruby_T)defaultValue);
}
}

throw std::runtime_error("No default value set or allowed for parameter " + this->arg()->name);
else
{
throw std::runtime_error("Default value not allowed for parameter " + this->arg()->name);
}
}

template<typename T>
Expand Down
9 changes: 6 additions & 3 deletions rice/detail/Wrapper.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -155,11 +155,14 @@ namespace Rice::detail
{
Registries::instance.instances.remove(this->get(this->rb_data_type_));

if constexpr (std::is_destructible_v<T>)
if constexpr (is_complete_v<T>)
{
if (this->isOwner_)
if constexpr (std::is_destructible_v<T>)
{
delete this->data_;
if (this->isOwner_)
{
delete this->data_;
}
}
}
}
Expand Down
57 changes: 57 additions & 0 deletions test/test_Buffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -623,4 +623,61 @@ TESTCASE(void_pointer_function)
// This should compile and run without errors
void** result = getVoidPtrs();
ASSERT_EQUAL(&value1, result[0]);
}

namespace
{
static int defaultIntBuffer[] = { 10, 20, 30 };
static int secretNumber = 42;

int processIntBuffer(int* buffer, size_t size)
{
int sum = 0;
for (size_t i = 0; i < size; i++)
{
sum += buffer[i];
}
return sum;
}

int processIntBufferWithDefault(int* buffer = defaultIntBuffer, size_t size = 3)
{
return processIntBuffer(buffer, size);
}

int processVoidPtrWithDefault(void* ptr = &secretNumber)
{
return *static_cast<int*>(ptr);
}
}

TESTCASE(arg_buffer_int_default)
{
define_buffer<int>();
Module m = define_module("Testing");
m.define_module_function("process_int_buffer", &processIntBufferWithDefault,
ArgBuffer("buffer") = static_cast<int*>(defaultIntBuffer), Arg("size") = static_cast<size_t>(3));

// Call with no arguments - should use defaults (10 + 20 + 30 = 60)
std::string code = R"(process_int_buffer())";
Object result = m.module_eval(code);
ASSERT_EQUAL(60, detail::From_Ruby<int>().convert(result));

// Call with buffer
code = R"(buffer = Rice::Buffer≺int≻.new([1, 2, 3])
process_int_buffer(buffer.data, buffer.size))";
result = m.module_eval(code);
ASSERT_EQUAL(6, detail::From_Ruby<int>().convert(result));
}

TESTCASE(arg_buffer_void_default)
{
Module m = define_module("Testing");
m.define_module_function("process_void_ptr", &processVoidPtrWithDefault,
ArgBuffer("ptr") = static_cast<void*>(&secretNumber));

// Call with no arguments - should use default (42)
std::string code = R"(process_void_ptr())";
Object result = m.module_eval(code);
ASSERT_EQUAL(42, detail::From_Ruby<int>().convert(result));
}
4 changes: 2 additions & 2 deletions test/test_Data_Type.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ TEARDOWN(Data_Type)
rb_gc_start();
}

/*namespace
namespace
{
class MyClass
{
Expand Down Expand Up @@ -499,7 +499,7 @@ TESTCASE(null_ptrs)

result = o.call("set", nullptr);
ASSERT_EQUAL(Qnil, result.value());
}*/
}

namespace
{
Expand Down
Loading