Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions lib/elixir/lib/module/types/expr.ex
Original file line number Diff line number Diff line change
Expand Up @@ -634,7 +634,10 @@ defmodule Module.Types.Expr do
# is ok for now because we only check for bitstring if the type
# is a subset of empty_list() or bitstring(), but we may want to
# relax in the future.
if empty?(intersection) do
#
# If the collectable may also be a list, the body may be valid
# on that path, so we only error when the list path is impossible.
if empty?(intersection) and :non_empty_list not in into_kinds do

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can remove the TODO above because I believe it is talking about exactly this case!

error = {:badbitbody, block_type, block, context}
{error_type(), error(__MODULE__, error, meta, stack, context)}
else
Expand Down Expand Up @@ -864,12 +867,16 @@ defmodule Module.Types.Expr do
# only bitstring/list, even if a dynamic with something else is given.
if subtype?(type, @into_compile) do
cond do
# A comprehension may concatenate the block an arbitrary number of times.
# Even if both the initial value and each block are unaligned bitstrings,
# repeated concatenation may eventually produce an aligned binary.
bitstring_type?(type) and empty_list_type?(type) ->
Comment on lines 869 to +873

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's convert this into a case {bitstring_type?(type), empty_list_type?(type)}.

# The collectable may be a list, which accepts any element,
# so we cannot restrict the body to bitstrings.
{[:bitstring, :non_empty_list], opt_union(binary(), type), term(), context}

bitstring_type?(type) ->
kinds = if empty_list_type?(type), do: [:bitstring, :non_empty_list], else: [:bitstring]
# A comprehension may concatenate the block an arbitrary number of times.
# Even if both the initial value and each block are unaligned bitstrings,
# repeated concatenation may eventually produce an aligned binary.
{kinds, opt_union(binary(), type), bitstring(), context}
{[:bitstring], opt_union(binary(), type), bitstring(), context}

empty_list_type?(type) ->
{[:non_empty_list], type, maybe_list_hd_or_term(expected), context}
Expand Down
36 changes: 36 additions & 0 deletions lib/elixir/test/elixir/module/types/expr_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3586,6 +3586,42 @@ defmodule Module.Types.ExprTest do
dynamic(
opt_union(opt_union(bitstring(), empty_list()), list(bitstring_no_binary()))
)

# The list path accepts any element, so the body is not restricted to bitstrings
assert typecheck!(
[flag],
(
into = if flag, do: [], else: ""
value = if flag, do: :ok, else: "ok"
for(_ <- [1], do: value, into: into)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this should type check in the static version either. Because if you write it as value = if !flag, it will fail, and the type system would accept it.

)
) == opt_union(binary(), list(opt_union(atom([:ok]), binary())))

assert typedyn!(
[flag],
(
into = if flag, do: [], else: ""
value = if flag, do: :ok, else: "ok"
for(_ <- [1], do: value, into: into)
)
) == dynamic(opt_union(binary(), list(opt_union(atom([:ok]), binary()))))

assert typecheck!(
[flag],
(
into = if flag, do: [], else: ""
for(_ <- [1], do: :ok, into: into)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not type check in the static version. It would type check in the dynamic one.

)
) == opt_union(binary(), list(atom([:ok])))

assert typecheck!(
[flag, value],
(
into = if flag, do: [], else: ""
for(_ <- [1], do: value, into: into)
value
)
) == dynamic()
end

test ":into bitstrings" do
Expand Down
Loading