lib/matcha/context/filter_map.ex

defmodule Matcha.Context.FilterMap do
  @moduledoc """
  Functions and operators that `:filter_map` match specs can use.

  ???

  Specs created in this context are unique in that they can differentiate
  between specs that fail to find a matching clause for the given input,
  and specs with matching clauses that literally return the `false` value.
  They return `:no_return` in the former case, and `{:matched, result}` tuples in the latter,
  where `result` can be a literal `false` returned from a clause.
  """

  # TODO: handle `:EXIT`s better in :filter_map/:match contexts

  import Matcha

  alias Matcha.Context
  alias Matcha.Spec

  use Context

  ###
  # CALLBACKS
  ##

  @impl Context
  def __erl_spec_type__ do
    :table
  end

  @impl Context
  def __default_match_target__ do
    nil
  end

  @impl Context
  def __valid_match_target__(_match_target) do
    true
  end

  @impl Context
  def __invalid_match_target_error_message__(_match_target) do
    ""
  end

  @impl Context
  def __prepare_source__(source) do
    {:ok,
     for {match, guards, body} <- source do
       {last_expr, body} = List.pop_at(body, -1)
       body = [body | [{{:returned, last_expr}}]]
       {match, guards, body}
     end ++ [{:_, [], [{{:no_return}}]}]}
  end

  @impl Context
  def __emit_erl_test_result__({:returned, result}) do
    {:emit, result}
  end

  def __emit_erl_test_result__({:no_return}) do
    :no_emit
  end

  @impl Context
  def __transform_erl_test_result__(result) do
    case result do
      {:ok, {:no_return}, [], _warnings} ->
        {:ok, nil}

      {:ok, {:returned, result}, [], _warnings} ->
        {:ok, result}

      {:error, problems} ->
        {:error, problems}
    end
  end

  @impl Context
  def __transform_erl_run_results__(results) do
    spec(:table) do
      {:returned, value} -> value
    end
    |> Spec.run(results)
  end
end