Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 22 additions & 17 deletions lib/net/imap/sequence_set.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

require "set" unless defined?(::Set)

require_relative "utils"

module Net
class IMAP

Expand Down Expand Up @@ -423,6 +425,8 @@ class IMAP
# representation and returns +self+.
#
class SequenceSet
include Utils # :nodoc:

# The largest possible non-zero unsigned 32-bit integer
UINT32_MAX = 2**32 - 1

Expand Down Expand Up @@ -1019,9 +1023,9 @@ def none_below?(number)
# is empty. (+star+ is ignored when +count+ is given.)
#
# Related: #min, #minmax, #slice
def max(count = nil, star: :*)
if count
count = Integer(count.to_int)
def max(count = (unset_count = true; nil), star: :*)
if !unset_count
count = implicit_int(count)
raise ArgumentError, 'negative count' if count < 0
return remain_frozen_empty if count == 0
if cardinality <= count
Expand All @@ -1046,9 +1050,9 @@ def max(count = nil, star: :*)
# is empty. (+star+ is ignored when +count+ is given.)
#
# Related: #max, #minmax, #slice
def min(count = nil, star: :*)
if count
count = Integer(count.to_int)
def min(count = (unset_count = true; nil), star: :*)
if !unset_count
count = implicit_int(count)
raise ArgumentError, 'negative count' if count < 0
slice(0...count) || remain_frozen_empty
elsif (val = min_num)
Expand Down Expand Up @@ -1360,7 +1364,7 @@ def delete?(element)
#
# Related: #delete, #delete?, #slice!, #subtract, #difference
def delete_at(index)
slice! Integer(index.to_int)
slice! implicit_int index
end

# :call-seq:
Expand All @@ -1376,9 +1380,10 @@ def delete_at(index)
# #string will be regenerated after deletion.
#
# Related: #slice, #delete_at, #delete, #delete?, #subtract, #difference
def slice!(index, length = nil)
def slice!(index, length = (length_unset = true; nil))
modifying! # short-circuit before slice
deleted = slice(index, length) and subtract deleted
deleted = length_unset ? slice(index) : slice(index, length)
subtract deleted if deleted
deleted
end

Expand Down Expand Up @@ -1720,7 +1725,7 @@ def find_ordered_index(number)
#
# Related: #[], #slice, #ordered_at
def at(index)
export_num sorted_set_num_at Integer index.to_int
export_num sorted_set_num_at implicit_int index
end

# :call-seq: ordered_at(index) -> integer or nil
Expand All @@ -1733,7 +1738,7 @@ def at(index)
#
# Related: #[], #slice, #ordered_at
def ordered_at(index)
export_num ordered_list_num_at Integer index.to_int
export_num ordered_list_num_at implicit_int index
end

# :call-seq:
Expand Down Expand Up @@ -1870,8 +1875,8 @@ def ordered_at(index)
# +range.size+ as +length+, when that both sides of the range are either
# negative or non-negative. Note that the minimum +range.size+ is zero,
# so this can't return +nil+ for a negative range length.
def [](index, length = nil)
if length then slice_length(index, length)
def [](index, length = (length_unset = true; nil))
if !length_unset then slice_length(index, length)
elsif index.is_a?(Range) then slice_range(index)
else at(index)
end
Expand Down Expand Up @@ -2418,16 +2423,16 @@ def reverse_each_minmax_with_index(minmaxes)
end

def slice_length(start, length)
start = Integer(start.to_int)
length = Integer(length.to_int)
start = implicit_int(start)
length = implicit_int(length)
return nil if length.negative?
stop = start + length unless start.negative? && start.abs <= length
slice_range(start...stop)
end

def slice_range(range)
first = range.begin || 0
rend = range.end
first = implicit_int(range.begin || 0)
rend = implicit_int(range.end) unless range.end.nil?
excl = range.exclude_end?
last = !(excl && rend == 0) && # (i...0)
(excl && rend&.pred || rend || -1) # (i...j) vs (i..j) vs (i...)
Expand Down
13 changes: 13 additions & 0 deletions lib/net/imap/utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,19 @@ class IMAP < Protocol
module Utils
module_function

# Something like this exists in ruby's C API. Why not in ruby's ruby?
def implicit_int(input)
Integer.try_convert(input) or
raise TypeError, case input
when nil
"no implicit conversion from %p to Integer" % [input]
when true, false
"no implicit conversion of %p into Integer" % [input]
else
"no implicit conversion of %s into Integer" % [input.class.name]
end
end

if defined?(Ractor.shareable_proc)
def shareable
case obj = yield
Expand Down
5 changes: 5 additions & 0 deletions test/lib/helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,11 @@ def assert_pattern
end
end

def assert_implicit_integer_type_error(&)
msg = /\Ano implicit conversion (from|of) \w+ (in)?to Integer\z/
assert_raise_with_message(TypeError, msg, &)
end

def assert_stream_closed_error
assert_local_raise(IOError, /\A(?:stream closed|closed stream)\z/) do
yield
Expand Down
49 changes: 49 additions & 0 deletions test/net/imap/test_sequence_set.rb
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,13 @@ def obj.to_sequence_set; 192_168.001_255 end
assert_nil SequenceSet["101:110,201:210,301:310"][-44]
end

test "#at(invalid), argument type errors" do
assert_implicit_integer_type_error do SequenceSet.full[nil] end
assert_implicit_integer_type_error do SequenceSet.full["1"] end
assert_implicit_integer_type_error do SequenceSet.full.at(nil) end
assert_implicit_integer_type_error do SequenceSet.full.at("1") end
end

test "#ordered_at(non-negative index)" do
assert_nil SequenceSet.empty.ordered_at(0)
assert_equal 1, SequenceSet.full.ordered_at(0)
Expand Down Expand Up @@ -472,6 +479,11 @@ def obj.to_sequence_set; 192_168.001_255 end
assert_equal :*, SequenceSet["1:10,*"].ordered_at(10)
end

test "#ordered_at(invalid), argument type errors" do
assert_implicit_integer_type_error do SequenceSet.full.ordered_at(nil) end
assert_implicit_integer_type_error do SequenceSet.full.ordered_at("1") end
end

test "#[start, length]" do
assert_equal SequenceSet[10..99], SequenceSet.full[9, 90]
assert_equal 90, SequenceSet.full[9, 90].count
Expand All @@ -487,6 +499,16 @@ def obj.to_sequence_set; 192_168.001_255 end
SequenceSet[1...200][100, 10000]
end

test "#[], argument type errors" do
assert_implicit_integer_type_error do SequenceSet.full[nil] end
assert_implicit_integer_type_error do SequenceSet.full["1"] end
assert_implicit_integer_type_error do SequenceSet.full[nil, 1] end
assert_implicit_integer_type_error do SequenceSet.full["1", 1] end
assert_implicit_integer_type_error do SequenceSet.full[1, nil] end
assert_implicit_integer_type_error do SequenceSet.full[1, "1"] end
assert_implicit_integer_type_error do SequenceSet.full["1".."2"] end
end

test "#slice(start, length) -> nil, for negative length" do
set = SequenceSet[1...200]
assert_nil set[ 100, -1]
Expand Down Expand Up @@ -1155,6 +1177,16 @@ def obj.to_sequence_set; 192_168.001_255 end
assert_raise(ArgumentError) { set.max(-1) }
end

test "#min(invalid), argument type errors" do
assert_implicit_integer_type_error do SequenceSet.full.min("1") end
assert_implicit_integer_type_error do SequenceSet.full.min(nil) end
end

test "#max(invalid), argument type errors" do
assert_implicit_integer_type_error do SequenceSet.full.max("1") end
assert_implicit_integer_type_error do SequenceSet.full.max(nil) end
end

test "#minmax" do
assert_equal [ 3, 3], SequenceSet["3"].minmax
assert_equal [ :*, :*], SequenceSet["*"].minmax
Expand Down Expand Up @@ -1217,6 +1249,17 @@ def obj.to_sequence_set; 192_168.001_255 end
assert_equal SequenceSet.empty, set.slice!(3..)
end

test "#slice!, argument type errors" do
set = SequenceSet.new 1..20
assert_implicit_integer_type_error do set.slice!(nil) end
assert_implicit_integer_type_error do set.slice!("1") end
assert_implicit_integer_type_error do set.slice!(nil, 1) end
assert_implicit_integer_type_error do set.slice!("1", 1) end
assert_implicit_integer_type_error do set.slice!(1, nil) end
assert_implicit_integer_type_error do set.slice!(1, "1") end
assert_implicit_integer_type_error do set.slice!("1".."2") end
end

test "#delete_at" do
set = SequenceSet.new [5..10, 20]
assert_nil set.delete_at(20)
Expand All @@ -1229,6 +1272,12 @@ def obj.to_sequence_set; 192_168.001_255 end
assert_equal SequenceSet[5, 7..8], set
end

test "#delete_at, argument type errors" do
set = SequenceSet.new 1..20
assert_implicit_integer_type_error do set.delete_at(nil) end
assert_implicit_integer_type_error do set.delete_at("1") end
end

test "#include_star?" do
assert SequenceSet["2,*:12"].include_star?
assert SequenceSet[-1].include_star?
Expand Down
32 changes: 32 additions & 0 deletions test/net/imap/test_utils.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# frozen_string_literal: true

require "net/imap/utils"
require "test/unit"

class UtilsTest < Net::IMAP::TestCase
include Net::IMAP::Utils

test "#implicit_int" do
assert_equal 0, implicit_int(0)
assert_equal(-123, implicit_int(-123))
assert_equal 4567, implicit_int(4567)
assert_equal 1, implicit_int(1.23)

int = Object.new
def int.to_int = 890
assert_equal 890, implicit_int(890)

assert_implicit_integer_type_error do implicit_int(nil) end
assert_implicit_integer_type_error do implicit_int(true) end
assert_implicit_integer_type_error do implicit_int(false) end
assert_implicit_integer_type_error do implicit_int("123") end
assert_implicit_integer_type_error do implicit_int(:sym) end
assert_implicit_integer_type_error do implicit_int(Class) end
assert_implicit_integer_type_error do implicit_int(Object.new) end

broken = Object.new
def broken.to_int = :wat
assert_raise(TypeError) do implicit_int(broken) end
end

end
Loading