@@ -262,7 +262,7 @@ expand({for, Meta, [_ | _] = Args}, E) ->
262262 {Args , []}
263263 end ,
264264
265- validate_opts (Meta , for , [do , into , uniq ], Block , E ),
265+ validate_opts (Meta , for , [do , into , uniq , reduce ], Block , E ),
266266
267267 {Expr , Opts } =
268268 case lists :keytake (do , 1 , Block ) of
@@ -272,16 +272,16 @@ expand({for, Meta, [_ | _] = Args}, E) ->
272272 form_error (Meta , ? key (E , file ), ? MODULE , {missing_option , for , [do ]})
273273 end ,
274274
275- case lists :keyfind (uniq , 1 , Opts ) of
276- false -> ok ;
277- {uniq , Value } when is_boolean (Value ) -> ok ;
278- {uniq , Value } -> form_error (Meta , ? key (E , file ), ? MODULE , {for_invalid_uniq , Value })
279- end ,
280-
281275 {EOpts , EO } = expand (Opts , E ),
282276 {ECases , EC } = lists :mapfoldl (fun expand_for /2 , EO , Cases ),
283- {EExpr , EE } = expand (Expr , EC ),
284277 assert_generator_start (Meta , ECases , E ),
278+
279+ {EExpr , EE } =
280+ case validate_for_options (EOpts , false , false , false ) of
281+ {ok , MaybeReduce } -> expand_for_do_block (Meta , Expr , EC , MaybeReduce );
282+ {error , Error } -> form_error (Meta , ? key (E , file ), ? MODULE , Error )
283+ end ,
284+
285285 {{for , Meta , ECases ++ [[{do , EExpr } | EOpts ]]}, elixir_env :merge_and_check_unused_vars (E , EE )};
286286
287287% % With
@@ -697,6 +697,42 @@ generated_case_clauses([{do, Clauses}]) ->
697697 RClauses = [{'->' , ? generated (Meta ), Args } || {'->' , Meta , Args } <- Clauses ],
698698 [{do , RClauses }].
699699
700+ % % Comprehensions
701+
702+ validate_for_options ([{into , _ } = Pair | Opts ], _Into , Uniq , Reduce ) ->
703+ validate_for_options (Opts , Pair , Uniq , Reduce );
704+ validate_for_options ([{uniq , Boolean } = Pair | Opts ], Into , _Uniq , Reduce ) when is_boolean (Boolean ) ->
705+ validate_for_options (Opts , Into , Pair , Reduce );
706+ validate_for_options ([{uniq , Value } | _ ], _ , _ , _ ) ->
707+ {error , {for_invalid_uniq , Value }};
708+ validate_for_options ([{reduce , _ } = Pair | Opts ], Into , Uniq , _Reduce ) ->
709+ validate_for_options (Opts , Into , Uniq , Pair );
710+ validate_for_options ([], Into , Uniq , {reduce , _ }) when Into /= false ; Uniq /= false ->
711+ {error , for_conflicting_reduce_into_uniq };
712+ validate_for_options ([], _Into , _Uniq , Reduce ) ->
713+ {ok , Reduce }.
714+
715+ expand_for_do_block (Meta , Expr , E , false ) ->
716+ case Expr of
717+ [{'->' , _ , _ } | _ ] -> form_error (Meta , ? key (E , file ), ? MODULE , for_without_reduce_bad_block );
718+ _ -> expand (Expr , E )
719+ end ;
720+ expand_for_do_block (Meta , Clauses , E , {reduce , _ }) ->
721+ Transformer = fun ({_ , _ , [Left , _Right ]} = Clause , Acc ) ->
722+ case Left of
723+ [_ ] ->
724+ {EClause , EAcc } = elixir_clauses :clause (Meta , fn , fun elixir_clauses :head /2 , Clause , Acc ),
725+ {EClause , elixir_env :merge_and_check_unused_vars (Acc , EAcc )};
726+ _ ->
727+ form_error (Meta , ? key (E , file ), ? MODULE , for_with_reduce_bad_block )
728+ end
729+ end ,
730+
731+ case Clauses of
732+ [{'->' , _ , _ } | _ ] -> lists :mapfoldl (Transformer , E , Clauses );
733+ _ -> form_error (Meta , ? key (E , file ), ? MODULE , for_with_reduce_bad_block )
734+ end .
735+
700736% % Locals
701737
702738assert_no_ambiguous_op (Name , Meta , [Arg ], E ) ->
@@ -1045,6 +1081,12 @@ format_error({invalid_args, Construct}) ->
10451081 io_lib :format (" invalid arguments for \" ~ts \" " , [Construct ]);
10461082format_error ({for_invalid_uniq , Value }) ->
10471083 io_lib :format (" :uniq option for comprehensions only accepts a boolean, got: ~ts " , ['Elixir.Macro' :to_string (Value )]);
1084+ format_error (for_conflicting_reduce_into_uniq ) ->
1085+ " cannot use :reduce alongside :into/:uniq in comprehension" ;
1086+ format_error (for_with_reduce_bad_block ) ->
1087+ " when using :reduce with comprehensions, the do block must be written using acc -> expr clauses, where each clause expects the accumulator as a single argument" ;
1088+ format_error (for_without_reduce_bad_block ) ->
1089+ " the do block was written using acc -> expr clauses but the :reduce option was not given" ;
10481090format_error (for_generator_start ) ->
10491091 " for comprehensions must start with a generator" ;
10501092format_error (unhandled_arrow_op ) ->
0 commit comments