Hello.
I’m reading elixir-lang/elixir for my study. I found a strange point in ExUnit.
That’s this.
defmacro assert({:=, meta, [left, right]} = assertion) do
code = escape_quoted(:assert, meta, assertion)
# If the match works, we need to check if the value
# is not nil nor false. We need to rewrite the if
# to avoid silly warnings though.
check =
suppress_warning(
quote do
case right do
x when x in [nil, false] ->
raise ExUnit.AssertionError,
expr: expr,
message: "Expected truthy, got #{inspect(right)}"
_ ->
:ok
end
end
)
__match__(left, right, code, check, __CALLER__)
end
I think the right variable should be unquoted.
defmacro my_if(condition, clauses) do
do_clause = Keyword.get(clauses, :do, nil)
else_clause = Keyword.get(clauses, :else, nil)
quote do
case unquote(condition) do
x when x in [false, nil] ->
unquote(else_clause)
_ ->
unquote(do_clause)
end
end
end
This is my_if function which is written in a book. I don’t know why both my_if and the assert work.
This clause of assert handles the pattern matching case like
assert foo = bar
where foo is a pattern and bar can be anything.
Remember that atoms keep as is in the code and in the AST, and false and nil are atoms. So, if right is literallynil or false in the code, it is nil or false in the AST, too. And if right is nil or false, the assertion will definitely fail because pattern matching using = always returns the right hand side if it matches the left hand side, or raises an error if it does not match. So, in this specific case, unquote(right) should not be used.
The other cases of pattern matching when right is not literallyfalse or nil is handled by the line
def __match__(left, right, code, check, caller) do
...
match_expr = ... # It's an AST!!
quote do
right = unquote(right) #<---- This line does the trick!
expr = unquote(code)
unquote(vars) = unquote(match_expr)
right
end
end
When you try to perform a match that fails using = then a MatchError exception will be raised. For example if you try {x} = 1 in iex you’ll get a match exception. That’s the normal expected behaviour.
But …
In a test where we want to assert that a match occured and it doesn’t then we don’t want the match exception, we want a better error message. So what I think the code is doing is rewriting the match-and-bind expression into a match?/2 call.
That is, its re-writing {x} = 1 into something like match?({x}, 1). That way the assertion will get a Boolean result and it can then accept or refute the assertion and return a better error message.