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}