DidactMacros

DidactMacros

Regarding evaluation of arguments to ExUnit.Assertions.assert/2

This pertains to when a call is made to the function ExUnit.Assertions.assert/2 from within ExUnit.Assertions.translate_operator/6 (matched against the equality_check: false argument-set), and the first argument sent to it is unquote(call).

The call made to this function is from within a quote do: block, and the first argument sent is unquote(call), with the second being a keyword list.

In the ExUnit.Assertions.assert/2 function there is an unless expression that is to execute its code in the event that unquote(call), matched to the variable value, evaluates to false.

How can value ever be false?

When does unquote(call) have a chance to be evaluated?

Code for reference:-

assert/1

  defmacro assert(assertion) do
    if translated = translate_assertion(:assert, assertion, __CALLER__) do
      translated
    else
      {args, value} = extract_args(assertion, __CALLER__)

      quote generated: true do
        if value = unquote(value) do
          value
        else
          raise ExUnit.AssertionError,
            args: unquote(args),
            expr: unquote(escape_quoted(:assert, [], assertion)),
            message: "Expected truthy, got #{inspect(value)}"
        end
      end
    end
  end

translate_assertion

  defp translate_assertion(:assert, {operator, meta, [_, _]} = expr, caller)
       when operator in @operator do
    if match?([{_, Kernel}], Macro.Env.lookup_import(caller, {operator, 2})) do
      left = Macro.var(:left, __MODULE__)
      right = Macro.var(:right, __MODULE__)
      call = {operator, meta, [left, right]}
      equality_check? = operator in [:<, :>, :!==, :!=]
      message = "Assertion with #{operator} failed"
      translate_operator(:assert, expr, call, message, equality_check?, caller)
    end
  end

translate_operator

  defp translate_operator(kind, {op, meta, [left, right]} = expr, call, message, false, _caller) do
    expr = escape_quoted(kind, meta, expr)
    context = if op in [:===, :!==], do: :===, else: :==

    quote do
      left = unquote(left)
      right = unquote(right)

      ExUnit.Assertions.assert(unquote(call),
        left: left,
        right: right,
        expr: unquote(expr),
        message: unquote(message),
        context: unquote(context)
      )
    end
  end

assert/2

  def assert(value, opts) when is_list(opts) do
    unless value, do: raise(ExUnit.AssertionError, opts)
    true
  end

Marked As Solved

DidactMacros

DidactMacros

The quote dounquote is located in a def do: block which is quoted, so the unquote is in a nested quote which means that is quoted rather than executed.

  defp translate_operator(kind, {op, meta, [left, right]} = expr, call, message, false, _caller) do
    expr = escape_quoted(kind, meta, expr)
    context = if op in [:===, :!==], do: :===, else: :==

    quote do
      left = unquote(left)
      right = unquote(right)

      ExUnit.Assertions.assert(unquote(call),
        left: left,
        right: right,
        expr: unquote(expr),
        message: unquote(message),
        context: unquote(context)
      )
    end
  end

Results in…

    quote do
      left = unquote(left)
      right = unquote(right)

      ExUnit.Assertions.assert(unquote(call),
        left: left,
        right: right,
        expr: unquote(expr),
        message: unquote(message),
        context: unquote(context)
      )
    end

…getting sent back to the macro that started the chain of function calls.

The macro will then subject the AST to a macro.escape-like processing that identifies :unquote operators in the AST and does not escape the args (children), leaving them in their current form with :unquote stripped or generating a valid AST to describe them. This occurs because the returned AST from translate_operator is the last reachable expression in the macro, and so it must be validated as a formal AST.

The macro will then insert this valid AST fragment into the over all AST at the location were it (the macro, defmacro assert(assertion)) was called.

I initial thought that evaluation was taking place at unquote and at escape, however this was corrected:

If anyone has an question about this I’d be happy to go into further detail.

If I got anything wrong please let me know.

Also Liked

al2o3cr

al2o3cr

Code like:

assert 3 * 14 == 6 * 7

will expand to:

      left = 3 * 14
      right = 6 * 7

      ExUnit.Assertions.assert(left == right,
        left: left,
        right: right,
        expr: #<ast omitted>,
        message: "Assertion with operator == failed",
        context: #<__CALLER__ omitted>
      )
al2o3cr

al2o3cr

Functions like unq would need to be called from inside a defmacro to get the AST they generate turned into executable code.

Where Next?

Popular in Questions Top

siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
earth10
Hi, I’m just starting to build a side-project with Elixir and Phoenix and doing some basic test with Elixir alone. What strikes me is th...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
Emily
I have VueJS GUIs with the project generated using Webpack. I have Elixir modules that will need to be used by the VueJS GUIs. I forese...
New
yawaramin
In the Dialyzer docs ( dialyzer — OTP 29.0.2 (dialyzer 6.0.1) ), there is a way to turn off a specific warning for a function: -dialyzer...
New
PeterCarter
There are pre-rolled solutions for other frameworks that do work. However, Phoenix does not seem to have these. Have people had good expe...
New
jononomo
For some reason my phoenix channels are working for me in my local dev environment, but as soon as I deploy via Docker, I get a 403 error...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New

Other popular topics Top

WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
274 41539 114
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 43622 214
New
JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1144 53690 245
New
Lily
In templates/appointment/index.html.eex: &lt;%= for appointment &lt;- @appointments do %&gt; &lt;tr&gt; &lt;td&gt;&lt;%= appoi...
New
hariharasudhan94
lets say i have a sample like a = 20; b = 10; if (a &gt; b) do {:ok, "a"} end if (a &lt; b) do {:ok, b} end if (a == b) do {:ok, "equa...
New
aesmail
Hello guys, I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the curre...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
AstonJ
We’ve put together this wiki for Phoenix LiveView - please feel free to add any info you feel is worth including. What is Phoenix LiveV...
New

Latest on Elixir Forum

We're in Beta

About us Mission Statement