blueprint_starlark_syntax/
codemap.rs

1/*
2 * Copyright 2019 The Starlark in Rust Authors.
3 * Copyright (c) Facebook, Inc. and its affiliates.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *     https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18//! A data structure for tracking source positions in language implementations
19//! The `CodeMap` tracks all source files and maps positions within them to linear indexes as if all
20//! source files were concatenated. This allows a source position to be represented by a small
21//! 32-bit `Pos` indexing into the `CodeMap`, under the assumption that the total amount of parsed
22//! source code will not exceed 4GiB. The `CodeMap` can look up the source file, line, and column
23//! of a `Pos` or `Span`, as well as provide source code snippets for error reporting.
24use std::cmp;
25use std::cmp::Ordering;
26use std::collections::HashMap;
27use std::collections::hash_map::Entry;
28use std::fmt;
29use std::fmt::Debug;
30use std::fmt::Display;
31use std::hash::Hash;
32use std::hash::Hasher;
33use std::ops::Add;
34use std::ops::AddAssign;
35use std::ops::Deref;
36use std::ops::DerefMut;
37use std::ops::Sub;
38use std::ptr;
39use std::sync::Arc;
40
41use blueprint_allocative::Allocative;
42use blueprint_dupe::Dupe;
43use once_cell::sync::Lazy;
44
45use crate::fast_string;
46
47/// A small, `Copy`, value representing a position in a `CodeMap`'s file.
48#[derive(
49    Copy, Clone, Dupe, Hash, Eq, PartialEq, PartialOrd, Ord, Debug, Default, Allocative
50)]
51pub struct Pos(u32);
52
53impl Pos {
54    /// Constructor.
55    pub const fn new(x: u32) -> Self {
56        Self(x)
57    }
58
59    /// Get the value.
60    pub const fn get(self) -> u32 {
61        self.0
62    }
63}
64
65impl Add<u32> for Pos {
66    type Output = Pos;
67    fn add(self, other: u32) -> Pos {
68        Pos(self.0 + other)
69    }
70}
71
72impl Sub<u32> for Pos {
73    type Output = Pos;
74    fn sub(self, other: u32) -> Pos {
75        Pos(self.0 - other)
76    }
77}
78
79impl AddAssign<u32> for Pos {
80    fn add_assign(&mut self, other: u32) {
81        self.0 += other;
82    }
83}
84
85/// A range of text within a CodeMap.
86#[derive(
87    Copy, Dupe, Clone, Hash, Eq, PartialEq, Ord, PartialOrd, Debug, Default, Allocative
88)]
89pub struct Span {
90    /// The position in the codemap representing the first byte of the span.
91    begin: Pos,
92
93    /// The position after the last byte of the span.
94    end: Pos,
95}
96
97impl Span {
98    /// Create a new span. Panics if `end < begin`.
99    pub fn new(begin: Pos, end: Pos) -> Self {
100        assert!(begin <= end);
101        Span { begin, end }
102    }
103
104    /// The position at the first byte of the span.
105    pub fn begin(self) -> Pos {
106        self.begin
107    }
108
109    /// The position after the last byte of the span.
110    pub fn end(self) -> Pos {
111        self.end
112    }
113
114    /// The length in bytes of the text of the span
115    #[cfg(test)]
116    pub fn len(self) -> u32 {
117        self.end.0 - self.begin.0
118    }
119
120    /// Create a span that encloses both `self` and `other`.
121    pub fn merge(self, other: Span) -> Span {
122        Span {
123            begin: cmp::min(self.begin, other.begin),
124            end: cmp::max(self.end, other.end),
125        }
126    }
127
128    pub fn merge_all(spans: impl Iterator<Item = Span>) -> Span {
129        spans.reduce(Span::merge).unwrap_or_default()
130    }
131
132    /// Empty span in the end of this span.
133    pub fn end_span(self) -> Span {
134        Span {
135            begin: self.end,
136            end: self.end,
137        }
138    }
139
140    /// Determines whether a `pos` is within this span.
141    pub fn contains(self, pos: Pos) -> bool {
142        self.begin <= pos && pos <= self.end
143    }
144
145    /// Determines whether a `span` intersects with this span.
146    /// End of range is inclusive.
147    pub fn intersects(self, span: Span) -> bool {
148        self.contains(span.begin) || self.contains(span.end) || span.contains(self.begin)
149    }
150}
151
152/// Associate a Span with a value of arbitrary type (e.g. an AST node).
153#[derive(Clone, Copy, Dupe, PartialEq, Eq, Hash, Debug)]
154pub struct Spanned<T> {
155    /// Data in the node.
156    pub node: T,
157    pub span: Span,
158}
159
160impl<T> Spanned<T> {
161    /// Apply the function to the node, keep the span.
162    pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Spanned<U> {
163        Spanned {
164            node: f(self.node),
165            span: self.span,
166        }
167    }
168
169    pub fn as_ref(&self) -> Spanned<&T> {
170        Spanned {
171            node: &self.node,
172            span: self.span,
173        }
174    }
175}
176
177impl<T> Deref for Spanned<T> {
178    type Target = T;
179
180    fn deref(&self) -> &T {
181        &self.node
182    }
183}
184
185impl<T> DerefMut for Spanned<T> {
186    fn deref_mut(&mut self) -> &mut T {
187        &mut self.node
188    }
189}
190
191// A cheap unowned unique identifier per file/CodeMap,
192// somewhat delving into internal details.
193// Remains unique because we take a reference to the CodeMap.
194#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, Dupe, Allocative)]
195pub struct CodeMapId(#[allocative(skip)] *const ());
196
197unsafe impl Send for CodeMapId {}
198unsafe impl Sync for CodeMapId {}
199
200impl CodeMapId {
201    pub const EMPTY: CodeMapId = CodeMapId(ptr::null());
202}
203
204#[derive(Clone, Dupe, Allocative)]
205enum CodeMapImpl {
206    Real(Arc<CodeMapData>),
207    #[allocative(skip)]
208    Native(&'static NativeCodeMap),
209}
210
211/// A data structure recording a source code file for position lookup.
212#[derive(Clone, Dupe, Allocative)]
213pub struct CodeMap(CodeMapImpl);
214
215/// Multiple [`CodeMap`].
216#[derive(Clone, Default, Debug, PartialEq, Allocative)]
217pub struct CodeMaps {
218    codemaps: HashMap<CodeMapId, CodeMap>,
219}
220
221impl CodeMaps {
222    /// Lookup by id.
223    pub fn get(&self, id: CodeMapId) -> Option<&CodeMap> {
224        self.codemaps.get(&id)
225    }
226
227    /// Add codemap if not already present.
228    pub fn add(&mut self, codemap: &CodeMap) {
229        match self.codemaps.entry(codemap.id()) {
230            Entry::Occupied(_) => {}
231            Entry::Vacant(e) => {
232                e.insert(codemap.dupe());
233            }
234        }
235    }
236
237    /// Add all codemaps.
238    pub fn add_all(&mut self, codemaps: &CodeMaps) {
239        for codemap in codemaps.codemaps.values() {
240            self.add(codemap);
241        }
242    }
243}
244
245/// A `CodeMap`'s record of a source file.
246#[derive(Allocative)]
247struct CodeMapData {
248    /// The filename as it would be displayed in an error message.
249    filename: String,
250    /// Contents of the file.
251    source: String,
252    /// Byte positions of line beginnings.
253    lines: Vec<Pos>,
254}
255
256/// "Codemap" for `.rs` files.
257pub struct NativeCodeMap {
258    filename: &'static str,
259    start: ResolvedPos,
260}
261
262impl NativeCodeMap {
263    const SOURCE: &'static str = "<native>";
264
265    pub const FULL_SPAN: Span = Span {
266        begin: Pos::new(0),
267        end: Pos::new(Self::SOURCE.len() as u32),
268    };
269
270    pub const fn new(filename: &'static str, line: u32, column: u32) -> NativeCodeMap {
271        Self {
272            filename,
273            start: ResolvedPos {
274                line: line as usize,
275                column: column as usize,
276            },
277        }
278    }
279
280    pub const fn to_codemap(&'static self) -> CodeMap {
281        CodeMap(CodeMapImpl::Native(self))
282    }
283}
284
285impl Default for CodeMap {
286    fn default() -> Self {
287        Self::new("".to_owned(), "".to_owned())
288    }
289}
290
291impl fmt::Debug for CodeMap {
292    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
293        write!(f, "CodeMap({:?})", self.filename())
294    }
295}
296
297impl PartialEq for CodeMap {
298    /// Compares by identity
299    fn eq(&self, other: &Self) -> bool {
300        self.id() == other.id()
301    }
302}
303
304impl Eq for CodeMap {}
305
306impl Hash for CodeMap {
307    fn hash<H: Hasher>(&self, state: &mut H) {
308        self.id().hash(state)
309    }
310}
311
312impl CodeMap {
313    /// Creates an new `CodeMap`.
314    pub fn new(filename: String, source: String) -> CodeMap {
315        let mut lines = vec![Pos(0)];
316        lines.extend(source.match_indices('\n').map(|(p, _)| Pos(p as u32 + 1)));
317
318        CodeMap(CodeMapImpl::Real(Arc::new(CodeMapData {
319            filename,
320            source,
321            lines,
322        })))
323    }
324
325    pub fn empty_static() -> &'static CodeMap {
326        static EMPTY_CODEMAP: Lazy<CodeMap> = Lazy::new(CodeMap::default);
327        &EMPTY_CODEMAP
328    }
329
330    /// Only used internally for profiling optimisations
331    pub fn id(&self) -> CodeMapId {
332        match &self.0 {
333            CodeMapImpl::Real(data) => CodeMapId(Arc::as_ptr(data) as *const ()),
334            CodeMapImpl::Native(data) => CodeMapId(*data as *const NativeCodeMap as *const ()),
335        }
336    }
337
338    pub fn full_span(&self) -> Span {
339        let source = self.source();
340        Span {
341            begin: Pos(0),
342            end: Pos(source.len() as u32),
343        }
344    }
345
346    /// Gets the file and its line and column ranges represented by a `Span`.
347    pub fn file_span(&self, span: Span) -> FileSpan {
348        FileSpan {
349            file: self.dupe(),
350            span,
351        }
352    }
353
354    /// Gets the name of the file
355    pub fn filename(&self) -> &str {
356        match &self.0 {
357            CodeMapImpl::Real(data) => &data.filename,
358            CodeMapImpl::Native(data) => data.filename,
359        }
360    }
361
362    pub fn byte_at(&self, pos: Pos) -> u8 {
363        self.source().as_bytes()[pos.0 as usize]
364    }
365
366    /// Gets the line number of a Pos.
367    ///
368    /// The lines are 0-indexed (first line is numbered 0)
369    ///
370    /// Panics if `pos` is not within this file's span.
371    pub fn find_line(&self, pos: Pos) -> usize {
372        assert!(pos <= self.full_span().end());
373        match &self.0 {
374            CodeMapImpl::Real(data) => match data.lines.binary_search(&pos) {
375                Ok(i) => i,
376                Err(i) => i - 1,
377            },
378            CodeMapImpl::Native(data) => data.start.line,
379        }
380    }
381
382    /// Gets the line and column of a Pos.
383    ///
384    /// Panics if `pos` is not with this file's span or
385    /// if `pos` points to a byte in the middle of a UTF-8 character.
386    fn find_line_col(&self, pos: Pos) -> ResolvedPos {
387        assert!(pos <= self.full_span().end());
388        match &self.0 {
389            CodeMapImpl::Real(_) => {
390                let line = self.find_line(pos);
391                let line_span = self.line_span(line);
392                let byte_col = pos.0 - line_span.begin.0;
393                let column = fast_string::len(&self.source_span(line_span)[..byte_col as usize]).0;
394
395                ResolvedPos { line, column }
396            }
397            CodeMapImpl::Native(data) => ResolvedPos {
398                line: data.start.line,
399                column: data.start.column + pos.0 as usize,
400            },
401        }
402    }
403
404    /// Gets the full source text of the file
405    pub fn source(&self) -> &str {
406        match &self.0 {
407            CodeMapImpl::Real(data) => &data.source,
408            CodeMapImpl::Native(_) => NativeCodeMap::SOURCE,
409        }
410    }
411
412    /// Gets the source text of a Span.
413    ///
414    /// Panics if `span` is not entirely within this file.
415    pub fn source_span(&self, span: Span) -> &str {
416        &self.source()[(span.begin.0 as usize)..(span.end.0 as usize)]
417    }
418
419    /// Like `line_span_opt` but panics if the line number is out of range.
420    pub fn line_span(&self, line: usize) -> Span {
421        self.line_span_opt(line)
422            .unwrap_or_else(|| panic!("Line {line} is out of range for {self:?}"))
423    }
424
425    /// Trim trailing newline if any, including windows, from the line span.
426    pub fn line_span_trim_newline(&self, line: usize) -> Span {
427        let mut span = self.line_span(line);
428        if self.source_span(span).ends_with('\n') {
429            span.end.0 -= 1;
430        }
431        if self.source_span(span).ends_with('\r') {
432            span.end.0 -= 1;
433        }
434        span
435    }
436
437    /// Gets the span representing a line by line number.
438    ///
439    /// The line number is 0-indexed (first line is numbered 0). The returned span includes the
440    /// line terminator.
441    ///
442    /// Returns None if the number if out of range.
443    pub fn line_span_opt(&self, line: usize) -> Option<Span> {
444        match &self.0 {
445            CodeMapImpl::Real(data) if line < data.lines.len() => Some(Span {
446                begin: data.lines[line],
447                end: *data.lines.get(line + 1).unwrap_or(&self.full_span().end),
448            }),
449            CodeMapImpl::Native(data) if line == data.start.line => Some(Span {
450                begin: Pos(0),
451                end: Pos(NativeCodeMap::SOURCE.len() as u32),
452            }),
453            _ => None,
454        }
455    }
456
457    pub fn resolve_span(&self, span: Span) -> ResolvedSpan {
458        let begin = self.find_line_col(span.begin);
459        let end = self.find_line_col(span.end);
460        ResolvedSpan::from_span(begin, end)
461    }
462
463    /// Gets the source text of a line.
464    ///
465    /// The string returned does not include the terminating \r or \n characters.
466    ///
467    /// Panics if the line number is out of range.
468    pub fn source_line(&self, line: usize) -> &str {
469        self.source_span(self.line_span(line))
470            .trim_end_matches(&['\n', '\r'][..])
471    }
472
473    pub fn source_line_at_pos(&self, pos: Pos) -> &str {
474        self.source_line(self.find_line(pos))
475    }
476}
477
478/// All are 0-based, but print out with 1-based.
479#[derive(
480    Copy, Clone, Dupe, Hash, Eq, PartialEq, Ord, PartialOrd, Debug, Default
481)]
482pub struct ResolvedPos {
483    /// The line number within the file (0-indexed).
484    pub line: usize,
485
486    /// The column within the line (0-indexed in characters).
487    pub column: usize,
488}
489
490impl Display for ResolvedPos {
491    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
492        write!(f, "{}:{}", self.line + 1, self.column + 1)
493    }
494}
495
496impl ResolvedPos {
497    fn _testing_parse(line_col: &str) -> ResolvedPos {
498        let (line, col) = line_col.split_once(':').unwrap();
499        ResolvedPos {
500            line: line.parse::<usize>().unwrap().checked_sub(1).unwrap(),
501            column: col.parse::<usize>().unwrap().checked_sub(1).unwrap(),
502        }
503    }
504}
505
506/// A file, and a line and column range within it.
507#[derive(Clone, Copy, Dupe, Eq, PartialEq, Debug)]
508pub struct FileSpanRef<'a> {
509    pub file: &'a CodeMap,
510    pub span: Span,
511}
512
513/// A file, and a line and column range within it.
514#[derive(Clone, Dupe, Eq, PartialEq, Debug, Hash, Allocative)]
515pub struct FileSpan {
516    pub file: CodeMap,
517    pub span: Span,
518}
519
520impl PartialOrd for FileSpan {
521    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
522        Some(self.cmp(other))
523    }
524}
525
526impl Ord for FileSpan {
527    fn cmp(&self, that: &Self) -> Ordering {
528        Ord::cmp(
529            &(self.filename(), self.span, self.file.id().0 as usize),
530            &(that.filename(), that.span, that.file.id().0 as usize),
531        )
532    }
533}
534
535impl<'a> fmt::Display for FileSpanRef<'a> {
536    /// Formats the span as `filename:start_line:start_column: end_line:end_column`,
537    /// or if the span is zero-length, `filename:line:column`, with a 1-indexed line and column.
538    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
539        write!(f, "{}:{}", self.file.filename(), self.resolve_span())
540    }
541}
542
543impl fmt::Display for FileSpan {
544    /// Formats the span as `filename:start_line:start_column: end_line:end_column`,
545    /// or if the span is zero-length, `filename:line:column`, with a 1-indexed line and column.
546    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
547        fmt::Display::fmt(&self.as_ref(), f)
548    }
549}
550
551impl<'a> FileSpanRef<'a> {
552    /// Filename of this reference.
553    pub fn filename(&self) -> &str {
554        self.file.filename()
555    }
556
557    /// Convert to the owned span.
558    pub fn to_file_span(self) -> FileSpan {
559        FileSpan {
560            file: self.file.dupe(),
561            span: self.span,
562        }
563    }
564
565    /// Resolve span offsets to lines and columns.
566    pub fn resolve_span(&self) -> ResolvedSpan {
567        self.file.resolve_span(self.span)
568    }
569
570    /// Resolve the span.
571    pub fn source_span(self) -> &'a str {
572        self.file.source_span(self.span)
573    }
574}
575
576impl FileSpan {
577    /// Creates an new `FileSpan` covering the entire file.
578    pub fn new(filename: String, source: String) -> Self {
579        let file = CodeMap::new(filename, source);
580        let span = file.full_span();
581        Self { file, span }
582    }
583
584    /// Filename of this span.
585    pub fn filename(&self) -> &str {
586        self.file.filename()
587    }
588
589    /// Resolve the span.
590    pub fn source_span(&self) -> &str {
591        self.as_ref().source_span()
592    }
593
594    /// Cheap reference to the span.
595    pub fn as_ref(&self) -> FileSpanRef<'_> {
596        FileSpanRef {
597            file: &self.file,
598            span: self.span,
599        }
600    }
601
602    /// Resolve the span to lines and columns.
603    pub fn resolve_span(&self) -> ResolvedSpan {
604        self.as_ref().resolve_span()
605    }
606
607    /// Resolve the span to lines and columns.
608    pub fn resolve(&self) -> ResolvedFileSpan {
609        ResolvedFileSpan {
610            file: self.file.filename().to_owned(),
611            span: self.file.resolve_span(self.span),
612        }
613    }
614}
615
616/// The locations of values within a span.
617/// All are 0-based, but print out with 1-based.
618#[derive(
619    Debug, Dupe, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash, Default
620)]
621pub struct ResolvedSpan {
622    /// Beginning of the span.
623    pub begin: ResolvedPos,
624    /// End of the span.
625    pub end: ResolvedPos,
626}
627
628impl Display for ResolvedSpan {
629    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
630        let single_line = self.begin.line == self.end.line;
631        let is_empty = single_line && self.begin.column == self.end.column;
632
633        if is_empty {
634            write!(f, "{}:{}", self.begin.line + 1, self.begin.column + 1)
635        } else if single_line {
636            write!(f, "{}-{}", self.begin, self.end.column + 1)
637        } else {
638            write!(f, "{}-{}", self.begin, self.end,)
639        }
640    }
641}
642
643impl From<ResolvedSpan> for lsp_types::Range {
644    fn from(span: ResolvedSpan) -> Self {
645        lsp_types::Range::new(
646            lsp_types::Position::new(span.begin.line as u32, span.begin.column as u32),
647            lsp_types::Position::new(span.end.line as u32, span.end.column as u32),
648        )
649    }
650}
651
652impl ResolvedSpan {
653    /// Check that the given position is contained within this span.
654    /// Includes positions both at the beginning and the end of the range.
655    pub fn contains(&self, pos: ResolvedPos) -> bool {
656        (self.begin.line < pos.line
657            || (self.begin.line == pos.line && self.begin.column <= pos.column))
658            && (self.end.line > pos.line
659                || (self.end.line == pos.line && self.end.column >= pos.column))
660    }
661
662    fn from_span(begin: ResolvedPos, end: ResolvedPos) -> Self {
663        ResolvedSpan { begin, end }
664    }
665
666    fn _testing_parse(span: &str) -> ResolvedSpan {
667        match span.split_once('-') {
668            None => {
669                let line_col = ResolvedPos::_testing_parse(span);
670                ResolvedSpan::from_span(line_col, line_col)
671            }
672            Some((begin, end)) => {
673                let begin = ResolvedPos::_testing_parse(begin);
674                if end.contains(':') {
675                    let end = ResolvedPos::_testing_parse(end);
676                    ResolvedSpan::from_span(begin, end)
677                } else {
678                    let end_col = end.parse::<usize>().unwrap().checked_sub(1).unwrap();
679                    ResolvedSpan::from_span(
680                        begin,
681                        ResolvedPos {
682                            line: begin.line,
683                            column: end_col,
684                        },
685                    )
686                }
687            }
688        }
689    }
690}
691
692/// File and line number.
693#[derive(Debug, PartialEq, Eq, Hash, Clone, derive_more::Display)]
694#[display("{}:{}", file, line + 1)]
695pub struct ResolvedFileLine {
696    /// File name.
697    pub file: String,
698    /// Line number is 0-based but displayed as 1-based.
699    pub line: usize,
700}
701
702/// File name and line and column pairs for a span.
703#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Clone)]
704pub struct ResolvedFileSpan {
705    /// File name.
706    pub file: String,
707    /// The span.
708    pub span: ResolvedSpan,
709}
710
711impl ResolvedFileSpan {
712    pub(crate) fn _testing_parse(span: &str) -> ResolvedFileSpan {
713        let (file, span) = span.split_once(':').unwrap();
714        ResolvedFileSpan {
715            file: file.to_owned(),
716            span: ResolvedSpan::_testing_parse(span),
717        }
718    }
719
720    /// File and line number of the beginning of the span.
721    pub fn begin_file_line(&self) -> ResolvedFileLine {
722        ResolvedFileLine {
723            file: self.file.clone(),
724            line: self.span.begin.line,
725        }
726    }
727}
728
729impl Display for ResolvedFileSpan {
730    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
731        write!(f, "{}:{}", self.file, self.span)
732    }
733}
734
735#[cfg(test)]
736mod tests {
737    use super::*;
738
739    #[test]
740    fn test_codemap() {
741        let source = "abcd\nefghij\nqwerty";
742        let codemap = CodeMap::new("test1.rs".to_owned(), source.to_owned());
743        let start = codemap.full_span().begin;
744
745        // Test .name()
746        assert_eq!(codemap.filename(), "test1.rs");
747
748        // Test .find_line_col()
749        assert_eq!(
750            codemap.find_line_col(start),
751            ResolvedPos { line: 0, column: 0 }
752        );
753        assert_eq!(
754            codemap.find_line_col(start + 4),
755            ResolvedPos { line: 0, column: 4 }
756        );
757        assert_eq!(
758            codemap.find_line_col(start + 5),
759            ResolvedPos { line: 1, column: 0 }
760        );
761        assert_eq!(
762            codemap.find_line_col(start + 16),
763            ResolvedPos { line: 2, column: 4 }
764        );
765
766        // Test .source() and num lines.
767        assert_eq!(codemap.source(), source);
768        assert!(
769            matches!(&codemap, CodeMap(CodeMapImpl::Real(codemap)) if codemap.lines.len() == 3)
770        );
771
772        // Test generic properties on each line
773        for line in 0..3 {
774            let line_str = codemap.source_line(line);
775            let line_span = codemap.line_span(line);
776            // The line_str omits trailing newlines
777            assert_eq!(
778                line_str.len() + if line < 2 { 1 } else { 0 },
779                line_span.len() as usize
780            );
781            assert_eq!(line_str, source.lines().nth(line).unwrap());
782            assert_eq!(codemap.find_line(line_span.begin), line);
783            // The final character might be a newline, which is counted as the next line.
784            // Not sure this is a good thing!
785            let end = Pos(line_span.end().0 - 1);
786            assert_eq!(codemap.find_line(end), line);
787            assert_eq!(
788                codemap.find_line_col(line_span.begin),
789                ResolvedPos { line, column: 0 }
790            );
791            assert_eq!(
792                codemap.find_line_col(end),
793                ResolvedPos {
794                    line,
795                    column: line_span.len() as usize - 1
796                }
797            );
798        }
799        assert_eq!(codemap.line_span_opt(4), None);
800    }
801
802    #[test]
803    fn test_multibyte() {
804        let content = "65°00′N 18°00′W 汉语\n🔬";
805        let codemap = CodeMap::new("<test>".to_owned(), content.to_owned());
806
807        assert_eq!(
808            codemap.find_line_col(codemap.full_span().begin + 21),
809            ResolvedPos {
810                line: 0,
811                column: 15
812            }
813        );
814        assert_eq!(
815            codemap.find_line_col(codemap.full_span().begin + 28),
816            ResolvedPos {
817                line: 0,
818                column: 18
819            }
820        );
821        assert_eq!(
822            codemap.find_line_col(codemap.full_span().begin + 33),
823            ResolvedPos { line: 1, column: 1 }
824        );
825    }
826
827    #[test]
828    fn test_line_col_span_display_point() {
829        let line_col = ResolvedPos { line: 0, column: 0 };
830        let span = ResolvedSpan::from_span(line_col, line_col);
831        assert_eq!(span.to_string(), "1:1");
832    }
833
834    #[test]
835    fn test_line_col_span_display_single_line_span() {
836        let begin = ResolvedPos { line: 0, column: 0 };
837        let end = ResolvedPos {
838            line: 0,
839            column: 32,
840        };
841        let span = ResolvedSpan::from_span(begin, end);
842        assert_eq!(span.to_string(), "1:1-33");
843    }
844
845    #[test]
846    fn test_line_col_span_display_multi_line_span() {
847        let begin = ResolvedPos { line: 0, column: 0 };
848        let end = ResolvedPos {
849            line: 2,
850            column: 32,
851        };
852        let span = ResolvedSpan::from_span(begin, end);
853        assert_eq!(span.to_string(), "1:1-3:33");
854    }
855
856    #[test]
857    fn test_native_code_map() {
858        static NATIVE_CODEMAP: NativeCodeMap = NativeCodeMap::new("test.rs", 100, 200);
859        static CODEMAP: CodeMap = NATIVE_CODEMAP.to_codemap();
860        assert_eq!(NativeCodeMap::SOURCE, CODEMAP.source());
861        assert_eq!(NativeCodeMap::SOURCE, CODEMAP.source_line(100));
862        assert_eq!(
863            ResolvedSpan {
864                begin: ResolvedPos {
865                    line: 100,
866                    column: 200,
867                },
868                end: ResolvedPos {
869                    line: 100,
870                    column: 200 + NativeCodeMap::SOURCE.len(),
871                }
872            },
873            CODEMAP.resolve_span(CODEMAP.full_span())
874        );
875    }
876
877    #[test]
878    fn test_resolved_span_contains() {
879        let span = ResolvedSpan {
880            begin: ResolvedPos { line: 2, column: 3 },
881            end: ResolvedPos { line: 4, column: 5 },
882        };
883        assert!(!span.contains(ResolvedPos { line: 0, column: 7 }));
884        assert!(!span.contains(ResolvedPos { line: 2, column: 2 }));
885        assert!(span.contains(ResolvedPos { line: 2, column: 3 }));
886        assert!(span.contains(ResolvedPos { line: 2, column: 9 }));
887        assert!(span.contains(ResolvedPos { line: 3, column: 1 }));
888        assert!(span.contains(ResolvedPos { line: 4, column: 4 }));
889        assert!(span.contains(ResolvedPos { line: 4, column: 5 }));
890        assert!(!span.contains(ResolvedPos { line: 4, column: 6 }));
891        assert!(!span.contains(ResolvedPos { line: 5, column: 0 }));
892    }
893
894    #[test]
895    fn test_span_intersects() {
896        let span = Span {
897            begin: Pos(2),
898            end: Pos(4),
899        };
900        // s: |---|
901        // o:      |---|
902        assert!(!span.intersects(Span {
903            begin: Pos(5),
904            end: Pos(7),
905        }));
906
907        // s: |---|
908        // o:     |---|
909        assert!(span.intersects(Span {
910            begin: Pos(4),
911            end: Pos(6),
912        }));
913
914        // s: |---|
915        // o:    |---|
916        assert!(span.intersects(Span {
917            begin: Pos(3),
918            end: Pos(5),
919        }));
920
921        // s: |---|
922        // o: |---|
923        assert!(span.intersects(Span {
924            begin: Pos(2),
925            end: Pos(4),
926        }));
927
928        // s:   |---|
929        // o: |---|
930        assert!(span.intersects(Span {
931            begin: Pos(1),
932            end: Pos(3),
933        }));
934
935        // s:     |---|
936        // o: |---|
937        assert!(span.intersects(Span {
938            begin: Pos(0),
939            end: Pos(2),
940        }));
941
942        // s:     |---|
943        // o: |--|
944        assert!(!span.intersects(Span {
945            begin: Pos(0),
946            end: Pos(1),
947        }));
948
949        let large_span = Span {
950            begin: Pos(2),
951            end: Pos(8),
952        };
953
954        // s:  |-------|
955        // o:    |---|
956        assert!(large_span.intersects(span));
957
958        // s:    |---|
959        // o:  |-------|
960        assert!(span.intersects(large_span));
961    }
962
963    #[test]
964    fn test_resolved_file_span_to_begin_resolved_file_line() {
965        let span = ResolvedFileSpan {
966            file: "test.rs".to_owned(),
967            span: ResolvedSpan {
968                begin: ResolvedPos { line: 2, column: 3 },
969                end: ResolvedPos { line: 4, column: 5 },
970            },
971        };
972        assert_eq!("test.rs:3", span.begin_file_line().to_string());
973    }
974}