// Additional type conversion abstractions
//
//  Copyright (C) 2014-2023 Ryan Specialty, LLC.
//
//  This file is part of TAME.
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.

//! TAMER opinionated traits for conversion between types.
//!
//! _This should not be used for error reporting to users!_
//! Panics in TAMER represent internal compiler errors.
//!
//! This module introduces two new traits:
//!
//!   - [`ExpectFrom`] implemented for all [`TryFrom`]; and
//!   - [`ExpectInto`] implemented for all [`TryInto`].
//!
//! These traits provide two new methods:
//!
//!   - `expect_{from,into}`, which is equivalent to calling
//!     [`Result::expect`] on the result; and
//!   - `unwrap_{from,into}`, which is equivalent to calling
//!     [`Result::unwrap`] on the result.
//!
//! These two traits are intended to eliminate boilerplate in situations
//!   where _it is expected that these will never fail_.
//! There may be better options;
//!   these are most useful in writing tests.

use crate::{
    diagnose::{panic::DiagnosticPanic, NO_DESC},
    f::ThunkOrStaticRef,
};
use std::{
    convert::{TryFrom, TryInto},
    fmt::{Debug, Display},
};

/// Safe type conversion that may panic under some circumstances.
///
/// This is implemented for every type implementing [`TryFrom`] and is
///   intended to be used in situations where we are certain that failure
///   cannot occur,
///     or where failure would otherwise be folowed by [`Result::expect`] or
///     [`Result::unwrap`].
///
/// Once Rust is further along with a sound trait impl specialization
///   implementation,
///     this system ought to utilize spans when available for diagnostic
///     messages.
///
/// See the [module-level documentation](self) for more information.
pub trait ExpectFrom<T>: TryFrom<T>
where
    <Self as TryFrom<T>>::Error: Debug,
{
    /// Attempt to convert `value` using `T::try_from`,
    ///   causing a panic with the given `msg` on failure.
    ///
    /// Panics
    /// ======
    /// Causes a panic on failure.
    fn expect_from<M: ThunkOrStaticRef<str>>(value: T, msg: M) -> Self
    where
        M::Output: Display,
    {
        Self::try_from(value).diagnostic_expect(|| NO_DESC, msg)
    }

    /// Attempt to convert and unwrap `value` using `T::try_from`.
    ///
    /// Panics
    /// ======
    /// Causes a panic on failure.
    fn unwrap_from(value: T) -> Self {
        Self::try_from(value).diagnostic_unwrap(|| NO_DESC)
    }
}

impl<T, U> ExpectFrom<U> for T
where
    T: TryFrom<U>,
    <T as TryFrom<U>>::Error: Debug,
{
}

/// An attempted conversion that consumes `self`,
///   which may or may not be expensive and will panic on failure.
///
/// This is implemented for every type implementing [`TryInto`] and is
///   intended to be used in situations where we are certain that failure
///   cannot occur,
///     or where failure would otherwise be folowed by [`Result::expect`] or
///     [`Result::unwrap`].
///
/// See the [module-level documentation](self) for more information.
pub trait ExpectInto<T>: TryInto<T>
where
    <Self as TryInto<T>>::Error: Debug,
{
    /// Attempt to convert a value using `self.try_into()`,
    ///   causing a panic with the given `msg` on failure.
    ///
    /// Panics
    /// ======
    /// Causes a panic on failure.
    fn expect_into<M: ThunkOrStaticRef<str>>(self, msg: M) -> T
    where
        M::Output: Display,
    {
        self.try_into().diagnostic_expect(|| NO_DESC, msg)
    }

    /// Attempt to convert and unwrap a value using `self.try_into()`.
    ///
    /// Panics
    /// ======
    /// Causes a panic on failure.
    fn unwrap_into(self) -> T {
        self.try_into().diagnostic_unwrap(|| NO_DESC)
    }
}

impl<T, U> ExpectInto<U> for T
where
    T: TryInto<U>,
    <T as TryInto<U>>::Error: Debug,
{
}
