typst-syntax 0.15.1

Parser and syntax tree for Typst.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
use std::fmt::{self, Debug, Formatter};
use std::num::{NonZeroU16, NonZeroU32, NonZeroU64};
use std::ops::Range;

use ecow::{EcoString, eco_format};

use crate::FileId;

/// Defines a range of text in a Typst source file.
///
/// Spans are used throughout the compiler to track which source section an
/// element stems from or an error/warning applies to. Errors and warnings use
/// the [`DiagSpan`] type which can contain either a normal span or a range
/// targeting a location in a non-Typst file, such as a JSON parsing error.
///
/// - The [`.id()`](Self::id) method can be used to get the [`FileId`] for the
///   span and, by extension, its file system path.
/// - The `WorldExt::range` function can be used to map the span to a
///   `Range<usize>`.
///
/// This type is stored compactly in 8 bytes, and is copyable and null-optimized
/// (i.e. `Option<Span>` also takes 8 bytes), but can be expanded for easier
/// usage into the [`SpanKind`] enum via [`Self::get()`].
///
/// Spans internally distinguish between four kinds of values, these are
/// accessible as the [`SpanKind`] or [`DiagSpanKind`] enums via the
/// [`Span::get`] or [`DiagSpan::get`] methods.
/// 1. They can be detached, originating from nowhere or from the compiler
///    itself.
/// 2. They can be numbered values, corresponding to a node in a Typst source
///    file's concrete syntax tree. These are the most common span type and are
///    explained more below.
/// 3. They can be raw range spans, containing a range of two indices that came
///    from parsing a text as Typst syntax. The file itself is not necessarily a
///    Typst source file. The maximum value for the start/end of these ranges is
///    `2^23-1`, larger values will be saturated.
/// 4. They can be an external start index, used for diagnostics on externally
///    loaded text files. These are only accessible as part of a [`DiagSpan`]
///    which also contains the end index. The maximum value for the start/end of
///    these ranges is `2^46-1`, larger values will be saturated.
///
/// # Numbered spans
/// Typst source files use _numbered spans._ Rather than using byte ranges,
/// which shift a lot as you type, each syntax tree node gets a unique number.
///
/// During editing, the span numbers stay mostly stable, even for nodes behind
/// an insertion. This is not true for simple ranges as they would shift. This
/// allows spans to be used as inputs to memoized functions without hurting
/// cache performance when text is inserted somewhere in the document other than
/// the end.
///
/// Span numbers are ordered in the syntax tree to enable quickly finding the
/// node of a known span:
/// - The span number of a parent node is always smaller than the number of any
///   of its children
/// - The span numbers of sibling nodes always increase from left to right
///
/// Combining those guarantees, we have that for siblings in order [A, B, C],
/// the span numbers for node A and _all of A's children_ are less than node B's
/// span number, and the numbers for node C and all of C's children are greater
/// than B's span number.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Span(NonZeroU64);

/// The unique number of a span within its [`Source`](crate::Source). Known to
/// be within the range of `Span::FULL`.
///
/// This is mainly used externally as an input to the
/// [`Source::range`](crate::Source::range) method for efficiently finding the
/// byte range of a span.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct SpanNumber(pub(crate) u64);

/// The possible kinds of span.
#[derive(Debug)]
pub enum SpanKind {
    /// A span that does not point into any file.
    Detached,
    /// A numbered span.
    Number { id: FileId, num: SpanNumber },
    /// A raw byte range in a file.
    Range { id: FileId, range: Range<usize> },
}

impl Span {
    /// The full range of numbers available for source file span numbering.
    pub(crate) const FULL: Range<u64> = 2..(1 << 47);

    /// The value reserved for the detached span.
    const DETACHED: Self = Self(NonZeroU64::new(1).unwrap());

    /// The span's internal data is laid out with the high 16 bits as the file
    /// id and the low 48 bits as the span number:
    /// | 16 bits file id | 48 bits number |
    ///
    /// Possible values for the span number are:
    /// - always non-zero
    /// - Detached: 1 (file id is 0, otherwise non-zero)
    /// - Typst source file:           2 ..= 2^47-1      (one 47-bit number)
    /// - External file start:      2^47 ..= 2^47+2^46-1 (one 46-bit number)
    /// - Internal range span: 2^47+2^46 ..= 2^48-1      (two 23-bit numbers)
    const NUMBER_BITS: usize = 48;
    const RANGE_BITS: usize = 46;
    const FILE_ID_SHIFT: usize = Self::NUMBER_BITS;
    const NUMBER_MASK: u64 = (1 << Self::NUMBER_BITS) - 1;
    const EXTERNAL_BASE: u64 = Self::FULL.end;
    const EXTERNAL_VALUE_MAX: u64 = (1 << Self::RANGE_BITS) - 1;
    const RANGE_BASE: u64 = Self::EXTERNAL_BASE + (1 << Self::RANGE_BITS);
    const RANGE_VALUE_BITS: usize = 23;
    const RANGE_VALUE_MAX: u64 = (1 << Self::RANGE_VALUE_BITS) - 1;

    /// The detached span that does not point into any file.
    pub const fn detached() -> Self {
        Self::DETACHED
    }

    /// Create a new span from a [`FileId`] and a [`SpanNumber`].
    pub(crate) const fn from_number(id: FileId, SpanNumber(number): SpanNumber) -> Self {
        debug_assert!(Self::FULL.start <= number);
        debug_assert!(number < Self::FULL.end);
        Self::pack(id, number)
    }

    /// Create a new span from a raw byte range instead of a span number.
    ///
    /// If one of the range's parts exceeds the maximum value of `2^23-1`, it is
    /// saturated.
    pub(crate) const fn from_range(id: FileId, range: Range<usize>) -> Self {
        let start = saturate(range.start, Self::RANGE_VALUE_MAX);
        let end = saturate(range.end, Self::RANGE_VALUE_MAX);
        let number = (start << Self::RANGE_VALUE_BITS) | end;
        Self::pack(id, Self::RANGE_BASE + number)
    }

    /// Construct from a raw number.
    ///
    /// Should only be used with numbers retrieved via
    /// [`into_raw`](Self::into_raw). Misuse may results in panics, but no
    /// unsafety.
    pub const fn from_raw(v: NonZeroU64) -> Self {
        Self(v)
    }

    /// Pack a file ID and the low bits into a span.
    const fn pack(id: FileId, low: u64) -> Self {
        let bits = ((id.into_raw().get() as u64) << Self::FILE_ID_SHIFT) | low;

        // The file ID is non-zero.
        Self(NonZeroU64::new(bits).unwrap())
    }

    /// Whether the span is detached.
    pub const fn is_detached(self) -> bool {
        self.0.get() == Self::DETACHED.0.get()
    }

    /// The id of the file the span points into.
    ///
    /// Returns `None` if the span is detached.
    pub const fn id(self) -> Option<FileId> {
        // Detached span has only zero high bits, so it will trigger the
        // `None` case.
        match NonZeroU16::new((self.0.get() >> Self::FILE_ID_SHIFT) as u16) {
            Some(v) => Some(FileId::from_raw(v)),
            None => None,
        }
    }

    /// The unique number of the span within its [`Source`](crate::Source).
    pub(crate) const fn number(self) -> u64 {
        self.0.get() & Self::NUMBER_MASK
    }

    /// Unpack the span into the variants of a [`SpanKind`] for easier use.
    ///
    /// To access a range, you may want to use `WorldExt::range` instead.
    pub const fn get(self) -> SpanKind {
        let Some(id) = self.id() else { return SpanKind::Detached };
        let num = self.number();
        if let Some(packed_range) = num.checked_sub(Self::RANGE_BASE) {
            let start = (packed_range >> Self::RANGE_VALUE_BITS) as usize;
            let end = (packed_range & Self::RANGE_VALUE_MAX) as usize;
            SpanKind::Range { id, range: start..end }
        } else {
            SpanKind::Number { id, num: SpanNumber(num) }
        }
    }

    /// Extract the raw underlying number.
    pub const fn into_raw(self) -> NonZeroU64 {
        self.0
    }

    /// Return `other` if `self` is detached and `self` otherwise.
    pub fn or(self, other: Self) -> Self {
        if self.is_detached() { other } else { self }
    }

    /// Find the first non-detached span in the iterator.
    pub fn find(iter: impl IntoIterator<Item = Self>) -> Self {
        iter.into_iter()
            .find(|span| !span.is_detached())
            .unwrap_or(Span::detached())
    }
}

/// The span of a diagnostic message. Either from a Typst source file or from a
/// loaded external file.
///
/// Typst source spans may additionally contain a sub-range targeting just part
/// of the overall range of the span.
///
/// When storing an external file range, the maximum value of the start/end is
/// `2^46-1`, larger values are saturated.
///
/// This type is stored compactly in 16 bytes and null-optimized, but can be
/// expanded for easier usage as the [`DiagSpanKind`] enum via [`Self::get()`].
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub struct DiagSpan {
    span: Span,
    extra: u64,
}

/// The possible kinds of a diagnostic span.
#[derive(Debug)]
pub enum DiagSpanKind {
    /// A span that does not point into any file.
    Detached,
    /// A numbered span with an optional sub-range.
    Number { id: FileId, num: SpanNumber, sub_range: Option<SubRange> },
    /// A raw byte range in a file.
    Range { id: FileId, range: Range<usize> },
}

impl DiagSpan {
    /// The detached diagnostic span that does not point into any file.
    pub const fn detached() -> Self {
        Self { span: Span::DETACHED, extra: 0 }
    }

    /// Create a new diagnostic span from an external file's byte range instead
    /// of an internal span.
    ///
    /// If either of the range's parts exceeds the maximum value of `2^46-1`, it
    /// will be saturated.
    pub fn from_range(id: FileId, range: Range<usize>) -> Self {
        let start = saturate(range.start, Span::EXTERNAL_VALUE_MAX);
        let end = saturate(range.end, Span::EXTERNAL_VALUE_MAX);
        Self {
            span: Span::pack(id, Span::EXTERNAL_BASE + start),
            extra: end,
        }
    }

    /// Create a new diagnostic span from a source span and optional sub-range.
    ///
    /// The preferred interface for converting a [`Span`] into [`DiagSpan`] is
    /// using `span.into()`, which calls this with `sub_range: None`.
    pub fn from_span(span: Span, sub_range: Option<SubRange>) -> Self {
        let extra = sub_range.map_or(0, |SubRange { start, end }| {
            ((start as u64) << 32) | (end.get() as u64)
        });
        Self { span, extra }
    }

    /// Whether the diagnostic span is detached.
    pub const fn is_detached(self) -> bool {
        self.span.0.get() == Span::DETACHED.0.get()
    }

    /// The id of the file the span points into.
    ///
    /// Returns `None` if the span is detached.
    pub fn id(self) -> Option<FileId> {
        self.span.id()
    }

    /// Return `other` if `self` is detached and `self` otherwise.
    pub fn or(self, other: Self) -> Self {
        if self.is_detached() { other } else { self }
    }

    /// Unpack the diagnostic span into the variants of a [`DiagSpanKind`] for
    /// easier use.
    ///
    /// To access a range, you may want to use `WorldExt::range` instead.
    pub fn get(self) -> DiagSpanKind {
        let DiagSpan { span, extra } = self;
        match span.get() {
            SpanKind::Detached => DiagSpanKind::Detached,
            SpanKind::Number { id, num } => {
                if let Some(start) = num.0.checked_sub(Span::EXTERNAL_BASE) {
                    // This `checked_sub` must come after the internal range
                    // check from `span.get()`.
                    let start = start as usize;
                    let end = extra as usize;
                    DiagSpanKind::Range { id, range: start..end }
                } else {
                    let sub_range = {
                        let start = (extra >> 32) as u32;
                        let end = NonZeroU32::new(extra as u32); // `as` truncates
                        end.map(|end| SubRange { start, end })
                    };
                    DiagSpanKind::Number { id, num, sub_range }
                }
            }
            SpanKind::Range { id, range } => {
                if let Some(end) = NonZeroU32::new(extra as u32) {
                    let start = (extra >> 32) as u32;
                    let sub_range = SubRange { start, end };
                    let range = sub_range.to_absolute(range.start);
                    DiagSpanKind::Range { id, range }
                } else {
                    DiagSpanKind::Range { id, range }
                }
            }
        }
    }
}

impl From<Span> for DiagSpan {
    fn from(span: Span) -> Self {
        Self::from_span(span, None)
    }
}

/// Saturate a value at a given maximum. Can't use `.min()` since it isn't
/// stable in const :/
const fn saturate(value: usize, max: u64) -> u64 {
    if value as u64 > max { max } else { value as u64 }
}

/// A non-empty range targeting a smaller part of a spanned section of text.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct SubRange {
    start: u32,
    end: NonZeroU32,
}

impl SubRange {
    /// Create a new sub-range. The given start and end must create a non-empty
    /// range.
    ///
    /// If start or end are above a `2^32-1`, they will be saturated.
    pub fn new(start: usize, end: usize) -> Option<Self> {
        if start < end {
            Some(Self {
                start: to_u32_saturated(start),
                // (0 <= start) && (start < end) --> (end != 0)
                end: NonZeroU32::new(to_u32_saturated(end)).unwrap(),
            })
        } else {
            None
        }
    }

    /// Convert to a normal range relative to the spanned range.
    pub fn to_relative(self) -> Range<usize> {
        Range {
            start: self.start as usize,
            end: self.end.get() as usize,
        }
    }

    /// Convert to a normal range at an offset.
    pub fn to_absolute(self, offset: usize) -> Range<usize> {
        Range {
            start: self.start as usize + offset,
            end: self.end.get() as usize + offset,
        }
    }
}

// Convert a `usize` to a `u32` by saturating at `u32::MAX`.
fn to_u32_saturated(value: usize) -> u32 {
    value.try_into().unwrap_or(u32::MAX)
}

/// A value with a span locating it in the source code.
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
#[expect(private_bounds)]
pub struct Spanned<T, S: Copy + SpanDetached = Span> {
    /// The spanned value.
    pub v: T,
    /// The value's location in source code.
    pub span: S,
}

#[expect(private_bounds)]
impl<T, S: Copy + SpanDetached> Spanned<T, S> {
    /// Create a new instance from a value and its span.
    pub const fn new(v: T, span: S) -> Self {
        Self { v, span }
    }

    /// Create a new instance with a span that does not point into any file.
    pub const fn detached(v: T) -> Self {
        Self { v, span: S::SPAN_DETACHED }
    }

    /// Convert from `&Spanned<T>` to `Spanned<&T>`
    pub const fn as_ref(&self) -> Spanned<&T, S> {
        Spanned { v: &self.v, span: self.span }
    }

    /// Map the value using a function.
    pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Spanned<U, S> {
        Spanned { v: f(self.v), span: self.span }
    }
}

impl<T: Debug, S: Copy + SpanDetached> Debug for Spanned<T, S> {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        self.v.fmt(f)
    }
}

/// Allows generic access to the detached span. Only used to implement
/// [`Spanned::detached`].
trait SpanDetached {
    const SPAN_DETACHED: Self;
}

impl SpanDetached for Span {
    const SPAN_DETACHED: Self = Self::detached();
}

impl SpanDetached for DiagSpan {
    const SPAN_DETACHED: Self = Self::detached();
}

/// Remaps ranges.
///
/// Useful in combination with
/// [`SyntaxNode::synthesize_mapped`](super::SyntaxNode::synthesize_mapped) to
/// have accurate error spans for source text that is non-consecutive in its
/// source file (for instance, Typst code in a doc comment with start-of-line
/// slashes).
#[derive(Hash)]
pub struct RangeMapper {
    vec: Vec<Mapping>,
    total: usize,
}

/// A mapping from an old index to a new one, guarantees that `old <= new`.
#[derive(Hash, Clone, Copy)]
struct Mapping {
    old: usize,
    new: usize,
}

impl RangeMapper {
    /// Creates a new range mapper.
    ///
    /// The iterator should returns ranges in the original text that will be
    /// consecutively concatenated to produce the derived text.
    ///
    /// Segments should be in order. (The start of a later range must not
    /// precede the end of an earlier range.)
    ///
    /// Note that this representation implies that ranges can only ever increase
    /// in their start position and length when mapped.
    pub fn new(
        segments: impl IntoIterator<Item = Range<usize>>,
    ) -> Result<Self, EcoString> {
        let mut map = Mapping { old: 0, new: 0 };
        let vec = segments
            .into_iter()
            .map(|Range { start, end }| {
                if start > end || map.new > start {
                    return Err(eco_format!("invalid mapper segment: ({start}, {end})"));
                }
                map.new = start;
                let segment_map = map;
                map.old += end - start;
                Ok(segment_map)
            })
            .collect::<Result<Vec<Mapping>, EcoString>>()?;

        if vec.is_empty() {
            Ok(Self { vec: vec![map], total: 0 })
        } else {
            Ok(Self { vec, total: map.old })
        }
    }

    /// The total length of the original text.
    pub(crate) fn total_len(&self) -> usize {
        self.total
    }

    /// Maps a range in the derived text back to a range in the original text.
    /// If the range spans over multiple segments, the gap between the two
    /// segments will be included in the resulting range.
    ///
    /// Input ranges must have  `start <= end`, and the caller should have
    /// verified that `end <= self.total`.
    pub(crate) fn map(&self, range: Range<usize>) -> Range<usize> {
        debug_assert!(range.start <= range.end);
        if range.end == 0 {
            // Handles the panic case of `map_end`.
            let offset = self.vec[0].new;
            offset..offset
        } else if range.start == range.end {
            // If start/end are at a boundary, map them to the first position,
            // not the second.
            let offset = self.map_end(range.start);
            offset..offset
        } else {
            // We now know that `start < end`, so the values from `map_start`
            // and `map_end` must be non-overlapping.
            let start = self.map_start(range.start);
            let end = self.map_end(range.end);
            start..end
        }
    }

    /// Map a relative sub-range at an offset to a new sub-range. If the
    /// sub-range spans over multiple segments, the gap between them will be
    /// included in the new sub-range.
    pub(crate) fn map_sub_range(&self, offset: usize, sub_range: SubRange) -> SubRange {
        let range = sub_range.to_absolute(offset);
        let new_offset = self.map_start(offset);
        let start = self.map_start(range.start);
        let end = self.map_end(range.end); // sub-ranges have `start < end`.
        SubRange::new(start - new_offset, end - new_offset).unwrap()
    }

    /// Map a single offset, preferring the second index if at a boundary.
    fn map_start(&self, offset: usize) -> usize {
        let idx = self.vec.partition_point(|&Mapping { old, new: _ }| old <= offset);
        // Subtracting by 1 is valid: vec is non-empty, index 0 has `old == 0`,
        // and `partition_point` returns the index of the first item to fail the
        // predicate (or the length), which is not index 0, since `0 <= usize`
        // is true for all usize.
        let Mapping { old, new } = &self.vec[idx - 1];
        new + (offset - old)
    }

    /// Map a single offset, preferring the first index if at a boundary.
    ///
    /// This will panic if `offset` is 0.
    fn map_end(&self, offset: usize) -> usize {
        debug_assert_ne!(offset, 0);
        let idx = self.vec.partition_point(|&Mapping { old, new: _ }| old < offset);
        // Unlike `map_start`, this can yield index 0 when `offset == 0`, making
        // `idx - 1` potentially panicking.
        let Mapping { old, new } = &self.vec[idx - 1];
        new + (offset - old)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_span_detached() {
        let span = Span::detached();
        assert!(span.is_detached());
        assert_eq!(span.id(), None);
    }

    #[test]
    fn test_span_number_encoding() {
        let id = FileId::from_raw(NonZeroU16::new(5).unwrap());
        let span = Span::from_number(id, SpanNumber(10));
        assert_eq!(span.id(), Some(id));
        assert_eq!(span.number(), 10);
    }

    #[test]
    fn test_span_range_encoding() {
        let file_id = FileId::from_raw(NonZeroU16::new(u16::MAX).unwrap());
        let roundtrip = |range: Range<usize>| {
            let span = Span::from_range(file_id, range.clone());
            let SpanKind::Range { id, range: actual } = span.get() else {
                panic!("bad span kind")
            };
            assert_eq!(id, file_id);
            assert_eq!(actual, range);
        };

        roundtrip(0..0);
        roundtrip(177..233);
        roundtrip(0..8388607);
        roundtrip(8388606..8388607); // 2^23-2 .. 2^23-1
    }

    #[test]
    fn test_diag_span_range() {
        let file_id = FileId::from_raw(NonZeroU16::new(u16::MAX).unwrap());
        let roundtrip = |range: Range<usize>| {
            let span = DiagSpan::from_range(file_id, range.clone());
            let DiagSpanKind::Range { id, range: actual } = span.get() else {
                panic!("bad diagspan kind")
            };
            assert_eq!(id, file_id);
            assert_eq!(actual, range);
        };

        roundtrip(0..0);
        roundtrip(177..233);
        roundtrip(0..8388607);
        roundtrip(8388606..8388607); // 2^23-2 .. 2^23-1
        roundtrip(8388608..8388609); // 2^23   .. 2^23+1
        #[cfg(target_pointer_width = "64")]
        roundtrip(70368744177662..70368744177663); // 2^46-2 .. 2^46-1
    }

    #[test]
    fn test_sub_range_constructor() {
        let max = u32::MAX as usize;
        // valid
        assert!(SubRange::new(0, 1).is_some());
        assert!(SubRange::new(4, 5).is_some());
        assert!(SubRange::new(0, max).is_some());
        assert!(SubRange::new(0, max - 1).is_some());
        assert!(SubRange::new(max - 1, max).is_some());
        // invalid
        assert!(SubRange::new(0, 0).is_none());
        assert!(SubRange::new(5, 5).is_none());
        assert!(SubRange::new(5, 4).is_none());
        assert!(SubRange::new(max - 1, max - 1).is_none());
        assert!(SubRange::new(max, max).is_none());
    }

    #[cfg(target_pointer_width = "64")]
    #[test]
    fn test_sub_range_saturating() {
        // Values saturate at 2^32-1
        let max = u32::MAX as usize;
        let maxxed = SubRange::new(max, max + 1).unwrap();
        assert_eq!(maxxed.start, maxxed.end.get());
        assert_eq!(SubRange::new(1 << 47, 1 << 63), Some(maxxed));
    }

    #[test]
    fn test_range_mapper() {
        let base = "-- Hello\n-- world\n";
        let ranges = [(3..9), (12..18)];
        let mapped = ranges.iter().map(|r| &base[r.clone()]).collect::<String>();
        let m = RangeMapper::new(ranges).unwrap();

        assert_eq!(mapped, "Hello\nworld\n");
        assert_eq!(m.map(2..3), 5..6); // l -> l
        assert_eq!(m.map(4..6), (7..9)); // o\n -> o\n
        assert_eq!(m.map(6..8), (12..14)); // wo -> wo
        assert_eq!(m.map(8..11), (14..17)); // rld -> rld
        assert_eq!(m.map(2..12), (5..18)); // llo\n-- world\n -> llo\n-- world\n

        // Empty ranges on boundaries:
        assert_eq!(m.map(0..0), (3..3));
        assert_eq!(m.map(6..6), (9..9)); // maps to the left of the boundary
        assert_eq!(m.map(12..12), (18..18));
    }

    /// Small exhaustive edge case tests for the range mapper
    #[test]
    fn test_range_mapper_exhaustive() {
        let empty = RangeMapper::new([]).unwrap();
        assert_eq!(empty.map(0..0), 0..0);

        let exact = RangeMapper::new(Some(0..1)).unwrap();
        assert_eq!(exact.map(0..0), 0..0);
        assert_eq!(exact.map(0..1), 0..1);
        assert_eq!(exact.map(1..1), 1..1);

        let plus = RangeMapper::new(Some(10..11)).unwrap();
        assert_eq!(plus.map(0..0), 10..10);
        assert_eq!(plus.map(0..1), 10..11);
        assert_eq!(plus.map(1..1), 11..11);

        let disjoint = RangeMapper::new([(10..11), (21..22)]).unwrap();
        assert_eq!(disjoint.map(0..0), 10..10);
        assert_eq!(disjoint.map(0..1), 10..11);
        assert_eq!(disjoint.map(0..2), 10..22);
        assert_eq!(disjoint.map(1..1), 11..11);
        assert_eq!(disjoint.map(1..2), 21..22);
        assert_eq!(disjoint.map(2..2), 22..22);

        // disjoint with interspersed empty ranges.
        let with_empty = RangeMapper::new([
            (10..10),
            (10..11),
            (11..11),
            (16..16),
            (21..21),
            (21..22),
            (22..22),
        ])
        .unwrap();
        assert_eq!(with_empty.map(0..0), 10..10);
        assert_eq!(with_empty.map(0..1), 10..11);
        assert_eq!(with_empty.map(0..2), 10..22);
        assert_eq!(with_empty.map(1..1), 11..11);
        assert_eq!(with_empty.map(1..2), 21..22);
        assert_eq!(with_empty.map(2..2), 22..22);
    }

    #[test]
    fn test_sub_range_mapping() {
        let base = "01_23__45";
        let ranges = [(0..2), (3..5), (7..9)];
        let mapped = ranges.iter().map(|r| &base[r.clone()]).collect::<String>();
        assert_eq!(mapped, "012345");
        let m = RangeMapper::new(ranges).unwrap();

        let map_at = |at: usize, sr: Option<SubRange>| {
            let sub_range = sr.unwrap();
            m.map_sub_range(at, sub_range).to_relative()
        };

        // Ranges within each section:
        assert_eq!(map_at(0, SubRange::new(0, 1)), 0..1); // 0
        assert_eq!(map_at(0, SubRange::new(2, 3)), 3..4); // 2
        assert_eq!(map_at(0, SubRange::new(2, 4)), 3..5); // 23
        assert_eq!(map_at(0, SubRange::new(4, 5)), 7..8); // 4
        assert_eq!(map_at(1, SubRange::new(0, 1)), 0..1); // 1
        assert_eq!(map_at(3, SubRange::new(0, 1)), 0..1); // 3
        assert_eq!(map_at(4, SubRange::new(0, 2)), 0..2); // 45
        // Across boundaries:
        assert_eq!(map_at(1, SubRange::new(0, 2)), 0..3); // 12 -> 1_2
        assert_eq!(map_at(0, SubRange::new(1, 3)), 1..4); // 12 -> 1_2
        assert_eq!(map_at(3, SubRange::new(0, 2)), 0..4); // 34 -> 3__4
        assert_eq!(map_at(0, SubRange::new(3, 5)), 4..8); // 34 -> 3__4
        assert_eq!(map_at(1, SubRange::new(0, 4)), 0..7); // 1234 -> 1_23__4
        assert_eq!(map_at(0, SubRange::new(1, 5)), 1..8); // 1234 -> 1_23__4
        assert_eq!(map_at(0, SubRange::new(0, 6)), 0..9); // 012345 -> 01_23__45
    }
}