1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
|
-module(elixir_env).
-include("elixir.hrl").
-export([
new/0, to_caller/1, with_vars/2, reset_vars/1, env_to_ex/1, set_prematch_from_config/1,
reset_unused_vars/1, check_unused_vars/2, merge_and_check_unused_vars/3,
trace/2, format_error/1,
reset_read/2, prepare_write/1, close_write/2
]).
new() ->
#{
'__struct__' => 'Elixir.Macro.Env',
module => nil, %% the current module
file => <<"nofile">>, %% the current filename
line => 1, %% the current line
function => nil, %% the current function
context => nil, %% can be match, guard or nil
aliases => [], %% a list of aliases by new -> old names
requires => elixir_dispatch:default_requires(), %% a set with modules required
functions => elixir_dispatch:default_functions(), %% a list with functions imported from module
macros => elixir_dispatch:default_macros(), %% a list with macros imported from module
macro_aliases => [], %% keep aliases defined inside a macro
context_modules => [], %% modules defined in the current context
versioned_vars => #{}, %% a map of vars with their latest versions
lexical_tracker => nil, %% lexical tracker PID
tracers => [] %% available compilation tracers
}.
trace(Event, #{tracers := Tracers} = E) ->
[ok = Tracer:trace(Event, E) || Tracer <- Tracers],
ok.
to_caller({Line, #elixir_ex{vars={Read, _}}, Env}) ->
Env#{line := Line, versioned_vars := Read};
to_caller(#{} = Env) ->
Env.
with_vars(Env, Vars) when is_list(Vars) ->
{ReversedVars, _} =
lists:foldl(fun(Var, {Acc, I}) -> {[{Var, I} | Acc], I + 1} end, {[], 0}, Vars),
Env#{versioned_vars := maps:from_list(ReversedVars)};
with_vars(Env, #{} = Vars) ->
Env#{versioned_vars := Vars}.
reset_vars(Env) ->
Env#{versioned_vars := #{}}.
set_prematch_from_config(#elixir_ex{} = S) ->
S#elixir_ex{prematch=elixir_config:get(on_undefined_variable)}.
%% CONVERSIONS
env_to_ex(#{context := match, versioned_vars := Vars}) ->
Counter = map_size(Vars),
#elixir_ex{prematch={Vars, Counter}, vars={Vars, false}, unused={#{}, Counter}};
env_to_ex(#{versioned_vars := Vars}) ->
set_prematch_from_config(#elixir_ex{vars={Vars, false}, unused={#{}, map_size(Vars)}}).
%% VAR HANDLING
reset_read(#elixir_ex{vars={_, Write}} = S, #elixir_ex{vars={Read, _}}) ->
S#elixir_ex{vars={Read, Write}}.
prepare_write(#elixir_ex{vars={Read, _}} = S) ->
S#elixir_ex{vars={Read, Read}}.
close_write(#elixir_ex{vars={_Read, Write}} = S, #elixir_ex{vars={_, false}}) ->
S#elixir_ex{vars={Write, false}};
close_write(#elixir_ex{vars={_Read, Write}} = S, #elixir_ex{vars={_, UpperWrite}}) ->
S#elixir_ex{vars={Write, merge_vars(UpperWrite, Write)}}.
merge_vars(V, V) ->
V;
merge_vars(V1, V2) ->
maps:fold(fun(K, M2, Acc) ->
case Acc of
#{K := M1} when M1 >= M2 -> Acc;
_ -> Acc#{K => M2}
end
end, V1, V2).
%% UNUSED VARS
reset_unused_vars(#elixir_ex{unused={_Unused, Version}} = S) ->
S#elixir_ex{unused={#{}, Version}}.
check_unused_vars(#elixir_ex{unused={Unused, _Version}}, E) ->
[elixir_errors:file_warn([{line, Line}], E, ?MODULE, {unused_var, Name, Overridden}) ||
{{{Name, nil}, _}, {Line, Overridden}} <- maps:to_list(Unused), is_unused_var(Name)],
E.
merge_and_check_unused_vars(S, #elixir_ex{vars={Read, Write}, unused={Unused, _Version}}, E) ->
#elixir_ex{unused={ClauseUnused, Version}} = S,
NewUnused = merge_and_check_unused_vars(Read, Unused, ClauseUnused, E),
S#elixir_ex{unused={NewUnused, Version}, vars={Read, Write}}.
merge_and_check_unused_vars(Current, Unused, ClauseUnused, E) ->
maps:fold(fun
({Var, Count} = Key, false, Acc) ->
case Current of
#{Var := CurrentCount} when Count =< CurrentCount ->
%% The parent knows it, so we have to propagate it was used up.
Acc#{Key => false};
#{} ->
Acc
end;
({{Name, Kind}, _Count}, {Line, Overridden}, Acc) ->
case (Kind == nil) andalso is_unused_var(Name) of
true ->
Warn = {unused_var, Name, Overridden},
elixir_errors:file_warn([{line, Line}], E, ?MODULE, Warn);
false ->
ok
end,
Acc
end, Unused, ClauseUnused).
is_unused_var(Name) ->
case atom_to_list(Name) of
"_" ++ Rest -> is_compiler_var(Rest);
_ -> true
end.
is_compiler_var([$_]) -> true;
is_compiler_var([Var | Rest]) when Var =:= $_; Var >= $A, Var =< $Z -> is_compiler_var(Rest);
is_compiler_var(_) -> false.
format_error({unused_var, Name, Overridden}) ->
case atom_to_list(Name) of
"_" ++ _ ->
io_lib:format("unknown compiler variable \"~ts\" (expected one of __MODULE__, __ENV__, __DIR__, __CALLER__, __STACKTRACE__)", [Name]);
_ when Overridden ->
io_lib:format("variable \"~ts\" is unused (there is a variable with the same name in the context, use the pin operator (^) to match on it or prefix this variable with underscore if it is not meant to be used)", [Name]);
_ ->
io_lib:format("variable \"~ts\" is unused (if the variable is not meant to be used, prefix it with an underscore)", [Name])
end.
|