Skip to main content

moxy_token/span/
mod.rs

1mod delim;
2pub(crate) mod fallback;
3mod range;
4
5#[doc(inline)]
6pub use delim::*;
7#[doc(inline)]
8pub use range::*;
9
10use crate::Location;
11
12/// Provides the source span occupied by a value.
13pub trait Spanner {
14    fn span(&self) -> Span;
15}
16
17/// A compiler or fallback source span.
18#[derive(Debug, Copy, Clone)]
19pub enum Span {
20    Compiler(proc_macro::Span),
21    Fallback(fallback::Span),
22}
23
24impl Span {
25    pub fn call_site() -> Self {
26        if proc_macro::is_available() {
27            Self::Compiler(proc_macro::Span::call_site())
28        } else {
29            Self::Fallback(fallback::Span::call_site())
30        }
31    }
32
33    pub fn mixed_site() -> Self {
34        if proc_macro::is_available() {
35            Self::Compiler(proc_macro::Span::mixed_site())
36        } else {
37            Self::Fallback(fallback::Span::mixed_site())
38        }
39    }
40
41    pub fn def_site() -> Self {
42        #[cfg(nightly)]
43        if proc_macro::is_available() {
44            return Self::Compiler(proc_macro::Span::def_site());
45        }
46
47        Self::Fallback(fallback::Span::def_site())
48    }
49
50    pub fn start(&self) -> Location {
51        match self {
52            Self::Compiler(v) => {
53                let lc = v.start();
54
55                #[cfg(nightly)]
56                {
57                    Location::new(v.byte_range().start, lc.line(), lc.column())
58                }
59
60                #[cfg(not(nightly))]
61                {
62                    Location::new(0, lc.line(), lc.column())
63                }
64            }
65            Self::Fallback(v) => v.start(),
66        }
67    }
68
69    pub fn end(&self) -> Location {
70        match self {
71            Self::Compiler(v) => {
72                let lc = v.end();
73
74                #[cfg(nightly)]
75                {
76                    Location::new(v.byte_range().end, lc.line(), lc.column())
77                }
78
79                #[cfg(not(nightly))]
80                {
81                    let src = v.source_text().unwrap_or_default();
82                    let index = line_column_offset(&src, v.line(), v.column());
83                    Location::new(index, lc.line(), lc.column())
84                }
85            }
86            Self::Fallback(v) => v.end(),
87        }
88    }
89
90    pub fn byte_range(&self) -> std::ops::Range<usize> {
91        match self {
92            #[allow(unused)]
93            Self::Compiler(v) => {
94                #[cfg(nightly)]
95                {
96                    v.byte_range()
97                }
98
99                #[cfg(not(nightly))]
100                {
101                    let start = v.start();
102                    let end = v.end();
103                    let src = v.source_text().unwrap_or_default();
104                    let start = line_column_offset(&src, start.line(), start.column());
105                    let end = line_column_offset(&src, end.line(), end.column());
106                    start..end
107                }
108            }
109            Self::Fallback(v) => v.byte_range(),
110        }
111    }
112
113    #[inline]
114    pub fn len(&self) -> usize {
115        match self {
116            Self::Compiler(_) => 0,
117            Self::Fallback(v) => v.len(),
118        }
119    }
120
121    #[inline]
122    pub fn is_empty(&self) -> bool {
123        match self {
124            Self::Compiler(_) => true,
125            Self::Fallback(v) => v.is_empty(),
126        }
127    }
128
129    #[inline]
130    pub fn contains(&self, i: usize) -> bool {
131        match self {
132            Self::Compiler(_) => false,
133            Self::Fallback(v) => v.contains(i),
134        }
135    }
136
137    #[inline]
138    pub fn is_subset(&self, other: &Self) -> bool {
139        match (self, other) {
140            (Self::Fallback(a), Self::Fallback(b)) => a.is_subset(b),
141            _ => false,
142        }
143    }
144
145    pub fn join(&self, other: Self) -> Self {
146        #[cfg(nightly)]
147        if let (Self::Compiler(a), Self::Compiler(b)) = (self, other) {
148            if let Some(joined) = a.join(b) {
149                return Self::Compiler(joined);
150            }
151        }
152
153        if let (Self::Fallback(a), Self::Fallback(b)) = (self, other) {
154            return Self::Fallback(a.join(b));
155        }
156
157        other
158    }
159
160    /// Split a span at `head_len` characters from its start, returning
161    /// `(head_span, rest_span)`. Only `Fallback` spans carry offsets we can split;
162    /// for compiler spans we reuse the whole span for both halves.
163    pub fn split(self, at: usize) -> (Span, Span) {
164        match self {
165            Self::Fallback(s) => {
166                let range = s.byte_range();
167                let mid = (range.start + at) as u32;
168                let head = fallback::Span::new(range.start as u32, mid);
169                let rest = fallback::Span::new(mid, range.end as u32);
170                (Span::Fallback(head), Span::Fallback(rest))
171            }
172            other => (other, other),
173        }
174    }
175}
176
177impl Default for Span {
178    #[inline]
179    fn default() -> Self {
180        Self::call_site()
181    }
182}
183
184impl Eq for Span {}
185
186impl PartialEq for Span {
187    fn eq(&self, other: &Self) -> bool {
188        match (self, other) {
189            (Self::Compiler(a), Self::Compiler(b)) => {
190                a.start().line() == b.start().line()
191                    && a.start().column() == b.start().column()
192                    && a.end().line() == b.end().line()
193                    && a.end().column() == b.end().column()
194            }
195            (Self::Fallback(a), Self::Fallback(b)) => a == b,
196            _ => false,
197        }
198    }
199}
200
201impl Ord for Span {
202    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
203        match (self, other) {
204            (Self::Compiler(a), Self::Compiler(b)) => match a.start().line().cmp(&b.start().line()) {
205                std::cmp::Ordering::Equal => match a.start().column().cmp(&b.start().column()) {
206                    std::cmp::Ordering::Equal => match a.end().line().cmp(&b.end().line()) {
207                        std::cmp::Ordering::Equal => a.end().column().cmp(&b.end().column()),
208                        ord => ord,
209                    },
210                    ord => ord,
211                },
212                ord => ord,
213            },
214            (Self::Fallback(a), Self::Fallback(b)) => a.cmp(b),
215            (Self::Fallback(_), Self::Compiler(_)) => std::cmp::Ordering::Less,
216            (Self::Compiler(_), Self::Fallback(_)) => std::cmp::Ordering::Greater,
217        }
218    }
219}
220
221impl PartialOrd for Span {
222    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
223        Some(self.cmp(other))
224    }
225}
226
227impl std::hash::Hash for Span {
228    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
229        match self {
230            #[allow(unused)]
231            Self::Compiler(s) => {
232                #[cfg(nightly)]
233                {
234                    let range = s.byte_range();
235                    range.start.hash(state);
236                    range.end.hash(state);
237                }
238
239                #[cfg(not(nightly))]
240                {
241                    let start = s.start();
242                    let end = s.end();
243                    let src = s.source_text().unwrap_or_default();
244
245                    line_column_offset(&src, start.line(), start.column()).hash(state);
246                    line_column_offset(&src, end.line(), end.column()).hash(state);
247                }
248            }
249            Self::Fallback(s) => s.hash(state),
250        }
251    }
252}
253
254impl From<fallback::Span> for Span {
255    #[inline]
256    fn from(value: fallback::Span) -> Self {
257        Self::Fallback(value)
258    }
259}
260
261#[cfg(feature = "serde")]
262impl serde::Serialize for Span {
263    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
264    where
265        S: serde::Serializer,
266    {
267        use serde::ser::SerializeStruct;
268
269        match self {
270            Self::Fallback(v) => v.serialize(s),
271            #[allow(unused)]
272            Self::Compiler(v) => {
273                let mut o = s.serialize_struct("Span", 2)?;
274
275                #[cfg(nightly)]
276                {
277                    o.serialize_field("start", &v.byte_range().start)?;
278                    o.serialize_field("end", &v.byte_range().end)?;
279                }
280
281                #[cfg(not(nightly))]
282                {
283                    let start = v.start();
284                    let end = v.end();
285                    let src = v.source_text().unwrap_or_default();
286
287                    o.serialize_field("start", &line_column_offset(&src, start.line(), start.column()))?;
288                    o.serialize_field("end", &line_column_offset(&src, end.line(), end.column()))?;
289                }
290
291                o.end()
292            }
293        }
294    }
295}
296
297pub fn line_column_offset(src: &str, ln: usize, col: usize) -> usize {
298    let mut curr = 0;
299
300    for (i, line) in src.lines().enumerate() {
301        if i + 1 == ln {
302            let offset = line.char_indices().nth(col).map(|(i, _)| i).unwrap_or(line.len());
303            return curr + offset;
304        }
305
306        curr += line.len() + 1;
307    }
308
309    0
310}