Skip to content

Commit

Permalink
Merge pull request #218 from ruby-rice/overloads
Browse files Browse the repository at this point in the history
Support Method Overloads
  • Loading branch information
cfis authored Nov 13, 2024
2 parents 47fe14e + 6b6f176 commit c23986c
Show file tree
Hide file tree
Showing 45 changed files with 5,429 additions and 3,076 deletions.
5,635 changes: 3,162 additions & 2,473 deletions include/rice/rice.hpp

Large diffs are not rendered by default.

513 changes: 413 additions & 100 deletions include/rice/stl.hpp

Large diffs are not rendered by default.

51 changes: 39 additions & 12 deletions rice/Data_Object.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -258,10 +258,16 @@ namespace Rice::detail
{
}

bool is_convertible(VALUE value)
Convertible is_convertible(VALUE value)
{
return rb_type(value) == RUBY_T_DATA &&
Data_Type<T>::is_descendant(value);
switch (rb_type(value))
{
case RUBY_T_DATA:
return Data_Type<T>::is_descendant(value) ? Convertible::Exact : Convertible::None;
break;
default:
return Convertible::None;
}
}

T convert(VALUE value)
Expand Down Expand Up @@ -294,10 +300,16 @@ namespace Rice::detail
{
}

bool is_convertible(VALUE value)
Convertible is_convertible(VALUE value)
{
return rb_type(value) == RUBY_T_DATA &&
Data_Type<T>::is_descendant(value);
switch (rb_type(value))
{
case RUBY_T_DATA:
return Data_Type<T>::is_descendant(value) ? Convertible::Exact : Convertible::None;
break;
default:
return Convertible::None;
}
}

T& convert(VALUE value)
Expand All @@ -324,10 +336,19 @@ namespace Rice::detail
static_assert(!std::is_fundamental_v<intrinsic_type<T>>,
"Data_Object cannot be used with fundamental types");
public:
bool is_convertible(VALUE value)
Convertible is_convertible(VALUE value)
{
return rb_type(value) == RUBY_T_DATA &&
Data_Type<T>::is_descendant(value);
switch (rb_type(value))
{
case RUBY_T_DATA:
return Data_Type<T>::is_descendant(value) ? Convertible::Exact : Convertible::None;
break;
case RUBY_T_NIL:
return Convertible::Exact;
break;
default:
return Convertible::None;
}
}

T* convert(VALUE value)
Expand All @@ -351,10 +372,16 @@ namespace Rice::detail
static_assert(!std::is_fundamental_v<intrinsic_type<T>>,
"Data_Object cannot be used with fundamental types");
public:
bool is_convertible(VALUE value)
Convertible is_convertible(VALUE value)
{
return rb_type(value) == RUBY_T_DATA &&
Data_Type<T>::is_descendant(value);
switch (rb_type(value))
{
case RUBY_T_DATA:
return Data_Type<T>::is_descendant(value) ? Convertible::Exact : Convertible::None;
break;
default:
return Convertible::None;
}
}

T* convert(VALUE value)
Expand Down
21 changes: 17 additions & 4 deletions rice/Data_Type.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
#include "traits/attribute_traits.hpp"
#include "traits/method_traits.hpp"
#include "detail/NativeRegistry.hpp"
#include "detail/NativeAttribute.hpp"
#include "detail/NativeAttributeGet.hpp"
#include "detail/NativeAttributeSet.hpp"
#include "detail/default_allocation_func.hpp"
#include "detail/TypeRegistry.hpp"
#include "detail/Wrapper.hpp"
Expand Down Expand Up @@ -256,8 +257,13 @@ namespace Rice
// Make sure the Attribute type has been previously seen by Rice
detail::verifyType<typename detail::attribute_traits<Attribute_T>::attr_type>();

// Define native attribute
detail::NativeAttribute<Attribute_T>::define(klass_, name, std::forward<Attribute_T>(attribute), access);
// Define native attribute getter
if (access == AttrAccess::ReadWrite || access == AttrAccess::Read)
detail::NativeAttributeGet<Attribute_T>::define(klass_, name, std::forward<Attribute_T>(attribute));

// Define native attribute setter
if (access == AttrAccess::ReadWrite || access == AttrAccess::Write)
detail::NativeAttributeSet<Attribute_T>::define(klass_, name, std::forward<Attribute_T>(attribute));

return *this;
}
Expand All @@ -271,7 +277,14 @@ namespace Rice

// Define native attribute
VALUE singleton = detail::protect(rb_singleton_class, this->value());
detail::NativeAttribute<Attribute_T>::define(singleton, name, std::forward<Attribute_T>(attribute), access);

// Define native attribute getter
if (access == AttrAccess::ReadWrite || access == AttrAccess::Read)
detail::NativeAttributeGet<Attribute_T>::define(singleton, name, std::forward<Attribute_T>(attribute));

// Define native attribute setter
if (access == AttrAccess::ReadWrite || access == AttrAccess::Write)
detail::NativeAttributeSet<Attribute_T>::define(singleton, name, std::forward<Attribute_T>(attribute));

return *this;
}
Expand Down
12 changes: 12 additions & 0 deletions rice/cpp_api/Array.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,18 @@ namespace Rice::detail
class From_Ruby<Array>
{
public:
Convertible is_convertible(VALUE value)
{
switch (rb_type(value))
{
case RUBY_T_ARRAY:
return Convertible::Exact;
break;
default:
return Convertible::None;
}
}

Array convert(VALUE value)
{
return Array(value);
Expand Down
14 changes: 12 additions & 2 deletions rice/cpp_api/Class.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,22 @@ namespace Rice

inline Class define_class_under(Object module, char const* name, const Class& superclass)
{
return detail::protect(rb_define_class_under, module.value(), name, superclass.value());
VALUE klass = detail::protect(rb_define_class_under, module.value(), name, superclass.value());

// We MUST reset the instance registry in case the user just redefined a class which resets it
detail::Registries::instance.natives.reset(klass);

return klass;
}

inline Class define_class(char const* name, const Class& superclass)
{
return detail::protect(rb_define_class, name, superclass.value());
VALUE klass = detail::protect(rb_define_class, name, superclass.value());

// We MUST reset the instance registry in case the user just redefined a class which resets it
detail::Registries::instance.natives.reset(klass);

return klass;
}

inline Class anonymous_class()
Expand Down
12 changes: 12 additions & 0 deletions rice/cpp_api/Hash.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,18 @@ namespace Rice::detail
class From_Ruby<Hash>
{
public:
Convertible is_convertible(VALUE value)
{
switch (rb_type(value))
{
case RUBY_T_HASH:
return Convertible::Exact;
break;
default:
return Convertible::None;
}
}

Hash convert(VALUE value)
{
return Hash(value);
Expand Down
12 changes: 12 additions & 0 deletions rice/cpp_api/Object.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,18 @@ namespace Rice::detail
class From_Ruby<Object>
{
public:
Convertible is_convertible(VALUE value)
{
switch (rb_type(value))
{
case RUBY_T_OBJECT:
return Convertible::Exact;
break;
default:
return Convertible::None;
}
}

Object convert(VALUE value)
{
return Object(value);
Expand Down
12 changes: 12 additions & 0 deletions rice/cpp_api/String.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,18 @@ namespace Rice::detail
class From_Ruby<String>
{
public:
Convertible is_convertible(VALUE value)
{
switch (rb_type(value))
{
case RUBY_T_STRING:
return Convertible::Exact;
break;
default:
return Convertible::None;
}
}

String convert(VALUE value)
{
return String(value);
Expand Down
34 changes: 34 additions & 0 deletions rice/detail/Native.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#ifndef Rice__detail__Native__hpp_
#define Rice__detail__Native__hpp_

#include "from_ruby.hpp"

namespace Rice::detail
{
class Native;

class Resolved
{
public:
inline bool operator<(Resolved other);
inline bool operator>(Resolved other);

Convertible convertible;
double parameterMatch;
Native* native;
};

class Native
{
public:
static VALUE resolve(int argc, VALUE* argv, VALUE self);
public:
virtual ~Native() = default;
VALUE call(int argc, VALUE* argv, VALUE self);

virtual Resolved matches(int argc, VALUE* argv, VALUE self) = 0;
virtual VALUE operator()(int argc, VALUE* argv, VALUE self) = 0;
};
}

#endif // Rice__detail__Native__hpp_
115 changes: 115 additions & 0 deletions rice/detail/Native.ipp
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
#include "Native.hpp"
#include "../Identifier.hpp"
#include "cpp_protect.hpp"
#include "Registries.hpp"

namespace Rice::detail
{
inline bool Resolved::operator<(Resolved other)
{
if (this->convertible != other.convertible)
{
return this->convertible < other.convertible;
}
else
{
return this->parameterMatch < other.parameterMatch;
}
}

inline bool Resolved::operator>(Resolved other)
{
if (this->convertible != other.convertible)
{
return this->convertible > other.convertible;
}
else
{
return this->parameterMatch > other.parameterMatch;
}
}

inline VALUE Native::resolve(int argc, VALUE* argv, VALUE self)
{
/* This method is called from Ruby and is responsible for determining the correct
Native object (ie, NativeFunction, NativeIterator, NativeAttributeGet and
NativeAttributeSet) that shoudl be used to invoke the underlying C++ code.
Most of the time there will be a single Native object registered for a C++ function,
method, constructor, iterator or attribute. However, there can be multiple Natives
when a C++ function/method/construtor is overloaded.
In that case, the code iterates over each Native and calls its matches method. The matches
method returns a Resolved object which includes a Convertible field and parameterMatch field.
The Convertible field is an enum that specifies if the types of the values supplied by Ruby
match the types of the C++ function parameters. Allowed values include can be Exact (example Ruby into to C++ int),
TypeCast (example Ruby into to C++ float) or None (cannot be converted).
The parameterMatch field is simply the number or arguments provided by Ruby divided by the
number of arguments required by C++. These numbers can be different because C++ method
parameters can have default values.
Taking these two values into account, the method sorts the Natives and picks the one with the
highest score (Convertible::Exact and 1.0 for parameterMatch). Thus given these two C++ functions:
void some_method(int a);
void some_mtehod(int a, float b = 2.0).
A call from ruby of some_method(1) will exactly match both signatures, but the first one
will be chosen because the parameterMatch will be 1.0 for the first overload but 0.5
for the second. */
Native* native = nullptr;

ID methodId;
VALUE klass;
if (!rb_frame_method_id_and_class(&methodId, &klass))
{
rb_raise(rb_eRuntimeError, "Cannot get method id and class for function");
}

const std::vector<std::unique_ptr<Native>>& natives = Registries::instance.natives.lookup(klass, methodId);

if (natives.size() == 1)
{
native = natives.front().get();
}
else
{
// Loop over every native to see how well they match the Ruby parameters
std::vector<Resolved> resolves;
std::transform(natives.begin(), natives.end(),
std::back_inserter(resolves),
[&](const std::unique_ptr<Native>& native)
{
return native->matches(argc, argv, self);
});

// Now sort from best to worst
std::sort(resolves.begin(), resolves.end(), std::greater{});

// Get the best one
Resolved resolved = resolves.front();

// Did it match?
if (resolved.convertible != Convertible::None)
{
native = resolved.native;
}
else
{
Identifier identifier(methodId);
rb_raise(rb_eArgError, "Could not resolve method call for %s#%s", rb_class2name(klass), identifier.c_str());
}
}

return native->call(argc, argv, self);
}

inline VALUE Native::call( int argc, VALUE* argv, VALUE self)
{
// Execute the function but make sure to catch any C++ exceptions!
return cpp_protect([&]
{
return this->operator()(argc, argv, self);
});
}
}
Loading

0 comments on commit c23986c

Please sign in to comment.