1use std::{
23 cmp, env, fs,
24 hash::Hash,
25 io,
26 path::{Path, PathBuf},
27 sync::atomic::{AtomicUsize, Ordering::SeqCst},
28};
29
30use once_cell::sync::Lazy;
31use rustc_hash::FxHashMap;
32#[cfg(feature = "sourcemap")]
33use sourcemap::SourceMapBuilder;
34use tracing::debug;
35
36pub use crate::syntax_pos::*;
37use crate::{
38 errors::SourceMapper,
39 rustc_data_structures::stable_hasher::StableHasher,
40 sync::{Lock, LockGuard, Lrc, MappedLockGuard},
41};
42
43static CURRENT_DIR: Lazy<Option<PathBuf>> = Lazy::new(|| env::current_dir().ok());
44
45pub trait FileLoader {
51 fn file_exists(&self, path: &Path) -> bool;
53
54 fn abs_path(&self, path: &Path) -> Option<PathBuf>;
56
57 fn read_file(&self, path: &Path) -> io::Result<String>;
59}
60
61pub struct RealFileLoader;
63
64impl FileLoader for RealFileLoader {
65 fn file_exists(&self, path: &Path) -> bool {
66 fs::metadata(path).is_ok()
67 }
68
69 fn abs_path(&self, path: &Path) -> Option<PathBuf> {
70 if path.is_absolute() {
71 Some(path.to_path_buf())
72 } else {
73 CURRENT_DIR.as_ref().map(|cwd| cwd.join(path))
74 }
75 }
76
77 fn read_file(&self, path: &Path) -> io::Result<String> {
78 fs::read_to_string(path)
79 }
80}
81
82#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
86pub struct StableSourceFileId(u128);
87
88impl StableSourceFileId {
89 pub fn new(source_file: &SourceFile) -> StableSourceFileId {
90 let mut hasher = StableHasher::new();
91
92 source_file.name.hash(&mut hasher);
93 source_file.name_was_remapped.hash(&mut hasher);
94 source_file.unmapped_path.hash(&mut hasher);
95
96 StableSourceFileId(hasher.finish())
97 }
98}
99
100#[derive(Default)]
105pub(super) struct SourceMapFiles {
106 pub(super) source_files: Vec<Lrc<SourceFile>>,
107 stable_id_to_source_file: FxHashMap<StableSourceFileId, Lrc<SourceFile>>,
108}
109
110pub struct SourceMap {
130 pub(super) files: Lock<SourceMapFiles>,
131 start_pos: AtomicUsize,
132 file_loader: Box<dyn FileLoader + Sync + Send>,
133 path_mapping: FilePathMapping,
136 doctest_offset: Option<(FileName, isize)>,
139}
140
141impl Default for SourceMap {
142 fn default() -> Self {
143 Self::new(FilePathMapping::empty())
144 }
145}
146
147impl SourceMap {
148 pub fn new(path_mapping: FilePathMapping) -> SourceMap {
149 SourceMap {
150 files: Default::default(),
151 start_pos: AtomicUsize::new(1),
152 file_loader: Box::new(RealFileLoader),
153 path_mapping,
154 doctest_offset: None,
155 }
156 }
157
158 pub fn with_file_loader(
159 file_loader: Box<dyn FileLoader + Sync + Send>,
160 path_mapping: FilePathMapping,
161 ) -> SourceMap {
162 SourceMap {
163 files: Default::default(),
164 start_pos: AtomicUsize::new(1),
165 file_loader,
166 path_mapping,
167 doctest_offset: None,
168 }
169 }
170
171 pub fn path_mapping(&self) -> &FilePathMapping {
172 &self.path_mapping
173 }
174
175 pub fn file_exists(&self, path: &Path) -> bool {
176 self.file_loader.file_exists(path)
177 }
178
179 pub fn load_file(&self, path: &Path) -> io::Result<Lrc<SourceFile>> {
180 let src = self.file_loader.read_file(path)?;
181 let filename = Lrc::new(path.to_path_buf().into());
182 Ok(self.new_source_file(filename, src))
183 }
184
185 pub fn files(&self) -> MappedLockGuard<'_, Vec<Lrc<SourceFile>>> {
186 LockGuard::map(self.files.borrow(), |files| &mut files.source_files)
187 }
188
189 pub fn source_file_by_stable_id(
190 &self,
191 stable_id: StableSourceFileId,
192 ) -> Option<Lrc<SourceFile>> {
193 self.files
194 .borrow()
195 .stable_id_to_source_file
196 .get(&stable_id)
197 .cloned()
198 }
199
200 fn next_start_pos(&self, len: usize) -> usize {
201 self.start_pos.fetch_add(len + 1, SeqCst)
204 }
205
206 pub fn new_source_file(&self, filename: Lrc<FileName>, mut src: String) -> Lrc<SourceFile> {
209 remove_bom(&mut src);
210
211 self.new_source_file_from(filename, Lrc::new(src))
212 }
213
214 pub fn new_source_file_from(
219 &self,
220 filename: Lrc<FileName>,
221 src: Lrc<String>,
222 ) -> Lrc<SourceFile> {
223 let unmapped_path = filename.clone();
229
230 let (filename, was_remapped) = match &*filename {
231 FileName::Real(filename) => {
232 let (filename, was_remapped) = self.path_mapping.map_prefix(filename);
233 (Lrc::new(FileName::Real(filename)), was_remapped)
234 }
235 _ => (filename, false),
236 };
237
238 let mut files = self.files.borrow_mut();
241
242 let start_pos = self.next_start_pos(src.len());
243
244 let source_file = Lrc::new(SourceFile::new_from(
245 filename,
246 was_remapped,
247 unmapped_path,
248 src,
249 SmallPos::from_usize(start_pos),
250 ));
251
252 {
253 files.source_files.push(source_file.clone());
254 files
255 .stable_id_to_source_file
256 .insert(StableSourceFileId::new(&source_file), source_file.clone());
257 }
258
259 source_file
260 }
261
262 pub fn mk_substr_filename(&self, sp: Span) -> String {
263 let pos = self.lookup_char_pos(sp.lo());
264 format!(
265 "<{}:{}:{}>",
266 pos.file.name,
267 pos.line,
268 pos.col.to_usize() + 1
269 )
270 }
271
272 pub fn doctest_offset_line(&self, mut orig: usize) -> usize {
274 if let Some((_, line)) = self.doctest_offset {
275 if line >= 0 {
276 orig += line as usize;
277 } else {
278 orig -= (-line) as usize;
279 }
280 }
281 orig
282 }
283
284 pub fn lookup_char_pos(&self, pos: BytePos) -> Loc {
286 self.try_lookup_char_pos(pos).unwrap()
287 }
288
289 pub fn try_lookup_char_pos(&self, pos: BytePos) -> Result<Loc, SourceMapLookupError> {
291 let fm = self.try_lookup_source_file(pos)?;
292 self.try_lookup_char_pos_with(fm, pos)
293 }
294
295 #[doc(hidden)]
301 pub fn lookup_char_pos_with(&self, fm: Lrc<SourceFile>, pos: BytePos) -> Loc {
302 self.try_lookup_char_pos_with(fm, pos).unwrap()
303 }
304
305 #[doc(hidden)]
311 pub fn try_lookup_char_pos_with(
312 &self,
313 fm: Lrc<SourceFile>,
314 pos: BytePos,
315 ) -> Result<Loc, SourceMapLookupError> {
316 let line_info = self.lookup_line_with(fm, pos);
317 match line_info {
318 Ok(SourceFileAndLine { sf: f, line: a }) => {
319 let analysis = f.analyze();
320 let chpos = self.bytepos_to_file_charpos_with(&f, pos);
321
322 let line = a + 1; let linebpos = f.analyze().lines[a];
324 assert!(
325 pos >= linebpos,
326 "{}: bpos = {:?}; linebpos = {:?};",
327 f.name,
328 pos,
329 linebpos,
330 );
331
332 let linechpos = self.bytepos_to_file_charpos_with(&f, linebpos);
333 let col = chpos - linechpos;
334
335 let col_display = {
336 let start_width_idx = analysis
337 .non_narrow_chars
338 .binary_search_by_key(&linebpos, |x| x.pos())
339 .unwrap_or_else(|x| x);
340 let end_width_idx = analysis
341 .non_narrow_chars
342 .binary_search_by_key(&pos, |x| x.pos())
343 .unwrap_or_else(|x| x);
344 let special_chars = end_width_idx - start_width_idx;
345 let non_narrow: usize = analysis.non_narrow_chars
346 [start_width_idx..end_width_idx]
347 .iter()
348 .map(|x| x.width())
349 .sum();
350 col.0 - special_chars + non_narrow
351 };
352 if cfg!(feature = "debug") {
353 debug!(
354 "byte pos {:?} is on the line at byte pos {:?}",
355 pos, linebpos
356 );
357 debug!(
358 "char pos {:?} is on the line at char pos {:?}",
359 chpos, linechpos
360 );
361 debug!("byte is on line: {}", line);
362 }
363 Ok(Loc {
365 file: f,
366 line,
367 col,
368 col_display,
369 })
370 }
371 Err(f) => {
372 let analysis = f.analyze();
373 let chpos = self.bytepos_to_file_charpos(pos)?;
374
375 let col_display = {
376 let end_width_idx = analysis
377 .non_narrow_chars
378 .binary_search_by_key(&pos, |x| x.pos())
379 .unwrap_or_else(|x| x);
380 let non_narrow: usize = analysis.non_narrow_chars[0..end_width_idx]
381 .iter()
382 .map(|x| x.width())
383 .sum();
384 chpos.0 - end_width_idx + non_narrow
385 };
386 Ok(Loc {
387 file: f,
388 line: 0,
389 col: chpos,
390 col_display,
391 })
392 }
393 }
394 }
395
396 pub fn lookup_line(&self, pos: BytePos) -> Result<SourceFileAndLine, Lrc<SourceFile>> {
398 let f = self.try_lookup_source_file(pos).unwrap();
399
400 self.lookup_line_with(f, pos)
401 }
402
403 #[doc(hidden)]
408 pub fn lookup_line_with(
409 &self,
410 f: Lrc<SourceFile>,
411 pos: BytePos,
412 ) -> Result<SourceFileAndLine, Lrc<SourceFile>> {
413 match f.lookup_line(pos) {
414 Some(line) => Ok(SourceFileAndLine { sf: f, line }),
415 None => Err(f),
416 }
417 }
418
419 pub fn lookup_char_pos_adj(&self, pos: BytePos) -> LocWithOpt {
420 let loc = self.lookup_char_pos(pos);
421 LocWithOpt {
422 filename: loc.file.name.clone(),
423 line: loc.line,
424 col: loc.col,
425 file: Some(loc.file),
426 }
427 }
428
429 pub fn merge_spans(&self, sp_lhs: Span, sp_rhs: Span) -> Option<Span> {
438 let lhs_end = match self.lookup_line(sp_lhs.hi()) {
439 Ok(x) => x,
440 Err(_) => return None,
441 };
442 let rhs_begin = match self.lookup_line(sp_rhs.lo()) {
443 Ok(x) => x,
444 Err(_) => return None,
445 };
446
447 if lhs_end.line != rhs_begin.line {
449 return None;
450 }
451
452 if (sp_lhs.lo() <= sp_rhs.lo()) && (sp_lhs.hi() <= sp_rhs.lo()) {
454 Some(sp_lhs.to(sp_rhs))
455 } else {
456 None
457 }
458 }
459
460 pub fn span_to_string(&self, sp: Span) -> String {
461 if self.files.borrow().source_files.is_empty() && sp.is_dummy() {
462 return "no-location".to_string();
463 }
464
465 let lo = self.lookup_char_pos_adj(sp.lo());
466 let hi = self.lookup_char_pos_adj(sp.hi());
467 format!(
468 "{}:{}:{}: {}:{}",
469 lo.filename,
470 lo.line,
471 lo.col.to_usize() + 1,
472 hi.line,
473 hi.col.to_usize() + 1
474 )
475 }
476
477 pub fn span_to_filename(&self, sp: Span) -> Lrc<FileName> {
478 self.lookup_char_pos(sp.lo()).file.name.clone()
479 }
480
481 pub fn span_to_unmapped_path(&self, sp: Span) -> Lrc<FileName> {
482 self.lookup_char_pos(sp.lo())
483 .file
484 .unmapped_path
485 .clone()
486 .expect("SourceMap::span_to_unmapped_path called for imported SourceFile?")
487 }
488
489 pub fn is_multiline(&self, sp: Span) -> bool {
490 let lo = self.lookup_char_pos(sp.lo());
491 let hi = self.lookup_char_pos(sp.hi());
492 lo.line != hi.line
493 }
494
495 pub fn span_to_lines(&self, sp: Span) -> FileLinesResult {
496 if cfg!(feature = "debug") {
497 debug!("span_to_lines(sp={:?})", sp);
498 }
499
500 if sp.lo() > sp.hi() {
501 return Err(Box::new(SpanLinesError::IllFormedSpan(sp)));
502 }
503
504 let lo = self.lookup_char_pos(sp.lo());
505 if cfg!(feature = "debug") {
506 debug!("span_to_lines: lo={:?}", lo);
507 }
508 let hi = self.lookup_char_pos(sp.hi());
509 if cfg!(feature = "debug") {
510 debug!("span_to_lines: hi={:?}", hi);
511 }
512
513 if lo.file.start_pos != hi.file.start_pos {
514 return Err(Box::new(SpanLinesError::DistinctSources(DistinctSources {
515 begin: FilePos(lo.file.name.clone(), lo.file.start_pos),
516 end: FilePos(hi.file.name.clone(), hi.file.start_pos),
517 })));
518 }
519 assert!(hi.line >= lo.line);
520
521 if lo.file.src.is_empty() {
523 return Ok(FileLines {
524 file: lo.file,
525 lines: Vec::new(),
526 });
527 }
528
529 let mut lines = Vec::with_capacity(hi.line - lo.line + 1);
530
531 let mut start_col = lo.col;
534
535 for line_index in lo.line - 1..hi.line - 1 {
540 let line_len = lo
541 .file
542 .get_line(line_index)
543 .map(|s| s.chars().count())
544 .unwrap_or(0);
545 lines.push(LineInfo {
546 line_index,
547 start_col,
548 end_col: CharPos::from_usize(line_len),
549 });
550 start_col = CharPos::from_usize(0);
551 }
552
553 lines.push(LineInfo {
555 line_index: hi.line - 1,
556 start_col,
557 end_col: hi.col,
558 });
559
560 Ok(FileLines {
561 file: lo.file,
562 lines,
563 })
564 }
565
566 fn span_to_source<F, Ret>(
572 &self,
573 sp: Span,
574 extract_source: F,
575 ) -> Result<Ret, Box<SpanSnippetError>>
576 where
577 F: FnOnce(&str, usize, usize) -> Ret,
578 {
579 if sp.lo() > sp.hi() {
580 return Err(Box::new(SpanSnippetError::IllFormedSpan(sp)));
581 }
582 if sp.lo.is_dummy() || sp.hi.is_dummy() {
583 return Err(Box::new(SpanSnippetError::DummyBytePos));
584 }
585
586 let local_begin = self.try_lookup_byte_offset(sp.lo())?;
587 let local_end = self.try_lookup_byte_offset(sp.hi())?;
588
589 if local_begin.sf.start_pos != local_end.sf.start_pos {
590 Err(Box::new(SpanSnippetError::DistinctSources(
591 DistinctSources {
592 begin: FilePos(local_begin.sf.name.clone(), local_begin.sf.start_pos),
593 end: FilePos(local_end.sf.name.clone(), local_end.sf.start_pos),
594 },
595 )))
596 } else {
597 let start_index = local_begin.pos.to_usize();
598 let end_index = local_end.pos.to_usize();
599 let source_len = (local_begin.sf.end_pos - local_begin.sf.start_pos).to_usize();
600
601 if start_index > end_index || end_index > source_len {
602 return Err(Box::new(SpanSnippetError::MalformedForSourcemap(
603 MalformedSourceMapPositions {
604 name: local_begin.sf.name.clone(),
605 source_len,
606 begin_pos: local_begin.pos,
607 end_pos: local_end.pos,
608 },
609 )));
610 }
611
612 let src = &local_begin.sf.src;
613 Ok(extract_source(src, start_index, end_index))
614 }
615 }
616
617 pub fn with_snippet_of_span<F, Ret>(
619 &self,
620 sp: Span,
621 op: F,
622 ) -> Result<Ret, Box<SpanSnippetError>>
623 where
624 F: FnOnce(&str) -> Ret,
625 {
626 self.span_to_source(sp, |src, start_index, end_index| {
627 op(&src[start_index..end_index])
628 })
629 }
630
631 pub fn span_to_margin(&self, sp: Span) -> Option<usize> {
632 match self.span_to_prev_source(sp) {
633 Err(_) => None,
634 Ok(source) => source
635 .split('\n')
636 .last()
637 .map(|last_line| last_line.len() - last_line.trim_start().len()),
638 }
639 }
640
641 pub fn with_span_to_prev_source<F, Ret>(
643 &self,
644 sp: Span,
645 op: F,
646 ) -> Result<Ret, Box<SpanSnippetError>>
647 where
648 F: FnOnce(&str) -> Ret,
649 {
650 self.span_to_source(sp, |src, start_index, _| op(&src[..start_index]))
651 }
652
653 pub fn span_to_prev_source(&self, sp: Span) -> Result<String, Box<SpanSnippetError>> {
655 self.with_span_to_prev_source(sp, |s| s.to_string())
656 }
657
658 pub fn with_span_to_next_source<F, Ret>(
660 &self,
661 sp: Span,
662 op: F,
663 ) -> Result<Ret, Box<SpanSnippetError>>
664 where
665 F: FnOnce(&str) -> Ret,
666 {
667 self.span_to_source(sp, |src, _, end_index| op(&src[end_index..]))
668 }
669
670 pub fn span_to_next_source(&self, sp: Span) -> Result<String, Box<SpanSnippetError>> {
672 self.with_span_to_next_source(sp, |s| s.to_string())
673 }
674
675 pub fn span_extend_to_prev_char(&self, sp: Span, c: char) -> Span {
679 if let Ok(prev_source) = self.span_to_prev_source(sp) {
680 let prev_source = prev_source.rsplit(c).next().unwrap_or("").trim_start();
681 if !prev_source.is_empty() && !prev_source.contains('\n') {
682 return sp.with_lo(BytePos(sp.lo().0 - prev_source.len() as u32));
683 }
684 }
685
686 sp
687 }
688
689 pub fn span_extend_to_prev_str(&self, sp: Span, pat: &str, accept_newlines: bool) -> Span {
694 for ws in &[" ", "\t", "\n"] {
699 let pat = pat.to_owned() + ws;
700 if let Ok(prev_source) = self.span_to_prev_source(sp) {
701 let prev_source = prev_source.rsplit(&pat).next().unwrap_or("").trim_start();
702 if !prev_source.is_empty() && (!prev_source.contains('\n') || accept_newlines) {
703 return sp.with_lo(BytePos(sp.lo().0 - prev_source.len() as u32));
704 }
705 }
706 }
707
708 sp
709 }
710
711 pub fn span_extend_to_next_char(&self, sp: Span, c: char) -> Span {
715 if let Ok(next_source) = self.span_to_next_source(sp) {
716 let next_source = next_source.split(c).next().unwrap_or("").trim_end();
717 if !next_source.is_empty() && !next_source.contains('\n') {
718 return sp.with_hi(BytePos(sp.hi().0 + next_source.len() as u32));
719 }
720 }
721
722 sp
723 }
724
725 pub fn span_extend_to_next_str(&self, sp: Span, pat: &str, accept_newlines: bool) -> Span {
730 for ws in &[" ", "\t", "\n"] {
731 let pat = pat.to_owned() + ws;
732 if let Ok(next_source) = self.span_to_next_source(sp) {
733 let next_source = next_source.split(&pat).next().unwrap_or("").trim_end();
734 if !next_source.is_empty() && (!next_source.contains('\n') || accept_newlines) {
735 return sp.with_hi(BytePos(sp.hi().0 + next_source.len() as u32));
736 }
737 }
738 }
739
740 sp
741 }
742
743 pub fn span_until_char(&self, sp: Span, c: char) -> Span {
751 if sp.is_dummy() {
752 return sp;
753 }
754
755 let v = self.span_to_source(sp, |src, start_index, end_index| {
756 let snippet = &src[start_index..end_index];
757 let snippet = snippet.split(c).next().unwrap_or("").trim_end();
758 if !snippet.is_empty() && !snippet.contains('\n') {
759 sp.with_hi(BytePos(sp.lo().0 + snippet.len() as u32))
760 } else {
761 sp
762 }
763 });
764 match v {
765 Ok(v) => v,
766 Err(_) => sp,
767 }
768 }
769
770 pub fn span_through_char(&self, sp: Span, c: char) -> Span {
777 if sp.is_dummy() {
778 return sp;
779 }
780
781 if let Ok(snippet) = self.span_to_snippet(sp) {
782 if let Some(offset) = snippet.find(c) {
783 return sp.with_hi(BytePos(sp.lo().0 + (offset + c.len_utf8()) as u32));
784 }
785 }
786 sp
787 }
788
789 pub fn span_until_non_whitespace(&self, sp: Span) -> Span {
795 let mut whitespace_found = false;
796
797 self.span_take_while(sp, |c| {
798 if !whitespace_found && c.is_whitespace() {
799 whitespace_found = true;
800 }
801
802 !whitespace_found || c.is_whitespace()
803 })
804 }
805
806 pub fn span_until_whitespace(&self, sp: Span) -> Span {
812 self.span_take_while(sp, |c| !c.is_whitespace())
813 }
814
815 pub fn span_take_while<P>(&self, sp: Span, mut predicate: P) -> Span
817 where
818 P: for<'r> FnMut(&'r char) -> bool,
819 {
820 self.span_to_source(sp, |src, start_index, end_index| {
821 let snippet = &src[start_index..end_index];
822
823 let offset = snippet
824 .chars()
825 .take_while(&mut predicate)
826 .map(|c| c.len_utf8())
827 .sum::<usize>();
828
829 sp.with_hi(BytePos(sp.lo().0 + (offset as u32)))
830 })
831 .unwrap_or(sp)
832 }
833
834 pub fn def_span(&self, sp: Span) -> Span {
835 self.span_until_char(sp, '{')
836 }
837
838 pub fn start_point(&self, sp: Span) -> Span {
840 let pos = sp.lo().0;
841 let width = self.find_width_of_character_at_span(sp, false);
842 let corrected_start_position = pos.checked_add(width).unwrap_or(pos);
843 let end_point = BytePos(cmp::max(corrected_start_position, sp.lo().0));
844 sp.with_hi(end_point)
845 }
846
847 pub fn end_point(&self, sp: Span) -> Span {
849 let pos = sp.hi().0;
850
851 let width = self.find_width_of_character_at_span(sp, false);
852 let corrected_end_position = pos.checked_sub(width).unwrap_or(pos);
853
854 let end_point = BytePos(cmp::max(corrected_end_position, sp.lo().0));
855 sp.with_lo(end_point)
856 }
857
858 pub fn next_point(&self, sp: Span) -> Span {
861 let start_of_next_point = sp.hi().0;
862
863 let width = self.find_width_of_character_at_span(sp, true);
864 let end_of_next_point = start_of_next_point
869 .checked_add(width - 1)
870 .unwrap_or(start_of_next_point);
871
872 let end_of_next_point = BytePos(cmp::max(sp.lo().0 + 1, end_of_next_point));
873 Span::new(BytePos(start_of_next_point), end_of_next_point)
874 }
875
876 fn find_width_of_character_at_span(&self, sp: Span, forwards: bool) -> u32 {
879 if sp.lo() >= sp.hi() {
881 debug!("find_width_of_character_at_span: early return malformed span");
882 return 1;
883 }
884
885 let local_begin = self.lookup_byte_offset(sp.lo());
886 let local_end = self.lookup_byte_offset(sp.hi());
887 debug!(
888 "find_width_of_character_at_span: local_begin=`{:?}`, local_end=`{:?}`",
889 local_begin, local_end
890 );
891
892 let start_index = local_begin.pos.to_usize();
893 let end_index = local_end.pos.to_usize();
894 debug!(
895 "find_width_of_character_at_span: start_index=`{:?}`, end_index=`{:?}`",
896 start_index, end_index
897 );
898
899 if (!forwards && end_index == usize::MIN) || (forwards && start_index == usize::MAX) {
902 debug!("find_width_of_character_at_span: start or end of span, cannot be multibyte");
903 return 1;
904 }
905
906 let source_len = (local_begin.sf.end_pos - local_begin.sf.start_pos).to_usize();
907 debug!(
908 "find_width_of_character_at_span: source_len=`{:?}`",
909 source_len
910 );
911 if start_index > end_index || end_index > source_len {
913 debug!("find_width_of_character_at_span: source indexes are malformed");
914 return 1;
915 }
916
917 let src = &local_begin.sf.src;
921 let snippet = {
922 let len = src.len();
923 &src[start_index..len]
924 };
925 debug!("find_width_of_character_at_span: snippet=`{:?}`", snippet);
926
927 let mut target = if forwards {
928 end_index + 1
929 } else {
930 end_index - 1
931 };
932 debug!(
933 "find_width_of_character_at_span: initial target=`{:?}`",
934 target
935 );
936
937 while !snippet.is_char_boundary(target - start_index) && target < source_len {
938 target = if forwards {
939 target + 1
940 } else {
941 match target.checked_sub(1) {
942 Some(target) => target,
943 None => {
944 break;
945 }
946 }
947 };
948 debug!("find_width_of_character_at_span: target=`{:?}`", target);
949 }
950 debug!(
951 "find_width_of_character_at_span: final target=`{:?}`",
952 target
953 );
954
955 if forwards {
956 (target - end_index) as u32
957 } else {
958 (end_index - target) as u32
959 }
960 }
961
962 pub fn get_source_file(&self, filename: &FileName) -> Option<Lrc<SourceFile>> {
963 for sf in self.files.borrow().source_files.iter() {
964 if *filename == *sf.name {
965 return Some(sf.clone());
966 }
967 }
968 None
969 }
970
971 pub fn lookup_byte_offset(&self, bpos: BytePos) -> SourceFileAndBytePos {
974 self.try_lookup_byte_offset(bpos).unwrap()
975 }
976
977 pub fn try_lookup_byte_offset(
980 &self,
981 bpos: BytePos,
982 ) -> Result<SourceFileAndBytePos, SourceMapLookupError> {
983 let sf = self.try_lookup_source_file(bpos)?;
984 let offset = bpos - sf.start_pos;
985 Ok(SourceFileAndBytePos { sf, pos: offset })
986 }
987
988 fn bytepos_to_file_charpos(&self, bpos: BytePos) -> Result<CharPos, SourceMapLookupError> {
990 let map = self.try_lookup_source_file(bpos)?;
991
992 Ok(self.bytepos_to_file_charpos_with(&map, bpos))
993 }
994
995 fn bytepos_to_file_charpos_with(&self, map: &SourceFile, bpos: BytePos) -> CharPos {
996 let total_extra_bytes = self.calc_utf16_offset(map, bpos, &mut Default::default());
997 assert!(
998 map.start_pos.to_u32() + total_extra_bytes <= bpos.to_u32(),
999 "map.start_pos = {:?}; total_extra_bytes = {}; bpos = {:?}",
1000 map.start_pos,
1001 total_extra_bytes,
1002 bpos,
1003 );
1004 CharPos(bpos.to_usize() - map.start_pos.to_usize() - total_extra_bytes as usize)
1005 }
1006
1007 pub fn span_to_char_offset(&self, file: &SourceFile, span: Span) -> (u32, u32) {
1010 let start_offset = file.start_pos;
1012
1013 let mut state = ByteToCharPosState::default();
1014 let start = span.lo.to_u32()
1015 - start_offset.to_u32()
1016 - self.calc_utf16_offset(file, span.lo, &mut state);
1017 let end = span.hi.to_u32()
1018 - start_offset.to_u32()
1019 - self.calc_utf16_offset(file, span.hi, &mut state);
1020
1021 (start, end)
1022 }
1023
1024 fn calc_utf16_offset(
1027 &self,
1028 file: &SourceFile,
1029 bpos: BytePos,
1030 state: &mut ByteToCharPosState,
1031 ) -> u32 {
1032 let mut total_extra_bytes = state.total_extra_bytes;
1033 let mut index = state.mbc_index;
1034 let analysis = file.analyze();
1035 if bpos >= state.pos {
1036 let range = index..analysis.multibyte_chars.len();
1037 for i in range {
1038 let mbc = &analysis.multibyte_chars[i];
1039 debug!("{}-byte char at {:?}", mbc.bytes, mbc.pos);
1040 if mbc.pos >= bpos {
1041 break;
1042 }
1043 total_extra_bytes += mbc.byte_to_char_diff() as u32;
1044 debug_assert!(
1047 bpos.to_u32() >= mbc.pos.to_u32() + mbc.bytes as u32,
1048 "bpos = {:?}, mbc.pos = {:?}, mbc.bytes = {:?}",
1049 bpos,
1050 mbc.pos,
1051 mbc.bytes
1052 );
1053 index += 1;
1054 }
1055 } else {
1056 let range = 0..index;
1057 for i in range.rev() {
1058 let mbc = &analysis.multibyte_chars[i];
1059 debug!("{}-byte char at {:?}", mbc.bytes, mbc.pos);
1060 if mbc.pos < bpos {
1061 break;
1062 }
1063 total_extra_bytes -= mbc.byte_to_char_diff() as u32;
1064 debug_assert!(
1067 bpos.to_u32() <= mbc.pos.to_u32(),
1068 "bpos = {:?}, mbc.pos = {:?}",
1069 bpos,
1070 mbc.pos,
1071 );
1072 index -= 1;
1073 }
1074 }
1075
1076 state.pos = bpos;
1077 state.total_extra_bytes = total_extra_bytes;
1078 state.mbc_index = index;
1079
1080 total_extra_bytes
1081 }
1082
1083 #[doc(hidden)]
1088 pub fn lookup_source_file_in(
1089 files: &[Lrc<SourceFile>],
1090 pos: BytePos,
1091 ) -> Option<Lrc<SourceFile>> {
1092 if pos.is_dummy() {
1093 return None;
1094 }
1095
1096 let count = files.len();
1097
1098 let mut a = 0;
1100 let mut b = count;
1101 while b - a > 1 {
1102 let m = (a + b) / 2;
1103 if files[m].start_pos > pos {
1104 b = m;
1105 } else {
1106 a = m;
1107 }
1108 }
1109
1110 if a >= count {
1111 return None;
1112 }
1113
1114 Some(files[a].clone())
1115 }
1116
1117 #[doc(hidden)]
1121 pub fn lookup_source_file(&self, pos: BytePos) -> Lrc<SourceFile> {
1122 self.try_lookup_source_file(pos).unwrap()
1123 }
1124
1125 #[doc(hidden)]
1129 pub fn try_lookup_source_file(
1130 &self,
1131 pos: BytePos,
1132 ) -> Result<Lrc<SourceFile>, SourceMapLookupError> {
1133 let files = self.files.borrow();
1134 let files = &files.source_files;
1135 let fm = Self::lookup_source_file_in(files, pos);
1136 match fm {
1137 Some(fm) => Ok(fm),
1138 None => Err(SourceMapLookupError::NoFileFor(pos)),
1139 }
1140 }
1141
1142 pub fn count_lines(&self) -> usize {
1143 self.files().iter().fold(0, |a, f| a + f.count_lines())
1144 }
1145
1146 pub fn generate_fn_name_span(&self, span: Span) -> Option<Span> {
1147 let prev_span = self.span_extend_to_prev_str(span, "fn", true);
1148 self.span_to_snippet(prev_span)
1149 .map(|snippet| {
1150 let len = snippet
1151 .find(|c: char| !c.is_alphanumeric() && c != '_')
1152 .expect("no label after fn");
1153 prev_span.with_hi(BytePos(prev_span.lo().0 + len as u32))
1154 })
1155 .ok()
1156 }
1157
1158 pub fn generate_local_type_param_snippet(&self, span: Span) -> Option<(Span, String)> {
1179 let sugg_span = self.span_extend_to_prev_str(span, "fn", false);
1182 if sugg_span != span {
1183 if let Ok(snippet) = self.span_to_snippet(sugg_span) {
1184 let mut offset = snippet
1186 .find(|c: char| !c.is_alphanumeric() && c != '_')
1187 .expect("no label after fn");
1188
1189 let mut bracket_counter = 0;
1191 let mut last_char = None;
1192 for c in snippet[offset..].chars() {
1193 match c {
1194 '<' => bracket_counter += 1,
1195 '>' => bracket_counter -= 1,
1196 '(' => {
1197 if bracket_counter == 0 {
1198 break;
1199 }
1200 }
1201 _ => {}
1202 }
1203 offset += c.len_utf8();
1204 last_char = Some(c);
1205 }
1206
1207 let sugg_span = sugg_span.with_hi(BytePos(sugg_span.lo().0 + offset as u32));
1209
1210 let mut new_snippet = if last_char == Some('>') {
1213 format!("{}, ", &snippet[..(offset - '>'.len_utf8())])
1214 } else {
1215 format!("{}<", &snippet[..offset])
1216 };
1217 new_snippet.push_str(
1218 &self
1219 .span_to_snippet(span)
1220 .unwrap_or_else(|_| "T".to_string()),
1221 );
1222 new_snippet.push('>');
1223
1224 return Some((sugg_span, new_snippet));
1225 }
1226 }
1227
1228 None
1229 }
1230
1231 #[cfg(feature = "sourcemap")]
1232 #[cfg_attr(docsrs, doc(cfg(feature = "sourcemap")))]
1233 pub fn build_source_map(&self, mappings: &[(BytePos, LineCol)]) -> sourcemap::SourceMap {
1234 self.build_source_map_from(mappings, None)
1235 }
1236
1237 #[cfg(feature = "sourcemap")]
1239 #[cfg_attr(docsrs, doc(cfg(feature = "sourcemap")))]
1240 pub fn build_source_map_from(
1241 &self,
1242 mappings: &[(BytePos, LineCol)],
1243 orig: Option<&sourcemap::SourceMap>,
1244 ) -> sourcemap::SourceMap {
1245 self.build_source_map_with_config(mappings, orig, DefaultSourceMapGenConfig)
1246 }
1247
1248 #[allow(clippy::ptr_arg)]
1249 #[cfg(feature = "sourcemap")]
1250 #[cfg_attr(docsrs, doc(cfg(feature = "sourcemap")))]
1251 pub fn build_source_map_with_config(
1252 &self,
1253 mappings: &[(BytePos, LineCol)],
1254 orig: Option<&sourcemap::SourceMap>,
1255 config: impl SourceMapGenConfig,
1256 ) -> sourcemap::SourceMap {
1257 let mut builder = SourceMapBuilder::new(None);
1258
1259 let mut src_id = 0u32;
1260
1261 let mut cur_file: Option<Lrc<SourceFile>> = None;
1265
1266 let mut prev_dst_line = u32::MAX;
1267
1268 let mut ch_state = ByteToCharPosState::default();
1269 let mut line_state = ByteToCharPosState::default();
1270
1271 for (pos, lc) in mappings.iter() {
1272 let pos = *pos;
1273
1274 if pos.is_reserved_for_comments() {
1275 continue;
1276 }
1277
1278 let lc = *lc;
1279
1280 if lc.line == 0 && lc.col == 0 && pos.is_dummy() {
1283 continue;
1284 }
1285
1286 if pos == BytePos(u32::MAX) {
1287 builder.add_raw(lc.line, lc.col, 0, 0, Some(src_id), None, false);
1288 continue;
1289 }
1290
1291 let f;
1292 let f = match cur_file {
1293 Some(ref f) if f.start_pos <= pos && pos < f.end_pos => f,
1294 _ => {
1295 f = self.try_lookup_source_file(pos).unwrap();
1296 if config.skip(&f.name) {
1297 continue;
1298 }
1299 src_id = builder.add_source(&config.file_name_to_source(&f.name));
1300
1301 let inline_sources_content = config.inline_sources_content(&f.name);
1302 if inline_sources_content {
1303 builder.set_source_contents(src_id, Some(&f.src));
1304 }
1305
1306 ch_state = ByteToCharPosState::default();
1307 line_state = ByteToCharPosState::default();
1308
1309 cur_file = Some(f.clone());
1310 &f
1311 }
1312 };
1313 if config.skip(&f.name) {
1314 continue;
1315 }
1316
1317 let emit_columns = config.emit_columns(&f.name);
1318
1319 if !emit_columns && lc.line == prev_dst_line {
1320 continue;
1321 }
1322
1323 let line = match f.lookup_line(pos) {
1324 Some(line) => line as u32,
1325 None => continue,
1326 };
1327
1328 let analysis = f.analyze();
1329 let linebpos = analysis.lines[line as usize];
1330 debug_assert!(
1331 pos >= linebpos,
1332 "{}: bpos = {:?}; linebpos = {:?};",
1333 f.name,
1334 pos,
1335 linebpos,
1336 );
1337
1338 let linechpos =
1339 linebpos.to_u32() - self.calc_utf16_offset(f, linebpos, &mut line_state);
1340 let chpos = pos.to_u32() - self.calc_utf16_offset(f, pos, &mut ch_state);
1341
1342 debug_assert!(
1343 chpos >= linechpos,
1344 "{}: chpos = {:?}; linechpos = {:?};",
1345 f.name,
1346 chpos,
1347 linechpos,
1348 );
1349
1350 let col = chpos - linechpos;
1351 let name = None;
1352
1353 let name_idx = name
1354 .or_else(|| config.name_for_bytepos(pos))
1355 .map(|name| builder.add_name(name));
1356
1357 builder.add_raw(lc.line, lc.col, line, col, Some(src_id), name_idx, false);
1358 prev_dst_line = lc.line;
1359 }
1360
1361 let map = builder.into_sourcemap();
1362
1363 if let Some(mut orig) = orig.cloned() {
1364 orig.adjust_mappings(&map);
1365 return orig;
1366 }
1367
1368 map
1369 }
1370}
1371
1372impl SourceMapper for SourceMap {
1373 fn lookup_char_pos(&self, pos: BytePos) -> Loc {
1374 self.lookup_char_pos(pos)
1375 }
1376
1377 fn span_to_lines(&self, sp: Span) -> FileLinesResult {
1378 self.span_to_lines(sp)
1379 }
1380
1381 fn span_to_string(&self, sp: Span) -> String {
1382 self.span_to_string(sp)
1383 }
1384
1385 fn span_to_filename(&self, sp: Span) -> Lrc<FileName> {
1386 self.span_to_filename(sp)
1387 }
1388
1389 fn span_to_snippet(&self, sp: Span) -> Result<String, Box<SpanSnippetError>> {
1391 self.span_to_source(sp, |src, start_index, end_index| {
1392 src[start_index..end_index].to_string()
1393 })
1394 }
1395
1396 fn merge_spans(&self, sp_lhs: Span, sp_rhs: Span) -> Option<Span> {
1397 self.merge_spans(sp_lhs, sp_rhs)
1398 }
1399
1400 fn call_span_if_macro(&self, sp: Span) -> Span {
1401 sp
1402 }
1403
1404 fn doctest_offset_line(&self, line: usize) -> usize {
1405 self.doctest_offset_line(line)
1406 }
1407}
1408
1409#[derive(Clone, Default)]
1410pub struct FilePathMapping {
1411 mapping: Vec<(PathBuf, PathBuf)>,
1412}
1413
1414impl FilePathMapping {
1415 pub fn empty() -> FilePathMapping {
1416 FilePathMapping {
1417 mapping: Vec::new(),
1418 }
1419 }
1420
1421 pub fn new(mapping: Vec<(PathBuf, PathBuf)>) -> FilePathMapping {
1422 FilePathMapping { mapping }
1423 }
1424
1425 pub fn map_prefix(&self, path: &Path) -> (PathBuf, bool) {
1429 for (from, to) in self.mapping.iter().rev() {
1433 if let Ok(rest) = path.strip_prefix(from) {
1434 return (to.join(rest), true);
1435 }
1436 }
1437
1438 (path.to_path_buf(), false)
1439 }
1440}
1441
1442pub trait SourceMapGenConfig {
1443 fn file_name_to_source(&self, f: &FileName) -> String;
1449
1450 fn name_for_bytepos(&self, _bpos: BytePos) -> Option<&str> {
1452 None
1453 }
1454
1455 fn inline_sources_content(&self, f: &FileName) -> bool {
1457 !matches!(
1458 f,
1459 FileName::Real(..) | FileName::Custom(..) | FileName::Url(..)
1460 )
1461 }
1462
1463 fn emit_columns(&self, _f: &FileName) -> bool {
1465 true
1466 }
1467
1468 fn skip(&self, f: &FileName) -> bool {
1470 matches!(f, FileName::Internal(..))
1471 }
1472}
1473
1474#[derive(Debug, Clone)]
1475pub struct DefaultSourceMapGenConfig;
1476
1477macro_rules! impl_ref {
1478 ($TP:ident, $T:ty) => {
1479 impl<$TP> SourceMapGenConfig for $T
1480 where
1481 $TP: SourceMapGenConfig,
1482 {
1483 fn file_name_to_source(&self, f: &FileName) -> String {
1484 (**self).file_name_to_source(f)
1485 }
1486 }
1487 };
1488}
1489
1490impl_ref!(T, &'_ T);
1491impl_ref!(T, Box<T>);
1492impl_ref!(T, std::rc::Rc<T>);
1493impl_ref!(T, std::sync::Arc<T>);
1494
1495impl SourceMapGenConfig for DefaultSourceMapGenConfig {
1496 fn file_name_to_source(&self, f: &FileName) -> String {
1497 f.to_string()
1498 }
1499}
1500
1501#[derive(Debug, Clone, Default)]
1503pub struct ByteToCharPosState {
1504 pos: BytePos,
1506
1507 total_extra_bytes: u32,
1509
1510 mbc_index: usize,
1513}
1514
1515#[cfg(test)]
1520mod tests {
1521 use super::*;
1522
1523 fn init_source_map() -> SourceMap {
1524 let sm = SourceMap::new(FilePathMapping::empty());
1525 sm.new_source_file(
1526 Lrc::new(PathBuf::from("blork.rs").into()),
1527 "first line.\nsecond line".to_string(),
1528 );
1529 sm.new_source_file(Lrc::new(PathBuf::from("empty.rs").into()), String::new());
1530 sm.new_source_file(
1531 Lrc::new(PathBuf::from("blork2.rs").into()),
1532 "first line.\nsecond line".to_string(),
1533 );
1534 sm
1535 }
1536
1537 #[test]
1538 fn t3() {
1539 let sm = init_source_map();
1541
1542 let srcfbp1 = sm.lookup_byte_offset(BytePos(24));
1543 assert_eq!(*srcfbp1.sf.name, PathBuf::from("blork.rs").into());
1544 assert_eq!(srcfbp1.pos, BytePos(23));
1545
1546 let srcfbp1 = sm.lookup_byte_offset(BytePos(25));
1547 assert_eq!(*srcfbp1.sf.name, PathBuf::from("empty.rs").into());
1548 assert_eq!(srcfbp1.pos, BytePos(0));
1549
1550 let srcfbp2 = sm.lookup_byte_offset(BytePos(26));
1551 assert_eq!(*srcfbp2.sf.name, PathBuf::from("blork2.rs").into());
1552 assert_eq!(srcfbp2.pos, BytePos(0));
1553 }
1554
1555 #[test]
1556 fn t4() {
1557 let sm = init_source_map();
1559
1560 let cp1 = sm.bytepos_to_file_charpos(BytePos(23)).unwrap();
1561 assert_eq!(cp1, CharPos(22));
1562
1563 let cp2 = sm.bytepos_to_file_charpos(BytePos(26)).unwrap();
1564 assert_eq!(cp2, CharPos(0));
1565 }
1566
1567 #[test]
1568 fn t5() {
1569 let sm = init_source_map();
1571
1572 let loc1 = sm.lookup_char_pos(BytePos(23));
1573 assert_eq!(*loc1.file.name, PathBuf::from("blork.rs").into());
1574 assert_eq!(loc1.line, 2);
1575 assert_eq!(loc1.col, CharPos(10));
1576
1577 let loc2 = sm.lookup_char_pos(BytePos(26));
1578 assert_eq!(*loc2.file.name, PathBuf::from("blork2.rs").into());
1579 assert_eq!(loc2.line, 1);
1580 assert_eq!(loc2.col, CharPos(0));
1581 }
1582
1583 fn init_source_map_mbc() -> SourceMap {
1584 let sm = SourceMap::new(FilePathMapping::empty());
1585 sm.new_source_file(
1587 Lrc::new(PathBuf::from("blork.rs").into()),
1588 "fir€st €€€€ line.\nsecond line".to_string(),
1589 );
1590 sm.new_source_file(
1591 Lrc::new(PathBuf::from("blork2.rs").into()),
1592 "first line€€.\n€ second line".to_string(),
1593 );
1594 sm
1595 }
1596
1597 #[test]
1598 fn t6() {
1599 let sm = init_source_map_mbc();
1601
1602 let cp1 = sm.bytepos_to_file_charpos(BytePos(4)).unwrap();
1603 assert_eq!(cp1, CharPos(3));
1604
1605 let cp2 = sm.bytepos_to_file_charpos(BytePos(7)).unwrap();
1606 assert_eq!(cp2, CharPos(4));
1607
1608 let cp3 = sm.bytepos_to_file_charpos(BytePos(57)).unwrap();
1609 assert_eq!(cp3, CharPos(12));
1610
1611 let cp4 = sm.bytepos_to_file_charpos(BytePos(62)).unwrap();
1612 assert_eq!(cp4, CharPos(15));
1613 }
1614
1615 #[test]
1616 fn t7() {
1617 let sm = init_source_map();
1619 let span = Span::new(BytePos(13), BytePos(24));
1620 let file_lines = sm.span_to_lines(span).unwrap();
1621
1622 assert_eq!(*file_lines.file.name, PathBuf::from("blork.rs").into());
1623 assert_eq!(file_lines.lines.len(), 1);
1624 assert_eq!(file_lines.lines[0].line_index, 1);
1625 }
1626
1627 fn span_from_selection(input: &str, selection: &str) -> Span {
1632 assert_eq!(input.len(), selection.len());
1633 let left_index = (selection.find('~').unwrap() + 1) as u32;
1635 let right_index = selection
1636 .rfind('~')
1637 .map(|x| {
1638 (x + 1) as u32
1640 })
1641 .unwrap_or(left_index);
1642 Span::new(BytePos(left_index), BytePos(right_index + 1))
1643 }
1644
1645 #[test]
1648 fn span_to_snippet_and_lines_spanning_multiple_lines() {
1649 let sm = SourceMap::new(FilePathMapping::empty());
1650 let inputtext = "aaaaa\nbbbbBB\nCCC\nDDDDDddddd\neee\n";
1651 let selection = " \n ~~\n~~~\n~~~~~ \n \n";
1652 sm.new_source_file(
1653 Lrc::new(Path::new("blork.rs").to_path_buf().into()),
1654 inputtext.to_string(),
1655 );
1656 let span = span_from_selection(inputtext, selection);
1657
1658 assert_eq!(&sm.span_to_snippet(span).unwrap(), "BB\nCCC\nDDDDD");
1660
1661 let lines = sm.span_to_lines(span).unwrap();
1664 let expected = vec![
1665 LineInfo {
1666 line_index: 1,
1667 start_col: CharPos(4),
1668 end_col: CharPos(6),
1669 },
1670 LineInfo {
1671 line_index: 2,
1672 start_col: CharPos(0),
1673 end_col: CharPos(3),
1674 },
1675 LineInfo {
1676 line_index: 3,
1677 start_col: CharPos(0),
1678 end_col: CharPos(5),
1679 },
1680 ];
1681 assert_eq!(lines.lines, expected);
1682 }
1683
1684 #[test]
1685 fn t8() {
1686 let sm = init_source_map();
1688 let span = Span::new(BytePos(13), BytePos(24));
1689 let snippet = sm.span_to_snippet(span);
1690
1691 assert_eq!(snippet, Ok("second line".to_string()));
1692 }
1693
1694 #[test]
1695 fn t9() {
1696 let sm = init_source_map();
1698 let span = Span::new(BytePos(13), BytePos(24));
1699 let sstr = sm.span_to_string(span);
1700
1701 assert_eq!(sstr, "blork.rs:2:1: 2:12");
1702 }
1703
1704 #[test]
1705 fn t10() {
1706 let sm = SourceMap::new(FilePathMapping::empty());
1708 sm.new_source_file(Lrc::new(PathBuf::from("blork.rs").into()), "".to_string());
1709 let span = Span::new(BytePos(1), BytePos(1));
1710 let file_lines = sm.span_to_lines(span).unwrap();
1711
1712 assert_eq!(*file_lines.file.name, PathBuf::from("blork.rs").into());
1713 assert_eq!(file_lines.lines.len(), 0);
1714 }
1715
1716 #[test]
1718 fn span_merging_fail() {
1719 let sm = SourceMap::new(FilePathMapping::empty());
1720 let inputtext = "bbbb BB\ncc CCC\n";
1721 let selection1 = " ~~\n \n";
1722 let selection2 = " \n ~~~\n";
1723 sm.new_source_file(
1724 Lrc::new(Path::new("blork.rs").to_owned().into()),
1725 inputtext.to_owned(),
1726 );
1727 let span1 = span_from_selection(inputtext, selection1);
1728 let span2 = span_from_selection(inputtext, selection2);
1729
1730 assert!(sm.merge_spans(span1, span2).is_none());
1731 }
1732
1733 #[test]
1734 fn calc_utf16_offset() {
1735 let input = "t¢e∆s💩t";
1736 let sm = SourceMap::new(FilePathMapping::empty());
1737 let file = sm.new_source_file(
1738 Lrc::new(PathBuf::from("blork.rs").into()),
1739 input.to_string(),
1740 );
1741
1742 let mut state = ByteToCharPosState::default();
1743 let mut bpos = file.start_pos;
1744 let mut cpos = CharPos(bpos.to_usize());
1745 for c in input.chars() {
1746 let actual = bpos.to_u32() - sm.calc_utf16_offset(&file, bpos, &mut state);
1747
1748 assert_eq!(actual, cpos.to_u32());
1749
1750 bpos = bpos + BytePos(c.len_utf8() as u32);
1751 cpos = cpos + CharPos(c.len_utf16());
1752 }
1753
1754 for c in input.chars().rev() {
1755 bpos = bpos - BytePos(c.len_utf8() as u32);
1756 cpos = cpos - CharPos(c.len_utf16());
1757
1758 let actual = bpos.to_u32() - sm.calc_utf16_offset(&file, bpos, &mut state);
1759
1760 assert_eq!(actual, cpos.to_u32());
1761 }
1762 }
1763
1764 #[test]
1765 fn bytepos_to_charpos() {
1766 let input = "t¢e∆s💩t";
1767 let sm = SourceMap::new(FilePathMapping::empty());
1768 let file = sm.new_source_file(
1769 Lrc::new(PathBuf::from("blork.rs").into()),
1770 input.to_string(),
1771 );
1772
1773 let mut bpos = file.start_pos;
1774 let mut cpos = CharPos(0);
1775 for c in input.chars() {
1776 let actual = sm.bytepos_to_file_charpos_with(&file, bpos);
1777
1778 assert_eq!(actual, cpos);
1779
1780 bpos = bpos + BytePos(c.len_utf8() as u32);
1781 cpos = cpos + CharPos(c.len_utf16());
1782 }
1783 }
1784}