defmodule TypeCheck.Builtin.List do
defstruct [:element_type]
use TypeCheck
@type! t :: t(TypeCheck.Type.t())
@type! t(element_type) :: %__MODULE__{element_type: element_type}
@type! problem_tuple ::
{t(), :not_a_list, %{}, any()}
| {t(), :element_error,
%{
problem: lazy(TypeCheck.TypeError.Formatter.problem_tuple()),
index: non_neg_integer()
}, any()}
defimpl TypeCheck.Protocols.Escape do
def escape(s) do
update_in(s.element_type, &TypeCheck.Protocols.Escape.escape(&1))
end
end
defimpl TypeCheck.Protocols.ToCheck do
def to_check(s = %{element_type: element_type}, param) do
quote generated: true, location: :keep do
case unquote(param) do
x when not is_list(x) ->
{:error,
{unquote(TypeCheck.Internals.Escaper.escape(s)), :not_a_list, %{}, unquote(param)}}
_ ->
unquote(build_element_check(element_type, param, s))
end
end
end
defp build_element_check(%TypeCheck.Builtin.Any{}, param, _s) do
quote generated: true, location: :keep do
{:ok, [], unquote(param)}
end
end
defp build_element_check(element_type, param, s) do
element_check =
TypeCheck.Protocols.ToCheck.to_check(element_type, Macro.var(:single_param, __MODULE__))
quote generated: true, location: :keep do
orig_param = unquote(param)
res =
orig_param
|> Enum.with_index()
|> Enum.reduce_while({:ok, [], []}, fn {input, index}, {:ok, bindings, altered_param} ->
var!(single_param, unquote(__MODULE__)) = input
case unquote(element_check) do
{:ok, element_bindings, altered_element} ->
{:cont, {:ok, element_bindings ++ bindings, [altered_element | altered_param]}}
{:error, problem} ->
problem =
{:error,
{unquote(TypeCheck.Internals.Escaper.escape(s)), :element_error,
%{problem: problem, index: index}, orig_param}}
{:halt, problem}
end
end)
case res do
{:ok, bindings, altered_param} -> {:ok, bindings, :lists.reverse(altered_param)}
other -> other
end
end
end
end
defimpl TypeCheck.Protocols.Inspect do
def inspect(list, opts) do
Inspect.Algebra.container_doc(
Inspect.Algebra.color("list(", :builtin_type, opts),
[TypeCheck.Protocols.Inspect.inspect(list.element_type, opts)],
Inspect.Algebra.color(")", :builtin_type, opts),
opts,
fn x, _ -> x end,
separator: "",
break: :maybe
)
|> Inspect.Algebra.color(:builtin_type, opts)
end
end
if Code.ensure_loaded?(StreamData) do
defimpl TypeCheck.Protocols.ToStreamData do
def to_gen(s) do
s.element_type
|> TypeCheck.Protocols.ToStreamData.to_gen()
|> StreamData.list_of()
end
end
end
end