whitaker_common/rstest/span.rs
1//! Pure helpers for recovering user-editable spans from macro-heavy frame chains.
2
3/// A single span-recovery frame and whether it still comes from macro expansion.
4#[derive(Clone, Debug, PartialEq, Eq)]
5pub struct SpanRecoveryFrame<T> {
6 value: T,
7 from_expansion: bool,
8}
9
10impl<T> SpanRecoveryFrame<T> {
11 /// Builds a recovery frame from a value and expansion flag.
12 ///
13 /// # Examples
14 ///
15 /// ```
16 /// use whitaker_common::rstest::SpanRecoveryFrame;
17 /// use whitaker_common::span::{SourceLocation, SourceSpan};
18 ///
19 /// let span = SourceSpan::new(SourceLocation::new(3, 1), SourceLocation::new(3, 8))
20 /// .expect("example span should be valid");
21 /// let frame = SpanRecoveryFrame::new(span, true);
22 ///
23 /// assert!(frame.from_expansion());
24 /// ```
25 #[must_use]
26 pub const fn new(value: T, from_expansion: bool) -> Self {
27 Self {
28 value,
29 from_expansion,
30 }
31 }
32
33 /// Returns the stored frame value.
34 #[must_use]
35 pub const fn value(&self) -> &T {
36 &self.value
37 }
38
39 /// Consumes the frame and returns the stored value.
40 #[must_use]
41 pub fn into_value(self) -> T {
42 self.value
43 }
44
45 /// Returns whether the frame still originates from macro expansion.
46 #[must_use]
47 pub const fn from_expansion(&self) -> bool {
48 self.from_expansion
49 }
50}
51
52/// The result of recovering a user-editable span from an ordered frame chain.
53#[derive(Clone, Debug, PartialEq, Eq)]
54pub enum UserEditableSpan<T> {
55 /// The original frame was already user-editable.
56 Direct(T),
57 /// A later frame recovered a user-editable span.
58 Recovered(T),
59 /// No frame in the chain pointed at user-editable code.
60 MacroOnly,
61}
62
63impl<T> UserEditableSpan<T> {
64 /// Converts the recovery result into an optional recovered value.
65 ///
66 /// # Examples
67 ///
68 /// ```
69 /// use whitaker_common::rstest::UserEditableSpan;
70 /// use whitaker_common::span::{SourceLocation, SourceSpan};
71 ///
72 /// let span = SourceSpan::new(SourceLocation::new(5, 1), SourceLocation::new(5, 7))
73 /// .expect("example span should be valid");
74 ///
75 /// assert_eq!(UserEditableSpan::Recovered(span).into_option(), Some(span));
76 /// assert_eq!(UserEditableSpan::<SourceSpan>::MacroOnly.into_option(), None);
77 /// ```
78 #[must_use]
79 pub fn into_option(self) -> Option<T> {
80 match self {
81 Self::Direct(value) | Self::Recovered(value) => Some(value),
82 Self::MacroOnly => None,
83 }
84 }
85}
86
87/// Recovers the first user-editable frame from an ordered recovery chain.
88///
89/// The first frame represents the original location. Later frames are fallback
90/// candidates such as macro invocation sites or expansion call-sites.
91///
92/// # Examples
93///
94/// ```
95/// use whitaker_common::rstest::{SpanRecoveryFrame, UserEditableSpan, recover_user_editable_span};
96/// use whitaker_common::span::{SourceLocation, SourceSpan};
97///
98/// let macro_span = SourceSpan::new(SourceLocation::new(2, 1), SourceLocation::new(2, 5))
99/// .expect("example span should be valid");
100/// let user_span = SourceSpan::new(SourceLocation::new(10, 1), SourceLocation::new(10, 12))
101/// .expect("example span should be valid");
102///
103/// let recovered = recover_user_editable_span(&[
104/// SpanRecoveryFrame::new(macro_span, true),
105/// SpanRecoveryFrame::new(user_span, false),
106/// ]);
107///
108/// assert_eq!(recovered, UserEditableSpan::Recovered(user_span));
109/// ```
110#[must_use]
111pub fn recover_user_editable_span<T: Clone>(
112 frames: &[SpanRecoveryFrame<T>],
113) -> UserEditableSpan<T> {
114 frames
115 .iter()
116 .enumerate()
117 .find(|(_, frame)| !frame.from_expansion())
118 .map_or(UserEditableSpan::MacroOnly, |(index, frame)| {
119 if index == 0 {
120 UserEditableSpan::Direct(frame.value().clone())
121 } else {
122 UserEditableSpan::Recovered(frame.value().clone())
123 }
124 })
125}