Skip to content

Commit 66cf08e

Browse files
author
José Valim
committed
Support bitstrings in Collectable
Closes #7234
1 parent ad62bb9 commit 66cf08e

File tree

3 files changed

+190
-47
lines changed

3 files changed

+190
-47
lines changed

lib/elixir/lib/collectable.ex

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,37 @@ defimpl Collectable, for: List do
9090
end
9191

9292
defimpl Collectable, for: BitString do
93-
def into(original) do
93+
def into(original) when is_binary(original) do
9494
fun = fn
95-
acc, {:cont, x} when is_bitstring(x) -> [acc | x]
96-
acc, :done -> IO.iodata_to_binary(acc)
97-
_, :halt -> :ok
95+
acc, {:cont, x} when is_binary(x) and is_list(acc) ->
96+
[acc | x]
97+
98+
acc, {:cont, x} when is_bitstring(x) and is_bitstring(acc) ->
99+
<<acc::bitstring, x::bitstring>>
100+
101+
acc, {:cont, x} when is_bitstring(x) ->
102+
<<IO.iodata_to_binary(acc)::bitstring, x::bitstring>>
103+
104+
acc, :done ->
105+
IO.iodata_to_binary(acc)
106+
107+
_, :halt ->
108+
:ok
109+
end
110+
111+
{[original], fun}
112+
end
113+
114+
def into(original) when is_bitstring(original) do
115+
fun = fn
116+
acc, {:cont, x} when is_bitstring(x) ->
117+
<<acc::bitstring, x::bitstring>>
118+
119+
acc, :done ->
120+
acc
121+
122+
_, :halt ->
123+
:ok
98124
end
99125

100126
{original, fun}

lib/elixir/src/elixir_erl_for.erl

Lines changed: 62 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,14 @@
33
-include("elixir.hrl").
44

55
translate(Meta, Args, Return, S) ->
6-
{AccName, _, SA} = elixir_erl_var:build('_', S),
7-
{VarName, _, SV} = elixir_erl_var:build('_', SA),
8-
96
Ann = ?ann(Meta),
10-
Acc = {var, Ann, AccName},
11-
Var = {var, Ann, VarName},
12-
137
{Cases, [{do, Expr} | Opts]} = elixir_utils:split_last(Args),
148

159
{TInto, SI} =
1610
case lists:keyfind(into, 1, Opts) of
17-
{into, Into} -> elixir_erl_pass:translate(Into, SV);
18-
false when Return -> {{nil, Ann}, SV};
19-
false -> {false, SV}
11+
{into, Into} -> elixir_erl_pass:translate(Into, S);
12+
false when Return -> {{nil, Ann}, S};
13+
false -> {false, S}
2014
end,
2115

2216
TUniq = lists:keyfind(uniq, 1, Opts) == {uniq, true},
@@ -27,9 +21,9 @@ translate(Meta, Args, Return, S) ->
2721

2822
case comprehension_expr(TInto, TExpr) of
2923
{inline, TIntoExpr} ->
30-
{build_inline(Ann, TCases, TIntoExpr, TInto, TUniq, Var, Acc, SF), SF};
24+
{build_inline(Ann, TCases, TIntoExpr, TInto, TUniq, SF), SF};
3125
{into, TIntoExpr} ->
32-
build_into(Ann, TCases, TIntoExpr, TInto, TUniq, Var, Acc, SF)
26+
build_into(Ann, TCases, TIntoExpr, TInto, TUniq, SF)
3327
end.
3428

3529
%% In case we have no return, we wrap the expression
@@ -95,30 +89,61 @@ collect_filters([], Acc) ->
9589
{Acc, []}.
9690

9791

98-
build_inline(Ann, Clauses, Expr, Into, Uniq, _Var, Acc, S) ->
92+
build_inline(Ann, Clauses, Expr, Into, Uniq, S) ->
9993
case not Uniq and lists:all(fun(Clause) -> element(1, Clause) == bin end, Clauses) of
10094
true -> build_comprehension(Ann, Clauses, Expr, Into);
101-
false -> build_inline(Ann, Clauses, Expr, Into, Uniq, Acc, S)
95+
false -> build_inline_each(Ann, Clauses, Expr, Into, Uniq, S)
10296
end.
10397

104-
build_inline(Ann, Clauses, Expr, false, Uniq, Acc, S) ->
98+
build_inline_each(Ann, Clauses, Expr, false, Uniq, S) ->
10599
InnerFun = fun(InnerExpr, _InnerAcc) -> InnerExpr end,
106-
build_reduce(Ann, Clauses, InnerFun, Expr, {nil, Ann}, Uniq, Acc, S);
107-
build_inline(Ann, Clauses, Expr, {nil, _} = Into, Uniq, Acc, S) ->
100+
build_reduce(Ann, Clauses, InnerFun, Expr, {nil, Ann}, Uniq, S);
101+
build_inline_each(Ann, Clauses, Expr, {nil, _} = Into, Uniq, S) ->
108102
InnerFun = fun(InnerExpr, InnerAcc) -> {cons, Ann, InnerExpr, InnerAcc} end,
109-
ReduceExpr = build_reduce(Ann, Clauses, InnerFun, Expr, Into, Uniq, Acc, S),
103+
ReduceExpr = build_reduce(Ann, Clauses, InnerFun, Expr, Into, Uniq, S),
110104
elixir_erl:remote(Ann, lists, reverse, [ReduceExpr]);
111-
build_inline(Ann, Clauses, Expr, {bin, _, _} = Into, Uniq, Acc, S) ->
112-
InnerFun = fun(InnerExpr, InnerAcc) -> {cons, Ann, InnerAcc, InnerExpr} end,
113-
ReduceExpr = build_reduce(Ann, Clauses, InnerFun, Expr, Into, Uniq, Acc, S),
105+
build_inline_each(Ann, Clauses, Expr, {bin, _, []}, Uniq, S) ->
106+
{InnerValue, SV} = build_var(Ann, S),
107+
108+
InnerFun = fun(InnerExpr, InnerAcc) ->
109+
{'case', Ann, InnerExpr, [
110+
{clause, Ann,
111+
[InnerValue],
112+
[[elixir_erl:remote(Ann, erlang, is_binary, [InnerValue]),
113+
elixir_erl:remote(Ann, erlang, is_list, [InnerAcc])]],
114+
[{cons, Ann, InnerAcc, InnerValue}]},
115+
{clause, Ann,
116+
[InnerValue],
117+
[[elixir_erl:remote(Ann, erlang, is_bitstring, [InnerValue]),
118+
elixir_erl:remote(Ann, erlang, is_bitstring, [InnerAcc])]],
119+
[{bin, Ann, [
120+
{bin_element, Ann, InnerAcc, default, [bitstring]},
121+
{bin_element, Ann, InnerValue, default, [bitstring]}
122+
]}]},
123+
{clause, Ann,
124+
[InnerValue],
125+
[[elixir_erl:remote(Ann, erlang, is_bitstring, [InnerValue])]],
126+
[{bin, Ann, [
127+
{bin_element, Ann, elixir_erl:remote(Ann, erlang, iolist_to_binary, [InnerAcc]), default, [bitstring]},
128+
{bin_element, Ann, InnerValue, default, [bitstring]}
129+
]}]},
130+
{clause, Ann,
131+
[InnerValue],
132+
[],
133+
[elixir_erl:remote(Ann, erlang, error, [{tuple, Ann, [{atom, Ann, badarg}, InnerValue]}])]}
134+
]}
135+
end,
136+
137+
ReduceExpr = build_reduce(Ann, Clauses, InnerFun, Expr, {nil, Ann}, Uniq, SV),
114138
elixir_erl:remote(Ann, erlang, iolist_to_binary, [ReduceExpr]).
115139

116-
build_into(Ann, Clauses, Expr, {map, _, []}, Uniq, _Var, Acc, S) ->
117-
ReduceExpr = build_inline(Ann, Clauses, Expr, {nil, Ann}, Uniq, Acc, S),
140+
build_into(Ann, Clauses, Expr, {map, _, []}, Uniq, S) ->
141+
ReduceExpr = build_inline_each(Ann, Clauses, Expr, {nil, Ann}, Uniq, S),
118142
{elixir_erl:remote(Ann, maps, from_list, [ReduceExpr]), S};
119-
120-
build_into(Ann, Clauses, Expr, Into, Uniq, Fun, Acc, S) ->
121-
{Kind, SK} = build_var(Ann, S),
143+
build_into(Ann, Clauses, Expr, Into, Uniq, S) ->
144+
{Fun, SF} = build_var(Ann, S),
145+
{Acc, SA} = build_var(Ann, SF),
146+
{Kind, SK} = build_var(Ann, SA),
122147
{Reason, SR} = build_var(Ann, SK),
123148
{Stack, ST} = build_var(Ann, SR),
124149
{Done, SD} = build_var(Ann, ST),
@@ -132,7 +157,7 @@ build_into(Ann, Clauses, Expr, Into, Uniq, Fun, Acc, S) ->
132157
elixir_erl:remote(Ann, 'Elixir.Collectable', into, [Into])
133158
},
134159

135-
IntoReduceExpr = build_reduce(Ann, Clauses, InnerFun, Expr, Acc, Uniq, Acc, SD),
160+
IntoReduceExpr = build_reduce(Ann, Clauses, InnerFun, Expr, Acc, Uniq, SD),
136161

137162
TryExpr =
138163
{'try', Ann,
@@ -153,12 +178,14 @@ build_into(Ann, Clauses, Expr, Into, Uniq, Fun, Acc, S) ->
153178

154179
%% Helpers
155180

156-
build_reduce(_Ann, Clauses, InnerFun, Expr, Into, false, Acc, S) ->
157-
build_reduce_each(Clauses, InnerFun(Expr, Acc), Into, Acc, S);
158-
build_reduce(Ann, Clauses, InnerFun, Expr, Into, true, Acc, S) ->
181+
build_reduce(Ann, Clauses, InnerFun, Expr, Into, false, S) ->
182+
{Acc, SA} = build_var(Ann, S),
183+
build_reduce_each(Clauses, InnerFun(Expr, Acc), Into, Acc, SA);
184+
build_reduce(Ann, Clauses, InnerFun, Expr, Into, true, S) ->
159185
%% Those variables are used only inside the anonymous function
160186
%% so we don't need to worry about returning the scope.
161-
{Value, SV} = build_var(Ann, S),
187+
{Acc, SA} = build_var(Ann, S),
188+
{Value, SV} = build_var(Ann, SA),
162189
{IntoAcc, SI} = build_var(Ann, SV),
163190
{UniqAcc, SU} = build_var(Ann, SI),
164191

@@ -302,12 +329,12 @@ join_filter(Ann, {nil, Filter}, True, False) ->
302329
{clause, Ann, [{atom, Ann, false}], [], [False]}
303330
]};
304331
join_filter(Ann, {Var, Filter}, True, False) ->
305-
Guard =
306-
{op, Ann, 'orelse',
307-
{op, Ann, '==', Var, {atom, Ann, false}},
308-
{op, Ann, '==', Var, {atom, Ann, nil}}},
332+
Guards = [
333+
[{op, Ann, '==', Var, {atom, Ann, false}}],
334+
[{op, Ann, '==', Var, {atom, Ann, nil}}]
335+
],
309336

310337
{'case', Ann, Filter, [
311-
{clause, Ann, [Var], [[Guard]], [False]},
338+
{clause, Ann, [Var], Guards, [False]},
312339
{clause, Ann, [{var, Ann, '_'}], [], [True]}
313340
]}.

lib/elixir/test/elixir/kernel/comprehension_test.exs

Lines changed: 98 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,38 @@ defmodule Kernel.ComprehensionTest do
143143
end
144144

145145
test "for comprehensions into binary" do
146-
enum = 1..3
147-
assert for(x <- enum, into: "", do: to_bin(x * 2)) == <<2, 4, 6>>
146+
enum = 0..3
147+
148+
assert (for x <- enum, into: "" do
149+
to_bin(x * 2)
150+
end) == <<0, 2, 4, 6>>
151+
152+
assert (for x <- enum, into: "" do
153+
if Integer.is_even(x), do: <<x::size(2)>>, else: <<x::size(1)>>
154+
end) == <<0::size(2), 1::size(1), 2::size(2), 3::size(1)>>
155+
end
156+
157+
test "for comprehensions into dynamic binary" do
158+
enum = 0..3
159+
into = ""
160+
161+
assert (for x <- enum, into: into do
162+
to_bin(x * 2)
163+
end) == <<0, 2, 4, 6>>
164+
165+
assert (for x <- enum, into: into do
166+
if Integer.is_even(x), do: <<x::size(2)>>, else: <<x::size(1)>>
167+
end) == <<0::size(2), 1::size(1), 2::size(2), 3::size(1)>>
168+
169+
into = <<7::size(1)>>
170+
171+
assert (for x <- enum, into: into do
172+
to_bin(x * 2)
173+
end) == <<7::size(1), 0, 2, 4, 6>>
174+
175+
assert (for x <- enum, into: into do
176+
if Integer.is_even(x), do: <<x::size(2)>>, else: <<x::size(1)>>
177+
end) == <<7::size(1), 0::size(2), 1::size(1), 2::size(2), 3::size(1)>>
148178
end
149179

150180
test "for comprehensions where value is not used" do
@@ -238,9 +268,39 @@ defmodule Kernel.ComprehensionTest do
238268
assert for(x <- enum, into: [], do: x * 2) == [2, 4, 6]
239269
end
240270

241-
test "list for comprehensions into binaries" do
242-
enum = [1, 2, 3]
243-
assert for(x <- enum, into: "", do: to_bin(x * 2)) == <<2, 4, 6>>
271+
test "list for comprehensions into binary" do
272+
enum = [0, 1, 2, 3]
273+
274+
assert (for x <- enum, into: "" do
275+
to_bin(x * 2)
276+
end) == <<0, 2, 4, 6>>
277+
278+
assert (for x <- enum, into: "" do
279+
if Integer.is_even(x), do: <<x::size(2)>>, else: <<x::size(1)>>
280+
end) == <<0::size(2), 1::size(1), 2::size(2), 3::size(1)>>
281+
end
282+
283+
test "list for comprehensions into dynamic binary" do
284+
enum = [0, 1, 2, 3]
285+
into = ""
286+
287+
assert (for x <- enum, into: into do
288+
to_bin(x * 2)
289+
end) == <<0, 2, 4, 6>>
290+
291+
assert (for x <- enum, into: into do
292+
if Integer.is_even(x), do: <<x::size(2)>>, else: <<x::size(1)>>
293+
end) == <<0::size(2), 1::size(1), 2::size(2), 3::size(1)>>
294+
295+
into = <<7::size(1)>>
296+
297+
assert (for x <- enum, into: into do
298+
to_bin(x * 2)
299+
end) == <<7::size(1), 0, 2, 4, 6>>
300+
301+
assert (for x <- enum, into: into do
302+
if Integer.is_even(x), do: <<x::size(2)>>, else: <<x::size(1)>>
303+
end) == <<7::size(1), 0::size(2), 1::size(1), 2::size(2), 3::size(1)>>
244304
end
245305

246306
test "map for comprehensions into map" do
@@ -278,9 +338,39 @@ defmodule Kernel.ComprehensionTest do
278338
assert for(<<x <- bin>>, into: [], do: x * 2) == [2, 4, 6]
279339
end
280340

281-
test "binary for comprehensions into binaries" do
282-
bin = <<1, 2, 3>>
283-
assert for(<<x <- bin>>, into: "", do: to_bin(x * 2)) == <<2, 4, 6>>
341+
test "binary for comprehensions into binary" do
342+
bin = <<0, 1, 2, 3>>
343+
344+
assert (for <<x <- bin>>, into: "" do
345+
to_bin(x * 2)
346+
end) == <<0, 2, 4, 6>>
347+
348+
assert (for <<x <- bin>>, into: "" do
349+
if Integer.is_even(x), do: <<x::size(2)>>, else: <<x::size(1)>>
350+
end) == <<0::size(2), 1::size(1), 2::size(2), 3::size(1)>>
351+
end
352+
353+
test "binary for comprehensions into dynamic binary" do
354+
bin = <<0, 1, 2, 3>>
355+
into = ""
356+
357+
assert (for <<x <- bin>>, into: into do
358+
to_bin(x * 2)
359+
end) == <<0, 2, 4, 6>>
360+
361+
assert (for <<x <- bin>>, into: into do
362+
if Integer.is_even(x), do: <<x::size(2)>>, else: <<x::size(1)>>
363+
end) == <<0::size(2), 1::size(1), 2::size(2), 3::size(1)>>
364+
365+
into = <<7::size(1)>>
366+
367+
assert (for <<x <- bin>>, into: into do
368+
to_bin(x * 2)
369+
end) == <<7::size(1), 0, 2, 4, 6>>
370+
371+
assert (for <<x <- bin>>, into: into do
372+
if Integer.is_even(x), do: <<x::size(2)>>, else: <<x::size(1)>>
373+
end) == <<7::size(1), 0::size(2), 1::size(1), 2::size(2), 3::size(1)>>
284374
end
285375

286376
test "binary for comprehensions with variable size" do

0 commit comments

Comments
 (0)