Skip to content

Commit 3e9fb4d

Browse files
committed
Type checking of case and receive
1 parent 4c7e723 commit 3e9fb4d

File tree

2 files changed

+72
-31
lines changed

2 files changed

+72
-31
lines changed

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

Lines changed: 31 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -239,13 +239,15 @@ defmodule Module.Types.Expr do
239239
# TODO: case expr do pat -> expr end
240240
def of_expr({:case, _meta, [case_expr, [{:do, clauses}]]}, stack, context) do
241241
{_expr_type, context} = of_expr(case_expr, stack, context)
242-
context = of_clauses(clauses, stack, context)
243-
{dynamic(), context}
242+
243+
clauses
244+
|> of_clauses(stack, {none(), context})
245+
|> dynamic_unless_static(stack)
244246
end
245247

246248
# TODO: fn pat -> expr end
247249
def of_expr({:fn, _meta, clauses}, stack, context) do
248-
context = of_clauses(clauses, stack, context)
250+
{_acc, context} = of_clauses(clauses, stack, {none(), context})
249251
{fun(), context}
250252
end
251253

@@ -269,35 +271,34 @@ defmodule Module.Types.Expr do
269271
of_expr_context(body, stack, context)
270272

271273
{block, clauses}, context when block in @try_clause_blocks ->
272-
of_clauses(clauses, stack, context)
274+
{_, context} = of_clauses(clauses, stack, {none(), context})
275+
context
273276
end)
274277

275278
{dynamic(), context}
276279
end
277280

278-
# TODO: receive do pat -> expr end
279281
def of_expr({:receive, _meta, [blocks]}, stack, context) do
280-
context =
281-
Enum.reduce(blocks, context, fn
282-
{:do, {:__block__, _, []}}, context ->
283-
context
284-
285-
{:do, clauses}, context ->
286-
of_clauses(clauses, stack, context)
282+
blocks
283+
|> Enum.reduce({none(), context}, fn
284+
{:do, {:__block__, _, []}}, {acc, context} ->
285+
{acc, context}
287286

288-
{:after, [{:->, meta, [[timeout], body]}]}, context ->
289-
{timeout_type, context} = of_expr(timeout, stack, context)
290-
{_body_type, context} = of_expr(body, stack, context)
287+
{:do, clauses}, {acc, context} ->
288+
of_clauses(clauses, stack, {acc, context})
291289

292-
if integer_type?(timeout_type) do
293-
context
294-
else
295-
error = {:badtimeout, timeout_type, timeout, context}
296-
error(__MODULE__, error, meta, stack, context)
297-
end
298-
end)
290+
{:after, [{:->, meta, [[timeout], body]}]}, {acc, context} ->
291+
{timeout_type, context} = of_expr(timeout, stack, context)
292+
{body_type, context} = of_expr(body, stack, context)
299293

300-
{dynamic(), context}
294+
if integer_type?(timeout_type) do
295+
{union(body_type, acc), context}
296+
else
297+
error = {:badtimeout, timeout_type, timeout, context}
298+
{union(body_type, acc), error(__MODULE__, error, meta, stack, context)}
299+
end
300+
end)
301+
|> dynamic_unless_static(stack)
301302
end
302303

303304
# TODO: for pat <- expr do expr end
@@ -307,7 +308,7 @@ defmodule Module.Types.Expr do
307308
context = Enum.reduce(opts, context, &for_option(&1, stack, &2))
308309

309310
if Keyword.has_key?(opts, :reduce) do
310-
context = of_clauses(block, stack, context)
311+
{_, context} = of_clauses(block, stack, {none(), context})
311312
{dynamic(), context}
312313
else
313314
{_type, context} = of_expr(block, stack, context)
@@ -484,7 +485,8 @@ defmodule Module.Types.Expr do
484485
end
485486

486487
defp with_option({:else, clauses}, stack, context) do
487-
of_clauses(clauses, stack, context)
488+
{_, context} = of_clauses(clauses, stack, {none(), context})
489+
context
488490
end
489491

490492
## General helpers
@@ -515,13 +517,12 @@ defmodule Module.Types.Expr do
515517
defp dynamic_unless_static({_, _} = output, %{mode: :static}), do: output
516518
defp dynamic_unless_static({type, context}, %{mode: _}), do: {dynamic(type), context}
517519

518-
defp of_clauses(clauses, stack, context) do
519-
Enum.reduce(clauses, context, fn {:->, meta, [head, body]}, context ->
520+
defp of_clauses(clauses, stack, acc_context) do
521+
Enum.reduce(clauses, acc_context, fn {:->, meta, [head, body]}, {acc, context} ->
520522
{patterns, guards} = extract_head(head)
521-
522523
{_types, context} = Pattern.of_head(patterns, guards, meta, stack, context)
523-
{_, context} = of_expr(body, stack, context)
524-
context
524+
{body, context} = of_expr(body, stack, context)
525+
{union(acc, body), context}
525526
end)
526527
end
527528

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

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ defmodule Module.Types.ExprTest do
144144
{dynamic(), "URI.unknown/1 is undefined or private"}
145145

146146
assert typewarn!(if(true, do: URI.unknown("foo"))) ==
147-
{dynamic(), "URI.unknown/1 is undefined or private"}
147+
{dynamic() |> union(atom([nil])), "URI.unknown/1 is undefined or private"}
148148

149149
assert typewarn!(try(do: :ok, after: URI.unknown("foo"))) ==
150150
{dynamic(), "URI.unknown/1 is undefined or private"}
@@ -996,7 +996,47 @@ defmodule Module.Types.ExprTest do
996996
end
997997
end
998998

999+
describe "case" do
1000+
test "returns unions of all clauses" do
1001+
assert typecheck!(
1002+
[x],
1003+
case x do
1004+
:ok -> :ok
1005+
:error -> :error
1006+
end
1007+
) == atom([:ok, :error])
1008+
1009+
assert typedyn!(
1010+
[x],
1011+
case x do
1012+
:ok -> :ok
1013+
:error -> :error
1014+
end
1015+
) == dynamic(atom([:ok, :error]))
1016+
end
1017+
end
1018+
9991019
describe "receive" do
1020+
test "returns unions of all clauses" do
1021+
assert typecheck!(
1022+
receive do
1023+
:ok -> :ok
1024+
:error -> :error
1025+
after
1026+
0 -> :timeout
1027+
end
1028+
) == atom([:ok, :error, :timeout])
1029+
1030+
assert typedyn!(
1031+
receive do
1032+
:ok -> :ok
1033+
:error -> :error
1034+
after
1035+
0 -> :timeout
1036+
end
1037+
) == dynamic(atom([:ok, :error, :timeout]))
1038+
end
1039+
10001040
test "errors on bad timeout" do
10011041
assert typeerror!(
10021042
[x = :timeout],

0 commit comments

Comments
 (0)