Skip to content

Commit 48e2efd

Browse files
committed
Return diagnostics from Kernel.ParallelCompiler
1 parent fb1f183 commit 48e2efd

File tree

5 files changed

+117
-90
lines changed

5 files changed

+117
-90
lines changed

lib/elixir/lib/io.ex

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ defmodule IO do
327327

328328
def warn(message, []) do
329329
message = [to_chardata(message), ?\n]
330-
:elixir_errors.print_warning(0, nil, message, message)
330+
:elixir_errors.print_diagnostic(:warning, 0, nil, message, message)
331331
end
332332

333333
def warn(message, %Macro.Env{} = env) do
@@ -357,7 +357,8 @@ defmodule IO do
357357
line = opts[:line]
358358
file = opts[:file]
359359

360-
:elixir_errors.print_warning(
360+
:elixir_errors.print_diagnostic(
361+
:warning,
361362
line || 0,
362363
file && List.to_string(file),
363364
message,

lib/elixir/lib/kernel/parallel_compiler.ex

Lines changed: 50 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,24 @@ defmodule Kernel.ParallelCompiler do
55

66
@typedoc "The line. 0 indicates no line."
77
@type line() :: non_neg_integer()
8-
@type location() :: line() | {pos_integer(), column :: non_neg_integer}
9-
@type warning() :: {file :: Path.t(), location(), message :: String.t()}
10-
@type error() :: {file :: Path.t(), location(), message :: String.t()}
8+
@type position() :: line() | {pos_integer(), column :: non_neg_integer}
9+
10+
@type diagnostic(severity) :: %{
11+
file: Path.t(),
12+
severity: severity,
13+
message: String.t(),
14+
position: position
15+
}
1116

1217
@type info :: %{
13-
runtime_warnings: [warning],
14-
compile_warnings: [warning]
18+
runtime_warnings: [diagnostic(:warning)],
19+
compile_warnings: [diagnostic(:warning)]
1520
}
1621

22+
# Deprecated types
23+
@type warning() :: {file :: Path.t(), position(), message :: String.t()}
24+
@type error() :: {file :: Path.t(), position(), message :: String.t()}
25+
1726
@doc """
1827
Starts a task for parallel compilation.
1928
@@ -62,17 +71,14 @@ defmodule Kernel.ParallelCompiler do
6271
6372
It returns `{:ok, modules, warnings}` or `{:error, errors, warnings}`
6473
by default but we recommend using `return_maps: true` so it returns
65-
a map as third element instead of a list of warnings. The map has the
66-
shape of:
74+
diagnostics as maps as well as a map of compilation information.
75+
The map has the shape of:
6776
6877
%{
6978
runtime_warnings: [warning],
7079
compile_warnings: [warning]
7180
}
7281
73-
Both errors and warnings are a list of three-element tuples containing
74-
the file, line and the formatted error/warning.
75-
7682
## Options
7783
7884
* `:each_file` - for each file compiled, invokes the callback passing the
@@ -109,12 +115,13 @@ defmodule Kernel.ParallelCompiler do
109115
* `:beam_timestamp` - the modification timestamp to give all BEAM files
110116
111117
* `:return_maps` (since v1.15.0) - returns maps with information instead of
112-
a list of warnings
118+
a list of warnings and returns diagnostics as maps instead of tuples
113119
114120
"""
115121
@doc since: "1.6.0"
116122
@spec compile([Path.t()], keyword()) ::
117-
{:ok, [atom], [warning] | info()} | {:error, [error], [warning] | info()}
123+
{:ok, [atom], [warning] | info()}
124+
| {:error, [error] | [diagnostic(:error)], [warning] | info()}
118125
def compile(files, options \\ []) when is_list(options) do
119126
spawn_workers(files, :compile, options)
120127
end
@@ -126,7 +133,8 @@ defmodule Kernel.ParallelCompiler do
126133
"""
127134
@doc since: "1.6.0"
128135
@spec compile_to_path([Path.t()], Path.t(), keyword()) ::
129-
{:ok, [atom], [warning] | info()} | {:error, [error], [warning] | info()}
136+
{:ok, [atom], [warning] | info()}
137+
| {:error, [error] | [diagnostic(:error)], [warning] | info()}
130138
def compile_to_path(files, path, options \\ []) when is_binary(path) and is_list(options) do
131139
spawn_workers(files, {:compile, path}, options)
132140
end
@@ -139,17 +147,14 @@ defmodule Kernel.ParallelCompiler do
139147
140148
It returns `{:ok, modules, warnings}` or `{:error, errors, warnings}`
141149
by default but we recommend using `return_maps: true` so it returns
142-
a map as third element instead of a list of warnings. The map has the
143-
shape of:
150+
diagnostics as maps as well as a map of compilation information.
151+
The map has the shape of:
144152
145153
%{
146154
runtime_warnings: [warning],
147155
compile_warnings: [warning]
148156
}
149157
150-
Both errors and warnings are a list of three-element tuples containing
151-
the file, line and the formatted error/warning.
152-
153158
## Options
154159
155160
* `:each_file` - for each file compiled, invokes the callback passing the
@@ -167,12 +172,17 @@ defmodule Kernel.ParallelCompiler do
167172
end
168173

169174
@doc """
170-
Prints a warning returned by the compiler.
175+
Prints a diagnostic returned by the compiler into stderr.
171176
"""
172-
@doc since: "1.13.0"
173-
@spec print_warning(warning) :: :ok
177+
@doc since: "1.15.0"
178+
def print_diagnostic(%{file: file, position: position, message: message, severity: severity}) do
179+
:elixir_errors.print_diagnostic_no_capture(severity, position, file, message)
180+
end
181+
182+
@doc false
183+
# TODO: Deprecate me on Elixir v1.19
174184
def print_warning({file, location, warning}) do
175-
:elixir_errors.print_warning_no_diagnostic(location, file, warning)
185+
:elixir_errors.print_diagnostic_no_capture(:warning, location, file, warning)
176186
end
177187

178188
@doc false
@@ -209,7 +219,8 @@ defmodule Kernel.ParallelCompiler do
209219
"Compilation failed due to warnings while using the --warnings-as-errors option"
210220

211221
IO.puts(:stderr, message)
212-
{:error, r_warnings ++ c_warnings, %{info | runtime_warnings: [], compile_warnings: []}}
222+
errors = Enum.map(r_warnings ++ c_warnings, &Map.replace!(&1, :severity, :error))
223+
{:error, errors, %{info | runtime_warnings: [], compile_warnings: []}}
213224

214225
{{:ok, outcome, info}, _} ->
215226
beam_timestamp = Keyword.get(options, :beam_timestamp)
@@ -230,7 +241,12 @@ defmodule Kernel.ParallelCompiler do
230241
if Keyword.get(options, :return_maps, false) do
231242
{status, modules_or_errors, info}
232243
else
233-
{status, modules_or_errors, info.runtime_warnings ++ info.compile_warnings}
244+
to_tuples = &Enum.map(&1, fn diag -> {diag.file, diag.position, diag.message} end)
245+
246+
modules_or_errors =
247+
if status == :ok, do: modules_or_errors, else: to_tuples.(modules_or_errors)
248+
249+
{status, modules_or_errors, to_tuples.(info.runtime_warnings ++ info.compile_warnings)}
234250
end
235251
end
236252

@@ -633,19 +649,13 @@ defmodule Kernel.ParallelCompiler do
633649
state = %{state | timer_ref: timer_ref}
634650
spawn_workers(queue, spawned, waiting, files, result, warnings, errors, state)
635651

636-
{:diagnostic, type, file, location, message} ->
637-
file = file && Path.absname(file)
638-
message = :unicode.characters_to_binary(message)
652+
{:diagnostic, %{severity: :warning} = diagnostic} ->
653+
warnings = [diagnostic | warnings]
654+
wait_for_messages(queue, spawned, waiting, files, result, warnings, errors, state)
639655

640-
case type do
641-
:warning ->
642-
warnings = [{file, location, message} | warnings]
643-
wait_for_messages(queue, spawned, waiting, files, result, warnings, errors, state)
644-
645-
:error ->
646-
errors = [{file, location, message} | errors]
647-
wait_for_messages(queue, spawned, waiting, files, result, warnings, errors, state)
648-
end
656+
{:diagnostic, %{severity: :error} = diagnostic} ->
657+
errors = [diagnostic | errors]
658+
wait_for_messages(queue, spawned, waiting, files, result, warnings, errors, state)
649659

650660
{:file_ok, child_pid, ref, file, lexical} ->
651661
state.each_file.(file, lexical)
@@ -816,7 +826,9 @@ defmodule Kernel.ParallelCompiler do
816826
"and that the modules they reference exist and are correctly named\n"
817827
)
818828

819-
for {file, _, description} <- deadlock, do: {Path.absname(file), nil, description}
829+
for {file, _, description} <- deadlock do
830+
%{severity: :error, file: Path.absname(file), position: nil, message: description}
831+
end
820832
end
821833

822834
defp terminate(files) do
@@ -842,7 +854,7 @@ defmodule Kernel.ParallelCompiler do
842854
line = get_line(file, reason, stack)
843855
file = Path.absname(file)
844856
message = :unicode.characters_to_binary(Kernel.CLI.format_error(kind, reason, stack))
845-
{file, line || 0, message}
857+
%{file: file, position: line || 0, message: message, severity: :error}
846858
end
847859

848860
defp get_line(_file, %{line: line, column: column}, _stack)

lib/elixir/lib/module/parallel_checker.ex

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -147,24 +147,21 @@ defmodule Module.ParallelChecker do
147147
spawn({self(), checker}, module, file)
148148
end
149149

150-
modules = :gen_server.call(checker, :start, :infinity)
151-
collect_results(modules, [])
150+
count = :gen_server.call(checker, :start, :infinity)
151+
collect_results(count, [])
152152
end
153153

154-
defp collect_results([], warnings) do
155-
warnings
154+
defp collect_results(0, diagnostics) do
155+
diagnostics
156156
end
157157

158-
defp collect_results(modules, warnings) do
158+
defp collect_results(count, diagnostics) do
159159
receive do
160-
{:diagnostic, _type, file, location, message} ->
161-
file = file && Path.absname(file)
162-
message = :unicode.characters_to_binary(message)
163-
warning = {file, location, message}
164-
collect_results(modules, [warning | warnings])
165-
166-
{__MODULE__, _module, new_warnings} ->
167-
collect_results(tl(modules), new_warnings ++ warnings)
160+
{:diagnostic, diagnostic} ->
161+
collect_results(count, [diagnostic | diagnostics])
162+
163+
{__MODULE__, _module, new_diagnostics} ->
164+
collect_results(count - 1, new_diagnostics ++ diagnostics)
168165
end
169166
end
170167

@@ -294,10 +291,14 @@ defmodule Module.ParallelChecker do
294291
def emit_warnings(warnings) do
295292
Enum.flat_map(warnings, fn {module, warning, locations} ->
296293
message = module.format_warning(warning)
297-
:elixir_errors.print_warning_no_diagnostic([message, ?\n, format_locations(locations)])
294+
295+
:elixir_errors.print_diagnostic_no_capture(
296+
:warning,
297+
[message, ?\n, format_locations(locations)]
298+
)
298299

299300
Enum.map(locations, fn {file, line, _mfa} ->
300-
{file, line, message}
301+
%{severity: :warning, file: file, position: line, message: message}
301302
end)
302303
end)
303304
end
@@ -485,7 +486,7 @@ defmodule Module.ParallelChecker do
485486
end
486487
end
487488

488-
{:reply, modules, run_checkers(state)}
489+
{:reply, length(modules), run_checkers(state)}
489490
end
490491

491492
def handle_call(:ets, _from, state) do

lib/elixir/src/elixir_errors.erl

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
-export([compile_error/1, compile_error/3, parse_error/5]).
88
-export([function_error/4, module_error/4, file_error/4]).
99
-export([erl_warn/3, file_warn/4]).
10-
-export([print_warning/4, print_warning_no_diagnostic/1, print_warning_no_diagnostic/3]).
10+
-export([print_diagnostic/5, print_diagnostic_no_capture/2, print_diagnostic_no_capture/4]).
1111
-include("elixir.hrl").
1212
-type location() :: non_neg_integer() | {non_neg_integer(), non_neg_integer()}.
1313

@@ -17,21 +17,22 @@ erl_warn(none, File, Warning) ->
1717
erl_warn(0, File, Warning);
1818
erl_warn(Location, File, Warning) when is_binary(File) ->
1919
send_diagnostic(warning, Location, File, Warning),
20-
print_warning_no_diagnostic(Location, File, Warning).
20+
print_diagnostic_no_capture(warning, Location, File, Warning).
2121

22-
-spec print_warning_no_diagnostic(location(), unicode:chardata(), unicode:chardata()) -> ok.
23-
print_warning_no_diagnostic(Location, File, Warning) ->
24-
print_warning_no_diagnostic([Warning, "\n ", file_format(Location, File), $\n]).
22+
-spec print_diagnostic_no_capture(warning | error, location(), unicode:chardata(), unicode:chardata()) -> ok.
23+
print_diagnostic_no_capture(Type, Location, File, Message) ->
24+
print_diagnostic_no_capture(Type, [Message, "\n ", file_format(Location, File), $\n]).
2525

26-
-spec print_warning_no_diagnostic(unicode:chardata()) -> ok.
27-
print_warning_no_diagnostic(Message) ->
28-
io:put_chars(standard_error, [warning_prefix(), Message, $\n]),
26+
-spec print_diagnostic_no_capture(warning | error, unicode:chardata()) -> ok.
27+
print_diagnostic_no_capture(Type, Message) ->
28+
io:put_chars(standard_error, [prefix(Type), Message, $\n]),
2929
ok.
3030

31-
-spec print_warning(location(), unicode:chardata() | nil, unicode:chardata(), unicode:chardata()) -> ok.
32-
print_warning(Location, File, DiagMessage, PrintMessage) when is_binary(File) or (File == nil) ->
33-
send_diagnostic(warning, Location, File, DiagMessage),
34-
print_warning_no_diagnostic(PrintMessage).
31+
-spec print_diagnostic(warning | error, location(), unicode:chardata() | nil, unicode:chardata(), unicode:chardata()) -> ok.
32+
print_diagnostic(Type, Location, File, DiagMessage, PrintMessage)
33+
when is_binary(File) or (File == nil) ->
34+
send_diagnostic(Type, Location, File, DiagMessage),
35+
print_diagnostic_no_capture(Type, PrintMessage).
3536

3637
%% Compilation error/warn handling.
3738

@@ -44,8 +45,8 @@ file_warn(Meta, E, Module, Desc) when is_list(Meta) ->
4445
true -> ok;
4546
false ->
4647
{EnvLine, EnvFile, EnvLocation} = env_format(Meta, E),
47-
Warning = Module:format_error(Desc),
48-
print_warning(EnvLine, EnvFile, Warning, [Warning, "\n ", EnvLocation, $\n])
48+
Message = Module:format_error(Desc),
49+
print_diagnostic(warning, EnvLine, EnvFile, Message, [Message, "\n ", EnvLocation, $\n])
4950
end.
5051

5152
-spec file_error(list(), binary() | #{file := binary(), _ => _}, module(), any()) -> no_return().
@@ -78,8 +79,7 @@ function_error(Meta, Env, Module, Desc) ->
7879
print_error(Meta, Env, Module, Desc) ->
7980
{EnvLine, EnvFile, EnvLocation} = env_format(Meta, Env),
8081
Message = Module:format_error(Desc),
81-
send_diagnostic(error, EnvLine, EnvFile, Message),
82-
io:put_chars(standard_error, [error_prefix(), Message, "\n ", EnvLocation, $\n, $\n]),
82+
print_diagnostic(error, EnvLine, EnvFile, Message, [Message, "\n ", EnvLocation, $\n]),
8383
ok.
8484

8585
%% Compilation error.
@@ -210,20 +210,27 @@ snippet(InputString, Location, StartLine, StartColumn) ->
210210

211211
%% Helpers
212212

213-
send_diagnostic(Type, Line, File, Message) ->
213+
send_diagnostic(Severity, Position, File, Message) ->
214214
case get(elixir_compiler_info) of
215215
undefined -> ok;
216-
{CompilerPid, _} -> CompilerPid ! {diagnostic, Type, File, Line, Message}
216+
{CompilerPid, _} ->
217+
Diagnostic = #{
218+
severity => Severity,
219+
file => if File =:= nil -> nil; true -> filename:absname(File) end,
220+
position => Position,
221+
message => unicode:characters_to_binary(Message)
222+
},
223+
224+
CompilerPid ! {diagnostic, Diagnostic}
217225
end,
218226
ok.
219227

220-
warning_prefix() ->
228+
prefix(warning) ->
221229
case application:get_env(elixir, ansi_enabled, false) of
222230
true -> <<"\e[33mwarning: \e[0m">>;
223231
false -> <<"warning: ">>
224-
end.
225-
226-
error_prefix() ->
232+
end;
233+
prefix(error) ->
227234
case application:get_env(elixir, ansi_enabled, false) of
228235
true -> <<"\e[31merror: \e[0m">>;
229236
false -> <<"error: ">>

0 commit comments

Comments
 (0)