Skip to content

Commit c64a035

Browse files
committed
Track return type of for-comprehensions using :into
1 parent deca849 commit c64a035

File tree

2 files changed

+55
-27
lines changed

2 files changed

+55
-27
lines changed

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

Lines changed: 43 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -341,14 +341,30 @@ defmodule Module.Types.Expr do
341341
def of_expr({:for, meta, [_ | _] = args}, stack, context) do
342342
{clauses, [[{:do, block} | opts]]} = Enum.split(args, -1)
343343
context = Enum.reduce(clauses, context, &for_clause(&1, stack, &2))
344-
context = Enum.reduce(opts, context, &for_option(&1, meta, stack, &2))
345344

345+
# We don't need to type check uniq, as it is a compile-time boolean.
346+
# We handle reduce and into accordingly instead.
346347
if Keyword.has_key?(opts, :reduce) do
348+
reduce = Keyword.fetch!(opts, :reduce)
349+
{_, context} = of_expr(reduce, stack, context)
347350
{_, context} = of_clauses(block, [dynamic()], :for_reduce, stack, {none(), context})
348351
{dynamic(), context}
349352
else
350-
{_type, context} = of_expr(block, stack, context)
351-
{dynamic(), context}
353+
into = Keyword.get(opts, :into, [])
354+
{into_wrapper, context} = for_into(into, meta, stack, context)
355+
{block_type, context} = of_expr(block, stack, context)
356+
357+
for_type =
358+
for type <- into_wrapper do
359+
case type do
360+
:binary -> binary()
361+
:list -> list(block_type)
362+
:dynamic -> dynamic()
363+
end
364+
end
365+
|> Enum.reduce(&union/2)
366+
367+
{for_type, context}
352368
end
353369
end
354370

@@ -504,35 +520,35 @@ defmodule Module.Types.Expr do
504520
context
505521
end
506522

507-
defp for_option({:into, expr}, _meta, _stack, context) when is_list(expr) or is_binary(expr) do
508-
context
509-
end
510-
511-
defp for_option({:into, expr}, meta, stack, context) do
512-
{type, context} = of_expr(expr, stack, context)
523+
@into_compile union(binary(), empty_list())
513524

514-
meta =
515-
case expr do
516-
{_, meta, _} -> meta
517-
_ -> meta
518-
end
525+
defp for_into([], _meta, _stack, context),
526+
do: {[:list], context}
519527

520-
wrapped_expr = {:__block__, [type_check: :into] ++ meta, [expr]}
521-
522-
{_type, context} =
523-
Apply.remote(Collectable, :into, [expr], [type], wrapped_expr, stack, context)
528+
defp for_into(binary, _meta, _stack, context) when is_binary(binary),
529+
do: {[:binary], context}
524530

525-
context
526-
end
531+
# TODO: Use the collectable protocol for the output
532+
defp for_into(into, meta, stack, context) do
533+
{type, context} = of_expr(into, stack, context)
527534

528-
defp for_option({:reduce, expr}, _meta, stack, context) do
529-
{_type, context} = of_expr(expr, stack, context)
530-
context
531-
end
535+
if subtype?(type, @into_compile) do
536+
case {binary_type?(type), empty_list_type?(type)} do
537+
{false, true} -> {[:list], context}
538+
{true, false} -> {[:binary], context}
539+
{_, _} -> {[:binary, :list], context}
540+
end
541+
else
542+
meta =
543+
case into do
544+
{_, meta, _} -> meta
545+
_ -> meta
546+
end
532547

533-
defp for_option({:uniq, _}, _meta, _stack, context) do
534-
# This option is verified to be a boolean at compile-time
535-
context
548+
expr = {:__block__, [type_check: :into] ++ meta, [into]}
549+
{_type, context} = Apply.remote(Collectable, :into, [into], [type], expr, stack, context)
550+
{[:dynamic], context}
551+
end
536552
end
537553

538554
## With

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1432,6 +1432,18 @@ defmodule Module.Types.ExprTest do
14321432
#{hints(:inferred_bitstring_spec)}
14331433
"""
14341434
end
1435+
1436+
test ":into" do
1437+
assert typecheck!([binary], for(<<x <- binary>>, do: x)) == list(integer())
1438+
assert typecheck!([binary], for(<<x <- binary>>, do: x, into: [])) == list(integer())
1439+
assert typecheck!([binary], for(<<x <- binary>>, do: x, into: "")) == binary()
1440+
assert typecheck!([binary, other], for(<<x <- binary>>, do: x, into: other)) == dynamic()
1441+
1442+
assert typecheck!([enum], for(x <- enum, do: x)) == list(dynamic())
1443+
assert typecheck!([enum], for(x <- enum, do: x, into: [])) == list(dynamic())
1444+
assert typecheck!([enum], for(x <- enum, do: x, into: "")) == binary()
1445+
assert typecheck!([enum, other], for(x <- enum, do: x, into: other)) == dynamic()
1446+
end
14351447
end
14361448

14371449
describe "info" do

0 commit comments

Comments
 (0)