Skip to content

Commit c68549d

Browse files
committed
[GR-44710] Fix Range#bsearch validation
PullRequest: truffleruby/3962
2 parents 294a695 + 3d43e58 commit c68549d

File tree

3 files changed

+50
-13
lines changed

3 files changed

+50
-13
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Bug fixes:
1515
* Run context cleanup such as showing the output of tools when `SignalException` and `Interrupt` escape (@eregon).
1616
* Handle a new variable inside the `case` target expression correctly (#3377, @eregon).
1717
* The arguments of `Thread.new(*args, &block)` need to be marked as shared between multiple threads (#3179, @eregon).
18+
* Fix `Range#bsearch` and raise `TypeError` when range boundaries are non-numeric and block not passed (@andrykonchin).
1819

1920
Compatibility:
2021

spec/ruby/core/range/bsearch_spec.rb

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,30 @@
99
it_behaves_like :enumeratorized_with_unknown_size, :bsearch, (1..3)
1010

1111
it "raises a TypeError if the block returns an Object" do
12-
-> { (0..1).bsearch { Object.new } }.should raise_error(TypeError)
12+
-> { (0..1).bsearch { Object.new } }.should raise_error(TypeError, "wrong argument type Object (must be numeric, true, false or nil)")
1313
end
1414

15-
it "raises a TypeError if the block returns a String" do
16-
-> { (0..1).bsearch { "1" } }.should raise_error(TypeError)
15+
it "raises a TypeError if the block returns a String and boundaries are Integer values" do
16+
-> { (0..1).bsearch { "1" } }.should raise_error(TypeError, "wrong argument type String (must be numeric, true, false or nil)")
17+
end
18+
19+
it "raises a TypeError if the block returns a String and boundaries are Float values" do
20+
-> { (0.0..1.0).bsearch { "1" } }.should raise_error(TypeError, "wrong argument type String (must be numeric, true, false or nil)")
1721
end
1822

1923
it "raises a TypeError if the Range has Object values" do
2024
value = mock("range bsearch")
2125
r = Range.new value, value
2226

23-
-> { r.bsearch { true } }.should raise_error(TypeError)
27+
-> { r.bsearch { true } }.should raise_error(TypeError, "can't do binary search for MockObject")
2428
end
2529

2630
it "raises a TypeError if the Range has String values" do
27-
-> { ("a".."e").bsearch { true } }.should raise_error(TypeError)
31+
-> { ("a".."e").bsearch { true } }.should raise_error(TypeError, "can't do binary search for String")
32+
end
33+
34+
it "raises TypeError when non-Numeric begin/end and block not passed" do
35+
-> { ("a".."e").bsearch }.should raise_error(TypeError, "can't do binary search for String")
2836
end
2937

3038
context "with Integer values" do
@@ -94,6 +102,10 @@
94102
(4..2).bsearch { 0 }.should == nil
95103
(4..2).bsearch { -1 }.should == nil
96104
end
105+
106+
it "returns enumerator when block not passed" do
107+
(0...3).bsearch.kind_of?(Enumerator).should == true
108+
end
97109
end
98110

99111
context "with Float values" do
@@ -156,7 +168,6 @@
156168

157169
it "returns nil if the block returns greater than zero for every element" do
158170
(0.3..3.0).bsearch { |x| x <=> -1 }.should be_nil
159-
160171
end
161172

162173
it "returns nil if the block never returns zero" do
@@ -213,6 +224,10 @@
213224
(0...inf).bsearch { |x| x >= Float::MAX ? 0 : 1 }.should == Float::MAX
214225
end
215226
end
227+
228+
it "returns enumerator when block not passed" do
229+
(0.1...2.3).bsearch.kind_of?(Enumerator).should == true
230+
end
216231
end
217232

218233
context "with endless ranges and Integer values" do
@@ -250,6 +265,10 @@
250265
[1, 2, 3].should include(result)
251266
end
252267
end
268+
269+
it "returns enumerator when block not passed" do
270+
eval("(-2..)").bsearch.kind_of?(Enumerator).should == true
271+
end
253272
end
254273

255274
context "with endless ranges and Float values" do
@@ -327,8 +346,11 @@
327346
eval("(0.0...)").bsearch { 0 }.should != inf
328347
end
329348
end
330-
end
331349

350+
it "returns enumerator when block not passed" do
351+
eval("(0.1..)").bsearch.kind_of?(Enumerator).should == true
352+
end
353+
end
332354

333355
context "with beginless ranges and Integer values" do
334356
context "with a block returning true or false" do
@@ -361,6 +383,10 @@
361383
[1, 2, 3].should include(result)
362384
end
363385
end
386+
387+
it "returns enumerator when block not passed" do
388+
(..10).bsearch.kind_of?(Enumerator).should == true
389+
end
364390
end
365391

366392
context "with beginless ranges and Float values" do
@@ -432,5 +458,9 @@
432458
(...inf).bsearch { |x| 3 - x }.should == 3
433459
end
434460
end
461+
462+
it "returns enumerator when block not passed" do
463+
(..-0.1).bsearch.kind_of?(Enumerator).should == true
464+
end
435465
end
436466
end

src/main/ruby/truffleruby/core/range.rb

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,6 @@ def eql?(other)
6666
end
6767

6868
def bsearch(&block)
69-
return to_enum :bsearch unless block_given?
70-
7169
start = self.begin
7270
stop = self.end
7371

@@ -79,12 +77,12 @@ def bsearch(&block)
7977
elsif Primitive.is_a?(stop, Integer)
8078
bsearch_integer(&block)
8179
else
82-
raise TypeError, "bsearch is not available for #{Primitive.class(stop)}"
80+
raise TypeError, "can't do binary search for #{Primitive.class(stop)}"
8381
end
8482
elsif Primitive.nil?(start) && Primitive.is_a?(stop, Integer)
8583
bsearch_beginless(&block)
8684
else
87-
raise TypeError, "bsearch is not available for #{Primitive.class(start)}"
85+
raise TypeError, "can't do binary search for #{Primitive.class(start)}"
8886
end
8987
end
9088

@@ -99,6 +97,8 @@ def bsearch(&block)
9997
end
10098

10199
private def bsearch_float(&block)
100+
return to_enum :bsearch_float unless block_given?
101+
102102
normalized_begin = Primitive.nil?(self.begin) ? -Float::INFINITY : self.begin.to_f
103103
normalized_end = Primitive.nil?(self.end) ? Float::INFINITY : self.end.to_f
104104
normalized_end = normalized_end.prev_float if self.exclude_end?
@@ -165,7 +165,7 @@ def bsearch(&block)
165165
when false, nil
166166
min = mid
167167
else
168-
raise TypeError, 'Range#bsearch block must return Numeric or boolean'
168+
raise TypeError, "wrong argument type #{Primitive.class(result)} (must be numeric, true, false or nil)"
169169
end
170170
end
171171

@@ -187,6 +187,8 @@ def bsearch(&block)
187187
end
188188

189189
private def bsearch_endless(&block)
190+
return to_enum :bsearch_endless unless block_given?
191+
190192
min = self.begin
191193
cur = min
192194
diff = 1
@@ -207,6 +209,8 @@ def bsearch(&block)
207209
end
208210

209211
private def bsearch_beginless(&block)
212+
return to_enum :bsearch_beginless unless block_given?
213+
210214
max = self.end
211215
cur = max
212216
diff = 1
@@ -227,6 +231,8 @@ def bsearch(&block)
227231
end
228232

229233
private def bsearch_integer(&block)
234+
return to_enum :bsearch_integer unless block_given?
235+
230236
min = self.begin
231237
max = self.end
232238
max -= 1 if Primitive.is_a?(max, Integer) and exclude_end?
@@ -255,7 +261,7 @@ def bsearch(&block)
255261
when false, nil
256262
min = mid + 1
257263
else
258-
raise TypeError, 'Range#bsearch block must return Numeric or boolean'
264+
raise TypeError, "wrong argument type #{Primitive.class(result)} (must be numeric, true, false or nil)"
259265
end
260266
end
261267

0 commit comments

Comments
 (0)