Remove improper_list from type specification

This commit is contained in:
José Valim
2025-12-08 09:36:23 +01:00
parent 904adda55f
commit 82983cf979
6 changed files with 58 additions and 45 deletions
+2 -2
View File
@@ -360,14 +360,14 @@ defmodule Module.Types.Apply do
defp remote_apply(:erlang, :hd, _info, [list], _stack) do
case list_hd(list) do
{_, value_type} -> {:ok, value_type}
{:ok, value_type} -> {:ok, value_type}
:badnonemptylist -> {:error, badremote(:erlang, :hd, 1)}
end
end
defp remote_apply(:erlang, :tl, _info, [list], _stack) do
case list_tl(list) do
{_, value_type} -> {:ok, value_type}
{:ok, value_type} -> {:ok, value_type}
:badnonemptylist -> {:error, badremote(:erlang, :tl, 1)}
end
end
+10 -20
View File
@@ -54,7 +54,6 @@ defmodule Module.Types.Descr do
@list_top %{bitmap: @bit_empty_list, list: @non_empty_list_top}
@empty_list %{bitmap: @bit_empty_list}
@not_non_empty_list Map.delete(@term, :list)
@not_list Map.replace!(@not_non_empty_list, :bitmap, @bit_top - @bit_empty_list)
@not_set %{optional: 1}
@term_or_optional Map.put(@term, :optional, 1)
@@ -1972,7 +1971,7 @@ defmodule Module.Types.Descr do
static_value = list_hd_static(descr)
if non_empty_list_only?(descr) and not empty?(static_value) do
{false, static_value}
{:ok, static_value}
else
:badnonemptylist
end
@@ -1981,7 +1980,7 @@ defmodule Module.Types.Descr do
dynamic_value = list_hd_static(dynamic)
if non_empty_list_only?(static) and not empty?(dynamic_value) do
{true, union(dynamic(dynamic_value), list_hd_static(static))}
{:ok, union(dynamic(dynamic_value), list_hd_static(static))}
else
:badnonemptylist
end
@@ -2015,7 +2014,7 @@ defmodule Module.Types.Descr do
static_value = list_tl_static(descr)
if non_empty_list_only?(descr) and not empty?(static_value) do
{false, static_value}
{:ok, static_value}
else
:badnonemptylist
end
@@ -2024,7 +2023,7 @@ defmodule Module.Types.Descr do
dynamic_value = list_tl_static(dynamic)
if non_empty_list_only?(static) and not empty?(dynamic_value) do
{true, union(dynamic(dynamic_value), list_tl_static(static))}
{:ok, union(dynamic(dynamic_value), list_tl_static(static))}
else
:badnonemptylist
end
@@ -2048,10 +2047,6 @@ defmodule Module.Types.Descr do
defp list_tl_static(%{}), do: none()
defp list_improper_static?(:term), do: false
defp list_improper_static?(%{bitmap: bitmap}) when (bitmap &&& @bit_empty_list) != 0, do: false
defp list_improper_static?(term), do: equal?(term, @not_list)
defp list_to_quoted(bdd, empty?, opts) do
dnf = list_normalize(bdd)
@@ -2059,17 +2054,12 @@ defmodule Module.Types.Descr do
dnf
|> Enum.reduce({[], false}, fn {list_type, last_type, negs}, {acc, list_rendered?} ->
{name, arguments, list_rendered?} =
cond do
list_type == term() and list_improper_static?(last_type) ->
{:improper_list, [], list_rendered?}
subtype?(last_type, @empty_list) ->
name = if empty?, do: :list, else: :non_empty_list
{name, [to_quoted(list_type, opts)], empty?}
true ->
args = [to_quoted(list_type, opts), to_quoted(last_type, opts)]
{:non_empty_list, args, list_rendered?}
if subtype?(last_type, @empty_list) do
name = if empty?, do: :list, else: :non_empty_list
{name, [to_quoted(list_type, opts)], empty?}
else
args = [to_quoted(list_type, opts), to_quoted(last_type, opts)]
{:non_empty_list, args, list_rendered?}
end
acc =
+3 -3
View File
@@ -94,7 +94,7 @@ defmodule Module.Types.Expr do
else
hd_type =
case list_hd(expected) do
{_, type} -> type
{:ok, type} -> type
_ -> term()
end
@@ -106,8 +106,8 @@ defmodule Module.Types.Expr do
else
tl_type =
case list_tl(expected) do
{_, type} -> type
_ -> term()
{:ok, type} -> type
:badnonemptylist -> term()
end
of_expr(suffix, tl_type, expr, stack, context)
+3 -3
View File
@@ -305,7 +305,7 @@ defmodule Module.Types.Pattern do
defp of_pattern_var([{:head, counter} | rest], type, _reachable_var?, info, context) do
case list_hd(type) do
{_, head} ->
{:ok, head} ->
tree = Map.fetch!(info, -counter)
type = intersection(of_pattern_tree(tree, context), head)
of_pattern_var(rest, type, false, info, context)
@@ -317,8 +317,8 @@ defmodule Module.Types.Pattern do
defp of_pattern_var([:tail | rest], type, reachable_var?, info, context) do
case list_tl(type) do
{_, tail} -> of_pattern_var(rest, tail, reachable_var?, info, context)
_ -> :error
{:ok, tail} -> of_pattern_var(rest, tail, reachable_var?, info, context)
:badnonemptylist -> :error
end
end
@@ -5,6 +5,32 @@
# Set-theoretic types cheatsheet
## Set operators
#### Union
```elixir
type1 or type2
```
#### Intersection
```elixir
type1 and type2
```
#### Difference
```elixir
type1 and not type2
```
#### Negation
```elixir
not type
```
## Data types
### Indivisible types
@@ -1246,12 +1246,12 @@ defmodule Module.Types.DescrTest do
assert list_hd(term()) == :badnonemptylist
assert list_hd(list(term())) == :badnonemptylist
assert list_hd(empty_list()) == :badnonemptylist
assert list_hd(non_empty_list(term())) == {false, term()}
assert list_hd(non_empty_list(integer())) == {false, integer()}
assert list_hd(difference(list(number()), list(integer()))) == {false, number()}
assert list_hd(non_empty_list(term())) == {:ok, term()}
assert list_hd(non_empty_list(integer())) == {:ok, integer()}
assert list_hd(difference(list(number()), list(integer()))) == {:ok, number()}
assert list_hd(dynamic()) == {true, dynamic()}
assert list_hd(dynamic(list(integer()))) == {true, dynamic(integer())}
assert list_hd(dynamic()) == {:ok, dynamic()}
assert list_hd(dynamic(list(integer()))) == {:ok, dynamic(integer())}
assert list_hd(union(dynamic(), atom())) == :badnonemptylist
assert list_hd(union(dynamic(), list(term()))) == :badnonemptylist
@@ -1259,12 +1259,12 @@ defmodule Module.Types.DescrTest do
assert list_hd(dynamic(difference(list(number()), list(number())))) == :badnonemptylist
assert list_hd(union(dynamic(list(float())), non_empty_list(atom()))) ==
{true, union(dynamic(float()), atom())}
{:ok, union(dynamic(float()), atom())}
# If term() is in the tail, it means list(term()) is in the tail
# and therefore any term can be returned from hd.
assert list_hd(non_empty_list(atom(), term())) == {false, term()}
assert list_hd(non_empty_list(atom(), negation(list(term(), term())))) == {false, atom()}
assert list_hd(non_empty_list(atom(), term())) == {:ok, term()}
assert list_hd(non_empty_list(atom(), negation(list(term(), term())))) == {:ok, atom()}
end
test "list_tl" do
@@ -1274,27 +1274,27 @@ defmodule Module.Types.DescrTest do
assert list_tl(list(integer())) == :badnonemptylist
assert list_tl(difference(list(number()), list(number()))) == :badnonemptylist
assert list_tl(non_empty_list(integer())) == {false, list(integer())}
assert list_tl(non_empty_list(integer())) == {:ok, list(integer())}
assert list_tl(non_empty_list(integer(), atom())) ==
{false, union(atom(), non_empty_list(integer(), atom()))}
{:ok, union(atom(), non_empty_list(integer(), atom()))}
# The tail of either a (non empty) list of integers with an atom tail or a (non empty) list
# of tuples with a float tail is either an atom, or a float, or a (possibly empty) list of
# integers with an atom tail, or a (possibly empty) list of tuples with a float tail.
assert list_tl(union(non_empty_list(integer(), atom()), non_empty_list(tuple(), float()))) ==
{false,
{:ok,
atom()
|> union(float())
|> union(
union(non_empty_list(integer(), atom()), non_empty_list(tuple(), float()))
)}
assert list_tl(dynamic()) == {true, dynamic()}
assert list_tl(dynamic(list(integer()))) == {true, dynamic(list(integer()))}
assert list_tl(dynamic()) == {:ok, dynamic()}
assert list_tl(dynamic(list(integer()))) == {:ok, dynamic(list(integer()))}
assert list_tl(dynamic(list(integer(), atom()))) ==
{true, dynamic(union(atom(), list(integer(), atom())))}
{:ok, dynamic(union(atom(), list(integer(), atom())))}
end
test "tuple_fetch" do
@@ -2374,9 +2374,6 @@ defmodule Module.Types.DescrTest do
assert list(term(), term()) |> to_quoted_string() ==
"empty_list() or non_empty_list(term(), term())"
assert non_empty_list(term(), difference(term(), list(term()))) |> to_quoted_string() ==
"improper_list()"
# Test normalization
# Remove duplicates