summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authornagachika <nagachika@ruby-lang.org>2023-03-25 10:31:31 +0900
committernagachika <nagachika@ruby-lang.org>2023-03-25 10:31:31 +0900
commit477ab3f6c7f14c79e13f694463aa06a59206dda1 (patch)
treea84fe461ec57055daec684dc319a33493a40b92c
parent02bee9d4d404b5134d19b16bc489c20459cce4ac (diff)
downloadruby-477ab3f6c7f14c79e13f694463aa06a59206dda1.tar.gz
merge revision(s) c5475f42694eff35465c3332e0182c0611ca5918: [Backport #18748]
Fix Range#cover? returning true for beginless ranges of different types Previously `(2..).cover?("2"..)` was false, but `(..2).cover?(.."2")` was true. This changes it so both are false, treating beginless ranges the same as endless ranges in regards to type checks. This also adds documentation to #cover? to describe behavior with beginless and endless ranges, testing each documentation example, which is how this bug was found. Fixes [Bug #18155] --- range.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++++- test/ruby/test_range.rb | 29 ++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-)
-rw-r--r--range.c54
-rw-r--r--test/ruby/test_range.rb29
-rw-r--r--version.h2
3 files changed, 83 insertions, 2 deletions
diff --git a/range.c b/range.c
index faf6ab997b..cf9d5fbccf 100644
--- a/range.c
+++ b/range.c
@@ -1885,6 +1885,49 @@ static int r_cover_range_p(VALUE range, VALUE beg, VALUE end, VALUE val);
* - An internal call to <tt><=></tt> returns +nil+;
* that is, the operands are not comparable.
*
+ * Beginless ranges cover all values of the same type before the end,
+ * excluding the end for exclusive ranges. Beginless ranges cover
+ * ranges that end before the end of the beginless range, or at the
+ * end of the beginless range for inclusive ranges.
+ *
+ * (..2).cover?(1) # => true
+ * (..2).cover?(2) # => true
+ * (..2).cover?(3) # => false
+ * (...2).cover?(2) # => false
+ * (..2).cover?("2") # => false
+ * (..2).cover?(..2) # => true
+ * (..2).cover?(...2) # => true
+ * (..2).cover?(.."2") # => false
+ * (...2).cover?(..2) # => false
+ *
+ * Endless ranges cover all values of the same type after the
+ * beginning. Endless exclusive ranges do not cover endless
+ * inclusive ranges.
+ *
+ * (2..).cover?(1) # => false
+ * (2..).cover?(3) # => true
+ * (2...).cover?(3) # => true
+ * (2..).cover?(2) # => true
+ * (2..).cover?("2") # => false
+ * (2..).cover?(2..) # => true
+ * (2..).cover?(2...) # => true
+ * (2..).cover?("2"..) # => false
+ * (2...).cover?(2..) # => false
+ * (2...).cover?(3...) # => true
+ * (2...).cover?(3..) # => false
+ * (3..).cover?(2..) # => false
+ *
+ * Ranges that are both beginless and endless cover all values and
+ * ranges, and return true for all arguments, with the exception that
+ * beginless and endless exclusive ranges do not cover endless
+ * inclusive ranges.
+ *
+ * (nil...).cover?(Object.new) # => true
+ * (nil...).cover?(nil...) # => true
+ * (nil..).cover?(nil...) # => true
+ * (nil...).cover?(nil..) # => false
+ * (nil...).cover?(1..) # => false
+ *
* Related: Range#include?.
*
*/
@@ -1923,7 +1966,16 @@ r_cover_range_p(VALUE range, VALUE beg, VALUE end, VALUE val)
if (!NIL_P(val_beg) && !NIL_P(val_end) && r_less(val_beg, val_end) > (EXCL(val) ? -1 : 0)) return FALSE;
if (!NIL_P(val_beg) && !r_cover_p(range, beg, end, val_beg)) return FALSE;
- cmp_end = r_less(end, val_end);
+
+ if (!NIL_P(val_end) && !NIL_P(end)) {
+ VALUE r_cmp_end = rb_funcall(end, id_cmp, 1, val_end);
+ if (NIL_P(r_cmp_end)) return FALSE;
+ cmp_end = rb_cmpint(r_cmp_end, end, val_end);
+ }
+ else {
+ cmp_end = r_less(end, val_end);
+ }
+
if (EXCL(range) == EXCL(val)) {
return cmp_end >= 0;
diff --git a/test/ruby/test_range.rb b/test/ruby/test_range.rb
index 8ac1930be6..dc591b0604 100644
--- a/test/ruby/test_range.rb
+++ b/test/ruby/test_range.rb
@@ -666,6 +666,35 @@ class TestRange < Test::Unit::TestCase
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)
+
+ assert_operator(..2, :cover?, 1)
+ assert_operator(..2, :cover?, 2)
+ assert_not_operator(..2, :cover?, 3)
+ assert_not_operator(...2, :cover?, 2)
+ assert_not_operator(..2, :cover?, "2")
+ assert_operator(..2, :cover?, ..2)
+ assert_operator(..2, :cover?, ...2)
+ assert_not_operator(..2, :cover?, .."2")
+ assert_not_operator(...2, :cover?, ..2)
+
+ assert_not_operator(2.., :cover?, 1)
+ assert_operator(2.., :cover?, 2)
+ assert_operator(2..., :cover?, 3)
+ assert_operator(2.., :cover?, 2)
+ assert_not_operator(2.., :cover?, "2")
+ assert_operator(2.., :cover?, 2..)
+ assert_operator(2.., :cover?, 2...)
+ assert_not_operator(2.., :cover?, "2"..)
+ assert_not_operator(2..., :cover?, 2..)
+ assert_operator(2..., :cover?, 3...)
+ assert_not_operator(2..., :cover?, 3..)
+ assert_not_operator(3.., :cover?, 2..)
+
+ assert_operator(nil..., :cover?, Object.new)
+ assert_operator(nil..., :cover?, nil...)
+ assert_operator(nil.., :cover?, nil...)
+ assert_not_operator(nil..., :cover?, nil..)
+ assert_not_operator(nil..., :cover?, 1..)
end
def test_beg_len
diff --git a/version.h b/version.h
index 6aa80e0066..0f28e33d86 100644
--- a/version.h
+++ b/version.h
@@ -11,7 +11,7 @@
# define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR
#define RUBY_VERSION_TEENY 4
#define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR
-#define RUBY_PATCHLEVEL 210
+#define RUBY_PATCHLEVEL 211
#define RUBY_RELEASE_YEAR 2023
#define RUBY_RELEASE_MONTH 3