Skip to content

Commit d856e66

Browse files
author
José Valim
committed
Add warnings for unused aliases
1 parent 5bd12f9 commit d856e66

File tree

12 files changed

+81
-38
lines changed

12 files changed

+81
-38
lines changed

lib/elixir/lib/kernel/special_forms.ex

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,18 @@ defmodule Kernel.SpecialForms do
321321
`import`, `require` and `alias` are called directives and all
322322
have lexical scope. This means you can set up aliases inside
323323
specific functions and it won't affect the overall scope.
324+
325+
## Warnings
326+
327+
If you alias a module and you don't use the alias, Elixir is
328+
going to issue a warning implying the alias is not being used.
329+
330+
In case the alias is generated automatically by a macro,
331+
Elixir won't emit any warnings though, since the alias
332+
was not explicitly defined.
333+
334+
Both warning behaviors could be changed by explicitily
335+
setting the `:warn` option to true or false.
324336
"""
325337
defmacro alias(module, opts)
326338

lib/elixir/lib/macro.ex

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -534,17 +534,17 @@ defmodule Macro do
534534
end
535535

536536
defp expand_once({ :__aliases__, _, _ } = original, env, cache) do
537-
case :elixir_aliases.expand(original, env.aliases, env.macro_aliases) do
537+
case :elixir_aliases.expand(original, env.aliases, env.macro_aliases, env.lexical_tracker) do
538538
receiver when is_atom(receiver) ->
539-
:elixir_lexical.record_alias(receiver, env.lexical_tracker)
539+
:elixir_lexical.record_remote(receiver, env.lexical_tracker)
540540
{ receiver, true, cache }
541541
aliases ->
542542
aliases = lc alias inlist aliases, do: elem(expand_once(alias, env, cache), 0)
543543

544544
case :lists.all(&is_atom/1, aliases) do
545545
true ->
546546
receiver = :elixir_aliases.concat(aliases)
547-
:elixir_lexical.record_alias(receiver, env.lexical_tracker)
547+
:elixir_lexical.record_remote(receiver, env.lexical_tracker)
548548
{ receiver, true, cache }
549549
false -> { original, false, cache }
550550
end

lib/elixir/src/elixir_aliases.erl

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
-module(elixir_aliases).
22
-export([nesting_alias/2, last/1, concat/1, safe_concat/1,
3-
format_error/1, ensure_loaded/3, ensure_loaded/4, expand/3, store/4]).
3+
format_error/1, ensure_loaded/3, ensure_loaded/4, expand/4, store/5]).
44
-include("elixir.hrl").
55

66
%% Store an alias in the given scope
7-
store(_Meta, New, New, S) -> S;
8-
store(Meta, New, Old, S) ->
7+
store(_Meta, New, New, _TKV, S) -> S;
8+
store(Meta, New, Old, TKV, S) ->
9+
record_warn(Meta, New, TKV, S),
10+
911
SA = S#elixir_scope{
1012
aliases=orddict:store(New, Old, S#elixir_scope.aliases)
1113
},
@@ -19,47 +21,58 @@ store(Meta, New, Old, S) ->
1921
SA
2022
end.
2123

24+
record_warn(Meta, Ref, Opts, S) ->
25+
Warn =
26+
case lists:keyfind(warn, 1, Opts) of
27+
{ warn, false } -> false;
28+
{ warn, true } -> true;
29+
false -> not lists:keymember(context, 1, Meta)
30+
end,
31+
elixir_lexical:record_alias(Ref, ?line(Meta), Warn, S#elixir_scope.lexical_tracker).
32+
2233
%% Expand an alias. It returns an atom (meaning that there
2334
%% was an expansion) or a list of atoms.
2435

25-
expand({ '__aliases__', Meta, _ } = Alias, Aliases, MacroAliases) ->
36+
expand({ '__aliases__', Meta, _ } = Alias, Aliases, MacroAliases, LexicalTracker) ->
2637
case lists:keyfind(alias, 1, Meta) of
2738
{ alias, false } ->
28-
expand(Alias, MacroAliases);
39+
expand(Alias, MacroAliases, LexicalTracker);
2940
{ alias, Atom } when is_atom(Atom) ->
30-
case expand(Alias, MacroAliases) of
41+
case expand(Alias, MacroAliases, LexicalTracker) of
3142
OtherAtom when is_atom(OtherAtom) -> OtherAtom;
3243
OtherAliases when is_list(OtherAliases) -> Atom
3344
end;
3445
false ->
35-
expand(Alias, Aliases)
46+
expand(Alias, Aliases, LexicalTracker)
3647
end.
3748

38-
expand({ '__aliases__', _Meta, [H] }, Aliases) when H /= 'Elixir' ->
39-
case expand_one(H, Aliases) of
49+
expand({ '__aliases__', _Meta, [H] }, Aliases, LexicalTracker) when H /= 'Elixir' ->
50+
case expand_one(H, Aliases, LexicalTracker) of
4051
false -> [H];
4152
Atom -> Atom
4253
end;
4354

44-
expand({ '__aliases__', _Meta, [H|T] }, Aliases) when is_atom(H) ->
55+
expand({ '__aliases__', _Meta, [H|T] }, Aliases, LexicalTracker) when is_atom(H) ->
4556
case H of
4657
'Elixir' ->
4758
concat(T);
4859
_ ->
49-
case expand_one(H, Aliases) of
60+
case expand_one(H, Aliases, LexicalTracker) of
5061
false -> [H|T];
5162
Atom -> concat([Atom|T])
5263
end
5364
end;
5465

55-
expand({ '__aliases__', _Meta, List }, _Aliases) ->
66+
expand({ '__aliases__', _Meta, List }, _Aliases, _LexicalTracker) ->
5667
List.
5768

58-
expand_one(H, Aliases) ->
69+
expand_one(H, Aliases, LexicalTracker) ->
5970
Lookup = list_to_atom("Elixir." ++ atom_to_list(H)),
6071
case lookup(Lookup, Aliases) of
6172
Lookup -> false;
62-
Else -> Else
73+
Else ->
74+
elixir_lexical:record_alias(Lookup, LexicalTracker),
75+
Else
6376
end.
6477

6578
%% Ensure a module is loaded before its usage.

lib/elixir/src/elixir_lexical.erl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ run(File, Callback) ->
1717
try
1818
Callback(Pid)
1919
after
20+
warn_unused_aliases(File, Pid),
2021
warn_unused_imports(File, Pid),
2122
unlink(Pid), ?tracker:stop(Pid)
2223
end;
@@ -68,5 +69,12 @@ warn_unused_imports(File, Pid) ->
6869
elixir_errors:handle_file_warning(File, { L, ?MODULE, { unused_import, M } })
6970
end || { M, L } <- ?tracker:collect_unused_imports(Pid)].
7071

72+
warn_unused_aliases(File, Pid) ->
73+
[ begin
74+
elixir_errors:handle_file_warning(File, { L, ?MODULE, { unused_alias, M } })
75+
end || { M, L } <- ?tracker:collect_unused_aliases(Pid)].
76+
77+
format_error({unused_alias, Module}) ->
78+
io_lib:format("unused alias ~ts", [elixir_errors:inspect(Module)]);
7179
format_error({unused_import, Module}) ->
7280
io_lib:format("unused import ~ts", [elixir_errors:inspect(Module)]).

lib/elixir/src/elixir_macros.erl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ translate({defmodule, Meta, [Ref, KV]}, S) when is_list(KV) ->
163163
FullModule = expand_module(Ref, Module, S),
164164

165165
RS = case elixir_aliases:nesting_alias(S#elixir_scope.module, FullModule) of
166-
{ New, Old } -> elixir_aliases:store(Meta, New, Old, S);
166+
{ New, Old } -> elixir_aliases:store(Meta, New, Old, [{warn,false}], S);
167167
false -> S
168168
end,
169169

@@ -308,7 +308,8 @@ expand_module({ '__aliases__', _, [H] }, _Module, S) ->
308308

309309
%% defmodule Hello.World
310310
expand_module({ '__aliases__', _, _ } = Alias, Module, S) ->
311-
case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases) of
311+
case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases,
312+
S#elixir_scope.lexical_tracker) of
312313
Atom when is_atom(Atom) ->
313314
Module;
314315
Aliases when is_list(Aliases) ->

lib/elixir/src/elixir_quote.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ do_quote({ 'alias!', _Meta, [Expr] }, #elixir_quote{aliases_hygiene=true} = Q, S
174174
{ TExpr, TQ#elixir_quote{aliases_hygiene=true} };
175175

176176
do_quote({ '__aliases__', Meta, [H|T] } = Alias, #elixir_quote{aliases_hygiene=true} = Q, S) when is_atom(H) and (H /= 'Elixir') ->
177-
Annotation = case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases) of
177+
Annotation = case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases, S#elixir_scope.lexical_tracker) of
178178
Atom when is_atom(Atom) -> Atom;
179179
Aliases when is_list(Aliases) -> false
180180
end,

lib/elixir/src/elixir_translator.erl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ translate_each({ alias, Meta, [Ref] }, S) ->
8787
translate_each({ alias, Meta, [Ref, KV] }, S) ->
8888
assert_no_match_or_guard_scope(Meta, alias, S),
8989
{ TRef, SR } = translate_each(Ref, S),
90-
{ TKV, ST } = translate_opts(Meta, alias, [as], no_alias_opts(KV), SR),
90+
{ TKV, ST } = translate_opts(Meta, alias, [as, warn], no_alias_opts(KV), SR),
9191

9292
case TRef of
9393
{ atom, _, Old } ->
@@ -103,7 +103,7 @@ translate_each({ require, Meta, [Ref, KV] }, S) ->
103103
assert_no_match_or_guard_scope(Meta, require, S),
104104

105105
{ TRef, SR } = translate_each(Ref, S),
106-
{ TKV, ST } = translate_opts(Meta, require, [as], no_alias_opts(KV), SR),
106+
{ TKV, ST } = translate_opts(Meta, require, [as, warn], no_alias_opts(KV), SR),
107107

108108
case TRef of
109109
{ atom, _, Old } ->
@@ -151,9 +151,9 @@ translate_each({ '__CALLER__', Meta, Atom }, S) when is_atom(Atom) ->
151151
%% Aliases
152152

153153
translate_each({ '__aliases__', Meta, _ } = Alias, S) ->
154-
case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases) of
154+
case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases, S#elixir_scope.lexical_tracker) of
155155
Receiver when is_atom(Receiver) ->
156-
elixir_lexical:record_alias(Receiver, S#elixir_scope.lexical_tracker),
156+
elixir_lexical:record_remote(Receiver, S#elixir_scope.lexical_tracker),
157157
{ { atom, ?line(Meta), Receiver }, S };
158158
Aliases ->
159159
{ TAliases, SA } = translate_args(Aliases, S),
@@ -162,7 +162,7 @@ translate_each({ '__aliases__', Meta, _ } = Alias, S) ->
162162
true ->
163163
Atoms = [Atom || { atom, _, Atom } <- TAliases],
164164
Receiver = elixir_aliases:concat(Atoms),
165-
elixir_lexical:record_alias(Receiver, S#elixir_scope.lexical_tracker),
165+
elixir_lexical:record_remote(Receiver, S#elixir_scope.lexical_tracker),
166166
{ { atom, ?line(Meta), Receiver }, SA };
167167
false ->
168168
Args = [elixir_utils:list_to_cons(?line(Meta), TAliases)],
@@ -523,7 +523,7 @@ translate_alias(Meta, IncludeByDefault, Old, TKV, S) ->
523523
"invalid :as for alias, nested alias ~s not allowed", [elixir_errors:inspect(New)])
524524
end,
525525

526-
{ { atom, ?line(Meta), nil }, elixir_aliases:store(Meta, New, Old, S) }.
526+
{ { atom, ?line(Meta), nil }, elixir_aliases:store(Meta, New, Old, TKV, S) }.
527527

528528
no_alias_opts(KV) when is_list(KV) ->
529529
case lists:keyfind(as, 1, KV) of

lib/elixir/test/elixir/kernel/alias_test.exs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ defmodule Kernel.AliasTest do
2929

3030
test :lexical do
3131
if true do
32-
alias OMG, as: List
32+
alias OMG, as: List, warn: false
3333
else
34-
alias ABC, as: List
34+
alias ABC, as: List, warn: false
3535
end
3636

3737
assert List.flatten([1, [2], 3]) == [1, 2, 3]

lib/elixir/test/elixir/kernel/quote_test.exs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -327,13 +327,13 @@ defmodule Kernel.QuoteTest.AliasHygieneTest do
327327
end
328328

329329
test :expand_aliases_with_macro_does_not_expand_source_alias do
330-
alias HashDict, as: Dict
330+
alias HashDict, as: Dict, warn: false
331331
require Kernel.QuoteTest.AliasHygiene
332332
assert Kernel.QuoteTest.AliasHygiene.dict == Elixir.Dict.Bar
333333
end
334334

335335
test :expand_aliases_with_macro_has_higher_preference do
336-
alias HashDict, as: SuperDict
336+
alias HashDict, as: SuperDict, warn: false
337337
require Kernel.QuoteTest.AliasHygiene
338338
assert Kernel.QuoteTest.AliasHygiene.super_dict == Elixir.Dict.Bar
339339
end

lib/elixir/test/elixir/kernel/warning_test.exs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -138,18 +138,27 @@ defmodule Kernel.WarningTest do
138138
test :unused_import do
139139
assert capture_err(fn ->
140140
Code.compile_string """
141-
defmodule Sample1 do
142-
def hello, do: nil
141+
defmodule Sample do
142+
import :lists, only: [flatten: 1]
143+
def a, do: nil
143144
end
145+
"""
146+
end) =~ "unused import :lists"
147+
after
148+
purge [Sample]
149+
end
144150

145-
defmodule Sample2 do
146-
import Sample1
151+
test :unused_alias do
152+
assert capture_err(fn ->
153+
Code.compile_string """
154+
defmodule Sample do
155+
alias :lists, as: List
147156
def a, do: nil
148157
end
149158
"""
150-
end) =~ "unused import Sample1"
159+
end) =~ "unused alias List"
151160
after
152-
purge [Sample1, Sample2]
161+
purge [Sample]
153162
end
154163

155164
test :unused_guard do

0 commit comments

Comments
 (0)