Skip to content

Commit d7709a5

Browse files
committed
Allow slice to overflow on both starting and ending positions
Prior to this patch, ending positions could overflow but not starting ones.
1 parent 91fa0e3 commit d7709a5

File tree

6 files changed

+92
-66
lines changed

6 files changed

+92
-66
lines changed

lib/elixir/lib/enum.ex

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ defmodule Enum do
465465
"""
466466
@spec at(t, index, default) :: element | default
467467
def at(enumerable, index, default \\ nil) when is_integer(index) do
468-
case slice_any(enumerable, index, 1, 1) do
468+
case slice_forward(enumerable, index, 1, 1) do
469469
[value] -> value
470470
[] -> default
471471
end
@@ -1034,7 +1034,7 @@ defmodule Enum do
10341034
"""
10351035
@spec fetch(t, index) :: {:ok, element} | :error
10361036
def fetch(enumerable, index) when is_integer(index) do
1037-
case slice_any(enumerable, index, 1, 1) do
1037+
case slice_forward(enumerable, index, 1, 1) do
10381038
[value] -> {:ok, value}
10391039
[] -> :error
10401040
end
@@ -1060,7 +1060,7 @@ defmodule Enum do
10601060
"""
10611061
@spec fetch!(t, index) :: element
10621062
def fetch!(enumerable, index) when is_integer(index) do
1063-
case slice_any(enumerable, index, 1, 1) do
1063+
case slice_forward(enumerable, index, 1, 1) do
10641064
[value] -> value
10651065
[] -> raise Enum.OutOfBoundsError
10661066
end
@@ -2657,14 +2657,13 @@ defmodule Enum do
26572657
def slide(enumerable, first..last, insertion_index)
26582658
when first < 0 or last < 0 or insertion_index < 0 do
26592659
count = Enum.count(enumerable)
2660-
normalized_first = if first >= 0, do: first, else: first + count
2660+
normalized_first = if first >= 0, do: first, else: Kernel.max(first + count, 0)
26612661
normalized_last = if last >= 0, do: last, else: last + count
26622662

26632663
normalized_insertion_index =
26642664
if insertion_index >= 0, do: insertion_index, else: insertion_index + count
26652665

2666-
if normalized_first >= 0 and normalized_first < count and
2667-
normalized_first != normalized_insertion_index do
2666+
if normalized_first < count and normalized_first != normalized_insertion_index do
26682667
normalized_range = normalized_first..normalized_last//1
26692668
slide(enumerable, normalized_range, normalized_insertion_index)
26702669
else
@@ -2678,7 +2677,8 @@ defmodule Enum do
26782677

26792678
def slide(_, first..last, insertion_index)
26802679
when insertion_index > first and insertion_index <= last do
2681-
raise "Insertion index for slide must be outside the range being moved " <>
2680+
raise ArgumentError,
2681+
"insertion index for slide must be outside the range being moved " <>
26822682
"(tried to insert #{first}..#{last} at #{insertion_index})"
26832683
end
26842684

@@ -2876,7 +2876,9 @@ defmodule Enum do
28762876
iex> Enum.slice([1, 2, 3, 4, 5], 0..-1//2)
28772877
[1, 3, 5]
28782878
2879-
If values are out of bounds, it returns an empty list:
2879+
If the first position is after the end of the enumerable
2880+
or after the last position of the range, it returns an
2881+
empty list:
28802882
28812883
iex> Enum.slice([1, 2, 3, 4, 5], 6..10)
28822884
[]
@@ -2924,16 +2926,16 @@ defmodule Enum do
29242926

29252927
defp slice_range(enumerable, first, last, step)
29262928
when last >= first and last >= 0 and first >= 0 do
2927-
slice_any(enumerable, first, last - first + 1, step)
2929+
slice_forward(enumerable, first, last - first + 1, step)
29282930
end
29292931

29302932
defp slice_range(enumerable, first, last, step) do
29312933
{count, fun} = slice_count_and_fun(enumerable, step)
2932-
first = if first >= 0, do: first, else: first + count
2934+
first = if first >= 0, do: first, else: Kernel.max(first + count, 0)
29332935
last = if last >= 0, do: last, else: last + count
29342936
amount = last - first + 1
29352937

2936-
if first >= 0 and first < count and amount > 0 do
2938+
if first < count and amount > 0 do
29372939
amount = Kernel.min(amount, count - first)
29382940
amount = if step == 1, do: amount, else: div(amount - 1, step) + 1
29392941
fun.(first, amount, step)
@@ -2971,22 +2973,33 @@ defmodule Enum do
29712973
# using a negative start index
29722974
iex> Enum.slice(1..10, -6, 3)
29732975
[5, 6, 7]
2976+
iex> Enum.slice(1..10, -11, 5)
2977+
[1, 2, 3, 4, 5]
29742978
2975-
# out of bound start index (positive)
2979+
# out of bound start index
29762980
iex> Enum.slice(1..10, 10, 5)
29772981
[]
29782982
2979-
# out of bound start index (negative)
2980-
iex> Enum.slice(1..10, -11, 5)
2981-
[]
2982-
29832983
"""
29842984
@spec slice(t, index, non_neg_integer) :: list
29852985
def slice(_enumerable, start_index, 0) when is_integer(start_index), do: []
29862986

2987+
def slice(enumerable, start_index, amount)
2988+
when is_integer(start_index) and start_index < 0 and is_integer(amount) and amount >= 0 do
2989+
{count, fun} = slice_count_and_fun(enumerable, 1)
2990+
start_index = Kernel.max(count + start_index, 0)
2991+
amount = Kernel.min(amount, count - start_index)
2992+
2993+
if amount > 0 do
2994+
fun.(start_index, amount, 1)
2995+
else
2996+
[]
2997+
end
2998+
end
2999+
29873000
def slice(enumerable, start_index, amount)
29883001
when is_integer(start_index) and is_integer(amount) and amount >= 0 do
2989-
slice_any(enumerable, start_index, amount, 1)
3002+
slice_forward(enumerable, start_index, amount, 1)
29903003
end
29913004

29923005
@doc """
@@ -4308,7 +4321,7 @@ defmodule Enum do
43084321

43094322
## slice
43104323

4311-
defp slice_any(enumerable, start, amount, step) when start < 0 do
4324+
defp slice_forward(enumerable, start, amount, step) when start < 0 do
43124325
{count, fun} = slice_count_and_fun(enumerable, step)
43134326
start = count + start
43144327

@@ -4321,11 +4334,11 @@ defmodule Enum do
43214334
end
43224335
end
43234336

4324-
defp slice_any(list, start, amount, step) when is_list(list) do
4337+
defp slice_forward(list, start, amount, step) when is_list(list) do
43254338
slice_list(list, start, amount, step)
43264339
end
43274340

4328-
defp slice_any(enumerable, start, amount, step) do
4341+
defp slice_forward(enumerable, start, amount, step) do
43294342
case Enumerable.slice(enumerable) do
43304343
{:ok, count, _} when start >= count ->
43314344
[]

lib/elixir/lib/kernel.ex

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4509,6 +4509,14 @@ defmodule Kernel do
45094509
iex> binary_slice("elixir", 0, 10)
45104510
"elixir"
45114511
4512+
If `start` is negative, it is normalized against the binary
4513+
size and clamped to 0:
4514+
4515+
iex> binary_slice("elixir", -3, 10)
4516+
"xir"
4517+
iex> binary_slice("elixir", -10, 10)
4518+
"elixir"
4519+
45124520
If the `size` is zero, an empty binary is returned:
45134521
45144522
iex> binary_slice("elixir", 1, 0)
@@ -4517,17 +4525,15 @@ defmodule Kernel do
45174525
If `start` is greater than or equal to the binary size,
45184526
an empty binary is returned:
45194527
4520-
iex> binary_slice("elixir", 3, 10)
4521-
"xir"
45224528
iex> binary_slice("elixir", 10, 10)
45234529
""
45244530
45254531
"""
45264532
@doc since: "1.14.0"
45274533
def binary_slice(binary, start, size)
4528-
when is_binary(binary) and is_integer(start) and is_integer(size) and
4529-
start >= 0 and size >= 0 do
4534+
when is_binary(binary) and is_integer(start) and is_integer(size) and size >= 0 do
45304535
total = byte_size(binary)
4536+
start = if start < 0, do: max(total + start, 0), else: start
45314537

45324538
case start < total do
45334539
true -> :erlang.binary_part(binary, start, min(size, total - start))
@@ -4562,6 +4568,8 @@ defmodule Kernel do
45624568
"ixir"
45634569
iex> binary_slice("elixir", -4..6)
45644570
"ixir"
4571+
iex> binary_slice("elixir", -10..10)
4572+
"elixir"
45654573
45664574
For ranges where `start > stop`, you need to explicitly
45674575
mark them as increasing:
@@ -4583,7 +4591,8 @@ defmodule Kernel do
45834591
iex> binary_slice("elixir", 0..-1//2)
45844592
"eii"
45854593
4586-
If values are out of bounds, it returns an empty binary:
4594+
If the first position is after the string ends or after
4595+
the last position of the range, it returns an empty string:
45874596
45884597
iex> binary_slice("elixir", 10..3//1)
45894598
""
@@ -4600,7 +4609,7 @@ defmodule Kernel do
46004609

46014610
first =
46024611
case first < 0 do
4603-
true -> first + total
4612+
true -> max(first + total, 0)
46044613
false -> first
46054614
end
46064615

@@ -4610,7 +4619,7 @@ defmodule Kernel do
46104619
false -> last
46114620
end
46124621

4613-
case first >= 0 and first < total do
4622+
case first < total do
46144623
true ->
46154624
part = binary_part(binary, first, min(total - first, last - first + 1))
46164625

lib/elixir/lib/string.ex

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2086,19 +2086,19 @@ defmodule String do
20862086
iex> String.slice("elixir", 10, 3)
20872087
""
20882088
2089+
If the start position is negative, it is normalized
2090+
against the string length and clamped to 0:
2091+
20892092
iex> String.slice("elixir", -4, 4)
20902093
"ixir"
20912094
20922095
iex> String.slice("elixir", -10, 3)
2093-
""
2096+
"eli"
20942097
2095-
iex> String.slice("a", 0, 1500)
2096-
"a"
2098+
If start is more than the string length, an empty
2099+
string is returned:
20972100
2098-
iex> String.slice("a", 1, 1500)
2099-
""
2100-
2101-
iex> String.slice("a", 2, 1500)
2101+
iex> String.slice("elixir", 10, 1500)
21022102
""
21032103
21042104
"""
@@ -2117,12 +2117,8 @@ defmodule String do
21172117
def slice(string, start, length)
21182118
when is_binary(string) and is_integer(start) and is_integer(length) and start < 0 and
21192119
length >= 0 do
2120-
start = length(string) + start
2121-
2122-
case start >= 0 do
2123-
true -> do_slice(string, start, length)
2124-
false -> ""
2125-
end
2120+
start = max(length(string) + start, 0)
2121+
do_slice(string, start, length)
21262122
end
21272123

21282124
defp do_slice(string, start, length) do
@@ -2160,6 +2156,8 @@ defmodule String do
21602156
"ixir"
21612157
iex> String.slice("elixir", -4..6)
21622158
"ixir"
2159+
iex> String.slice("elixir", -100..100)
2160+
"elixir"
21632161
21642162
For ranges where `start > stop`, you need to explicitly
21652163
mark them as increasing:
@@ -2181,12 +2179,11 @@ defmodule String do
21812179
iex> String.slice("elixir", 0..-1//2)
21822180
"eii"
21832181
2184-
If values are out of bounds, it returns an empty string:
2182+
If the first position is after the string ends or after
2183+
the last position of the range, it returns an empty string:
21852184
21862185
iex> String.slice("elixir", 10..3)
21872186
""
2188-
iex> String.slice("elixir", -10..-7)
2189-
""
21902187
iex> String.slice("a", 1..1500)
21912188
""
21922189
@@ -2250,10 +2247,10 @@ defmodule String do
22502247

22512248
defp slice_range_negative(string, first, last) do
22522249
{reversed_bytes, length} = acc_bytes(string, [], 0)
2253-
first = add_if_negative(first, length)
2250+
first = add_if_negative(first, length) |> max(0)
22542251
last = add_if_negative(last, length)
22552252

2256-
if first < 0 or first > last or first > length do
2253+
if first > last or first > length do
22572254
""
22582255
else
22592256
last = min(last + 1, length)

lib/elixir/test/elixir/calendar/date_range_test.exs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,9 @@ defmodule Date.RangeTest do
7272
end
7373

7474
test "for empty range" do
75+
assert Enum.slice(@empty_range, 1, 3) == []
7576
assert Enum.slice(@empty_range, 3, 3) == []
77+
assert Enum.slice(@empty_range, -1, 3) == []
7678
assert Enum.slice(@empty_range, -3, 3) == []
7779
end
7880
end

0 commit comments

Comments
 (0)