Skip to content

Commit 2d4722a

Browse files
author
José Valim
committed
Allow consuming multiple items from suspended enumerable in Stream.transform/3
Closes #5763. Closes #5772. Signed-off-by: José Valim <[email protected]>
1 parent e002ac5 commit 2d4722a

File tree

2 files changed

+27
-17
lines changed

2 files changed

+27
-17
lines changed

lib/elixir/lib/stream.ex

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -789,16 +789,18 @@ defmodule Stream do
789789
do_after(after_fun, user_acc)
790790
:erlang.raise(kind, reason, stacktrace)
791791
else
792-
{:suspended, [val], next} ->
793-
do_transform_user(val, user_acc, user, fun, :cont, next, inner_acc, inner, after_fun)
794-
{_, [val]} ->
795-
do_transform_user(val, user_acc, user, fun, :halt, next, inner_acc, inner, after_fun)
796-
{_, []} ->
797-
do_transform(user_acc, user, fun, :halt, next, inner_acc, inner, after_fun)
792+
{:suspended, vals, next} ->
793+
do_transform_user(:lists.reverse(vals), user_acc, user, fun, :cont, next, inner_acc, inner, after_fun)
794+
{_, vals} ->
795+
do_transform_user(:lists.reverse(vals), user_acc, user, fun, :halt, next, inner_acc, inner, after_fun)
798796
end
799797
end
800798

801-
defp do_transform_user(val, user_acc, user, fun, next_op, next, inner_acc, inner, after_fun) do
799+
defp do_transform_user([], user_acc, user, fun, next_op, next, inner_acc, inner, after_fun) do
800+
do_transform(user_acc, user, fun, next_op, next, inner_acc, inner, after_fun)
801+
end
802+
803+
defp do_transform_user([val | vals], user_acc, user, fun, next_op, next, inner_acc, inner, after_fun) do
802804
user.(val, user_acc)
803805
catch
804806
kind, reason ->
@@ -808,20 +810,20 @@ defmodule Stream do
808810
:erlang.raise(kind, reason, stacktrace)
809811
else
810812
{[], user_acc} ->
811-
do_transform(user_acc, user, fun, next_op, next, inner_acc, inner, after_fun)
813+
do_transform_user(vals, user_acc, user, fun, next_op, next, inner_acc, inner, after_fun)
812814
{list, user_acc} when is_list(list) ->
813-
do_list_transform(user_acc, user, fun, next_op, next, inner_acc, inner,
815+
do_list_transform(vals, user_acc, user, fun, next_op, next, inner_acc, inner,
814816
&Enumerable.List.reduce(list, &1, fun), after_fun)
815817
{:halt, user_acc} ->
816818
next.({:halt, []})
817819
do_after(after_fun, user_acc)
818820
{:halted, elem(inner_acc, 1)}
819821
{other, user_acc} ->
820-
do_enum_transform(user_acc, user, fun, next_op, next, inner_acc, inner,
822+
do_enum_transform(vals, user_acc, user, fun, next_op, next, inner_acc, inner,
821823
&Enumerable.reduce(other, &1, inner), after_fun)
822824
end
823825

824-
defp do_list_transform(user_acc, user, fun, next_op, next, inner_acc, inner, reduce, after_fun) do
826+
defp do_list_transform(vals, user_acc, user, fun, next_op, next, inner_acc, inner, reduce, after_fun) do
825827
try do
826828
reduce.(inner_acc)
827829
catch
@@ -832,17 +834,17 @@ defmodule Stream do
832834
:erlang.raise(kind, reason, stacktrace)
833835
else
834836
{:done, acc} ->
835-
do_transform(user_acc, user, fun, next_op, next, {:cont, acc}, inner, after_fun)
837+
do_transform_user(vals, user_acc, user, fun, next_op, next, {:cont, acc}, inner, after_fun)
836838
{:halted, acc} ->
837839
next.({:halt, []})
838840
do_after(after_fun, user_acc)
839841
{:halted, acc}
840842
{:suspended, acc, c} ->
841-
{:suspended, acc, &do_list_transform(user_acc, user, fun, next_op, next, &1, inner, c, after_fun)}
843+
{:suspended, acc, &do_list_transform(vals, user_acc, user, fun, next_op, next, &1, inner, c, after_fun)}
842844
end
843845
end
844846

845-
defp do_enum_transform(user_acc, user, fun, next_op, next, {op, inner_acc}, inner, reduce, after_fun) do
847+
defp do_enum_transform(vals, user_acc, user, fun, next_op, next, {op, inner_acc}, inner, reduce, after_fun) do
846848
try do
847849
reduce.({op, [:outer | inner_acc]})
848850
catch
@@ -855,15 +857,15 @@ defmodule Stream do
855857
# Only take into account outer halts when the op is not halt itself.
856858
# Otherwise, we were the ones wishing to halt, so we should just stop.
857859
{:halted, [:outer | acc]} when op != :halt ->
858-
do_transform(user_acc, user, fun, next_op, next, {:cont, acc}, inner, after_fun)
860+
do_transform_user(vals, user_acc, user, fun, next_op, next, {:cont, acc}, inner, after_fun)
859861
{:halted, [_ | acc]} ->
860862
next.({:halt, []})
861863
do_after(after_fun, user_acc)
862864
{:halted, acc}
863865
{:done, [_ | acc]} ->
864-
do_transform(user_acc, user, fun, next_op, next, {:cont, acc}, inner, after_fun)
866+
do_transform_user(vals, user_acc, user, fun, next_op, next, {:cont, acc}, inner, after_fun)
865867
{:suspended, [_ | acc], c} ->
866-
{:suspended, acc, &do_enum_transform(user_acc, user, fun, next_op, next, &1, inner, c, after_fun)}
868+
{:suspended, acc, &do_enum_transform(vals, user_acc, user, fun, next_op, next, &1, inner, c, after_fun)}
867869
end
868870
end
869871

lib/elixir/test/elixir/stream_test.exs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,14 @@ defmodule StreamTest do
481481
assert Process.get(:stream_transform)
482482
end
483483

484+
test "transform/3 (via flat_map) handles multiple returns from suspension" do
485+
assert [false]
486+
|> Stream.take(1)
487+
|> Stream.concat([true])
488+
|> Stream.flat_map(&[&1])
489+
|> Enum.to_list() == [false, true]
490+
end
491+
484492
test "iterate/2" do
485493
stream = Stream.iterate(0, &(&1+2))
486494
assert Enum.take(stream, 5) == [0, 2, 4, 6, 8]

0 commit comments

Comments
 (0)