defmodule TypeCheck.Builtin.NamedType do
defstruct [:name, :type, :local, :type_kind, :called_as]
use TypeCheck
@type! t :: %TypeCheck.Builtin.NamedType{
name: atom(),
type: TypeCheck.Type.t(),
local: boolean(),
type_kind: :type | :typep | :opaque,
called_as: nil | {atom(), list(any())}
}
@type! problem_tuple ::
{t(), :named_type, %{problem: lazy(TypeCheck.TypeError.Formatter.problem_tuple())},
any()}
def stringify_name(atom, _opts) when is_atom(atom), do: to_string(atom)
def stringify_name(str, _opts) when is_binary(str), do: to_string(str)
def stringify_name(other, opts), do: TypeCheck.Protocols.Inspect.inspect(other, opts)
defimpl TypeCheck.Protocols.Escape do
def escape(s) do
case s do
%{called_as: {module, function, args}, type_kind: kind} when kind in [:type, :opaque] ->
escaped_args =
args
|> Enum.map(&TypeCheck.Protocols.Escape.escape/1)
|> Macro.escape(unquote: true)
res =
quote do
unquote(module).unquote(function)(unquote_splicing(escaped_args))
end
{:unquote, [], [res]}
_other ->
%{s | type: TypeCheck.Protocols.Escape.escape(s.type)}
# other
end
end
end
defimpl TypeCheck.Protocols.ToCheck do
def to_check(s, param) do
inner_check = TypeCheck.Protocols.ToCheck.to_check(s.type, param)
if !s.local do
# Do not expose bindings across non-local types
quote generated: true, location: :keep do
inner_res = unquote(inner_check)
case inner_res do
{:ok, _bindings, altered_inner} ->
# Reset bindings
{:ok, [], altered_inner}
{:error, problem} ->
{:error,
{unquote(TypeCheck.Internals.Escaper.escape(s)), :named_type, %{problem: problem},
unquote(param)}}
end
end
else
quote generated: true, location: :keep do
inner_res = unquote(inner_check)
case inner_res do
{:ok, bindings, altered_inner} ->
# Write it to a non-hygienic variable
# that we can read from more outer-level types
# unquote(Macro.var(s.name, TypeCheck.Builtin.NamedType)) = unquote(param)
{:ok, [{unquote(s.name), unquote(param)} | bindings], altered_inner}
{:error, problem} ->
{:error,
{unquote(TypeCheck.Internals.Escaper.escape(s)), :named_type, %{problem: problem},
unquote(param)}}
end
end
end
end
end
defimpl TypeCheck.Protocols.Inspect do
def inspect(s, opts) do
if Map.get(opts, :show_long_named_type, false) || s.local do
@for.stringify_name(s.name, opts)
|> Inspect.Algebra.glue("::")
|> Inspect.Algebra.glue(
TypeCheck.Protocols.Inspect.inspect(s.type, Map.put(opts, :show_long_named_type, false))
)
|> Inspect.Algebra.group()
else
@for.stringify_name(s.name, opts)
end
|> Inspect.Algebra.color(:named_type, opts)
end
end
if Code.ensure_loaded?(StreamData) do
defimpl TypeCheck.Protocols.ToStreamData do
def to_gen(s) do
TypeCheck.Protocols.ToStreamData.to_gen(s.type)
end
end
end
end