Skip to content

Commit b098c04

Browse files
committed
Fix bin comprehensions when option is used (#15473)
* Fix bin comprehensions - static size * Fix bin comprehensions - pinned var size * Fix bin comprehensions - operations * Add tests for nested generators
1 parent 1ba39f3 commit b098c04

2 files changed

Lines changed: 77 additions & 38 deletions

File tree

lib/elixir/src/elixir_erl_for.erl

Lines changed: 50 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,15 @@
1414
translate(Meta, Args, S) ->
1515
{Cases, [{do, Expr} | Opts]} = elixir_utils:split_last(Args),
1616

17+
% needs to be the original variables, excluding variables from generators
18+
InitVars = S#elixir_erl.var_names,
19+
1720
case lists:keyfind(reduce, 1, Opts) of
18-
{reduce, Reduce} -> translate_reduce(Meta, Cases, Expr, Reduce, S);
19-
false -> translate_into(Meta, Cases, Expr, Opts, S)
21+
{reduce, Reduce} -> translate_reduce(Meta, Cases, Expr, Reduce, InitVars, S);
22+
false -> translate_into(Meta, Cases, Expr, Opts, InitVars, S)
2023
end.
2124

22-
translate_reduce(Meta, Cases, Expr, Reduce, S) ->
25+
translate_reduce(Meta, Cases, Expr, Reduce, InitVars, S) ->
2326
Ann = ?ann(Meta),
2427
{TReduce, SR} = elixir_erl_pass:translate(Reduce, Ann, S),
2528
{TCases, SC} = translate_gen(Meta, Cases, [], SR),
@@ -30,9 +33,9 @@ translate_reduce(Meta, Cases, Expr, Reduce, S) ->
3033
({'case', CaseAnn, _, CaseBlock}, InnerAcc) -> {'case', CaseAnn, InnerAcc, CaseBlock}
3134
end,
3235

33-
build_reduce(Ann, TCases, InnerFun, TExpr, TReduce, false, SE).
36+
build_reduce(Ann, TCases, InnerFun, TExpr, TReduce, false, InitVars, SE).
3437

35-
translate_into(Meta, Cases, Expr, Opts, S) ->
38+
translate_into(Meta, Cases, Expr, Opts, InitVars, S) ->
3639
Ann = ?ann(Meta),
3740

3841
{TInto, SI} =
@@ -47,8 +50,8 @@ translate_into(Meta, Cases, Expr, Opts, S) ->
4750
{TExpr, SE} = elixir_erl_pass:translate(wrap_expr_if_unused(Expr, TInto), Ann, SC),
4851

4952
case inline_or_into(TInto) of
50-
inline -> build_inline(Ann, TCases, TExpr, TInto, TUniq, SE);
51-
into -> build_into(Ann, TCases, TExpr, TInto, TUniq, SE)
53+
inline -> build_inline(Ann, TCases, TExpr, TInto, TUniq, InitVars, SE);
54+
into -> build_into(Ann, TCases, TExpr, TInto, TUniq, InitVars, SE)
5255
end.
5356

5457
%% In case we have no return, we wrap the expression
@@ -115,29 +118,29 @@ collect_filters([H | T], Acc) ->
115118
collect_filters([], Acc) ->
116119
{Acc, []}.
117120

118-
build_inline(Ann, Clauses, Expr, Into, Uniq, S) ->
121+
build_inline(Ann, Clauses, Expr, Into, Uniq, InitVars, S) ->
119122
case not Uniq and lists:all(fun(Clause) -> element(1, Clause) == bin end, Clauses) of
120123
true -> {build_comprehension(Ann, Clauses, Expr, Into), S};
121-
false -> build_inline_each(Ann, Clauses, Expr, Into, Uniq, S)
124+
false -> build_inline_each(Ann, Clauses, Expr, Into, Uniq, InitVars, S)
122125
end.
123126

124-
build_inline_each(Ann, Clauses, Expr, false, Uniq, S) ->
127+
build_inline_each(Ann, Clauses, Expr, false, Uniq, InitVars, S) ->
125128
InnerFun = fun(InnerExpr, _InnerAcc) -> InnerExpr end,
126-
build_reduce(Ann, Clauses, InnerFun, Expr, {nil, Ann}, Uniq, S);
127-
build_inline_each(Ann, [{enum, _, Left = {var, _, _}, Right, [] = _Filters}], Expr, {nil, _} = _Into, false, S) ->
129+
build_reduce(Ann, Clauses, InnerFun, Expr, {nil, Ann}, Uniq, InitVars, S);
130+
build_inline_each(Ann, [{enum, _, Left = {var, _, _}, Right, [] = _Filters}], Expr, {nil, _} = _Into, false, _InitVars, S) ->
128131
Clauses = [{clause, Ann, [Left], [], [Expr]}],
129132
Args = [Right, {'fun', Ann, {clauses, Clauses}}],
130133
{?remote(Ann, 'Elixir.Enum', map, Args), S};
131-
build_inline_each(Ann, [{enum, _, Left = {var, _, _}, Right, [] = _Filters}], Expr, {map, _, []} = _Into, false, S) ->
134+
build_inline_each(Ann, [{enum, _, Left = {var, _, _}, Right, [] = _Filters}], Expr, {map, _, []} = _Into, false, _InitVars, S) ->
132135
Clauses = [{clause, Ann, [Left], [], [Expr]}],
133136
Args = [Right, {'fun', Ann, {clauses, Clauses}}],
134137
List = ?remote(Ann, 'Elixir.Enum', map, Args),
135138
{?remote(Ann, maps, from_list, [List]), S};
136-
build_inline_each(Ann, Clauses, Expr, {nil, _} = Into, Uniq, S) ->
139+
build_inline_each(Ann, Clauses, Expr, {nil, _} = Into, Uniq, InitVars, S) ->
137140
InnerFun = fun(InnerExpr, InnerAcc) -> {cons, Ann, InnerExpr, InnerAcc} end,
138-
{ReduceExpr, SR} = build_reduce(Ann, Clauses, InnerFun, Expr, Into, Uniq, S),
141+
{ReduceExpr, SR} = build_reduce(Ann, Clauses, InnerFun, Expr, Into, Uniq, InitVars, S),
139142
{?remote(Ann, lists, reverse, [ReduceExpr]), SR};
140-
build_inline_each(Ann, Clauses, Expr, {bin, _, []}, Uniq, S) ->
143+
build_inline_each(Ann, Clauses, Expr, {bin, _, []}, Uniq, InitVars, S) ->
141144
{InnerValue, SV} = build_var(Ann, S),
142145
Generated = erl_anno:set_generated(true, Ann),
143146

@@ -155,17 +158,17 @@ build_inline_each(Ann, Clauses, Expr, {bin, _, []}, Uniq, S) ->
155158
]}
156159
end,
157160

158-
{ReduceExpr, SR} = build_reduce(Ann, Clauses, InnerFun, Expr, {nil, Ann}, Uniq, SV),
161+
{ReduceExpr, SR} = build_reduce(Ann, Clauses, InnerFun, Expr, {nil, Ann}, Uniq, InitVars, SV),
159162
{?remote(Ann, erlang, list_to_bitstring, [ReduceExpr]), SR}.
160163

161-
build_into(Ann, Clauses, Expr, {map, _, []}, Uniq, S) ->
162-
{ReduceExpr, SR} = build_inline_each(Ann, Clauses, Expr, {nil, Ann}, Uniq, S),
164+
build_into(Ann, Clauses, Expr, {map, _, []}, Uniq, InitVars, S) ->
165+
{ReduceExpr, SR} = build_inline_each(Ann, Clauses, Expr, {nil, Ann}, Uniq, InitVars, S),
163166
{?remote(Ann, maps, from_list, [ReduceExpr]), SR};
164-
build_into(Ann, Clauses, Expr, ?empty_map_set_pattern = _Into, Uniq, S) ->
167+
build_into(Ann, Clauses, Expr, ?empty_map_set_pattern = _Into, Uniq, InitVars, S) ->
165168
InnerFun = fun(InnerExpr, InnerAcc) -> {cons, Ann, InnerExpr, InnerAcc} end,
166-
{ReduceExpr, SR} = build_reduce(Ann, Clauses, InnerFun, Expr, {nil, Ann}, Uniq, S),
169+
{ReduceExpr, SR} = build_reduce(Ann, Clauses, InnerFun, Expr, {nil, Ann}, Uniq, InitVars, S),
167170
{?remote(Ann, 'Elixir.MapSet', new, [ReduceExpr]), SR};
168-
build_into(Ann, Clauses, Expr, Into, Uniq, S) ->
171+
build_into(Ann, Clauses, Expr, Into, Uniq, InitVars, S) ->
169172
{Fun, SF} = build_var(Ann, S),
170173
{Acc, SA} = build_var(Ann, SF),
171174
{Kind, SK} = build_var(Ann, SA),
@@ -182,7 +185,7 @@ build_into(Ann, Clauses, Expr, Into, Uniq, S) ->
182185
?remote(Ann, 'Elixir.Collectable', into, [Into])
183186
},
184187

185-
{IntoReduceExpr, SN} = build_reduce(Ann, Clauses, InnerFun, Expr, Acc, Uniq, SD),
188+
{IntoReduceExpr, SN} = build_reduce(Ann, Clauses, InnerFun, Expr, Acc, Uniq, InitVars, SD),
186189

187190
TryExpr =
188191
{'try', Ann,
@@ -205,10 +208,10 @@ stacktrace_clause(Ann, Fun, Acc, Kind, Reason, Stack) ->
205208

206209
%% Helpers
207210

208-
build_reduce(Ann, Clauses, InnerFun, Expr, Into, false, S) ->
211+
build_reduce(Ann, Clauses, InnerFun, Expr, Into, false, InitVars, S) ->
209212
{Acc, SA} = build_var(Ann, S),
210-
{build_reduce_each(Clauses, InnerFun(Expr, Acc), Into, Acc, SA), SA};
211-
build_reduce(Ann, Clauses, InnerFun, Expr, Into, true, S) ->
213+
{build_reduce_each(Clauses, InnerFun(Expr, Acc), Into, Acc, InitVars, SA), SA};
214+
build_reduce(Ann, Clauses, InnerFun, Expr, Into, true, InitVars, S) ->
212215
%% Those variables are used only inside the anonymous function
213216
%% so we don't need to worry about returning the scope.
214217
{Acc, SA} = build_var(Ann, S),
@@ -229,12 +232,12 @@ build_reduce(Ann, Clauses, InnerFun, Expr, Into, true, S) ->
229232
]}
230233
]},
231234

232-
EnumReduceCall = build_reduce_each(Clauses, InnerExpr, NewInto, Acc, SU),
235+
EnumReduceCall = build_reduce_each(Clauses, InnerExpr, NewInto, Acc, InitVars, SU),
233236
{?remote(Ann, erlang, element, [{integer, Ann, 1}, EnumReduceCall]), SU}.
234237

235-
build_reduce_each([{enum, Meta, Left, Right, Filters} | T], Expr, Arg, Acc, S) ->
238+
build_reduce_each([{enum, Meta, Left, Right, Filters} | T], Expr, Arg, Acc, InitVars, S) ->
236239
Ann = ?ann(Meta),
237-
True = build_reduce_each(T, Expr, Acc, Acc, S),
240+
True = build_reduce_each(T, Expr, Acc, Acc, InitVars, S),
238241
False = Acc,
239242
Generated = erl_anno:set_generated(true, Ann),
240243

@@ -255,13 +258,13 @@ build_reduce_each([{enum, Meta, Left, Right, Filters} | T], Expr, Arg, Acc, S) -
255258
Args = [Right, Arg, {'fun', Ann, {clauses, Clauses1}}],
256259
?remote(Ann, 'Elixir.Enum', reduce, Args);
257260

258-
build_reduce_each([{bin, Meta, Left, Right, Filters} | T], Expr, Arg, Acc, S) ->
261+
build_reduce_each([{bin, Meta, Left, Right, Filters} | T], Expr, Arg, Acc, InitVars, S) ->
259262
Ann = ?ann(Meta),
260263
Generated = erl_anno:set_generated(true, Ann),
261264
{Tail, ST} = build_var(Ann, S),
262265
{Fun, SF} = build_var(Ann, ST),
263266

264-
True = build_reduce_each(T, Expr, Acc, Acc, SF),
267+
True = build_reduce_each(T, Expr, Acc, Acc, InitVars, SF),
265268
False = Acc,
266269
{bin, _, Elements} = Left,
267270
TailElement = {bin_element, Ann, Tail, default, [bitstring]},
@@ -275,7 +278,7 @@ build_reduce_each([{bin, Meta, Left, Right, Filters} | T], Expr, Arg, Acc, S) ->
275278
[?remote(Ann, erlang, error, [pair(Ann, badarg, Tail)])]}],
276279

277280
NoVarClauses =
278-
case no_var(Generated, Elements) of
281+
case no_unbound_var(Generated, Elements, InitVars) of
279282
error ->
280283
Clauses;
281284

@@ -294,7 +297,7 @@ build_reduce_each([{bin, Meta, Left, Right, Filters} | T], Expr, Arg, Acc, S) ->
294297
{named_fun, Ann, element(3, Fun), VarClauses},
295298
[Right, Arg]};
296299

297-
build_reduce_each([], Expr, _Arg, _Acc, _S) ->
300+
build_reduce_each([], Expr, _Arg, _Acc, _InitVars, _S) ->
298301
Expr.
299302

300303
is_var({var, _, _}) -> true;
@@ -307,9 +310,10 @@ build_var(Ann, S) ->
307310
{Name, ST} = elixir_erl_var:build('_', S),
308311
{{var, Ann, Name}, ST}.
309312

310-
no_var(ParentAnn, Elements) ->
313+
no_unbound_var(ParentAnn, Elements, InitVars) ->
314+
Vars = #{V => K || K := V <- InitVars},
311315
try
312-
[{bin_element, Ann, NoVarExpr, no_var_size(Size), Types} ||
316+
[{bin_element, Ann, NoVarExpr, no_unbound_var_size(Size, Vars), Types} ||
313317
{bin_element, Ann, Expr, Size, Types} <- Elements,
314318
NoVarExpr <- no_var_expr(ParentAnn, Expr)]
315319
catch
@@ -319,9 +323,17 @@ no_var(ParentAnn, Elements) ->
319323
no_var_expr(Ann, {string, _, String}) -> [{var, Ann, '_'} || _ <- String];
320324
no_var_expr(Ann, _) -> [{var, Ann, '_'}].
321325

322-
no_var_size(default) -> default;
323-
no_var_size(Size) when is_integer(Size) -> Size;
324-
no_var_size(_) -> throw(unbound_size).
326+
no_unbound_var_size(Size, Vars) ->
327+
valid_var_size(Size, Vars) orelse throw(unbound_size),
328+
Size.
329+
330+
valid_var_size({var, _, Var}, Vars) when is_map_key(Var, Vars) -> true;
331+
valid_var_size(default, _Vars) -> true;
332+
valid_var_size({integer, _, _}, _Vars) -> true;
333+
valid_var_size(Size, _Vars) when is_integer(Size) -> true;
334+
valid_var_size({op, _Ann, _Op, Left, Right}, Vars) ->
335+
valid_var_size(Left, Vars) andalso valid_var_size(Right, Vars);
336+
valid_var_size(_Size, _vars) -> false.
325337

326338
build_comprehension(Ann, Clauses, Expr, Into) ->
327339
{comprehension_kind(Into), Ann, Expr, comprehension_clause(Clauses)}.

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,33 @@ defmodule Kernel.ComprehensionTest do
520520
assert for(<<s, x::size(s * 8) <- bin>>, into: %{}, do: {s, x}) == %{1 => 1, 2 => 515}
521521
end
522522

523+
test "binary for comprehensions with chunk matching" do
524+
bin = <<0, 1, 255, 2, 0, 3, 0, 1>>
525+
526+
# static sizes
527+
assert for(<<0::8, x::8 <- bin>>, do: x) == [1, 3, 1]
528+
assert for(<<0::8, x::8 <- bin>>, uniq: true, do: x) == [1, 3]
529+
assert for(<<0::8, x::8 <- bin>>, into: "", do: <<x>>) == <<1, 3, 1>>
530+
assert for(<<0::8, x::8 <- bin>>, into: %{}, do: {x, x}) == %{1 => 1, 3 => 3}
531+
532+
# size from pinned variable
533+
s = 8
534+
assert for(<<0::size(^s), x::size(^s) <- bin>>, do: x) == [1, 3, 1]
535+
assert for(<<0::size(^s), x::size(^s) <- bin>>, uniq: true, do: x) == [1, 3]
536+
assert for(<<0::size(^s), x::size(^s) <- bin>>, into: "", do: <<x>>) == <<1, 3, 1>>
537+
assert for(<<0::size(^s), x::size(^s) <- bin>>, into: %{}, do: {x, x}) == %{1 => 1, 3 => 3}
538+
539+
# operation using fixed integers and pinned variables
540+
assert for(<<0::size(^s * 1), x::size(^s * 1) <- bin>>, do: x) == [1, 3, 1]
541+
assert for(<<0::size(^s * 1), x::size(^s * 1) <- bin>>, uniq: true, do: x) == [1, 3]
542+
assert for(<<0::size(^s * 1), x::size(^s * 1) <- bin>>, into: "", do: <<x>>) == <<1, 3, 1>>
543+
544+
# nested generators
545+
assert for(b <- [bin], <<0::size(^s), x::size(^s) <- b>>, do: x) == [1, 3, 1]
546+
assert for(b <- [bin], <<0::size(^s), x::size(^s) <- b>>, uniq: true, do: x) == [1, 3]
547+
assert for(b <- [bin], <<0::size(^s), x::size(^s) <- b>>, into: "", do: <<x>>) == <<1, 3, 1>>
548+
end
549+
523550
test "binary for comprehensions where value is not used" do
524551
bin = <<1, 2, 3>>
525552

0 commit comments

Comments
 (0)