1#![cfg_attr(not(any(test, doctest, feature = "std")), no_std)]
38#![warn(missing_docs)]
39
40#[cfg(feature = "alloc")]
41extern crate alloc;
42
43#[cfg(feature = "alloc")]
44use alloc::boxed::Box;
45use core::{convert::Infallible, fmt, panic::Location};
46
47#[derive(Clone, Debug, Eq, PartialEq)]
48pub struct Bug(
52 #[cfg(feature = "alloc")] Box<BugInner>,
53 #[cfg(not(feature = "alloc"))] BugInner,
54);
55
56#[derive(Clone, Debug, Eq, PartialEq)]
57struct BugInner {
58 msg: &'static str,
59 location: &'static Location<'static>,
60}
61
62impl Bug {
63 #[cold]
64 #[track_caller]
65 #[doc(hidden)]
66 pub fn new(msg: &'static str) -> Self {
67 cfg_if::cfg_if! {
68 if #[cfg(any(test, doc, not(debug_assertions)))] {
69 #[allow(clippy::useless_conversion)]
70 Self(BugInner {
71 msg,
72 location: Location::caller(),
73 }.into())
74 } else {{
75 #![allow(clippy::disallowed_macros)]
76 unreachable!("{}", msg)
77 }}
78 }
79 }
80
81 #[cold]
82 #[track_caller]
83 #[doc(hidden)]
84 pub fn new_with_source(msg: &'static str, _cause: impl fmt::Display) -> Self {
85 cfg_if::cfg_if! {
86 if #[cfg(any(test, doc, not(debug_assertions)))] {
87 Self::new(msg)
88 } else {{
89 #![allow(clippy::disallowed_macros)]
90 unreachable!("{msg}, caused by: {_cause}")
91 }}
92 }
93 }
94
95 pub fn msg(&self) -> &'static str {
97 self.0.msg
98 }
99}
100
101impl fmt::Display for Bug {
102 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103 writeln!(f, "bug: {}", self.0.msg)?;
104 writeln!(f, "location: {}", self.0.location)?;
105 Ok(())
106 }
107}
108
109impl core::error::Error for Bug {}
110
111impl From<Infallible> for Bug {
112 fn from(err: Infallible) -> Self {
113 match err {}
114 }
115}
116
117pub trait BugExt<T> {
119 fn assume(self, msg: &'static str) -> Result<T, Bug>;
121}
122
123impl<T> BugExt<T> for Option<T> {
124 #[track_caller]
125 fn assume(self, msg: &'static str) -> Result<T, Bug> {
126 match self {
127 Some(val) => Ok(val),
128 None => bug!(msg),
129 }
130 }
131}
132
133impl<T, E: fmt::Display> BugExt<T> for Result<T, E> {
134 #[track_caller]
135 fn assume(self, msg: &'static str) -> Result<T, Bug> {
136 match self {
137 Ok(val) => Ok(val),
138 Err(_err) => bug!(msg, _err),
139 }
140 }
141}
142
143#[macro_export]
159macro_rules! bug {
160 ($msg:expr) => {
161 return ::core::result::Result::Err($crate::Bug::new($msg).into()).into()
162 };
163 ($msg:expr, $source:expr) => {
164 return ::core::result::Result::Err($crate::Bug::new_with_source($msg, $source).into())
165 .into()
166 };
167}
168
169#[cfg(test)]
170mod test {
171 #![allow(clippy::panic)]
172
173 use super::*;
174
175 #[test]
176 fn option_some() {
177 assert_eq!(Some(42).assume("").unwrap(), 42);
178 }
179
180 #[test]
181 fn result_ok() {
182 assert_eq!(Ok::<_, &str>(42).assume("").unwrap(), 42);
183 }
184
185 #[test]
186 fn option_none() {
187 let val: Option<()> = None;
188 let msg = "option_none test";
189
190 let before = Location::caller();
191 let err = val.assume(msg).unwrap_err();
192 let after = Location::caller();
193
194 assert_between(err.0.location, before, after);
195 assert_eq!(err.0.msg, msg);
196 }
197
198 #[test]
199 fn result_err() {
200 let val: Result<(), &str> = Err("inner");
201 let msg = "result_err test";
202
203 let before = Location::caller();
204 let err = val.assume(msg).unwrap_err();
205 let after = Location::caller();
206
207 assert_between(err.0.location, before, after);
208 assert_eq!(err.0.msg, msg);
209 }
210
211 fn assert_between(loc: &Location<'_>, before: &Location<'_>, after: &Location<'_>) {
212 assert_eq!(loc.file(), before.file());
213 assert_eq!(loc.file(), after.file());
214 assert!(before.line() < loc.line());
215 assert!(loc.line() < after.line());
216 }
217}