oxc_span/
span.rs

1use std::{
2    fmt::{self, Debug},
3    hash::{Hash, Hasher},
4    ops::{Index, IndexMut, Range},
5};
6
7use miette::{LabeledSpan, SourceOffset, SourceSpan};
8#[cfg(feature = "serialize")]
9use serde::{Serialize, Serializer as SerdeSerializer, ser::SerializeMap};
10
11use oxc_allocator::{Allocator, CloneIn, Dummy};
12use oxc_ast_macros::ast;
13use oxc_estree::ESTree;
14
15#[cfg(feature = "serialize")]
16use oxc_estree::ESTreeSpan;
17
18/// An empty span.
19///
20/// Should be used for newly created new AST nodes.
21pub const SPAN: Span = Span::new(0, 0);
22
23/// A range in text, represented by a zero-indexed start and end offset.
24///
25/// It is a logical error for `end` to be less than `start`.
26///
27/// ```
28/// # use oxc_span::Span;
29/// let text = "foo bar baz";
30/// let span = Span::new(4, 7);
31/// assert_eq!(&text[span], "bar");
32/// ```
33///
34/// Spans use `u32` for offsets, meaning only files up to 4GB are supported.
35/// This is sufficient for "all" reasonable programs. This tradeof cuts the size
36/// of `Span` in half, offering a sizeable performance improvement and memory
37/// footprint reduction.
38///
39/// ## Creating Spans
40/// Span offers several constructors, each of which is more or less convenient
41/// depending on the context. In general, [`Span::new`] is sufficient for most
42/// cases. If you want to create a span starting at some point of a certain
43/// length, you can use [`Span::sized`].
44///
45/// ```
46/// # use oxc_span::Span;
47/// let a = Span::new(5, 10);  // Start and end offsets
48/// let b = Span::sized(5, 5); // Start offset and size
49/// assert_eq!(a, b);
50/// ```
51///
52/// ## Re-Sizing Spans
53/// Span offsets can be mutated directly, but it is often more convenient to use
54/// one of the [`expand`] or [`shrink`] methods. Each of these create a new span
55/// without modifying the original.
56///
57/// ```
58/// # use oxc_span::Span;
59/// let s = Span::new(5, 10);
60/// assert_eq!(s.shrink(2), Span::new(7, 8));
61/// assert_eq!(s.shrink(2), s.shrink_left(2).shrink_right(2));
62///
63/// assert_eq!(s.expand(5), Span::new(0, 15));
64/// assert_eq!(s.expand(5), s.expand_left(5).expand_right(5));
65/// ```
66///
67/// ## Comparison
68/// [`Span`] has a normal implementation of [`PartialEq`]. If you want to compare two
69/// AST nodes without considering their locations (e.g. to see if they have the
70/// same content), use [`ContentEq`] instead.
71///
72/// ## Implementation Notes
73/// See the [`text-size`](https://docs.rs/text-size) crate for details.
74/// Utility methods can be copied from the `text-size` crate if they are needed.
75///
76/// [`expand`]: Span::expand
77/// [`shrink`]: Span::shrink
78/// [`ContentEq`]: crate::ContentEq
79#[ast(visit)]
80#[derive(Default, Clone, Copy, Eq, PartialOrd, Ord)]
81#[generate_derive(ESTree)]
82#[builder(skip)]
83#[content_eq(skip)]
84#[estree(
85    no_type,
86    flatten,
87    no_ts_def,
88    add_ts_def = "interface Span { start: number; end: number; range?: [number, number]; }"
89)]
90pub struct Span {
91    /// The zero-based start offset of the span
92    pub start: u32,
93    /// The zero-based end offset of the span. This may be equal to [`start`](Span::start) if
94    /// the span is empty, but should not be less than it.
95    pub end: u32,
96    /// Align `Span` on 8 on 64-bit platforms
97    #[estree(skip)]
98    _align: PointerAlign,
99}
100
101impl Span {
102    /// Create a new [`Span`] from a start and end position.
103    ///
104    /// # Invariants
105    /// The `start` position must be less than or equal to `end`. Note that this
106    /// invariant is only checked in debug builds to avoid a performance
107    /// penalty.
108    ///
109    #[inline]
110    pub const fn new(start: u32, end: u32) -> Self {
111        Self { start, end, _align: PointerAlign::new() }
112    }
113
114    /// Create a new empty [`Span`] that starts and ends at an offset position.
115    ///
116    /// # Examples
117    /// ```
118    /// use oxc_span::Span;
119    ///
120    /// let fifth = Span::empty(5);
121    /// assert!(fifth.is_empty());
122    /// assert_eq!(fifth, Span::sized(5, 0));
123    /// assert_eq!(fifth, Span::new(5, 5));
124    /// ```
125    pub fn empty(at: u32) -> Self {
126        Self::new(at, at)
127    }
128
129    /// Create a new [`Span`] starting at `start` and covering `size` bytes.
130    ///
131    /// # Example
132    /// ```
133    /// use oxc_span::Span;
134    ///
135    /// let span = Span::sized(2, 4);
136    /// assert_eq!(span.size(), 4);
137    /// assert_eq!(span, Span::new(2, 6));
138    /// ```
139    pub const fn sized(start: u32, size: u32) -> Self {
140        Self::new(start, start + size)
141    }
142
143    /// Get the number of bytes covered by the [`Span`].
144    ///
145    /// # Example
146    /// ```
147    /// use oxc_span::Span;
148    ///
149    /// assert_eq!(Span::new(1, 1).size(), 0);
150    /// assert_eq!(Span::new(0, 5).size(), 5);
151    /// assert_eq!(Span::new(5, 10).size(), 5);
152    /// ```
153    pub const fn size(self) -> u32 {
154        debug_assert!(self.start <= self.end);
155        self.end - self.start
156    }
157
158    /// Returns `true` if `self` covers a range of zero length.
159    ///
160    /// # Example
161    /// ```
162    /// use oxc_span::Span;
163    ///
164    /// assert!(Span::new(0, 0).is_empty());
165    /// assert!(Span::new(5, 5).is_empty());
166    /// assert!(!Span::new(0, 5).is_empty());
167    /// ```
168    pub const fn is_empty(self) -> bool {
169        debug_assert!(self.start <= self.end);
170        self.start == self.end
171    }
172
173    /// Returns `true` if `self` is not a real span.
174    /// i.e. `SPAN` which is used for generated nodes which are not in source code.
175    ///
176    /// # Example
177    /// ```
178    /// use oxc_span::{Span, SPAN};
179    ///
180    /// assert!(SPAN.is_unspanned());
181    /// assert!(!Span::new(0, 5).is_unspanned());
182    /// assert!(!Span::new(5, 5).is_unspanned());
183    /// ```
184    #[inline]
185    pub const fn is_unspanned(self) -> bool {
186        self.const_eq(SPAN)
187    }
188
189    /// Check if this [`Span`] contains another [`Span`].
190    ///
191    /// [`Span`]s that start & end at the same position as this [`Span`] are
192    /// considered contained.
193    ///
194    /// # Examples
195    ///
196    /// ```rust
197    /// # use oxc_span::Span;
198    /// let span = Span::new(5, 10);
199    ///
200    /// assert!(span.contains_inclusive(span)); // always true for itself
201    /// assert!(span.contains_inclusive(Span::new(5, 5)));
202    /// assert!(span.contains_inclusive(Span::new(6, 10)));
203    /// assert!(span.contains_inclusive(Span::empty(5)));
204    ///
205    /// assert!(!span.contains_inclusive(Span::new(4, 10)));
206    /// assert!(!span.contains_inclusive(Span::empty(0)));
207    /// ```
208    #[inline]
209    pub const fn contains_inclusive(self, span: Span) -> bool {
210        self.start <= span.start && span.end <= self.end
211    }
212
213    /// Create a [`Span`] covering the maximum range of two [`Span`]s.
214    ///
215    /// # Example
216    /// ```
217    /// use oxc_span::Span;
218    ///
219    /// let span1 = Span::new(0, 5);
220    /// let span2 = Span::new(3, 8);
221    /// let merged_span = span1.merge(span2);
222    /// assert_eq!(merged_span, Span::new(0, 8));
223    /// ```
224    #[must_use]
225    pub fn merge(self, other: Self) -> Self {
226        Self::new(self.start.min(other.start), self.end.max(other.end))
227    }
228
229    /// Create a [`Span`] that is grown by `offset` on either side.
230    ///
231    /// This is equivalent to `span.expand_left(offset).expand_right(offset)`.
232    /// See [`expand_left`] and [`expand_right`] for more info.
233    ///
234    /// # Example
235    /// ```
236    /// use oxc_span::Span;
237    ///
238    /// let span = Span::new(3, 5);
239    /// assert_eq!(span.expand(1), Span::new(2, 6));
240    /// // start and end cannot be expanded past `0` and `u32::MAX`, respectively
241    /// assert_eq!(span.expand(5), Span::new(0, 10));
242    /// ```
243    ///
244    /// [`expand_left`]: Span::expand_left
245    /// [`expand_right`]: Span::expand_right
246    #[must_use]
247    pub fn expand(self, offset: u32) -> Self {
248        Self::new(self.start.saturating_sub(offset), self.end.saturating_add(offset))
249    }
250
251    /// Create a [`Span`] that has its start and end positions shrunk by
252    /// `offset` amount.
253    ///
254    /// It is a logical error to shrink the start of the [`Span`] past its end
255    /// position. This will panic in debug builds.
256    ///
257    /// This is equivalent to `span.shrink_left(offset).shrink_right(offset)`.
258    /// See [`shrink_left`] and [`shrink_right`] for more info.
259    ///
260    /// # Example
261    /// ```
262    /// use oxc_span::Span;
263    /// let span = Span::new(5, 10);
264    /// assert_eq!(span.shrink(2), Span::new(7, 8));
265    /// ```
266    ///
267    /// [`shrink_left`]: Span::shrink_left
268    /// [`shrink_right`]: Span::shrink_right
269    #[must_use]
270    pub fn shrink(self, offset: u32) -> Self {
271        let start = self.start.saturating_add(offset);
272        let end = self.end.saturating_sub(offset);
273        debug_assert!(start <= end, "Cannot shrink span past zero length");
274        Self::new(start, end)
275    }
276
277    /// Create a [`Span`] that has its start position moved to the left by
278    /// `offset` bytes.
279    ///
280    /// # Example
281    ///
282    /// ```
283    /// use oxc_span::Span;
284    ///
285    /// let a = Span::new(5, 10);
286    /// assert_eq!(a.expand_left(5), Span::new(0, 10));
287    /// ```
288    ///
289    /// ## Bounds
290    ///
291    /// The leftmost bound of the span is clamped to 0. It is safe to call this
292    /// method with a value larger than the start position.
293    ///
294    /// ```
295    /// use oxc_span::Span;
296    ///
297    /// let a = Span::new(0, 5);
298    /// assert_eq!(a.expand_left(5), Span::new(0, 5));
299    /// ```
300    #[must_use]
301    pub const fn expand_left(self, offset: u32) -> Self {
302        Self::new(self.start.saturating_sub(offset), self.end)
303    }
304
305    /// Create a [`Span`] that has its start position moved to the right by
306    /// `offset` bytes.
307    ///
308    /// It is a logical error to shrink the start of the [`Span`] past its end
309    /// position.
310    ///
311    /// # Example
312    ///
313    /// ```
314    /// use oxc_span::Span;
315    ///
316    /// let a = Span::new(5, 10);
317    /// let shrunk = a.shrink_left(5);
318    /// assert_eq!(shrunk, Span::new(10, 10));
319    ///
320    /// // Shrinking past the end of the span is a logical error that will panic
321    /// // in debug builds.
322    /// std::panic::catch_unwind(|| {
323    ///    shrunk.shrink_left(5);
324    /// });
325    /// ```
326    ///
327    #[must_use]
328    pub const fn shrink_left(self, offset: u32) -> Self {
329        let start = self.start.saturating_add(offset);
330        debug_assert!(start <= self.end);
331        Self::new(self.start.saturating_add(offset), self.end)
332    }
333
334    /// Create a [`Span`] that has its end position moved to the right by
335    /// `offset` bytes.
336    ///
337    /// # Example
338    ///
339    /// ```
340    /// use oxc_span::Span;
341    ///
342    /// let a = Span::new(5, 10);
343    /// assert_eq!(a.expand_right(5), Span::new(5, 15));
344    /// ```
345    ///
346    /// ## Bounds
347    ///
348    /// The rightmost bound of the span is clamped to `u32::MAX`. It is safe to
349    /// call this method with a value larger than the end position.
350    ///
351    /// ```
352    /// use oxc_span::Span;
353    ///
354    /// let a = Span::new(0, u32::MAX);
355    /// assert_eq!(a.expand_right(5), Span::new(0, u32::MAX));
356    /// ```
357    #[must_use]
358    pub const fn expand_right(self, offset: u32) -> Self {
359        Self::new(self.start, self.end.saturating_add(offset))
360    }
361
362    /// Create a [`Span`] that has its end position moved to the left by
363    /// `offset` bytes.
364    ///
365    /// It is a logical error to shrink the end of the [`Span`] past its start
366    /// position.
367    ///
368    /// # Example
369    ///
370    /// ```
371    /// use oxc_span::Span;
372    ///
373    /// let a = Span::new(5, 10);
374    /// let shrunk = a.shrink_right(5);
375    /// assert_eq!(shrunk, Span::new(5, 5));
376    ///
377    /// // Shrinking past the start of the span is a logical error that will panic
378    /// // in debug builds.
379    /// std::panic::catch_unwind(|| {
380    ///    shrunk.shrink_right(5);
381    /// });
382    /// ```
383    #[must_use]
384    pub const fn shrink_right(self, offset: u32) -> Self {
385        let end = self.end.saturating_sub(offset);
386        debug_assert!(self.start <= end);
387        Self::new(self.start, end)
388    }
389
390    /// Create a [`Span`] that has its start and end position moved to the left by
391    /// `offset` bytes.
392    ///
393    /// # Example
394    ///
395    /// ```
396    /// use oxc_span::Span;
397    ///
398    /// let a = Span::new(5, 10);
399    /// let moved = a.move_left(5);
400    /// assert_eq!(moved, Span::new(0, 5));
401    ///
402    /// // Moving the start over 0 is logical error that will panic in debug builds.
403    /// std::panic::catch_unwind(|| {
404    ///    moved.move_left(5);
405    /// });
406    /// ```
407    #[must_use]
408    pub const fn move_left(self, offset: u32) -> Self {
409        let start = self.start.saturating_sub(offset);
410        #[cfg(debug_assertions)]
411        if start == 0 {
412            debug_assert!(self.start == offset, "Cannot move span past zero length");
413        }
414        Self::new(start, self.end.saturating_sub(offset))
415    }
416
417    /// Create a [`Span`] that has its start and end position moved to the right by
418    /// `offset` bytes.
419    ///
420    /// # Example
421    ///
422    /// ```
423    /// use oxc_span::Span;
424    ///
425    /// let a = Span::new(5, 10);
426    /// let moved = a.move_right(5);
427    /// assert_eq!(moved, Span::new(10, 15));
428    ///
429    /// // Moving the end over `u32::MAX` is logical error that will panic in debug builds.
430    /// std::panic::catch_unwind(|| {
431    ///    moved.move_right(u32::MAX);
432    /// });
433    /// ```
434    #[must_use]
435    pub const fn move_right(self, offset: u32) -> Self {
436        let end = self.end.saturating_add(offset);
437        #[cfg(debug_assertions)]
438        if end == u32::MAX {
439            debug_assert!(
440                u32::MAX.saturating_sub(offset) == self.end,
441                "Cannot move span past `u32::MAX` length"
442            );
443        }
444        Self::new(self.start.saturating_add(offset), end)
445    }
446
447    /// Get a snippet of text from a source string that the [`Span`] covers.
448    ///
449    /// # Example
450    /// ```
451    /// use oxc_span::Span;
452    ///
453    /// let source = "function add (a, b) { return a + b; }";
454    /// let name_span = Span::new(9, 12);
455    /// let name = name_span.source_text(source);
456    /// assert_eq!(name_span.size(), name.len() as u32);
457    /// ```
458    pub fn source_text(self, source_text: &str) -> &str {
459        &source_text[self.start as usize..self.end as usize]
460    }
461
462    /// Create a [`LabeledSpan`] covering this [`Span`] with the given label.
463    ///
464    /// Use [`Span::primary_label`] if this is the primary span for the diagnostic.
465    #[must_use]
466    pub fn label<S: Into<String>>(self, label: S) -> LabeledSpan {
467        LabeledSpan::new_with_span(Some(label.into()), self)
468    }
469
470    /// Creates a primary [`LabeledSpan`] covering this [`Span`] with the given label.
471    #[must_use]
472    pub fn primary_label<S: Into<String>>(self, label: S) -> LabeledSpan {
473        LabeledSpan::new_primary_with_span(Some(label.into()), self)
474    }
475
476    /// Creates a primary [`LabeledSpan`] covering this [`Span`].
477    #[must_use]
478    pub fn primary(self) -> LabeledSpan {
479        LabeledSpan::new_primary_with_span(None, self)
480    }
481
482    /// Convert [`Span`] to a single `u64`.
483    ///
484    /// On 64-bit platforms, `Span` is aligned on 8, so equivalent to a `u64`.
485    /// Compiler boils this conversion down to a no-op on 64-bit platforms.
486    /// <https://godbolt.org/z/9rcMoT1fc>
487    ///
488    /// Do not use this on 32-bit platforms as it's likely to be less efficient.
489    ///
490    /// Note: `#[ast]` macro adds `#[repr(C)]` to the struct, so field order is guaranteed.
491    #[expect(clippy::inline_always)] // Because this is a no-op on 64-bit platforms.
492    #[inline(always)]
493    const fn as_u64(self) -> u64 {
494        if cfg!(target_endian = "little") {
495            ((self.end as u64) << 32) | (self.start as u64)
496        } else {
497            ((self.start as u64) << 32) | (self.end as u64)
498        }
499    }
500
501    /// Compare two [`Span`]s.
502    ///
503    /// Same as `PartialEq::eq`, but a const function, and takes owned `Span`s.
504    //
505    // `#[inline(always)]` because want to make sure this is inlined into `PartialEq::eq`.
506    #[expect(clippy::inline_always)]
507    #[inline(always)]
508    const fn const_eq(self, other: Self) -> bool {
509        if cfg!(target_pointer_width = "64") {
510            self.as_u64() == other.as_u64()
511        } else {
512            self.start == other.start && self.end == other.end
513        }
514    }
515}
516
517impl Index<Span> for str {
518    type Output = str;
519
520    #[inline]
521    fn index(&self, index: Span) -> &Self::Output {
522        &self[index.start as usize..index.end as usize]
523    }
524}
525
526impl IndexMut<Span> for str {
527    #[inline]
528    fn index_mut(&mut self, index: Span) -> &mut Self::Output {
529        &mut self[index.start as usize..index.end as usize]
530    }
531}
532
533impl From<Range<u32>> for Span {
534    #[inline]
535    fn from(range: Range<u32>) -> Self {
536        Self::new(range.start, range.end)
537    }
538}
539
540impl From<Span> for SourceSpan {
541    fn from(val: Span) -> Self {
542        Self::new(SourceOffset::from(val.start as usize), val.size() as usize)
543    }
544}
545
546impl From<Span> for LabeledSpan {
547    fn from(val: Span) -> Self {
548        LabeledSpan::underline(val)
549    }
550}
551
552// On 64-bit platforms, compare `Span`s as single `u64`s, which is faster when used with `&Span` refs.
553// https://godbolt.org/z/sEf9MGvsr
554impl PartialEq for Span {
555    #[inline]
556    fn eq(&self, other: &Self) -> bool {
557        self.const_eq(*other)
558    }
559}
560
561// Skip hashing `_align` field.
562// On 64-bit platforms, hash `Span` as a single `u64`, which is faster with `FxHash`.
563// https://godbolt.org/z/4fbvcsTxM
564impl Hash for Span {
565    #[inline] // We exclusively use `FxHasher`, which produces small output hashing `u64`s and `u32`s
566    fn hash<H: Hasher>(&self, hasher: &mut H) {
567        if cfg!(target_pointer_width = "64") {
568            self.as_u64().hash(hasher);
569        } else {
570            self.start.hash(hasher);
571            self.end.hash(hasher);
572        }
573    }
574}
575
576// Skip `_align` field in `Debug` output
577#[expect(clippy::missing_fields_in_debug)]
578impl Debug for Span {
579    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
580        f.debug_struct("Span").field("start", &self.start).field("end", &self.end).finish()
581    }
582}
583
584/// Get the span for an AST node.
585pub trait GetSpan {
586    /// Get the [`Span`] for an AST node.
587    fn span(&self) -> Span;
588}
589
590/// Get mutable ref to span for an AST node.
591pub trait GetSpanMut {
592    /// Get a mutable reference to an AST node's [`Span`].
593    fn span_mut(&mut self) -> &mut Span;
594}
595
596impl GetSpan for Span {
597    #[inline]
598    fn span(&self) -> Span {
599        *self
600    }
601}
602
603impl GetSpanMut for Span {
604    #[inline]
605    fn span_mut(&mut self) -> &mut Span {
606        self
607    }
608}
609
610impl<'a> CloneIn<'a> for Span {
611    type Cloned = Self;
612
613    #[inline]
614    fn clone_in(&self, _: &'a Allocator) -> Self {
615        *self
616    }
617}
618
619impl<'a> Dummy<'a> for Span {
620    /// Create a dummy [`Span`].
621    #[expect(clippy::inline_always)]
622    #[inline(always)]
623    fn dummy(_allocator: &'a Allocator) -> Self {
624        SPAN
625    }
626}
627
628#[cfg(feature = "serialize")]
629impl Serialize for Span {
630    fn serialize<S: SerdeSerializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
631        let mut map = serializer.serialize_map(None)?;
632        map.serialize_entry("start", &self.start)?;
633        map.serialize_entry("end", &self.end)?;
634        map.end()
635    }
636}
637
638#[cfg(feature = "serialize")]
639impl ESTreeSpan for Span {
640    #[expect(clippy::inline_always)] // `#[inline(always)]` because it's a no-op
641    #[inline(always)]
642    fn range(self) -> [u32; 2] {
643        [self.start, self.end]
644    }
645}
646
647/// Zero-sized type which has pointer alignment (8 on 64-bit, 4 on 32-bit).
648#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
649#[repr(transparent)]
650struct PointerAlign([usize; 0]);
651
652impl PointerAlign {
653    #[inline]
654    const fn new() -> Self {
655        Self([])
656    }
657}
658
659#[cfg(test)]
660mod test {
661    use super::Span;
662
663    #[test]
664    fn test_size() {
665        let s = Span::sized(0, 5);
666        assert_eq!(s.size(), 5);
667        assert!(!s.is_empty());
668
669        let s = Span::sized(5, 0);
670        assert_eq!(s.size(), 0);
671        assert!(s.is_empty());
672    }
673
674    #[test]
675    fn test_hash() {
676        use std::hash::{DefaultHasher, Hash, Hasher};
677        fn hash<T: Hash>(value: T) -> u64 {
678            let mut hasher = DefaultHasher::new();
679            value.hash(&mut hasher);
680            hasher.finish()
681        }
682
683        let first_hash = hash(Span::new(1, 5));
684        let second_hash = hash(Span::new(1, 5));
685        assert_eq!(first_hash, second_hash);
686
687        // On 64-bit platforms, check hash is equivalent to `u64`
688        #[cfg(target_pointer_width = "64")]
689        {
690            let u64_equivalent: u64 =
691                if cfg!(target_endian = "little") { 1 + (5 << 32) } else { (1 << 32) + 5 };
692            let u64_hash = hash(u64_equivalent);
693            assert_eq!(first_hash, u64_hash);
694        }
695
696        // On 32-bit platforms, check `_align` field does not alter hash
697        #[cfg(not(target_pointer_width = "64"))]
698        {
699            #[derive(Hash)]
700            #[repr(C)]
701            struct PlainSpan {
702                start: u32,
703                end: u32,
704            }
705
706            let plain_hash = hash(PlainSpan { start: 1, end: 5 });
707            assert_eq!(first_hash, plain_hash);
708        }
709    }
710
711    #[test]
712    fn test_eq() {
713        assert_eq!(Span::new(0, 0), Span::new(0, 0));
714        assert_eq!(Span::new(0, 1), Span::new(0, 1));
715        assert_eq!(Span::new(1, 5), Span::new(1, 5));
716
717        assert_ne!(Span::new(0, 0), Span::new(0, 1));
718        assert_ne!(Span::new(1, 5), Span::new(0, 5));
719        assert_ne!(Span::new(1, 5), Span::new(2, 5));
720        assert_ne!(Span::new(1, 5), Span::new(1, 4));
721        assert_ne!(Span::new(1, 5), Span::new(1, 6));
722    }
723
724    #[test]
725    fn test_ordering_less() {
726        assert!(Span::new(0, 0) < Span::new(0, 1));
727        assert!(Span::new(0, 3) < Span::new(2, 5));
728    }
729
730    #[test]
731    fn test_ordering_greater() {
732        assert!(Span::new(0, 1) > Span::new(0, 0));
733        assert!(Span::new(2, 5) > Span::new(0, 3));
734    }
735
736    #[test]
737    fn test_contains() {
738        let span = Span::new(5, 10);
739
740        assert!(span.contains_inclusive(span));
741        assert!(span.contains_inclusive(Span::new(5, 5)));
742        assert!(span.contains_inclusive(Span::new(10, 10)));
743        assert!(span.contains_inclusive(Span::new(6, 9)));
744
745        assert!(!span.contains_inclusive(Span::new(0, 0)));
746        assert!(!span.contains_inclusive(Span::new(4, 10)));
747        assert!(!span.contains_inclusive(Span::new(5, 11)));
748        assert!(!span.contains_inclusive(Span::new(4, 11)));
749    }
750
751    #[test]
752    fn test_expand() {
753        let span = Span::new(3, 5);
754        assert_eq!(span.expand(0), Span::new(3, 5));
755        assert_eq!(span.expand(1), Span::new(2, 6));
756        // start and end cannot be expanded past `0` and `u32::MAX`, respectively
757        assert_eq!(span.expand(5), Span::new(0, 10));
758    }
759
760    #[test]
761    fn test_shrink() {
762        let span = Span::new(4, 8);
763        assert_eq!(span.shrink(0), Span::new(4, 8));
764        assert_eq!(span.shrink(1), Span::new(5, 7));
765        // can be equal
766        assert_eq!(span.shrink(2), Span::new(6, 6));
767    }
768
769    #[test]
770    #[should_panic(expected = "Cannot shrink span past zero length")]
771    fn test_shrink_past_start() {
772        let span = Span::new(5, 10);
773        let _ = span.shrink(5);
774    }
775
776    #[test]
777    fn test_move_left() {
778        let span = Span::new(5, 10);
779        assert_eq!(span.move_left(1), Span::new(4, 9));
780        assert_eq!(span.move_left(2), Span::new(3, 8));
781        assert_eq!(span.move_left(5), Span::new(0, 5));
782    }
783
784    #[test]
785    #[should_panic(expected = "Cannot move span past zero length")]
786    fn test_move_past_start() {
787        let span = Span::new(5, 10);
788        let _ = span.move_left(6);
789    }
790
791    #[test]
792    fn test_move_right() {
793        let span: Span = Span::new(5, 10);
794        assert_eq!(span.move_right(1), Span::new(6, 11));
795        assert_eq!(span.move_right(2), Span::new(7, 12));
796        assert_eq!(
797            span.move_right(u32::MAX.saturating_sub(10)),
798            Span::new(u32::MAX.saturating_sub(5), u32::MAX)
799        );
800    }
801
802    #[test]
803    #[should_panic(expected = "Cannot move span past `u32::MAX` length")]
804    fn test_move_past_end() {
805        let span = Span::new(u32::MAX.saturating_sub(2), u32::MAX.saturating_sub(1));
806        let _ = span.move_right(2);
807    }
808}
809
810#[cfg(test)]
811mod doctests {
812    use super::Span;
813
814    /// Tests from [`Span`] docs, since rustdoc test runner is disabled
815    #[test]
816    fn doctest() {
817        // 1
818        let text = "foo bar baz";
819        let span = Span::new(4, 7);
820        assert_eq!(&text[span], "bar");
821
822        // 2
823        let a = Span::new(5, 10); // Start and end offsets
824        let b = Span::sized(5, 5); // Start offset and size
825        assert_eq!(a, b);
826
827        // 3
828        let s = Span::new(5, 10);
829        assert_eq!(s.shrink(2), Span::new(7, 8));
830        assert_eq!(s.shrink(2), s.shrink_left(2).shrink_right(2));
831
832        assert_eq!(s.expand(5), Span::new(0, 15));
833        assert_eq!(s.expand(5), s.expand_left(5).expand_right(5));
834    }
835}
836
837#[cfg(test)]
838mod size_asserts {
839    use std::mem::{align_of, size_of};
840
841    use super::Span;
842
843    const _: () = assert!(size_of::<Span>() == 8);
844
845    #[cfg(target_pointer_width = "64")]
846    const _: () = assert!(align_of::<Span>() == 8);
847
848    #[cfg(not(target_pointer_width = "64"))]
849    const _: () = assert!(align_of::<Span>() == 4);
850}