Skip to content

Commit 4ee0241

Browse files
authored
Represent bitstring and binary as distinct types (#15038)
1 parent 1b234ca commit 4ee0241

14 files changed

Lines changed: 275 additions & 149 deletions

File tree

lib/elixir/lib/module/types/apply.ex

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -168,13 +168,13 @@ defmodule Module.Types.Apply do
168168
{:erlang, :binary_to_integer, [{[binary()], integer()}]},
169169
{:erlang, :binary_to_integer, [{[binary(), integer()], integer()}]},
170170
{:erlang, :binary_to_float, [{[binary()], float()}]},
171-
{:erlang, :bit_size, [{[binary()], integer()}]},
171+
{:erlang, :bit_size, [{[bitstring()], integer()}]},
172172
{:erlang, :bnot, [{[integer()], integer()}]},
173173
{:erlang, :bor, [{[integer(), integer()], integer()}]},
174174
{:erlang, :bsl, [{[integer(), integer()], integer()}]},
175175
{:erlang, :bsr, [{[integer(), integer()], integer()}]},
176176
{:erlang, :bxor, [{[integer(), integer()], integer()}]},
177-
{:erlang, :byte_size, [{[binary()], integer()}]},
177+
{:erlang, :byte_size, [{[bitstring()], integer()}]},
178178
{:erlang, :ceil, [{[union(integer(), float())], integer()}]},
179179
{:erlang, :div, [{[integer(), integer()], integer()}]},
180180
{:erlang, :error, [{[term()], none()}]},
@@ -288,7 +288,7 @@ defmodule Module.Types.Apply do
288288
is_guards = [
289289
is_atom: atom(),
290290
is_binary: binary(),
291-
is_bitstring: binary(),
291+
is_bitstring: bitstring(),
292292
is_boolean: boolean(),
293293
is_float: float(),
294294
is_function: fun(),
@@ -303,13 +303,10 @@ defmodule Module.Types.Apply do
303303
]
304304

305305
for {guard, type} <- is_guards do
306-
# is_binary can actually fail for binaries if they are bitstrings
307-
return = if guard == :is_binary, do: boolean(), else: atom([true])
308-
309306
domain_clauses =
310307
{:strong, [term()],
311308
[
312-
{[type], return},
309+
{[type], atom([true])},
313310
{[negation(type)], atom([false])}
314311
]}
315312

@@ -1282,6 +1279,17 @@ defmodule Module.Types.Apply do
12821279
end
12831280
end
12841281

1282+
@doc """
1283+
Computes the return type of an application.
1284+
"""
1285+
def return(type, args_types, stack) do
1286+
cond do
1287+
stack.mode == :static -> type
1288+
Enum.any?(args_types, &gradual?/1) -> dynamic(type)
1289+
true -> type
1290+
end
1291+
end
1292+
12851293
## Map helpers
12861294

12871295
defp map_put_new(map, key, value, name, args_types, stack) do
@@ -1325,14 +1333,6 @@ defmodule Module.Types.Apply do
13251333

13261334
## Application helpers
13271335

1328-
defp return(type, args_types, stack) do
1329-
cond do
1330-
stack.mode == :static -> type
1331-
Enum.any?(args_types, &gradual?/1) -> dynamic(type)
1332-
true -> type
1333-
end
1334-
end
1335-
13361336
defp domain(nil, [{domain, _}]), do: domain
13371337
defp domain(domain, _clauses), do: domain
13381338

lib/elixir/lib/module/types/descr.ex

Lines changed: 39 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,23 @@ defmodule Module.Types.Descr do
1616

1717
import Bitwise
1818

19-
@bit_binary 1 <<< 0
20-
@bit_empty_list 1 <<< 1
21-
@bit_integer 1 <<< 2
22-
@bit_float 1 <<< 3
23-
@bit_pid 1 <<< 4
24-
@bit_port 1 <<< 5
25-
@bit_reference 1 <<< 6
26-
@bit_top (1 <<< 7) - 1
19+
@bit_binary 0b1
20+
@bit_bitstring_no_binary 0b10
21+
@bit_empty_list 0b100
22+
@bit_integer 0b1000
23+
@bit_float 0b10000
24+
@bit_pid 0b100000
25+
@bit_port 0b1000000
26+
@bit_reference 0b10000000
27+
@bit_top 0b11111111
28+
29+
# We use two bits to represent bitstrings and binaries,
30+
# which must be looked at together
31+
# bitstring = 0b11
32+
# bitstring and not binary = 0b10
33+
# binary = 0b01
34+
# none = 0b00
35+
@bit_bitstring @bit_binary ||| @bit_bitstring_no_binary
2736
@bit_number @bit_integer ||| @bit_float
2837

2938
defmacro bdd_leaf(arg1, arg2), do: {arg1, arg2}
@@ -80,6 +89,8 @@ defmodule Module.Types.Descr do
8089
def atom(as), do: %{atom: atom_new(as)}
8190
def atom(), do: %{atom: @atom_top}
8291
def binary(), do: %{bitmap: @bit_binary}
92+
def bitstring(), do: %{bitmap: @bit_bitstring}
93+
def bitstring_no_binary(), do: %{bitmap: @bit_bitstring_no_binary}
8394
def closed_map(pairs), do: map_descr(:closed, pairs)
8495
def empty_list(), do: %{bitmap: @bit_empty_list}
8596
def empty_map(), do: %{map: @map_empty}
@@ -843,29 +854,14 @@ defmodule Module.Types.Descr do
843854
@doc """
844855
Optimized version of `not empty?(intersection(binary(), type))`.
845856
"""
846-
def binary_type?(:term), do: true
847-
def binary_type?(%{dynamic: :term}), do: true
848-
def binary_type?(%{dynamic: %{bitmap: bitmap}}) when (bitmap &&& @bit_binary) != 0, do: true
849-
def binary_type?(%{bitmap: bitmap}) when (bitmap &&& @bit_binary) != 0, do: true
850-
def binary_type?(_), do: false
857+
def bitstring_type?(:term), do: true
858+
def bitstring_type?(%{dynamic: :term}), do: true
851859

852-
@doc """
853-
Optimized version of `not empty?(intersection(integer(), type))`.
854-
"""
855-
def integer_type?(:term), do: true
856-
def integer_type?(%{dynamic: :term}), do: true
857-
def integer_type?(%{dynamic: %{bitmap: bitmap}}) when (bitmap &&& @bit_integer) != 0, do: true
858-
def integer_type?(%{bitmap: bitmap}) when (bitmap &&& @bit_integer) != 0, do: true
859-
def integer_type?(_), do: false
860+
def bitstring_type?(%{dynamic: %{bitmap: bitmap}}) when (bitmap &&& @bit_bitstring) != 0,
861+
do: true
860862

861-
@doc """
862-
Optimized version of `not empty?(intersection(float(), type))`.
863-
"""
864-
def float_type?(:term), do: true
865-
def float_type?(%{dynamic: :term}), do: true
866-
def float_type?(%{dynamic: %{bitmap: bitmap}}) when (bitmap &&& @bit_float) != 0, do: true
867-
def float_type?(%{bitmap: bitmap}) when (bitmap &&& @bit_float) != 0, do: true
868-
def float_type?(_), do: false
863+
def bitstring_type?(%{bitmap: bitmap}) when (bitmap &&& @bit_bitstring) != 0, do: true
864+
def bitstring_type?(_), do: false
869865

870866
@doc """
871867
Optimized version of `not empty?(intersection(integer() or float(), type))`.
@@ -881,7 +877,6 @@ defmodule Module.Types.Descr do
881877
defp bitmap_to_quoted(val) do
882878
pairs =
883879
[
884-
binary: @bit_binary,
885880
empty_list: @bit_empty_list,
886881
integer: @bit_integer,
887882
float: @bit_float,
@@ -890,9 +885,17 @@ defmodule Module.Types.Descr do
890885
reference: @bit_reference
891886
]
892887

893-
for {type, mask} <- pairs,
894-
(mask &&& val) !== 0,
895-
do: {type, [], []}
888+
quoted =
889+
for {type, mask} <- pairs,
890+
(mask &&& val) !== 0,
891+
do: {type, [], []}
892+
893+
case val &&& @bit_bitstring do
894+
0 -> quoted
895+
1 -> [{:binary, [], []} | quoted]
896+
2 -> [{:and, [], [{:bitstring, [], []}, {:not, [], [{:binary, [], []}]}]} | quoted]
897+
3 -> [{:bitstring, [], []} | quoted]
898+
end
896899
end
897900

898901
## Atoms
@@ -2368,7 +2371,7 @@ defmodule Module.Types.Descr do
23682371

23692372
defp indivisible_bitmap(descr, opts) do
23702373
with true <- Keyword.get(opts, :skip_dynamic_for_indivisible, true),
2371-
%{bitmap: bitmap} when map_size(descr) == 1 <- descr,
2374+
%{bitmap: bitmap} when map_size(descr) == 1 and bitmap != @bit_bitstring <- descr,
23722375
[single] <- bitmap_to_quoted(bitmap) do
23732376
single
23742377
else
@@ -2423,6 +2426,7 @@ defmodule Module.Types.Descr do
24232426

24242427
defp bitmap_to_domain_keys(bitmap, acc) do
24252428
acc = if (bitmap &&& @bit_binary) != 0, do: [:binary | acc], else: acc
2429+
acc = if (bitmap &&& @bit_bitstring_no_binary) != 0, do: [:bitstring | acc], else: acc
24262430
acc = if (bitmap &&& @bit_empty_list) != 0, do: [:list | acc], else: acc
24272431
acc = if (bitmap &&& @bit_integer) != 0, do: [:integer | acc], else: acc
24282432
acc = if (bitmap &&& @bit_float) != 0, do: [:float | acc], else: acc
@@ -2433,6 +2437,7 @@ defmodule Module.Types.Descr do
24332437
end
24342438

24352439
defp domain_key_to_descr(:atom), do: atom()
2440+
defp domain_key_to_descr(:bitstring), do: bitstring_no_binary()
24362441
defp domain_key_to_descr(:binary), do: binary()
24372442
defp domain_key_to_descr(:integer), do: integer()
24382443
defp domain_key_to_descr(:float), do: float()

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

Lines changed: 69 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,8 @@ defmodule Module.Types.Expr do
122122
end
123123

124124
# <<...>>>
125-
def of_expr({:<<>>, _meta, args}, _expected, _expr, stack, context) do
126-
context = Of.binary(args, :expr, stack, context)
127-
{binary(), context}
125+
def of_expr({:<<>>, meta, args}, _expected, _expr, stack, context) do
126+
Of.bitstring(meta, args, :expr, stack, context)
128127
end
129128

130129
def of_expr({:__CALLER__, _meta, var_context}, _expected, _expr, _stack, context)
@@ -315,7 +314,7 @@ defmodule Module.Types.Expr do
315314
|> dynamic_unless_static(stack)
316315
end
317316

318-
# TODO: fn pat -> expr end
317+
# fn pat -> expr end
319318
def of_expr({:fn, _meta, clauses}, _expected, _expr, stack, context) do
320319
[{:->, _, [head, _]} | _] = clauses
321320
{patterns, _guards} = extract_head(head)
@@ -416,20 +415,26 @@ defmodule Module.Types.Expr do
416415
else
417416
# TODO: Use the collectable protocol for the output
418417
into = Keyword.get(opts, :into, [])
419-
{into_wrapper, gradual?, context} = for_into(into, meta, stack, context)
418+
{into_type, into_kind, context} = for_into(into, meta, stack, context)
420419
{block_type, context} = of_expr(block, @pending, block, stack, context)
421420

422-
for_type =
423-
for type <- into_wrapper do
424-
case type do
425-
:binary -> binary()
426-
:list -> list(block_type)
427-
:term -> term()
421+
case into_kind do
422+
:bitstring ->
423+
case compatible_intersection(block_type, bitstring()) do
424+
{:ok, intersection} ->
425+
{return_union(into_type, intersection, stack), context}
426+
427+
{:error, _} ->
428+
error = {:badbitbody, block_type, block, context}
429+
{error_type(), error(__MODULE__, error, meta, stack, context)}
428430
end
429-
end
430-
|> Enum.reduce(&union/2)
431431

432-
{if(gradual?, do: dynamic(for_type), else: for_type), context}
432+
:non_empty_list ->
433+
{return_union(into_type, non_empty_list(block_type), stack), context}
434+
435+
:none ->
436+
{into_type, context}
437+
end
433438
end
434439
end
435440

@@ -469,7 +474,7 @@ defmodule Module.Types.Expr do
469474
apply_many(mods, name, args, expected, call, stack, context)
470475
end
471476

472-
# TODO: &Foo.bar/1
477+
# &Foo.bar/1
473478
def of_expr(
474479
{:&, _, [{:/, _, [{{:., _, [remote, name]}, meta, []}, arity]}]} = call,
475480
_expected,
@@ -483,7 +488,7 @@ defmodule Module.Types.Expr do
483488
Apply.remote_capture(mods, name, arity, meta, stack, context)
484489
end
485490

486-
# TODO: &foo/1
491+
# &foo/1
487492
def of_expr({:&, _meta, [{:/, _, [{fun, meta, _}, arity]}]}, _expected, _expr, stack, context) do
488493
Apply.local_capture(fun, arity, meta, stack, context)
489494
end
@@ -577,13 +582,13 @@ defmodule Module.Types.Expr do
577582
end
578583

579584
defp for_clause({:<<>>, _, [{:<-, meta, [left, right]}]} = expr, stack, context) do
580-
{right_type, context} = of_expr(right, binary(), expr, stack, context)
581-
context = Pattern.of_generator(left, [], binary(), :for, expr, stack, context)
585+
{right_type, context} = of_expr(right, bitstring(), expr, stack, context)
586+
context = Pattern.of_generator(left, [], bitstring(), :for, expr, stack, context)
582587

583-
if compatible?(right_type, binary()) do
588+
if compatible?(right_type, bitstring()) do
584589
context
585590
else
586-
error = {:badbinary, right_type, right, context}
591+
error = {:badbitgenerator, right_type, right, context}
587592
error(__MODULE__, error, meta, stack, context)
588593
end
589594
end
@@ -593,13 +598,13 @@ defmodule Module.Types.Expr do
593598
context
594599
end
595600

596-
@into_compile union(binary(), empty_list())
601+
@into_compile union(bitstring(), empty_list())
597602

598603
defp for_into([], _meta, _stack, context),
599-
do: {[:list], false, context}
604+
do: {empty_list(), :non_empty_list, context}
600605

601606
defp for_into(binary, _meta, _stack, context) when is_binary(binary),
602-
do: {[:binary], false, context}
607+
do: {binary(), :bitstring, context}
603608

604609
defp for_into(into, meta, stack, context) do
605610
meta =
@@ -616,21 +621,33 @@ defmodule Module.Types.Expr do
616621
{type, context} = of_expr(into, domain, expr, stack, context)
617622

618623
# We use subtype? instead of compatible because we want to handle
619-
# only binary/list, even if a dynamic with something else is given.
624+
# only bitstring/list, even if a dynamic with something else is given.
620625
if subtype?(type, @into_compile) do
621-
case {binary_type?(type), empty_list_type?(type)} do
622-
{false, true} -> {[:list], gradual?(type), context}
623-
{true, false} -> {[:binary], gradual?(type), context}
624-
{_, _} -> {[:binary, :list], gradual?(type), context}
626+
case {bitstring_type?(type), empty_list_type?(type)} do
627+
# If they can be both be true, then we don't know
628+
# what the contents of the block are for
629+
{true, true} ->
630+
type = union(bitstring(), list(term()))
631+
{if(gradual?(type), do: dynamic(type), else: type), :none, context}
632+
633+
{false, true} ->
634+
{type, :non_empty_list, context}
635+
636+
{true, false} ->
637+
{type, :bitstring, context}
625638
end
626639
else
627640
{_type, context} =
628641
Apply.remote_apply(info, Collectable, :into, [type], expr, stack, context)
629642

630-
{[:term], true, context}
643+
{dynamic(), :none, context}
631644
end
632645
end
633646

647+
defp return_union(left, right, stack) do
648+
Apply.return(union(left, right), [left, right], stack)
649+
end
650+
634651
## With
635652

636653
defp with_clause({:<-, _meta, [left, right]} = expr, stack, context) do
@@ -866,15 +883,36 @@ defmodule Module.Types.Expr do
866883
}
867884
end
868885

869-
def format_diagnostic({:badbinary, type, expr, context}) do
886+
def format_diagnostic({:badbitgenerator, type, expr, context}) do
887+
traces = collect_traces(expr, context)
888+
889+
%{
890+
details: %{typing_traces: traces},
891+
message:
892+
IO.iodata_to_binary([
893+
"""
894+
expected the right side of <- in a binary generator to be a binary (or bitstring):
895+
896+
#{expr_to_string(expr) |> indent(4)}
897+
898+
but got type:
899+
900+
#{to_quoted_string(type) |> indent(4)}
901+
""",
902+
format_traces(traces)
903+
])
904+
}
905+
end
906+
907+
def format_diagnostic({:badbitbody, type, expr, context}) do
870908
traces = collect_traces(expr, context)
871909

872910
%{
873911
details: %{typing_traces: traces},
874912
message:
875913
IO.iodata_to_binary([
876914
"""
877-
expected the right side of <- in a binary generator to be a binary:
915+
expected the body of a for-comprehension with into: binary() (or bitstring()) to be a binary (or bitstring):
878916
879917
#{expr_to_string(expr) |> indent(4)}
880918

0 commit comments

Comments
 (0)