@typedoc false is not an acceptable value

@doc false is accepted, but @typedoc false is not?
This is confusing, and in order for me to exclude other package types from my own package’s docs, I have to use @typep.

Is there a good reason for this?

@typep has the behavior that the type you define is not accessible outside of the module it’s defined in. @typedoc false would mean you have a typedoc that is accessible outside the module, but without a type specification that you can see in the docs. That would mean that if you used that type outside the module, a user of your docs would see the @spec, but not the definition of the types in that spec.

What do you mean by that it is not accepted?

This code happily compiles on my machine and Elixir 1.6.4:

defmodule Example do
  @typedoc false
  @type t :: :ok

  @spec run() :: t()
  def run(), do: :ok
end

And, as far as I remember, this behaves exactly as you expect: the type will be usable outside the module but won’t be included in the documentation :slightly_smiling_face:

Do you have an example to reproduce the issue you’re experiencing?

@arkgil have you compiled docs from that example? I think that the @type t will still be included in the documentation.

For context: https://github.com/teamon/tesla/issues/207

You’re right, it doesn’t work as expected! Sorry, I have previously tried it on a module simply loaded into IEx.

Anyway, I remember using it in the past. But maybe I didn’t pay attention if it was really excluded for the docs.

I assume this is really exdoc…but I’m not sure. Regardless, I’d assume that @typedoc false would work the same as @doc false. Changing the code to coerce doc generation feels pretty wrong, don’t you think?

It doesn’t seem to be ExDoc’s issue. From what I understand, ExDoc pulls documentation from dedicated BEAM file chunk. This means that documentation for the type has been generated by the compiler and embedded inside the compiled file.

You can verify this by running

iex> t YourModule

You’ll see that the @typedoc falsed type is there.

Can you please clarify what you mean?

The root issue here is that adding @typedoc false does not actually exclude type-docs from being generated in shared libraries, as seen here: https://hexdocs.pm/duo_client/DuoClient.html (Warning, this is contextual and may change with subsequent versions of the package)

By using use Tesla, I get the options documented in my package. :frowning_face:

Sure! Before I start this longish post, I want to say that you’re probably right that this is the ExDoc’s issue :slightly_smiling_face:

Elixir compiles all your modules’ source code stored in .ex files to .beam files (there is always one .beam file per module). Each .beam file consists of so called “chunks”, and each chunk contains different data. So there is a chunk with the bytecode executed by the Erlang VM, there is chunk with all the atoms used in the module, there is chunk with all literals in the module etc. These are the “official” ones, which are used by all languages hosted on the Erlang VM, but you are free to add your own chunks. Elixir uses that power and stores all the documentation for the module inside dedicated chunk.

The data stored in the chunks can be retrieved at any point in time by parsing the .beam files by hand or using the functions from Erlang’s :beam_lib module. But Elixir provides its own convenience function for retrieving information stored in the docs chunk, and in addition formats it nicely: this function is Code.get_docs/2.

If we’d run that function in the example module below:

defmodule Example do
  @typedoc "My favourite type"
  @type type_with_doc :: :ok

  @type type_without_doc :: :ok

  @typedoc false
  @type type_with_doc_false :: :ok

  @doc "My favourite function"
  def with_doc, do: :ok

  def without_doc, do: :ok

  @doc false
  def with_doc_false, do: :ok
end

iex> Code.get_docs(Example, :all)
[
  docs: [
    {{:with_doc, 0}, 10, :def, [], "My favourite function"},
    {{:with_doc_false, 0}, 15, :def, [], false},
    {{:without_doc, 0}, 13, :def, [], nil}
  ],
  moduledoc: {1, nil},
  callback_docs: [],
  type_docs: [
    {{:type_with_doc, 0}, 2, :type, "My favourite type"},
    {{:type_with_doc_false, 0}, 7, :type, false},
    {{:type_without_doc, 0}, 5, :type, nil}
  ]
]

You can see that all types and functions are included here, regardless of whether they have documentation or not. However, undocumented objects have nil as the last element of the tuple, and @doc false and @typedoc false make the false appear there.

My theory is that ExDoc uses exactly this API for retrieving the documentation and misinterprets the last element of the tuple when it comes to documentation of types. If that’s true, then it’s a low-hanging-PR fruit :smile:

2 Likes

It looks like this line https://github.com/elixir-lang/ex_doc/blob/5acd21e67c6fd82bb364e55bc8992785d9f96c7c/lib/ex_doc/retriever.ex#L533 is the issue. ExDoc treats both nil and false the same, so in both situations the type is included in the generated documentation page.

3 Likes

Fantastic! Thank you for the help, and I now understand a tad more elixir than I did yesterday.

3 Likes