You can’t expand them, because unlike sigil
they are special forms
. However if you still expect to pass raw map
then you can solve that in few ways depends on use case …
defmodule UsedModule do
defmacro __using__(opts) do
if get_config!(opts, __CALLER__).with_extra do
quote do
def extra(), do: "with extra"
end
else
quote do
def extra(), do: "no extra"
end
end
end
defp get_config!(opts, env) do
case opts[:config] do
nil ->
raise ":config option not passed"
{:%, _, [{:__aliases__, _, [:PatternMatchingConfig]}, {:%{}, _, config}]} ->
struct(PatternMatchingConfig, config)
{:%, _, [aliases, {:%{}, _, config}]} ->
aliases |> Macro.expand_literal(env) |> struct(config)
{:%{}, _, config} ->
Map.new(config)
_config ->
raise "unexpected config value. Did you passed a raw map?"
end
end
end
defmodule ExpandedConfig do
defstruct with_extra: false
end
defmodule PatternMatchingConfig do
defstruct with_extra: false
end
defmodule Examples do
defmodule ExpandedStruct do
use UsedModule, config: %ExpandedConfig{}
end
defmodule PatternMatchingMap do
use UsedModule, config: %{with_extra: true}
end
defmodule PatternMatchingStruct do
use UsedModule, config: %PatternMatchingConfig{}
end
end
If you do not want to mix your code with handling special forms then all you need to do is write something like:
defmodule Example do
def sample(ast, env) do
if Macro.quoted_literal?(ast) do
Macro.postwalk(ast, fn
{:__aliases__, _, _} = alias -> Macro.expand(alias, env)
{:%{}, [], opts} -> Map.new(opts)
{:%, [], [module, opts]} -> struct(module, opts)
other -> other
end)
else
ast
end
end
end
iex> map_ast = quote do: %{day: 1, month: 1, year: 2022}
{:%{}, [], [day: 1, month: 1, year: 2022]}
iex> Example.sample(map_ast, __ENV__)
%{day: 1, month: 1, year: 2022}
iex> struct_ast = quote do: %Date{day: 1, month: 1, year: 2022}
{:%, [],
[
{:__aliases__, [alias: false], [:Date]},
{:%{}, [], [day: 1, month: 1, year: 2022]}
]}
iex> Example.sample(struct_ast, __ENV__)
~D[2022-01-01]