@@ -314,8 +314,9 @@ expand({'^', Meta, [Arg]}, #{context := match, prematch_vars := PrematchVars} =
314314 % % If the variable was defined, then we return the expanded ^, otherwise
315315 % % we raise. We cannot use the expanded env because it would contain the
316316 % % variable.
317- case lists :member ({VarName , var_kind (VarMeta , Kind )}, PrematchVars ) of
317+ case lists :member ({VarName , var_context (VarMeta , Kind )}, PrematchVars ) of
318318 true ->
319+ warn_underscored_var_access (VarMeta , VarName , Kind , E ),
319320 {{'^' , Meta , [Var ]}, EA };
320321 false ->
321322 form_error (Meta , ? key (EA , file ), ? MODULE , {unbound_variable_pin , VarName })
@@ -331,17 +332,28 @@ expand({'_', _Meta, Kind} = Var, #{context := match} = E) when is_atom(Kind) ->
331332expand ({'_' , Meta , Kind }, E ) when is_atom (Kind ) ->
332333 form_error (Meta , ? key (E , file ), ? MODULE , unbound_underscore );
333334
334- expand ({Name , Meta , Kind } = Var , #{context := match , export_vars := Export } = E ) when is_atom (Name ), is_atom (Kind ) ->
335- Pair = {Name , var_kind (Meta , Kind )},
336- NewVars = ordsets :add_element (Pair , ? key (E , vars )),
337- NewExport = case (Export /= nil ) of
338- true -> ordsets :add_element (Pair , Export );
339- false -> Export
340- end ,
341- {Var , E #{vars := NewVars , export_vars := NewExport }};
335+ expand ({Name , Meta , Kind } = Var , #{context := match } = E ) when is_atom (Name ), is_atom (Kind ) ->
336+ #{vars := Vars , match_vars := Match , export_vars := Export } = E ,
337+ Pair = {Name , var_context (Meta , Kind )},
338+ NewVars = ordsets :add_element (Pair , Vars ),
339+
340+ NewExport =
341+ case (Export /= nil ) of
342+ true -> ordsets :add_element (Pair , Export );
343+ false -> Export
344+ end ,
345+
346+ NewMatch =
347+ case lists :member (Pair , Match ) of
348+ true -> warn_underscored_var_repeat (Meta , Name , Kind , E ), Match ;
349+ false -> [Pair | Match ]
350+ end ,
351+
352+ {Var , E #{vars := NewVars , match_vars := NewMatch , export_vars := NewExport }};
342353expand ({Name , Meta , Kind } = Var , #{vars := Vars } = E ) when is_atom (Name ), is_atom (Kind ) ->
343- case lists :member ({Name , var_kind (Meta , Kind )}, Vars ) of
354+ case lists :member ({Name , var_context (Meta , Kind )}, Vars ) of
344355 true ->
356+ warn_underscored_var_access (Meta , Name , Kind , E ),
345357 {Var , E };
346358 false ->
347359 case lists :keyfind (var , 1 , Meta ) of
@@ -555,7 +567,7 @@ expand_args(Args, E) ->
555567
556568% % Match/var helpers
557569
558- var_kind (Meta , Kind ) ->
570+ var_context (Meta , Kind ) ->
559571 case lists :keyfind (counter , 1 , Meta ) of
560572 {counter , Counter } -> Counter ;
561573 false -> Kind
@@ -814,6 +826,30 @@ assert_no_underscore_clause_in_cond(_Other, _E) ->
814826
815827% % Warnings
816828
829+ warn_underscored_var_repeat (Meta , Name , Kind , E ) ->
830+ Warn = should_warn (Meta ),
831+ case atom_to_list (Name ) of
832+ " _" ++ _ when Warn ->
833+ elixir_errors :form_warn (Meta , ? key (E , file ), ? MODULE , {underscored_var_repeat , Name , Kind });
834+ _ ->
835+ ok
836+ end .
837+
838+ warn_underscored_var_access (Meta , Name , Kind , E ) ->
839+ Warn = should_warn (Meta ),
840+ case atom_to_list (Name ) of
841+ " _" ++ _ when Warn ->
842+ elixir_errors :form_warn (Meta , ? key (E , file ), ? MODULE , {underscored_var_access , Name , Kind });
843+ _ ->
844+ ok
845+ end .
846+
847+ context_info (Kind ) when Kind == nil ; is_integer (Kind ) -> " " ;
848+ context_info (Kind ) -> io_lib :format (" (context ~ts )" , [elixir_aliases :inspect (Kind )]).
849+
850+ should_warn (Meta ) ->
851+ lists :keyfind (generated , 1 , Meta ) /= {generated , true }.
852+
817853format_error ({useless_literal , Term }) ->
818854 io_lib :format (" code block contains unused literal ~ts "
819855 " (remove the literal or assign it to _ to avoid warnings)" ,
@@ -866,7 +902,7 @@ format_error({undefined_var, Name, Kind}) ->
866902 Message =
867903 " expected variable \" ~ts \" ~ts to expand to an existing variable "
868904 " or be part of a match" ,
869- io_lib :format (Message , [Name , elixir_erl_var : context_info (Kind )]);
905+ io_lib :format (Message , [Name , context_info (Kind )]);
870906format_error (underscore_in_cond ) ->
871907 " unbound variable _ inside \" cond\" . If you want the last clause to always match, "
872908 " you probably meant to use: true ->" ;
@@ -918,4 +954,15 @@ format_error({options_are_not_keyword, Kind, Opts}) ->
918954 io_lib :format (" invalid options for ~s , expected a keyword list, got: ~ts " ,
919955 [Kind , 'Elixir.Macro' :to_string (Opts )]);
920956format_error ({undefined_function , Name , Args }) ->
921- io_lib :format (" undefined function ~ts /~B " , [Name , length (Args )]).
957+ io_lib :format (" undefined function ~ts /~B " , [Name , length (Args )]);
958+ format_error ({underscored_var_repeat , Name , Kind }) ->
959+ io_lib :format (" the underscored variable \" ~ts \" ~ts appears more than once in a "
960+ " match. This means the pattern will only match if all \" ~ts \" bind "
961+ " to the same value. If this is the intended behaviour, please "
962+ " remove the leading underscore from the variable name, otherwise "
963+ " give the variables different names" , [Name , context_info (Kind ), Name ]);
964+ format_error ({underscored_var_access , Name , Kind }) ->
965+ io_lib :format (" the underscored variable \" ~ts \" ~ts is used after being set. "
966+ " A leading underscore indicates that the value of the variable "
967+ " should be ignored. If this is intended please rename the "
968+ " variable to remove the underscore" , [Name , context_info (Kind )]).
0 commit comments