1%% Module responsible for tracking lexical information.
2-module(elixir_lexical).
3-export([run/3, with_file/3, trace/2, format_error/1]).
4-include("elixir.hrl").
5
6-define(tracker, 'Elixir.Kernel.LexicalTracker').
7
8run(#{tracers := Tracers} = E, ExecutionCallback, AfterExecutionCallback) ->
9  case elixir_config:get(bootstrap) of
10    false ->
11      {ok, Pid} = ?tracker:start_link(),
12      LexEnv = E#{lexical_tracker := Pid, tracers := [?MODULE | Tracers]},
13      elixir_env:trace(start, LexEnv),
14
15      try ExecutionCallback(LexEnv) of
16        Res ->
17          warn_unused_aliases(Pid, LexEnv),
18          warn_unused_imports(Pid, LexEnv),
19          AfterExecutionCallback(LexEnv),
20          Res
21      after
22        elixir_env:trace(stop, LexEnv),
23        unlink(Pid),
24        ?tracker:stop(Pid)
25      end;
26
27    true ->
28      ExecutionCallback(E),
29      AfterExecutionCallback(E)
30  end.
31
32trace({import, Meta, Module, Opts}, #{lexical_tracker := Pid}) ->
33  {imported, Imported} = lists:keyfind(imported, 1, Meta),
34
35  Only =
36    case lists:keyfind(only, 1, Opts) of
37      {only, List} when is_list(List) -> List;
38      _ -> []
39    end,
40
41  ?tracker:add_import(Pid, Module, Only, ?line(Meta), Imported and should_warn(Meta, Opts)),
42  ok;
43trace({alias, Meta, _Old, New, Opts}, #{lexical_tracker := Pid}) ->
44  ?tracker:add_alias(Pid, New, ?line(Meta), should_warn(Meta, Opts)),
45  ok;
46trace({alias_expansion, _Meta, Lookup, _Result}, #{lexical_tracker := Pid}) ->
47  ?tracker:alias_dispatch(Pid, Lookup),
48  ok;
49trace({require, _Meta, Module, _Opts}, #{lexical_tracker := Pid}) ->
50  ?tracker:add_require(Pid, Module),
51  ok;
52trace({struct_expansion, _Meta, Module, _Keys}, #{lexical_tracker := Pid}) ->
53  ?tracker:add_require(Pid, Module),
54  ok;
55trace({alias_reference, _Meta, Module}, #{lexical_tracker := Pid} = E) ->
56  ?tracker:remote_dispatch(Pid, Module, mode(E)),
57  ok;
58trace({remote_function, _Meta, Module, _Function, _Arity}, #{lexical_tracker := Pid} = E) ->
59  ?tracker:remote_dispatch(Pid, Module, mode(E)),
60  ok;
61trace({remote_macro, _Meta, Module, _Function, _Arity}, #{lexical_tracker := Pid}) ->
62  ?tracker:remote_dispatch(Pid, Module, compile),
63  ok;
64trace({imported_function, _Meta, Module, Function, Arity}, #{lexical_tracker := Pid} = E) ->
65  ?tracker:import_dispatch(Pid, Module, {Function, Arity}, mode(E)),
66  ok;
67trace({imported_macro, _Meta, Module, Function, Arity}, #{lexical_tracker := Pid}) ->
68  ?tracker:import_dispatch(Pid, Module, {Function, Arity}, compile),
69  ok;
70trace({compile_env, App, Path, Return}, #{lexical_tracker := Pid}) ->
71  ?tracker:add_compile_env(Pid, App, Path, Return),
72  ok;
73trace(_, _) ->
74  ok.
75
76mode(#{function := nil}) -> compile;
77mode(#{}) -> runtime.
78
79should_warn(Meta, Opts) ->
80  case lists:keyfind(warn, 1, Opts) of
81    {warn, false} -> false;
82    {warn, true} -> true;
83    false -> not lists:keymember(context, 1, Meta)
84  end.
85
86%% EXTERNAL SOURCES
87
88with_file(File, #{lexical_tracker := nil} = E, Callback) ->
89  Callback(E#{file := File});
90with_file(File, #{lexical_tracker := Pid} = E, Callback) ->
91  try
92    ?tracker:set_file(Pid, File),
93    Callback(E#{file := File})
94  after
95    ?tracker:reset_file(Pid)
96  end.
97
98%% ERROR HANDLING
99
100warn_unused_imports(Pid, E) ->
101  {ModuleImports, MFAImports} =
102    lists:partition(fun({M, _}) -> is_atom(M) end, ?tracker:collect_unused_imports(Pid)),
103
104  Modules = [M || {M, _L} <- ModuleImports],
105  MFAImportsFiltered = [T || {{M, _, _}, _} = T <- MFAImports, not lists:member(M, Modules)],
106
107  [begin
108    elixir_errors:form_warn([{line, L}], ?key(E, file), ?MODULE, {unused_import, M})
109   end || {M, L} <- ModuleImports ++ MFAImportsFiltered],
110  ok.
111
112warn_unused_aliases(Pid, E) ->
113  [begin
114    elixir_errors:form_warn([{line, L}], ?key(E, file), ?MODULE, {unused_alias, M})
115   end || {M, L} <- ?tracker:collect_unused_aliases(Pid)],
116  ok.
117
118format_error({unused_alias, Module}) ->
119  io_lib:format("unused alias ~ts", [elixir_aliases:inspect(Module)]);
120format_error({unused_import, {Module, Function, Arity}}) ->
121  io_lib:format("unused import ~ts.~ts/~w", [elixir_aliases:inspect(Module), Function, Arity]);
122format_error({unused_import, Module}) ->
123  io_lib:format("unused import ~ts", [elixir_aliases:inspect(Module)]).
124