Skip to content

Commit

Permalink
Properly track gradual types in for comprehensions :into
Browse files Browse the repository at this point in the history
  • Loading branch information
josevalim committed Dec 30, 2024
1 parent 1f9256a commit b0abc2b
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 9 deletions.
18 changes: 9 additions & 9 deletions lib/elixir/lib/module/types/expr.ex
Original file line number Diff line number Diff line change
Expand Up @@ -351,20 +351,20 @@ defmodule Module.Types.Expr do
{dynamic(), context}
else
into = Keyword.get(opts, :into, [])
{into_wrapper, context} = for_into(into, meta, stack, context)
{into_wrapper, gradual?, context} = for_into(into, meta, stack, context)
{block_type, context} = of_expr(block, stack, context)

for_type =
for type <- into_wrapper do
case type do
:binary -> binary()
:list -> list(block_type)
:dynamic -> dynamic()
:term -> term()
end
end
|> Enum.reduce(&union/2)

{for_type, context}
{if(gradual?, do: dynamic(for_type), else: for_type), context}
end
end

Expand Down Expand Up @@ -523,20 +523,20 @@ defmodule Module.Types.Expr do
@into_compile union(binary(), empty_list())

defp for_into([], _meta, _stack, context),
do: {[:list], context}
do: {[:list], false, context}

defp for_into(binary, _meta, _stack, context) when is_binary(binary),
do: {[:binary], context}
do: {[:binary], false, context}

# TODO: Use the collectable protocol for the output
defp for_into(into, meta, stack, context) do
{type, context} = of_expr(into, stack, context)

if subtype?(type, @into_compile) do
case {binary_type?(type), empty_list_type?(type)} do
{false, true} -> {[:list], context}
{true, false} -> {[:binary], context}
{_, _} -> {[:binary, :list], context}
{false, true} -> {[:list], gradual?(type), context}
{true, false} -> {[:binary], gradual?(type), context}
{_, _} -> {[:binary, :list], gradual?(type), context}
end
else
meta =
Expand All @@ -547,7 +547,7 @@ defmodule Module.Types.Expr do

expr = {:__block__, [type_check: :into] ++ meta, [into]}
{_type, context} = Apply.remote(Collectable, :into, [into], [type], expr, stack, context)
{[:dynamic], context}
{[:term], true, context}
end
end

Expand Down
16 changes: 16 additions & 0 deletions lib/elixir/test/elixir/module/types/expr_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -1443,6 +1443,22 @@ defmodule Module.Types.ExprTest do
assert typecheck!([enum], for(x <- enum, do: x, into: [])) == list(dynamic())
assert typecheck!([enum], for(x <- enum, do: x, into: "")) == binary()
assert typecheck!([enum, other], for(x <- enum, do: x, into: other)) == dynamic()

assert typecheck!(
[binary],
(
into = if :rand.uniform() > 0.5, do: [], else: "0"
for(<<x::float <- binary>>, do: x, into: into)
)
) == union(binary(), list(float()))

assert typecheck!(
[binary, empty_list = []],
(
into = if :rand.uniform() > 0.5, do: empty_list, else: "0"
for(<<x::float <- binary>>, do: x, into: into)
)
) == dynamic(union(binary(), list(float())))
end
end

Expand Down

0 comments on commit b0abc2b

Please sign in to comment.