@@ -223,35 +223,46 @@ defmodule Exception do
223223 @ doc """
224224 Receives a tuple representing a stacktrace entry and formats it.
225225 """
226- def format_stacktrace_entry ( entry )
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
237+ """
227238
228239 # From Macro.Env.stacktrace
229- def format_stacktrace_entry ( { module , :__MODULE__ , 0 , location } ) do
230- format_location ( location ) <> inspect ( module ) <> " (module)"
240+ def format_stacktrace_entry_into_fields ( { module , :__MODULE__ , 0 , location } ) do
241+ { nil , format_location ( location ) , inspect ( module ) <> " (module)" }
231242 end
232243
233244 # From :elixir_compiler_*
234- def format_stacktrace_entry ( { _module , :__MODULE__ , 1 , location } ) do
235- format_location ( location ) <> "(module)"
245+ def format_stacktrace_entry_into_fields ( { _module , :__MODULE__ , 1 , location } ) do
246+ { nil , format_location ( location ) , "(module)" }
236247 end
237248
238249 # From :elixir_compiler_*
239- def format_stacktrace_entry ( { _module , :__FILE__ , 1 , location } ) do
240- format_location ( location ) <> "(file)"
250+ def format_stacktrace_entry_into_fields ( { _module , :__FILE__ , 1 , location } ) do
251+ { nil , format_location ( location ) , "(file)" }
241252 end
242253
243- def format_stacktrace_entry ( { module , fun , arity , location } ) do
244- format_application ( module ) <> format_location ( location ) <> format_mfa ( module , fun , arity )
254+ def format_stacktrace_entry_into_fields ( { module , fun , arity , location } ) do
255+ { format_application ( module ) , format_location ( location ) , format_mfa ( module , fun , arity ) }
245256 end
246257
247- def format_stacktrace_entry ( { fun , arity , location } ) do
248- format_location ( location ) <> format_fa ( fun , arity )
258+ def format_stacktrace_entry_into_fields ( { fun , arity , location } ) do
259+ { nil , format_location ( location ) , format_fa ( fun , arity ) }
249260 end
250261
251262 defp format_application ( module ) do
252263 case :application . get_application ( module ) do
253- { :ok , app } -> "(" <> atom_to_binary ( app ) <> ") "
254- :undefined -> ""
264+ { :ok , app } -> "(" <> atom_to_binary ( app ) <> ")"
265+ :undefined -> nil
255266 end
256267 end
257268
@@ -268,7 +279,6 @@ defmodule Exception do
268279 catch
269280 :stacktrace -> Enum . drop ( :erlang . get_stacktrace , 1 )
270281 end
271-
272282 case trace do
273283 [ ] -> "\n "
274284 s -> " " <> Enum . map_join ( s , "\n " , & format_stacktrace_entry ( & 1 ) ) <> "\n "
@@ -309,74 +319,97 @@ defmodule Exception do
309319
310320 """
311321 def format_fa ( fun , arity ) do
312- if is_list ( arity ) do
313- inspected = lc x inlist arity , do: inspect ( x )
314- "#{ inspect fun } (#{ Enum . join ( inspected , ", " ) } )"
315- else
316- "#{ inspect fun } /#{ arity } "
317- end
322+ "#{ inspect fun } #{ format_arity ( arity ) } "
318323 end
319324
320325 @ doc """
321326 Receives a module, fun and arity and formats it
322327 as shown in stacktraces. The arity may also be a list
323328 of arguments.
324-
329+
325330 ## Examples
326-
327331 iex> Exception.format_mfa Foo, :bar, 1
328332 "Foo.bar/1"
329333 iex> Exception.format_mfa Foo, :bar, []
330334 "Foo.bar()"
331335 iex> Exception.format_mfa nil, :bar, []
332336 "nil.bar()"
333337
338+ Anonymous functions are reported as -func/arity-anonfn-count-,
339+ where func is the name of the enclosing function. Convert to
340+ "nth fn in func/arity"
334341 """
335- def format_mfa ( module , fun , arity ) do
336- fun =
337- case inspect ( fun ) do
338- << ?: , erl :: binary >> -> erl
339- elixir -> elixir
340- end
341342
342- if is_list ( arity ) do
343- inspected = lc x inlist arity , do: inspect ( x )
344- "#{ inspect module } .#{ fun } (#{ Enum . join ( inspected , ", " ) } )"
345- else
346- "#{ inspect module } .#{ fun } /#{ arity } "
347- end
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 } "
348355 end
349356
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 ) } "
363+ end
364+
365+ defp format_arity ( arity ) when is_list ( arity ) do
366+ inspected = lc x inlist arity , do: inspect ( x )
367+ "(#{ Enum . join ( inspected , ", " ) } )"
368+ end
369+
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+
377+
350378 @ doc """
351379 Formats the given file and line as shown in stacktraces.
352- If any of the values are nil, they are omitted.
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.
353383
354384 ## Examples
355385
356386 iex> Exception.format_file_line("foo", 1)
357387 "foo:1: "
358388
389+ iex> Exception.format_file_line("foo", 1, "")
390+ "foo:1:"
391+
359392 iex> Exception.format_file_line("foo", nil)
360393 "foo: "
361394
362395 iex> Exception.format_file_line(nil, nil)
363396 ""
364397
365398 """
366- def format_file_line ( file , line ) do
399+ def format_file_line ( file , line , suffix // " " ) do
367400 if file do
368401 if line && line != 0 do
369- "#{ file } :#{ line } : "
402+ "#{ file } :#{ line } :#{ suffix } "
370403 else
371- "#{ file } : "
404+ "#{ file } :#{ suffix } "
372405 end
373406 else
374407 ""
375408 end
376409 end
377410
378411 defp format_location ( opts ) do
379- format_file_line Keyword . get ( opts , :file ) , Keyword . get ( opts , :line )
412+ format_file_line Keyword . get ( opts , :file ) , Keyword . get ( opts , :line ) , ""
380413 end
381414
382415 defp from_stacktrace ( [ { module , function , args , _ } | _ ] ) when is_list ( args ) do
@@ -390,4 +423,50 @@ defmodule Exception do
390423 defp from_stacktrace ( _ ) do
391424 { nil , nil , nil }
392425 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+
393472end
0 commit comments