Bug #19864
closedRuby 3.2 Changed Behavior With One Sided Ranges
Description
Thank you for taking the time to read my issue. I know there has been some previous discussion here https://bugs.ruby-lang.org/issues/19533 regarding a similar issue, although I think this is different. I apologize if this has already been addressed.
Prior to Ruby 3.2, you could use a beginless or endless range, and use === (or a case statement) to determine if a given date matched that range. For example, (..today) === yesterday would have returned true, and (tomorrow..) === today would have returned false. Please see my attached file for a more concrete example.
Starting with Ruby 3.2, this results in "`===': cannot determine inclusion in beginless/endless ranges (TypeError)".
I can imagine that there is difficulty and ambiguity with these infinite ranges and non numeric objects, however I do feel that these examples with dates should work, especially since (..Date.today).cover?(Date.today) still works as expected.
Files
Updated by jgomo3 (Jesús Gómez) 12 months ago
- File range_test_2.rb range_test_2.rb added
- File range_test_3.rb range_test_3.rb added
I tested the idea with whole numbers, and it doesn't fail.
I tested the idea with dates not in the extremes, and it DOES fail.
Attached 2 examples.
The first one using numbers:
(..0) === 0
Which works fine both in Ruby 3.1 and 3.2
The second one using different dates. In particular, checking if "yesterday" is in the range of "..today":
require 'date'
today = Date.today
yesterday = today - 1
(..today) === yesterday
And it fails in 3.2, but works fine in 3.1.
Updated by zverok (Victor Shepelev) 12 months ago
Seems to be a bug indeed.
Originates from this commit, which fixed behavior for semi-open Ranges #include?
but also added semi-openness check to range_string_cover_internal
.
Updated by zverok (Victor Shepelev) 12 months ago
- Backport changed from 3.0: UNKNOWN, 3.1: UNKNOWN, 3.2: UNKNOWN to 3.0: DONTNEED, 3.1: DONTNEED, 3.2: REQUIRED
Updated by jeremyevans0 (Jeremy Evans) 12 months ago
This appears to fix it:
diff --git a/range.c b/range.c
index 62e957e622..4b2e2460c7 100644
--- a/range.c
+++ b/range.c
@@ -1818,6 +1818,7 @@ range_string_cover_internal(VALUE range, VALUE val)
return r_cover_p(range, beg, end, val);
}
if (NIL_P(beg)) {
+unbounded_begin:;
VALUE r = rb_funcall(val, id_cmp, 1, end);
if (NIL_P(r)) return Qfalse;
if (RANGE_EXCL(range)) {
@@ -1826,12 +1827,20 @@ range_string_cover_internal(VALUE range, VALUE val)
return RBOOL(rb_cmpint(r, val, end) <= 0);
}
else if (NIL_P(end)) {
+unbounded_end:;
VALUE r = rb_funcall(beg, id_cmp, 1, val);
if (NIL_P(r)) return Qfalse;
return RBOOL(rb_cmpint(r, beg, val) <= 0);
}
}
+ if (!NIL_P(beg) && NIL_P(end)) {
+ goto unbounded_end;
+ }
+ if (NIL_P(beg) && !NIL_P(end)) {
+ goto unbounded_begin;
+ }
+
return range_include_fallback(beg, end, val);
}
I'll try to add tests and submit a pull request within a week.
Updated by jeremyevans0 (Jeremy Evans) 11 months ago
I've submitted a pull request for the diff posted earlier (with tests): https://github.com/ruby/ruby/pull/8458
Updated by jeremyevans (Jeremy Evans) 11 months ago
- Status changed from Open to Closed
Applied in changeset git|25711683e86271385e8abe09a9c03782000e48db.
Fix regression when testing inclusion in unbounded ranges
Caused by 04a92a6764bf678919cf4b68a27496a39d6b886a. This treats
unbounded ranges of arbitrary objects the same as how unbounded
string ranges are treated:
(..x) === y # (y <=> x) <= 0
(...x) === y # (y <=> x) < 0
(x..) === y # (x <=> y) <= 0
Fixes [Bug #19864]
Updated by nagachika (Tomoyuki Chikanaga) 11 months ago
- Backport changed from 3.0: DONTNEED, 3.1: DONTNEED, 3.2: REQUIRED to 3.0: DONTNEED, 3.1: DONTNEED, 3.2: DONE
ruby_3_2 217ef2bf89b3861e83c2e2a3a633c019f0731de6 merged revision(s) 25711683e86271385e8abe09a9c03782000e48db.