Skip to content

Commit

Permalink
Fixed range argument condition [Feature #14784]
Browse files Browse the repository at this point in the history
Allows a beginless/endless range, and an end-exclusive range
unless the receiver is smaller than its end.
  • Loading branch information
nobu committed Oct 25, 2019
1 parent 8813584 commit 42c652d
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 26 deletions.
56 changes: 37 additions & 19 deletions compar.c
Expand Up @@ -179,53 +179,71 @@ cmp_between(VALUE x, VALUE min, VALUE max)
* obj.clamp(min, max) -> obj
* obj.clamp(range) -> obj
*
* In the first form, returns _min_ if _obj_ <code><=></code> _min_ is
* less than zero, _max_ if _obj_ <code><=></code> _max_ is greater
* than zero and _obj_ otherwise. In the second form, clamps by
* _range.min_ and _range.max_. If _range_ is an exclusive range,
* raises an ArgumentError.
* In <code>(min, max)</code> form, returns _min_ if _obj_
* <code><=></code> _min_ is less than zero, _max_ if _obj_
* <code><=></code> _max_ is greater than zero, and _obj_
* otherwise.
*
* 12.clamp(0, 100) #=> 12
* 523.clamp(0, 100) #=> 100
* -3.123.clamp(0, 100) #=> 0
*
* 'd'.clamp('a', 'f') #=> 'd'
* 'z'.clamp('a', 'f') #=> 'f'
*
* In <code>(range)</code> form, returns _range.begin_ if _obj_
* <code><=></code> _range.begin_ is less than zero, _range.end_
* if _obj_ <code><=></code> _range.end_ is greater than zero, and
* _obj_ otherwise.
*
* 12.clamp(0..100) #=> 12
* 523.clamp(0..100) #=> 100
* -3.123.clamp(0..100) #=> 0
*
* 'd'.clamp('a', 'f') #=> 'd'
* 'z'.clamp('a', 'f') #=> 'f'
* 'd'.clamp('a'..'f') #=> 'd'
* 'z'.clamp('a'..'f') #=> 'f'
*
* 12.clamp(0...100) #=> ArgumentError
* If _range.begin_ is +nil+, it is considered smaller than _obj_,
* and if _range.end_ is +nil+, it is considered greater than
* _obj_.
*
* -20.clamp(0..) #=> 0
* 523.clamp(..100) #=> 100
*
* When _range.end_ is excluded, and _obj_ is greater than or
* equal to _range.end_, an exception is raised.
*
* 100.clamp(0...100) # ArgumentError
*/

static VALUE
cmp_clamp(int argc, VALUE *argv, VALUE x)
{
VALUE min, max;
int c;
int c, excl = 0, allow_nil = 0;

if (rb_scan_args(argc, argv, "11", &min, &max) == 1) {
VALUE range = min;
int excl;
if (!rb_range_values(range, &min, &max, &excl)) {
rb_raise(rb_eTypeError, "wrong argument type %s (expected Range)",
rb_builtin_class_name(range));
}
if (excl || NIL_P(min) || NIL_P(max)) {
rb_raise(rb_eArgError, "cannot clamp with an exclusive range");
}
allow_nil = 1;
}
if (cmpint(min, max) > 0) {
if (!(allow_nil && (NIL_P(min) || NIL_P(max))) && cmpint(min, max) > 0) {
rb_raise(rb_eArgError, "min argument must be smaller than max argument");
}

c = cmpint(x, min);
if (c == 0) return x;
if (c < 0) return min;
c = cmpint(x, max);
if (c > 0) return max;
if (!NIL_P(min)) {
c = cmpint(x, min);
if (c == 0) return x;
if (c < 0) return min;
}
if (!NIL_P(max)) {
c = cmpint(x, max);
if (excl && c >= 0) rb_raise(rb_eArgError, "cannot clamp with an exclusive range");
if (c > 0) return max;
}
return x;
}

Expand Down
21 changes: 14 additions & 7 deletions test/ruby/test_comparable.rb
Expand Up @@ -99,14 +99,21 @@ def test_clamp_with_range
assert_equal(1, @o.clamp(1..1))
assert_equal(@o, @o.clamp(0..0))

assert_equal(1, @o.clamp(1...2))
assert_equal(1, @o.clamp(1..))
assert_equal(1, @o.clamp(1...))
assert_equal(@o, @o.clamp(0...2))
assert_equal(@o, @o.clamp(0..))
assert_equal(@o, @o.clamp(0...))
assert_equal(@o, @o.clamp(..2))
assert_equal(@o, @o.clamp(...2))
assert_equal(-1, @o.clamp(-2..-1))
assert_equal(@o, @o.clamp(-2..0))
assert_equal(@o, @o.clamp(-2..))
assert_equal(@o, @o.clamp(-2...))

assert_raise_with_message(ArgumentError, 'cannot clamp with an exclusive range') {
@o.clamp(1...2)
}
assert_raise_with_message(ArgumentError, 'cannot clamp with an exclusive range') {
@o.clamp(1...)
}
assert_raise_with_message(ArgumentError, 'cannot clamp with an exclusive range') {
@o.clamp(...2)
@o.clamp(-1...0)
}
assert_raise_with_message(ArgumentError, 'min argument must be smaller than max argument') {
@o.clamp(2..1)
Expand Down

0 comments on commit 42c652d

Please sign in to comment.