| 1 | //! Panic support for core |
| 2 | //! |
| 3 | //! In core, panicking is always done with a message, resulting in a `core::panic::PanicInfo` |
| 4 | //! containing a `fmt::Arguments`. In std, however, panicking can be done with panic_any, which |
| 5 | //! throws a `Box<dyn Any>` containing any type of value. Because of this, |
| 6 | //! `std::panic::PanicHookInfo` is a different type, which contains a `&dyn Any` instead of a |
| 7 | //! `fmt::Arguments`. std's panic handler will convert the `fmt::Arguments` to a `&dyn Any` |
| 8 | //! containing either a `&'static str` or `String` containing the formatted message. |
| 9 | //! |
| 10 | //! The core library cannot define any panic handler, but it can invoke it. |
| 11 | //! This means that the functions inside of core are allowed to panic, but to be |
| 12 | //! useful an upstream crate must define panicking for core to use. The current |
| 13 | //! interface for panicking is: |
| 14 | //! |
| 15 | //! ``` |
| 16 | //! fn panic_impl(pi: &core::panic::PanicInfo<'_>) -> ! |
| 17 | //! # { loop {} } |
| 18 | //! ``` |
| 19 | //! |
| 20 | //! This module contains a few other panicking functions, but these are just the |
| 21 | //! necessary lang items for the compiler. All panics are funneled through this |
| 22 | //! one function. The actual symbol is declared through the `#[panic_handler]` attribute. |
| 23 | |
| 24 | #![allow (dead_code, missing_docs)] |
| 25 | #![unstable ( |
| 26 | feature = "panic_internals" , |
| 27 | reason = "internal details of the implementation of the `panic!` and related macros" , |
| 28 | issue = "none" |
| 29 | )] |
| 30 | |
| 31 | use crate::fmt; |
| 32 | use crate::intrinsics::const_eval_select; |
| 33 | use crate::panic::{Location, PanicInfo}; |
| 34 | |
| 35 | #[cfg (feature = "panic_immediate_abort" )] |
| 36 | const _: () = assert!(cfg!(panic = "abort" ), "panic_immediate_abort requires -C panic=abort" ); |
| 37 | |
| 38 | // First we define the two main entry points that all panics go through. |
| 39 | // In the end both are just convenience wrappers around `panic_impl`. |
| 40 | |
| 41 | /// The entry point for panicking with a formatted message. |
| 42 | /// |
| 43 | /// This is designed to reduce the amount of code required at the call |
| 44 | /// site as much as possible (so that `panic!()` has as low an impact |
| 45 | /// on (e.g.) the inlining of other functions as possible), by moving |
| 46 | /// the actual formatting into this shared place. |
| 47 | // If panic_immediate_abort, inline the abort call, |
| 48 | // otherwise avoid inlining because of it is cold path. |
| 49 | #[cfg_attr (not(feature = "panic_immediate_abort" ), inline(never), cold)] |
| 50 | #[cfg_attr (feature = "panic_immediate_abort" , inline)] |
| 51 | #[track_caller ] |
| 52 | #[lang = "panic_fmt" ] // needed for const-evaluated panics |
| 53 | #[rustc_do_not_const_check ] // hooked by const-eval |
| 54 | #[rustc_const_stable_indirect] // must follow stable const rules since it is exposed to stable |
| 55 | pub const fn panic_fmt(fmt: fmt::Arguments<'_>) -> ! { |
| 56 | if cfg!(feature = "panic_immediate_abort" ) { |
| 57 | super::intrinsics::abort() |
| 58 | } |
| 59 | |
| 60 | // NOTE This function never crosses the FFI boundary; it's a Rust-to-Rust call |
| 61 | // that gets resolved to the `#[panic_handler]` function. |
| 62 | unsafe extern "Rust" { |
| 63 | #[lang = "panic_impl" ] |
| 64 | unsafefn panic_impl(pi: &PanicInfo<'_>) -> !; |
| 65 | } |
| 66 | |
| 67 | let pi: PanicInfo<'_> = PanicInfo::new( |
| 68 | &fmt, |
| 69 | Location::caller(), |
| 70 | /* can_unwind */ can_unwind:true, |
| 71 | /* force_no_backtrace */ force_no_backtrace:false, |
| 72 | ); |
| 73 | |
| 74 | // SAFETY: `panic_impl` is defined in safe Rust code and thus is safe to call. |
| 75 | unsafe { panic_impl(&pi) } |
| 76 | } |
| 77 | |
| 78 | /// Like `panic_fmt`, but for non-unwinding panics. |
| 79 | /// |
| 80 | /// Has to be a separate function so that it can carry the `rustc_nounwind` attribute. |
| 81 | #[cfg_attr (not(feature = "panic_immediate_abort" ), inline(never), cold)] |
| 82 | #[cfg_attr (feature = "panic_immediate_abort" , inline)] |
| 83 | #[track_caller ] |
| 84 | // This attribute has the key side-effect that if the panic handler ignores `can_unwind` |
| 85 | // and unwinds anyway, we will hit the "unwinding out of nounwind function" guard, |
| 86 | // which causes a "panic in a function that cannot unwind". |
| 87 | #[rustc_nounwind ] |
| 88 | #[rustc_const_stable_indirect] // must follow stable const rules since it is exposed to stable |
| 89 | #[rustc_allow_const_fn_unstable (const_eval_select)] |
| 90 | pub const fn panic_nounwind_fmt(fmt: fmt::Arguments<'_>, force_no_backtrace: bool) -> ! { |
| 91 | const_eval_select!( |
| 92 | @capture { fmt: fmt::Arguments<'_>, force_no_backtrace: bool } -> !: |
| 93 | if const #[track_caller ] { |
| 94 | // We don't unwind anyway at compile-time so we can call the regular `panic_fmt`. |
| 95 | panic_fmt(fmt) |
| 96 | } else #[track_caller ] { |
| 97 | if cfg!(feature = "panic_immediate_abort" ) { |
| 98 | super::intrinsics::abort() |
| 99 | } |
| 100 | |
| 101 | // NOTE This function never crosses the FFI boundary; it's a Rust-to-Rust call |
| 102 | // that gets resolved to the `#[panic_handler]` function. |
| 103 | unsafe extern "Rust" { |
| 104 | #[lang = "panic_impl" ] |
| 105 | fn panic_impl(pi: &PanicInfo<'_>) -> !; |
| 106 | } |
| 107 | |
| 108 | // PanicInfo with the `can_unwind` flag set to false forces an abort. |
| 109 | let pi = PanicInfo::new( |
| 110 | &fmt, |
| 111 | Location::caller(), |
| 112 | /* can_unwind */ false, |
| 113 | force_no_backtrace, |
| 114 | ); |
| 115 | |
| 116 | // SAFETY: `panic_impl` is defined in safe Rust code and thus is safe to call. |
| 117 | unsafe { panic_impl(&pi) } |
| 118 | } |
| 119 | ) |
| 120 | } |
| 121 | |
| 122 | // Next we define a bunch of higher-level wrappers that all bottom out in the two core functions |
| 123 | // above. |
| 124 | |
| 125 | /// The underlying implementation of core's `panic!` macro when no formatting is used. |
| 126 | // Never inline unless panic_immediate_abort to avoid code |
| 127 | // bloat at the call sites as much as possible. |
| 128 | #[cfg_attr (not(feature = "panic_immediate_abort" ), inline(never), cold)] |
| 129 | #[cfg_attr (feature = "panic_immediate_abort" , inline)] |
| 130 | #[track_caller ] |
| 131 | #[rustc_const_stable_indirect] // must follow stable const rules since it is exposed to stable |
| 132 | #[lang = "panic" ] // used by lints and miri for panics |
| 133 | pub const fn panic(expr: &'static str) -> ! { |
| 134 | // Use Arguments::new_const instead of format_args!("{expr}") to potentially |
| 135 | // reduce size overhead. The format_args! macro uses str's Display trait to |
| 136 | // write expr, which calls Formatter::pad, which must accommodate string |
| 137 | // truncation and padding (even though none is used here). Using |
| 138 | // Arguments::new_const may allow the compiler to omit Formatter::pad from the |
| 139 | // output binary, saving up to a few kilobytes. |
| 140 | // However, this optimization only works for `'static` strings: `new_const` also makes this |
| 141 | // message return `Some` from `Arguments::as_str`, which means it can become part of the panic |
| 142 | // payload without any allocation or copying. Shorter-lived strings would become invalid as |
| 143 | // stack frames get popped during unwinding, and couldn't be directly referenced from the |
| 144 | // payload. |
| 145 | panic_fmt(fmt::Arguments::new_const(&[expr])); |
| 146 | } |
| 147 | |
| 148 | // We generate functions for usage by compiler-generated assertions. |
| 149 | // |
| 150 | // Placing these functions in libcore means that all Rust programs can generate a jump into this |
| 151 | // code rather than expanding to panic("...") above, which adds extra bloat to call sites (for the |
| 152 | // constant string argument's pointer and length). |
| 153 | // |
| 154 | // This is especially important when this code is called often (e.g., with -Coverflow-checks) for |
| 155 | // reducing binary size impact. |
| 156 | macro_rules! panic_const { |
| 157 | ($($lang:ident = $message:expr,)+) => { |
| 158 | $( |
| 159 | /// This is a panic called with a message that's a result of a MIR-produced Assert. |
| 160 | // |
| 161 | // never inline unless panic_immediate_abort to avoid code |
| 162 | // bloat at the call sites as much as possible |
| 163 | #[cfg_attr(not(feature = "panic_immediate_abort" ), inline(never), cold)] |
| 164 | #[cfg_attr(feature = "panic_immediate_abort" , inline)] |
| 165 | #[track_caller] |
| 166 | #[rustc_const_stable_indirect] // must follow stable const rules since it is exposed to stable |
| 167 | #[lang = stringify!($lang)] |
| 168 | pub const fn $lang() -> ! { |
| 169 | // Use Arguments::new_const instead of format_args!("{expr}") to potentially |
| 170 | // reduce size overhead. The format_args! macro uses str's Display trait to |
| 171 | // write expr, which calls Formatter::pad, which must accommodate string |
| 172 | // truncation and padding (even though none is used here). Using |
| 173 | // Arguments::new_const may allow the compiler to omit Formatter::pad from the |
| 174 | // output binary, saving up to a few kilobytes. |
| 175 | panic_fmt(fmt::Arguments::new_const(&[$message])); |
| 176 | } |
| 177 | )+ |
| 178 | } |
| 179 | } |
| 180 | |
| 181 | // Unfortunately this set of strings is replicated here and in a few places in the compiler in |
| 182 | // slightly different forms. It's not clear if there's a good way to deduplicate without adding |
| 183 | // special cases to the compiler (e.g., a const generic function wouldn't have a single definition |
| 184 | // shared across crates, which is exactly what we want here). |
| 185 | pub mod panic_const { |
| 186 | use super::*; |
| 187 | panic_const! { |
| 188 | panic_const_add_overflow = "attempt to add with overflow" , |
| 189 | panic_const_sub_overflow = "attempt to subtract with overflow" , |
| 190 | panic_const_mul_overflow = "attempt to multiply with overflow" , |
| 191 | panic_const_div_overflow = "attempt to divide with overflow" , |
| 192 | panic_const_rem_overflow = "attempt to calculate the remainder with overflow" , |
| 193 | panic_const_neg_overflow = "attempt to negate with overflow" , |
| 194 | panic_const_shr_overflow = "attempt to shift right with overflow" , |
| 195 | panic_const_shl_overflow = "attempt to shift left with overflow" , |
| 196 | panic_const_div_by_zero = "attempt to divide by zero" , |
| 197 | panic_const_rem_by_zero = "attempt to calculate the remainder with a divisor of zero" , |
| 198 | panic_const_coroutine_resumed = "coroutine resumed after completion" , |
| 199 | panic_const_async_fn_resumed = "`async fn` resumed after completion" , |
| 200 | panic_const_async_gen_fn_resumed = "`async gen fn` resumed after completion" , |
| 201 | panic_const_gen_fn_none = "`gen fn` should just keep returning `None` after completion" , |
| 202 | panic_const_coroutine_resumed_panic = "coroutine resumed after panicking" , |
| 203 | panic_const_async_fn_resumed_panic = "`async fn` resumed after panicking" , |
| 204 | panic_const_async_gen_fn_resumed_panic = "`async gen fn` resumed after panicking" , |
| 205 | panic_const_gen_fn_none_panic = "`gen fn` should just keep returning `None` after panicking" , |
| 206 | } |
| 207 | // Separated panic constants list for async drop feature |
| 208 | // (May be joined when the corresponding lang items will be in the bootstrap) |
| 209 | panic_const! { |
| 210 | panic_const_coroutine_resumed_drop = "coroutine resumed after async drop" , |
| 211 | panic_const_async_fn_resumed_drop = "`async fn` resumed after async drop" , |
| 212 | panic_const_async_gen_fn_resumed_drop = "`async gen fn` resumed after async drop" , |
| 213 | panic_const_gen_fn_none_drop = "`gen fn` resumed after async drop" , |
| 214 | } |
| 215 | } |
| 216 | |
| 217 | /// Like `panic`, but without unwinding and track_caller to reduce the impact on codesize on the caller. |
| 218 | /// If you want `#[track_caller]` for nicer errors, call `panic_nounwind_fmt` directly. |
| 219 | #[cfg_attr (not(feature = "panic_immediate_abort" ), inline(never), cold)] |
| 220 | #[cfg_attr (feature = "panic_immediate_abort" , inline)] |
| 221 | #[lang = "panic_nounwind" ] // needed by codegen for non-unwinding panics |
| 222 | #[rustc_nounwind ] |
| 223 | #[rustc_const_stable_indirect] // must follow stable const rules since it is exposed to stable |
| 224 | pub const fn panic_nounwind(expr: &'static str) -> ! { |
| 225 | panic_nounwind_fmt(fmt:fmt::Arguments::new_const(&[expr]), /* force_no_backtrace */ force_no_backtrace:false); |
| 226 | } |
| 227 | |
| 228 | /// Like `panic_nounwind`, but also inhibits showing a backtrace. |
| 229 | #[cfg_attr (not(feature = "panic_immediate_abort" ), inline(never), cold)] |
| 230 | #[cfg_attr (feature = "panic_immediate_abort" , inline)] |
| 231 | #[rustc_nounwind ] |
| 232 | pub fn panic_nounwind_nobacktrace(expr: &'static str) -> ! { |
| 233 | panic_nounwind_fmt(fmt:fmt::Arguments::new_const(&[expr]), /* force_no_backtrace */ force_no_backtrace:true); |
| 234 | } |
| 235 | |
| 236 | #[track_caller ] |
| 237 | #[cfg_attr (not(feature = "panic_immediate_abort" ), inline(never), cold)] |
| 238 | #[cfg_attr (feature = "panic_immediate_abort" , inline)] |
| 239 | #[rustc_const_stable_indirect] // must follow stable const rules since it is exposed to stable |
| 240 | pub const fn panic_explicit() -> ! { |
| 241 | panic_display(&"explicit panic" ); |
| 242 | } |
| 243 | |
| 244 | #[inline ] |
| 245 | #[track_caller ] |
| 246 | #[rustc_diagnostic_item = "unreachable_display" ] // needed for `non-fmt-panics` lint |
| 247 | pub fn unreachable_display<T: fmt::Display>(x: &T) -> ! { |
| 248 | panic_fmt(format_args!("internal error: entered unreachable code: {}" , *x)); |
| 249 | } |
| 250 | |
| 251 | /// This exists solely for the 2015 edition `panic!` macro to trigger |
| 252 | /// a lint on `panic!(my_str_variable);`. |
| 253 | #[inline ] |
| 254 | #[track_caller ] |
| 255 | #[rustc_diagnostic_item = "panic_str_2015" ] |
| 256 | #[rustc_const_stable_indirect] // must follow stable const rules since it is exposed to stable |
| 257 | pub const fn panic_str_2015(expr: &str) -> ! { |
| 258 | panic_display(&expr); |
| 259 | } |
| 260 | |
| 261 | #[inline ] |
| 262 | #[track_caller ] |
| 263 | #[rustc_do_not_const_check ] // hooked by const-eval |
| 264 | // enforce a &&str argument in const-check and hook this by const-eval |
| 265 | #[rustc_const_panic_str ] |
| 266 | #[rustc_const_stable_indirect] // must follow stable const rules since it is exposed to stable |
| 267 | pub const fn panic_display<T: fmt::Display>(x: &T) -> ! { |
| 268 | panic_fmt(format_args!(" {}" , *x)); |
| 269 | } |
| 270 | |
| 271 | #[cfg_attr (not(feature = "panic_immediate_abort" ), inline(never), cold, optimize(size))] |
| 272 | #[cfg_attr (feature = "panic_immediate_abort" , inline)] |
| 273 | #[track_caller ] |
| 274 | #[lang = "panic_bounds_check" ] // needed by codegen for panic on OOB array/slice access |
| 275 | fn panic_bounds_check(index: usize, len: usize) -> ! { |
| 276 | if cfg!(feature = "panic_immediate_abort" ) { |
| 277 | super::intrinsics::abort() |
| 278 | } |
| 279 | |
| 280 | panic!("index out of bounds: the len is {len} but the index is {index}" ) |
| 281 | } |
| 282 | |
| 283 | #[cfg_attr (not(feature = "panic_immediate_abort" ), inline(never), cold, optimize(size))] |
| 284 | #[cfg_attr (feature = "panic_immediate_abort" , inline)] |
| 285 | #[track_caller ] |
| 286 | #[lang = "panic_misaligned_pointer_dereference" ] // needed by codegen for panic on misaligned pointer deref |
| 287 | #[rustc_nounwind ] // `CheckAlignment` MIR pass requires this function to never unwind |
| 288 | fn panic_misaligned_pointer_dereference(required: usize, found: usize) -> ! { |
| 289 | if cfg!(feature = "panic_immediate_abort" ) { |
| 290 | super::intrinsics::abort() |
| 291 | } |
| 292 | |
| 293 | panic_nounwind_fmt( |
| 294 | fmt:format_args!( |
| 295 | "misaligned pointer dereference: address must be a multiple of {required:#x} but is {found:#x}" |
| 296 | ), |
| 297 | /* force_no_backtrace */ force_no_backtrace:false, |
| 298 | ) |
| 299 | } |
| 300 | |
| 301 | #[cfg_attr (not(feature = "panic_immediate_abort" ), inline(never), cold, optimize(size))] |
| 302 | #[cfg_attr (feature = "panic_immediate_abort" , inline)] |
| 303 | #[track_caller ] |
| 304 | #[lang = "panic_null_pointer_dereference" ] // needed by codegen for panic on null pointer deref |
| 305 | #[rustc_nounwind ] // `CheckNull` MIR pass requires this function to never unwind |
| 306 | fn panic_null_pointer_dereference() -> ! { |
| 307 | if cfg!(feature = "panic_immediate_abort" ) { |
| 308 | super::intrinsics::abort() |
| 309 | } |
| 310 | |
| 311 | panic_nounwind_fmt( |
| 312 | fmt:format_args!("null pointer dereference occurred" ), |
| 313 | /* force_no_backtrace */ force_no_backtrace:false, |
| 314 | ) |
| 315 | } |
| 316 | |
| 317 | /// Panics because we cannot unwind out of a function. |
| 318 | /// |
| 319 | /// This is a separate function to avoid the codesize impact of each crate containing the string to |
| 320 | /// pass to `panic_nounwind`. |
| 321 | /// |
| 322 | /// This function is called directly by the codegen backend, and must not have |
| 323 | /// any extra arguments (including those synthesized by track_caller). |
| 324 | #[cfg_attr (not(feature = "panic_immediate_abort" ), inline(never), cold, optimize(size))] |
| 325 | #[cfg_attr (feature = "panic_immediate_abort" , inline)] |
| 326 | #[lang = "panic_cannot_unwind" ] // needed by codegen for panic in nounwind function |
| 327 | #[rustc_nounwind ] |
| 328 | fn panic_cannot_unwind() -> ! { |
| 329 | // Keep the text in sync with `UnwindTerminateReason::as_str` in `rustc_middle`. |
| 330 | panic_nounwind(expr:"panic in a function that cannot unwind" ) |
| 331 | } |
| 332 | |
| 333 | /// Panics because we are unwinding out of a destructor during cleanup. |
| 334 | /// |
| 335 | /// This is a separate function to avoid the codesize impact of each crate containing the string to |
| 336 | /// pass to `panic_nounwind`. |
| 337 | /// |
| 338 | /// This function is called directly by the codegen backend, and must not have |
| 339 | /// any extra arguments (including those synthesized by track_caller). |
| 340 | #[cfg_attr (not(feature = "panic_immediate_abort" ), inline(never), cold, optimize(size))] |
| 341 | #[cfg_attr (feature = "panic_immediate_abort" , inline)] |
| 342 | #[lang = "panic_in_cleanup" ] // needed by codegen for panic in nounwind function |
| 343 | #[rustc_nounwind ] |
| 344 | fn panic_in_cleanup() -> ! { |
| 345 | // Keep the text in sync with `UnwindTerminateReason::as_str` in `rustc_middle`. |
| 346 | panic_nounwind_nobacktrace(expr:"panic in a destructor during cleanup" ) |
| 347 | } |
| 348 | |
| 349 | /// This function is used instead of panic_fmt in const eval. |
| 350 | #[lang = "const_panic_fmt" ] // needed by const-eval machine to replace calls to `panic_fmt` lang item |
| 351 | #[rustc_const_stable_indirect] // must follow stable const rules since it is exposed to stable |
| 352 | pub const fn const_panic_fmt(fmt: fmt::Arguments<'_>) -> ! { |
| 353 | if let Some(msg: &'static str) = fmt.as_str() { |
| 354 | // The panic_display function is hooked by const eval. |
| 355 | panic_display(&msg); |
| 356 | } else { |
| 357 | // SAFETY: This is only evaluated at compile time, which reliably |
| 358 | // handles this UB (in case this branch turns out to be reachable |
| 359 | // somehow). |
| 360 | unsafe { crate::hint::unreachable_unchecked() }; |
| 361 | } |
| 362 | } |
| 363 | |
| 364 | #[derive (Debug)] |
| 365 | #[doc (hidden)] |
| 366 | pub enum AssertKind { |
| 367 | Eq, |
| 368 | Ne, |
| 369 | Match, |
| 370 | } |
| 371 | |
| 372 | /// Internal function for `assert_eq!` and `assert_ne!` macros |
| 373 | #[cfg_attr (not(feature = "panic_immediate_abort" ), inline(never), cold, optimize(size))] |
| 374 | #[cfg_attr (feature = "panic_immediate_abort" , inline)] |
| 375 | #[track_caller ] |
| 376 | #[doc (hidden)] |
| 377 | pub fn assert_failed<T, U>( |
| 378 | kind: AssertKind, |
| 379 | left: &T, |
| 380 | right: &U, |
| 381 | args: Option<fmt::Arguments<'_>>, |
| 382 | ) -> ! |
| 383 | where |
| 384 | T: fmt::Debug + ?Sized, |
| 385 | U: fmt::Debug + ?Sized, |
| 386 | { |
| 387 | assert_failed_inner(kind, &left, &right, args) |
| 388 | } |
| 389 | |
| 390 | /// Internal function for `assert_match!` |
| 391 | #[cfg_attr (not(feature = "panic_immediate_abort" ), inline(never), cold, optimize(size))] |
| 392 | #[cfg_attr (feature = "panic_immediate_abort" , inline)] |
| 393 | #[track_caller ] |
| 394 | #[doc (hidden)] |
| 395 | pub fn assert_matches_failed<T: fmt::Debug + ?Sized>( |
| 396 | left: &T, |
| 397 | right: &str, |
| 398 | args: Option<fmt::Arguments<'_>>, |
| 399 | ) -> ! { |
| 400 | // The pattern is a string so it can be displayed directly. |
| 401 | struct Pattern<'a>(&'a str); |
| 402 | impl fmt::Debug for Pattern<'_> { |
| 403 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 404 | f.write_str(self.0) |
| 405 | } |
| 406 | } |
| 407 | assert_failed_inner(kind:AssertKind::Match, &left, &Pattern(right), args); |
| 408 | } |
| 409 | |
| 410 | /// Non-generic version of the above functions, to avoid code bloat. |
| 411 | #[cfg_attr (not(feature = "panic_immediate_abort" ), inline(never), cold, optimize(size))] |
| 412 | #[cfg_attr (feature = "panic_immediate_abort" , inline)] |
| 413 | #[track_caller ] |
| 414 | fn assert_failed_inner( |
| 415 | kind: AssertKind, |
| 416 | left: &dyn fmt::Debug, |
| 417 | right: &dyn fmt::Debug, |
| 418 | args: Option<fmt::Arguments<'_>>, |
| 419 | ) -> ! { |
| 420 | let op: &'static str = match kind { |
| 421 | AssertKind::Eq => "==" , |
| 422 | AssertKind::Ne => "!=" , |
| 423 | AssertKind::Match => "matches" , |
| 424 | }; |
| 425 | |
| 426 | match args { |
| 427 | Some(args: Arguments<'_>) => panic!( |
| 428 | r#"assertion `left {op} right` failed: {args} |
| 429 | left: {left:?} |
| 430 | right: {right:?}"# |
| 431 | ), |
| 432 | None => panic!( |
| 433 | r#"assertion `left {op} right` failed |
| 434 | left: {left:?} |
| 435 | right: {right:?}"# |
| 436 | ), |
| 437 | } |
| 438 | } |
| 439 | |