diff --git a/range.c b/range.c index e9073e53c40eb3..a6bf0fca51eb35 100644 --- a/range.c +++ b/range.c @@ -827,7 +827,12 @@ sym_each_i(VALUE v, VALUE arg) * (1..4).size # => 4 * (1...4).size # => 3 * (1..).size # => Infinity - * ('a'..'z').size #=> nil + * ('a'..'z').size # => nil + * + * If +self+ is not iterable, raises an exception: + * + * (0.5..2.5).size # TypeError + * (..1).size # TypeError * * Related: Range#count. */ @@ -836,7 +841,8 @@ static VALUE range_size(VALUE range) { VALUE b = RANGE_BEG(range), e = RANGE_END(range); - if (rb_obj_is_kind_of(b, rb_cNumeric)) { + + if (RB_INTEGER_TYPE_P(b)) { if (rb_obj_is_kind_of(e, rb_cNumeric)) { return ruby_num_interval_step_size(b, e, INT2FIX(1), EXCL(range)); } @@ -844,10 +850,10 @@ range_size(VALUE range) return DBL2NUM(HUGE_VAL); } } - else if (NIL_P(b)) { - if (rb_obj_is_kind_of(e, rb_cNumeric)) { - return DBL2NUM(HUGE_VAL); - } + + if (!discrete_object_p(b)) { + rb_raise(rb_eTypeError, "can't iterate from %s", + rb_obj_classname(b)); } return Qnil; diff --git a/spec/ruby/core/range/size_spec.rb b/spec/ruby/core/range/size_spec.rb index 81ea5a3846ab37..a1fe3ce17de3d7 100644 --- a/spec/ruby/core/range/size_spec.rb +++ b/spec/ruby/core/range/size_spec.rb @@ -4,34 +4,22 @@ it "returns the number of elements in the range" do (1..16).size.should == 16 (1...16).size.should == 15 - - (1.0..16.0).size.should == 16 - (1.0...16.0).size.should == 15 - (1.0..15.9).size.should == 15 - (1.1..16.0).size.should == 15 - (1.1..15.9).size.should == 15 end it "returns 0 if last is less than first" do (16..0).size.should == 0 - (16.0..0.0).size.should == 0 - (Float::INFINITY..0).size.should == 0 end it 'returns Float::INFINITY for increasing, infinite ranges' do (0..Float::INFINITY).size.should == Float::INFINITY - (-Float::INFINITY..0).size.should == Float::INFINITY - (-Float::INFINITY..Float::INFINITY).size.should == Float::INFINITY end it 'returns Float::INFINITY for endless ranges if the start is numeric' do eval("(1..)").size.should == Float::INFINITY - eval("(0.5...)").size.should == Float::INFINITY end it 'returns nil for endless ranges if the start is not numeric' do eval("('z'..)").size.should == nil - eval("([]...)").size.should == nil end ruby_version_is ""..."3.2" do @@ -43,7 +31,7 @@ end end - ruby_version_is "3.2" do + ruby_version_is "3.2"..."3.4" do it 'returns Float::INFINITY for all beginless ranges if the end is numeric' do (..1).size.should == Float::INFINITY (...0.5).size.should == Float::INFINITY @@ -58,6 +46,54 @@ end end + ruby_version_is ""..."3.4" do + it "returns the number of elements in the range" do + (1.0..16.0).size.should == 16 + (1.0...16.0).size.should == 15 + (1.0..15.9).size.should == 15 + (1.1..16.0).size.should == 15 + (1.1..15.9).size.should == 15 + end + + it "returns 0 if last is less than first" do + (16.0..0.0).size.should == 0 + (Float::INFINITY..0).size.should == 0 + end + + it 'returns Float::INFINITY for increasing, infinite ranges' do + (-Float::INFINITY..0).size.should == Float::INFINITY + (-Float::INFINITY..Float::INFINITY).size.should == Float::INFINITY + end + + it 'returns Float::INFINITY for endless ranges if the start is numeric' do + eval("(0.5...)").size.should == Float::INFINITY + end + + it 'returns nil for endless ranges if the start is not numeric' do + eval("([]...)").size.should == nil + end + end + + ruby_version_is "3.4" do + it 'raises TypeError if a range is not iterable' do + -> { (1.0..16.0).size }.should raise_error(TypeError, /can't iterate from/) + -> { (1.0...16.0).size }.should raise_error(TypeError, /can't iterate from/) + -> { (1.0..15.9).size }.should raise_error(TypeError, /can't iterate from/) + -> { (1.1..16.0).size }.should raise_error(TypeError, /can't iterate from/) + -> { (1.1..15.9).size }.should raise_error(TypeError, /can't iterate from/) + -> { (16.0..0.0).size }.should raise_error(TypeError, /can't iterate from/) + -> { (Float::INFINITY..0).size }.should raise_error(TypeError, /can't iterate from/) + -> { (-Float::INFINITY..0).size }.should raise_error(TypeError, /can't iterate from/) + -> { (-Float::INFINITY..Float::INFINITY).size }.should raise_error(TypeError, /can't iterate from/) + -> { (..1).size }.should raise_error(TypeError, /can't iterate from/) + -> { (...0.5).size }.should raise_error(TypeError, /can't iterate from/) + -> { (..nil).size }.should raise_error(TypeError, /can't iterate from/) + -> { (...'o').size }.should raise_error(TypeError, /can't iterate from/) + -> { eval("(0.5...)").size }.should raise_error(TypeError, /can't iterate from/) + -> { eval("([]...)").size }.should raise_error(TypeError, /can't iterate from/) + end + end + it "returns nil if first and last are not Numeric" do (:a..:z).size.should be_nil ('a'..'z').size.should be_nil diff --git a/test/ruby/test_range.rb b/test/ruby/test_range.rb index 2aa69dc6a49e47..84b3b205f03343 100644 --- a/test/ruby/test_range.rb +++ b/test/ruby/test_range.rb @@ -983,26 +983,38 @@ def test_comparison_when_recursive end def test_size - assert_equal 42, (1..42).size - assert_equal 41, (1...42).size - assert_equal 6, (1...6.3).size - assert_equal 5, (1.1...6).size - assert_equal 3, (1..3r).size - assert_equal 2, (1...3r).size - assert_equal 3, (1..3.1r).size - assert_equal 3, (1...3.1r).size - assert_equal 42, (1..42).each.size + Enumerator.product([:to_i, :to_f, :to_r].repeated_permutation(2), [1, 10], [5, 5.5], [true, false]) do |(m1, m2), beg, ende, exclude_end| + r = Range.new(beg.send(m1), ende.send(m2), exclude_end) + iterable = true + yielded = [] + begin + r.each { yielded << _1 } + rescue TypeError + iterable = false + end + + if iterable + assert_equal(yielded.size, r.size, "failed on #{r}") + assert_equal(yielded.size, r.each.size, "failed on #{r}") + else + assert_raise(TypeError, "failed on #{r}") { r.size } + assert_raise(TypeError, "failed on #{r}") { r.each.size } + end + end + assert_nil ("a"..."z").size - assert_nil ("a"...).size - assert_nil (..."z").size # [Bug #18983] - assert_nil (nil...nil).size # [Bug #18983] - - assert_equal Float::INFINITY, (1...).size - assert_equal Float::INFINITY, (1.0...).size - assert_equal Float::INFINITY, (...1).size - assert_equal Float::INFINITY, (...1.0).size - assert_nil ("a"...).size - assert_nil (..."z").size + + assert_equal Float::INFINITY, (1..).size + assert_raise(TypeError) { (1.0..).size } + assert_raise(TypeError) { (1r..).size } + assert_nil ("a"..).size + + assert_raise(TypeError) { (..1).size } + assert_raise(TypeError) { (..1.0).size } + assert_raise(TypeError) { (..1r).size } + assert_raise(TypeError) { (..'z').size } + + assert_raise(TypeError) { (nil...nil).size } end def test_bsearch_typechecks_return_values