Bug #19533
closedBehavior of ===/include? on a beginless/endless range (nil..nil) changed in ruby 3.2
Description
Starting from Ruby 2.7.0 a range nil..nil
used to match any value until 3.2.0-preview3, but 3.2.0-rc1 started to reject it.
% docker run -it --rm rubylang/all-ruby ./all-ruby -e 'p( (nil..nil) === 1 )'
(snip)
ruby-2.6.10 false
ruby-2.7.0-preview1 true
...
ruby-3.2.0-preview3 true
ruby-3.2.0-rc1 -e:1:in `===': cannot determine inclusion in beginless/endless ranges (TypeError)
p( (nil..nil) === 1 )
^
from -e:1:in `<main>'
exit 1
...
ruby-3.2.1 -e:1:in `===': cannot determine inclusion in beginless/endless ranges (TypeError)
p( (nil..nil) === 1 )
^
from -e:1:in `<main>'
The previous behavior was so useful because when you have optional lower and upper bounds lbound..rbound
would always work regardless of each end being nil or not.
There is no mention of this in doc/NEWS/NEWS-3.2.0.md, so I'm afraid it was caused unintentionally while fixing other problems.
% docker run -it --rm rubylang/all-ruby ./all-ruby -e 'p( (nil..nil).cover?(1) )'
(snip)
ruby-2.6.10 false
ruby-2.7.0-preview1 true
...
ruby-3.2.1 true
This was pointed out by the following blog article (written in Japanese) as a "pitfall" that you need to work around when upgrading Ruby from 3.0/3.1 to 3.2.
https://blog.studysapuri.jp/entry/2023/03/16/ujihisa-ruby32#endless-range%E3%81%AE%E6%8C%99%E5%8B%95%E3%81%AE%E5%A4%89%E6%9B%B4
Updated by mame (Yusuke Endoh) over 1 year ago
- Related to Bug #18580: Range#include? inconsistency for beginless String ranges added
Updated by mame (Yusuke Endoh) over 1 year ago
In #18580, @matz (Yukihiro Matsumoto) said:
I decided to make
include?
to raise exception for beginless/endless non-numeric ranges.
and (nil..nil)
is considered as a non-numeric range.
Updated by knu (Akinori MUSHA) over 1 year ago
mame (Yusuke Endoh) wrote in #note-2:
In #18580, @matz (Yukihiro Matsumoto) said:
I decided to make
include?
to raise exception for beginless/endless non-numeric ranges.and
(nil..nil)
is considered as a non-numeric range.
Thanks for the info. This hurts not only because the behavior changed without notice (in the NEWS file) but because it broke the useful feature. Before the change, you could just say scores.grep(min..max)
when min
and max
are both optional, but now you need to check if min
and max
are both nil and branch the logic.
While (nil..nil) === value
(used in case
, Enumerable#grep
, any?
, etc.) no longer works, ActiveRecord queries like Product.where(price: min..max)
still work because ===
is not used there. This is a bit confusing and hard to explain why.
I'm not absolutely sure about include?
, but as for ===
I'd like the previous behavior to be restored because it was so useful and aligned with cover?
, and changing back wouldn't break existing code because the current behavior is to raise an error rather than returning false.
Updated by matz (Yukihiro Matsumoto) over 1 year ago
It is an unintentional behavior. I agree to fix nil .. nil
case.
Matz.
Updated by nobu (Nobuyoshi Nakada) over 1 year ago
- Backport changed from 2.7: UNKNOWN, 3.0: UNKNOWN, 3.1: UNKNOWN, 3.2: UNKNOWN to 2.7: DONTNEED, 3.0: DONTNEED, 3.1: DONTNEED, 3.2: REQUIRED
Updated by nobu (Nobuyoshi Nakada) over 1 year ago
- Status changed from Open to Closed
Applied in changeset git|fb17c833f542222afdf482924877d43aa577782d.
[Bug #19533] Fix infinite range inclusion with numeric value
Updated by nagachika (Tomoyuki Chikanaga) over 1 year ago
- Backport changed from 2.7: DONTNEED, 3.0: DONTNEED, 3.1: DONTNEED, 3.2: REQUIRED to 2.7: DONTNEED, 3.0: DONTNEED, 3.1: DONTNEED, 3.2: DONE
ruby_3_2 5328c58c7d00540f4f56749aaeefb68761bd7eba merged revision(s) 1a149aab776aa6741628eb35482eff1ded197fd2,fb17c833f542222afdf482924877d43aa577782d,60f22ebf86248388b41b4ec751d16700f2b4b621.