Skip to content

Commit c3f7e34

Browse files
authored
Raise in Stream.cycle when enumerable reduce call yields no elements (#15344)
1 parent 68c413f commit c3f7e34

2 files changed

Lines changed: 44 additions & 2 deletions

File tree

lib/elixir/lib/stream.ex

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1437,7 +1437,7 @@ defmodule Stream do
14371437
do_cycle(cycle, [], cycle, fun.(element, acc), fun)
14381438

14391439
{_, []} ->
1440-
do_cycle(cycle, [], cycle, {:cont, acc}, fun)
1440+
do_cycle(check_cycle_subsequent_element(cycle), [], cycle, {:cont, acc}, fun)
14411441
end
14421442
end
14431443

@@ -1446,10 +1446,26 @@ defmodule Stream do
14461446
end
14471447

14481448
defp check_cycle_first_element(reduce) do
1449+
check_cycle_non_empty(
1450+
reduce,
1451+
ArgumentError,
1452+
"cannot cycle over an empty enumerable"
1453+
)
1454+
end
1455+
1456+
defp check_cycle_subsequent_element(reduce) do
1457+
check_cycle_non_empty(
1458+
reduce,
1459+
RuntimeError,
1460+
"cycled enumerable became empty after a previous iteration produced elements"
1461+
)
1462+
end
1463+
1464+
defp check_cycle_non_empty(reduce, exception, message) do
14491465
fn acc ->
14501466
case reduce.(acc) do
14511467
{state, []} when state in [:done, :halted] and elem(acc, 0) != :halt ->
1452-
raise ArgumentError, "cannot cycle over an empty enumerable"
1468+
raise exception, message
14531469

14541470
other ->
14551471
other

lib/elixir/test/elixir/stream_test.exs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,10 @@ defmodule StreamTest do
263263
Stream.cycle(%{}) |> Enum.to_list()
264264
end
265265

266+
assert_raise ArgumentError, "cannot cycle over an empty enumerable", fn ->
267+
Stream.cycle(%HaltAcc{acc: []}) |> Enum.to_list()
268+
end
269+
266270
assert Stream.cycle([1, 2, 3]) |> Stream.take(5) |> Enum.to_list() == [1, 2, 3, 1, 2]
267271
assert Enum.take(stream, 5) == [1, 2, 3, 1, 2]
268272
end
@@ -281,6 +285,28 @@ defmodule StreamTest do
281285
[1, 1, 1, 1, 1]
282286
end
283287

288+
test "cycle/1 raises when a subsequent reduce yields no elements" do
289+
Process.put(:cycle_counter, 0)
290+
291+
stream =
292+
Stream.resource(
293+
fn ->
294+
n = Process.get(:cycle_counter)
295+
Process.put(:cycle_counter, n + 1)
296+
n
297+
end,
298+
fn
299+
0 -> {[:a], :done}
300+
_ -> {:halt, :ok}
301+
end,
302+
fn _ -> :ok end
303+
)
304+
305+
assert_raise RuntimeError,
306+
"cycled enumerable became empty after a previous iteration produced elements",
307+
fn -> Stream.cycle(stream) |> Enum.take(3) end
308+
end
309+
284310
test "dedup/1 is lazy" do
285311
assert lazy?(Stream.dedup([1, 2, 3]))
286312
end

0 commit comments

Comments
 (0)