#![cfg_attr(not(any(test, doctest, feature = "std")), no_std)]
#![warn(missing_docs)]
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
use alloc::boxed::Box;
use core::{convert::Infallible, fmt, panic::Location};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Bug(
#[cfg(feature = "alloc")] Box<BugInner>,
#[cfg(not(feature = "alloc"))] BugInner,
);
#[derive(Clone, Debug, Eq, PartialEq)]
struct BugInner {
msg: &'static str,
location: &'static Location<'static>,
}
impl Bug {
#[cold]
#[track_caller]
#[doc(hidden)]
pub fn new(msg: &'static str) -> Self {
cfg_if::cfg_if! {
if #[cfg(any(test, doc, not(debug_assertions)))] {
#[allow(clippy::useless_conversion)]
Self(BugInner {
msg,
location: Location::caller(),
}.into())
} else {{
#![allow(clippy::disallowed_macros)]
unreachable!("{}", msg)
}}
}
}
#[cold]
#[track_caller]
#[doc(hidden)]
pub fn new_with_source(msg: &'static str, _cause: impl fmt::Display) -> Self {
cfg_if::cfg_if! {
if #[cfg(any(test, doc, not(debug_assertions)))] {
Self::new(msg)
} else {{
#![allow(clippy::disallowed_macros)]
unreachable!("{msg}, caused by: {_cause}")
}}
}
}
pub fn msg(&self) -> &'static str {
self.0.msg
}
}
impl fmt::Display for Bug {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "bug: {}", self.0.msg)?;
writeln!(f, "location: {}", self.0.location)?;
Ok(())
}
}
impl core::error::Error for Bug {}
impl From<Infallible> for Bug {
fn from(err: Infallible) -> Self {
match err {}
}
}
pub trait BugExt<T> {
fn assume(self, msg: &'static str) -> Result<T, Bug>;
}
impl<T> BugExt<T> for Option<T> {
#[track_caller]
fn assume(self, msg: &'static str) -> Result<T, Bug> {
match self {
Some(val) => Ok(val),
None => bug!(msg),
}
}
}
impl<T, E: fmt::Display> BugExt<T> for Result<T, E> {
#[track_caller]
fn assume(self, msg: &'static str) -> Result<T, Bug> {
match self {
Ok(val) => Ok(val),
Err(_err) => bug!(msg, _err),
}
}
}
#[macro_export]
macro_rules! bug {
($msg:expr) => {
return ::core::result::Result::Err($crate::Bug::new($msg).into()).into()
};
($msg:expr, $source:expr) => {
return ::core::result::Result::Err($crate::Bug::new_with_source($msg, $source).into())
.into()
};
}
#[cfg(test)]
mod test {
#![allow(clippy::panic)]
use super::*;
#[test]
fn option_some() {
assert_eq!(Some(42).assume("").unwrap(), 42);
}
#[test]
fn result_ok() {
assert_eq!(Ok::<_, &str>(42).assume("").unwrap(), 42);
}
#[test]
fn option_none() {
let val: Option<()> = None;
let msg = "option_none test";
let before = Location::caller();
let err = val.assume(msg).unwrap_err();
let after = Location::caller();
assert_between(err.0.location, before, after);
assert_eq!(err.0.msg, msg);
}
#[test]
fn result_err() {
let val: Result<(), &str> = Err("inner");
let msg = "result_err test";
let before = Location::caller();
let err = val.assume(msg).unwrap_err();
let after = Location::caller();
assert_between(err.0.location, before, after);
assert_eq!(err.0.msg, msg);
}
fn assert_between(loc: &Location<'_>, before: &Location<'_>, after: &Location<'_>) {
assert_eq!(loc.file(), before.file());
assert_eq!(loc.file(), after.file());
assert!(before.line() < loc.line());
assert!(loc.line() < after.line());
}
}