提问人:denis.peplin 提问时间:11/3/2023 更新时间:11/3/2023 访问量:25
宏代码转换的透析问题
Dialyxir issue with macro code transformation
问:
我正在尝试构建一个宏,将 Elixir 代码转换为某种“配置”以供以后使用,所以这段代码:
build_config with one_and_two <- SomeModule.run("one", "two"),
_three_and_four <- SomeModule.run(one_and_two, "four"),
_six_and_five <- SomeModule.run("six", "five") do
IO.puts(one_and_two)
end
将被表示为
[
{:one_and_two, SomeModule, :run, ["one", "two"]},
{:_three_and_four, SomeModule, :run, [{:from, :one_and_two}, "four"]},
{:_six_and_five, SomeModule, :run, ["six", "five"]}
]
这种方法的问题在于,当我运行时,它不会抱怨明显不好的类型,但是当我运行常规代码时,它确实会抱怨。mix dialyzer
例如,我在 SomeModule 中有以下代码:
defmodule SomeModule do
@spec run(binary, atom) :: binary
def run(arg1, arg2) do
IO.puts("RUN FOR #{arg1} AND #{arg2}")
arg1 <> Atom.to_string(arg2)
end
end
让 Dialyzer 抱怨的常规代码是这样的:
one_and_two = SomeModule.run("one", "two")
SomeModule.run(one_and_two, "four")
我试图过度简化我构建的宏,但它仍然有点太复杂了:
defmacro build_config({:with, _meta, args}, do: _block) do
{lines, _variables_map} =
Enum.map_reduce(args, MapSet.new(), fn arg, names ->
{:<-, _,
[
{name, _, nil},
{{:., _, [{:__aliases__, _, [_module]}, _function]} = call, meta, variables}
]} = arg
names = MapSet.put(names, name)
new_variables =
Enum.map(variables, fn
{variable_name, _, nil} = variable ->
if variable_name in names do
{:from, variable_name}
else
variable
end
other ->
other
end)
{module, function, params} = Macro.decompose_call({call, meta, new_variables})
line =
quote do
{unquote(name), unquote(module), unquote(function), unquote(params)}
end
{line, names}
end)
lines
end
Github 上的存储库包含示例代码:https://github.com/denispeplin/config_generator
我的问题不是关于宏本身,而是关于整个方法:如果宏输出的东西不是可执行的“代码”,而是某种“配置”,它本身不调用任何东西,Dialyzer应该抱怨类型错误吗?
有什么方法可以实现我想要的:从代码生成配置并让 Dialyzer 同时工作?
答:
上面宏的 MRE 可以简化为返回元组的单行代码。一般来说,这个问题与宏无关,也与它的返回类型无关。宏确实在适当的位置注入了 AST,没有魔法,透析器对宏一无所知,它可以与注入的代码一起使用。
宏结果只是一个元组。你没有展示如何使用这个元组,但我猜它会通过 Kernel.apply/3
,它有一个类型作为第三个参数。list()
在 erlang 中没有这样的类型(因此在 elixir 中)作为具有指定类型的固定长度列表。这就是为什么无法检查上面介绍的方法中参数列表中的元素的原因。
不过,您可以做的是引入您自己的宏,这将扩展 unquote_splicing/1
的参数。然后,注入的代码将类似于常规代码,参数逐个传递,而不是作为列表传递,因此透析器将能够检查它们的类型。apply/3
我的建议是避免走这条路,直到你对宏的工作原理有绝对清楚的了解,并且使用不那么麻烦的东西。
评论
apply
Code.Typespec.fetch_types/1
来自己验证类型。请不要告诉任何人我建议的:)
评论