// Matchers for functions, created with `expect_call`.

///|
/// Call the function under test. Return the error it raises, if any.
fn[T] Expectation::call(self : Expectation[() -> T raise Error]) -> Error? {
  try (self.actual)() catch {
    error => Some(error)
  } noraise {
    _ => None
  }
}

///|
/// Describe what a call raised, for the `Received` line.
fn describe_raised(raised : Error?) -> String {
  match raised {
    Some(error) => "error \{show(describe_error(error))}"
    None => "no error"
  }
}

///|
/// Assert the function returns without an error, and return an expectation
/// on the returned value. Cannot be used after `not()`.
#callsite(autofill(loc))
pub fn[T] Expectation::to_return(
  self : Expectation[() -> T raise Error],
  loc~ : SourceLoc,
) -> Expectation[T] raise Error {
  self.check_not_negated("to_return", loc)
  let value = (self.actual)() catch {
    error =>
      self.report(
        "to_return",
        "",
        [("Expected", "no error"), ("Received", describe_raised(Some(error)))],
        loc,
      )
  }
  self.with_value(value)
}

///|
/// Assert the function raises an error, and return an expectation on the
/// error. Use `message()` to check the error text. Cannot be used after
/// `not()`.
#callsite(autofill(loc))
pub fn[T] Expectation::to_raise_error(
  self : Expectation[() -> T raise Error],
  loc~ : SourceLoc,
) -> Expectation[Error] raise Error {
  self.check_not_negated("to_raise_error", loc)
  match self.call() {
    Some(error) => self.with_value(error)
    None =>
      self.report(
        "to_raise_error",
        "",
        [("Expected", "an error"), ("Received", "no error")],
        loc,
      )
  }
}

///|
/// Return an expectation on the message of an error. For a `Failure` raised
/// by `fail`, the message does not include the source location.
pub fn Expectation::message(self : Expectation[Error]) -> Expectation[String] {
  self.navigate(describe_error(self.actual), "message")
}

///|
/// Assert the function raises an error that satisfies `predicate`. Use an
/// `is` pattern to check the error type:
/// `expect_call(() => parse("x")).to_raise_matching(e => e is ParseError::Invalid(_))`.
///
/// Negated, the assertion fails only when the function raises an error that
/// satisfies the predicate.
#callsite(autofill(loc))
pub fn[T] Expectation::to_raise_matching(
  self : Expectation[() -> T raise Error],
  predicate : (Error) -> Bool,
  description? : String = "predicate",
  loc~ : SourceLoc,
) -> Unit raise Error {
  let raised = self.call()
  self.assert_that(
    raised is Some(error) && predicate(error),
    "to_raise_matching",
    args="predicate",
    expected=() => "an error matching \{description}",
    received=() => describe_raised(raised),
    loc~,
  )
}