diff --git a/spec/ruby/optional/capi/object_spec.rb b/spec/ruby/optional/capi/object_spec.rb index 57c1e8ac9d..1699b9e09c 100644 --- a/spec/ruby/optional/capi/object_spec.rb +++ b/spec/ruby/optional/capi/object_spec.rb @@ -169,6 +169,13 @@ class DescObjectTest < ObjectTest @o.rb_check_convert_type(ao).should == [] @o.rb_check_convert_type(h).should == nil end + + it "raises a TypeError when the coercion method returns a different object" do + dummy = mock('to_ary') + dummy.should_receive(:to_ary).and_return(10) + + lambda { @o.rb_check_convert_type(dummy) }.should raise_error(TypeError) + end end describe "rb_check_array_type" do diff --git a/vm/capi/float.cpp b/vm/capi/float.cpp index f86115e614..dd6ab15517 100644 --- a/vm/capi/float.cpp +++ b/vm/capi/float.cpp @@ -68,6 +68,6 @@ extern "C" { } VALUE rb_Float(VALUE object_handle) { - return rb_convert_type(object_handle, 0, "Float", "to_f"); + return rb_convert_type(object_handle, -1, "Float", "to_f"); } } diff --git a/vm/capi/numeric.cpp b/vm/capi/numeric.cpp index 757f24af0d..d764f16138 100644 --- a/vm/capi/numeric.cpp +++ b/vm/capi/numeric.cpp @@ -275,7 +275,7 @@ extern "C" { return env->get_handle(ret); } - return rb_convert_type(object_handle, 0, "Integer", "to_i"); + return rb_convert_type(object_handle, -1, "Integer", "to_i"); } void rb_num_zerodiv(void) { diff --git a/vm/capi/object.cpp b/vm/capi/object.cpp index 0f45f7196d..bd4c0b2923 100644 --- a/vm/capi/object.cpp +++ b/vm/capi/object.cpp @@ -118,24 +118,45 @@ extern "C" { return rb_funcall(env->get_handle(env->state()->globals().type.get()), rb_intern("try_convert"), 3, object_handle, rb_cString, rb_intern("to_str")); } - VALUE rb_check_convert_type(VALUE object_handle, int /*type*/, + /* + * NOTE: when `0` is given as the `type` no error will be raised. This is due + * to the way this function is used in Rbx itself and Rbx not having MRI's + * `convert_type` function. + */ + VALUE rb_check_convert_type(VALUE object_handle, int type, const char* type_name, const char* method_name) { NativeMethodEnvironment* env = NativeMethodEnvironment::get(); VALUE name = env->get_handle(String::create(env->state(), method_name)); + VALUE retval = Qnil; if(RTEST(rb_funcall(object_handle, rb_intern("respond_to?"), 1, name)) ) { - return rb_funcall2(object_handle, rb_intern(method_name), 0, NULL); + retval = rb_funcall2(object_handle, rb_intern(method_name), 0, NULL); } - return Qnil; + // If the method returns nil we can bail out right away. + if (NIL_P(retval)) return retval; + + /* + * When the coercion method exists but returns a different type than + * specified in `type` MRI will raise an error. This code is mostly a + * copy-paste job from the MRI source code. + */ + if (type != -1 && TYPE(retval) != type) { + const char *cname = rb_obj_classname(object_handle); + + rb_raise(rb_eTypeError, "can't convert %s to %s (%s#%s gives %s)", + cname, type_name, cname, method_name, rb_obj_classname(retval)); + } + + return retval; } VALUE rb_check_to_integer(VALUE object_handle, const char *method_name) { if(FIXNUM_P(object_handle)) { return object_handle; } - VALUE result = rb_check_convert_type(object_handle, 0, "Integer", method_name); + VALUE result = rb_check_convert_type(object_handle, -1, "Integer", method_name); if(rb_obj_is_kind_of(result, rb_cInteger)) { return result; } @@ -375,7 +396,7 @@ extern "C" { } VALUE rb_to_int(VALUE object_handle) { - return rb_convert_type(object_handle, 0, "Integer", "to_int"); + return rb_convert_type(object_handle, -1, "Integer", "to_int"); } VALUE rb_hash(VALUE obj) { diff --git a/vm/capi/string.cpp b/vm/capi/string.cpp index 993b10a371..a64fbbdf45 100644 --- a/vm/capi/string.cpp +++ b/vm/capi/string.cpp @@ -167,7 +167,7 @@ extern "C" { } VALUE rb_String(VALUE object) { - return rb_convert_type(object, 0, "String", "to_s"); + return rb_convert_type(object, -1, "String", "to_s"); } void rb_str_modify(VALUE self) { @@ -363,7 +363,7 @@ extern "C" { } VALUE rb_str_to_str(VALUE object) { - return rb_convert_type(object, 0, "String", "to_str"); + return rb_convert_type(object, -1, "String", "to_str"); } VALUE rb_string_value(volatile VALUE* object_variable) {