Skip to content

Commit cf84ed3

Browse files
author
José Valim
committed
Only justify applications in IEx stacktraces
1 parent 95de5ba commit cf84ed3

File tree

8 files changed

+102
-238
lines changed

8 files changed

+102
-238
lines changed

lib/elixir/lib/exception.ex

Lines changed: 52 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,21 @@ defexception SystemLimitError, message: "a system limit has been reached"
1010
defexception SyntaxError, [file: nil, line: nil, description: "syntax error"] do
1111
def message(exception) do
1212
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
13-
exception.description
13+
" " <> exception.description
1414
end
1515
end
1616

1717
defexception TokenMissingError, [file: nil, line: nil, description: "expression is incomplete"] do
1818
def message(exception) do
1919
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
20-
exception.description
20+
" " <> exception.description
2121
end
2222
end
2323

2424
defexception CompileError, [file: nil, line: nil, description: "compile error"] do
2525
def message(exception) do
2626
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
27-
exception.description
27+
" " <> exception.description
2828
end
2929
end
3030

@@ -149,6 +149,16 @@ defmodule Exception do
149149
pass the `System.stacktrace` as argument.
150150
"""
151151

152+
@type stacktrace :: [stacktrace_entry]
153+
154+
@type stacktrace_entry ::
155+
{ module, function, arity_or_args, location } |
156+
{ function, arity_or_args, location }
157+
158+
@typep function :: atom
159+
@typep arity_or_args :: non_neg_integer | list
160+
@typep location :: Keyword.t
161+
152162
@doc """
153163
Normalizes an exception, converting Erlang exceptions
154164
to Elixir exceptions.
@@ -221,48 +231,38 @@ defmodule Exception do
221231
end
222232

223233
@doc """
224-
Receives a tuple representing a stacktrace entry and formats it.
225-
"""
226-
def format_stacktrace_entry(entry) do
227-
format_stacktrace_entry_into_fields(entry)
228-
|> tuple_to_list
229-
|> Enum.filter(fn field -> field && field != "" end)
230-
|> Enum.join(" ")
231-
end
232-
233-
@doc """
234-
Returns the fields from a single frame in a stack trace as a list of
235-
`[ app, location, mfa/module/file ]` where all but location can be nil.
236-
Intended for use inside the Elixir libraries and iex only
234+
Receives an stacktrace entry and formats it into a string.
237235
"""
236+
@spec format_stacktrace_entry(stacktrace_entry) :: String.t
237+
def format_stacktrace_entry(entry)
238238

239239
# From Macro.Env.stacktrace
240-
def format_stacktrace_entry_into_fields({ module, :__MODULE__, 0, location }) do
241-
{ nil, format_location(location), inspect(module) <> " (module)" }
240+
def format_stacktrace_entry({ module, :__MODULE__, 0, location }) do
241+
format_location(location) <> inspect(module) <> " (module)"
242242
end
243243

244244
# From :elixir_compiler_*
245-
def format_stacktrace_entry_into_fields({ _module, :__MODULE__, 1, location }) do
246-
{ nil, format_location(location), "(module)" }
245+
def format_stacktrace_entry({ _module, :__MODULE__, 1, location }) do
246+
format_location(location) <> "(module)"
247247
end
248248

249249
# From :elixir_compiler_*
250-
def format_stacktrace_entry_into_fields({ _module, :__FILE__, 1, location }) do
251-
{ nil, format_location(location), "(file)" }
250+
def format_stacktrace_entry({ _module, :__FILE__, 1, location }) do
251+
format_location(location) <> "(file)"
252252
end
253253

254-
def format_stacktrace_entry_into_fields({module, fun, arity, location}) do
255-
{ format_application(module), format_location(location), format_mfa(module, fun, arity) }
254+
def format_stacktrace_entry({module, fun, arity, location}) do
255+
format_application(module) <> format_location(location) <> format_mfa(module, fun, arity)
256256
end
257257

258-
def format_stacktrace_entry_into_fields({fun, arity, location}) do
259-
{ nil, format_location(location), format_fa(fun, arity) }
258+
def format_stacktrace_entry({fun, arity, location}) do
259+
format_location(location) <> format_fa(fun, arity)
260260
end
261261

262262
defp format_application(module) do
263263
case :application.get_application(module) do
264-
{ :ok, app } -> "(" <> atom_to_binary(app) <> ")"
265-
:undefined -> nil
264+
{ :ok, app } -> "(" <> atom_to_binary(app) <> ") "
265+
:undefined -> ""
266266
end
267267
end
268268

@@ -285,29 +285,6 @@ defmodule Exception do
285285
end
286286
end
287287

288-
@doc """
289-
Formats the caller, i.e. the first entry in the stacktrace.
290-
291-
A stacktrace must be given as an argument. If not, this function
292-
calculates a new stacktrace based on the caller and formats it. As
293-
a consequence, the value of `System.stacktrace` is changed.
294-
295-
Notice that due to tail call optimization, the stacktrace
296-
may not report the direct caller of the function.
297-
"""
298-
def format_caller(trace \\ nil) do
299-
trace = trace || try do
300-
throw(:stacktrace)
301-
catch
302-
:stacktrace -> Enum.drop(:erlang.get_stacktrace, 2)
303-
end
304-
305-
case trace do
306-
[entry|_] -> format_stacktrace_entry(entry)
307-
_ -> "nofile:0: "
308-
end
309-
end
310-
311288
@doc """
312289
Receives an anonymous function and arity and formats it as
313290
shown in stacktraces. The arity may also be a list of arguments.
@@ -326,8 +303,9 @@ defmodule Exception do
326303
Receives a module, fun and arity and formats it
327304
as shown in stacktraces. The arity may also be a list
328305
of arguments.
329-
306+
330307
## Examples
308+
331309
iex> Exception.format_mfa Foo, :bar, 1
332310
"Foo.bar/1"
333311
iex> Exception.format_mfa Foo, :bar, []
@@ -337,66 +315,51 @@ defmodule Exception do
337315
338316
Anonymous functions are reported as -func/arity-anonfn-count-,
339317
where func is the name of the enclosing function. Convert to
340-
"nth fn in func/arity"
318+
"anonymous fn in func/arity"
341319
"""
320+
def format_mfa(module, fun, arity) when is_atom(fun) do
321+
fun =
322+
case inspect(fun) do
323+
":" <> fun -> fun
324+
fun -> fun
325+
end
342326

343-
def format_mfa(module, nil, arity),
344-
do: do_format_mfa(module, "nil", arity)
345-
346-
def format_mfa(module, fun, arity) when is_atom(fun),
347-
do: do_format_mfa(module, to_string(fun), arity)
348-
349-
defp do_format_mfa(module, fun, arity) when not(is_binary(fun)),
350-
do: format_mfa(module, inspect(fun), arity)
351-
352-
defp do_format_mfa(module, "-" <> fun, arity) do
353-
[ outer_fun, "fun", count, "" ] = String.split(fun, "-")
354-
"#{format_nth(count)} anonymous fn#{format_arity(arity)} in #{inspect module}.#{outer_fun}"
355-
end
356-
357-
# Erlang internal
358-
defp do_format_mfa(module, ":" <> fun, arity),
359-
do: format_mfa(module, maybe_quote_name(fun), arity)
360-
361-
defp do_format_mfa(module, fun, arity) do
362-
"#{inspect module}.#{maybe_quote_name(fun)}#{format_arity(arity)}"
327+
case match?("\"-" <> _, fun) and String.split(fun, "-") do
328+
[ "\"", outer_fun, "fun", _count, "\"" ] ->
329+
"anonymous fn#{format_arity(arity)} in #{inspect module}.#{outer_fun}"
330+
_ ->
331+
"#{inspect module}.#{fun}#{format_arity(arity)}"
332+
end
363333
end
364334

365335
defp format_arity(arity) when is_list(arity) do
366336
inspected = lc x inlist arity, do: inspect(x)
367337
"(#{Enum.join(inspected, ", ")})"
368338
end
369339

370-
defp format_arity(arity), do: "/#{arity}"
371-
372-
defp format_nth("0"), do: "first"
373-
defp format_nth("1"), do: "second"
374-
defp format_nth("2"), do: "third"
375-
defp format_nth(n), do: "#{binary_to_integer(n)+1}th"
376-
340+
defp format_arity(arity), do: "/#{arity}"
377341

378342
@doc """
379343
Formats the given file and line as shown in stacktraces.
380-
If any of the values are nil, they are omitted. If the
381-
optional suffix is omitted, a space is appended to
382-
the result.
344+
If any of the values are nil, they are omitted.
383345
384346
## Examples
385347
386348
iex> Exception.format_file_line("foo", 1)
387-
"foo:1: "
388-
389-
iex> Exception.format_file_line("foo", 1, "")
390349
"foo:1:"
391350
392351
iex> Exception.format_file_line("foo", nil)
393-
"foo: "
352+
"foo:"
394353
395354
iex> Exception.format_file_line(nil, nil)
396355
""
397356
398357
"""
399-
def format_file_line(file, line, suffix // " ") do
358+
def format_file_line(file, line) do
359+
format_file_line(file, line, "")
360+
end
361+
362+
defp format_file_line(file, line, suffix) do
400363
if file do
401364
if line && line != 0 do
402365
"#{file}:#{line}:#{suffix}"
@@ -409,7 +372,7 @@ defmodule Exception do
409372
end
410373

411374
defp format_location(opts) do
412-
format_file_line Keyword.get(opts, :file), Keyword.get(opts, :line), ""
375+
format_file_line Keyword.get(opts, :file), Keyword.get(opts, :line), " "
413376
end
414377

415378
defp from_stacktrace([{ module, function, args, _ }|_]) when is_list(args) do
@@ -423,50 +386,4 @@ defmodule Exception do
423386
defp from_stacktrace(_) do
424387
{ nil, nil, nil }
425388
end
426-
427-
428-
# have to use :re here because exceptions may be triggered before Regexp
429-
# module is compiled.
430-
@function_name_re :re.compile(
431-
%S{
432-
\A(
433-
[\w]+[?!]?
434-
| ->
435-
| <-
436-
| ::
437-
| \|{1,3}
438-
| =
439-
| &&&?
440-
| <=?
441-
| >=?
442-
| ===?
443-
| !==?
444-
| =~
445-
| <<<
446-
| >>>
447-
| \+\+?
448-
| --?
449-
| <>
450-
| \+
451-
| -
452-
| \*
453-
| //?
454-
| ^^^
455-
| !
456-
| \^
457-
| &
458-
| ~~~
459-
| @
460-
)\z}, [:extended] )
461-
462-
defp maybe_quote_name(fun) do
463-
name = to_string(fun)
464-
{:ok, re} = @function_name_re
465-
case :re.run(name, re) do
466-
{ :match, _} -> name
467-
_ -> inspect name
468-
end
469-
end
470-
471-
472389
end

lib/elixir/lib/module.ex

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -777,8 +777,10 @@ defmodule Module do
777777
cond do
778778
:lists.member(key, acc) ->
779779
[]
780-
is_list(warn) ->
781-
:elixir_errors.warn Exception.format_caller(warn), "undefined module attribute @#{key}, " <>
780+
is_list(warn) && (entry = List.first(warn)) ->
781+
opts = elem(entry, size(entry) - 1)
782+
info = Exception.format_file_line(Keyword.get(opts, :file), Keyword.get(opts, :line))
783+
:elixir_errors.warn info, "undefined module attribute @#{key}, " <>
782784
"please remove access to @#{key} or explicitly set it to nil before access\n"
783785
nil
784786
true ->

lib/elixir/test/elixir/exception_test.exs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ defmodule Kernel.ExceptionTest do
6262
assert Exception.format_mfa(nil, :bar, []) == "nil.bar()"
6363
assert Exception.format_mfa(:foo, :bar, [1, 2]) == ":foo.bar(1, 2)"
6464
assert Exception.format_mfa(Foo, :"bar baz", 1) == "Foo.\"bar baz\"/1"
65+
assert Exception.format_mfa(Foo, :"-func/2-fun-0-", 4) == "anonymous fn/4 in Foo.func/2"
6566
end
6667

6768
test "format_fa" do
@@ -104,7 +105,7 @@ defmodule Kernel.ExceptionTest do
104105
end
105106
file = __ENV__.file |> Path.relative_to_cwd |> String.to_char_list!
106107
assert {Kernel.ExceptionTest, :"test raise preserves the stacktrace", _,
107-
[file: ^file, line: 100]} = stacktrace
108+
[file: ^file, line: 101]} = stacktrace
108109
end
109110

110111
test "defexception" do

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ defmodule Kernel.WarningTest do
334334
test :undefined_module_attribute_with_file do
335335
assert capture_err(fn ->
336336
Code.load_file(fixture_path("attribute_warning.ex"))
337-
end) =~ "attribute_warning.ex:2: AttributeWarning (module) warning: undefined module attribute @foo, please remove access to @foo or explicitly set it to nil before access"
337+
end) =~ "attribute_warning.ex:2: warning: undefined module attribute @foo, please remove access to @foo or explicitly set it to nil before access"
338338
after
339339
purge AttributeWarning
340340
end

0 commit comments

Comments
 (0)