在 Elixir 宏中获取当前范围的文档

Get documentation in Elixir macro for current scope

我正在尝试编写辅助宏,它允许我在没有大量样板的情况下编写一堆 Elixir 结构,所以我编写了宏:

defmodule Events do
  @moduledoc false

  defmacro defevent(module, fields \ []) do
    keys = Keyword.keys(fields)

    quote do
      defmodule unquote(module) do
        @type t :: %__MODULE__{unquote_splicing(fields)}

        defstruct unquote(keys)
      end
    end
  end
end

可以这样使用:

defmofule Event do
  import Events

  defevent Foo, foo: String.t(), bar: number()
end

但是我想增加通过 @doc 模块属性向此类模块添加文档的可能性:

defmodule Events do
  @moduledoc false

  defmacro defevent(module, fields \ []) do
    keys = Keyword.keys(fields)

    quote do
      docs = Module.delete_attribute(__MODULE__, :doc)

      defmodule unquote(module) do
        for doc <- docs do
          @moduledoc doc
        end

        @type t :: %__MODULE__{unquote_splicing(fields)}

        defstruct unquote(keys)
      end
    end
  end
end

然而,它不会像 Module.get_attribute(__MODULE__, :doc) returns 一样工作,如果没有文档则 nil 或如果有文档字符串 {integer(), String.t()} 。但是它错过了所有标签,因此编写如下代码:

defmofule Event do
  import Events

  @doc "Foo"
  @doc deprecated: "Bar"
  defevent Foo, foo: String.t(), bar: number()
end

将仅包含 "Foo" 没有指定标签的字符串。我目前的解决方法是使用自定义属性,但这不是最漂亮的解决方案,因为它需要非标准参数和定义自定义模块属性(这让我依赖 use)。

defmodule Events do
  @moduledoc false

  defmacro __using__(_) do
    quote do
      import unquote(__MODULE__), only: [defevent: 1, defevent: 2]

      Module.register_attribute(__MODULE__, :eventdoc, accumulate: true)
    end
  end

  defmacro defevent(module, fields \ []) do
    keys = Keyword.keys(fields)

    quote do
      docs = Module.delete_attribute(__MODULE__, :eventdoc)

      defmodule unquote(module) do
        for doc <- docs do
          @moduledoc doc
        end

        @type t :: %__MODULE__{unquote_splicing(fields)}

        defstruct unquote(keys)
      end
    end
  end
end

有什么方法可以获取当前 @doc 的所有属性都已分配的值吗?

非常有偏见的愚见:没有一个 Elixir 开发者不尝试增强结构。它们很酷、简洁而生动,而您的代码只是让事情变得过于复杂。

我相信,Elixir 核心团队同意这个观点,这就是为什么这个功能没有公开的原因。不管你是否有足够的勇气去解决它,你去吧。

这里是how documentation is built by Elixir。请注意前导评论:

@doc false  
# Used internally to compile documentation.  
# This function is private and must be used only internally.

你还在吗?好的。

进一步挖掘 Module.compile_definition_attributes/6,我们进入 Module.compile_doc_meta/5

我们距离解决方案仅一步之遥。谨防!私人活动区!

您要自己重新实现 Module.get_doc_meta/2

case :ets.take(set, {:doc, :meta}) do
  [{{:doc, :meta}, metadata, _}] -> Map.merge(existing_meta, metadata)
  [] -> existing_meta
end

也就是说,您要从 ETS 读取 {:doc, :meta} 密钥,其中 setdefined here