Skip to content

Commit 94759fb

Browse files
committed
Type hd/1 and tl/1
1 parent 8f31a4b commit 94759fb

File tree

4 files changed

+132
-36
lines changed

4 files changed

+132
-36
lines changed

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

Lines changed: 18 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -781,34 +781,29 @@ defmodule Module.Types.Descr do
781781
On a non empty list of integers, it returns the first integer.
782782
On a non empty list of integers, with an atom head element, it returns the atom.
783783
"""
784-
def list_hd(:term), do: :badlist
784+
def list_hd(:term), do: :badnonemptylist
785785

786786
def list_hd(%{} = descr) do
787787
case :maps.take(:dynamic, descr) do
788788
:error ->
789789
has_empty = empty_list_type?(descr)
790790
is_list_type = list_only?(descr)
791791

792-
cond do
793-
is_list_type and not has_empty -> {false, list_hd_static(descr)}
794-
is_list_type -> :empty_list
795-
true -> :badlist
792+
if is_list_type and not has_empty do
793+
{false, list_hd_static(descr)}
794+
else
795+
:badnonemptylist
796796
end
797797

798798
{dynamic, static} ->
799799
has_empty = empty_list_type?(static)
800800
only_list = list_only?(static)
801801
is_dynamic_list = list_type?(dynamic)
802802

803-
cond do
804-
is_dynamic_list and only_list and not has_empty ->
805-
{is_dynamic_list, union(dynamic(list_hd_static(dynamic)), list_hd_static(static))}
806-
807-
is_dynamic_list and only_list ->
808-
:empty_list
809-
810-
true ->
811-
:badlist
803+
if is_dynamic_list and only_list and not has_empty do
804+
{is_dynamic_list, union(dynamic(list_hd_static(dynamic)), list_hd_static(static))}
805+
else
806+
:badnonemptylist
812807
end
813808
end
814809
end
@@ -836,34 +831,29 @@ defmodule Module.Types.Descr do
836831
On a non empty list of integers, with an atom tail element, it returns either an atom,
837832
or a (possibly empty) list of integers with an atom tail element.
838833
"""
839-
def list_tl(:term), do: :badlist
834+
def list_tl(:term), do: :badnonemptylist
840835

841836
def list_tl(descr) do
842837
case :maps.take(:dynamic, descr) do
843838
:error ->
844839
has_empty = empty_list_type?(descr)
845840
is_list_type = list_only?(descr)
846841

847-
cond do
848-
is_list_type and not has_empty -> {false, list_tl_static(descr)}
849-
is_list_type -> :empty_list
850-
true -> :badlist
842+
if is_list_type and not has_empty do
843+
{false, list_tl_static(descr)}
844+
else
845+
:badnonemptylist
851846
end
852847

853848
{dynamic, static} ->
854849
has_empty = empty_list_type?(static)
855850
only_list = list_only?(static)
856851
is_dynamic_list = list_type?(dynamic)
857852

858-
cond do
859-
is_dynamic_list and only_list and not has_empty ->
860-
{is_dynamic_list, union(dynamic(list_tl_static(dynamic)), list_tl_static(static))}
861-
862-
is_dynamic_list and only_list ->
863-
:empty_list
864-
865-
true ->
866-
:badlist
853+
if is_dynamic_list and only_list and not has_empty do
854+
{is_dynamic_list, union(dynamic(list_tl_static(dynamic)), list_tl_static(static))}
855+
else
856+
:badnonemptylist
867857
end
868858
end
869859
end

lib/elixir/lib/module/types/of.ex

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,26 @@ defmodule Module.Types.Of do
342342
{tuple(List.duplicate(elem, size)), context}
343343
end
344344

345+
def apply(:erlang, :hd, [list], expr, stack, context) do
346+
case list_hd(list) do
347+
{_, value_type} ->
348+
{value_type, context}
349+
350+
reason ->
351+
{error_type(), error({reason, expr, list, context}, elem(expr, 1), stack, context)}
352+
end
353+
end
354+
355+
def apply(:erlang, :tl, [list], expr, stack, context) do
356+
case list_tl(list) do
357+
{_, value_type} ->
358+
{value_type, context}
359+
360+
reason ->
361+
{error_type(), error({reason, expr, list, context}, elem(expr, 1), stack, context)}
362+
end
363+
end
364+
345365
def apply(:erlang, name, [left, right], expr, stack, context)
346366
when name in [:>=, :"=<", :>, :<, :min, :max] do
347367
result = if name in [:min, :max], do: union(left, right), else: boolean()
@@ -618,6 +638,27 @@ defmodule Module.Types.Of do
618638
}
619639
end
620640

641+
def format_diagnostic({:badnonemptylist, expr, type, context}) do
642+
traces = collect_traces(expr, context)
643+
644+
%{
645+
details: %{typing_traces: traces},
646+
message:
647+
IO.iodata_to_binary([
648+
"""
649+
expected a non-empty list in expression:
650+
651+
#{expr_to_string(expr) |> indent(4)}
652+
653+
but got type:
654+
655+
#{to_quoted_string(type) |> indent(4)}
656+
""",
657+
format_traces(traces)
658+
])
659+
}
660+
end
661+
621662
def format_diagnostic({:badtuple, expr, type, _index, context}) do
622663
traces = collect_traces(expr, context)
623664

lib/elixir/test/elixir/module/types/descr_test.exs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -592,17 +592,17 @@ defmodule Module.Types.DescrTest do
592592
end
593593

594594
test "list_hd" do
595-
assert list_hd(term()) == :badlist
596-
assert list_hd(list(term())) == :empty_list
597-
assert list_hd(empty_list()) == :empty_list
595+
assert list_hd(term()) == :badnonemptylist
596+
assert list_hd(list(term())) == :badnonemptylist
597+
assert list_hd(empty_list()) == :badnonemptylist
598598
assert list_hd(non_empty_list(term())) == {false, term()}
599599
assert list_hd(non_empty_list(integer())) == {false, integer()}
600600
assert list_hd(difference(list(number()), list(integer()))) == {false, number()}
601601

602602
assert list_hd(dynamic()) == {true, dynamic()}
603603
assert list_hd(dynamic(list(integer()))) == {true, dynamic(integer())}
604-
assert list_hd(union(dynamic(), atom())) == :badlist
605-
assert list_hd(union(dynamic(), list(term()))) == :empty_list
604+
assert list_hd(union(dynamic(), atom())) == :badnonemptylist
605+
assert list_hd(union(dynamic(), list(term()))) == :badnonemptylist
606606

607607
assert list_hd(union(dynamic(list(float())), non_empty_list(atom()))) ==
608608
{true, union(dynamic(float()), atom())}
@@ -614,9 +614,9 @@ defmodule Module.Types.DescrTest do
614614
end
615615

616616
test "list_tl" do
617-
assert list_tl(term()) == :badlist
618-
assert list_tl(empty_list()) == :empty_list
619-
assert list_tl(list(integer())) == :empty_list
617+
assert list_tl(term()) == :badnonemptylist
618+
assert list_tl(empty_list()) == :badnonemptylist
619+
assert list_tl(list(integer())) == :badnonemptylist
620620
assert list_tl(non_empty_list(integer())) == {false, list(integer())}
621621

622622
assert list_tl(non_empty_list(integer(), atom())) ==

lib/elixir/test/elixir/module/types/expr_test.exs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,71 @@ defmodule Module.Types.ExprTest do
3434
assert typecheck!([1, 2]) == non_empty_list(integer())
3535
assert typecheck!([1, 2 | 3]) == non_empty_list(integer(), integer())
3636
assert typecheck!([1, 2 | [3, 4]]) == non_empty_list(integer())
37+
38+
assert typecheck!([:ok, 123]) == non_empty_list(union(atom([:ok]), integer()))
39+
assert typecheck!([:ok | 123]) == non_empty_list(atom([:ok]), integer())
40+
assert typecheck!([x], [:ok, x]) == dynamic(non_empty_list(term()))
41+
assert typecheck!([x], [:ok | x]) == dynamic(non_empty_list(term(), term()))
42+
end
43+
44+
test "hd" do
45+
assert typecheck!([x = [123, :foo]], hd(x)) == dynamic(union(atom([:foo]), integer()))
46+
assert typecheck!([x = [123 | :foo]], hd(x)) == dynamic(integer())
47+
48+
assert typewarn!(hd([])) ==
49+
{dynamic(),
50+
~l"""
51+
expected a non-empty list in expression:
52+
53+
hd([])
54+
55+
but got type:
56+
57+
empty_list()
58+
"""}
59+
60+
assert typewarn!(hd(123)) ==
61+
{dynamic(),
62+
~l"""
63+
expected a non-empty list in expression:
64+
65+
hd(123)
66+
67+
but got type:
68+
69+
integer()
70+
"""}
71+
end
72+
73+
test "tl" do
74+
assert typecheck!([x = [123, :foo]], tl(x)) == dynamic(list(union(atom([:foo]), integer())))
75+
76+
assert typecheck!([x = [123 | :foo]], tl(x)) ==
77+
dynamic(union(atom([:foo]), list(integer(), atom([:foo]))))
78+
79+
assert typewarn!(tl([])) ==
80+
{dynamic(),
81+
~l"""
82+
expected a non-empty list in expression:
83+
84+
tl([])
85+
86+
but got type:
87+
88+
empty_list()
89+
"""}
90+
91+
assert typewarn!(tl(123)) ==
92+
{dynamic(),
93+
~l"""
94+
expected a non-empty list in expression:
95+
96+
tl(123)
97+
98+
but got type:
99+
100+
integer()
101+
"""}
37102
end
38103
end
39104

0 commit comments

Comments
 (0)