Skip to content

Commit c903033

Browse files
committed
Compute redundant clauses for try-catch and try-else
1 parent 7caa954 commit c903033

4 files changed

Lines changed: 213 additions & 118 deletions

File tree

lib/elixir/lib/module/types/descr.ex

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -254,9 +254,6 @@ defmodule Module.Types.Descr do
254254
* For gradual tuple types: processes both dynamic and static components separately,
255255
then combines them.
256256
257-
The list of arguments can be flattened into a broad domain by calling:
258-
259-
|> Enum.zip_with(fn types -> Enum.reduce(types, &union/2) end)
260257
"""
261258
def domain_to_args(descr) do
262259
case :maps.take(:dynamic, descr) do
@@ -269,19 +266,23 @@ defmodule Module.Types.Descr do
269266
end
270267
end
271268

269+
@doc """
270+
Converts the domain to a flatten list of arguments.
271+
"""
272+
def domain_to_flat_args(domain, arity_or_args) do
273+
case domain_to_args(domain) do
274+
[] when is_integer(arity_or_args) -> List.duplicate(none(), arity_or_args)
275+
[] when is_list(arity_or_args) -> Enum.map(arity_or_args, fn _ -> none() end)
276+
args -> Enum.zip_with(args, fn types -> Enum.reduce(types, &union/2) end)
277+
end
278+
end
279+
272280
defp unwrap_domain_tuple(%{tuple: bdd} = descr, transform) when map_size(descr) == 1 do
273281
tuple_bdd_to_dnf(bdd) |> Enum.map(transform)
274282
end
275283

276284
defp unwrap_domain_tuple(descr, _transform) when descr == %{}, do: []
277285

278-
defp domain_to_flat_args(domain, arity) do
279-
case domain_to_args(domain) do
280-
[] -> List.duplicate(none(), arity)
281-
args -> Enum.zip_with(args, fn types -> Enum.reduce(types, &union/2) end)
282-
end
283-
end
284-
285286
## Optional
286287

287288
# `not_set()` is a special base type that represents a not_set field in a map.

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

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ defmodule Module.Types.Expr do
308308

309309
def of_expr({:case, meta, [case_expr, [{:do, clauses}]]}, expected, _expr, stack, context) do
310310
{case_type, context} = of_expr(case_expr, @pending, case_expr, stack, context)
311-
info = {:case, meta, case_expr}
311+
info = {:case, meta, case_expr, case_type}
312312

313313
added_meta =
314314
if Macro.quoted_literal?(case_expr) do
@@ -325,7 +325,7 @@ defmodule Module.Types.Expr do
325325
else
326326
clauses
327327
end
328-
|> of_redundant_clauses(case_type, expected, info, stack, context, none())
328+
|> of_redundant_clauses([case_type], expected, info, stack, context, none())
329329
|> dynamic_unless_static(stack)
330330
end
331331

@@ -345,15 +345,15 @@ defmodule Module.Types.Expr do
345345
{fun_from_inferred_clauses(acc), context}
346346
end
347347

348-
def of_expr({:try, _meta, [[do: body] ++ blocks]}, expected, expr, stack, original) do
348+
def of_expr({:try, meta, [[do: body] ++ blocks]}, expected, expr, stack, original) do
349349
{after_block, blocks} = Keyword.pop(blocks, :after)
350350
{else_block, blocks} = Keyword.pop(blocks, :else)
351351

352352
{type, context} =
353353
if else_block do
354354
{type, context} = of_expr(body, @pending, body, stack, original)
355-
info = {:try_else, type}
356-
of_clauses(else_block, [type], expected, expr, info, stack, context, none())
355+
info = {:try_else, meta, body, type}
356+
of_redundant_clauses(else_block, [type], expected, info, stack, context, none())
357357
else
358358
of_expr(body, expected, expr, stack, original)
359359
end
@@ -378,7 +378,7 @@ defmodule Module.Types.Expr do
378378

379379
{:catch, clauses}, {acc, context} ->
380380
args = [@try_catch, dynamic()]
381-
of_clauses(clauses, args, expected, expr, :try_catch, stack, context, acc)
381+
of_redundant_clauses(clauses, args, expected, :try_catch, stack, context, acc)
382382
end)
383383
|> dynamic_unless_static(stack)
384384

@@ -399,7 +399,7 @@ defmodule Module.Types.Expr do
399399
acc_context
400400

401401
{:do, clauses}, {acc, context} ->
402-
of_redundant_clauses(clauses, dynamic(), expected, :receive, stack, context, acc)
402+
of_redundant_clauses(clauses, [dynamic()], expected, :receive, stack, context, acc)
403403

404404
{:after, [{:->, meta, [[timeout], body]}] = after_expr}, {acc, context} ->
405405
{timeout_type, context} = of_expr(timeout, @timeout_type, after_expr, stack, context)
@@ -742,23 +742,22 @@ defmodule Module.Types.Expr do
742742
%{failed: failed?} = original
743743

744744
{result, _previous, context} =
745-
Enum.reduce(clauses, {acc, none(), original}, fn
746-
{:->, meta, [[clause] = head, body]}, {acc, previous, context} ->
745+
Enum.reduce(clauses, {acc, [], original}, fn
746+
{:->, meta, [head, body]} = clause, {acc, previous, context} ->
747747
{failed?, context} = reset_failed(context, failed?)
748748
{patterns, guards} = extract_head(head)
749-
750-
info = {clause_info, [domain], [previous]}
749+
info = {clause_info, head, previous}
751750

752751
{trees, precise?, context} =
753-
Pattern.of_head(patterns, guards, [domain], [previous], info, meta, stack, context)
752+
Pattern.of_head(patterns, guards, domain, previous, info, meta, stack, context)
754753

755754
# It failed, let's try to detect if it was due a previous or current clause.
756755
# The current clause will be easier to understand, so we prefer that
757-
{[{clause_tree, _, _}], precise?, context} =
758-
if context.failed and previous != none() do
759-
info = {clause_info, [domain], [none()]}
756+
{trees, precise?, context} =
757+
if context.failed and previous != [] do
758+
info = {clause_info, head, []}
760759

761-
case Pattern.of_head(patterns, guards, [domain], info, meta, stack, context) do
760+
case Pattern.of_head(patterns, guards, domain, info, meta, stack, context) do
762761
{_, _, %{failed: true}} = result -> result
763762
_ -> {trees, precise?, context}
764763
end
@@ -770,15 +769,21 @@ defmodule Module.Types.Expr do
770769
if context.failed do
771770
{previous, context}
772771
else
773-
clause_type = Pattern.of_pattern_tree(clause_tree, stack, context) |> upper_bound()
772+
clause_type =
773+
Enum.map(trees, fn {tree, _, _} ->
774+
tree
775+
|> Pattern.of_pattern_tree(stack, context)
776+
|> upper_bound()
777+
end)
774778

775779
cond do
776-
stack.mode != :infer and previous != none() and subtype?(clause_type, previous) ->
780+
stack.mode != :infer and previous != [] and
781+
Pattern.args_subtype?(clause_type, previous) ->
777782
stack = %{stack | meta: meta}
778-
{previous, Pattern.badpattern_error(clause, 0, info, stack, context)}
783+
{previous, Pattern.badpattern_error(clause, nil, info, stack, context)}
779784

780785
precise? ->
781-
{union(previous, clause_type), context}
786+
{[clause_type | previous], context}
782787

783788
true ->
784789
{previous, context}

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

Lines changed: 64 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,22 @@ defmodule Module.Types.Pattern do
88
alias Module.Types.{Apply, Of}
99
import Module.Types.{Helpers, Descr}
1010

11+
@doc """
12+
Defines if two list of arguments are subtypes of each other.
13+
"""
14+
def args_subtype?([], []),
15+
do: true
16+
17+
def args_subtype?([type], previous),
18+
do: subtype?(type, Enum.reduce(previous, none(), &union(&2, hd(&1))))
19+
20+
def args_subtype?(args, previous) do
21+
subtype?(
22+
args_to_domain(args),
23+
Enum.reduce(previous, none(), &union(&2, args_to_domain(&1)))
24+
)
25+
end
26+
1127
@doc """
1228
Refine the dependencies of variables represented by version.
1329
"""
@@ -127,7 +143,7 @@ defmodule Module.Types.Pattern do
127143
4. Then we propagate all dependencies to refine variables
128144
129145
"""
130-
def of_head(patterns, guards, expected, previous \\ nil, tag, meta, stack, context) do
146+
def of_head(patterns, guards, expected, previous \\ [], tag, meta, stack, context) do
131147
%{vars: vars} = context
132148
stack = %{stack | meta: meta}
133149

@@ -216,7 +232,7 @@ defmodule Module.Types.Pattern do
216232

217233
with {:ok, types} <- of_pattern_intersect(args, 0, [], pattern_info, tag, stack, context),
218234
{[type], changed, context} <-
219-
of_pattern_refine(types, nil, pattern_info, tag, stack, context) do
235+
of_pattern_refine(types, [], pattern_info, tag, stack, context) do
220236
{type, of_changed(changed, stack, context)}
221237
else
222238
{:error, context} -> {expected, context}
@@ -237,7 +253,7 @@ defmodule Module.Types.Pattern do
237253

238254
with {:ok, types} <- of_pattern_intersect(args, 0, [], pattern_info, tag, stack, context),
239255
{_types, changed, context} <-
240-
of_pattern_refine(types, nil, pattern_info, tag, stack, context) do
256+
of_pattern_refine(types, [], pattern_info, tag, stack, context) do
241257
{_precise?, context} = of_guards(guards, changed, vars, stack, context)
242258
context
243259
else
@@ -265,13 +281,17 @@ defmodule Module.Types.Pattern do
265281

266282
defp of_pattern_refine(types, previous, pattern_info, tag, stack, context) do
267283
types =
268-
case previous do
269-
nil ->
284+
case types do
285+
_ when previous == [] ->
270286
types
271287

272-
[previous] ->
273-
[type] = types
274-
[difference(type, previous)]
288+
[type] ->
289+
[Enum.reduce(previous, type, &difference(&2, hd(&1)))]
290+
291+
[_ | _] ->
292+
previous
293+
|> Enum.reduce(args_to_domain(types), &difference(&2, args_to_domain(&1)))
294+
|> domain_to_flat_args(types)
275295
end
276296

277297
try do
@@ -1346,15 +1366,17 @@ defmodule Module.Types.Pattern do
13461366
# $ type tag = head_pattern() or match_pattern()
13471367
#
13481368
# $ typep head_pattern =
1349-
# :for_reduce or :with_else or :receive or :try_catch or :fn or :default or
1350-
# {:try_else, type} or {{:case, meta, expr}, [domain], [previous_type]}
1369+
# :for_reduce or :with_else or :fn or :default or
1370+
# {{:case | :try_else, meta, expr, type}, [arg], [previous]} or
1371+
# {:receive | :try_catch, [arg], [previous]}
13511372
#
13521373
# $ typep match_pattern =
13531374
# :with or :for or {:match, type}
13541375
#
13551376
# The match pattern ones have the whole expression instead
13561377
# of a single pattern.
13571378
def format_diagnostic({:badpattern, meta, pattern_or_expr, index, tag, context}) do
1379+
# TODO: stop passing pattern_or_expr as argument
13581380
{to_trace, message} = badpattern(tag, pattern_or_expr, index)
13591381
traces = collect_traces(to_trace, context)
13601382

@@ -1370,24 +1392,9 @@ defmodule Module.Types.Pattern do
13701392
}
13711393
end
13721394

1373-
defp badpattern({:try_else, type}, pattern, _) do
1374-
{pattern,
1375-
"""
1376-
the following clause will never match:
1377-
1378-
#{expr_to_string(pattern) |> indent(4)}
1379-
1380-
it attempts to match on the result of the try do-block which has incompatible type:
1381-
1382-
#{to_quoted_string(type) |> indent(4)}
1383-
"""}
1384-
end
1385-
1386-
defp badpattern({{:case, meta, expr}, [type], [previous_type]}, pattern, _) do
1395+
defp badpattern({{op, meta, expr, type}, args, previous}, _, _) when op in [:case, :try_else] do
13871396
cond do
13881397
meta[:type_check] == :expr ->
1389-
error_type = if previous_type == none(), do: type, else: previous_type
1390-
13911398
{expr,
13921399
"""
13931400
the following conditional expression:
@@ -1396,15 +1403,15 @@ defmodule Module.Types.Pattern do
13961403
13971404
will always evaluate to:
13981405
1399-
#{to_quoted_string(error_type) |> indent(4)}
1406+
#{to_quoted_string(type) |> indent(4)}
14001407
"""}
14011408

1402-
previous_type == none() ->
1403-
{pattern,
1409+
previous == [] ->
1410+
{args,
14041411
"""
14051412
the following clause will never match:
14061413
1407-
#{expr_to_string(pattern) |> indent(4)} ->
1414+
#{args_to_string(args) |> indent(4)} ->
14081415
14091416
because it attempts to match on the result of:
14101417
@@ -1415,46 +1422,46 @@ defmodule Module.Types.Pattern do
14151422
#{to_quoted_string(type) |> indent(4)}
14161423
"""}
14171424

1418-
subtype?(type, previous_type) ->
1419-
{pattern,
1425+
args_subtype?([type], previous) ->
1426+
{args,
14201427
"""
14211428
the following clause cannot match because the previous clauses already matched all possible values:
14221429
1423-
#{expr_to_string(pattern) |> indent(4)} ->
1430+
#{args_to_string(args) |> indent(4)} ->
14241431
14251432
it attempts to match on the result of:
14261433
14271434
#{expr_to_string(expr) |> indent(4)}
14281435
1429-
and the following types have already been matched:
1436+
which has the already matched type:
14301437
1431-
#{to_quoted_string(previous_type) |> indent(4)}
1438+
#{to_quoted_string(type) |> indent(4)}
14321439
"""}
14331440

14341441
true ->
1435-
{pattern,
1442+
{args,
14361443
"""
14371444
the following clause is redundant:
14381445
1439-
#{expr_to_string(pattern) |> indent(4)} ->
1446+
#{args_to_string(args) |> indent(4)} ->
14401447
1441-
the following types are expected (and have already been matched):
1448+
previous clauses have already matched on the following types:
14421449
1443-
#{to_quoted_string(previous_type) |> indent(4)}
1450+
#{previous_to_string(previous)}
14441451
"""}
14451452
end
14461453
end
14471454

1448-
defp badpattern({:receive, [_type], [previous_type]}, pattern, _) do
1449-
{pattern,
1455+
defp badpattern({op, args, previous}, _, _) when op in [:receive, :try_catch] do
1456+
{args,
14501457
"""
14511458
the following clause is redundant:
14521459
1453-
#{expr_to_string(pattern) |> indent(4)} ->
1460+
#{args_to_string(args) |> indent(4)} ->
14541461
14551462
previous clauses have already matched on the following types:
14561463
1457-
#{to_quoted_string(previous_type) |> indent(4)}
1464+
#{previous_to_string(previous)}
14581465
"""}
14591466
end
14601467

@@ -1504,4 +1511,18 @@ defmodule Module.Types.Pattern do
15041511
#{expr_to_string(pattern_or_expr) |> indent(4)}
15051512
"""}
15061513
end
1514+
1515+
defp args_to_string(args) do
1516+
args
1517+
|> Enum.map_join(", ", &expr_to_string/1)
1518+
|> indent(4)
1519+
end
1520+
1521+
defp previous_to_string(previous) do
1522+
Enum.map_join(previous, "\n ", fn types ->
1523+
types
1524+
|> Enum.map_join(", ", &to_quoted_string/1)
1525+
|> indent(4)
1526+
end)
1527+
end
15071528
end

0 commit comments

Comments
 (0)