Bug #18155


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

Added by urbanautomaton (Simon Coffey) over 1 year ago. Updated 10 months 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) over 1 year 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) over 1 year 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) over 1 year 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.

Updated by sawa (Tsuyoshi Sawada) 10 months ago

I do not understand the logic behind option 2. I would rather expect this logic:

a beginless-and-endless range has no type, so it imposes no restriction on the type

which would lead us to option 1. Its coverage should return true for any object.

To my understanding, the begin value restricts the range by imposing a minimum (type and value), and the end value restricts the range by imposing a maximum (type and value). Lacking one of them means it is restricted only in one end, lacking both of them should mean it is restricted in neither end.

Updated by matz (Yukihiro Matsumoto) 10 months ago

  • Status changed from Open to Closed

We consider nil .. nil as a begin-less end-less range, so that we keep the current (post 2.7) behavior.
Probably we should add description in the document.



Also available in: Atom PDF