Skip to content

Commit b4c73b2

Browse files
author
José Valim
committed
Move match vars handling and warnings to elixir_expand
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
1 parent 4fa496c commit b4c73b2

File tree

8 files changed

+94
-86
lines changed

8 files changed

+94
-86
lines changed

lib/elixir/lib/macro/env.ex

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ defmodule Macro.Env do
4444
4545
* `export_vars` - a list keeping all variables to be exported in a
4646
construct (may be `nil`)
47+
* `match_vars` - a list of variables defined in a given match (is
48+
`nil` when not inside a match)
4749
* `prematch_vars` - a list of variables defined before a match (is
4850
`nil` when not inside a match)
4951
@@ -64,6 +66,7 @@ defmodule Macro.Env do
6466
@type local :: atom | nil
6567

6668
@opaque export_vars :: vars | nil
69+
@opaque match_vars :: vars | nil
6770
@opaque prematch_vars :: vars | nil
6871

6972
@type t :: %{__struct__: __MODULE__,
@@ -80,6 +83,7 @@ defmodule Macro.Env do
8083
context_modules: context_modules,
8184
vars: vars,
8285
export_vars: export_vars,
86+
match_vars: match_vars,
8387
prematch_vars: prematch_vars,
8488
lexical_tracker: lexical_tracker}
8589

@@ -99,6 +103,7 @@ defmodule Macro.Env do
99103
vars: [],
100104
lexical_tracker: nil,
101105
export_vars: nil,
106+
match_vars: nil,
102107
prematch_vars: nil}
103108
end
104109

lib/elixir/src/elixir.hrl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
caller=false, %% when true, it means caller was invoked
1212
vars=#{}, %% a map of defined variables and their alias
1313
backup_vars=nil, %% a copy of vars to be used on ^var
14-
match_vars=nil, %% a set of all variables defined in a particular match
1514
export_vars=nil, %% a dict of all variables defined in a particular clause
1615
extra_guards=nil, %% extra guards from args expansion
1716
counter=#{}, %% a map counting the variables defined

lib/elixir/src/elixir_clauses.erl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@
99

1010
match(Fun, Expr, #{context := match} = E) ->
1111
Fun(Expr, E);
12-
match(Fun, Expr, #{context := Context, prematch_vars := nil, vars := Vars} = E) ->
13-
{EExpr, EE} = Fun(Expr, E#{context := match, prematch_vars := Vars}),
14-
{EExpr, EE#{context := Context, prematch_vars := nil}}.
12+
match(Fun, Expr, #{context := Context, match_vars := nil, prematch_vars := nil, vars := Vars} = E) ->
13+
{EExpr, EE} = Fun(Expr, E#{context := match, match_vars := [], prematch_vars := Vars}),
14+
{EExpr, EE#{context := Context, match_vars := nil, prematch_vars := nil}}.
1515

1616
def({Meta, Args, Guards, Body}, E) ->
17-
{EArgs, EA} = elixir_expand:expand(Args, E#{context := match}),
18-
{EGuards, EG} = guard(Guards, EA#{context := guard}),
17+
{EArgs, EA} = elixir_expand:expand(Args, E#{context := match, match_vars := []}),
18+
{EGuards, EG} = guard(Guards, EA#{context := guard, match_vars := nil}),
1919
{EBody, _} = elixir_expand:expand(Body, EG#{context := ?key(E, context)}),
2020
{Meta, EArgs, EGuards, EBody}.
2121

lib/elixir/src/elixir_env.erl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ new() ->
1919
lexical_tracker => nil, %% holds the lexical tracker PID
2020
vars => [], %% a set of defined variables
2121
export_vars => nil, %% a set of variables to be exported in some constructs
22+
match_vars => nil, %% a set of variables defined in the current match
2223
prematch_vars => nil}. %% a set of variables defined before the current match
2324

2425
linify({Line, Env}) ->

lib/elixir/src/elixir_erl_clauses.erl

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,9 @@ get_clauses(Key, Keyword, As, AllowNil) ->
2020

2121
%% Translate matches
2222

23-
match(Fun, Args, #elixir_erl{context=Context, match_vars=MatchVars,
24-
backup_vars=BackupVars, vars=Vars} = S) when Context /= match ->
25-
{Result, NewS} = match(Fun, Args, S#elixir_erl{context=match,
26-
match_vars=#{}, backup_vars=Vars}),
27-
{Result, NewS#elixir_erl{context=Context,
28-
match_vars=MatchVars, backup_vars=BackupVars}};
23+
match(Fun, Args, #elixir_erl{context=Context, backup_vars=BackupVars, vars=Vars} = S) when Context /= match ->
24+
{Result, NewS} = match(Fun, Args, S#elixir_erl{context=match, backup_vars=Vars}),
25+
{Result, NewS#elixir_erl{context=Context, backup_vars=BackupVars}};
2926
match(Fun, Args, S) -> Fun(Args, S).
3027

3128
%% Translate clauses with args, guards and expressions

lib/elixir/src/elixir_erl_pass.erl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,6 @@ translate({for, Meta, [_ | _] = Args}, S) ->
153153
translate({'^', Meta, [{Name, VarMeta, Kind}]}, #elixir_erl{context=match, file=File} = S) when is_atom(Name), is_atom(Kind) ->
154154
Tuple = {Name, var_kind(VarMeta, Kind)},
155155
{ok, {Value, _Counter, Safe}} = maps:find(Tuple, S#elixir_erl.backup_vars),
156-
elixir_erl_var:warn_underscored_var_access(VarMeta, File, Name),
157156
elixir_erl_var:warn_unsafe_var(VarMeta, File, Name, Safe),
158157

159158
PAnn = ?ann(Meta),

lib/elixir/src/elixir_erl_var.erl

Lines changed: 20 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
%% Convenience functions used to manipulate scope and its variables.
22
-module(elixir_erl_var).
3-
-export([translate/4, build/2, context_info/1,
3+
-export([translate/4, build/2,
44
load_binding/2, dump_binding/2,
55
mergev/2, mergec/2, merge_vars/2, merge_opt_vars/2,
6-
warn_unsafe_var/4, warn_underscored_var_access/3, format_error/1
6+
warn_unsafe_var/4, format_error/1
77
]).
88
-include("elixir.hrl").
99

@@ -13,22 +13,26 @@ translate(Meta, Name, Kind, S) when is_atom(Kind); is_integer(Kind) ->
1313
Ann = ?ann(Meta),
1414
Tuple = {Name, Kind},
1515
Vars = S#elixir_erl.vars,
16+
BackupVars = S#elixir_erl.backup_vars,
1617

17-
{Current, Exists, Safe} =
18+
{Current, Safe} =
1819
case maps:find({Name, Kind}, Vars) of
19-
{ok, {VarC, _, VarS}} -> {VarC, true, VarS};
20-
error -> {nil, false, true}
20+
{ok, {VarC, _, VarS}} -> {VarC, VarS};
21+
error -> {nil, true}
2122
end,
2223

2324
case S#elixir_erl.context of
2425
match ->
25-
MatchVars = S#elixir_erl.match_vars,
26-
27-
case Exists andalso maps:get(Tuple, MatchVars, false) of
28-
true ->
29-
warn_underscored_var_repeat(Meta, S#elixir_erl.file, Name, Kind),
26+
Previous =
27+
case maps:find({Name, Kind}, BackupVars) of
28+
{ok, {BackupVarC, _, _}} -> BackupVarC;
29+
error -> nil
30+
end,
31+
32+
if
33+
Current /= nil, Current /= Previous ->
3034
{{var, Ann, Current}, S};
31-
false ->
35+
true ->
3236
%% We attempt to give vars a nice name because we
3337
%% still use the unused vars warnings from erl_lint.
3438
%%
@@ -44,7 +48,6 @@ translate(Meta, Name, Kind, S) when is_atom(Kind); is_integer(Kind) ->
4448

4549
FS = NS#elixir_erl{
4650
vars=maps:put(Tuple, {NewVar, Counter, true}, Vars),
47-
match_vars=maps:put(Tuple, true, MatchVars),
4851
export_vars=case S#elixir_erl.export_vars of
4952
nil -> nil;
5053
EV -> maps:put(Tuple, {NewVar, Counter, true}, EV)
@@ -53,8 +56,7 @@ translate(Meta, Name, Kind, S) when is_atom(Kind); is_integer(Kind) ->
5356

5457
{{var, Ann, NewVar}, FS}
5558
end;
56-
_ when Exists ->
57-
warn_underscored_var_access(Meta, S#elixir_erl.file, Name),
59+
_ when Current /= nil ->
5860
warn_unsafe_var(Meta, S#elixir_erl.file, Name, Safe),
5961
{{var, Ann, Current}, S}
6062
end.
@@ -69,43 +71,14 @@ build(Key, #elixir_erl{counter=Counter} = S) ->
6971
Cnt,
7072
S#elixir_erl{counter=maps:put(Key, Cnt, Counter)}}.
7173

72-
context_info(Kind) when Kind == nil; is_integer(Kind) -> "";
73-
context_info(Kind) -> io_lib:format(" (context ~ts)", [elixir_aliases:inspect(Kind)]).
74-
75-
warn_underscored_var_repeat(Meta, File, Name, Kind) ->
76-
Warn = should_warn(Meta),
77-
case atom_to_list(Name) of
78-
"_@" ++ _ ->
79-
ok; %% Automatically generated variables
80-
"_" ++ _ when Warn ->
81-
elixir_errors:form_warn(Meta, File, ?MODULE, {unused_match, Name, Kind});
82-
_ ->
83-
ok
84-
end.
85-
8674
warn_unsafe_var(Meta, File, Name, Safe) ->
87-
Warn = should_warn(Meta),
88-
if
89-
(not Safe) and Warn ->
90-
elixir_errors:form_warn(Meta, File, ?MODULE, {unsafe_var, Name});
75+
case (not Safe) andalso (lists:keyfind(generated, 1, Meta) /= {generated, true}) of
9176
true ->
77+
elixir_errors:form_warn(Meta, File, ?MODULE, {unsafe_var, Name});
78+
false ->
9279
ok
9380
end.
9481

95-
warn_underscored_var_access(Meta, File, Name) ->
96-
Warn = should_warn(Meta),
97-
case atom_to_list(Name) of
98-
"_@" ++ _ ->
99-
ok; %% Automatically generated variables
100-
"_" ++ _ when Warn ->
101-
elixir_errors:form_warn(Meta, File, ?MODULE, {underscore_var_access, Name});
102-
_ ->
103-
ok
104-
end.
105-
106-
should_warn(Meta) ->
107-
lists:keyfind(generated, 1, Meta) /= {generated, true}.
108-
10982
%% SCOPE MERGING
11083

11184
%% Receives two scopes and return a new scope based on
@@ -193,13 +166,6 @@ dump_binding(Binding, #elixir_erl{vars=Vars}) ->
193166

194167
%% Errors
195168

196-
format_error({unused_match, Name, Kind}) ->
197-
io_lib:format("the underscored variable \"~ts\"~ts appears more than once in a "
198-
"match. This means the pattern will only match if all \"~ts\" bind "
199-
"to the same value. If this is the intended behaviour, please "
200-
"remove the leading underscore from the variable name, otherwise "
201-
"give the variables different names", [Name, context_info(Kind), Name]);
202-
203169
format_error({unsafe_var, Name}) ->
204170
io_lib:format("the variable \"~ts\" is unsafe as it has been set inside "
205171
"one of: case, cond, receive, if, and, or, &&, ||. "
@@ -214,10 +180,4 @@ format_error({unsafe_var, Name}) ->
214180
" 1 -> :one\n"
215181
" 2 -> :two\n"
216182
" end\n\n"
217-
"Unsafe variable found at:", [Name]);
218-
219-
format_error({underscore_var_access, Name}) ->
220-
io_lib:format("the underscored variable \"~ts\" is used after being set. "
221-
"A leading underscore indicates that the value of the variable "
222-
"should be ignored. If this is intended please rename the "
223-
"variable to remove the underscore", [Name]).
183+
"Unsafe variable found at:", [Name]).

lib/elixir/src/elixir_expand.erl

Lines changed: 60 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -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) ->
331332
expand({'_', 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}};
342353
expand({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+
817853
format_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)]);
870906
format_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)]);
920956
format_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

Comments
 (0)