Skip to content

Commit cfe959b

Browse files
author
José Valim
committed
Give variables used alongside patterns high priority in signatures
1 parent 1662319 commit cfe959b

File tree

2 files changed

+121
-93
lines changed

2 files changed

+121
-93
lines changed

lib/elixir/lib/module.ex

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -669,15 +669,23 @@ defmodule Module do
669669
{{:\\, [], [left, right]}, acc}
670670
end
671671

672-
defp simplify_signature({:=, _, [_, right]}, acc) do
673-
simplify_signature(right, acc)
672+
# If the variable is being used explicitly for naming,
673+
# we always give it a higher priority (nil) even if it
674+
# starts with underscore.
675+
defp simplify_signature({:=, _, [{var, _, atom}, _]}, acc) when is_atom(atom) do
676+
{simplify_var(var, nil), acc}
674677
end
675678

679+
defp simplify_signature({:=, _, [_, {var, _, atom}]}, acc) when is_atom(atom) do
680+
{simplify_var(var, nil), acc}
681+
end
682+
683+
# If we have only the varible as argument, it also gets
684+
# higher priority. However, if the variable starts with
685+
# underscore, we give it a secondary context (Elixir) with
686+
# lower priority.
676687
defp simplify_signature({var, _, atom}, acc) when is_atom(atom) do
677-
case Atom.to_string(var) do
678-
"_" <> rest -> {{String.to_atom(rest), [], Elixir}, acc}
679-
_ -> {{var, [], nil}, acc}
680-
end
688+
{simplify_var(var, Elixir), acc}
681689
end
682690

683691
defp simplify_signature({:%, _, [left, _]}, acc) when is_atom(left) do
@@ -697,13 +705,21 @@ defmodule Module do
697705
defp simplify_signature(other, acc) when is_binary(other), do: autogenerated(acc, :binary)
698706
defp simplify_signature(_, acc), do: autogenerated(acc, :arg)
699707

708+
defp simplify_var(var, guess_priority) do
709+
case Atom.to_string(var) do
710+
"_" -> {:_, [], guess_priority}
711+
"_" <> rest -> {String.to_atom(rest), [], guess_priority}
712+
_ -> {var, [], nil}
713+
end
714+
end
715+
700716
defp simplify_module_name(module) when is_atom(module) do
701717
try do
702718
split(module)
703719
rescue
704720
ArgumentError -> module
705721
else
706-
module_name -> String.to_atom(camelcase_to_underscore(List.last(module_name)))
722+
module_name -> String.to_atom(Macro.underscore(List.last(module_name)))
707723
end
708724
end
709725

@@ -737,15 +753,6 @@ defmodule Module do
737753
length(:lists.filter(fn(el) -> el == key end, list))
738754
end
739755

740-
defp camelcase_to_underscore(<<c::utf8, rest::binary>>) when c >= ?A and c <= ?Z,
741-
do: do_camelcase_to_underscore(rest, <<c + 32::utf8>>)
742-
defp do_camelcase_to_underscore(<<c::utf8, rest::binary>>, acc) when c >= ?A and c <= ?Z,
743-
do: do_camelcase_to_underscore(rest, <<acc::binary, ?_, c + 32::utf8>>)
744-
defp do_camelcase_to_underscore(<<c::utf8, rest::binary>>, acc),
745-
do: do_camelcase_to_underscore(rest, <<acc::binary, c>>)
746-
defp do_camelcase_to_underscore(<<>>, acc),
747-
do: acc
748-
749756
# Merge
750757

751758
defp merge_signatures([h1 | t1], [h2 | t2], i) do
@@ -765,8 +772,8 @@ defmodule Module do
765772
end
766773

767774
# The older signature, when given, always have higher precedence
768-
defp merge_signature({_, _, nil} = older, _newer, _), do: older
769-
defp merge_signature(_older, {_, _, nil} = newer, _), do: newer
775+
defp merge_signature({_, _, nil} = older, _newer, _), do: older
776+
defp merge_signature(_older, {_, _, nil} = newer, _), do: newer
770777

771778
# Both are a guess, so check if they are the same guess
772779
defp merge_signature({var, _, _} = older, {var, _, _}, _), do: older

lib/elixir/test/elixir/kernel/docs_test.exs

Lines changed: 96 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -3,126 +3,147 @@ Code.require_file "../test_helper.exs", __DIR__
33
defmodule Kernel.DocsTest do
44
use ExUnit.Case
55

6+
import PathHelpers
7+
68
test "attributes format" do
79
defmodule DocAttributes do
810
@moduledoc "Module doc"
911
assert @moduledoc == "Module doc"
10-
assert Module.get_attribute(__MODULE__, :moduledoc) == {8, "Module doc"}
12+
assert Module.get_attribute(__MODULE__, :moduledoc) == {__ENV__.line - 2, "Module doc"}
1113

1214
@typedoc "Type doc"
1315
assert @typedoc == "Type doc"
14-
assert Module.get_attribute(__MODULE__, :typedoc) == {12, "Type doc"}
16+
assert Module.get_attribute(__MODULE__, :typedoc) == {__ENV__.line - 2, "Type doc"}
1517
@type foobar :: any
1618

1719
@doc "Function doc"
1820
assert @doc == "Function doc"
19-
assert Module.get_attribute(__MODULE__, :doc) == {17, "Function doc"}
21+
assert Module.get_attribute(__MODULE__, :doc) == {__ENV__.line - 2, "Function doc"}
2022
def foobar() do
2123
:ok
2224
end
2325
end
2426
end
2527

26-
test "compiled with docs" do
27-
deftestmodule(SampleDocs)
28-
29-
docs = Code.get_docs(SampleDocs, :all)
30-
assert Code.get_docs(SampleDocs, :docs) == docs[:docs]
31-
assert Code.get_docs(SampleDocs, :moduledoc) == docs[:moduledoc]
32-
assert Code.get_docs(SampleDocs, :type_docs) == docs[:type_docs]
33-
assert Code.get_docs(SampleDocs, :callback_docs) == docs[:callback_docs]
34-
35-
assert [{{:arg_names, 5}, _, :def,
36-
[{:list1, [], Elixir},
37-
{:list2, [], Elixir},
38-
{:map1, [], Elixir},
39-
{:list3, [], Elixir},
40-
{:map2, [], Elixir}], nil},
41-
{{:foo, 1}, _, :def, [{:arg, [], nil}], "Function doc"},
42-
{{:foobar, 0}, _, :def, [], nil},
43-
{{:qux, 1}, _, :def, [{:bool, [], Elixir}], false},
44-
{{:with_defaults, 4}, _, :def,
45-
[{:int, [], Elixir},
46-
{:\\, [], [{:arg, [], nil}, 0]},
47-
{:\\, [], [{:year, [], nil}, 2015]},
48-
{:\\, [], [{:fun, [], nil}, {:&, _, [{:/, _, [{:>=, _, nil}, 2]}]}]}], nil}] = docs[:docs]
49-
50-
assert {_, "Module doc"} = docs[:moduledoc]
51-
52-
assert [{{:bar, 1}, _, :opaque, "Opaque type doc"},
53-
{{:foo, 1}, _, :type, "Type doc"}] = docs[:type_docs]
54-
55-
assert [{{:bar, 0}, _, :callback, false},
56-
{{:baz, 2}, _, :callback, nil},
57-
{{:foo, 1}, _, :callback, "Callback doc"},
58-
{{:qux, 1}, _, :macrocallback, "Macrocallback doc"}] = docs[:callback_docs]
59-
end
60-
6128
test "compiled without docs" do
6229
Code.compiler_options(docs: false)
6330

64-
deftestmodule(WithoutSampleDocs)
31+
write_beam(defmodule WithoutDocs do
32+
@moduledoc "Module doc"
6533

66-
assert Code.get_docs(WithoutSampleDocs, :docs) == nil
67-
assert Code.get_docs(WithoutSampleDocs, :moduledoc) == nil
68-
assert Code.get_docs(WithoutSampleDocs, :type_docs) == nil
69-
assert Code.get_docs(WithoutSampleDocs, :callback_docs) == nil
34+
@doc "Some doc"
35+
def foobar(arg), do: arg
36+
end)
37+
38+
assert Code.get_docs(WithoutDocs, :docs) == nil
39+
assert Code.get_docs(WithoutDocs, :moduledoc) == nil
40+
assert Code.get_docs(WithoutDocs, :type_docs) == nil
41+
assert Code.get_docs(WithoutDocs, :callback_docs) == nil
7042
after
7143
Code.compiler_options(docs: true)
7244
end
7345

7446
test "compiled in memory does not have accessible docs" do
75-
defmodule WithoutDocs do
47+
defmodule InMemoryDocs do
7648
@moduledoc "Module doc"
7749

7850
@doc "Some doc"
7951
def foobar(arg), do: arg
8052
end
8153

82-
assert Code.get_docs(NoDocs, :docs) == nil
83-
assert Code.get_docs(NoDocs, :moduledoc) == nil
84-
assert Code.get_docs(NoDocs, :callback_docs) == nil
54+
assert Code.get_docs(InMemoryDocs, :docs) == nil
55+
assert Code.get_docs(InMemoryDocs, :moduledoc) == nil
56+
assert Code.get_docs(InMemoryDocs, :callback_docs) == nil
8557
end
8658

87-
defp deftestmodule(name) do
88-
import PathHelpers
59+
describe "compiled with docs" do
60+
test "infers signatures" do
61+
write_beam(defmodule SignatureDocs do
62+
def arg_names([], [], %{}, [], %{}), do: false
63+
64+
@year 2015
65+
def with_defaults(@year, arg \\ 0, year \\ @year, fun \\ &>=/2) do
66+
{fun, arg + year}
67+
end
68+
69+
def with_struct(%URI{}), do: :ok
70+
71+
def with_underscore({_, _} = _two_tuple), do: :ok
72+
def with_underscore(_), do: :error
73+
74+
def only_underscore(_), do: :ok
75+
76+
def two_good_names(first, :ok), do: first
77+
def two_good_names(second, :error), do: second
78+
end)
79+
80+
assert [{{:arg_names, 5}, _, :def,
81+
[{:list1, _, Elixir},
82+
{:list2, _, Elixir},
83+
{:map1, _, Elixir},
84+
{:list3, _, Elixir},
85+
{:map2, _, Elixir}], nil},
86+
{{:only_underscore, 1}, _, :def,
87+
[{:_, _, Elixir}], nil},
88+
{{:two_good_names, 2}, _, :def,
89+
[{:first, _, nil},
90+
{:atom, _, Elixir}], nil},
91+
{{:with_defaults, 4}, _, :def,
92+
[{:int, _, Elixir},
93+
{:\\, _, [{:arg, _, nil}, 0]},
94+
{:\\, _, [{:year, _, nil}, 2015]},
95+
{:\\, _, [{:fun, _, nil}, {:&, _, [{:/, _, [{:>=, _, nil}, 2]}]}]}], nil},
96+
{{:with_struct, 1}, _, :def, [{:uri, _, Elixir}], nil},
97+
{{:with_underscore, 1}, _, :def, [{:two_tuple, _, nil}], nil}] = Code.get_docs(SignatureDocs, :docs)
98+
end
8999

90-
write_beam(defmodule name do
91-
@moduledoc "Module doc"
100+
test "includes docs for functions, modules, types and callbacks" do
101+
write_beam(defmodule SampleDocs do
102+
@moduledoc "Module doc"
92103

93-
@typedoc "Type doc"
94-
@type foo(any) :: any
104+
@typedoc "Type doc"
105+
@type foo(any) :: any
95106

96-
@typedoc "Opaque type doc"
97-
@opaque bar(any) :: any
107+
@typedoc "Opaque type doc"
108+
@opaque bar(any) :: any
98109

99-
@doc "Callback doc"
100-
@callback foo(any) :: any
110+
@doc "Callback doc"
111+
@callback foo(any) :: any
101112

102-
@doc false
103-
@callback bar() :: term
113+
@doc false
114+
@callback bar() :: term
115+
@callback baz(any, term) :: any
104116

105-
@callback baz(any, term) :: any
117+
@doc "Macrocallback doc"
118+
@macrocallback qux(any) :: any
106119

107-
@doc "Macrocallback doc"
108-
@macrocallback qux(any) :: any
120+
@doc "Function doc"
121+
def foo(arg) do
122+
arg + 1
123+
end
109124

110-
@doc "Function doc"
111-
def foo(arg) do
112-
arg + 1
113-
end
125+
@doc false
126+
def bar(true), do: false
127+
end)
114128

115-
@doc false
116-
def qux(true), do: false
129+
docs = Code.get_docs(SampleDocs, :all)
130+
assert Code.get_docs(SampleDocs, :docs) == docs[:docs]
131+
assert Code.get_docs(SampleDocs, :moduledoc) == docs[:moduledoc]
132+
assert Code.get_docs(SampleDocs, :type_docs) == docs[:type_docs]
133+
assert Code.get_docs(SampleDocs, :callback_docs) == docs[:callback_docs]
117134

118-
def foobar(), do: nil
135+
assert [{{:bar, 1}, _, :def, [{:bool, _, Elixir}], false},
136+
{{:foo, 1}, _, :def, [{:arg, _, nil}], "Function doc"}] = docs[:docs]
119137

120-
def arg_names([], [], %{}, [], %{}), do: false
138+
assert {_, "Module doc"} = docs[:moduledoc]
121139

122-
@year 2015
123-
def with_defaults(@year, arg \\ 0, year \\ @year, fun \\ &>=/2) do
124-
{fun, arg + year}
125-
end
126-
end)
140+
assert [{{:bar, 1}, _, :opaque, "Opaque type doc"},
141+
{{:foo, 1}, _, :type, "Type doc"}] = docs[:type_docs]
142+
143+
assert [{{:bar, 0}, _, :callback, false},
144+
{{:baz, 2}, _, :callback, nil},
145+
{{:foo, 1}, _, :callback, "Callback doc"},
146+
{{:qux, 1}, _, :macrocallback, "Macrocallback doc"}] = docs[:callback_docs]
147+
end
127148
end
128149
end

0 commit comments

Comments
 (0)