Having trouble with: (CompileError) assertion_issue.exs:136: undefined function is_nil/0

Hello. Can someone explain why I get the following compile error?
Thanks

iex(1)> c "assertion_issue.exs"                                                                             
                                                                                                            
== Compilation error in file assertion_issue.exs ==                                                         
** (CompileError) assertion_issue.exs:136: undefined function is_nil/0                                      
    (elixir 1.11.3) src/elixir_locals.erl:114: anonymous fn/3 in :elixir_locals.ensure_no_undefined_local/3 
    assertion_issue.exs:129: (file)                                                                         
** (CompileError)  compile error                                                                            
    (iex 1.11.3) lib/iex/helpers.ex:200: IEx.Helpers.c/2                                                    
iex(1)>                                                                                                     

I forgot to mention that the source code is found here:

there is not such a fucntion in_nil/0,
what you meant is is_nil/1

Looking at your code,
replace.

assert elem(nil_tuple, 0) |> is_nil()

with

assert is_nil(elem(nil_tuple, 0))

or

assert (elem(nil_tuple, 0) |> is_nil())

3 Likes

The assert head responsible for matching functions on line 26 gets this AST as an argument:

{:|>, [line: 136],
 [
   {:elem, [line: 136], [{:nil_tuple, [line: 136], nil}, 0]},
   {:is_nil, [line: 136], []}
 ]}

That’s why you’re getting an error trying to evaluate is_nil/0, because |> doesn’t decompose neatly into “left hand” and “right hand” operands.

FWIW, ex_unit's version is more explicit about only handling comparison operators:

2 Likes

Thanks. However I pipe in is_nil/1 the result of elem(nil_tuple, 0) and therefore I wonder why the compiler believes is_nil/0 is being used. When I try the same code i.e. assert elem(nil_tuple, 0) |> is_nil() it works.
When I try assert is_nil(elem(nil_tuple, 0)) (your 1st suggestion above) it works. When I try assert (elem(nil_tuple, 0) |> is_nil()) (your 2nd suggestion above) I get the same error message.
And lastly, when I try elem(nil_tuple, 0) |> is_nil() |> assert() it works!

So the issue remains unexplained as far as I am concerned.

2 Likes
  • I meant to say:
    When I try the same code in ExUnit i.e. assert elem(nil_tuple, 0) |> is_nil() it works.

Sorry, I thought you were working directly with ExUnit.

As @al2o3cr mentioned, you need to limit the operators that you want to support in your assertion.

Read the way ExUnit deals with them, the code has been linked above. See how those two private functions are implemented, translate_assertion/2 and extract_args/2.

Also a main missing part is that is you are not dealing with assertions where there is not left and right hand side, like the line that is raising.

You do a series of pattern matches, and if nothing matched, you pass by writing “.”.

this will pass: assert is_nil(:not_nil) (WRONG)
this will not: assert is_nil(:not_nil) == true (GOOD)

I fixed the above compilation error (repeated below) by calling Macro.expand(ast, __CALLER__) at the beginning of the Assertion.assert/1 macro.

== Compilation error in file assertion_issue.exs ==                                                         
** (CompileError) assertion_issue.exs:136: undefined function is_nil/0

Nevertheless I believe this should be handled automatically by the quote bind_quoted: or unquote macros themselves without any additional intervention.

After all, isn’t it strange that this snippet would fail to compile:

nil_tuple = {nil}
assert elem(nil_tuple, 0) |> is_nil()

while this one would compile fine!!

nil_tupel = {nil}
assertion = elem(nil_tuple, 0) |> is_nil()
assert assertion

Just saying…

It’s not weird. They are not the same thing.

This code:

assert elem(nil_tuple, 0) |> is_nil()

passes the AST of elem(nil_tuple, 0) |> is_nil() to assert.

This code, on the other hand:

assertion = elem(nil_tuple, 0) |> is_nil()
assert assertion

passes the AST of the local variable access to assertion ({:assertion, [line: NNN], nil})

The binding of assertion to a particular value at runtime is not available at macro-expansion time.

2 Likes

Thanks al2o3cr. I realize what you’re saying when “looking under the hood”. But a newbie would conclude that to make this code work you have to somehow use a temporary variable as opposed to passing the expression directly to assert.