Skip to content

Commit 2d6e61c

Browse files
committed
Compute negated types from precise none() branches
1 parent 81dc365 commit 2d6e61c

5 files changed

Lines changed: 88 additions & 31 deletions

File tree

lib/elixir/lib/module/types.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ defmodule Module.Types do
329329
info = {base_info, args, guards}
330330

331331
try do
332-
{trees, head_no_previous_args_types, previous, head_context} =
332+
{trees, _precise?, head_no_previous_args_types, previous, head_context} =
333333
Pattern.of_head(args, guards, expected, previous, info, meta, stack, fresh_context)
334334

335335
{return_type, context} =

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

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -369,19 +369,41 @@ defmodule Module.Types.Expr do
369369
of_expr(body, expected, body, stack, reset_warnings(refined_context, context))
370370
end
371371

372-
result_context =
373-
cache_arrows(meta, stack, fn ->
374-
of_clauses_fun(clauses, [case_type], info, stack, context, of_body, [], fn
375-
trees, body_type, context, acc ->
376-
[arg_type] = Pattern.of_domain(trees, stack, context)
377-
[{arg_type, body_type} | acc]
378-
end)
379-
end) ||
380-
of_clauses_fun(clauses, [case_type], info, stack, context, of_body, none(), fn
381-
_trees, body_type, _context, acc -> union(acc, body_type)
382-
end)
372+
{{head_type, body_type}, _, context} =
373+
cache_arrows(meta, stack, fn cache? ->
374+
acc = {none(), none(), []}
375+
376+
{{head_acc, body_acc, clauses_acc}, context} =
377+
of_clauses_fun(clauses, [case_type], info, stack, context, of_body, acc, fn
378+
trees, precise?, body_type, context, {head_acc, body_acc, clauses_acc} ->
379+
cond do
380+
precise? and empty?(body_type) ->
381+
[arg_type] = Pattern.of_domain(trees, stack, context)
382+
{union(negation(arg_type), head_acc), body_acc, clauses_acc}
383+
384+
cache? ->
385+
[arg_type] = Pattern.of_domain(trees, stack, context)
386+
{head_acc, union(body_type, body_acc), [{arg_type, body_type} | clauses_acc]}
387+
388+
true ->
389+
{head_acc, union(body_type, body_acc), clauses_acc}
390+
end
391+
end)
392+
393+
{{head_acc, body_acc}, clauses_acc, context}
394+
end)
395+
396+
context =
397+
if head_type == none() do
398+
context
399+
else
400+
{_, refined_context} =
401+
of_expr(case_expr, head_type, case_expr, %{stack | reverse_arrow: :use}, context)
383402

384-
dynamic_unless_static(result_context, stack)
403+
reset_warnings(refined_context, context)
404+
end
405+
406+
dynamic_unless_static({body_type, context}, stack)
385407
end
386408

387409
# fn pat -> expr end
@@ -395,7 +417,7 @@ defmodule Module.Types.Expr do
395417

396418
{acc, context} =
397419
of_clauses_fun(clauses, domain, :fn, stack, context, of_body, [], fn
398-
trees, body_type, context, acc ->
420+
trees, _precise?, body_type, context, acc ->
399421
args_types = Pattern.of_domain(trees, stack, context)
400422
add_inferred(acc, args_types, body_type)
401423
end)
@@ -828,19 +850,18 @@ defmodule Module.Types.Expr do
828850
end
829851
end
830852

831-
defp cache_arrows(_meta, %{reverse_arrow: nil}, _fun), do: nil
853+
defp cache_arrows(_meta, %{reverse_arrow: nil}, fun), do: fun.(false)
832854

833855
defp cache_arrows(meta, %{reverse_arrow: :cache}, fun) do
834-
{clauses, context} = fun.()
856+
{result, cache, context} = fun.(true)
835857
version = Keyword.fetch!(meta, :version)
836-
context = put_in(context.reverse_arrows[version], clauses)
837-
result = Enum.reduce(clauses, none(), &union(elem(&1, 1), &2))
838-
{result, context}
858+
context = put_in(context.reverse_arrows[version], cache)
859+
{result, cache, context}
839860
end
840861

841862
defp of_clauses(clauses, domain, expected, base_info, stack, context, acc) do
842863
of_body = fn _args_types, body, context -> of_expr(body, expected, body, stack, context) end
843-
of_acc = fn _args_types, body_type, _context, acc -> union(acc, body_type) end
864+
of_acc = fn _args_types, _precise?, body_type, _context, acc -> union(acc, body_type) end
844865
of_clauses_fun(clauses, domain, base_info, stack, context, of_body, acc, of_acc)
845866
end
846867

@@ -854,12 +875,12 @@ defmodule Module.Types.Expr do
854875
{patterns, guards} = extract_head(head)
855876
info = {base_info, head}
856877

857-
{trees, _, previous, context} =
878+
{trees, precise?, _, previous, context} =
858879
Pattern.of_head(patterns, guards, domain, previous, info, meta, stack, context)
859880

860881
{result, context} = of_body.(trees, body, context)
861882

862-
{of_acc.(trees, result, context, acc), previous,
883+
{of_acc.(trees, precise?, result, context, acc), previous,
863884
context |> set_failed(failed?) |> Of.reset_vars(original)}
864885
end)
865886

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -192,28 +192,28 @@ defmodule Module.Types.Pattern do
192192
# First we check if it fails without previous, if it doesn't, check if it is redundant.
193193
case of_precise_head(patterns, guards, expected, init_previous(), tag, stack, original) do
194194
{other_trees, _, _, _, %{failed: true} = other_context} ->
195-
{other_trees, args_types, previous, other_context}
195+
{other_trees, false, args_types, previous, other_context}
196196

197197
{other_trees, _, _, args_types, other_context} ->
198198
if previous_subtype?(args_types, previous) do
199199
warning = {:redundant, tag, expected, args_types, previous, other_context}
200200
context = warn(__MODULE__, warning, meta, stack, other_context)
201-
{other_trees, args_types, previous, context}
201+
{other_trees, false, args_types, previous, context}
202202
else
203-
{trees, args_types, previous, context}
203+
{trees, false, args_types, previous, context}
204204
end
205205
end
206206
else
207207
cond do
208208
check_previous? and previous_subtype?(args_types, previous) ->
209209
warning = {:redundant, tag, expected, args_types, previous, context}
210-
{trees, args_types, previous, warn(__MODULE__, warning, meta, stack, context)}
210+
{trees, false, args_types, previous, warn(__MODULE__, warning, meta, stack, context)}
211211

212212
precise? ->
213-
{trees, args_types, concat_previous(args_types, previous), context}
213+
{trees, true, args_types, concat_previous(args_types, previous), context}
214214

215215
true ->
216-
{trees, args_types, previous, context}
216+
{trees, false, args_types, previous, context}
217217
end
218218
end
219219
end

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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1812,6 +1812,42 @@ defmodule Module.Types.ExprTest do
18121812
atom([:non_empty_map, :maybe_empty_map])
18131813
end
18141814

1815+
test "computes types from dead branches" do
1816+
assert typecheck!(
1817+
[x],
1818+
(
1819+
if is_integer(x) do
1820+
raise "bad"
1821+
end
1822+
1823+
x
1824+
)
1825+
) == dynamic(negation(integer()))
1826+
1827+
assert typecheck!(
1828+
[x],
1829+
(
1830+
if x == :foo do
1831+
raise "bad"
1832+
end
1833+
1834+
x
1835+
)
1836+
) == dynamic(negation(atom([:foo])))
1837+
1838+
# When it is not precise enough, we don't filter
1839+
assert typecheck!(
1840+
[x],
1841+
(
1842+
if x == "foo" do
1843+
raise "bad"
1844+
end
1845+
1846+
x
1847+
)
1848+
) == dynamic()
1849+
end
1850+
18151851
test "warns on redundant clauses" do
18161852
assert typewarn!(
18171853
[x],

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,10 +156,10 @@ defmodule TypeHelper do
156156
init_previous = Pattern.init_previous()
157157
tag = {:fn, patterns}
158158

159-
{_trees, _, previous, _context} =
159+
{_trees, precise?, _args_types, _previous, _context} =
160160
Pattern.of_head(patterns, guards, expected, init_previous, tag, [], stack, new_context())
161161

162-
previous != init_previous
162+
precise?
163163
end
164164

165165
def __typecheck__(mode, patterns, guards, body) do
@@ -168,7 +168,7 @@ defmodule TypeHelper do
168168
previous = Pattern.init_previous()
169169
tag = {:fn, patterns}
170170

171-
{_trees, _, _, context} =
171+
{_trees, _, _, _, context} =
172172
Pattern.of_head(patterns, guards, expected, previous, tag, [], stack, new_context())
173173

174174
Expr.of_expr(body, Descr.term(), :ok, stack, context)

0 commit comments

Comments
 (0)