Skip to content

Commit

Permalink
[Misc #18984] Raise TypeError from Range#size if the range is not ite…
Browse files Browse the repository at this point in the history
…rable
  • Loading branch information
kyanagi authored and jeremyevans committed Apr 10, 2024
1 parent f9f25d0 commit 9f6deaa
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 38 deletions.
18 changes: 12 additions & 6 deletions range.c
Expand Up @@ -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.
*/
Expand All @@ -836,18 +841,19 @@ 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));
}
if (NIL_P(e)) {
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;
Expand Down
62 changes: 49 additions & 13 deletions spec/ruby/core/range/size_spec.rb
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
50 changes: 31 additions & 19 deletions test/ruby/test_range.rb
Expand Up @@ -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
Expand Down

0 comments on commit 9f6deaa

Please sign in to comment.