Why doesn't `Range#cover?` raise an exception when comparison fails?

74 views Asked by At

Given that Time objects cannot be compared with Fixnum without explicit casting:

0 <= Time.now # => ArgumentError: comparison of Fixnum with Time failed
Time.now <= 10000000000 # => ArgumentError: comparison of Time with 10000000000 failed

and what the documentation for Range#cover? says,

cover?(obj)true or false

Returns true if obj is between the begin and end of the range.

This tests begin <= obj <= end when exclude_end? is false and begin <= obj < end when exclude_end? is true.

I expect:

(0...10000000000).cover?(Time.now) # => false

to raise an exception rather than silently return false. Why doesn't it raise an exception?

It is understandable that, with explicit casting, the comparison works:

(0...10000000000).cover?(Time.now.to_i) # => true
2

There are 2 answers

4
Sergio Tulentsev On BEST ANSWER

The doc doesn't mention an implementation detail. range_cover is implemented in terms of r_less (via r_cover_p). And r_less comment says:

/* compares _a_ and _b_ and returns:
 * < 0: a < b
 * = 0: a = b
 * > 0: a > b or non-comparable
 */

Here is the source of r_cover_p:

static VALUE
r_cover_p(VALUE range, VALUE beg, VALUE end, VALUE val)
{
  if (r_less(beg, val) <= 0) {
    int excl = EXCL(range);
    if (r_less(val, end) <= -excl)
      return Qtrue;
  }
  return Qfalse;
}

As we can see, a positive number returned from either of r_less invocations will result in a Qfalse.

Now, the reason why the doc doesn't mention it, I think, is to keep it light. Normally (99.9999% of cases), you're supposed to compare comparable things, right? And in the odd case you don't, you still get a correct answer ("this Time does not belong to this range of integers").

3
hirolau On

I am pretty sure both .include? and .cover? uses the case quality operator (===) so the value you get is the same as:

p Time.now === 1000 #=> false