Skip to content

Commit 278eba5

Browse files
sabiwarajosevalim
authored andcommitted
Handle unused :uniq comprehensions (#12116)
1 parent b213013 commit 278eba5

File tree

5 files changed

+109
-67
lines changed

5 files changed

+109
-67
lines changed

lib/elixir/src/elixir_erl_for.erl

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
-module(elixir_erl_for).
2-
-export([translate/4]).
2+
-export([translate/3]).
33
-include("elixir.hrl").
44

5-
translate(Meta, Args, Return, S) ->
5+
translate(Meta, Args, S) ->
66
{Cases, [{do, Expr} | Opts]} = elixir_utils:split_last(Args),
77

88
case lists:keyfind(reduce, 1, Opts) of
99
{reduce, Reduce} -> translate_reduce(Meta, Cases, Expr, Reduce, S);
10-
false -> translate_into(Meta, Cases, Expr, Opts, Return, S)
10+
false -> translate_into(Meta, Cases, Expr, Opts, S)
1111
end.
1212

1313
translate_reduce(Meta, Cases, Expr, Reduce, S) ->
@@ -23,14 +23,13 @@ translate_reduce(Meta, Cases, Expr, Reduce, S) ->
2323

2424
build_reduce(Ann, TCases, InnerFun, TExpr, TReduce, false, SE).
2525

26-
translate_into(Meta, Cases, Expr, Opts, Return, S) ->
26+
translate_into(Meta, Cases, Expr, Opts, S) ->
2727
Ann = ?ann(Meta),
2828

2929
{TInto, SI} =
3030
case lists:keyfind(into, 1, Opts) of
3131
{into, Into} -> elixir_erl_pass:translate(Into, Ann, S);
32-
false when Return -> {{nil, Ann}, S};
33-
false -> {false, S}
32+
false -> {{nil, Ann}, S}
3433
end,
3534

3635
TUniq = lists:keyfind(uniq, 1, Opts) == {uniq, true},

lib/elixir/src/elixir_erl_pass.erl

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ translate({'receive', Meta, [Opts]}, _Ann, S) ->
182182
%% Comprehensions and with
183183

184184
translate({for, Meta, [_ | _] = Args}, _Ann, S) ->
185-
elixir_erl_for:translate(Meta, Args, true, S);
185+
elixir_erl_for:translate(Meta, Args, S);
186186

187187
translate({with, Meta, [_ | _] = Args}, _Ann, S) ->
188188
{Exprs, [{do, Do} | Opts]} = elixir_utils:split_last(Args),
@@ -348,12 +348,6 @@ translate_block([H], Ann, Acc, S) ->
348348
translate_block([{'__block__', Meta, Args} | T], Ann, Acc, S) when is_list(Args) ->
349349
{TAcc, SA} = translate_block(Args, ?ann(Meta), Acc, S),
350350
translate_block(T, Ann, TAcc, SA);
351-
translate_block([{for, Meta, [_ | _] = Args} | T], Ann, Acc, S) ->
352-
{TH, TS} = elixir_erl_for:translate(Meta, Args, false, S),
353-
translate_block(T, Ann, [TH | Acc], TS);
354-
translate_block([{'=', _, [{'_', _, Ctx}, {for, Meta, [_ | _] = Args}]} | T], Ann, Acc, S) when is_atom(Ctx) ->
355-
{TH, TS} = elixir_erl_for:translate(Meta, Args, false, S),
356-
translate_block(T, Ann, [TH | Acc], TS);
357351
translate_block([H | T], Ann, Acc, S) ->
358352
{TH, TS} = translate(H, Ann, S),
359353
translate_block(T, Ann, [TH | Acc], TS).

lib/elixir/src/elixir_expand.erl

Lines changed: 70 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -253,45 +253,8 @@ expand({'try', Meta, [Opts]}, S, E) ->
253253

254254
%% Comprehensions
255255

256-
expand({for, Meta, [_ | _] = Args}, S, E) ->
257-
assert_no_match_or_guard_scope(Meta, "for", S, E),
258-
259-
{Cases, Block} =
260-
case elixir_utils:split_last(Args) of
261-
{OuterCases, OuterOpts} when is_list(OuterOpts) ->
262-
case elixir_utils:split_last(OuterCases) of
263-
{InnerCases, InnerOpts} when is_list(InnerOpts) ->
264-
{InnerCases, InnerOpts ++ OuterOpts};
265-
_ ->
266-
{OuterCases, OuterOpts}
267-
end;
268-
_ ->
269-
{Args, []}
270-
end,
271-
272-
validate_opts(Meta, for, [do, into, uniq, reduce], Block, E),
273-
274-
{Expr, Opts} =
275-
case lists:keytake(do, 1, Block) of
276-
{value, {do, Do}, DoOpts} ->
277-
{Do, DoOpts};
278-
false ->
279-
form_error(Meta, E, ?MODULE, {missing_option, for, [do]})
280-
end,
281-
282-
{EOpts, SO, EO} = expand(Opts, elixir_env:reset_unused_vars(S), E),
283-
{ECases, SC, EC} = mapfold(fun expand_for/3, SO, EO, Cases),
284-
assert_generator_start(Meta, ECases, E),
285-
286-
{EExpr, SE, EE} =
287-
case validate_for_options(EOpts, false, false, false) of
288-
{ok, MaybeReduce} -> expand_for_do_block(Meta, Expr, SC, EC, MaybeReduce);
289-
{error, Error} -> form_error(Meta, E, ?MODULE, Error)
290-
end,
291-
292-
{{for, Meta, ECases ++ [[{do, EExpr} | EOpts]]},
293-
elixir_env:merge_and_check_unused_vars(SE, S, EE),
294-
E};
256+
expand({for, _, [_ | _] } = Expr, S, E) ->
257+
expand_for(Expr, S, E, true);
295258

296259
%% With
297260

@@ -563,6 +526,12 @@ expand_block([], Acc, _Meta, S, E) ->
563526
expand_block([H], Acc, Meta, S, E) ->
564527
{EH, SE, EE} = expand(H, S, E),
565528
expand_block([], [EH | Acc], Meta, SE, EE);
529+
expand_block([{for, _, [_ | _]} = H | T], Acc, Meta, S, E) ->
530+
{EH, SE, EE} = expand_for(H, S, E, false),
531+
expand_block(T, [EH | Acc], Meta, SE, EE);
532+
expand_block([{'=', _, [{'_', _, Ctx}, {for, _, [_ | _]} = H]} | T], Acc, Meta, S, E) when is_atom(Ctx) ->
533+
{EH, SE, EE} = expand_for(H, S, E, false),
534+
expand_block(T, [EH | Acc], Meta, SE, EE);
566535
expand_block([H | T], Acc, Meta, S, E) ->
567536
{EH, SE, EE} = expand(H, S, E),
568537

@@ -741,18 +710,65 @@ generated_case_clauses([{do, Clauses}]) ->
741710

742711
%% Comprehensions
743712

744-
validate_for_options([{into, _} = Pair | Opts], _Into, Uniq, Reduce) ->
745-
validate_for_options(Opts, Pair, Uniq, Reduce);
746-
validate_for_options([{uniq, Boolean} = Pair | Opts], Into, _Uniq, Reduce) when is_boolean(Boolean) ->
747-
validate_for_options(Opts, Into, Pair, Reduce);
748-
validate_for_options([{uniq, Value} | _], _, _, _) ->
713+
expand_for({for, Meta, [_ | _] = Args}, S, E, Return) ->
714+
assert_no_match_or_guard_scope(Meta, "for", S, E),
715+
716+
{Cases, Block} =
717+
case elixir_utils:split_last(Args) of
718+
{OuterCases, OuterOpts} when is_list(OuterOpts) ->
719+
case elixir_utils:split_last(OuterCases) of
720+
{InnerCases, InnerOpts} when is_list(InnerOpts) ->
721+
{InnerCases, InnerOpts ++ OuterOpts};
722+
_ ->
723+
{OuterCases, OuterOpts}
724+
end;
725+
_ ->
726+
{Args, []}
727+
end,
728+
729+
validate_opts(Meta, for, [do, into, uniq, reduce], Block, E),
730+
731+
{Expr, Opts} =
732+
case lists:keytake(do, 1, Block) of
733+
{value, {do, Do}, DoOpts} ->
734+
{Do, DoOpts};
735+
false ->
736+
form_error(Meta, E, ?MODULE, {missing_option, for, [do]})
737+
end,
738+
739+
{EOpts, SO, EO} = expand(Opts, elixir_env:reset_unused_vars(S), E),
740+
{ECases, SC, EC} = mapfold(fun expand_for_generator/3, SO, EO, Cases),
741+
assert_generator_start(Meta, ECases, E),
742+
743+
{{EExpr, SE, EE}, NormalizedOpts} =
744+
case validate_for_options(EOpts, false, false, false, Return, Meta, E, []) of
745+
{ok, MaybeReduce, NOpts} -> {expand_for_do_block(Meta, Expr, SC, EC, MaybeReduce), NOpts};
746+
{error, Error} -> {form_error(Meta, E, ?MODULE, Error), EOpts}
747+
end,
748+
749+
{{for, Meta, ECases ++ [[{do, EExpr} | NormalizedOpts]]},
750+
elixir_env:merge_and_check_unused_vars(SE, S, EE),
751+
E}.
752+
753+
validate_for_options([{into, _} = Pair | Opts], _Into, Uniq, Reduce, Return, Meta, E, Acc) ->
754+
validate_for_options(Opts, Pair, Uniq, Reduce, Return, Meta, E, [Pair | Acc]);
755+
validate_for_options([{uniq, Boolean} = Pair | Opts], Into, _Uniq, Reduce, Return, Meta, E, Acc) when is_boolean(Boolean) ->
756+
validate_for_options(Opts, Into, Pair, Reduce, Return, Meta, E, [Pair | Acc]);
757+
validate_for_options([{uniq, Value} | _], _, _, _, _, _, _, _) ->
749758
{error, {for_invalid_uniq, Value}};
750-
validate_for_options([{reduce, _} = Pair | Opts], Into, Uniq, _Reduce) ->
751-
validate_for_options(Opts, Into, Uniq, Pair);
752-
validate_for_options([], Into, Uniq, {reduce, _}) when Into /= false; Uniq /= false ->
759+
validate_for_options([{reduce, _} = Pair | Opts], Into, Uniq, _Reduce, Return, Meta, E, Acc) ->
760+
validate_for_options(Opts, Into, Uniq, Pair, Return, Meta, E, [Pair | Acc]);
761+
validate_for_options([], Into, Uniq, {reduce, _}, _Return, _Meta, _E, _Acc) when Into /= false; Uniq /= false ->
753762
{error, for_conflicting_reduce_into_uniq};
754-
validate_for_options([], _Into, _Uniq, Reduce) ->
755-
{ok, Reduce}.
763+
validate_for_options([], _Into = false, Uniq, Reduce = false, Return = true, Meta, E, Acc) ->
764+
Pair = {into, []},
765+
validate_for_options([Pair], Pair, Uniq, Reduce, Return, Meta, E, Acc);
766+
validate_for_options([], Into = false, {uniq, true}, Reduce = false, Return = false, Meta, E, Acc) ->
767+
elixir_errors:form_warn(Meta, E, ?MODULE, for_with_unused_uniq),
768+
AccWithoutUniq = lists:keydelete(uniq, 1, Acc),
769+
validate_for_options([], Into, false, Reduce, Return, Meta, E, AccWithoutUniq);
770+
validate_for_options([], _Into, _Uniq, Reduce, _Return, _Meta, _E, Acc) ->
771+
{ok, Reduce, lists:reverse(Acc)}.
756772

757773
expand_for_do_block(Meta, [{'->', _, _} | _], _S, E, false) ->
758774
form_error(Meta, E, ?MODULE, for_without_reduce_bad_block);
@@ -1031,12 +1047,12 @@ expand_aliases({'__aliases__', Meta, _} = Alias, S, E, Report) ->
10311047

10321048
%% Comprehensions
10331049

1034-
expand_for({'<-', Meta, [Left, Right]}, S, E) ->
1050+
expand_for_generator({'<-', Meta, [Left, Right]}, S, E) ->
10351051
{ERight, SR, ER} = expand(Right, S, E),
10361052
SM = elixir_env:reset_read(SR, S),
10371053
{[ELeft], SL, EL} = elixir_clauses:head([Left], SM, ER),
10381054
{{'<-', Meta, [ELeft, ERight]}, SL, EL};
1039-
expand_for({'<<>>', Meta, Args} = X, S, E) when is_list(Args) ->
1055+
expand_for_generator({'<<>>', Meta, Args} = X, S, E) when is_list(Args) ->
10401056
case elixir_utils:split_last(Args) of
10411057
{LeftStart, {'<-', OpMeta, [LeftEnd, Right]}} ->
10421058
{ERight, SR, ER} = expand(Right, S, E),
@@ -1048,7 +1064,7 @@ expand_for({'<<>>', Meta, Args} = X, S, E) when is_list(Args) ->
10481064
_ ->
10491065
expand(X, S, E)
10501066
end;
1051-
expand_for(X, S, E) ->
1067+
expand_for_generator(X, S, E) ->
10521068
expand(X, S, E).
10531069

10541070
assert_generator_start(_, [{'<-', _, [_, _]} | _], _) ->
@@ -1170,6 +1186,8 @@ format_error(for_without_reduce_bad_block) ->
11701186
"the do block was written using acc -> expr clauses but the :reduce option was not given";
11711187
format_error(for_generator_start) ->
11721188
"for comprehensions must start with a generator";
1189+
format_error(for_with_unused_uniq) ->
1190+
"the :uniq option has no effect since the result of the for comprehension is not used";
11731191
format_error(unhandled_arrow_op) ->
11741192
"unhandled operator ->";
11751193
format_error(as_in_multi_alias_call) ->

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,36 @@ defmodule Kernel.ComprehensionTest do
115115
assert for(x <- 1..3, nilly(), do: x * 2) == []
116116
end
117117

118+
test "for comprehensions with unique option where value is not used" do
119+
assert capture_io(:stderr, fn ->
120+
assert capture_io(fn ->
121+
Code.eval_quoted(
122+
quote do
123+
for x <- [1, 2, 1, 2], uniq: true, do: IO.puts(x)
124+
nil
125+
end
126+
)
127+
end) ==
128+
"1\n2\n1\n2\n"
129+
end) =~
130+
"the :uniq option has no effect since the result of the for comprehension is not used"
131+
end
132+
133+
test "for comprehensions with unique option where value is assigned to _" do
134+
assert capture_io(:stderr, fn ->
135+
assert capture_io(fn ->
136+
Code.eval_quoted(
137+
quote do
138+
_ = for x <- [1, 2, 1, 2], uniq: true, do: IO.puts(x)
139+
nil
140+
end
141+
)
142+
end) ==
143+
"1\n2\n1\n2\n"
144+
end) =~
145+
"the :uniq option has no effect since the result of the for comprehension is not used"
146+
end
147+
118148
test "for comprehensions with errors on filters" do
119149
assert_raise ArgumentError, fn ->
120150
for x <- 1..3, hd(x), do: x * 2

lib/elixir/test/elixir/kernel/expansion_test.exs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -798,7 +798,8 @@ defmodule Kernel.ExpansionTest do
798798
c = 3
799799
[4]
800800
),
801-
do: {a(), b, c(), d}
801+
do: {a(), b, c(), d},
802+
into: []
802803
)
803804
end
804805

@@ -807,7 +808,7 @@ defmodule Kernel.ExpansionTest do
807808

808809
test "variables inside filters are available in blocks" do
809810
assert expand(quote(do: for(a <- b, c = a, do: c))) ==
810-
quote(do: for(a <- b(), c = a, do: c))
811+
quote(do: for(a <- b(), c = a, do: c, into: []))
811812
end
812813

813814
test "variables inside options do not leak" do

0 commit comments

Comments
 (0)