Regarding AST form with qualified call

I’ve been reading a great AST guide that I really recommend.

One of the examples given in the first part correctly states that…

quote do
  Foo.{Bar, Baz}
end
#=>
{
  {:., [], [{:__aliases__, [], [:Foo]}, :{}]},
  [],
  [{:__aliases__, [], [:Bar]}, {:__aliases__, [], [:Baz]}]
}

There is an explanation given in the guide as to why the form [alias, :{}] is used, but I don’t really understand why there would be ambiguity in using the form below…

quote do
    Foo.{Bar, Baz}
end
#=>
  {
      :., 
      [], 
      [
          {:__alias__, [], [:Foo]}, 
          {:{}, [], [{:__alias__, [], [:Bar]}, {:__alias__, [], [:Baz]}]}
      ]
  }

I put the above hypothetical AST in a Macro.to_string call, and I got the following error.

** (CaseClauseError) no case clause matching: :Bar
    (elixir 1.16.0) lib/code/formatter.ex:2319: Code.Formatter.force_args?/2
    (elixir 1.16.0) lib/code/formatter.ex:1172: Code.Formatter.call_args_to_algebra_no_blocks/6
    (elixir 1.16.0) lib/code/formatter.ex:1109: Code.Formatter.call_args_to_algebra/6
    (elixir 1.16.0) lib/code/formatter.ex:1056: Code.Formatter.local_to_algebra/5
    (elixir 1.16.0) lib/code/formatter.ex:1695: anonymous fn/5 in Code.Formatter.args_to_algebra_with_comments/7
    (elixir 1.16.0) lib/code/formatter.ex:1998: Code.Formatter.each_quoted_to_algebra_without_comments/4
    (elixir 1.16.0) lib/code/formatter.ex:1984: Code.Formatter.quoted_to_algebra_with_comments/6
    iex:33: (file)

Is my AST not correct?

The AST is expecting :__aliases__; not sure where the singular form has appeared from.

Fixing that in your sample AST and calling Macro.to_string produces:

Foo . {Bar, Baz}

It may help to contemplate this syntax, which expands to identical AST as Foo.{Bar, Baz}:

Foo."{}"(Bar, Baz)

Foo."{}" corresponds to {:., [], [{:__aliases__, [], [:Foo]}, :{}]}.

This is the same shape that a normal function call like Foo.huh(Bar, Baz) would produce, except the function being called has the otherwise-impossible name {}.

2 Likes

Woops, thanks for that.

What is actually happening with Foo.{Bar, Baz} (which I now understand to be distinct from Foo<space>.<space>{Bar, Baz}) why is this a necessary syntax? I’ve not seen anything like that before.

The syntax to which I am referring is the contemplation Foo."{}"(Bar, Baz), which I am also taking to mean Foo.{} Bar, Baz .

Why is there syntax sugar for this?

It’s only normally written in the context of an alias statement, like alias Foo.{Bar, Baz}.

Interestingly, the parser won’t let you write a function named {} explicitly, but one can be created by using a macro to build the AST:

defmodule WeirdMacro do
  defmacro do_fun(name) do
    {:def, [context: Elixir, import: Kernel],
     [
       {name, [context: Elixir],
        [{:x, [if_undefined: :apply], Elixir}, {:y, [if_undefined: :apply], Elixir}]},
       [
         do: {{:., [], [{:__aliases__, [alias: false], [:IO]}, :inspect]}, [],
          [
            {{:x, [if_undefined: :apply], Elixir},
             {:y, [if_undefined: :apply], Elixir}}
          ]}
       ]
     ]}
  end
end

defmodule Foo do
  require WeirdMacro

  WeirdMacro.do_fun(:{})
end

Foo.{:arg1, :arg2}

I’m unsure when such a function would be useful - maybe in a complex DSL? - but it’s neat to see that it’s possible.

2 Likes