diff --git a/lib/elixir/lib/module/types/apply.ex b/lib/elixir/lib/module/types/apply.ex index a06949580bb..61e8511449b 100644 --- a/lib/elixir/lib/module/types/apply.ex +++ b/lib/elixir/lib/module/types/apply.ex @@ -168,13 +168,13 @@ defmodule Module.Types.Apply do {:erlang, :binary_to_integer, [{[binary()], integer()}]}, {:erlang, :binary_to_integer, [{[binary(), integer()], integer()}]}, {:erlang, :binary_to_float, [{[binary()], float()}]}, - {:erlang, :bit_size, [{[binary()], integer()}]}, + {:erlang, :bit_size, [{[bitstring()], integer()}]}, {:erlang, :bnot, [{[integer()], integer()}]}, {:erlang, :bor, [{[integer(), integer()], integer()}]}, {:erlang, :bsl, [{[integer(), integer()], integer()}]}, {:erlang, :bsr, [{[integer(), integer()], integer()}]}, {:erlang, :bxor, [{[integer(), integer()], integer()}]}, - {:erlang, :byte_size, [{[binary()], integer()}]}, + {:erlang, :byte_size, [{[bitstring()], integer()}]}, {:erlang, :ceil, [{[union(integer(), float())], integer()}]}, {:erlang, :div, [{[integer(), integer()], integer()}]}, {:erlang, :error, [{[term()], none()}]}, @@ -288,7 +288,7 @@ defmodule Module.Types.Apply do is_guards = [ is_atom: atom(), is_binary: binary(), - is_bitstring: binary(), + is_bitstring: bitstring(), is_boolean: boolean(), is_float: float(), is_function: fun(), @@ -303,13 +303,10 @@ defmodule Module.Types.Apply do ] for {guard, type} <- is_guards do - # is_binary can actually fail for binaries if they are bitstrings - return = if guard == :is_binary, do: boolean(), else: atom([true]) - domain_clauses = {:strong, [term()], [ - {[type], return}, + {[type], atom([true])}, {[negation(type)], atom([false])} ]} @@ -1282,6 +1279,17 @@ defmodule Module.Types.Apply do end end + @doc """ + Computes the return type of an application. + """ + def return(type, args_types, stack) do + cond do + stack.mode == :static -> type + Enum.any?(args_types, &gradual?/1) -> dynamic(type) + true -> type + end + end + ## Map helpers defp map_put_new(map, key, value, name, args_types, stack) do @@ -1325,14 +1333,6 @@ defmodule Module.Types.Apply do ## Application helpers - defp return(type, args_types, stack) do - cond do - stack.mode == :static -> type - Enum.any?(args_types, &gradual?/1) -> dynamic(type) - true -> type - end - end - defp domain(nil, [{domain, _}]), do: domain defp domain(domain, _clauses), do: domain diff --git a/lib/elixir/lib/module/types/descr.ex b/lib/elixir/lib/module/types/descr.ex index e8a7e47c836..4fa0409f287 100644 --- a/lib/elixir/lib/module/types/descr.ex +++ b/lib/elixir/lib/module/types/descr.ex @@ -16,14 +16,23 @@ defmodule Module.Types.Descr do import Bitwise - @bit_binary 1 <<< 0 - @bit_empty_list 1 <<< 1 - @bit_integer 1 <<< 2 - @bit_float 1 <<< 3 - @bit_pid 1 <<< 4 - @bit_port 1 <<< 5 - @bit_reference 1 <<< 6 - @bit_top (1 <<< 7) - 1 + @bit_binary 0b1 + @bit_bitstring_no_binary 0b10 + @bit_empty_list 0b100 + @bit_integer 0b1000 + @bit_float 0b10000 + @bit_pid 0b100000 + @bit_port 0b1000000 + @bit_reference 0b10000000 + @bit_top 0b11111111 + + # We use two bits to represent bitstrings and binaries, + # which must be looked at together + # bitstring = 0b11 + # bitstring and not binary = 0b10 + # binary = 0b01 + # none = 0b00 + @bit_bitstring @bit_binary ||| @bit_bitstring_no_binary @bit_number @bit_integer ||| @bit_float defmacro bdd_leaf(arg1, arg2), do: {arg1, arg2} @@ -80,6 +89,8 @@ defmodule Module.Types.Descr do def atom(as), do: %{atom: atom_new(as)} def atom(), do: %{atom: @atom_top} def binary(), do: %{bitmap: @bit_binary} + def bitstring(), do: %{bitmap: @bit_bitstring} + def bitstring_no_binary(), do: %{bitmap: @bit_bitstring_no_binary} def closed_map(pairs), do: map_descr(:closed, pairs) def empty_list(), do: %{bitmap: @bit_empty_list} def empty_map(), do: %{map: @map_empty} @@ -843,29 +854,14 @@ defmodule Module.Types.Descr do @doc """ Optimized version of `not empty?(intersection(binary(), type))`. """ - def binary_type?(:term), do: true - def binary_type?(%{dynamic: :term}), do: true - def binary_type?(%{dynamic: %{bitmap: bitmap}}) when (bitmap &&& @bit_binary) != 0, do: true - def binary_type?(%{bitmap: bitmap}) when (bitmap &&& @bit_binary) != 0, do: true - def binary_type?(_), do: false + def bitstring_type?(:term), do: true + def bitstring_type?(%{dynamic: :term}), do: true - @doc """ - Optimized version of `not empty?(intersection(integer(), type))`. - """ - def integer_type?(:term), do: true - def integer_type?(%{dynamic: :term}), do: true - def integer_type?(%{dynamic: %{bitmap: bitmap}}) when (bitmap &&& @bit_integer) != 0, do: true - def integer_type?(%{bitmap: bitmap}) when (bitmap &&& @bit_integer) != 0, do: true - def integer_type?(_), do: false + def bitstring_type?(%{dynamic: %{bitmap: bitmap}}) when (bitmap &&& @bit_bitstring) != 0, + do: true - @doc """ - Optimized version of `not empty?(intersection(float(), type))`. - """ - def float_type?(:term), do: true - def float_type?(%{dynamic: :term}), do: true - def float_type?(%{dynamic: %{bitmap: bitmap}}) when (bitmap &&& @bit_float) != 0, do: true - def float_type?(%{bitmap: bitmap}) when (bitmap &&& @bit_float) != 0, do: true - def float_type?(_), do: false + def bitstring_type?(%{bitmap: bitmap}) when (bitmap &&& @bit_bitstring) != 0, do: true + def bitstring_type?(_), do: false @doc """ Optimized version of `not empty?(intersection(integer() or float(), type))`. @@ -881,7 +877,6 @@ defmodule Module.Types.Descr do defp bitmap_to_quoted(val) do pairs = [ - binary: @bit_binary, empty_list: @bit_empty_list, integer: @bit_integer, float: @bit_float, @@ -890,9 +885,17 @@ defmodule Module.Types.Descr do reference: @bit_reference ] - for {type, mask} <- pairs, - (mask &&& val) !== 0, - do: {type, [], []} + quoted = + for {type, mask} <- pairs, + (mask &&& val) !== 0, + do: {type, [], []} + + case val &&& @bit_bitstring do + 0 -> quoted + 1 -> [{:binary, [], []} | quoted] + 2 -> [{:and, [], [{:bitstring, [], []}, {:not, [], [{:binary, [], []}]}]} | quoted] + 3 -> [{:bitstring, [], []} | quoted] + end end ## Atoms @@ -2368,7 +2371,7 @@ defmodule Module.Types.Descr do defp indivisible_bitmap(descr, opts) do with true <- Keyword.get(opts, :skip_dynamic_for_indivisible, true), - %{bitmap: bitmap} when map_size(descr) == 1 <- descr, + %{bitmap: bitmap} when map_size(descr) == 1 and bitmap != @bit_bitstring <- descr, [single] <- bitmap_to_quoted(bitmap) do single else @@ -2423,6 +2426,7 @@ defmodule Module.Types.Descr do defp bitmap_to_domain_keys(bitmap, acc) do acc = if (bitmap &&& @bit_binary) != 0, do: [:binary | acc], else: acc + acc = if (bitmap &&& @bit_bitstring_no_binary) != 0, do: [:bitstring | acc], else: acc acc = if (bitmap &&& @bit_empty_list) != 0, do: [:list | acc], else: acc acc = if (bitmap &&& @bit_integer) != 0, do: [:integer | acc], else: acc acc = if (bitmap &&& @bit_float) != 0, do: [:float | acc], else: acc @@ -2433,6 +2437,7 @@ defmodule Module.Types.Descr do end defp domain_key_to_descr(:atom), do: atom() + defp domain_key_to_descr(:bitstring), do: bitstring_no_binary() defp domain_key_to_descr(:binary), do: binary() defp domain_key_to_descr(:integer), do: integer() defp domain_key_to_descr(:float), do: float() diff --git a/lib/elixir/lib/module/types/expr.ex b/lib/elixir/lib/module/types/expr.ex index 4104cd65780..355b03ae1b4 100644 --- a/lib/elixir/lib/module/types/expr.ex +++ b/lib/elixir/lib/module/types/expr.ex @@ -122,9 +122,8 @@ defmodule Module.Types.Expr do end # <<...>>> - def of_expr({:<<>>, _meta, args}, _expected, _expr, stack, context) do - context = Of.binary(args, :expr, stack, context) - {binary(), context} + def of_expr({:<<>>, meta, args}, _expected, _expr, stack, context) do + Of.bitstring(meta, args, :expr, stack, context) end def of_expr({:__CALLER__, _meta, var_context}, _expected, _expr, _stack, context) @@ -315,7 +314,7 @@ defmodule Module.Types.Expr do |> dynamic_unless_static(stack) end - # TODO: fn pat -> expr end + # fn pat -> expr end def of_expr({:fn, _meta, clauses}, _expected, _expr, stack, context) do [{:->, _, [head, _]} | _] = clauses {patterns, _guards} = extract_head(head) @@ -416,20 +415,26 @@ defmodule Module.Types.Expr do else # TODO: Use the collectable protocol for the output into = Keyword.get(opts, :into, []) - {into_wrapper, gradual?, context} = for_into(into, meta, stack, context) + {into_type, into_kind, context} = for_into(into, meta, stack, context) {block_type, context} = of_expr(block, @pending, block, stack, context) - for_type = - for type <- into_wrapper do - case type do - :binary -> binary() - :list -> list(block_type) - :term -> term() + case into_kind do + :bitstring -> + case compatible_intersection(block_type, bitstring()) do + {:ok, intersection} -> + {return_union(into_type, intersection, stack), context} + + {:error, _} -> + error = {:badbitbody, block_type, block, context} + {error_type(), error(__MODULE__, error, meta, stack, context)} end - end - |> Enum.reduce(&union/2) - {if(gradual?, do: dynamic(for_type), else: for_type), context} + :non_empty_list -> + {return_union(into_type, non_empty_list(block_type), stack), context} + + :none -> + {into_type, context} + end end end @@ -469,7 +474,7 @@ defmodule Module.Types.Expr do apply_many(mods, name, args, expected, call, stack, context) end - # TODO: &Foo.bar/1 + # &Foo.bar/1 def of_expr( {:&, _, [{:/, _, [{{:., _, [remote, name]}, meta, []}, arity]}]} = call, _expected, @@ -483,7 +488,7 @@ defmodule Module.Types.Expr do Apply.remote_capture(mods, name, arity, meta, stack, context) end - # TODO: &foo/1 + # &foo/1 def of_expr({:&, _meta, [{:/, _, [{fun, meta, _}, arity]}]}, _expected, _expr, stack, context) do Apply.local_capture(fun, arity, meta, stack, context) end @@ -577,13 +582,13 @@ defmodule Module.Types.Expr do end defp for_clause({:<<>>, _, [{:<-, meta, [left, right]}]} = expr, stack, context) do - {right_type, context} = of_expr(right, binary(), expr, stack, context) - context = Pattern.of_generator(left, [], binary(), :for, expr, stack, context) + {right_type, context} = of_expr(right, bitstring(), expr, stack, context) + context = Pattern.of_generator(left, [], bitstring(), :for, expr, stack, context) - if compatible?(right_type, binary()) do + if compatible?(right_type, bitstring()) do context else - error = {:badbinary, right_type, right, context} + error = {:badbitgenerator, right_type, right, context} error(__MODULE__, error, meta, stack, context) end end @@ -593,13 +598,13 @@ defmodule Module.Types.Expr do context end - @into_compile union(binary(), empty_list()) + @into_compile union(bitstring(), empty_list()) defp for_into([], _meta, _stack, context), - do: {[:list], false, context} + do: {empty_list(), :non_empty_list, context} defp for_into(binary, _meta, _stack, context) when is_binary(binary), - do: {[:binary], false, context} + do: {binary(), :bitstring, context} defp for_into(into, meta, stack, context) do meta = @@ -616,21 +621,33 @@ defmodule Module.Types.Expr do {type, context} = of_expr(into, domain, expr, stack, context) # We use subtype? instead of compatible because we want to handle - # only binary/list, even if a dynamic with something else is given. + # only bitstring/list, even if a dynamic with something else is given. if subtype?(type, @into_compile) do - case {binary_type?(type), empty_list_type?(type)} do - {false, true} -> {[:list], gradual?(type), context} - {true, false} -> {[:binary], gradual?(type), context} - {_, _} -> {[:binary, :list], gradual?(type), context} + case {bitstring_type?(type), empty_list_type?(type)} do + # If they can be both be true, then we don't know + # what the contents of the block are for + {true, true} -> + type = union(bitstring(), list(term())) + {if(gradual?(type), do: dynamic(type), else: type), :none, context} + + {false, true} -> + {type, :non_empty_list, context} + + {true, false} -> + {type, :bitstring, context} end else {_type, context} = Apply.remote_apply(info, Collectable, :into, [type], expr, stack, context) - {[:term], true, context} + {dynamic(), :none, context} end end + defp return_union(left, right, stack) do + Apply.return(union(left, right), [left, right], stack) + end + ## With defp with_clause({:<-, _meta, [left, right]} = expr, stack, context) do @@ -866,7 +883,28 @@ defmodule Module.Types.Expr do } end - def format_diagnostic({:badbinary, type, expr, context}) do + def format_diagnostic({:badbitgenerator, type, expr, context}) do + traces = collect_traces(expr, context) + + %{ + details: %{typing_traces: traces}, + message: + IO.iodata_to_binary([ + """ + expected the right side of <- in a binary generator to be a binary (or bitstring): + + #{expr_to_string(expr) |> indent(4)} + + but got type: + + #{to_quoted_string(type) |> indent(4)} + """, + format_traces(traces) + ]) + } + end + + def format_diagnostic({:badbitbody, type, expr, context}) do traces = collect_traces(expr, context) %{ @@ -874,7 +912,7 @@ defmodule Module.Types.Expr do message: IO.iodata_to_binary([ """ - expected the right side of <- in a binary generator to be a binary: + expected the body of a for-comprehension with into: binary() (or bitstring()) to be a binary (or bitstring): #{expr_to_string(expr) |> indent(4)} diff --git a/lib/elixir/lib/module/types/of.ex b/lib/elixir/lib/module/types/of.ex index f3713284167..d49996ef029 100644 --- a/lib/elixir/lib/module/types/of.ex +++ b/lib/elixir/lib/module/types/of.ex @@ -16,6 +16,7 @@ defmodule Module.Types.Of do @integer integer() @float float() @binary binary() + @bitstring bitstring() ## Variables @@ -208,7 +209,7 @@ defmodule Module.Types.Of do impls = [ {Atom, atom()}, - {BitString, binary()}, + {BitString, bitstring()}, {Float, float()}, {Function, fun()}, {Integer, integer()}, @@ -467,44 +468,54 @@ defmodule Module.Types.Of do closed_map(pairs) end - ## Binary + ## Bitstrings @doc """ - Handles binaries. + Handles bitstrings. In the stack, we add nodes such as <>, <<..., expr>>, etc, based on the position of the expression within the binary. """ - def binary([], _kind, _stack, context) do + def bitstring(meta, parts, kind, stack, context) do + context = bitstring(parts, kind, stack, context) + + case Keyword.get(meta, :alignment, :unknown) do + :unknown -> {bitstring(), context} + 0 -> {binary(), context} + _ -> {bitstring_no_binary(), context} + end + end + + defp bitstring([], _kind, _stack, context) do context end - def binary([head], kind, stack, context) do - binary_segment(head, kind, [head], stack, context) + defp bitstring([head], kind, stack, context) do + bitstring_segment(head, kind, [head], stack, context) end - def binary([head | tail], kind, stack, context) do - context = binary_segment(head, kind, [head, @suffix], stack, context) - binary_many(tail, kind, stack, context) + defp bitstring([head | tail], kind, stack, context) do + context = bitstring_segment(head, kind, [head, @suffix], stack, context) + bitstring_tail(tail, kind, stack, context) end - defp binary_many([last], kind, stack, context) do - binary_segment(last, kind, [@prefix, last], stack, context) + defp bitstring_tail([last], kind, stack, context) do + bitstring_segment(last, kind, [@prefix, last], stack, context) end - defp binary_many([head | tail], kind, stack, context) do - context = binary_segment(head, kind, [@prefix, head, @suffix], stack, context) - binary_many(tail, kind, stack, context) + defp bitstring_tail([head | tail], kind, stack, context) do + context = bitstring_segment(head, kind, [@prefix, head, @suffix], stack, context) + bitstring_tail(tail, kind, stack, context) end # If the segment is a literal, the compiler has already checked its validity, # so we just check the size. - defp binary_segment({:"::", _meta, [left, right]}, kind, _args, stack, context) + defp bitstring_segment({:"::", _meta, [left, right]}, kind, _args, stack, context) when is_binary(left) or is_number(left) do specifier_size(kind, right, stack, context) end - defp binary_segment({:"::", meta, [left, right]}, kind, args, stack, context) do + defp bitstring_segment({:"::", meta, [left, right]}, kind, args, stack, context) do type = specifier_type(kind, right) expr = {:<<>>, meta, args} @@ -550,8 +561,8 @@ defmodule Module.Types.Of do defp specifier_type(_kind, {:utf16, _, _}), do: @integer_or_binary defp specifier_type(_kind, {:utf32, _, _}), do: @integer_or_binary defp specifier_type(_kind, {:integer, _, _}), do: @integer - defp specifier_type(_kind, {:bits, _, _}), do: @binary - defp specifier_type(_kind, {:bitstring, _, _}), do: @binary + defp specifier_type(_kind, {:bits, _, _}), do: @bitstring + defp specifier_type(_kind, {:bitstring, _, _}), do: @bitstring defp specifier_type(_kind, {:bytes, _, _}), do: @binary defp specifier_type(_kind, {:binary, _, _}), do: @binary defp specifier_type(_kind, _specifier), do: @integer diff --git a/lib/elixir/lib/module/types/pattern.ex b/lib/elixir/lib/module/types/pattern.ex index 3590c023563..ce56b8f4d86 100644 --- a/lib/elixir/lib/module/types/pattern.ex +++ b/lib/elixir/lib/module/types/pattern.ex @@ -401,8 +401,8 @@ defmodule Module.Types.Pattern do end end - def of_match_var({:<<>>, _meta, args}, _expected, _expr, stack, context) do - {binary(), Of.binary(args, :match, stack, context)} + def of_match_var({:<<>>, meta, args}, _expected, _expr, stack, context) do + Of.bitstring(meta, args, :match, stack, context) end def of_match_var({:^, _meta, [{_, meta, _}]}, expected, expr, stack, context) do @@ -592,9 +592,8 @@ defmodule Module.Types.Pattern do end # <<...>>> - defp of_pattern({:<<>>, _meta, args}, _path, stack, context) do - context = Of.binary(args, :match, stack, context) - {binary(), context} + defp of_pattern({:<<>>, meta, args}, _path, stack, context) do + Of.bitstring(meta, args, :match, stack, context) end # left ++ right @@ -852,9 +851,8 @@ defmodule Module.Types.Pattern do end # <<>> - def of_guard({:<<>>, _meta, args}, _expected, _expr, stack, context) do - context = Of.binary(args, :guard, stack, context) - {binary(), context} + def of_guard({:<<>>, meta, args}, _expected, _expr, stack, context) do + Of.bitstring(meta, args, :guard, stack, context) end # ^var diff --git a/lib/elixir/pages/cheatsheets/types-cheat.cheatmd b/lib/elixir/pages/cheatsheets/types-cheat.cheatmd index 93bbd776c42..6b0df3a6fcd 100644 --- a/lib/elixir/pages/cheatsheets/types-cheat.cheatmd +++ b/lib/elixir/pages/cheatsheets/types-cheat.cheatmd @@ -33,9 +33,10 @@ not type ## Data types -### Indivisible types +### Broad types ```elixir +bitstring() binary() empty_list() integer() @@ -45,6 +46,8 @@ port() reference() ``` +`binary()` is a subtype of `bitstring()`. + ### Atoms #### All atoms @@ -167,24 +170,20 @@ tuple() #### At least n-element tuples -``` +```elixir {binary(), binary(), ...} ``` ## Additional types for convenience -#### Aliases +#### Common aliases ```elixir -bitstring() = binary() boolean() = true or false number() = integer() or float() ``` -The type system currently does not distinguish between -binaries and bitstrings. - -#### Lists +#### List aliases ```elixir list() = empty_list() or non_empty_list(term()) diff --git a/lib/elixir/pages/references/gradual-set-theoretic-types.md b/lib/elixir/pages/references/gradual-set-theoretic-types.md index 745504764af..528fa9eeff5 100644 --- a/lib/elixir/pages/references/gradual-set-theoretic-types.md +++ b/lib/elixir/pages/references/gradual-set-theoretic-types.md @@ -24,6 +24,7 @@ The basic types are: ```elixir atom() binary() +bitstring() empty_list() integer() float() @@ -54,9 +55,13 @@ You can find a complete reference in the [set-theoretic types cheatsheet](../che In this section we will cover the syntax of all data types. At the moment, developers will interact with those types mostly through compiler warnings and diagnostics. -### Indivisible types +### Broad types -These types are indivisibile and have no further representation. They are: `binary()`, `integer()`, `float()`, `pid()`, `port()`, `reference()`. For example, the numbers `1` and `42` are both represented by the type `integer()`. +These types are broad in that they cannot represent individual elements, only the whole set. For example, the numbers `1` and `42` are both represented by the type `integer()`. + +They are: `binary()`, `bitstring()`, `integer()`, `float()`, `pid()`, `port()`, `reference()`. + +The `binary()` type is a subtype of the less frequently used `bitstring()` type, as binaries are bitstrings where the number of bits is divisible by 8. ### Atoms @@ -148,7 +153,7 @@ That's the same as specifying all lists: %{list() => integer() or binary()} ``` -The supported domain keys are `atom()`, `binary()`, `integer()`, `float()`, `fun()`, `list()`, `map()`, `pid()`, `port()`, `reference()`, and `tuple()`. +The supported domain keys are `atom()`, `bitstring()`, `binary()`, `integer()`, `float()`, `fun()`, `list()`, `map()`, `pid()`, `port()`, `reference()`, and `tuple()`. In the case of maps, the `bitstring()` domain stores exclusively keys which are not binary. The ones which are `binary()` are stored under the `binary()` domain. Furthermore, it is important to note that domain keys are, by definition, optional. Whenever you have a `%{integer() => integer()}`and you try to fetch a key, we must assume the key may not exist (after all, it is not possible to store all integers as map keys as they are infinite). diff --git a/lib/elixir/src/elixir_erl.erl b/lib/elixir/src/elixir_erl.erl index f56585ca8a3..75fad7ab7f3 100644 --- a/lib/elixir/src/elixir_erl.erl +++ b/lib/elixir/src/elixir_erl.erl @@ -11,7 +11,7 @@ -define(typespecs, 'Elixir.Kernel.Typespec'). checker_version() -> - elixir_checker_v4. + elixir_checker_v5. %% debug_info callback diff --git a/lib/elixir/test/elixir/module/types/descr_test.exs b/lib/elixir/test/elixir/module/types/descr_test.exs index 652cb04df73..12362219501 100644 --- a/lib/elixir/test/elixir/module/types/descr_test.exs +++ b/lib/elixir/test/elixir/module/types/descr_test.exs @@ -54,7 +54,7 @@ defmodule Module.Types.DescrTest do atom(), integer(), float(), - binary(), + bitstring(), open_map(), non_empty_list(term(), term()), empty_list(), @@ -182,7 +182,6 @@ defmodule Module.Types.DescrTest do ) == closed_map(a: union(float(), integer()), b: atom()) # Optimization two: we can tell that one map is a subtype of the other: - assert union( closed_map(a: term(), b: term()), closed_map(a: float(), b: binary()) @@ -216,8 +215,7 @@ defmodule Module.Types.DescrTest do tuple([integer(), atom()]) ) == tuple([union(float(), integer()), atom()]) - # Optimization two: we can tell that one tuple is a subtype of the other: - + # Optimization two: we can tell that one tuple is a subtype of the other assert union( tuple([term(), term()]), tuple([float(), binary()]) @@ -2489,8 +2487,14 @@ defmodule Module.Types.DescrTest do describe "to_quoted" do test "bitmap" do + assert union(pid(), bitstring()) |> to_quoted_string() == + "bitstring() or pid()" + assert union(integer(), union(float(), binary())) |> to_quoted_string() == "binary() or float() or integer()" + + assert difference(bitstring(), binary()) |> union(integer()) |> to_quoted_string() == + "(bitstring() and not binary()) or integer()" end test "none" do @@ -2524,16 +2528,23 @@ defmodule Module.Types.DescrTest do test "dynamic" do assert dynamic() |> to_quoted_string() == "dynamic()" + assert intersection(atom(), dynamic()) |> to_quoted_string() == "dynamic(atom())" + assert dynamic(union(atom(), integer())) |> union(integer()) |> to_quoted_string() == "dynamic(atom()) or integer()" - assert intersection(binary(), dynamic()) |> to_quoted_string() == "binary()" + assert intersection(binary(), dynamic()) |> to_quoted_string() == + "binary()" + + assert intersection(bitstring(), dynamic()) |> to_quoted_string() == + "dynamic(bitstring())" + + assert intersection(bitstring_no_binary(), dynamic()) |> to_quoted_string() == + "bitstring() and not binary()" assert intersection(union(binary(), pid()), dynamic()) |> to_quoted_string() == "dynamic(binary() or pid())" - assert intersection(atom(), dynamic()) |> to_quoted_string() == "dynamic(atom())" - assert union(atom([:foo, :bar]), dynamic()) |> to_quoted_string() == "dynamic() or :bar or :foo" diff --git a/lib/elixir/test/elixir/module/types/expr_test.exs b/lib/elixir/test/elixir/module/types/expr_test.exs index 4eab9a44a80..27fcbdefe3c 100644 --- a/lib/elixir/test/elixir/module/types/expr_test.exs +++ b/lib/elixir/test/elixir/module/types/expr_test.exs @@ -31,6 +31,15 @@ defmodule Module.Types.ExprTest do assert typecheck!([x = 1], generated(x)) == dynamic() end + describe "bitstrings" do + test "alignment" do + assert typecheck!(<>) == binary() + assert typecheck!(<>) == difference(bitstring(), binary()) + assert typecheck!(<>) == binary() + assert typecheck!([size], <>) == bitstring() + end + end + describe "lists" do test "creating lists" do assert typecheck!([1, 2]) == non_empty_list(integer()) @@ -678,7 +687,7 @@ defmodule Module.Types.ExprTest do end test "size ok" do - assert typecheck!([<>, z], <>) == binary() + assert typecheck!([<>, z], <>) == bitstring() end test "size error" do @@ -2040,10 +2049,10 @@ defmodule Module.Types.ExprTest do end describe "comprehensions" do - test "binary generators" do + test "bitstring generators" do assert typeerror!([<>], for(<>, do: y)) == ~l""" - expected the right side of <- in a binary generator to be a binary: + expected the right side of <- in a binary generator to be a binary (or bitstring): x @@ -2066,7 +2075,7 @@ defmodule Module.Types.ExprTest do for(< 0.5, do: x, else: y)>>, do: i) ) =~ ~l""" - expected the right side of <- in a binary generator to be a binary: + expected the right side of <- in a binary generator to be a binary (or bitstring): if :rand.uniform() > 0.5 do x @@ -2092,25 +2101,29 @@ defmodule Module.Types.ExprTest do """ end - test "infers binary generators" do + test "infers bitstring generators" do assert typecheck!( [x], ( for <<_ <- x>>, do: :ok x ) - ) == dynamic(binary()) + ) == dynamic(bitstring()) end test ":into" do assert typecheck!([binary], for(<>, do: x)) == list(integer()) assert typecheck!([binary], for(<>, do: x, into: [])) == list(integer()) - assert typecheck!([binary], for(<>, do: x, into: "")) == binary() + assert typecheck!([binary], for(<>, do: <>, into: "")) |> equal?(binary()) assert typecheck!([binary, other], for(<>, do: x, into: other)) == dynamic() - assert typecheck!([enum], for(x <- enum, do: x)) == list(dynamic()) - assert typecheck!([enum], for(x <- enum, do: x, into: [])) == list(dynamic()) - assert typecheck!([enum], for(x <- enum, do: x, into: "")) == binary() + assert typecheck!([enum], for(x <- enum, do: x)) == + union(list(dynamic()), empty_list()) + + assert typecheck!([enum], for(x <- enum, do: x, into: [])) == + union(list(dynamic()), empty_list()) + + assert typecheck!([enum], for(x <- enum, do: <>, into: "")) |> equal?(binary()) assert typecheck!([enum, other], for(x <- enum, do: x, into: other)) == dynamic() assert typecheck!( @@ -2119,7 +2132,7 @@ defmodule Module.Types.ExprTest do into = if :rand.uniform() > 0.5, do: [], else: "0" for(<>, do: x, into: into) ) - ) == union(binary(), list(float())) + ) == union(bitstring(), list(term())) assert typecheck!( [binary, empty_list = []], @@ -2127,7 +2140,25 @@ defmodule Module.Types.ExprTest do into = if :rand.uniform() > 0.5, do: empty_list, else: "0" for(<>, do: x, into: into) ) - ) == dynamic(union(binary(), list(float()))) + ) == union(bitstring(), list(term())) + end + + test ":into incompatibility" do + assert typeerror!([binary], for(<>, do: x, into: "")) =~ ~l""" + expected the body of a for-comprehension with into: binary() (or bitstring()) to be a binary (or bitstring): + + x + + but got type: + + integer() + + where "x" was given the type: + + # type: integer() + # from: types_test.ex:LINE + <> + """ end test ":reduce checks" do diff --git a/lib/elixir/test/elixir/module/types/integration_test.exs b/lib/elixir/test/elixir/module/types/integration_test.exs index 9177f41bb97..1a23fbf2168 100644 --- a/lib/elixir/test/elixir/module/types/integration_test.exs +++ b/lib/elixir/test/elixir/module/types/integration_test.exs @@ -240,7 +240,7 @@ defmodule Module.Types.IntegrationTest do end assert itself_arg.(Itself.Atom) == dynamic(atom()) - assert itself_arg.(Itself.BitString) == dynamic(binary()) + assert itself_arg.(Itself.BitString) == dynamic(bitstring()) assert itself_arg.(Itself.Float) == dynamic(float()) assert itself_arg.(Itself.Function) == dynamic(fun()) assert itself_arg.(Itself.Integer) == dynamic(integer()) @@ -583,7 +583,8 @@ defmodule Module.Types.IntegrationTest do dynamic( %Date{} or %DateTime{} or %NaiveDateTime{} or %Time{} or %URI{} or %Version{} or %Version.Requirement{} - ) or atom() or binary() or empty_list() or float() or integer() or non_empty_list(term(), term()) + ) or atom() or bitstring() or empty_list() or float() or integer() or + non_empty_list(term(), term()) """, """ warning: incompatible value given to string interpolation: @@ -667,7 +668,7 @@ defmodule Module.Types.IntegrationTest do hint: the Collectable protocol is implemented for the following types: - dynamic(%File.Stream{} or %HashDict{} or %HashSet{} or %IO.Stream{} or %MapSet{}) or binary() or + dynamic(%File.Stream{} or %HashDict{} or %HashSet{} or %IO.Stream{} or %MapSet{}) or bitstring() or empty_list() or non_empty_list(term(), term()) or non_struct_map() """, """ @@ -684,7 +685,7 @@ defmodule Module.Types.IntegrationTest do hint: the Collectable protocol is implemented for the following types: - dynamic(%File.Stream{} or %HashDict{} or %HashSet{} or %IO.Stream{} or %MapSet{}) or binary() or + dynamic(%File.Stream{} or %HashDict{} or %HashSet{} or %IO.Stream{} or %MapSet{}) or bitstring() or empty_list() or non_empty_list(term(), term()) or non_struct_map() """ ] @@ -1567,7 +1568,7 @@ defmodule Module.Types.IntegrationTest do defp read_chunk(binary) do assert {:ok, {_module, [{~c"ExCk", chunk}]}} = :beam_lib.chunks(binary, [~c"ExCk"]) - assert {:elixir_checker_v4, map} = :erlang.binary_to_term(chunk) + assert {:elixir_checker_v5, map} = :erlang.binary_to_term(chunk) map end diff --git a/lib/elixir/test/elixir/module/types/pattern_test.exs b/lib/elixir/test/elixir/module/types/pattern_test.exs index 53afa24ff44..607c3479fee 100644 --- a/lib/elixir/test/elixir/module/types/pattern_test.exs +++ b/lib/elixir/test/elixir/module/types/pattern_test.exs @@ -328,7 +328,14 @@ defmodule Module.Types.PatternTest do end end - describe "binaries" do + describe "bitstrings" do + test "alignment" do + assert typecheck!([<<_>> = x], x) == dynamic(binary()) + assert typecheck!([<<_::1>> = x], x) == dynamic(difference(bitstring(), binary())) + assert typecheck!([<<_::4, _::4>> = x], x) == dynamic(binary()) + assert typecheck!([<> = x], x) == dynamic(bitstring()) + end + test "ok" do assert typecheck!([<>], x) == integer() assert typecheck!([<>], x) == float() @@ -337,7 +344,27 @@ defmodule Module.Types.PatternTest do end test "nested" do - assert typecheck!([<<0, <>::binary>>], x) == binary() + assert typecheck!([<<0, <>::binary>>], x) == binary() + + assert typeerror!([<<0, <>::binary>>], x) == ~l""" + incompatible types in binary matching: + + <<..., <>::binary>> + + got type: + + bitstring() + + but expected type: + + binary() + + where "x" was given the type: + + # type: bitstring() + # from: types_test.ex:LINE + <> + """ end test "error" do @@ -552,10 +579,10 @@ defmodule Module.Types.PatternTest do test "is_binary/1" do assert typecheck!([x], is_binary(x), x) == dynamic(binary()) - assert typecheck!([x], not is_binary(x), x) == dynamic(term()) + assert typecheck!([x], not is_binary(x), x) == dynamic(negation(binary())) - assert typecheck!([x], is_bitstring(x), x) == dynamic(binary()) - assert typecheck!([x], not is_bitstring(x), x) == dynamic(negation(binary())) + assert typecheck!([x], is_bitstring(x), x) == dynamic(bitstring()) + assert typecheck!([x], not is_bitstring(x), x) == dynamic(negation(bitstring())) end test "is_function/2" do diff --git a/lib/elixir/test/elixir/protocol/consolidation_test.exs b/lib/elixir/test/elixir/protocol/consolidation_test.exs index 7e46f553299..aa4f90f4dc5 100644 --- a/lib/elixir/test/elixir/protocol/consolidation_test.exs +++ b/lib/elixir/test/elixir/protocol/consolidation_test.exs @@ -165,7 +165,7 @@ defmodule Protocol.ConsolidationTest do defp exports(binary) do {:ok, {_, [{~c"ExCk", check_bin}]}} = :beam_lib.chunks(binary, [~c"ExCk"]) - assert {:elixir_checker_v4, contents} = :erlang.binary_to_term(check_bin) + assert {:elixir_checker_v5, contents} = :erlang.binary_to_term(check_bin) Map.new(contents.exports) end diff --git a/lib/mix/lib/mix/compilers/elixir.ex b/lib/mix/lib/mix/compilers/elixir.ex index 68e0b22e9ed..0b9a5f53524 100644 --- a/lib/mix/lib/mix/compilers/elixir.ex +++ b/lib/mix/lib/mix/compilers/elixir.ex @@ -5,7 +5,7 @@ defmodule Mix.Compilers.Elixir do @moduledoc false - @manifest_vsn 30 + @manifest_vsn 31 @checkpoint_vsn 4 import Record