[Misc #18984] Raise TypeError from Range#size if the range is not iterable

This commit is contained in:
Kouhei Yanagita 2023-10-13 15:02:23 +09:00 коммит произвёл Jeremy Evans
Родитель f9f25d0ed0
Коммит 9f6deaa688
3 изменённых файлов: 92 добавлений и 38 удалений

18
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;

Просмотреть файл

@ -4,34 +4,22 @@ describe "Range#size" do
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 @@ describe "Range#size" do
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 @@ describe "Range#size" do
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

Просмотреть файл

@ -983,26 +983,38 @@ class TestRange < Test::Unit::TestCase
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
assert_nil ("a"..."z").size
assert_nil ("a"...).size
assert_nil (..."z").size # [Bug #18983]
assert_nil (nil...nil).size # [Bug #18983]
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
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
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_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