diff --git a/lib/ex_doc/retriever.ex b/lib/ex_doc/retriever.ex index eef741ab1..ad1ff8659 100644 --- a/lib/ex_doc/retriever.ex +++ b/lib/ex_doc/retriever.ex @@ -239,18 +239,9 @@ defmodule ExDoc.Retriever do function = actual_def(name, arity, type) line = find_actual_line(module_info.abst_code, function, :function) || doc_line - doc = docstring(doc, name, arity, Map.fetch(module_info.impls, {name, arity})) - - specs = module_info.specs - |> Map.get(function, []) - |> Enum.map(&Typespec.spec_to_ast(name, &1)) - - specs = - if type == :defmacro do - Enum.map(specs, &remove_first_macro_arg/1) - else - specs - end + impl = Map.fetch(module_info.impls, {name, arity}) + doc = docstring(doc, name, arity, impl) + specs = get_specs(module_info, type, function, name, arity, impl) annotations = case {type, name, arity} do @@ -297,6 +288,31 @@ defmodule ExDoc.Retriever do end end + defp get_specs(module_info, type, function, name, arity, impl) do + specs = + module_info.specs + |> Map.get(function, []) + |> Enum.map(&Typespec.spec_to_ast(name, &1)) + + specs = + if type == :defmacro do + Enum.map(specs, &remove_first_macro_arg/1) + else + specs + end + + copy_callback_specs(specs, name, arity, impl) + end + + defp copy_callback_specs([], name, arity, {:ok, behaviour}) do + callbacks = Kernel.Typespec.beam_callbacks(behaviour) + {_, specs} = List.keyfind(callbacks, {name, arity}, 0) + Enum.map(specs, &Typespec.spec_to_ast(name, &1)) + end + defp copy_callback_specs(specs, _, _, _) do + specs + end + defp docstring(nil, name, arity, {:ok, behaviour}) do info = "Callback implementation for `c:#{inspect behaviour}.#{name}/#{arity}`." callback_docs = Code.get_docs(behaviour, :callback_docs) || [] diff --git a/test/ex_doc/retriever_test.exs b/test/ex_doc/retriever_test.exs index 8db542076..4a3eafb37 100644 --- a/test/ex_doc/retriever_test.exs +++ b/test/ex_doc/retriever_test.exs @@ -230,10 +230,7 @@ defmodule ExDoc.RetrieverTest do end test "undocumented callback implementations get default doc" do - [module_node] = - ["CustomBehaviourOne", "CustomBehaviourTwo", "CustomBehaviourImpl"] - |> docs_from_files() - |> Enum.filter(&match?(%ExDoc.ModuleNode{id: "CustomBehaviourImpl"}, &1)) + [module_node] = docs_from_files ["CustomBehaviourImpl"] docs = module_node.docs assert Enum.map(docs, &(&1.id)) == ["bye/1", "greet/1", "hello/1"] assert Enum.at(docs, 0).doc == @@ -245,6 +242,17 @@ defmodule ExDoc.RetrieverTest do "Callback implementation for `c:CustomBehaviourOne.hello/1`." end + test "callback implementations get specs from behaviour" do + [module_node] = docs_from_files ["CustomBehaviourImpl"] + + [_, greet, hello] = module_node.docs + assert greet.name == :greet + assert Macro.to_string(greet.specs) == "[greet(integer() | String.t()) :: integer()]" + + assert hello.name == :hello + assert Macro.to_string(hello.specs) == "[hello(non_neg_integer()) :: non_neg_integer()]" + end + ## PROTOCOLS test "docs_from_files properly tag protocols" do diff --git a/test/fixtures/behaviour.ex b/test/fixtures/behaviour.ex index 99d5be208..4d380c8ce 100644 --- a/test/fixtures/behaviour.ex +++ b/test/fixtures/behaviour.ex @@ -20,7 +20,9 @@ defmodule CustomBehaviourImpl do @behaviour CustomBehaviourOne @behaviour CustomBehaviourTwo + @spec hello(non_neg_integer()) :: non_neg_integer() def hello(i), do: i + def greet(i), do: i @doc "A doc for this so it doesn't use 'Callback implementation for'"