1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
//!Extension library to panic facilities to make it more usable
//!
//!Requires Rust 1.81
//!
//!## Features
//!
//!- `alloc` - Enables usage of `alloc` types
//!- `std` - Enables `std::error::Error` impl on panic details. Implies `alloc`

#![no_std]
#![warn(missing_docs)]
#![allow(clippy::style)]

#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;

use core::fmt;
use core::panic::{Location, PanicInfo};

///Panic's message definition
pub trait Message: fmt::Display + fmt::Debug {}

impl<T: fmt::Display + fmt::Debug> Message for T {}

#[inline(always)]
///Retrieves panic message from the dynamic payload
///
///Note that while it accepts any type, it is designed to work with panic's payload
pub fn downcast_payload<'a>(payload: &'a (dyn core::any::Any + Send + 'static)) -> &'a dyn Message {
    const DEFAULT_MESSAGE: &'static str = "panic occurred";
    match payload.downcast_ref::<&'static str>() {
        Some(message) => message,
        #[cfg(feature = "alloc")]
        None => match payload.downcast_ref::<alloc::string::String>() {
            Some(message) => message,
            None => &DEFAULT_MESSAGE,
        },
        #[cfg(not(feature = "alloc"))]
        None => &DEFAULT_MESSAGE,
    }
}

#[derive(Clone, Copy, Debug)]
///Panic details
pub struct PanicDetails<'a, M: 'a> {
    ///Panic location
    ///
    ///Location is optional in PanicInfo.
    ///
    ///Therefore if for some reason standard library shall remove it,
    ///instead it will be initialized with location of where message function is called
    pub location: &'a Location<'a>,
    ///Panic message which can string or
    ///[core::fmt::Arguments](https://doc.rust-lang.org/core/fmt/struct.Arguments.html)
    pub message: M,
}

impl<M: Message> core::error::Error for PanicDetails<'_, M> {
}

impl<M: Message> fmt::Display for PanicDetails<'_, M> {
    #[inline(always)]
    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Display::fmt(&self.location, fmt)?;
        fmt.write_str(": ")?;
        fmt::Display::fmt(&self.message, fmt)
    }
}

///Extension trait to provide better API for PanicInfo
pub trait PanicInfoExt<'a> {
    ///Retrieves underlying panic message
    fn panic_message(&'a self) -> impl Message + 'a;

    #[track_caller]
    #[inline(always)]
    ///Access uniform details of panic
    ///
    ///Default implementation uses location of this function call.
    ///When panic location is known, it is overridden with specialized version
    fn panic_details(&'a self) -> PanicDetails<'a, impl Message + 'a> {
        PanicDetails {
            location: Location::caller(),
            message: self.panic_message(),
        }
    }
}

impl<'a> PanicInfoExt<'a> for PanicInfo<'a> {
    #[inline(always)]
    fn panic_message(&'a self) -> impl Message + 'a {
        self.message()
    }

    #[track_caller]
    #[inline(always)]
    fn panic_details(&'a self) -> PanicDetails<'a, impl Message + 'a> {
        let location = match self.location() {
            Some(location) => location,
            None => Location::caller(),
        };
        PanicDetails {
            location,
            message: self.panic_message()
        }
    }
}

impl<'a> PanicInfoExt<'a> for &'a (dyn core::any::Any + Send + 'static) {
    #[inline(always)]
    fn panic_message(&'a self) -> impl Message + 'a {
        downcast_payload(*self)
    }
}

#[cfg(feature = "alloc")]
impl<'a> PanicInfoExt<'a> for alloc::boxed::Box<dyn core::any::Any + Send + 'static> {
    #[inline(always)]
    fn panic_message(&'a self) -> impl Message + 'a {
        downcast_payload(self)
    }
}

#[cfg(feature = "std")]
impl<'a> PanicInfoExt<'a> for std::panic::PanicHookInfo<'a> {
    #[inline(always)]
    fn panic_message(&'a self) -> impl Message + 'a {
        downcast_payload(self.payload())
    }

    #[track_caller]
    #[inline(always)]
    fn panic_details(&'a self) -> PanicDetails<'a, impl Message + 'a> {
        let location = match self.location() {
            Some(location) => location,
            None => Location::caller(),
        };
        PanicDetails {
            location,
            message: self.panic_message()
        }
    }
}