Current section
Files
Jump to
Current section
Files
lib/parse_helpers.ex
defmodule TypeResolver.ParseHelpers do
alias TypeResolver.Env
alias TypeResolver.ParseHelpers.AnnotatedType
alias TypeResolver.ParseHelpers.Literals
alias TypeResolver.ParseHelpers.ParametrizedType
alias TypeResolver.ParseHelpers.RemoteTypes
alias TypeResolver.ParseHelpers.SimpleType
alias TypeResolver.ParseHelpers.UserTypes
alias TypeResolver.ParseHelpers.Vars
def parse(expr, env) do
with {:error, :cannot_parse} <- SimpleType.parse(expr),
{:error, :cannot_parse} <- AnnotatedType.parse(expr, env),
{:error, :cannot_parse} <- ParametrizedType.parse(expr, env),
{:error, :cannot_parse} <- Literals.parse(expr, env),
{:error, :cannot_parse} <- RemoteTypes.parse(expr, env),
{:error, :cannot_parse} <- RemoteTypes.parse_user_defined(expr, env),
{:error, :cannot_parse} <- Vars.parse(expr, env),
{:error, :cannot_parse} <- UserTypes.parse(expr, env),
{:error, :cannot_parse} <- catch_struct(expr) do
{:error, :cannot_parse}
end
end
def resolve(env, type, args \\ []) do
arity = Enum.count(args)
Code.ensure_compiled!(env.target_module)
do_raise = fn ->
raise "no types can be found for type #{type} in module #{env.target_module}. Env: #{inspect(env)}"
end
{:type, {_name, t, vars}} =
case Code.Typespec.fetch_types(env.target_module) do
:error ->
exported_module = Module.concat(env.target_module, ExportedTypes)
case Code.ensure_compiled(exported_module) do
{:module, _} ->
exported_module.types()
|> Enum.find(fn {:type, {t, _, args}} -> t == type && Enum.count(args) == arity end)
|> case do
nil -> do_raise.()
other -> other
end
{:error, _} ->
do_raise.()
end
{:ok, specs} ->
specs
|> Enum.find(fn {:type, {t, _, args}} -> t == type && Enum.count(args) == arity end)
|> case do
nil -> do_raise.()
something -> something
end
end
t |> parse(env |> Env.with_args(prepare_args(vars, args, t, env.target_module)))
end
def parse_user_types(types) do
Enum.map(types, &parse_user_type/1)
end
def parse_user_type({:type, {:"::", _, [{name, _, params}, t]}, _}) do
{name, {t, params}}
end
def prepare_args(nil, _values, _, _), do: %{}
def prepare_args(vars, values, t, target_module) do
if Enum.count(vars) != Enum.count(values) do
raise "Arity mismatched between vars and args for type #{inspect(t)} with target module #{inspect(target_module)}: vars = #{inspect(vars)}, args = #{inspect(values)}"
end
vars
|> Enum.map(fn
{:var, _, name} -> name
{name, _, nil} -> name
end)
|> Enum.zip(values)
|> Map.new()
end
def parse_args(args, env) when is_list(args) do
Enum.reduce(args, {:ok, []}, fn
arg, {:ok, ret} ->
with {:ok, parsed} <- parse(arg, env) do
{:ok, [parsed | ret]}
end
_arg, {:error, _} = err ->
err
end)
end
def parse_args(_, _env), do: {:error, :cannot_parse}
@doc !"""
We catch structs since already parsed parts can end up in the
expression. Only already parsed sub expressions are structs, else
there are none in AST.
"""
defp catch_struct(%_{} = s), do: {:ok, s}
defp catch_struct(_), do: {:error, :cannot_parse}
end