Skip to content

Weird parsing of binary operation with unary operation operands with blocks #15849

Description

@yastanotheruser

Existing issue

  • I have searched existing issues and could not find a duplicate.

Elixir and Erlang/OTP versions

Erlang/OTP 29 [erts-17.0.6] [source] [32-bit] [smp:2:2] [ds:2:2:10] [async-threads:1]
Elixir 1.20.4 (compiled with Erlang/OTP 29)

Operating system

Alpine Linux

Current behavior

Hi! I tried searching by "unary" and did find some issues but none seemed related. I work on a project that relies heavily on metaprogramming, I have some configuration files which compile to runtime modules, but for extra weirdness, we first build each module AST, then write it to a file using Macro.to_string/1, and finally load the module using Code.compile_file/1, we do this to ease debugging. Is there a cleaner way to do this?

While working on this project I found a boolean expression wasn't returning the expected value, so after a long debugging session I noticed the re-parsed AST is not correct.

# unary.exs
quote do
  !(if a do
    b
  end) ||
    !(if b do
      c
    end)
end
|> IO.inspect(label: "original")
|> Macro.to_string
|> tap(&IO.puts/1)
|> Code.string_to_quoted!
|> IO.inspect(label: "reparsed")
|> Macro.to_string
|> tap(&IO.puts/1)
$ elixir unary.exs
original: {:||, [context: Elixir, imports: [{2, Kernel}]],
 [
   {:!, [context: Elixir, imports: [{1, Kernel}]],
    [
      {:if, [context: Elixir, imports: [{2, Kernel}]],
       [{:a, [], Elixir}, [do: {:b, [], Elixir}]]}
    ]},
   {:!, [context: Elixir, imports: [{1, Kernel}]],
    [
      {:if, [context: Elixir, imports: [{2, Kernel}]],
       [{:b, [], Elixir}, [do: {:c, [], Elixir}]]}
    ]}
 ]}
!if a do
  b
end ||
  !if b do
    c
  end
reparsed: {:!, [line: 1],
 [
   {:||, [line: 3],
    [
      {:if, [line: 1], [{:a, [line: 1], nil}, [do: {:b, [line: 2], nil}]]},
      {:!, [line: 4],
       [{:if, [line: 4], [{:b, [line: 4], nil}, [do: {:c, [line: 5], nil}]]}]}
    ]}
 ]}
!(if a do
    b
  end ||
    !if b do
      c
    end)

See how first expression printed is very different from the other one.

Expected behavior

I'm not really sure if the problem is Macro.to_string/1 or the parser itself, but I think the expression should be parsed as the one with parentheses.

iex(1)> quote do
          !if a do
           b
          end ||
            !if b do
              c
            end
        end
{:!, [context: Elixir, imports: [{1, Kernel}]],
 [
   {:||, [context: Elixir, imports: [{2, Kernel}]],
    [
      {:if, [context: Elixir, imports: [{2, Kernel}]],
       [
         {:a, [], Elixir},
         [do: {:b, [context: Elixir, imports: [{1, IEx.Helpers}]], Elixir}]
       ]},
      {:!, [context: Elixir, imports: [{1, Kernel}]],
       [
         {:if, [context: Elixir, imports: [{2, Kernel}]],
          [
            {:b, [context: Elixir, imports: [{1, IEx.Helpers}]], Elixir},
            [
              do: {:c,
               [
                 if_undefined: :apply,
                 context: Elixir,
                 imports: [{0, IEx.Helpers}, {1, IEx.Helpers}, {2, IEx.Helpers}]
               ], Elixir}
            ]
          ]}
       ]}
    ]}
 ]}

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions