Hi!
I have a piece of code that parses some json and creates validations from them. In one place, I need to create a regular expression from string.
In Elixir 1.17 this worked
iex(1)> regex_string = "[0-9]{4}"
"[0-9]{4}"
iex(2)> quote do unquote(~r/#{regex_string}/) end |> Macro.to_string
"~r/[0-9]{4}/"
In Elixir 1.18, I get an error
iex(1)> regex_string = "[0-9]{4}"
"[0-9]{4}"
iex(2)> quote do unquote(~r/#{regex_string}/) end |> Macro.to_string
** (ArgumentError) tried to unquote invalid AST: ~r/[0-9]{4}/
Did you forget to escape term using Macro.escape/1?
(elixir 1.18.2) src/elixir_quote.erl:542: :elixir_quote.argument_error/1
iex:3: (file)
I thought, it might be because of interpolation, so I tried interpolating outside of quote:
iex(1)> regex_string = "[0-9]{4}"
"[0-9]{4}"
iex(2)> regex = ~r/#{regex_string}/
~r/[0-9]{4}/
iex(3)> quote do unquote(regex) end |> Macro.to_string
** (ArgumentError) tried to unquote invalid AST: ~r/[0-9]{4}/
Did you forget to escape term using Macro.escape/1?
(elixir 1.18.2) src/elixir_quote.erl:542: :elixir_quote.argument_error/1
iex:7: (file)
The error suggests using Macro.escape, but it doesn’t make sense to me. Unquote should already escape and indeed, I would the generated code will have AST of the code isntead of the code.
iex(1)> quote do ~r/[0-9]{4}/ end
{:sigil_r, [delimiter: "/", context: Elixir, imports: [{2, Kernel}]],
[{:<<>>, [], ["[0-9]{4}"]}, []]}
iex(2)> regex = ~r/#{regex_string}/
~r/[0-9]{4}/
iex(3)> escaped = Macro.escape(regex)
{:%{}, [],
[
__struct__: Regex,
opts: [],
re_pattern: {:{}, [],
[
:re_pattern,
0,
0,
0,
<<69, 82, 67, 80, 109, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 255, 255, 255,
255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, ...>>
]},
re_version: {"8.44 2020-02-12", :little},
source: "[0-9]{4}"
]}
iex(3)> quote do unquote(escaped) end |> Macro.to_string
"%{\n __struct__: Regex,\n opts: [],\n re_pattern:\n {:re_pattern, 0, 0, 0,\n \"ERCPm\\0\\0\\0\\0\\0\\0\\0\\x01\\0\\0\\0\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0@\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x83\\0)n\\0\\0\\0\\0\\0\\0\\xFF\\x03\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0m\\0\\x04\\0\\x04x\\0)\\0\"},\n re_version: {\"8.44 2020-02-12\", :little},\n source: \"[0-9]{4}\"\n}"
I am starting to wonder if Elixir 1.18 became more strict or is it a bug in Elixir?