Skip to content

Commit

Permalink
Add support for nominal types (#1963)
Browse files Browse the repository at this point in the history
This PR adds support for `-nominal` to be used in the same way as
`-type`. Without this change, OTP's CI can't build documentation
for nominal types.
  • Loading branch information
lucioleKi authored Nov 19, 2024
1 parent 1a5a3c9 commit b5384ef
Show file tree
Hide file tree
Showing 6 changed files with 37 additions and 8 deletions.
4 changes: 2 additions & 2 deletions lib/ex_doc/language.ex
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ defmodule ExDoc.Language do
The map has the following keys:
* `:type` - `:type` or `:opaque`
* `:type` - `:type` or `:opaque` or `:nominal`
* `:source_line` - the line where the code is located
Expand All @@ -122,7 +122,7 @@ defmodule ExDoc.Language do
"""
@callback type_data(entry :: tuple(), spec :: term()) ::
%{
type: :type | :opaque,
type: :type | :opaque | :nominal,
source_line: non_neg_integer(),
source_file: String.t() | nil,
signature: [binary()],
Expand Down
6 changes: 3 additions & 3 deletions lib/ex_doc/language/source.ex
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,13 @@ defmodule ExDoc.Language.Source do
end)
|> Map.new()

# Expand records in all specs, callbacks, types and opaques
# Expand records in all specs, callbacks, types, opaques and nominals
filtermap_ast(abst_code, nil, fn
{:attribute, anno, kind, {mfa, ast}} when kind in [:spec, :callback] ->
ast = Enum.map(ast, &expand_records(&1, records))
{:attribute, anno, kind, {mfa, ast}}

{:attribute, anno, type, {name, ast, args}} when type in [:opaque, :type] ->
{:attribute, anno, type, {name, ast, args}} when type in [:opaque, :nominal, :type] ->
{:attribute, anno, type, {name, expand_records(ast, records), args}}

otherwise ->
Expand Down Expand Up @@ -194,7 +194,7 @@ defmodule ExDoc.Language.Source do
def fetch_type!(module_data, name, arity) do
find_ast(module_data.private.abst_code, module_data.source_basedir, fn
{:attribute, anno, type, {^name, _, args} = spec} = attr ->
if type in [:opaque, :type] and length(args) == arity do
if type in [:nominal, :opaque, :type] and length(args) == arity do
%{
type: type,
spec: spec,
Expand Down
2 changes: 1 addition & 1 deletion lib/ex_doc/refs.ex
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ defmodule ExDoc.Refs do
[{{:module, module}, :limited}] ++
to_refs(exports(module), module, :function) ++
to_refs(callbacks(module), module, :callback) ++
to_refs(types(module, [:type, :opaque]), module, :type)
to_refs(types(module, [:type, :opaque, :nominal]), module, :type)
else
_ ->
[{{:module, module}, :undefined}]
Expand Down
2 changes: 1 addition & 1 deletion lib/ex_doc/retriever.ex
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ defmodule ExDoc.Retriever do
groups_for_docs =
config.groups_for_docs ++
[
Types: &(&1[:kind] in [:type, :opaque]),
Types: &(&1[:kind] in [:type, :opaque, :nominal]),
Callbacks: &(&1[:kind] in [:callback, :macrocallback]),
Functions: fn _ -> true end
]
Expand Down
5 changes: 5 additions & 0 deletions test/ex_doc/language/erlang_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,11 @@ defmodule ExDoc.Language.ErlangTest do
~s|foo(X, Y)|
end

test "nominal", c do
assert autolink_spec("-nominal foo() :: t().", c) ==
~s|foo() :: <a href="#t:t/0">t</a>().|
end

test "tuple", c do
assert autolink_spec(~S"-spec foo() -> {ok, t()}.", c) ==
~s|foo() -> {ok, <a href="#t:t/0">t</a>()}.|
Expand Down
26 changes: 25 additions & 1 deletion test/ex_doc/retriever/erlang_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -286,11 +286,14 @@ defmodule ExDoc.Retriever.ErlangTest do
-doc("opaque1/0 docs.").
-opaque opaque1() :: atom().
-doc("nominal1/0 docs.").
-nominal nominal1() :: atom().
""")

config = %ExDoc.Config{source_url_pattern: "%{path}:%{line}"}
{[mod], []} = Retriever.docs_from_modules([:mod], config)
[equiv_type1, opaque1, type1] = mod.typespecs
[equiv_type1, opaque1, nominal1, type1] = mod.typespecs

assert opaque1.id == "t:opaque1/0"
assert opaque1.type == :opaque
Expand All @@ -301,6 +304,15 @@ defmodule ExDoc.Retriever.ErlangTest do
assert opaque1.spec |> Erlang.autolink_spec(current_kfa: {:type, :opaque1, 0}) ==
"opaque1()"

assert nominal1.id == "t:nominal1/0"
assert nominal1.type == :nominal
assert nominal1.group == :Types
assert nominal1.signature == "nominal1()"
assert nominal1.doc |> DocAST.to_string() =~ "nominal1/0 docs."

assert nominal1.spec |> Erlang.autolink_spec(current_kfa: {:type, :nominal1, 0}) ==
"nominal1() :: <a href=\"https://www.erlang.org/doc/apps/erts/erlang.html#t:atom/0\">atom</a>()."

assert type1.id == "t:type1/0"
assert type1.type == :type
assert type1.group == :Types
Expand Down Expand Up @@ -484,6 +496,9 @@ defmodule ExDoc.Retriever.ErlangTest do
-opaque opaque1() :: atom().
%% opaque1/0 docs.
-nominal nominal1() :: atom().
%% -doc("nominal1/0 docs.").
""")

config = %ExDoc.Config{source_url_pattern: "%{path}:%{line}"}
Expand All @@ -498,6 +513,15 @@ defmodule ExDoc.Retriever.ErlangTest do
assert opaque1.spec |> Erlang.autolink_spec(current_kfa: {:type, :opaque1, 0}) ==
"opaque1()"

assert nominal1.id == "t:nominal1/0"

Check warning on line 516 in test/ex_doc/retriever/erlang_test.exs

View workflow job for this annotation

GitHub Actions / mix_test (1.14, 25)

variable "nominal1" does not exist and is being expanded to "nominal1()", please use parentheses to remove the ambiguity or change the variable name

Check failure on line 516 in test/ex_doc/retriever/erlang_test.exs

View workflow job for this annotation

GitHub Actions / mix_test (1.14, 25)

** (CompileError) test/ex_doc/retriever/erlang_test.exs:516: undefined function nominal1/0 (expected ExDoc.Retriever.ErlangTest to define such a function or for it to be imported, but none are available)
assert nominal1.type == :nominal

Check warning on line 517 in test/ex_doc/retriever/erlang_test.exs

View workflow job for this annotation

GitHub Actions / mix_test (1.14, 25)

variable "nominal1" does not exist and is being expanded to "nominal1()", please use parentheses to remove the ambiguity or change the variable name
assert nominal1.group == :Types

Check warning on line 518 in test/ex_doc/retriever/erlang_test.exs

View workflow job for this annotation

GitHub Actions / mix_test (1.14, 25)

variable "nominal1" does not exist and is being expanded to "nominal1()", please use parentheses to remove the ambiguity or change the variable name

Check warning on line 518 in test/ex_doc/retriever/erlang_test.exs

View workflow job for this annotation

GitHub Actions / mix_test (1.14, 25)

undefined function nominal1/0 (expected ExDoc.Retriever.ErlangTest to define such a function or for it to be imported, but none are available)
assert nominal1.signature == "nominal1/0"

Check warning on line 519 in test/ex_doc/retriever/erlang_test.exs

View workflow job for this annotation

GitHub Actions / mix_test (1.14, 25)

variable "nominal1" does not exist and is being expanded to "nominal1()", please use parentheses to remove the ambiguity or change the variable name

Check warning on line 519 in test/ex_doc/retriever/erlang_test.exs

View workflow job for this annotation

GitHub Actions / mix_test (1.14, 25)

undefined function nominal1/0 (expected ExDoc.Retriever.ErlangTest to define such a function or for it to be imported, but none are available)
assert nominal1.doc |> DocAST.to_string() =~ "nominal1/0 docs."

Check warning on line 520 in test/ex_doc/retriever/erlang_test.exs

View workflow job for this annotation

GitHub Actions / mix_test (1.14, 25)

variable "nominal1" does not exist and is being expanded to "nominal1()", please use parentheses to remove the ambiguity or change the variable name

Check warning on line 520 in test/ex_doc/retriever/erlang_test.exs

View workflow job for this annotation

GitHub Actions / mix_test (1.14, 25)

undefined function nominal1/0 (expected ExDoc.Retriever.ErlangTest to define such a function or for it to be imported, but none are available)

assert nominal1.spec |> Erlang.autolink_spec(current_kfa: {:type, :nominal1, 0}) ==

Check warning on line 522 in test/ex_doc/retriever/erlang_test.exs

View workflow job for this annotation

GitHub Actions / mix_test (1.14, 25)

variable "nominal1" does not exist and is being expanded to "nominal1()", please use parentheses to remove the ambiguity or change the variable name

Check warning on line 522 in test/ex_doc/retriever/erlang_test.exs

View workflow job for this annotation

GitHub Actions / mix_test (1.14, 25)

undefined function nominal1/0 (expected ExDoc.Retriever.ErlangTest to define such a function or for it to be imported, but none are available)
"nominal1() :: <a href=\"https://www.erlang.org/doc/apps/erts/erlang.html#t:atom/0\">atom</a>()."

assert type1.id == "t:type1/0"
assert type1.type == :type
assert type1.signature == "type1/0"
Expand Down

0 comments on commit b5384ef

Please sign in to comment.