Elixir: Possible interference of "unquote" option

Created on 1 Oct 2018  ·  7Comments  ·  Source: elixir-lang/elixir

Environment

Elixir 1.7.2
macOS High Sierra 10.13.6

Current behavior

Given that

iex(1)> x = quote do
...(1)>   unquote("Hello")
...(1)> end
"Hello"

and that

iex(2)> x = quote [unquote: false] do
...(2)>   unquote("Hello")
...(2)> end
{:unquote, [], ["Hello"]}

this works as expected

iex(3)> defmodule Z do
...(3)> x = quote do
...(3)>   unquote("Hello")
...(3)> end
...(3)> def test do
...(3)>     unquote(x)
...(3)> end
...(3)> end
{:module, Z, <<70, ...>>, {:test, 0}}
iex(4)> Z.test
"Hello"

but this does not work as expected (by me)

iex(5)> defmodule Z do
...(5)> x = quote [unquote: false] do
...(5)>   unquote("Hello")
...(5)> end
...(5)> def test do
...(5)>     unquote(x) # <--- it complains here
...(5)> end
...(5)> end
warning: redefining module Z (current version defined in memory)
  iex:5

** (CompileError) iex:9: unquote called outside quote
    iex:9: (module)

Expected behavior

This compiler error should not happen.

The variable x should take the value {:unquote, [], ["Hello"]}, and this value should be returned by Z.test.

Most helpful comment

IIRC def, defp, defmacro, defmacrop.

All 7 comments

This is expected behavior and is documented as “unquote fragments”:
https://hexdocs.pm/elixir/Kernel.SpecialForms.html#quote/2-binding-and-unquote-fragments

In a nutshell, def, defp, and friends wrap the block in a quote to make it
easier to dynamically generate functions. You can see how the unicode.ex
leverages this.

In case we did not have unquote fragments, your code would fail to compile.

José Valim
www.plataformatec.com.br
Skype: jv.ptec
Founder and Director of R&D

@josevalim That does not make sense, because unquote fragments is working in the first version of the module (inside def), but not working in the second version of the module in the same situation.

The problem seems that the unquote: false is breaking somehow the compilation in the second version of the module.

Note that, in the second version of the module, it is VERY STRANGE that an option that should have only local effect in the scope of the definition of x, to be interfering with the unquote below.

It makes sense because in the second version the ast is: {:unquote, [], [“Hello”]}.

When you give this to unquote, you are inserting a literal call to unquote
in your definition, which then fails.

So the error is not coming from the direct unquote call but rather from the
unquote call injected by the unquote call.

So, in the second version, the value of unquote(x), which is {:unquote, [], ["Hello"]} at compile-time, is put inside a quote by def, resulting in trying to compile a function with a unquote("Hello") outside quote in the function body, am I right?

resulting in trying to compile a function with a unquote("Hello") outside quote in the function body, am I right?

Correct. Because it is unquoted in a single depth quote (the implicit one in def) then it tried to call a 'function' called unquote, hence the error.

@josevalim What other def friends, other than defp, wrap the body of their do blocks inside an internal quote block?

IIRC def, defp, defmacro, defmacrop.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

shadowfacts picture shadowfacts  ·  3Comments

cmeiklejohn picture cmeiklejohn  ·  3Comments

alexrp picture alexrp  ·  4Comments

Irio picture Irio  ·  3Comments

LucianaMarques picture LucianaMarques  ·  3Comments