code_path/
lib.rs

1#![doc = include_str!("../README.md")]
2use std::fmt;
3
4/// Represents path in the code
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub struct CodePath {
7    context: &'static str,
8    location: &'static str,
9    scope: &'static str,
10}
11
12impl CodePath {
13    /// Creates a new code path value.
14    #[must_use]
15    pub const fn new(context: &'static str, location: &'static str, scope: &'static str) -> Self {
16        Self {
17            context,
18            location,
19            scope,
20        }
21    }
22}
23
24impl fmt::Display for CodePath {
25    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26        if !self.context.is_empty() {
27            write!(f, "{} in ", self.context)?;
28        }
29        write!(f, "{} at {}", self.scope, self.location)
30    }
31}
32
33impl From<CodePath> for String {
34    fn from(val: CodePath) -> Self {
35        val.to_string()
36    }
37}
38
39/// Returns the current code scope with location, e.g.
40/// `foo::bar at src/lib.rs:80:17`
41///
42/// Optionally accepts one or more tokens that `concat!` can combine into a
43/// string literal.
44#[macro_export]
45macro_rules! code_path {
46    ($($context:expr_2021),* $(,)?) => {
47        $crate::CodePath::new(
48            concat!($($context),*),
49            $crate::code_loc!(),
50            $crate::code_scope!(),
51        )
52    };
53}
54
55/// Returns the current scope path, e.g. `my_crate::my_module::my_function`)
56#[macro_export]
57macro_rules! code_scope {
58    () => {{
59        const fn f() {}
60        fn type_name_of<T>(_: T) -> &'static str {
61            ::std::any::type_name::<T>()
62        }
63        type_name_of(f)
64            .strip_suffix("::f")
65            .unwrap_or_default()
66            .trim_end_matches("::{{closure}}")
67    }};
68}
69
70/// Returns the code location: `file_name:line:column`
71#[macro_export]
72macro_rules! code_loc {
73    () => {
74        concat!(file!(), ":", line!())
75    };
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81
82    #[test]
83    fn nesting() {
84        fn foo() -> &'static str {
85            fn bar() -> &'static str {
86                code_scope!()
87            }
88            bar()
89        }
90
91        assert_eq!(foo(), "code_path::tests::nesting::foo::bar");
92    }
93
94    #[test]
95    fn ending_cloures() {
96        fn foo() -> &'static str {
97            #[allow(clippy::redundant_closure_call)]
98            (|| (|| code_scope!())())()
99        }
100        assert_eq!(foo(), "code_path::tests::ending_cloures::foo");
101    }
102
103    #[test]
104    fn literal_context() {
105        let CodePath { context, .. } = code_path!(42);
106
107        assert_eq!(context, "42");
108
109        let CodePath {
110            context: multi_context,
111            ..
112        } = code_path!("answer: ", 42);
113        assert_eq!(multi_context, "answer: 42");
114    }
115}