Elixir: ExUnit `assert` using macros not retaining new variables

Created on 12 Oct 2017  路  2Comments  路  Source: elixir-lang/elixir

Environment

  • Erlang 19.1, Elixir 1.4.2
  • Operating system: OSX 10.10

Background

The ~M sigil from ShorterMaps expands ~M{a, b} => %{a: a, b: b}.

Current behavior

The following ExUnit test fails:

  test "that fails" do
    assert {:a, ~M{b}} = {:a, %{b: 2}} # LHS expands to {:a, %{b: b}} 
    assert b == 2 # <= crashing line
  end

```

mix test
warning: variable "b" does not exist and is being expanded to "b()", please use parentheses to remove the ambiguity or change the variable name
test/shorter_maps_test.exs:7

warning: variable "b" is unused
test/shorter_maps_test.exs:6

** (CompileError) test/shorter_maps_test.exs:7: undefined function b/0
(stdlib) lists.erl:1338: :lists.foreach/2
(stdlib) erl_eval.erl:670: :erl_eval.do_apply/6
(elixir) lib/code.ex:370: Code.require_file/2
(elixir) lib/kernel/parallel_require.ex:57: anonymous fn/2 in Kernel.ParallelRequire.spawn_requires/5

and these tests pass:

```elixir
  test "passes" do
    assert ~M{b} = %{b: 2} # here the sigil is just not wrapped in a tuple
    assert b == 2
  end
  test "another passing test" do
    assert {:a, %{b: b}} = {:a, %{b: 2}} # this is the manually expanded LHS
    assert b == 2
  end

In the failing test, the sigil has been wrapped inside a tuple; the sigil expands and binds properly, note the warning variable "b" is unused.

Expected behavior

assert/2 should preserve all created variables in the LHS code block, even if they are in a nested datastructure.

This is an elevation of meyercm/shorter_maps#11 as the issue seems to be in assert.

ExUnit Bug Advanced

Most helpful comment

Sorry, should have thought to simplify the macro portion of the problem.

defmodule MinimalMacroTest do
  use ExUnit.Case

  defmodule MinimalMacro do
    defmacro sigil_l({:<<>>, _, [string]}, _), do: Code.string_to_quoted!(string, [])
  end

  test "smaller macro" do
    import MinimalMacro
    assert ~l(a) = 1     # just the macro
    assert a == 1        # this works
    assert {~l(b)} = {2} # add a tuple wrapper
    assert b == 2        # crashes here
  end
end

All 2 comments

Thank you @meyercm! Can you please isolate the issue a bit further by providing a minimal macro that introduces the mentioned problems?

Sorry, should have thought to simplify the macro portion of the problem.

defmodule MinimalMacroTest do
  use ExUnit.Case

  defmodule MinimalMacro do
    defmacro sigil_l({:<<>>, _, [string]}, _), do: Code.string_to_quoted!(string, [])
  end

  test "smaller macro" do
    import MinimalMacro
    assert ~l(a) = 1     # just the macro
    assert a == 1        # this works
    assert {~l(b)} = {2} # add a tuple wrapper
    assert b == 2        # crashes here
  end
end
Was this page helpful?
0 / 5 - 0 ratings

Related issues

dmorneau picture dmorneau  路  30Comments

jakubpawlowicz picture jakubpawlowicz  路  32Comments

josevalim picture josevalim  路  41Comments

devonestes picture devonestes  路  33Comments

josevalim picture josevalim  路  27Comments