1use 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#[derive(
49 Copy, Clone, Dupe, Hash, Eq, PartialEq, PartialOrd, Ord, Debug, Default, Allocative
50)]
51pub struct Pos(u32);
52
53impl Pos {
54 pub const fn new(x: u32) -> Self {
56 Self(x)
57 }
58
59 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#[derive(
87 Copy, Dupe, Clone, Hash, Eq, PartialEq, Ord, PartialOrd, Debug, Default, Allocative
88)]
89pub struct Span {
90 begin: Pos,
92
93 end: Pos,
95}
96
97impl Span {
98 pub fn new(begin: Pos, end: Pos) -> Self {
100 assert!(begin <= end);
101 Span { begin, end }
102 }
103
104 pub fn begin(self) -> Pos {
106 self.begin
107 }
108
109 pub fn end(self) -> Pos {
111 self.end
112 }
113
114 #[cfg(test)]
116 pub fn len(self) -> u32 {
117 self.end.0 - self.begin.0
118 }
119
120 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 pub fn end_span(self) -> Span {
134 Span {
135 begin: self.end,
136 end: self.end,
137 }
138 }
139
140 pub fn contains(self, pos: Pos) -> bool {
142 self.begin <= pos && pos <= self.end
143 }
144
145 pub fn intersects(self, span: Span) -> bool {
148 self.contains(span.begin) || self.contains(span.end) || span.contains(self.begin)
149 }
150}
151
152#[derive(Clone, Copy, Dupe, PartialEq, Eq, Hash, Debug)]
154pub struct Spanned<T> {
155 pub node: T,
157 pub span: Span,
158}
159
160impl<T> Spanned<T> {
161 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#[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#[derive(Clone, Dupe, Allocative)]
213pub struct CodeMap(CodeMapImpl);
214
215#[derive(Clone, Default, Debug, PartialEq, Allocative)]
217pub struct CodeMaps {
218 codemaps: HashMap<CodeMapId, CodeMap>,
219}
220
221impl CodeMaps {
222 pub fn get(&self, id: CodeMapId) -> Option<&CodeMap> {
224 self.codemaps.get(&id)
225 }
226
227 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 pub fn add_all(&mut self, codemaps: &CodeMaps) {
239 for codemap in codemaps.codemaps.values() {
240 self.add(codemap);
241 }
242 }
243}
244
245#[derive(Allocative)]
247struct CodeMapData {
248 filename: String,
250 source: String,
252 lines: Vec<Pos>,
254}
255
256pub 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 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 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 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 pub fn file_span(&self, span: Span) -> FileSpan {
348 FileSpan {
349 file: self.dupe(),
350 span,
351 }
352 }
353
354 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 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 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 pub fn source(&self) -> &str {
406 match &self.0 {
407 CodeMapImpl::Real(data) => &data.source,
408 CodeMapImpl::Native(_) => NativeCodeMap::SOURCE,
409 }
410 }
411
412 pub fn source_span(&self, span: Span) -> &str {
416 &self.source()[(span.begin.0 as usize)..(span.end.0 as usize)]
417 }
418
419 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 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 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 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#[derive(
480 Copy, Clone, Dupe, Hash, Eq, PartialEq, Ord, PartialOrd, Debug, Default
481)]
482pub struct ResolvedPos {
483 pub line: usize,
485
486 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#[derive(Clone, Copy, Dupe, Eq, PartialEq, Debug)]
508pub struct FileSpanRef<'a> {
509 pub file: &'a CodeMap,
510 pub span: Span,
511}
512
513#[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 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 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 pub fn filename(&self) -> &str {
554 self.file.filename()
555 }
556
557 pub fn to_file_span(self) -> FileSpan {
559 FileSpan {
560 file: self.file.dupe(),
561 span: self.span,
562 }
563 }
564
565 pub fn resolve_span(&self) -> ResolvedSpan {
567 self.file.resolve_span(self.span)
568 }
569
570 pub fn source_span(self) -> &'a str {
572 self.file.source_span(self.span)
573 }
574}
575
576impl FileSpan {
577 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 pub fn filename(&self) -> &str {
586 self.file.filename()
587 }
588
589 pub fn source_span(&self) -> &str {
591 self.as_ref().source_span()
592 }
593
594 pub fn as_ref(&self) -> FileSpanRef<'_> {
596 FileSpanRef {
597 file: &self.file,
598 span: self.span,
599 }
600 }
601
602 pub fn resolve_span(&self) -> ResolvedSpan {
604 self.as_ref().resolve_span()
605 }
606
607 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#[derive(
619 Debug, Dupe, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash, Default
620)]
621pub struct ResolvedSpan {
622 pub begin: ResolvedPos,
624 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 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#[derive(Debug, PartialEq, Eq, Hash, Clone, derive_more::Display)]
694#[display("{}:{}", file, line + 1)]
695pub struct ResolvedFileLine {
696 pub file: String,
698 pub line: usize,
700}
701
702#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Clone)]
704pub struct ResolvedFileSpan {
705 pub file: String,
707 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 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 assert_eq!(codemap.filename(), "test1.rs");
747
748 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 assert_eq!(codemap.source(), source);
768 assert!(
769 matches!(&codemap, CodeMap(CodeMapImpl::Real(codemap)) if codemap.lines.len() == 3)
770 );
771
772 for line in 0..3 {
774 let line_str = codemap.source_line(line);
775 let line_span = codemap.line_span(line);
776 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 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 assert!(!span.intersects(Span {
903 begin: Pos(5),
904 end: Pos(7),
905 }));
906
907 assert!(span.intersects(Span {
910 begin: Pos(4),
911 end: Pos(6),
912 }));
913
914 assert!(span.intersects(Span {
917 begin: Pos(3),
918 end: Pos(5),
919 }));
920
921 assert!(span.intersects(Span {
924 begin: Pos(2),
925 end: Pos(4),
926 }));
927
928 assert!(span.intersects(Span {
931 begin: Pos(1),
932 end: Pos(3),
933 }));
934
935 assert!(span.intersects(Span {
938 begin: Pos(0),
939 end: Pos(2),
940 }));
941
942 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 assert!(large_span.intersects(span));
957
958 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}