/
elixir_env.erl
138 lines (114 loc) · 5.07 KB
/
elixir_env.erl
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
-module(elixir_env).
-include("elixir.hrl").
-export([
new/0, linify/1, with_vars/2, reset_vars/1,
env_to_scope/1,
reset_unused_vars/1, check_unused_vars/1,
merge_and_check_unused_vars/2,
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
vars => [], %% a set of defined variables
current_vars => {#{}, false}, %% a tuple with maps of read and optional write current vars
unused_vars => {#{}, 0}, %% a map of unused vars and a version counter for vars
prematch_vars => warn, %% controls behaviour outside and inside matches
lexical_tracker => nil, %% lexical tracker PID
contextual_vars => [], %% available contextual variables
tracers => [] %% available compilation tracers
}.
trace(Event, #{tracers := Tracers} = E) ->
[ok = Tracer:trace(Event, E) || Tracer <- Tracers],
ok.
linify({Line, Env}) ->
Env#{line := Line};
linify(#{} = Env) ->
Env.
with_vars(Env, Vars) ->
NumVars = length(Vars),
VarVersions = lists:zip(Vars, lists:seq(0, NumVars - 1)),
Read = maps:from_list(VarVersions),
Env#{vars := Vars, current_vars := {Read, false}, unused_vars := {#{}, NumVars}}.
env_to_scope(#{context := Context, current_vars := {Read, _}}) ->
{VarsList, _Counter} = lists:mapfoldl(fun to_scope_var/2, 0, maps:values(Read)),
VarsMap = maps:from_list(VarsList),
Scope = #elixir_erl{
context=Context,
var_names=VarsMap,
counter=#{'_' => map_size(VarsMap)}
},
{VarsList, Scope}.
to_scope_var(Version, Counter) ->
{{Version, list_to_atom("_@" ++ integer_to_list(Counter))}, Counter + 1}.
reset_vars(Env) ->
Env#{vars := [], current_vars := {#{}, false}, unused_vars := {#{}, 0}}.
%% SCOPE MERGING
reset_read(#{current_vars := {_, Write}} = E, #{current_vars := {Read, _}}) ->
E#{current_vars := {Read, Write}}.
prepare_write(#{current_vars := {Read, _}} = E) ->
E#{current_vars := {Read, Read}}.
close_write(#{current_vars := {_Read, Write}} = E, #{current_vars := {_, false}}) ->
E#{current_vars := {Write, false}};
close_write(#{current_vars := {_Read, Write}} = E, #{current_vars := {_, UpperWrite}}) ->
E#{current_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(#{unused_vars := {_Unused, Version}} = E) ->
E#{unused_vars := {#{}, Version}}.
check_unused_vars(#{unused_vars := {Unused, _Version}} = E) ->
[elixir_errors:form_warn([{line, Line}], E, ?MODULE, {unused_var, Name}) ||
{{Name, _}, Line} <- maps:to_list(Unused), Line /= false, not_underscored(Name)],
E.
merge_and_check_unused_vars(E, #{unused_vars := {ClauseUnused, Version}}) ->
#{current_vars := {Read, _}, unused_vars := {Unused, _Version}} = E,
E#{unused_vars := {merge_and_check_unused_vars(Read, Unused, ClauseUnused, E), Version}}.
merge_and_check_unused_vars(Current, Unused, ClauseUnused, E) ->
maps:fold(fun({Name, Count} = Key, ClauseValue, Acc) ->
Var = {Name, nil},
case Current of
%% The parent knows it, so we have to propagate up.
#{Var := CurrentCount} when Count =< CurrentCount ->
Acc#{Key => ClauseValue};
%% The parent doesn't know it and we didn't use it
#{} when ClauseValue /= false ->
case not_underscored(Name) of
true ->
Warn = {unused_var, Name},
elixir_errors:form_warn([{line, ClauseValue}], E, ?MODULE, Warn);
false ->
ok
end,
Acc;
%% The parent doesn't know it and we used it
#{} ->
Acc
end
end, Unused, ClauseUnused).
not_underscored(Name) ->
case atom_to_list(Name) of
"_" ++ _ -> false;
_ -> true
end.
format_error({unused_var, Name}) ->
io_lib:format("variable \"~ts\" is unused (if the variable is not meant to be used, prefix it with an underscore)", [Name]).