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