[Bug #21030] Fix step for non-numeric range
When the end points of an inclusive range equal, `Range#step` should yields the element once.
This commit is contained in:
parent
d9e1a7cdf8
commit
f56f3eaae5
Notes:
git
2025-01-12 17:47:22 +00:00
37
range.c
37
range.c
@ -488,6 +488,7 @@ range_step(int argc, VALUE *argv, VALUE range)
|
|||||||
|
|
||||||
b = RANGE_BEG(range);
|
b = RANGE_BEG(range);
|
||||||
e = RANGE_END(range);
|
e = RANGE_END(range);
|
||||||
|
v = b;
|
||||||
|
|
||||||
const VALUE b_num_p = rb_obj_is_kind_of(b, rb_cNumeric);
|
const VALUE b_num_p = rb_obj_is_kind_of(b, rb_cNumeric);
|
||||||
const VALUE e_num_p = rb_obj_is_kind_of(e, rb_cNumeric);
|
const VALUE e_num_p = rb_obj_is_kind_of(e, rb_cNumeric);
|
||||||
@ -559,7 +560,8 @@ range_step(int argc, VALUE *argv, VALUE range)
|
|||||||
rb_yield(LONG2NUM(i));
|
rb_yield(LONG2NUM(i));
|
||||||
i += unit;
|
i += unit;
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
if (!EXCL(range))
|
if (!EXCL(range))
|
||||||
end += 1;
|
end += 1;
|
||||||
i = FIX2LONG(b);
|
i = FIX2LONG(b);
|
||||||
@ -571,7 +573,8 @@ range_step(int argc, VALUE *argv, VALUE range)
|
|||||||
}
|
}
|
||||||
else if (b_num_p && step_num_p && ruby_float_step(b, e, step, EXCL(range), TRUE)) {
|
else if (b_num_p && step_num_p && ruby_float_step(b, e, step, EXCL(range), TRUE)) {
|
||||||
/* done */
|
/* done */
|
||||||
} else if (!NIL_P(str_b) && FIXNUM_P(step)) {
|
}
|
||||||
|
else if (!NIL_P(str_b) && FIXNUM_P(step)) {
|
||||||
// backwards compatibility behavior for String only, when no step/Integer step is passed
|
// backwards compatibility behavior for String only, when no step/Integer step is passed
|
||||||
// See discussion in https://bugs.ruby-lang.org/issues/18368
|
// See discussion in https://bugs.ruby-lang.org/issues/18368
|
||||||
|
|
||||||
@ -583,7 +586,8 @@ range_step(int argc, VALUE *argv, VALUE range)
|
|||||||
else {
|
else {
|
||||||
rb_str_upto_each(str_b, e, EXCL(range), step_i, (VALUE)iter);
|
rb_str_upto_each(str_b, e, EXCL(range), step_i, (VALUE)iter);
|
||||||
}
|
}
|
||||||
} else if (!NIL_P(sym_b) && FIXNUM_P(step)) {
|
}
|
||||||
|
else if (!NIL_P(sym_b) && FIXNUM_P(step)) {
|
||||||
// same as above: backward compatibility for symbols
|
// same as above: backward compatibility for symbols
|
||||||
|
|
||||||
VALUE iter[2] = {INT2FIX(1), step};
|
VALUE iter[2] = {INT2FIX(1), step};
|
||||||
@ -594,10 +598,13 @@ range_step(int argc, VALUE *argv, VALUE range)
|
|||||||
else {
|
else {
|
||||||
rb_str_upto_each(sym_b, rb_sym2str(e), EXCL(range), sym_step_i, (VALUE)iter);
|
rb_str_upto_each(sym_b, rb_sym2str(e), EXCL(range), sym_step_i, (VALUE)iter);
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
v = b;
|
else if (NIL_P(e)) {
|
||||||
if (!NIL_P(e)) {
|
// endless range
|
||||||
if (b_num_p && step_num_p && r_less(step, INT2FIX(0)) < 0) {
|
for (;; v = rb_funcall(v, id_plus, 1, step))
|
||||||
|
rb_yield(v);
|
||||||
|
}
|
||||||
|
else if (b_num_p && step_num_p && r_less(step, INT2FIX(0)) < 0) {
|
||||||
// iterate backwards, for consistency with ArithmeticSequence
|
// iterate backwards, for consistency with ArithmeticSequence
|
||||||
if (EXCL(range)) {
|
if (EXCL(range)) {
|
||||||
for (; r_less(e, v) < 0; v = rb_funcall(v, id_plus, 1, step))
|
for (; r_less(e, v) < 0; v = rb_funcall(v, id_plus, 1, step))
|
||||||
@ -610,15 +617,19 @@ range_step(int argc, VALUE *argv, VALUE range)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
}
|
||||||
|
else if ((dir = r_less(b, e)) == 0) {
|
||||||
|
if (!EXCL(range)) {
|
||||||
|
rb_yield(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (dir == r_less(b, rb_funcall(b, id_plus, 1, step))) {
|
||||||
// Direction of the comparison. We use it as a comparison operator in cycle:
|
// Direction of the comparison. We use it as a comparison operator in cycle:
|
||||||
// if begin < end, the cycle performs while value < end (iterating forward)
|
// if begin < end, the cycle performs while value < end (iterating forward)
|
||||||
// if begin > end, the cycle performs while value > end (iterating backward with
|
// if begin > end, the cycle performs while value > end (iterating backward with
|
||||||
// a negative step)
|
// a negative step)
|
||||||
dir = r_less(b, e);
|
|
||||||
// One preliminary addition to check the step moves iteration in the same direction as
|
// One preliminary addition to check the step moves iteration in the same direction as
|
||||||
// from begin to end; otherwise, the iteration should be empty.
|
// from begin to end; otherwise, the iteration should be empty.
|
||||||
if (r_less(b, rb_funcall(b, id_plus, 1, step)) == dir) {
|
|
||||||
if (EXCL(range)) {
|
if (EXCL(range)) {
|
||||||
for (; r_less(v, e) == dir; v = rb_funcall(v, id_plus, 1, step))
|
for (; r_less(v, e) == dir; v = rb_funcall(v, id_plus, 1, step))
|
||||||
rb_yield(v);
|
rb_yield(v);
|
||||||
@ -630,12 +641,6 @@ range_step(int argc, VALUE *argv, VALUE range)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
for (;; v = rb_funcall(v, id_plus, 1, step))
|
|
||||||
rb_yield(v);
|
|
||||||
}
|
|
||||||
return range;
|
return range;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -594,6 +594,22 @@ class TestRange < Test::Unit::TestCase
|
|||||||
assert_equal(4, (1.0...5.6).step(1.5).to_a.size)
|
assert_equal(4, (1.0...5.6).step(1.5).to_a.size)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_step_with_nonnumeric_endpoint
|
||||||
|
num = Data.define(:value) do
|
||||||
|
def coerce(o); [o, 100]; end
|
||||||
|
def <=>(o) value<=>o; end
|
||||||
|
def +(o) with(value: value + o) end
|
||||||
|
end
|
||||||
|
i = num.new(100)
|
||||||
|
|
||||||
|
assert_equal([100], (100..100).step(10).to_a)
|
||||||
|
assert_equal([], (100...100).step(10).to_a)
|
||||||
|
assert_equal([100], (100..i).step(10).to_a)
|
||||||
|
assert_equal([i], (i..100).step(10).to_a)
|
||||||
|
assert_equal([], (100...i).step(10).to_a)
|
||||||
|
assert_equal([], (i...100).step(10).to_a)
|
||||||
|
end
|
||||||
|
|
||||||
def test_each
|
def test_each
|
||||||
a = []
|
a = []
|
||||||
(0..10).each {|x| a << x }
|
(0..10).each {|x| a << x }
|
||||||
|
Loading…
x
Reference in New Issue
Block a user