Skip to content

Commit 51c0bcd

Browse files
committed
Improve error message for violated protocol implementation
1 parent 7b0796c commit 51c0bcd

File tree

5 files changed

+89
-24
lines changed

5 files changed

+89
-24
lines changed

lib/elixir/lib/module/types.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ defmodule Module.Types do
282282

283283
try do
284284
{args_types, context} =
285-
Pattern.of_head(args, guards, expected, :default, meta, stack, context)
285+
Pattern.of_head(args, guards, expected, {:infer, expected}, meta, stack, context)
286286

287287
{return_type, context} =
288288
Expr.of_expr(body, stack, context)

lib/elixir/lib/module/types/apply.ex

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,13 +1093,4 @@ defmodule Module.Types.Apply do
10931093
single_line -> binary_slice(single_line, 1..-2//1)
10941094
end
10951095
end
1096-
1097-
defp integer_to_ordinal(i) do
1098-
case rem(i, 10) do
1099-
1 when rem(i, 100) != 11 -> "#{i}st"
1100-
2 when rem(i, 100) != 12 -> "#{i}nd"
1101-
3 when rem(i, 100) != 13 -> "#{i}rd"
1102-
_ -> "#{i}th"
1103-
end
1104-
end
11051096
end

lib/elixir/lib/module/types/helpers.ex

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,18 @@ defmodule Module.Types.Helpers do
4747

4848
## Warnings
4949

50+
@doc """
51+
Converts an itneger into ordinal.
52+
"""
53+
def integer_to_ordinal(i) do
54+
case rem(i, 10) do
55+
1 when rem(i, 100) != 11 -> "#{i}st"
56+
2 when rem(i, 100) != 12 -> "#{i}nd"
57+
3 when rem(i, 100) != 13 -> "#{i}rd"
58+
_ -> "#{i}th"
59+
end
60+
end
61+
5062
@doc """
5163
Formatted hints in typing errors.
5264
"""

lib/elixir/lib/module/types/pattern.ex

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ defmodule Module.Types.Pattern do
7272
stack,
7373
context
7474
) do
75-
with {:ok, type, context} <- of_pattern_intersect(tree, type, pattern, tag, stack, context) do
75+
with {:ok, type, context} <-
76+
of_pattern_intersect(tree, type, pattern, index, tag, stack, context) do
7677
acc = [type | acc]
7778
of_pattern_args_tree(tail, expected_types, changed, index + 1, acc, tag, stack, context)
7879
end
@@ -115,7 +116,7 @@ defmodule Module.Types.Pattern do
115116
{[type], context} =
116117
of_pattern_recur([expected], tag, stack, context, fn [type], [0], context ->
117118
with {:ok, type, context} <-
118-
of_pattern_intersect(tree, type, expr, tag, stack, context) do
119+
of_pattern_intersect(tree, type, expr, 0, tag, stack, context) do
119120
{:ok, [type], context}
120121
end
121122
end)
@@ -177,7 +178,7 @@ defmodule Module.Types.Pattern do
177178
end
178179

179180
:error ->
180-
throw({types, badpattern_error(expr, tag, stack, context)})
181+
throw({types, badpattern_error(expr, index, tag, stack, context)})
181182
end
182183
end)
183184

@@ -216,23 +217,23 @@ defmodule Module.Types.Pattern do
216217
end)
217218
end
218219

219-
defp badpattern_error(expr, tag, stack, context) do
220+
defp badpattern_error(expr, index, tag, stack, context) do
220221
meta =
221222
if meta = get_meta(expr) do
222223
meta ++ Keyword.take(stack.meta, [:generated, :line])
223224
else
224225
stack.meta
225226
end
226227

227-
error(__MODULE__, {:badpattern, expr, tag, context}, meta, stack, context)
228+
error(__MODULE__, {:badpattern, expr, index, tag, context}, meta, stack, context)
228229
end
229230

230-
defp of_pattern_intersect(tree, expected, expr, tag, stack, context) do
231+
defp of_pattern_intersect(tree, expected, expr, index, tag, stack, context) do
231232
actual = of_pattern_tree(tree, context)
232233
type = intersection(actual, expected)
233234

234235
if empty?(type) do
235-
{:error, badpattern_error(expr, tag, stack, context)}
236+
{:error, badpattern_error(expr, index, tag, stack, context)}
236237
else
237238
{:ok, type, context}
238239
end
@@ -750,8 +751,8 @@ defmodule Module.Types.Pattern do
750751
#
751752
# The match pattern ones have the whole expression instead
752753
# of a single pattern.
753-
def format_diagnostic({:badpattern, pattern_or_expr, tag, context}) do
754-
{to_trace, message} = badpattern(tag, pattern_or_expr)
754+
def format_diagnostic({:badpattern, pattern_or_expr, index, tag, context}) do
755+
{to_trace, message} = badpattern(tag, pattern_or_expr, index)
755756
traces = collect_traces(to_trace, context)
756757

757758
%{
@@ -760,7 +761,7 @@ defmodule Module.Types.Pattern do
760761
}
761762
end
762763

763-
defp badpattern({:try_else, type}, pattern) do
764+
defp badpattern({:try_else, type}, pattern, _) do
764765
{pattern,
765766
"""
766767
the following clause will never match:
@@ -773,7 +774,7 @@ defmodule Module.Types.Pattern do
773774
"""}
774775
end
775776

776-
defp badpattern({:case, meta, type, expr}, pattern) do
777+
defp badpattern({:case, meta, type, expr}, pattern, _) do
777778
if meta[:type_check] == :expr do
778779
{expr,
779780
"""
@@ -799,7 +800,7 @@ defmodule Module.Types.Pattern do
799800
end
800801
end
801802

802-
defp badpattern({:match, type}, expr) do
803+
defp badpattern({:match, type}, expr, _) do
803804
{expr,
804805
"""
805806
the following pattern will never match:
@@ -812,7 +813,32 @@ defmodule Module.Types.Pattern do
812813
"""}
813814
end
814815

815-
defp badpattern(_tag, pattern_or_expr) do
816+
defp badpattern({:infer, types}, pattern_or_expr, index) do
817+
type = Enum.fetch!(types, index)
818+
819+
if type == dynamic() do
820+
{pattern_or_expr,
821+
"""
822+
the #{integer_to_ordinal(index + 1)} pattern in clause will never match:
823+
824+
#{expr_to_string(pattern_or_expr) |> indent(4)}
825+
"""}
826+
else
827+
# This can only happen in protocol implementations
828+
{pattern_or_expr,
829+
"""
830+
the #{integer_to_ordinal(index + 1)} pattern in clause will never match:
831+
832+
#{expr_to_string(pattern_or_expr) |> indent(4)}
833+
834+
because it is expected to receive type:
835+
836+
#{to_quoted_string(type) |> indent(4)}
837+
"""}
838+
end
839+
end
840+
841+
defp badpattern(:default, pattern_or_expr, _) do
816842
{pattern_or_expr,
817843
"""
818844
the following pattern will never match:

lib/elixir/test/elixir/module/types/integration_test.exs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ defmodule Module.Types.IntegrationTest do
5353
]
5454
end
5555

56-
test "writes exports for inferred protocols and implementations" do
56+
test "writes exports for implementations" do
5757
files = %{
5858
"pi.ex" => """
5959
defprotocol Itself do
@@ -311,6 +311,42 @@ defmodule Module.Types.IntegrationTest do
311311
assert_no_warnings(files)
312312
end
313313

314+
test "mismatched impl" do
315+
files = %{
316+
"a.ex" => """
317+
defprotocol Itself do
318+
def itself(data)
319+
end
320+
321+
defimpl Itself, for: Range do
322+
def itself(nil), do: nil
323+
def itself(range), do: range
324+
end
325+
"""
326+
}
327+
328+
warnings = [
329+
"""
330+
warning: the 1st pattern in clause will never match:
331+
332+
nil
333+
334+
because it is expected to receive type:
335+
336+
dynamic(%Range{first: term(), last: term(), step: term()})
337+
338+
typing violation found at:
339+
340+
6 │ def itself(nil), do: nil
341+
│ ~~~~~~~~~~~~~~~~~~~~~~~~
342+
343+
└─ a.ex:6: Itself.Range.itself/1
344+
"""
345+
]
346+
347+
assert_warnings(files, warnings)
348+
end
349+
314350
test "returns diagnostics with source and file" do
315351
files = %{
316352
"a.ex" => """

0 commit comments

Comments
 (0)