ruby-changes:50986
From: mame <ko1@a...>
Date: Fri, 20 Apr 2018 00:18:58 +0900 (JST)
Subject: [ruby-changes:50986] mame:r63192 (trunk): Introduce endless range [Feature#12912]
mame 2018-04-20 00:18:50 +0900 (Fri, 20 Apr 2018) New Revision: 63192 https://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=63192 Log: Introduce endless range [Feature#12912] Typical usages: ``` p ary[1..] # drop the first element; identical to ary[1..-1] (1..).each {|n|...} # iterate forever from 1; identical to 1.step{...} ``` Modified files: trunk/internal.h trunk/numeric.c trunk/parse.y trunk/range.c trunk/string.c trunk/test/ruby/test_array.rb trunk/test/ruby/test_range.rb Index: string.c =================================================================== --- string.c (revision 63191) +++ string.c (revision 63192) @@ -4325,6 +4325,50 @@ str_upto_each(VALUE beg, VALUE end, int https://github.com/ruby/ruby/blob/trunk/string.c#L4325 return beg; } +VALUE +rb_str_upto_endless_each(VALUE beg, VALUE (*each)(VALUE, VALUE), VALUE arg) +{ + VALUE current; + ID succ; + + CONST_ID(succ, "succ"); + /* both edges are all digits */ + if (is_ascii_string(beg) && ISDIGIT(RSTRING_PTR(beg)[0]) && + all_digits_p(RSTRING_PTR(beg), RSTRING_LEN(beg))) { + VALUE b, args[2], fmt = rb_fstring_cstr("%.*d"); + int width = RSTRING_LENINT(beg); + b = rb_str_to_inum(beg, 10, FALSE); + if (FIXNUM_P(b)) { + long bi = FIX2LONG(b); + rb_encoding *usascii = rb_usascii_encoding(); + + while (FIXABLE(bi)) { + (*each)(rb_enc_sprintf(usascii, "%.*ld", width, bi), arg); + bi++; + } + b = LONG2NUM(bi); + } + args[0] = INT2FIX(width); + while (1) { + args[1] = b; + (*each)(rb_str_format(numberof(args), args, fmt), arg); + b = rb_funcallv(b, succ, 0, 0); + } + } + /* normal case */ + current = rb_str_dup(beg); + while (1) { + VALUE next = rb_funcallv(current, succ, 0, 0); + (*each)(current, arg); + current = next; + StringValue(current); + if (RSTRING_LEN(current) == 0) + break; + } + + return beg; +} + static int include_range_i(VALUE str, VALUE arg) { Index: range.c =================================================================== --- range.c (revision 63191) +++ range.c (revision 63192) @@ -37,7 +37,7 @@ static VALUE r_cover_p(VALUE, VALUE, VAL https://github.com/ruby/ruby/blob/trunk/range.c#L37 static void range_init(VALUE range, VALUE beg, VALUE end, VALUE exclude_end) { - if (!FIXNUM_P(beg) || !FIXNUM_P(end)) { + if ((!FIXNUM_P(beg) || !FIXNUM_P(end)) && !NIL_P(end)) { VALUE v; v = rb_funcall(beg, id_cmp, 1, end); @@ -400,7 +400,19 @@ range_step(int argc, VALUE *argv, VALUE https://github.com/ruby/ruby/blob/trunk/range.c#L400 step = check_step_domain(step); } - if (FIXNUM_P(b) && FIXNUM_P(e) && FIXNUM_P(step)) { /* fixnums are special */ + if (FIXNUM_P(b) && NIL_P(e) && FIXNUM_P(step)) { + long i = FIX2LONG(b), unit = FIX2LONG(step); + while (1) { + rb_yield(LONG2FIX(i)); + if (i + unit < i) break; + i += unit; + } + b = LONG2NUM(i); + + for (;; b = rb_funcallv(b, id_succ, 0, 0)) + rb_yield(b); + } + else if (FIXNUM_P(b) && FIXNUM_P(e) && FIXNUM_P(step)) { /* fixnums are special */ long end = FIX2LONG(e); long i, unit = FIX2LONG(step); @@ -414,16 +426,21 @@ range_step(int argc, VALUE *argv, VALUE https://github.com/ruby/ruby/blob/trunk/range.c#L426 } } - else if (SYMBOL_P(b) && SYMBOL_P(e)) { /* symbols are special */ + else if (SYMBOL_P(b) && (NIL_P(e) || SYMBOL_P(e))) { /* symbols are special */ VALUE args[2], iter[2]; - - args[0] = rb_sym2str(e); - args[1] = EXCL(range) ? Qtrue : Qfalse; iter[0] = INT2FIX(1); iter[1] = step; - rb_block_call(rb_sym2str(b), rb_intern("upto"), 2, args, sym_step_i, (VALUE)iter); + + if (NIL_P(e)) { + rb_str_upto_endless_each(rb_sym2str(b), sym_step_i, (VALUE)iter); + } + else { + args[0] = rb_sym2str(e); + args[1] = EXCL(range) ? Qtrue : Qfalse; + rb_block_call(rb_sym2str(b), rb_intern("upto"), 2, args, sym_step_i, (VALUE)iter); + } } - else if (ruby_float_step(b, e, step, EXCL(range))) { + else if (ruby_float_step(b, e, step, EXCL(range), TRUE)) { /* done */ } else if (rb_obj_is_kind_of(b, rb_cNumeric) || @@ -433,7 +450,7 @@ range_step(int argc, VALUE *argv, VALUE https://github.com/ruby/ruby/blob/trunk/range.c#L450 VALUE v = b; int i = 0; - while (RTEST(rb_funcall(v, op, 1, e))) { + while (NIL_P(e) || RTEST(rb_funcall(v, op, 1, e))) { rb_yield(v); i++; v = rb_funcall(b, '+', 1, rb_funcall(INT2NUM(i), '*', 1, step)); @@ -446,11 +463,17 @@ range_step(int argc, VALUE *argv, VALUE https://github.com/ruby/ruby/blob/trunk/range.c#L463 VALUE args[2], iter[2]; b = tmp; - args[0] = e; - args[1] = EXCL(range) ? Qtrue : Qfalse; iter[0] = INT2FIX(1); iter[1] = step; - rb_block_call(b, rb_intern("upto"), 2, args, step_i, (VALUE)iter); + + if (NIL_P(e)) { + rb_str_upto_endless_each(b, step_i, (VALUE)iter); + } + else { + args[0] = e; + args[1] = EXCL(range) ? Qtrue : Qfalse; + rb_block_call(b, rb_intern("upto"), 2, args, step_i, (VALUE)iter); + } } else { VALUE args[2]; @@ -746,7 +769,18 @@ range_each(VALUE range) https://github.com/ruby/ruby/blob/trunk/range.c#L769 beg = RANGE_BEG(range); end = RANGE_END(range); - if (FIXNUM_P(beg) && FIXNUM_P(end)) { /* fixnums are special */ + if (FIXNUM_P(beg) && NIL_P(end)) { + long i = FIX2LONG(beg); + while (FIXABLE(i)) { + rb_yield(LONG2FIX(i++)); + } + beg = LONG2NUM(i); + + inf_loop: + for (;; beg = rb_funcallv(beg, id_succ, 0, 0)) + rb_yield(beg); + } + else if (FIXNUM_P(beg) && FIXNUM_P(end)) { /* fixnums are special */ long lim = FIX2LONG(end); long i; @@ -767,18 +801,27 @@ range_each(VALUE range) https://github.com/ruby/ruby/blob/trunk/range.c#L801 VALUE tmp = rb_check_string_type(beg); if (!NIL_P(tmp)) { - VALUE args[2]; + if (!NIL_P(end)) { + VALUE args[2]; - args[0] = end; - args[1] = EXCL(range) ? Qtrue : Qfalse; - rb_block_call(tmp, rb_intern("upto"), 2, args, each_i, 0); + args[0] = end; + args[1] = EXCL(range) ? Qtrue : Qfalse; + rb_block_call(tmp, rb_intern("upto"), 2, args, each_i, 0); + } + else if (RB_TYPE_P(beg, T_STRING)) { + rb_str_upto_endless_each(beg, each_i, 0); + } + else goto inf_loop; } else { if (!discrete_object_p(beg)) { rb_raise(rb_eTypeError, "can't iterate from %s", rb_obj_classname(beg)); } - range_each_func(range, each_i, 0); + if (!NIL_P(end)) + range_each_func(range, each_i, 0); + else + goto inf_loop; } } return range; @@ -1012,7 +1055,8 @@ rb_range_beg_len(VALUE range, long *begp https://github.com/ruby/ruby/blob/trunk/range.c#L1055 if (!rb_range_values(range, &b, &e, &excl)) return Qfalse; beg = NUM2LONG(b); - end = NUM2LONG(e); + end = NIL_P(e) ? -1 : NUM2LONG(e); + if (NIL_P(e)) excl = 0; origbeg = beg; origend = end; if (beg < 0) { @@ -1072,16 +1116,16 @@ range_to_s(VALUE range) https://github.com/ruby/ruby/blob/trunk/range.c#L1116 static VALUE inspect_range(VALUE range, VALUE dummy, int recur) { - VALUE str, str2; + VALUE str, str2 = Qundef; if (recur) { return rb_str_new2(EXCL(range) ? "(... ... ...)" : "(... .. ...)"); } str = rb_inspect(RANGE_BEG(range)); - str2 = rb_inspect(RANGE_END(range)); + if (!NIL_P(RANGE_END(range))) str2 = rb_inspect(RANGE_END(range)); str = rb_str_dup(str); rb_str_cat(str, "...", EXCL(range) ? 3 : 2); - rb_str_append(str, str2); + if (str2 != Qundef) rb_str_append(str, str2); OBJ_INFECT(str, range); return str; Index: parse.y =================================================================== --- parse.y (revision 63191) +++ parse.y (revision 63192) @@ -1903,6 +1903,22 @@ arg : lhs '=' arg_rhs https://github.com/ruby/ruby/blob/trunk/parse.y#L1903 /*% %*/ /*% ripper: dot3!($1, $3) %*/ } + | arg tDOT2 + { + /*%%%*/ + value_expr($1); + $$ = NEW_DOT2($1, new_nil(&@$), &@$); + /*% %*/ + /*% ripper: dot2!($1, Qnil) %*/ + } + | arg tDOT3 + { + /*%%%*/ + value_expr($1); + $$ = NEW_DOT3($1, new_nil(&@$), &@$); + /*% %*/ + /*% ripper: dot3!($1, Qnil) %*/ + } | arg '+' arg { $$ = call_bin_op(p, $1, '+', $3, &@2, &@$); Index: numeric.c =================================================================== --- numeric.c (revision 63191) +++ numeric.c (revision 63192) @@ -2469,11 +2469,11 @@ ruby_float_step_size(double beg, double https://github.com/ruby/ruby/blob/trunk/numeric.c#L2469 } int -ruby_float_step(VALUE from, VALUE to, VALUE step, int excl) +ruby_float_step(VALUE from, VALUE to, VALUE step, int excl, int allow_endless) { if (RB_TYPE_P(from, T_FLOAT) || RB_TYPE_P(to, T_FLOAT) || RB_TYPE_P(step, T_FLOAT)) { double beg = NUM2DBL(from); - double end = NUM2DBL(to); + double end = (allow_endless && NIL_P(to)) ? HUGE_VAL : NUM2DBL(to); double unit = NUM2DBL(step); double n = ruby_float_step_size(beg, end, unit, excl); long i; @@ -2712,7 +2712,7 @@ num_step(int argc, VALUE *argv, VALUE fr https://github.com/ruby/ruby/blob/trunk/numeric.c#L2712 } } } - else if (!ruby_float_step(from, to, step, FALSE)) { + else if (!ruby_float_step(from, to, step, FALSE, FALSE)) { VALUE i = from; if (inf) { Index: internal.h =================================================================== --- internal.h (revision 63191) +++ internal.h (revision 63192) @@ -1404,7 +1404,7 @@ enum ruby_num_rounding_mode { https://github.com/ruby/ruby/blob/trunk/internal.h#L1404 int rb_num_to_uint(VALUE val, unsigned int *ret); VALUE ruby_num_interval_step_size(VALUE from, VALUE to, VALUE step, int excl); -int ruby_float_step(VALUE from, VALUE to, VALUE step, int excl); +int ruby_float_step(VALUE from, VALUE to, VALUE step, int excl, int allow_endless); double ruby_float_mod(double x, double y); int rb_num_negative_p(VALUE); VALUE rb_int_succ(VALUE num); @@ -2013,6 +2013,7 @@ VALUE rb_gcd_gmp(VALUE x, VALUE y); https://github.com/ruby/ruby/blob/trunk/internal.h#L2013 /* internal use */ VALUE rb_setup_fake_str(struct RString *fake_str, const char *name, long len, rb_encoding *enc); #endif +VALUE rb_str_upto_endless_each(VALUE, VALUE (*each)(), VALUE); /* thread.c (export) */ int ruby_thread_has_gvl_p(void); /* for ext/fiddle/closure.c */ Index: test/ruby/test_array.rb =================================================================== --- test/ruby/test_array.rb (revision 63191) +++ test/ruby/test_array.rb (revision 63192) @@ -41,6 +41,7 @@ class TestArray < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_array.rb#L41 assert_equal(2, x[2]) assert_equal([1, 2, 3], x[1..3]) assert_equal([1, 2, 3], x[1,3]) + assert_equal([3, 4, 5], x[3..]) x[0, 2] = 10 assert_equal([10, 2, 3, 4, 5], x) @@ -199,6 +200,8 @@ class TestArray < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_array.rb#L200 assert_equal([0, 1, 2, 13, 4, 5], [0, 1, 2, 3, 4, 5].fill(3...4){|i| i+10}) assert_equal([0, 1, 12, 13, 14, 5], [0, 1, 2, 3, 4, 5].fill(2..-2){|i| i+10}) assert_equal([0, 1, 12, 13, 4, 5], [0, 1, 2, 3, 4, 5].fill(2...-2){|i| i+10}) + assert_equal([0, 1, 2, 13, 14, 15], [0, 1, 2, 3, 4, 5].fill(3..){|i| i+10}) + assert_equal([0, 1, 2, 13, 14, 15], [0, 1, 2, 3, 4, 5].fill(3...){|i| i+10}) end # From rubicon @@ -346,7 +349,9 @@ class TestArray < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_array.rb#L349 assert_equal(@cls[99], a[-2..-2]) assert_equal(@cls[10, 11, 12], a[9..11]) + assert_equal(@cls[98, 99, 100], a[97..]) assert_equal(@cls[10, 11, 12], a[-91..-89]) + assert_equal(@cls[98, 99, 100], a[-3..]) assert_nil(a[10, -3]) assert_equal [], a[10..7] @@ -428,6 +433,10 @@ class TestArray < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_array.rb#L433 assert_equal(nil, a[10..19] = nil) assert_equal(@cls[*(0..9).to_a] + @cls[nil] + @cls[*(20..99).to_a], a) + a = @cls[*(0..99).to_a] + assert_equal(nil, a[10..] = nil) + assert_equal(@cls[*(0..9).to_a] + @cls[nil], a) + a = @cls[1, 2, 3] a[1, 0] = a assert_equal([1, 1, 2, 3, 2, 3], a) @@ -1378,9 +1387,12 @@ class TestArray < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_array.rb#L1387 assert_equal(@cls[99], a.slice(-2..-2)) assert_equal(@cls[10, 11, 12], a.slice(9..11)) + assert_equal(@cls[98, 99, 100], a.slice(97..)) + assert_equal(@cls[10, 11, 12], a.slice(-91..-89)) assert_equal(@cls[10, 11, 12], a.slice(-91..-89)) assert_nil(a.slice(-101..-1)) + assert_nil(a.slice(-101..)) assert_nil(a.slice(10, -3)) assert_equal @cls[], a.slice(10..7) @@ -2106,6 +2118,7 @@ class TestArray < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_array.rb#L2118 assert_raise(IndexError) { [0][LONGP] = 2 } assert_raise(IndexError) { [0][(LONGP + 1) / 2 - 1] = 2 } assert_raise(IndexError) { [0][LONGP..-1] = 2 } + assert_raise(IndexError) { [0][LONGP..] = 2 } a = [0] a[2] = 4 assert_equal([0, nil, 4], a) Index: test/ruby/test_range.rb =================================================================== --- test/ruby/test_range.rb (revision 63191) +++ test/ruby/test_range.rb (revision 63192) @@ -12,6 +12,9 @@ class TestRange < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_range.rb#L12 assert_raise(ArgumentError) { (1.."3") } + assert_equal((0..nil), Range.new(0, nil, false)) + assert_equal((0...nil), Range.new(0, nil, true)) + obj = Object.new def obj.<=>(other) raise RuntimeError, "cmp" @@ -31,14 +34,17 @@ class TestRange < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_range.rb#L34 assert_equal(["a"], ("a" .. "a").to_a) assert_equal(["a"], ("a" ... "b").to_a) assert_equal(["a", "b"], ("a" .. "b").to_a) + assert_equal([*"a".."z", "aa"], ("a"..).take(27)) end def test_range_numeric_string assert_equal(["6", "7", "8"], ("6".."8").to_a, "[ruby-talk:343187]") assert_equal(["6", "7"], ("6"..."8").to_a) assert_equal(["9", "10"], ("9".."10").to_a) + assert_equal(["9", "10"], ("9"..).take(2)) assert_equal(["09", "10"], ("09".."10").to_a, "[ruby-dev:39361]") assert_equal(["9", "10"], (SimpleDelegator.new("9").."10").to_a) + assert_equal(["9", "10"], (SimpleDelegator.new("9")..).take(2)) assert_equal(["9", "10"], ("9"..SimpleDelegator.new("10")).to_a) end @@ -123,9 +129,10 @@ class TestRange < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_range.rb#L129 assert_equal(r, Marshal.load(Marshal.dump(r))) r = 1...2 assert_equal(r, Marshal.load(Marshal.dump(r))) - s = Marshal.dump(r) - s.sub!(/endi./n, 'end0') - assert_raise(ArgumentError) {Marshal.load(s)} + r = (1..) + assert_equal(r, Marshal.load(Marshal.dump(r))) + r = (1...) + assert_equal(r, Marshal.load(Marshal.dump(r))) end def test_bad_value @@ -135,6 +142,8 @@ class TestRange < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_range.rb#L142 def test_exclude_end assert_not_predicate(0..1, :exclude_end?) assert_predicate(0...1, :exclude_end?) + assert_not_predicate(0.., :exclude_end?) + assert_predicate(0..., :exclude_end?) end def test_eq @@ -145,8 +154,17 @@ class TestRange < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_range.rb#L154 assert_not_equal(r, (1..2)) assert_not_equal(r, (0..2)) assert_not_equal(r, (0...1)) + assert_not_equal(r, (0..nil)) subclass = Class.new(Range) assert_equal(r, subclass.new(0,1)) + + r = (0..nil) + assert_equal(r, r) + assert_equal(r, (0..nil)) + assert_not_equal(r, 0) + assert_not_equal(r, (0...nil)) + subclass = Class.new(Range) + assert_equal(r, subclass.new(0,nil)) end def test_eql @@ -159,12 +177,22 @@ class TestRange < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_range.rb#L177 assert_not_operator(r, :eql?, 0...1) subclass = Class.new(Range) assert_operator(r, :eql?, subclass.new(0,1)) + + r = (0..nil) + assert_operator(r, :eql?, r) + assert_operator(r, :eql?, 0..nil) + assert_not_operator(r, :eql?, 0) + assert_not_operator(r, :eql?, 0...nil) + subclass = Class.new(Range) + assert_operator(r, :eql?, subclass.new(0,nil)) end def test_hash assert_kind_of(Integer, (0..1).hash) assert_equal((0..1).hash, (0..1).hash) assert_not_equal((0..1).hash, (0...1).hash) + assert_equal((0..nil).hash, (0..nil).hash) + assert_not_equal((0..nil).hash, (0...nil).hash) end def test_step @@ -173,17 +201,31 @@ class TestRange < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_range.rb#L201 assert_equal([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], a) a = [] + (0..).step {|x| a << x; break if a.size == 10 } + assert_equal([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], a) + + a = [] (0..10).step(2) {|x| a << x } assert_equal([0, 2, 4, 6, 8, 10], a) + a = [] + (0..).step(2) {|x| a << x; break if a.size == 10 } + assert_equal([0, 2, 4, 6, 8, 10, 12, 14, 16, 18], a) + assert_raise(ArgumentError) { (0..10).step(-1) { } } assert_raise(ArgumentError) { (0..10).step(0) { } } + assert_raise(ArgumentError) { (0..).step(-1) { } } + assert_raise(ArgumentError) { (0..).step(0) { } } a = [] ("a" .. "z").step(2) {|x| a << x } assert_equal(%w(a c e g i k m o q s u w y), a) a = [] + ("a" .. ).step(2) {|x| a << x; break if a.size == 13 } + assert_equal(%w(a c e g i k m o q s u w y), a) + + a = [] ("a" .. "z").step(2**32) {|x| a << x } assert_equal(["a"], a) @@ -192,12 +234,16 @@ class TestRange < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_range.rb#L234 assert_equal([4294967295, 4294967297], a) zero = (2**32).coerce(0).first assert_raise(ArgumentError) { (2**32-1 .. 2**32+1).step(zero) { } } + a = [] + (2**32-1 .. ).step(2) {|x| a << x; break if a.size == 2 } + assert_equal([4294967295, 4294967297], a) o1 = Object.new o2 = Object.new def o1.<=>(x); -1; end def o2.<=>(x); 0; end assert_raise(TypeError) { (o1..o2).step(1) { } } + assert_raise(TypeError) { (o1..).step(1) { } } class << o1; self; end.class_eval do define_method(:succ) { o2 } @@ -217,6 +263,10 @@ class TestRange < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_range.rb#L263 assert_equal([0, 0.5, 1.0, 1.5, 2.0], a) a = [] + (0..).step(0.5) {|x| a << x; break if a.size == 5 } + assert_equal([0, 0.5, 1.0, 1.5, 2.0], a) + + a = [] (0x40000000..0x40000002).step(0.5) {|x| a << x } assert_equal([1073741824, 1073741824.5, 1073741825.0, 1073741825.5, 1073741826], a) @@ -239,6 +289,10 @@ class TestRange < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_range.rb#L289 (0..10).each {|x| a << x } assert_equal([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], a) + a = [] + (0..).each {|x| a << x; break if a.size == 10 } + assert_equal([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], a) + o1 = Object.new o2 = Object.new def o1.setcmp(v) @cmpresult = v end @@ -285,6 +339,9 @@ class TestRange < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_range.rb#L339 assert_equal(0, (0..1).begin) assert_equal(1, (0..1).end) assert_equal(1, (0...1).end) + assert_equal(0, (0..nil).begin) + assert_equal(nil, (0..nil).end) + assert_equal(nil, (0...nil).end) end def test_first_last @@ -308,6 +365,8 @@ class TestRange < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_range.rb#L365 def test_to_s assert_equal("0..1", (0..1).to_s) assert_equal("0...1", (0...1).to_s) + assert_equal("0..", (0..nil).to_s) + assert_equal("0...", (0...nil).to_s) bug11767 = '[ruby-core:71811] [Bug #11767]' assert_predicate(("0".taint.."1").to_s, :tainted?, bug11767) @@ -318,6 +377,8 @@ class TestRange < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_ (... truncated) -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/