summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNobuyoshi Nakada <nobu@ruby-lang.org>2018-10-07 13:02:46 +0900
committerNobuyoshi Nakada <nobu@ruby-lang.org>2021-07-16 17:49:53 +0900
commit301d194ee3b49e6b078eccb999dd538e9bfa8c7c (patch)
tree8488af78fa5c0f0dab79905a577e365acef7ca01
parenteee709595cecdbc35d7bca9e779c36523c4659c5 (diff)
downloadruby-301d194ee3b49e6b078eccb999dd538e9bfa8c7c.tar.gz
Add Integer.try_convert [Feature #15211]
-rw-r--r--NEWS.md5
-rw-r--r--internal/numeric.h1
-rw-r--r--numeric.c7
-rw-r--r--object.c16
-rw-r--r--spec/ruby/core/integer/try_convert_spec.rb40
-rw-r--r--test/ruby/test_integer.rb17
6 files changed, 83 insertions, 3 deletions
diff --git a/NEWS.md b/NEWS.md
index b30d707b4d..793da5b579 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -92,6 +92,10 @@ Outstanding ones only.
* File.dirname now accepts an optional argument for the level to
strip path components. [[Feature #12194]]
+* Integer
+
+ * Integer.try_convert is added. [[Feature #15211]]
+
* Module
* Module#prepend now modifies the ancestor chain if the receiver
@@ -191,6 +195,7 @@ Excluding feature bug fixes.
[Feature #12194]: https://bugs.ruby-lang.org/issues/12194
[Feature #14256]: https://bugs.ruby-lang.org/issues/14256
[Feature #15198]: https://bugs.ruby-lang.org/issues/15198
+[Feature #15211]: https://bugs.ruby-lang.org/issues/15211
[Feature #16043]: https://bugs.ruby-lang.org/issues/16043
[Feature #16806]: https://bugs.ruby-lang.org/issues/16806
[Feature #17312]: https://bugs.ruby-lang.org/issues/17312
diff --git a/internal/numeric.h b/internal/numeric.h
index 32d5bd27fa..82ba4c1cfb 100644
--- a/internal/numeric.h
+++ b/internal/numeric.h
@@ -77,6 +77,7 @@ VALUE rb_int_lshift(VALUE x, VALUE y);
VALUE rb_int_div(VALUE x, VALUE y);
int rb_int_positive_p(VALUE num);
int rb_int_negative_p(VALUE num);
+VALUE rb_check_integer_type(VALUE);
VALUE rb_num_pow(VALUE x, VALUE y);
VALUE rb_float_ceil(VALUE num, int ndigits);
VALUE rb_float_floor(VALUE x, int ndigits);
diff --git a/numeric.c b/numeric.c
index c9dc24bcf1..345067c6a5 100644
--- a/numeric.c
+++ b/numeric.c
@@ -5247,6 +5247,12 @@ rb_int_s_isqrt(VALUE self, VALUE num)
}
}
+static VALUE
+int_s_try_convert(VALUE self, VALUE num)
+{
+ return rb_check_integer_type(num);
+}
+
/*
* Document-class: ZeroDivisionError
*
@@ -5473,6 +5479,7 @@ Init_Numeric(void)
rb_undef_alloc_func(rb_cInteger);
rb_undef_method(CLASS_OF(rb_cInteger), "new");
rb_define_singleton_method(rb_cInteger, "sqrt", rb_int_s_isqrt, 1);
+ rb_define_singleton_method(rb_cInteger, "try_convert", int_s_try_convert, 1);
rb_define_method(rb_cInteger, "to_s", int_to_s, -1);
rb_define_alias(rb_cInteger, "inspect", "to_s");
diff --git a/object.c b/object.c
index 695a11e366..43c97272dd 100644
--- a/object.c
+++ b/object.c
@@ -3232,19 +3232,23 @@ rb_check_convert_type_with_id(VALUE val, int type, const char *tname, ID method)
#define try_to_int(val, mid, raise) \
convert_type_with_id(val, "Integer", mid, raise, -1)
-ALWAYS_INLINE(static VALUE rb_to_integer(VALUE val, const char *method, ID mid));
+ALWAYS_INLINE(static VALUE rb_to_integer_with_id_exception(VALUE val, const char *method, ID mid, int raise));
+/* Integer specific rb_check_convert_type_with_id */
static inline VALUE
-rb_to_integer(VALUE val, const char *method, ID mid)
+rb_to_integer_with_id_exception(VALUE val, const char *method, ID mid, int raise)
{
VALUE v;
if (RB_INTEGER_TYPE_P(val)) return val;
- v = try_to_int(val, mid, TRUE);
+ v = try_to_int(val, mid, raise);
+ if (!raise && NIL_P(v)) return Qnil;
if (!RB_INTEGER_TYPE_P(v)) {
conversion_mismatch(val, "Integer", method, v);
}
return v;
}
+#define rb_to_integer(val, method, mid) \
+ rb_to_integer_with_id_exception(val, method, mid, TRUE)
/**
* Tries to convert \a val into \c Integer.
@@ -3371,6 +3375,12 @@ rb_Integer(VALUE val)
return rb_convert_to_integer(val, 0, TRUE);
}
+VALUE
+rb_check_integer_type(VALUE val)
+{
+ return rb_to_integer_with_id_exception(val, "to_int", idTo_int, FALSE);
+}
+
int
rb_bool_expected(VALUE obj, const char *flagname)
{
diff --git a/spec/ruby/core/integer/try_convert_spec.rb b/spec/ruby/core/integer/try_convert_spec.rb
new file mode 100644
index 0000000000..45c66eec79
--- /dev/null
+++ b/spec/ruby/core/integer/try_convert_spec.rb
@@ -0,0 +1,40 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+ruby_version_is "3.1" do
+ describe "Integer.try_convert" do
+ it "returns the argument if it's an Integer" do
+ x = 42
+ Integer.try_convert(x).should equal(x)
+ end
+
+ it "returns nil when the argument does not respond to #to_int" do
+ Integer.try_convert(Object.new).should be_nil
+ end
+
+ it "sends #to_int to the argument and returns the result if it's nil" do
+ obj = mock("to_int")
+ obj.should_receive(:to_int).and_return(nil)
+ Integer.try_convert(obj).should be_nil
+ end
+
+ it "sends #to_int to the argument and returns the result if it's an Integer" do
+ x = 234
+ obj = mock("to_int")
+ obj.should_receive(:to_int).and_return(x)
+ Integer.try_convert(obj).should equal(x)
+ end
+
+ it "sends #to_int to the argument and raises TypeError if it's not a kind of Integer" do
+ obj = mock("to_int")
+ obj.should_receive(:to_int).and_return(Object.new)
+ -> { Integer.try_convert obj }.should raise_error(TypeError)
+ end
+
+ it "does not rescue exceptions raised by #to_int" do
+ obj = mock("to_int")
+ obj.should_receive(:to_int).and_raise(RuntimeError)
+ -> { Integer.try_convert obj }.should raise_error(RuntimeError)
+ end
+ end
+end
diff --git a/test/ruby/test_integer.rb b/test/ruby/test_integer.rb
index 2755987276..1cd256a1cf 100644
--- a/test/ruby/test_integer.rb
+++ b/test/ruby/test_integer.rb
@@ -660,4 +660,21 @@ class TestInteger < Test::Unit::TestCase
def o.fdiv(x); 1; end
assert_equal(1.0, 1.fdiv(o))
end
+
+ def test_try_convert
+ assert_equal(1, Integer.try_convert(1))
+ assert_equal(1, Integer.try_convert(1.0))
+ assert_nil Integer.try_convert("1")
+ o = Object.new
+ assert_nil Integer.try_convert(o)
+ def o.to_i; 1; end
+ assert_nil Integer.try_convert(o)
+ o = Object.new
+ def o.to_int; 1; end
+ assert_equal(1, Integer.try_convert(o))
+
+ o = Object.new
+ def o.to_int; Object.new; end
+ assert_raise_with_message(TypeError, /can't convert Object to Integer/) {Integer.try_convert(o)}
+ end
end