-
Notifications
You must be signed in to change notification settings - Fork 3.3k
/
elixir_erl_compiler.erl
180 lines (148 loc) · 6.4 KB
/
elixir_erl_compiler.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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
-module(elixir_erl_compiler).
-export([spawn/1, forms/3, noenv_forms/3, erl_to_core/2]).
-include("elixir.hrl").
spawn(Fun) ->
CompilerInfo = get(elixir_compiler_info),
CodeDiagnostics =
case get(elixir_code_diagnostics) of
undefined -> undefined;
{_Tail, Log} -> {[], Log}
end,
{_, Ref} =
spawn_monitor(fun() ->
put(elixir_compiler_info, CompilerInfo),
put(elixir_code_diagnostics, CodeDiagnostics),
try Fun() of
Result -> exit({ok, Result, get(elixir_code_diagnostics)})
catch
Kind:Reason:Stack ->
exit({Kind, Reason, Stack, get(elixir_code_diagnostics)})
end
end),
receive
{'DOWN', Ref, process, _, {ok, Result, Diagnostics}} ->
copy_diagnostics(Diagnostics),
Result;
{'DOWN', Ref, process, _, {Kind, Reason, Stack, Diagnostics}} ->
copy_diagnostics(Diagnostics),
erlang:raise(Kind, Reason, Stack)
end.
copy_diagnostics(undefined) ->
ok;
copy_diagnostics({Head, _}) ->
case get(elixir_code_diagnostics) of
undefined -> ok;
{Tail, Log} -> put(elixir_code_diagnostics, {Head ++ Tail, Log})
end.
forms(Forms, File, Opts) ->
compile(Forms, File, Opts ++ compile:env_compiler_options()).
noenv_forms(Forms, File, Opts) ->
compile(Forms, File, Opts).
erl_to_core(Forms, Opts) ->
%% TODO: Remove parse transform handling on Elixir v2.0
case [M || {parse_transform, M} <- Opts] of
[] ->
v3_core:module(Forms, Opts);
_ ->
case compile:noenv_forms(Forms, [no_spawn_compiler_process, to_core0, return, no_auto_import | Opts]) of
{ok, _Module, Core, Warnings} -> {ok, Core, Warnings};
{error, Errors, Warnings} -> {error, Errors, Warnings}
end
end.
compile(Forms, File, Opts) when is_list(Forms), is_list(Opts), is_binary(File) ->
Source = elixir_utils:characters_to_list(File),
case erl_to_core(Forms, Opts) of
{ok, CoreForms, CoreWarnings} ->
format_warnings(Opts, CoreWarnings),
CompileOpts = [no_spawn_compiler_process, from_core, no_core_prepare,
no_auto_import, return, {source, Source} | Opts],
case compile:noenv_forms(CoreForms, CompileOpts) of
{ok, Module, Binary, Warnings} when is_binary(Binary) ->
format_warnings(Opts, Warnings),
{Module, Binary};
{ok, Module, _Binary, _Warnings} ->
Message = io_lib:format(
"could not compile module ~ts. We expected the compiler to return a .beam binary but "
"got something else. This usually happens because ERL_COMPILER_OPTIONS or @compile "
"was set to change the compilation outcome in a way that is incompatible with Elixir",
[elixir_aliases:inspect(Module)]
),
elixir_errors:compile_error([], File, Message);
{error, Errors, Warnings} ->
format_warnings(Opts, Warnings),
format_errors(Errors)
end;
{error, CoreErrors, CoreWarnings} ->
format_warnings(Opts, CoreWarnings),
format_errors(CoreErrors)
end.
format_errors([]) ->
exit({nocompile, "compilation failed but no error was raised"});
format_errors(Errors) ->
lists:foreach(fun
({File, Each}) when is_list(File) ->
BinFile = elixir_utils:characters_to_binary(File),
lists:foreach(fun(Error) -> handle_file_error(BinFile, Error) end, Each);
({Mod, Each}) when is_atom(Mod) ->
lists:foreach(fun(Error) -> handle_file_error(elixir_aliases:inspect(Mod), Error) end, Each)
end, Errors).
format_warnings(Opts, Warnings) ->
NoWarnNoMatch = proplists:get_value(nowarn_nomatch, Opts, false),
lists:foreach(fun ({File, Each}) ->
BinFile = elixir_utils:characters_to_binary(File),
lists:foreach(fun(Warning) ->
handle_file_warning(NoWarnNoMatch, BinFile, Warning)
end, Each)
end, Warnings).
%% Handle warnings from Erlang land
%% Those we implement ourselves
handle_file_warning(_, _File, {_Line, v3_core, {map_key_repeated, _}}) -> ok;
handle_file_warning(_, _File, {_Line, sys_core_fold, {ignored, useless_building}}) -> ok;
%% Ignore all linting errors (only come up on parse transforms)
handle_file_warning(_, _File, {_Line, erl_lint, _}) -> ok;
handle_file_warning(_, File, {Line, Module, Desc}) ->
Message = custom_format(Module, Desc),
elixir_errors:erl_warn(Line, File, Message).
%% Handle warnings
handle_file_error(File, {beam_validator, Desc}) ->
elixir_errors:compile_error([{line, 0}], File, beam_validator:format_error(Desc));
handle_file_error(File, {Line, Module, Desc}) ->
Message = custom_format(Module, Desc),
elixir_errors:compile_error([{line, Line}], File, Message).
%% Mention the capture operator in make_fun
custom_format(sys_core_fold, {ignored, {no_effect, {erlang, make_fun, 3}}}) ->
"the result of the capture operator & (:erlang.make_fun/3) is never used";
%% Make no_effect clauses pretty
custom_format(sys_core_fold, {ignored, {no_effect, {erlang, F, A}}}) ->
{Fmt, Args} = case erl_internal:comp_op(F, A) of
true -> {"use of operator ~ts has no effect", [elixir_utils:erlang_comparison_op_to_elixir(F)]};
false ->
case erl_internal:bif(F, A) of
false -> {"the call to :erlang.~ts/~B has no effect", [F, A]};
true -> {"the call to ~ts/~B has no effect", [F, A]}
end
end,
io_lib:format(Fmt, Args);
%% Rewrite nomatch to be more generic, it can happen inside if, unless, and the like
custom_format(sys_core_fold, {nomatch, X}) when X == guard; X == no_clause ->
"this check/guard will always yield the same result";
custom_format(sys_core_fold, {nomatch, {shadow, Line, {ErlName, ErlArity}}}) ->
{Name, Arity} = elixir_utils:erl_fa_to_elixir_fa(ErlName, ErlArity),
io_lib:format(
"this clause for ~ts/~B cannot match because a previous clause at line ~B always matches",
[Name, Arity, Line]
);
%% Handle literal eval failures
custom_format(sys_core_fold, {failed, {eval_failure, {Mod, Name, Arity}, Error}}) ->
#{'__struct__' := Struct} = 'Elixir.Exception':normalize(error, Error),
{ExMod, ExName, ExArgs} = elixir_rewrite:erl_to_ex(Mod, Name, lists:duplicate(Arity, nil)),
Call = 'Elixir.Exception':format_mfa(ExMod, ExName, length(ExArgs)),
Trimmed = case Call of
<<"Kernel.", Rest/binary>> -> Rest;
_ -> Call
end,
["the call to ", Trimmed, " will fail with ", elixir_aliases:inspect(Struct)];
custom_format([], Desc) ->
io_lib:format("~p", [Desc]);
custom_format(Module, Desc) ->
Module:format_error(Desc).