Bug #18155


(nil..nil).cover?(x) is true for all x since beginless ranges were introduced

Added by urbanautomaton (Simon Coffey) about 2 months ago. Updated 13 days ago.

Target version:
ruby -v:
2.7.0 and greater



The introduction of beginless ranges in #14799 changed the behaviour of Range#cover? when both the beginning and the end of a Range are nil. Previously such a range would cover no values; now it covers all values:

irb-2.6.6> (nil..nil).cover?("23 dogs")
=> false

irb-2.7.0> (nil..nil).cover?("23 dogs")
=> true

This change was noted at the time, but it was believed that the call would have previously raised an error instead of returning false. I've not exhaustively checked, but the old behaviour goes back to at least ruby 2.2.


I'm not sure that either of these results are "correct", since I'm not sure that the concept of a range with no beginning or end is meaningful. Without either parameter, it seems impossible to infer the values it ranges over, since we have no type information available.

However, I did find both the current result and the change in behaviour quite surprising, so I've opened this issue.

Some options I considered:

  1. Do nothing: a beginless-and-endless range is unbounded in both value and type, so covers all values of all types
  2. Reinstate the old behaviour: a beginless-and-endless range has no type, so covers no values
  3. Raise an error: a beginless-and-endless range is meaningless, so constructing one should raise an error

My personal preference is option 3, but I can clearly see that it represents the most effort. I'd welcome people's thoughts; if option 1 is preferred, I'd be happy to contribute a documentation patch to make the new behaviour clear.


range-18155.patch (1.08 KB) range-18155.patch eightbitraptor (Matthew Valentine-House), 10/13/2021 06:40 AM

Updated by urbanautomaton (Simon Coffey) about 2 months ago

I should add: if we don't want to raise an error, then out of options 1 and 2 my preference would be for 2, as saying a beginless-and-endless range has no type seems more persuasive to me.

Updated by jeremyevans0 (Jeremy Evans) about 2 months ago

I'm definitely against option 3. It would break backwards compatibility for code that expects Ruby 2.7 behavior:

start = params['start']
finish = params['finish']
(start..finish).cover? obj.modified_date

There are libraries that at least have tests for ranges unbounded in both directions (Sequel does, I would guess Rails does as well), and those would break if option 3 was taken.

I'm fine with the current behavior, and don't consider it a bug. I'm not strongly against option 2, though.

Updated by eightbitraptor (Matthew Valentine-House) 13 days ago

For interests sake, I've put together a short patch that implements option 2: Reinstating the old behaviour of returning false only when the range has nil at the beginning and the end. All other behaviour should remain the same.


Also available in: Atom PDF