Skip to content

Commit 6476355

Browse files
committed
Keep stacktrace in diagnostics
1 parent bfa6655 commit 6476355

File tree

9 files changed

+173
-128
lines changed

9 files changed

+173
-128
lines changed

lib/elixir/lib/calendar/datetime.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ defmodule DateTime do
169169
utc_now(unit, Calendar.ISO)
170170

171171
calendar ->
172-
utc_now(:native, calendar)
172+
System.os_time() |> from_unix!(:native, calendar)
173173
end
174174
end
175175

lib/elixir/lib/io.ex

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -325,15 +325,14 @@ defmodule IO do
325325
:ok
326326
def warn(message, stacktrace_info)
327327

328-
def warn(message, []) do
329-
message = [to_chardata(message), ?\n]
330-
:elixir_errors.print_diagnostic(:warning, 0, nil, message, message)
331-
end
332-
333328
def warn(message, %Macro.Env{} = env) do
334329
warn(message, Macro.Env.stacktrace(env))
335330
end
336331

332+
def warn(message, []) do
333+
:elixir_errors.emit_diagnostic(:warning, 0, nil, to_chardata(message), [])
334+
end
335+
337336
def warn(message, [{_, _} | _] = keyword) do
338337
if file = keyword[:file] do
339338
warn(
@@ -353,17 +352,10 @@ defmodule IO do
353352

354353
def warn(message, [{_, _, _, opts} | _] = stacktrace) do
355354
message = to_chardata(message)
356-
formatted_trace = Enum.map_join(stacktrace, "\n ", &Exception.format_stacktrace_entry(&1))
357355
line = opts[:line]
358356
file = opts[:file]
359-
360-
:elixir_errors.print_diagnostic(
361-
:warning,
362-
line || 0,
363-
file && List.to_string(file),
364-
message,
365-
[message, ?\n, " ", formatted_trace, ?\n]
366-
)
357+
file = file && List.to_string(file)
358+
:elixir_errors.emit_diagnostic(:warning, line || 0, file, message, stacktrace)
367359
end
368360

369361
@doc false

lib/elixir/lib/kernel/parallel_compiler.ex

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ defmodule Kernel.ParallelCompiler do
1111
file: Path.t(),
1212
severity: severity,
1313
message: String.t(),
14-
position: position
14+
position: position,
15+
stacktrace: Exception.stacktrace()
1516
}
1617

1718
@type info :: %{
@@ -175,14 +176,14 @@ defmodule Kernel.ParallelCompiler do
175176
Prints a diagnostic returned by the compiler into stderr.
176177
"""
177178
@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)
179+
def print_diagnostic(diagnostic) do
180+
:elixir_errors.print_diagnostic(diagnostic)
180181
end
181182

182183
@doc false
183184
# TODO: Deprecate me on Elixir v1.19
184185
def print_warning({file, location, warning}) do
185-
:elixir_errors.print_diagnostic_no_capture(:warning, location, file, warning)
186+
:elixir_errors.print_warning(location, file, warning)
186187
end
187188

188189
@doc false
@@ -802,7 +803,7 @@ defmodule Kernel.ParallelCompiler do
802803
description = "deadlocked waiting on #{kind} #{inspect(on)}"
803804
error = CompileError.exception(description: description, file: nil, line: nil)
804805
print_error(file, :error, error, stacktrace)
805-
{Path.relative_to_cwd(file), on, description}
806+
{Path.relative_to_cwd(file), on, description, stacktrace}
806807
end
807808

808809
IO.puts(:stderr, """
@@ -816,7 +817,7 @@ defmodule Kernel.ParallelCompiler do
816817
|> Enum.map(&(&1 |> elem(0) |> String.length()))
817818
|> Enum.max()
818819

819-
for {file, mod, _} <- deadlock do
820+
for {file, mod, _, _} <- deadlock do
820821
IO.puts(:stderr, [" ", String.pad_leading(file, max), " => " | inspect(mod)])
821822
end
822823

@@ -826,8 +827,14 @@ defmodule Kernel.ParallelCompiler do
826827
"and that the modules they reference exist and are correctly named\n"
827828
)
828829

829-
for {file, _, description} <- deadlock do
830-
%{severity: :error, file: Path.absname(file), position: nil, message: description}
830+
for {file, _, description, stacktrace} <- deadlock do
831+
%{
832+
severity: :error,
833+
file: Path.absname(file),
834+
position: nil,
835+
message: description,
836+
stacktrace: stacktrace
837+
}
831838
end
832839
end
833840

@@ -854,7 +861,7 @@ defmodule Kernel.ParallelCompiler do
854861
line = get_line(file, reason, stack)
855862
file = Path.absname(file)
856863
message = :unicode.characters_to_binary(Kernel.CLI.format_error(kind, reason, stack))
857-
%{file: file, position: line || 0, message: message, severity: :error}
864+
%{file: file, position: line || 0, message: message, severity: :error, stacktrace: stack}
858865
end
859866

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

lib/elixir/lib/module/parallel_checker.ex

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -291,46 +291,48 @@ defmodule Module.ParallelChecker do
291291
def emit_warnings(warnings) do
292292
Enum.flat_map(warnings, fn {module, warning, locations} ->
293293
message = module.format_warning(warning)
294-
295-
:elixir_errors.print_diagnostic_no_capture(
296-
:warning,
297-
[message, ?\n, format_locations(locations)]
298-
)
299-
300-
Enum.map(locations, fn {file, line, _mfa} ->
301-
%{severity: :warning, file: file, position: line, message: message}
302-
end)
294+
diagnostics = Enum.map(locations, &to_diagnostic(message, &1))
295+
:elixir_errors.print_warning([message, ?\n, format_stacktraces(diagnostics)])
296+
diagnostics
303297
end)
304298
end
305299

306-
defp format_locations([location]) do
307-
format_location(location)
300+
defp format_stacktraces([diagnostic]) do
301+
format_diagnostic_stacktrace(diagnostic)
308302
end
309303

310-
defp format_locations(locations) do
304+
defp format_stacktraces(diagnostics) do
311305
[
312-
"Invalid call found at #{length(locations)} locations:\n",
313-
Enum.map(locations, &format_location/1)
306+
"Invalid call found at #{length(diagnostics)} locations:\n",
307+
Enum.map(diagnostics, &format_diagnostic_stacktrace/1)
314308
]
315309
end
316310

317-
defp format_location({file, line, {module, fun, arity}}) do
318-
mfa = Exception.format_mfa(module, fun, arity)
319-
[format_file_line(file, line), ": ", mfa, ?\n]
311+
defp format_diagnostic_stacktrace(%{stacktrace: [stacktrace]}) do
312+
[" ", Exception.format_stacktrace_entry(stacktrace), ?\n]
320313
end
321314

322-
defp format_location({file, line, nil}) do
323-
[format_file_line(file, line), ?\n]
315+
defp to_diagnostic(message, {file, line, mfa}) do
316+
%{
317+
severity: :warning,
318+
file: file,
319+
position: line,
320+
message: message,
321+
stacktrace: [to_stacktrace(file, line, mfa)]
322+
}
324323
end
325324

326-
defp format_location({file, line, module}) do
327-
[format_file_line(file, line), ": ", inspect(module), ?\n]
328-
end
325+
defp to_stacktrace(file, line, {module, fun, arity}),
326+
do: {module, fun, arity, location(file, line)}
327+
328+
defp to_stacktrace(file, line, nil),
329+
do: {:elixir_compiler, :__FILE__, 1, location(file, line)}
330+
331+
defp to_stacktrace(file, line, module),
332+
do: {module, :__MODULE__, 0, location(file, line)}
329333

330-
defp format_file_line(file, line) do
331-
file = Path.relative_to_cwd(file)
332-
line = if line > 0, do: [?: | Integer.to_string(line)], else: []
333-
[" ", file, line]
334+
defp location(file, line) do
335+
[file: String.to_charlist(Path.relative_to_cwd(file)), line: line]
334336
end
335337

336338
## Cache

lib/elixir/src/elixir_errors.erl

Lines changed: 67 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -7,35 +7,61 @@
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_diagnostic/5, print_diagnostic_no_capture/2, print_diagnostic_no_capture/4]).
10+
-export([print_diagnostic/1, emit_diagnostic/5]).
11+
-export([print_warning/1, print_warning/3]).
1112
-include("elixir.hrl").
1213
-type location() :: non_neg_integer() | {non_neg_integer(), non_neg_integer()}.
1314

14-
%% Low-level warning, should be used only from Erlang passes.
15-
-spec erl_warn(location() | none, unicode:chardata(), unicode:chardata()) -> ok.
16-
erl_warn(none, File, Warning) ->
17-
erl_warn(0, File, Warning);
18-
erl_warn(Location, File, Warning) when is_binary(File) ->
19-
send_diagnostic(warning, Location, File, Warning),
20-
print_diagnostic_no_capture(warning, Location, File, Warning).
15+
%% Diagnostic API
16+
17+
%% TODO: Remove me on Elixir v2.0.
18+
print_warning(Location, File, Message) ->
19+
print_warning([Message, file_format(Location, File), $\n]).
20+
21+
%% Used by parallel checker as it groups warnings.
22+
print_warning(Message) ->
23+
io:put_chars(standard_error, [prefix(warning), Message, $\n]).
2124

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]).
25+
print_diagnostic(#{severity := Severity, message := Message, stacktrace := Stacktrace} = Diagnostic) ->
26+
Location =
27+
case (Stacktrace =:= []) orelse elixir_config:is_bootstrap() of
28+
true ->
29+
#{position := Position, file := File} = Diagnostic,
30+
file_format(Position, File);
2531

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]),
32+
false ->
33+
[["\n ", 'Elixir.Exception':format_stacktrace_entry(E)] || E <- Stacktrace]
34+
end,
35+
io:put_chars(standard_error, [prefix(Severity), Message, Location, "\n\n"]),
2936
ok.
3037

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).
38+
emit_diagnostic(Severity, Position, File, Message, Stacktrace) ->
39+
Diagnostic = #{
40+
severity => Severity,
41+
file => if File =:= nil -> nil; true -> filename:absname(File) end,
42+
position => Position,
43+
message => unicode:characters_to_binary(Message),
44+
stacktrace => Stacktrace
45+
},
46+
47+
print_diagnostic(Diagnostic),
48+
49+
case get(elixir_compiler_info) of
50+
undefined -> ok;
51+
{CompilerPid, _} -> CompilerPid ! {diagnostic, Diagnostic}
52+
end,
53+
54+
ok.
3655

3756
%% Compilation error/warn handling.
3857

58+
%% Low-level warning, should be used only from Erlang passes.
59+
-spec erl_warn(location() | none, unicode:chardata(), unicode:chardata()) -> ok.
60+
erl_warn(none, File, Warning) ->
61+
erl_warn(0, File, Warning);
62+
erl_warn(Location, File, Warning) when is_binary(File) ->
63+
emit_diagnostic(warning, Location, File, Warning, []).
64+
3965
-spec file_warn(list(), binary() | #{file := binary(), _ => _}, module(), any()) -> ok.
4066
file_warn(Meta, File, Module, Desc) when is_list(Meta), is_binary(File) ->
4167
file_warn(Meta, #{file => File}, Module, Desc);
@@ -44,9 +70,9 @@ file_warn(Meta, E, Module, Desc) when is_list(Meta) ->
4470
case elixir_config:is_bootstrap() of
4571
true -> ok;
4672
false ->
47-
{EnvLine, EnvFile, EnvLocation} = env_format(Meta, E),
73+
{EnvLine, EnvFile, EnvStacktrace} = env_format(Meta, E),
4874
Message = Module:format_error(Desc),
49-
print_diagnostic(warning, EnvLine, EnvFile, Message, [Message, "\n ", EnvLocation, $\n])
75+
emit_diagnostic(warning, EnvLine, EnvFile, Message, EnvStacktrace)
5076
end.
5177

5278
-spec file_error(list(), binary() | #{file := binary(), _ => _}, module(), any()) -> no_return().
@@ -77,9 +103,9 @@ function_error(Meta, Env, Module, Desc) ->
77103
file_error(Meta, Env, Module, Desc).
78104

79105
print_error(Meta, Env, Module, Desc) ->
80-
{EnvLine, EnvFile, EnvLocation} = env_format(Meta, Env),
106+
{EnvLine, EnvFile, EnvStacktrace} = env_format(Meta, Env),
81107
Message = Module:format_error(Desc),
82-
print_diagnostic(error, EnvLine, EnvFile, Message, [Message, "\n ", EnvLocation, $\n]),
108+
emit_diagnostic(error, EnvLine, EnvFile, Message, EnvStacktrace),
83109
ok.
84110

85111
%% Compilation error.
@@ -94,11 +120,11 @@ compile_error(#{file := File}) ->
94120

95121
-spec compile_error(list(), binary(), binary() | unicode:charlist()) -> no_return().
96122
compile_error(Meta, File, Message) when is_binary(Message) ->
97-
MetaLocation = meta_location(Meta, File),
98-
raise('Elixir.CompileError', Message, MetaLocation);
123+
{File, Line} = meta_location(Meta, File),
124+
raise('Elixir.CompileError', Message, [{file, File}, {line, Line}]);
99125
compile_error(Meta, File, Message) when is_list(Message) ->
100-
MetaLocation = meta_location(Meta, File),
101-
raise('Elixir.CompileError', elixir_utils:characters_to_binary(Message), MetaLocation).
126+
{File, Line} = meta_location(Meta, File),
127+
raise('Elixir.CompileError', elixir_utils:characters_to_binary(Message), [{file, File}, {line, Line}]).
102128

103129
%% Tokenization parsing/errors.
104130

@@ -210,21 +236,6 @@ snippet(InputString, Location, StartLine, StartColumn) ->
210236

211237
%% Helpers
212238

213-
send_diagnostic(Severity, Position, File, Message) ->
214-
case get(elixir_compiler_info) of
215-
undefined -> ok;
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}
225-
end,
226-
ok.
227-
228239
prefix(warning) ->
229240
case application:get_env(elixir, ansi_enabled, false) of
230241
true -> <<"\e[33mwarning: \e[0m">>;
@@ -237,33 +248,35 @@ prefix(error) ->
237248
end.
238249

239250
env_format(Meta, #{file := EnvFile} = E) ->
240-
[{file, File}, {line, Line}] = meta_location(Meta, EnvFile),
251+
{File, Line} = meta_location(Meta, EnvFile),
241252

242-
Location =
253+
Stacktrace =
243254
case E of
244255
#{function := {Name, Arity}, module := Module} ->
245-
[file_format(Line, File), ": ", 'Elixir.Exception':format_mfa(Module, Name, Arity)];
256+
[{Module, Name, Arity, [{file, elixir_utils:relative_to_cwd(File)}, {line, Line}]}];
246257
#{module := Module} when Module /= nil ->
247-
[file_format(Line, File), ": ", elixir_aliases:inspect(Module)];
258+
[{Module, '__MODULE__', 0, [{file, elixir_utils:relative_to_cwd(File)}, {line, Line}]}];
248259
#{} ->
249-
file_format(Line, File)
260+
[]
250261
end,
251262

252-
{Line, File, Location}.
263+
{Line, File, Stacktrace}.
253264

265+
file_format(_, nil) ->
266+
"";
254267
file_format({0, _Column}, File) ->
255-
elixir_utils:relative_to_cwd(File);
268+
["\n ", elixir_utils:relative_to_cwd(File)];
256269
file_format({Line, Column}, File) ->
257-
io_lib:format("~ts:~w:~w", [elixir_utils:relative_to_cwd(File), Line, Column]);
270+
io_lib:format("\n ~ts:~w:~w", [elixir_utils:relative_to_cwd(File), Line, Column]);
258271
file_format(0, File) ->
259-
elixir_utils:relative_to_cwd(File);
272+
["\n ", elixir_utils:relative_to_cwd(File)];
260273
file_format(Line, File) ->
261-
io_lib:format("~ts:~w", [elixir_utils:relative_to_cwd(File), Line]).
274+
io_lib:format("\n ~ts:~w", [elixir_utils:relative_to_cwd(File), Line]).
262275

263276
meta_location(Meta, File) ->
264277
case elixir_utils:meta_keep(Meta) of
265-
{F, L} -> [{file, F}, {line, L}];
266-
nil -> [{file, File}, {line, ?line(Meta)}]
278+
{F, L} -> {F, L};
279+
nil -> {File, ?line(Meta)}
267280
end.
268281

269282
raise(Kind, Message, Opts) when is_binary(Message) ->

lib/elixir/test/elixir/module/types/integration_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -717,7 +717,7 @@ defmodule Module.Types.IntegrationTest do
717717

718718
warning = """
719719
warning: A.a/0 is deprecated. oops
720-
b.ex:3: B
720+
b.ex:3: B (module)
721721
722722
"""
723723

0 commit comments

Comments
 (0)