ruby-changes:72074
From: Jeremy <ko1@a...>
Date: Tue, 7 Jun 2022 01:59:31 +0900 (JST)
Subject: [ruby-changes:72074] c5475f4269 (master): Fix Range#cover? returning true for beginless ranges of different types
https://git.ruby-lang.org/ruby.git/commit/?id=c5475f4269 From c5475f42694eff35465c3332e0182c0611ca5918 Mon Sep 17 00:00:00 2001 From: Jeremy Evans <code@j...> Date: Thu, 21 Apr 2022 19:46:17 -0700 Subject: 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(-) diff --git a/range.c b/range.c index 44c43047fd..e45e405f5a 100644 --- a/range.c +++ b/range.c @@ -1888,6 +1888,49 @@ static int r_cover_range_p(VALUE range, VALUE beg, VALUE end, VALUE val); https://github.com/ruby/ruby/blob/trunk/range.c#L1888 * - 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?. * */ @@ -1926,7 +1969,16 @@ r_cover_range_p(VALUE range, VALUE beg, VALUE end, VALUE val) https://github.com/ruby/ruby/blob/trunk/range.c#L1969 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 8789eca749..0df0a985ad 100644 --- a/test/ruby/test_range.rb +++ b/test/ruby/test_range.rb @@ -670,6 +670,35 @@ class TestRange < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_range.rb#L670 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 -- cgit v1.2.1 -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/