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