From 9ca738927293df1c7a2a1ed7e2d6cf89527b5438 Mon Sep 17 00:00:00 2001 From: tarui Date: Wed, 5 Sep 2018 19:06:08 +0000 Subject: [PATCH] range.c: Range#cover? accepts Range object. [Feature #14473] * range.c (range_cover): add code for range argument. If the argument is a Range, check it is or is not covered by the reciver. If it can be treated as a sequence, this method treats it that way. * test/ruby/test_range.rb (class TestRange): add tests for this feature. This patch is written by Owen Stephens. thank you! git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@64640 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- NEWS | 1 + range.c | 61 ++++++++++++++++++++++++++++++++++++++--- test/ruby/test_range.rb | 46 +++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 4 deletions(-) diff --git a/NEWS b/NEWS index ba459f7fe4..99c18de19c 100644 --- a/NEWS +++ b/NEWS @@ -176,6 +176,7 @@ sufficient information, see the ChangeLog file or Redmine * `Range#===` now uses `#cover?` instead of `#include?` method. [Feature #14575] + * `Range#cover?` now accepts Range object. [Feature #14473] * `RubyVM::AST` diff --git a/range.c b/range.c index 0629d2ee38..2763b323eb 100644 --- a/range.c +++ b/range.c @@ -1334,10 +1334,12 @@ range_include_internal(VALUE range, VALUE val) return Qundef; } +static int r_cover_range_p(VALUE range, VALUE beg, VALUE end, VALUE val); /* * call-seq: - * rng.cover?(obj) -> true or false + * rng.cover?(obj) -> true or false + * rng.cover?(range) -> true or false * * Returns true if +obj+ is between the begin and end of * the range. @@ -1345,9 +1347,21 @@ range_include_internal(VALUE range, VALUE val) * This tests begin <= obj <= end when #exclude_end? is +false+ * and begin <= obj < end when #exclude_end? is +true+. * - * ("a".."z").cover?("c") #=> true - * ("a".."z").cover?("5") #=> false - * ("a".."z").cover?("cc") #=> true + * Returns true for a Range when it is covered by the reciver, + * by comparing the begin and end values. If the argument can be treated as + * a sequence, this method treats it that way. In the specific case of + * (a..b).cover?(c...d) with a <= c && b < d, + * end of sequence must be calculated, which may exhibit poor performance if + * c is non-numeric. Returns false if the begin value of the + * Range is larger than the end value. + * + * Return + * ("a".."z").cover?("c") #=> true + * ("a".."z").cover?("5") #=> false + * ("a".."z").cover?("cc") #=> true + * (1..5).cover?(2..3) #=> true + * (1..5).cover?(0..6) #=> false + * (1..5).cover?(1...6) #=> true */ static VALUE @@ -1357,9 +1371,48 @@ range_cover(VALUE range, VALUE val) beg = RANGE_BEG(range); end = RANGE_END(range); + + if (rb_obj_is_kind_of(val, rb_cRange)) { + return RBOOL(r_cover_range_p(range, beg, end, val)); + } return r_cover_p(range, beg, end, val); } +static VALUE +r_call_max(VALUE r) +{ + return rb_funcallv(r, rb_intern("max"), 0, 0); +} + +static int +r_cover_range_p(VALUE range, VALUE beg, VALUE end, VALUE val) +{ + VALUE val_beg, val_end, val_max; + int cmp_end; + + val_beg = RANGE_BEG(val); + val_end = RANGE_END(val); + + if (!NIL_P(end) && NIL_P(val_end)) return FALSE; + if (!NIL_P(val_end) && r_less(val_beg, val_end) > -EXCL(val)) return FALSE; + if (!r_cover_p(range, beg, end, val_beg)) return FALSE; + + cmp_end = r_less(end, val_end); + + if (EXCL(range) == EXCL(val)) { + return cmp_end >= 0; + } else if (EXCL(range)) { + return cmp_end > 0; + } else if (cmp_end >= 0) { + return TRUE; + } + + val_max = rb_rescue2(r_call_max, val, NULL, Qnil, rb_eTypeError, (VALUE)0); + if (val_max == Qnil) return FALSE; + + return r_less(end, val_max) >= 0; +} + static VALUE r_cover_p(VALUE range, VALUE beg, VALUE end, VALUE val) { diff --git a/test/ruby/test_range.rb b/test/ruby/test_range.rb index a845dc4929..c1d9dc1848 100644 --- a/test/ruby/test_range.rb +++ b/test/ruby/test_range.rb @@ -525,6 +525,52 @@ class TestRange < Test::Unit::TestCase assert_not_operator(5..., :cover?, 0) assert_not_operator(5..., :cover?, "a") assert_operator(5.., :cover?, 10) + + assert_operator(2..5, :cover?, 2..5) + assert_operator(2...6, :cover?, 2...6) + assert_operator(2...6, :cover?, 2..5) + assert_operator(2..5, :cover?, 2...6) + assert_operator(2..5, :cover?, 2..4) + assert_operator(2..5, :cover?, 2...4) + assert_operator(2..5, :cover?, 2...5) + assert_operator(2..5, :cover?, 3..5) + assert_operator(2..5, :cover?, 3..4) + assert_operator(2..5, :cover?, 3...6) + assert_operator(2...6, :cover?, 2...5) + assert_operator(2...6, :cover?, 2..5) + assert_operator(2..6, :cover?, 2...6) + assert_operator(2.., :cover?, 2..) + assert_operator(2.., :cover?, 3..) + assert_operator(1.., :cover?, 1..10) + assert_operator(2.0..5.0, :cover?, 2..3) + assert_operator(2..5, :cover?, 2.0..3.0) + assert_operator(2..5, :cover?, 2.0...3.0) + assert_operator(2..5, :cover?, 2.0...5.0) + assert_operator(2.0..5.0, :cover?, 2.0...3.0) + assert_operator(2.0..5.0, :cover?, 2.0...5.0) + assert_operator('aa'..'zz', :cover?, 'aa'...'bb') + + assert_not_operator(2..5, :cover?, 1..5) + assert_not_operator(2...6, :cover?, 1..5) + assert_not_operator(2..5, :cover?, 1...6) + assert_not_operator(1..3, :cover?, 1...6) + assert_not_operator(2..5, :cover?, 2..6) + assert_not_operator(2...6, :cover?, 2..6) + assert_not_operator(2...6, :cover?, 2...7) + assert_not_operator(2..3, :cover?, 1..4) + assert_not_operator(1..2, :cover?, 1.0..3.0) + assert_not_operator(1.0..2.9, :cover?, 1.0..3.0) + assert_not_operator(1..2, :cover?, 4..3) + assert_not_operator(2..1, :cover?, 1..2) + assert_not_operator(1...2, :cover?, 1...3) + assert_not_operator(2.., :cover?, 1..) + assert_not_operator(2.., :cover?, 1..10) + assert_not_operator(1..10, :cover?, 1..) + assert_not_operator(1..5, :cover?, 3..2) + assert_not_operator(1..10, :cover?, 3...2) + assert_not_operator(1..10, :cover?, 3...3) + assert_not_operator('aa'..'zz', :cover?, 'aa'...'zzz') + assert_not_operator(1..10, :cover?, 1...10.1) end def test_beg_len