lib/matcha/context/match.ex

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

  Specs created in this 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_match` 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 | [{{:matched, last_expr}}]]
       {match, guards, body}
     end ++ [{:_, [], [{{:no_match}}]}]}
  end

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

  def __emit_erl_test_result__({:no_match}) do
    {:no_match}
  end

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

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

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

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