Skip to content

Commit 1f211d1

Browse files
committed
Clean up diagnostic formatting on patterns
1 parent aed240b commit 1f211d1

4 files changed

Lines changed: 122 additions & 97 deletions

File tree

lib/elixir/lib/module/types.ex

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,10 +324,11 @@ defmodule Module.Types do
324324
Enum.reduce(clauses, {0, 0, [], [], context}, fn
325325
{meta, args, guards, body}, {index, total, mapping, inferred, context} ->
326326
context = fresh_context(context)
327+
info = {:def, kind, fun, args, guards, expected}
327328

328329
try do
329330
{trees, _precise?, context} =
330-
Pattern.of_head(args, guards, expected, {:infer, expected}, meta, stack, context)
331+
Pattern.of_head(args, guards, expected, info, meta, stack, context)
331332

332333
{return_type, context} =
333334
Expr.of_expr(body, Descr.term(), body, stack, context)

lib/elixir/lib/module/types/expr.ex

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -597,7 +597,8 @@ defmodule Module.Types.Expr do
597597

598598
defp for_clause({:<<>>, _, [{:<-, meta, [left, right]}]} = expr, stack, context) do
599599
{right_type, context} = of_expr(right, bitstring(), expr, stack, context)
600-
context = Pattern.of_generator(left, [], bitstring(), :for, expr, stack, context)
600+
info = {:for, expr, dynamic()}
601+
context = Pattern.of_generator(left, [], bitstring(), info, expr, stack, context)
601602

602603
if compatible?(right_type, bitstring()) do
603604
context
@@ -667,7 +668,8 @@ defmodule Module.Types.Expr do
667668
defp with_clause({:<-, _meta, [left, right]} = expr, stack, context) do
668669
{pattern, guards} = extract_head([left])
669670
{_type, context} = of_expr(right, @pending, right, stack, context)
670-
Pattern.of_generator(pattern, guards, dynamic(), :with, expr, stack, context)
671+
info = {:with, expr, dynamic()}
672+
Pattern.of_generator(pattern, guards, dynamic(), info, expr, stack, context)
671673
end
672674

673675
defp with_clause(expr, stack, context) do
@@ -762,7 +764,7 @@ defmodule Module.Types.Expr do
762764
cond do
763765
stack.mode != :infer and previous != [] and
764766
Pattern.args_subtype?(upper_types, previous) ->
765-
{previous, Pattern.badpattern_warn(clause, info, stack, context)}
767+
{previous, Pattern.redundant_warn(clause, previous, stack, context)}
766768

767769
precise? ->
768770
{[upper_types | previous], context}

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

Lines changed: 113 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -105,15 +105,15 @@ defmodule Module.Types.Pattern do
105105

106106
{:error, _old_type, error_context} ->
107107
if match_error?(var, new_type) do
108-
throw(badpattern_error(var, expr, stack, context))
108+
throw(badmatch_error(var, expr, stack, context))
109109
else
110110
throw(badvar_error(var, old_type, new_type, stack, error_context))
111111
end
112112
end
113113
end
114114

115115
:error ->
116-
throw(badpattern_error(var, expr, stack, context))
116+
throw(badmatch_error(var, expr, stack, context))
117117
end
118118
end)
119119
catch
@@ -228,7 +228,7 @@ defmodule Module.Types.Pattern do
228228
{expected, context} = expected_fun.(of_pattern_tree(tree, stack, context), context)
229229

230230
args = [{tree, expected, expr}]
231-
tag = {:match, expected}
231+
tag = {:match, expr, expected}
232232

233233
with {:ok, types} <- of_pattern_intersect(args, 0, [], pattern_info, tag, stack, context),
234234
{[type], changed, context} <-
@@ -350,29 +350,25 @@ defmodule Module.Types.Pattern do
350350
error(__MODULE__, error, error_meta(var, stack), stack, context)
351351
end
352352

353-
defp badpattern_error(var, expr, stack, context) do
353+
defp badmatch_error(var, expr, stack, context) do
354354
meta = error_meta(expr, stack)
355355
context = Of.error_var(var, context)
356-
error = {:badpattern, meta, expr, nil, :default, context}
356+
error = {:badmatch, meta, expr, context}
357357
error(__MODULE__, error, meta, stack, context)
358358
end
359359

360-
@doc """
361-
Marks a badpattern error.
362-
"""
363-
def badpattern_error(expr, index, tag, stack, context) do
360+
defp badpattern_error(expr, index, tag, stack, context) do
364361
meta = error_meta(expr, stack)
365-
error = {:badpattern, meta, expr, index, tag, context}
362+
error = {:badpattern, meta, index, tag, context}
366363
error(__MODULE__, error, meta, stack, context)
367364
end
368365

369366
@doc """
370-
Marks a badpattern warnings.
367+
Warns on redundant clause.
371368
"""
372-
def badpattern_warn(expr, tag, stack, context) do
373-
meta = error_meta(expr, stack)
374-
error = {:badpattern, meta, expr, nil, tag, context}
375-
warn(__MODULE__, error, meta, stack, context)
369+
def redundant_warn({:->, meta, [head, _]}, previous, stack, context) do
370+
warning = {:redundant_clause, head, previous, context}
371+
warn(__MODULE__, warning, meta, stack, context)
376372
end
377373

378374
defp error_meta(expr, stack) do
@@ -1313,6 +1309,27 @@ defmodule Module.Types.Pattern do
13131309

13141310
## Helpers
13151311

1312+
def format_diagnostic({:badstruct, type, expr, context}) do
1313+
traces = collect_traces(expr, context)
1314+
1315+
%{
1316+
details: %{typing_traces: traces},
1317+
message:
1318+
IO.iodata_to_binary([
1319+
"""
1320+
expected an atom as struct name:
1321+
1322+
#{expr_to_string(expr) |> indent(4)}
1323+
1324+
got type:
1325+
1326+
#{to_quoted_string(type) |> indent(4)}
1327+
""",
1328+
format_traces(traces)
1329+
])
1330+
}
1331+
end
1332+
13161333
def format_diagnostic({:badguard, type, expr, context}) do
13171334
traces = collect_traces(expr, context)
13181335

@@ -1351,39 +1368,54 @@ defmodule Module.Types.Pattern do
13511368
}
13521369
end
13531370

1354-
def format_diagnostic({:badstruct, type, expr, context}) do
1355-
traces = collect_traces(expr, context)
1371+
def format_diagnostic({:redundant_clause, args, previous, context}) do
1372+
traces = collect_traces(args, context)
13561373

13571374
%{
13581375
details: %{typing_traces: traces},
13591376
message:
13601377
IO.iodata_to_binary([
13611378
"""
1362-
expected an atom as struct name:
1379+
the following clause is redundant:
13631380
1364-
#{expr_to_string(expr) |> indent(4)}
1381+
#{args_to_string(args) |> indent(4)} ->
13651382
1366-
got type:
1383+
previous clauses have already matched on the following types:
13671384
1368-
#{to_quoted_string(type) |> indent(4)}
1385+
#{previous_to_string(previous)}
13691386
""",
13701387
format_traces(traces)
13711388
])
13721389
}
13731390
end
13741391

1375-
# $ type tag = head_pattern() or match_pattern()
1392+
def format_diagnostic({:badmatch, _meta, pattern, context}) do
1393+
traces = collect_traces(pattern, context)
1394+
1395+
%{
1396+
details: %{typing_traces: traces},
1397+
message:
1398+
IO.iodata_to_binary([
1399+
"""
1400+
the following pattern will never match:
1401+
1402+
#{expr_to_string(pattern) |> indent(4)}
1403+
""",
1404+
format_traces(traces)
1405+
])
1406+
}
1407+
end
1408+
1409+
# $ type tag = {:def, kind, fun, args, guards, types} or clause_pattern() or match_pattern()
13761410
#
1377-
# $ typep head_pattern =
1378-
# :fn or :default or
1379-
# {{:case | :try_else, meta, expr, type}, [arg], [previous]} or
1380-
# {:for_reduce | :receive | :try_catch | :with_else | :fn, [arg], [previous]}
1411+
# $ typep clause_pattern =
1412+
# {{:case | :try_else, meta, expr, type}, [arg], [previous]} or
1413+
# {:for_reduce | :receive | :try_catch | :with_else | :fn, [arg], [previous]}
13811414
#
13821415
# $ typep match_pattern =
1383-
# :with or :for or {:match, type}
1384-
def format_diagnostic({:badpattern, meta, pattern_or_expr, index, tag, context}) do
1385-
# TODO: stop passing pattern_or_expr as argument
1386-
{to_trace, message} = badpattern(tag, pattern_or_expr, index)
1416+
# {:with or :for or :match, pattern, type}
1417+
def format_diagnostic({:badpattern, meta, index, tag, context}) do
1418+
{to_trace, message} = badpattern(tag, index)
13871419
traces = collect_traces(to_trace, context)
13881420

13891421
hints =
@@ -1398,7 +1430,35 @@ defmodule Module.Types.Pattern do
13981430
}
13991431
end
14001432

1401-
defp badpattern({{op, meta, expr, type}, args, previous}, _, _) when op in [:case, :try_else] do
1433+
defp badpattern({:def, _kind, _fun, args, _guards, types}, index)
1434+
when is_integer(index) do
1435+
arg = Enum.fetch!(args, index)
1436+
type = Enum.fetch!(types, index)
1437+
1438+
if type == dynamic() do
1439+
{arg,
1440+
"""
1441+
the #{integer_to_ordinal(index + 1)} pattern in clause will never match:
1442+
1443+
#{expr_to_string(arg) |> indent(4)}
1444+
"""}
1445+
else
1446+
# This can only happen in protocol implementations for now
1447+
{arg,
1448+
"""
1449+
the #{integer_to_ordinal(index + 1)} pattern in clause will never match:
1450+
1451+
#{expr_to_string(arg) |> indent(4)}
1452+
1453+
because it is expected to receive type:
1454+
1455+
#{to_quoted_string(type) |> indent(4)}
1456+
"""}
1457+
end
1458+
end
1459+
1460+
defp badpattern({{op, meta, expr, type}, args, previous}, _index)
1461+
when op in [:case, :try_else] do
14021462
type_check = meta[:type_check]
14031463

14041464
cond do
@@ -1485,7 +1545,7 @@ defmodule Module.Types.Pattern do
14851545
#{to_quoted_string(type) |> indent(4)}
14861546
"""}
14871547

1488-
args_subtype?([type], previous) ->
1548+
true ->
14891549
{args,
14901550
"""
14911551
the following clause cannot match because the previous clauses already matched all possible values:
@@ -1500,80 +1560,42 @@ defmodule Module.Types.Pattern do
15001560
15011561
#{to_quoted_string(type) |> indent(4)}
15021562
"""}
1503-
1504-
true ->
1505-
{args,
1506-
"""
1507-
the following clause is redundant:
1508-
1509-
#{args_to_string(args) |> indent(4)} ->
1510-
1511-
previous clauses have already matched on the following types:
1512-
1513-
#{previous_to_string(previous)}
1514-
"""}
15151563
end
15161564
end
15171565

1518-
defp badpattern({op, args, previous}, _, _)
1519-
when op in [:receive, :try_catch, :for_reduce, :with_else, :fn] do
1520-
{args,
1521-
"""
1522-
the following clause is redundant:
1523-
1524-
#{args_to_string(args) |> indent(4)} ->
1525-
1526-
previous clauses have already matched on the following types:
1566+
defp badpattern({op, args, _previous}, index)
1567+
when op in [:for_reduce, :receive, :try_catch, :with_else, :fn] do
1568+
arg = Enum.fetch!(args, index)
15271569

1528-
#{previous_to_string(previous)}
1529-
"""}
1530-
end
1531-
1532-
defp badpattern({:match, type}, expr, _) do
1533-
{expr,
1570+
{arg,
15341571
"""
15351572
the following pattern will never match:
15361573
1537-
#{expr_to_string(expr) |> indent(4)}
1538-
1539-
because the right-hand side has type:
1540-
1541-
#{to_quoted_string(type) |> indent(4)}
1574+
#{expr_to_string(arg) |> indent(4)}
15421575
"""}
15431576
end
15441577

1545-
defp badpattern({:infer, types}, pattern_or_expr, index) when is_integer(index) do
1546-
type = Enum.fetch!(types, index)
1578+
defp badpattern({op, pattern, type}, _index) when op in [:match, :for, :with] do
1579+
message =
1580+
if type == dynamic() do
1581+
"""
1582+
the following pattern will never match:
15471583
1548-
if type == dynamic() do
1549-
{pattern_or_expr,
1550-
"""
1551-
the #{integer_to_ordinal(index + 1)} pattern in clause will never match:
1552-
1553-
#{expr_to_string(pattern_or_expr) |> indent(4)}
1554-
"""}
1555-
else
1556-
# This can only happen in protocol implementations
1557-
{pattern_or_expr,
1558-
"""
1559-
the #{integer_to_ordinal(index + 1)} pattern in clause will never match:
1560-
1561-
#{expr_to_string(pattern_or_expr) |> indent(4)}
1584+
#{expr_to_string(pattern) |> indent(4)}
1585+
"""
1586+
else
1587+
"""
1588+
the following pattern will never match:
15621589
1563-
because it is expected to receive type:
1590+
#{expr_to_string(pattern) |> indent(4)}
15641591
1565-
#{to_quoted_string(type) |> indent(4)}
1566-
"""}
1567-
end
1568-
end
1592+
because the right-hand side has type:
15691593
1570-
defp badpattern(_, pattern_or_expr, _) do
1571-
{pattern_or_expr,
1572-
"""
1573-
the following pattern will never match:
1594+
#{to_quoted_string(type) |> indent(4)}
1595+
"""
1596+
end
15741597

1575-
#{expr_to_string(pattern_or_expr) |> indent(4)}
1576-
"""}
1598+
{pattern, message}
15771599
end
15781600

15791601
defp args_to_string(args) do

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ defmodule TypeHelper do
155155
expected = Enum.map(patterns, fn _ -> Descr.dynamic() end)
156156

157157
{_trees, precise?, _context} =
158-
Pattern.of_head(patterns, guards, expected, :default, [], stack, new_context())
158+
Pattern.of_head(patterns, guards, expected, {:fn, patterns, []}, [], stack, new_context())
159159

160160
precise?
161161
end
@@ -165,7 +165,7 @@ defmodule TypeHelper do
165165
expected = Enum.map(patterns, fn _ -> Descr.dynamic() end)
166166

167167
{_trees, _precise?, context} =
168-
Pattern.of_head(patterns, guards, expected, :default, [], stack, new_context())
168+
Pattern.of_head(patterns, guards, expected, {:fn, patterns, []}, [], stack, new_context())
169169

170170
Expr.of_expr(body, Descr.term(), :ok, stack, context)
171171
end

0 commit comments

Comments
 (0)