Skip to content

Commit

Permalink
Merge pull request #195 from jasonroelofs/ruby-3.3
Browse files Browse the repository at this point in the history
Build under Ruby 3.3.0
  • Loading branch information
jasonroelofs authored Jan 1, 2024
2 parents b7bae38 + 8e5c6ca commit 4e29d09
Show file tree
Hide file tree
Showing 11 changed files with 121 additions and 24 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
ruby: [2.7, '3.0', 3.1, 3.2]
ruby: ['2.7', '3.0', '3.1', '3.2', '3.3']
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 4.2

* Support Ruby 3.3.0.
* Split Object.call to an explicit Object.call_kw for calling methods expecting keyword arguments.

## 4.1

Rice 4.1 builds on the 4.0 release and has a number of improvements that both polish Rice and extend its functionality. However, there are three incompatibilities to know about:
Expand Down
38 changes: 36 additions & 2 deletions include/rice/rice.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4672,7 +4672,8 @@ namespace Rice

//! Call the Ruby method specified by 'id' on object 'obj'.
/*! Pass in arguments (arg1, arg2, ...). The arguments will be converted to
* Ruby objects with to_ruby<>.
* Ruby objects with to_ruby<>. To call methods expecting keyword arguments,
* use call_kw.
*
* E.g.:
* \code
Expand All @@ -4690,6 +4691,29 @@ namespace Rice
template<typename ...Arg_Ts>
Object call(Identifier id, Arg_Ts... args) const;

//! Call the Ruby method specified by 'id' on object 'obj'.
/*! Pass in arguments (arg1, arg2, ...). The arguments will be converted to
* Ruby objects with to_ruby<>. The final argument must be a Hash and will be treated
* as keyword arguments to the function.
*
* E.g.:
* \code
* Rice::Hash kw;
* kw[":argument"] = String("one")
* Rice::Object obj = x.call_kw("foo", kw);
* \endcode
*
* If a return type is specified, the return value will automatically be
* converted to that type as long as 'from_ruby' exists for that type.
*
* E.g.:
* \code
* float ret = x.call_kw<float>("foo", kw);
* \endcode
*/
template<typename ...Arg_Ts>
Object call_kw(Identifier id, Arg_Ts... args) const;

//! Vectorized call.
/*! Calls the method identified by id with the list of arguments
* identified by args.
Expand Down Expand Up @@ -4753,6 +4777,7 @@ namespace Rice
} // namespace Rice

#endif // Rice__Object_defn__hpp_

// --------- Object.ipp ---------
#ifndef Rice__Object__ipp_
#define Rice__Object__ipp_
Expand Down Expand Up @@ -4797,7 +4822,15 @@ namespace Rice
easy to duplicate by setting GC.stress to true and calling a constructor
that takes multiple values like a std::pair wrapper. */
std::array<VALUE, sizeof...(Arg_Ts)> values = { detail::To_Ruby<detail::remove_cv_recursive_t<Arg_Ts>>().convert(args)... };
return detail::protect(rb_funcallv_kw, value(), id.id(), (int)values.size(), (const VALUE*)values.data(), RB_PASS_CALLED_KEYWORDS);
return detail::protect(rb_funcallv, value(), id.id(), (int)values.size(), (const VALUE*)values.data());
}

template<typename ...Arg_Ts>
inline Object Object::call_kw(Identifier id, Arg_Ts... args) const
{
/* IMPORTANT - See call() above */
std::array<VALUE, sizeof...(Arg_Ts)> values = { detail::To_Ruby<detail::remove_cv_recursive_t<Arg_Ts>>().convert(args)... };
return detail::protect(rb_funcallv_kw, value(), id.id(), (int)values.size(), (const VALUE*)values.data(), RB_PASS_KEYWORDS);
}

template<typename T>
Expand Down Expand Up @@ -4975,6 +5008,7 @@ namespace Rice::detail
#endif // Rice__Object__ipp_



// ========= Builtin_Object.hpp =========


Expand Down
12 changes: 10 additions & 2 deletions rice/cpp_api/Object.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,15 @@ namespace Rice
easy to duplicate by setting GC.stress to true and calling a constructor
that takes multiple values like a std::pair wrapper. */
std::array<VALUE, sizeof...(Arg_Ts)> values = { detail::To_Ruby<detail::remove_cv_recursive_t<Arg_Ts>>().convert(args)... };
return detail::protect(rb_funcallv_kw, value(), id.id(), (int)values.size(), (const VALUE*)values.data(), RB_PASS_CALLED_KEYWORDS);
return detail::protect(rb_funcallv, value(), id.id(), (int)values.size(), (const VALUE*)values.data());
}

template<typename ...Arg_Ts>
inline Object Object::call_kw(Identifier id, Arg_Ts... args) const
{
/* IMPORTANT - See call() above */
std::array<VALUE, sizeof...(Arg_Ts)> values = { detail::To_Ruby<detail::remove_cv_recursive_t<Arg_Ts>>().convert(args)... };
return detail::protect(rb_funcallv_kw, value(), id.id(), (int)values.size(), (const VALUE*)values.data(), RB_PASS_KEYWORDS);
}

template<typename T>
Expand Down Expand Up @@ -216,4 +224,4 @@ namespace Rice::detail
}
};
}
#endif // Rice__Object__ipp_
#endif // Rice__Object__ipp_
28 changes: 26 additions & 2 deletions rice/cpp_api/Object_defn.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,8 @@ namespace Rice

//! Call the Ruby method specified by 'id' on object 'obj'.
/*! Pass in arguments (arg1, arg2, ...). The arguments will be converted to
* Ruby objects with to_ruby<>.
* Ruby objects with to_ruby<>. To call methods expecting keyword arguments,
* use call_kw.
*
* E.g.:
* \code
Expand All @@ -188,6 +189,29 @@ namespace Rice
template<typename ...Arg_Ts>
Object call(Identifier id, Arg_Ts... args) const;

//! Call the Ruby method specified by 'id' on object 'obj'.
/*! Pass in arguments (arg1, arg2, ...). The arguments will be converted to
* Ruby objects with to_ruby<>. The final argument must be a Hash and will be treated
* as keyword arguments to the function.
*
* E.g.:
* \code
* Rice::Hash kw;
* kw[":argument"] = String("one")
* Rice::Object obj = x.call_kw("foo", kw);
* \endcode
*
* If a return type is specified, the return value will automatically be
* converted to that type as long as 'from_ruby' exists for that type.
*
* E.g.:
* \code
* float ret = x.call_kw<float>("foo", kw);
* \endcode
*/
template<typename ...Arg_Ts>
Object call_kw(Identifier id, Arg_Ts... args) const;

//! Vectorized call.
/*! Calls the method identified by id with the list of arguments
* identified by args.
Expand Down Expand Up @@ -250,4 +274,4 @@ namespace Rice
extern Object const Undef;
} // namespace Rice

#endif // Rice__Object_defn__hpp_
#endif // Rice__Object_defn__hpp_
6 changes: 6 additions & 0 deletions test/embed_ruby.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ void embed_ruby()
ruby_init();
ruby_init_loadpath();

#if RUBY_API_VERSION_MAJOR == 3 && RUBY_API_VERSION_MINOR >= 3
// Force the prelude / builtins
char *opts[] = { "ruby", "-e;" };
ruby_options(2, opts);
#endif

initialized__ = true;
}
}
12 changes: 6 additions & 6 deletions test/test_Attribute.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#include <assert.h>
#include <assert.h>

#include "unittest.hpp"
#include "embed_ruby.hpp"
Expand Down Expand Up @@ -60,17 +60,17 @@ TESTCASE(attributes)
ASSERT_EXCEPTION_CHECK(
Exception,
o.call("read_char=", "some text"),
ASSERT_EQUAL("undefined method `read_char=' for :DataStruct", ex.what())
ASSERT(std::string(ex.what()).find("undefined method `read_char='") == 0)
);

// Test writeonly attribute
result = o.call("write_int=", 5);
ASSERT_EQUAL(5, detail::From_Ruby<int>().convert(result.value()));
ASSERT_EQUAL(5, dataStruct->writeInt);
ASSERT_EXCEPTION_CHECK(
Exception,
o.call("write_int", 3),
ASSERT_EQUAL("undefined method `write_int' for :DataStruct", ex.what())
ASSERT(std::string(ex.what()).find("undefined method `write_int'") == 0)
);

// Test readwrite attribute
Expand Down Expand Up @@ -101,7 +101,7 @@ TESTCASE(static_attributes)
ASSERT_EXCEPTION_CHECK(
Exception,
c.call("static_string=", true),
ASSERT_EQUAL("undefined method `static_string=' for DataStruct:Class", ex.what())
ASSERT(std::string(ex.what()).find("undefined method `static_string='") == 0)
);
}

Expand All @@ -127,7 +127,7 @@ TESTCASE(not_defined)
{
Data_Type<DataStruct> c = define_class<DataStruct>("DataStruct");

#ifdef _MSC_VER
#ifdef _MSC_VER
const char* message = "Type is not defined with Rice: class `anonymous namespace'::SomeClass";
#else
const char* message = "Type is not defined with Rice: (anonymous namespace)::SomeClass";
Expand Down
21 changes: 15 additions & 6 deletions test/test_Object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -174,20 +174,29 @@ TESTCASE(call_return_rice_object)

TESTCASE(call_with_keywords)
{
Module kernel = Module("Kernel");
Module m(anonymous_module());

m.module_eval(R"(
def self.keywords_test(value, exception:)
if exception
raise "An exception!"
end
value
end
)");

Hash keywords;
keywords[":exception"] = false;
Object result = kernel.call("Integer", "charlie", keywords);
ASSERT_EQUAL(Qnil, result.value());
Object result = m.call_kw("keywords_test", "charlie", keywords);
ASSERT_EQUAL("charlie", detail::From_Ruby<const char*>().convert(result.value()));

keywords[":exception"] = true;

ASSERT_EXCEPTION_CHECK(
Exception,
kernel.call("Integer", "charlie", keywords),
ASSERT_EQUAL("invalid value for Integer(): \"charlie\"", ex.what())
m.call_kw("keywords_test", "charlie", keywords),
ASSERT_EQUAL("An exception!", ex.what())
);
}

Expand Down Expand Up @@ -240,4 +249,4 @@ TESTCASE(test_mark)
Object o(INT2NUM(42));
rb_gc_start();
ASSERT_EQUAL(42, detail::From_Ruby<int>().convert(o.value()));
}
}
7 changes: 5 additions & 2 deletions test/test_Stl_String.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ TESTCASE(std_string_to_ruby_encoding)
Object object(value);
Object encoding = object.call("encoding");
Object encodingName = encoding.call("name");
ASSERT_EQUAL("ASCII-8BIT", detail::From_Ruby<std::string>().convert(encodingName));
std::string result = detail::From_Ruby<std::string>().convert(encodingName);
if(result != "ASCII-8BIT" && result != "US-ASCII" && result != "UTF-8") {
FAIL("Encoding incorrect", "ASCII-8BIT, US-ASCII, or UTF-8 (Windows)", result);
}
}

TESTCASE(std_string_to_ruby_encoding_utf8)
Expand Down Expand Up @@ -71,4 +74,4 @@ TESTCASE(std_string_from_ruby_with_binary)
std::string got = detail::From_Ruby<std::string>().convert(rb_str_new("\000test", 5));
ASSERT_EQUAL(5ul, got.length());
ASSERT_EQUAL(std::string("\000test", 5), got);
}
}
4 changes: 2 additions & 2 deletions test/test_To_From_Ruby.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ TESTCASE(unsigned_long_long_from_ruby)
ASSERT_EXCEPTION_CHECK(
Exception,
detail::From_Ruby<unsigned long long>().convert(rb_str_new2("bad value")),
ASSERT_EQUAL("no implicit conversion from string", ex.what())
ASSERT(std::string(ex.what()).find("no implicit conversion") == 0)
);
}

Expand Down Expand Up @@ -396,4 +396,4 @@ TESTCASE(char_star_from_ruby)
detail::From_Ruby<const char*>().convert(rb_float_new(11.11)),
ASSERT_EQUAL("wrong argument type Float (expected String)", ex.what())
);
}
}
10 changes: 9 additions & 1 deletion test/unittest.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ void assert_equal(

if constexpr (is_streamable<std::stringstream, T>::value && is_streamable<std::stringstream, U>::value)
{
strm << s_t << " != " << s_u;
strm << s_t << " != " << s_u << " (" << u << ") ";
}
strm << " at " << file << ":" << line;
throw Assertion_Failed(strm.str());
Expand All @@ -263,6 +263,14 @@ void assert_not_equal(
}
}

#define FAIL(message, expect, got) \
do \
{ \
std::stringstream strm; \
strm << message << " expected: " << (expect) << " got: " << (got); \
throw Assertion_Failed(strm.str()); \
} while(0)

#define ASSERT_EQUAL(x, y) \
do \
{ \
Expand Down

0 comments on commit 4e29d09

Please sign in to comment.