A problem with pattern-generating macros

I am trying to build a simple macro to reduce some repetitive code:

defmodule Example do
    defguard is_ok(x) when x == :ok or (is_tuple(x) and elem(x, 0) == :ok)

  defmacro ok() do
    quote do
      x when is_ok(x)
    end
  end
end

which could be used like this:

iex> require Example
iex> import Example
iex> # so far, so good...
iex> res = case {:ok, "Test"} do
           ok() -> "Yay!"
          _ -> "Failure"

(or also in a with-ladder, where it is arguably more useful).

However, instead of res becoming "Yay!", the following error is thrown:

** (CompileError) nofile:1: undefined function when/2
    (example) expanding macro: Example.ok/0

I have not been able to figure out what is going wrong here. I know for a fact that the guard is_ok is correct (you can test it yourself). Writing x when is_ok(x) the long-hand in the case-statement also works as intended.
It seems like it is a problem with the way the macro is expanded, but where it is going wrong, I cannot figure out.

EDIT: Even stranger, when writing a separate module to use it:

defmodule Bar do
  require Example
  import Example
  def foo(x) do
    case x do
      ok() -> "Yay"
      _ -> "Fail"
    end
  end
end

and attempting to compile the project, the following compilation error is thrown:

== Compilation error in file lib/bar.ex ==
** (CompileError) lib/bar.ex:6: cannot find or invoke local when/2 inside match. Only macros can be invoked in a match and they must be defined before their invocation. Called as: x when is_ok(x)
    expanding macro: Example.ok/0
    lib/bar.ex:6: Bar.foo/1
    (elixir) lib/kernel/parallel_compiler.ex:208: anonymous fn/4 in Kernel.ParallelCompiler.spawn_workers/6

Help is greatly appreciated :slight_smile: !

From comparing the full case and only the match quoted expression on iex via ssh on a mobile, it seems as if you need to return a list from the macro.

[quote do x when is_ok(x) end]
1 Like

From comparing the full case and only the match quoted expression on iex via ssh on a mobile

Wow, dedication :smile:!

Unfortunately, even with replacing the ok() macro with

  defmacro ok() do
    [quote do x when is_ok(x) end]
  end

the results are the exact same errors.
I have the feeling that when is not recognized properly, and that it might be treated as some special case by the compiler.

I have posted an issue on the Elixir repository, because I think that this might be a bug in the way macros are expanded.