diff --git a/range.c b/range.c index a52418d2cb..e8a35c22cc 100644 --- a/range.c +++ b/range.c @@ -309,8 +309,8 @@ range_each_func(VALUE range, int (*func)(VALUE, VALUE), VALUE arg) } } -// NB: Two functions below (step_i_iter and step_i) are used only to maintain the -// backward-compatible behavior for string ranges with integer steps. If that branch +// NB: Two functions below (step_i_iter, sym_step_i and step_i) are used only to maintain the +// backward-compatible behavior for string and symbol ranges with integer steps. If that branch // will be removed from range_step, these two can go, too. static bool step_i_iter(VALUE arg) @@ -328,6 +328,15 @@ step_i_iter(VALUE arg) return true; } +static int +sym_step_i(VALUE i, VALUE arg) +{ + if (step_i_iter(arg)) { + rb_yield(rb_str_intern(i)); + } + return 0; +} + static int step_i(VALUE i, VALUE arg) { @@ -482,15 +491,16 @@ range_step(int argc, VALUE *argv, VALUE range) 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); - // For backward compatibility reasons (conforming to behavior before 3.4), String supports - // both old behavior ('a'..).step(1) and new behavior ('a'..).step('a') + // For backward compatibility reasons (conforming to behavior before 3.4), String/Symbol + // supports both old behavior ('a'..).step(1) and new behavior ('a'..).step('a') // Hence the additional conversion/addional checks. - const VALUE sb = rb_check_string_type(b); + const VALUE str_b = rb_check_string_type(b); + const VALUE sym_b = SYMBOL_P(b) ? rb_sym2str(b) : Qnil; if (rb_check_arity(argc, 0, 1)) step = argv[0]; else { - if (b_num_p || !NIL_P(sb) || (NIL_P(b) && e_num_p)) + if (b_num_p || !NIL_P(str_b) || !NIL_P(sym_b) || (NIL_P(b) && e_num_p)) step = INT2FIX(1); else rb_raise(rb_eArgError, "step is required for non-numeric ranges"); @@ -561,17 +571,28 @@ 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)) { /* done */ - } else if (!NIL_P(sb) && 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 // See discussion in https://bugs.ruby-lang.org/issues/18368 VALUE iter[2] = {INT2FIX(1), step}; if (NIL_P(e)) { - rb_str_upto_endless_each(sb, step_i, (VALUE)iter); + rb_str_upto_endless_each(str_b, step_i, (VALUE)iter); } else { - rb_str_upto_each(sb, 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)) { + // same as above: backward compatibility for symbols + + VALUE iter[2] = {INT2FIX(1), step}; + + if (NIL_P(e)) { + rb_str_upto_endless_each(sym_b, sym_step_i, (VALUE)iter); + } + else { + rb_str_upto_each(sym_b, rb_sym2str(e), EXCL(range), sym_step_i, (VALUE)iter); } } else { v = b; diff --git a/test/ruby/test_range.rb b/test/ruby/test_range.rb index 6c591b3155..842be77ab3 100644 --- a/test/ruby/test_range.rb +++ b/test/ruby/test_range.rb @@ -519,6 +519,58 @@ class TestRange < Test::Unit::TestCase assert_equal(%w[a b c], ('a'...).step.take(3)) end + def test_step_symbol_legacy + # finite + a = [] + (:a..:g).step(2) { a << _1 } + assert_equal(%i[a c e g], a) + + assert_kind_of(Enumerator, (:a..:g).step(2)) + assert_equal(%i[a c e g], (:a..:g).step(2).to_a) + + a = [] + (:a...:g).step(2) { a << _1 } + assert_equal(%i[a c e], a) + + assert_kind_of(Enumerator, (:a...:g).step(2)) + assert_equal(%i[a c e], (:a...:g).step(2).to_a) + + # endless + a = [] + (:a...).step(2) { a << _1; break if a.size == 3 } + assert_equal(%i[a c e], a) + + assert_kind_of(Enumerator, (:a...).step(2)) + assert_equal(%i[a c e], (:a...).step(2).take(3)) + + # beginless + assert_raise(ArgumentError) { (...:g).step(2) {} } + assert_raise(ArgumentError) { (...:g).step(2) } + + # step is not provided + a = [] + (:a..:d).step { a << _1 } + assert_equal(%i[a b c d], a) + + assert_kind_of(Enumerator, (:a..:d).step) + assert_equal(%i[a b c d], (:a..:d).step.to_a) + + a = [] + (:a...:d).step { a << _1 } + assert_equal(%i[a b c], a) + + assert_kind_of(Enumerator, (:a...:d).step) + assert_equal(%i[a b c], (:a...:d).step.to_a) + + # endless + a = [] + (:a...).step { a << _1; break if a.size == 3 } + assert_equal(%i[a b c], a) + + assert_kind_of(Enumerator, (:a...).step) + assert_equal(%i[a b c], (:a...).step.take(3)) + end + def test_step_bug15537 assert_equal([10.0, 9.0, 8.0, 7.0], (10 ..).step(-1.0).take(4)) assert_equal([10.0, 9.0, 8.0, 7.0], (10.0 ..).step(-1).take(4))