Proposal: use name of the defined guard in error reporting

52 views
Skip to first unread message

Artur Plysyuk

unread,
Mar 30, 2020, 3:57:34 PM3/30/20
to elixir-lang-core
Let's say we have this module:

defmodule MyModule do
  defguard is_some_complex_item(item) when is_struct(item) or is_atom(item)

  def id(%{a: a, b: b} = item) when is_some_complex_item(a) and is_some_complex_item(b) do
    item
  end
end
 
 then invoking MyModule.id(1) produces the following error:

** (FunctionClauseError) no function clause matching in MyModule.id/1   
   
    The following arguments were given to MyModule.id/1:
   
        # 1
        1
   
    Attempted function clauses (showing 1 out of 1):
   
        def id(%{a: a, b: b} = item) when (is_map(a) and is_map_key(:__struct__, a) and is_atom(map_get(:__struct__, a)) or is_atom(a)) and (is_map(b) and is_map_key(:__struct__, b) and is_atom(map_get(:__struct__, b)) or is_atom(b))


The issue here is that internal implementation of my custom guard and even built-in is_struct is shown.
It is not easy to debug this kind of messages. My expectation is to see not expanded macro in guards.

Christopher Keele

unread,
Mar 31, 2020, 1:15:52 PM3/31/20
to elixir-lang-core
The issue here is that internal implementation of my custom guard and even built-in is_struct is shown.
It is not easy to debug this kind of messages. My expectation is to see not expanded macro in guards.

Correct me if I'm wrong, but isn't this not just specific to custom guards? As far as I know, all macros behave this way because macro expansion happens at compile time, and this sort of runtime-error within the generated code can't know about the macro that produced it without some sort of new compiler artifact to track the source?

Wojtek Mach

unread,
Mar 31, 2020, 1:30:15 PM3/31/20
to elixir-l...@googlegroups.com
Correct me if I'm wrong, but isn't this not just specific to custom guards? As far as I know, all macros behave this way because macro expansion happens at compile time, and this sort of runtime-error within the generated code can't know about the macro that produced it without some sort of new compiler artifact to track the source?

I think so too, similarly for:

    defmodule Foo do
      def foo(x) when x in 1..5 do
        x
      end

      def foo(x) when x in [10, 20] do
        x
      end
    end

    Foo.foo(100)

We have:

     ** (FunctionClauseError) no function clause matching in Foo.foo/1

     The following arguments were given to Foo.foo/1:

         # 1
         100

     Attempted function clauses (showing 2 out of 2):

         def foo(x) when is_integer(x) and (x >= 1 and x <= 5)
         def foo(x) when x === 10 or x === 20

     code: Foo.foo(100)
     stacktrace:
       (foo 0.1.0) lib/foo.ex:2: Foo.foo/1
       test/foo_test.exs:5: (test)

It would be nice to see the original guards though!

Christopher Keele

unread,
Mar 31, 2020, 2:45:51 PM3/31/20
to elixir-lang-core
It would be nice to see the original guards though!

That an interesting thought. For most macros, showing the source macro code would not be very helpful; but defguard has sufficiently constrained inputs for that not to be the case. So there definitely are some defguard-specific opportunities in this proposal.

I can't think of a great way to implement it:

  a. We'd need a macro-expansion-tracing compile-time metadata format similar to debug symbols
  b. We'd need to make the runtime exception reporting mechanisms aware of this metadata

That seems heavy. I'm not creative enough to think of a better solution, in the face of these constraints:

  a. Without radically changing the implementation of defguard as a macro, we'd have to implement metadata capture for all macros generically
    - This raises questions of how to handle nested macro expansion in these scenarios
  b. We can't add extra wrappers around just the product of defguards themselves, as their inputs are too constrained to do any sort of outputting or error handling, so the exception formatters themselves would have to do reporting on the metatdata of the original macro
    - Again, how would this report if several macros produced the generated code

There are probably approaches I'm missing here, though.
Reply all
Reply to author
Forward
0 new messages