summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_span/src/source_map/tests.rs
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_span/src/source_map/tests.rs')
-rw-r--r--compiler/rustc_span/src/source_map/tests.rs481
1 files changed, 481 insertions, 0 deletions
diff --git a/compiler/rustc_span/src/source_map/tests.rs b/compiler/rustc_span/src/source_map/tests.rs
new file mode 100644
index 000000000..be827cea8
--- /dev/null
+++ b/compiler/rustc_span/src/source_map/tests.rs
@@ -0,0 +1,481 @@
+use super::*;
+
+use rustc_data_structures::sync::Lrc;
+
+fn init_source_map() -> SourceMap {
+ let sm = SourceMap::new(FilePathMapping::empty());
+ sm.new_source_file(PathBuf::from("blork.rs").into(), "first line.\nsecond line".to_string());
+ sm.new_source_file(PathBuf::from("empty.rs").into(), String::new());
+ sm.new_source_file(PathBuf::from("blork2.rs").into(), "first line.\nsecond line".to_string());
+ sm
+}
+
+impl SourceMap {
+ /// Returns `Some(span)`, a union of the LHS and RHS span. The LHS must precede the RHS. If
+ /// there are gaps between LHS and RHS, the resulting union will cross these gaps.
+ /// For this to work,
+ ///
+ /// * the syntax contexts of both spans much match,
+ /// * the LHS span needs to end on the same line the RHS span begins,
+ /// * the LHS span must start at or before the RHS span.
+ fn merge_spans(&self, sp_lhs: Span, sp_rhs: Span) -> Option<Span> {
+ // Ensure we're at the same expansion ID.
+ if sp_lhs.ctxt() != sp_rhs.ctxt() {
+ return None;
+ }
+
+ let lhs_end = match self.lookup_line(sp_lhs.hi()) {
+ Ok(x) => x,
+ Err(_) => return None,
+ };
+ let rhs_begin = match self.lookup_line(sp_rhs.lo()) {
+ Ok(x) => x,
+ Err(_) => return None,
+ };
+
+ // If we must cross lines to merge, don't merge.
+ if lhs_end.line != rhs_begin.line {
+ return None;
+ }
+
+ // Ensure these follow the expected order and that we don't overlap.
+ if (sp_lhs.lo() <= sp_rhs.lo()) && (sp_lhs.hi() <= sp_rhs.lo()) {
+ Some(sp_lhs.to(sp_rhs))
+ } else {
+ None
+ }
+ }
+
+ /// Converts an absolute `BytePos` to a `CharPos` relative to the `SourceFile`.
+ fn bytepos_to_file_charpos(&self, bpos: BytePos) -> CharPos {
+ let idx = self.lookup_source_file_idx(bpos);
+ let sf = &(*self.files.borrow().source_files)[idx];
+ sf.bytepos_to_file_charpos(bpos)
+ }
+}
+
+/// Tests `lookup_byte_offset`.
+#[test]
+fn t3() {
+ let sm = init_source_map();
+
+ let srcfbp1 = sm.lookup_byte_offset(BytePos(23));
+ assert_eq!(srcfbp1.sf.name, PathBuf::from("blork.rs").into());
+ assert_eq!(srcfbp1.pos, BytePos(23));
+
+ let srcfbp1 = sm.lookup_byte_offset(BytePos(24));
+ assert_eq!(srcfbp1.sf.name, PathBuf::from("empty.rs").into());
+ assert_eq!(srcfbp1.pos, BytePos(0));
+
+ let srcfbp2 = sm.lookup_byte_offset(BytePos(25));
+ assert_eq!(srcfbp2.sf.name, PathBuf::from("blork2.rs").into());
+ assert_eq!(srcfbp2.pos, BytePos(0));
+}
+
+/// Tests `bytepos_to_file_charpos`.
+#[test]
+fn t4() {
+ let sm = init_source_map();
+
+ let cp1 = sm.bytepos_to_file_charpos(BytePos(22));
+ assert_eq!(cp1, CharPos(22));
+
+ let cp2 = sm.bytepos_to_file_charpos(BytePos(25));
+ assert_eq!(cp2, CharPos(0));
+}
+
+/// Tests zero-length `SourceFile`s.
+#[test]
+fn t5() {
+ let sm = init_source_map();
+
+ let loc1 = sm.lookup_char_pos(BytePos(22));
+ assert_eq!(loc1.file.name, PathBuf::from("blork.rs").into());
+ assert_eq!(loc1.line, 2);
+ assert_eq!(loc1.col, CharPos(10));
+
+ let loc2 = sm.lookup_char_pos(BytePos(25));
+ assert_eq!(loc2.file.name, PathBuf::from("blork2.rs").into());
+ assert_eq!(loc2.line, 1);
+ assert_eq!(loc2.col, CharPos(0));
+}
+
+fn init_source_map_mbc() -> SourceMap {
+ let sm = SourceMap::new(FilePathMapping::empty());
+ // "€" is a three-byte UTF8 char.
+ sm.new_source_file(
+ PathBuf::from("blork.rs").into(),
+ "fir€st €€€€ line.\nsecond line".to_string(),
+ );
+ sm.new_source_file(
+ PathBuf::from("blork2.rs").into(),
+ "first line€€.\n€ second line".to_string(),
+ );
+ sm
+}
+
+/// Tests `bytepos_to_file_charpos` in the presence of multi-byte chars.
+#[test]
+fn t6() {
+ let sm = init_source_map_mbc();
+
+ let cp1 = sm.bytepos_to_file_charpos(BytePos(3));
+ assert_eq!(cp1, CharPos(3));
+
+ let cp2 = sm.bytepos_to_file_charpos(BytePos(6));
+ assert_eq!(cp2, CharPos(4));
+
+ let cp3 = sm.bytepos_to_file_charpos(BytePos(56));
+ assert_eq!(cp3, CharPos(12));
+
+ let cp4 = sm.bytepos_to_file_charpos(BytePos(61));
+ assert_eq!(cp4, CharPos(15));
+}
+
+/// Test `span_to_lines` for a span ending at the end of a `SourceFile`.
+#[test]
+fn t7() {
+ let sm = init_source_map();
+ let span = Span::with_root_ctxt(BytePos(12), BytePos(23));
+ let file_lines = sm.span_to_lines(span).unwrap();
+
+ assert_eq!(file_lines.file.name, PathBuf::from("blork.rs").into());
+ assert_eq!(file_lines.lines.len(), 1);
+ assert_eq!(file_lines.lines[0].line_index, 1);
+}
+
+/// Given a string like " ~~~~~~~~~~~~ ", produces a span
+/// converting that range. The idea is that the string has the same
+/// length as the input, and we uncover the byte positions. Note
+/// that this can span lines and so on.
+fn span_from_selection(input: &str, selection: &str) -> Span {
+ assert_eq!(input.len(), selection.len());
+ let left_index = selection.find('~').unwrap() as u32;
+ let right_index = selection.rfind('~').map_or(left_index, |x| x as u32);
+ Span::with_root_ctxt(BytePos(left_index), BytePos(right_index + 1))
+}
+
+/// Tests `span_to_snippet` and `span_to_lines` for a span converting 3
+/// lines in the middle of a file.
+#[test]
+fn span_to_snippet_and_lines_spanning_multiple_lines() {
+ let sm = SourceMap::new(FilePathMapping::empty());
+ let inputtext = "aaaaa\nbbbbBB\nCCC\nDDDDDddddd\neee\n";
+ let selection = " \n ~~\n~~~\n~~~~~ \n \n";
+ sm.new_source_file(Path::new("blork.rs").to_owned().into(), inputtext.to_string());
+ let span = span_from_selection(inputtext, selection);
+
+ // Check that we are extracting the text we thought we were extracting.
+ assert_eq!(&sm.span_to_snippet(span).unwrap(), "BB\nCCC\nDDDDD");
+
+ // Check that span_to_lines gives us the complete result with the lines/cols we expected.
+ let lines = sm.span_to_lines(span).unwrap();
+ let expected = vec![
+ LineInfo { line_index: 1, start_col: CharPos(4), end_col: CharPos(6) },
+ LineInfo { line_index: 2, start_col: CharPos(0), end_col: CharPos(3) },
+ LineInfo { line_index: 3, start_col: CharPos(0), end_col: CharPos(5) },
+ ];
+ assert_eq!(lines.lines, expected);
+}
+
+/// Test span_to_snippet for a span ending at the end of a `SourceFile`.
+#[test]
+fn t8() {
+ let sm = init_source_map();
+ let span = Span::with_root_ctxt(BytePos(12), BytePos(23));
+ let snippet = sm.span_to_snippet(span);
+
+ assert_eq!(snippet, Ok("second line".to_string()));
+}
+
+/// Test `span_to_str` for a span ending at the end of a `SourceFile`.
+#[test]
+fn t9() {
+ let sm = init_source_map();
+ let span = Span::with_root_ctxt(BytePos(12), BytePos(23));
+ let sstr = sm.span_to_diagnostic_string(span);
+
+ assert_eq!(sstr, "blork.rs:2:1: 2:12");
+}
+
+/// Tests failing to merge two spans on different lines.
+#[test]
+fn span_merging_fail() {
+ let sm = SourceMap::new(FilePathMapping::empty());
+ let inputtext = "bbbb BB\ncc CCC\n";
+ let selection1 = " ~~\n \n";
+ let selection2 = " \n ~~~\n";
+ sm.new_source_file(Path::new("blork.rs").to_owned().into(), inputtext.to_owned());
+ let span1 = span_from_selection(inputtext, selection1);
+ let span2 = span_from_selection(inputtext, selection2);
+
+ assert!(sm.merge_spans(span1, span2).is_none());
+}
+
+/// Tests loading an external source file that requires normalization.
+#[test]
+fn t10() {
+ let sm = SourceMap::new(FilePathMapping::empty());
+ let unnormalized = "first line.\r\nsecond line";
+ let normalized = "first line.\nsecond line";
+
+ let src_file = sm.new_source_file(PathBuf::from("blork.rs").into(), unnormalized.to_string());
+
+ assert_eq!(src_file.src.as_ref().unwrap().as_ref(), normalized);
+ assert!(
+ src_file.src_hash.matches(unnormalized),
+ "src_hash should use the source before normalization"
+ );
+
+ let SourceFile {
+ name,
+ src_hash,
+ start_pos,
+ end_pos,
+ lines,
+ multibyte_chars,
+ non_narrow_chars,
+ normalized_pos,
+ name_hash,
+ ..
+ } = (*src_file).clone();
+
+ let imported_src_file = sm.new_imported_source_file(
+ name,
+ src_hash,
+ name_hash,
+ (end_pos - start_pos).to_usize(),
+ CrateNum::new(0),
+ lines,
+ multibyte_chars,
+ non_narrow_chars,
+ normalized_pos,
+ start_pos,
+ end_pos,
+ );
+
+ assert!(
+ imported_src_file.external_src.borrow().get_source().is_none(),
+ "imported source file should not have source yet"
+ );
+ imported_src_file.add_external_src(|| Some(unnormalized.to_string()));
+ assert_eq!(
+ imported_src_file.external_src.borrow().get_source().unwrap().as_ref(),
+ normalized,
+ "imported source file should be normalized"
+ );
+}
+
+/// Returns the span corresponding to the `n`th occurrence of `substring` in `source_text`.
+trait SourceMapExtension {
+ fn span_substr(
+ &self,
+ file: &Lrc<SourceFile>,
+ source_text: &str,
+ substring: &str,
+ n: usize,
+ ) -> Span;
+}
+
+impl SourceMapExtension for SourceMap {
+ fn span_substr(
+ &self,
+ file: &Lrc<SourceFile>,
+ source_text: &str,
+ substring: &str,
+ n: usize,
+ ) -> Span {
+ eprintln!(
+ "span_substr(file={:?}/{:?}, substring={:?}, n={})",
+ file.name, file.start_pos, substring, n
+ );
+ let mut i = 0;
+ let mut hi = 0;
+ loop {
+ let offset = source_text[hi..].find(substring).unwrap_or_else(|| {
+ panic!(
+ "source_text `{}` does not have {} occurrences of `{}`, only {}",
+ source_text, n, substring, i
+ );
+ });
+ let lo = hi + offset;
+ hi = lo + substring.len();
+ if i == n {
+ let span = Span::with_root_ctxt(
+ BytePos(lo as u32 + file.start_pos.0),
+ BytePos(hi as u32 + file.start_pos.0),
+ );
+ assert_eq!(&self.span_to_snippet(span).unwrap()[..], substring);
+ return span;
+ }
+ i += 1;
+ }
+ }
+}
+
+// Takes a unix-style path and returns a platform specific path.
+fn path(p: &str) -> PathBuf {
+ path_str(p).into()
+}
+
+// Takes a unix-style path and returns a platform specific path.
+fn path_str(p: &str) -> String {
+ #[cfg(not(windows))]
+ {
+ return p.into();
+ }
+
+ #[cfg(windows)]
+ {
+ let mut path = p.replace('/', "\\");
+ if let Some(rest) = path.strip_prefix('\\') {
+ path = ["X:\\", rest].concat();
+ }
+
+ path
+ }
+}
+
+fn map_path_prefix(mapping: &FilePathMapping, p: &str) -> String {
+ // It's important that we convert to a string here because that's what
+ // later stages do too (e.g. in the backend), and comparing `Path` values
+ // won't catch some differences at the string level, e.g. "abc" and "abc/"
+ // compare as equal.
+ mapping.map_prefix(path(p)).0.to_string_lossy().to_string()
+}
+
+#[test]
+fn path_prefix_remapping() {
+ // Relative to relative
+ {
+ let mapping = &FilePathMapping::new(vec![(path("abc/def"), path("foo"))]);
+
+ assert_eq!(map_path_prefix(mapping, "abc/def/src/main.rs"), path_str("foo/src/main.rs"));
+ assert_eq!(map_path_prefix(mapping, "abc/def"), path_str("foo"));
+ }
+
+ // Relative to absolute
+ {
+ let mapping = &FilePathMapping::new(vec![(path("abc/def"), path("/foo"))]);
+
+ assert_eq!(map_path_prefix(mapping, "abc/def/src/main.rs"), path_str("/foo/src/main.rs"));
+ assert_eq!(map_path_prefix(mapping, "abc/def"), path_str("/foo"));
+ }
+
+ // Absolute to relative
+ {
+ let mapping = &FilePathMapping::new(vec![(path("/abc/def"), path("foo"))]);
+
+ assert_eq!(map_path_prefix(mapping, "/abc/def/src/main.rs"), path_str("foo/src/main.rs"));
+ assert_eq!(map_path_prefix(mapping, "/abc/def"), path_str("foo"));
+ }
+
+ // Absolute to absolute
+ {
+ let mapping = &FilePathMapping::new(vec![(path("/abc/def"), path("/foo"))]);
+
+ assert_eq!(map_path_prefix(mapping, "/abc/def/src/main.rs"), path_str("/foo/src/main.rs"));
+ assert_eq!(map_path_prefix(mapping, "/abc/def"), path_str("/foo"));
+ }
+}
+
+#[test]
+fn path_prefix_remapping_expand_to_absolute() {
+ // "virtual" working directory is relative path
+ let mapping =
+ &FilePathMapping::new(vec![(path("/foo"), path("FOO")), (path("/bar"), path("BAR"))]);
+ let working_directory = path("/foo");
+ let working_directory = RealFileName::Remapped {
+ local_path: Some(working_directory.clone()),
+ virtual_name: mapping.map_prefix(working_directory).0,
+ };
+
+ assert_eq!(working_directory.remapped_path_if_available(), path("FOO"));
+
+ // Unmapped absolute path
+ assert_eq!(
+ mapping.to_embeddable_absolute_path(
+ RealFileName::LocalPath(path("/foo/src/main.rs")),
+ &working_directory
+ ),
+ RealFileName::Remapped { local_path: None, virtual_name: path("FOO/src/main.rs") }
+ );
+
+ // Unmapped absolute path with unrelated working directory
+ assert_eq!(
+ mapping.to_embeddable_absolute_path(
+ RealFileName::LocalPath(path("/bar/src/main.rs")),
+ &working_directory
+ ),
+ RealFileName::Remapped { local_path: None, virtual_name: path("BAR/src/main.rs") }
+ );
+
+ // Unmapped absolute path that does not match any prefix
+ assert_eq!(
+ mapping.to_embeddable_absolute_path(
+ RealFileName::LocalPath(path("/quux/src/main.rs")),
+ &working_directory
+ ),
+ RealFileName::LocalPath(path("/quux/src/main.rs")),
+ );
+
+ // Unmapped relative path
+ assert_eq!(
+ mapping.to_embeddable_absolute_path(
+ RealFileName::LocalPath(path("src/main.rs")),
+ &working_directory
+ ),
+ RealFileName::Remapped { local_path: None, virtual_name: path("FOO/src/main.rs") }
+ );
+
+ // Unmapped relative path with `./`
+ assert_eq!(
+ mapping.to_embeddable_absolute_path(
+ RealFileName::LocalPath(path("./src/main.rs")),
+ &working_directory
+ ),
+ RealFileName::Remapped { local_path: None, virtual_name: path("FOO/src/main.rs") }
+ );
+
+ // Unmapped relative path that does not match any prefix
+ assert_eq!(
+ mapping.to_embeddable_absolute_path(
+ RealFileName::LocalPath(path("quux/src/main.rs")),
+ &RealFileName::LocalPath(path("/abc")),
+ ),
+ RealFileName::LocalPath(path("/abc/quux/src/main.rs")),
+ );
+
+ // Already remapped absolute path
+ assert_eq!(
+ mapping.to_embeddable_absolute_path(
+ RealFileName::Remapped {
+ local_path: Some(path("/foo/src/main.rs")),
+ virtual_name: path("FOO/src/main.rs"),
+ },
+ &working_directory
+ ),
+ RealFileName::Remapped { local_path: None, virtual_name: path("FOO/src/main.rs") }
+ );
+
+ // Already remapped absolute path, with unrelated working directory
+ assert_eq!(
+ mapping.to_embeddable_absolute_path(
+ RealFileName::Remapped {
+ local_path: Some(path("/bar/src/main.rs")),
+ virtual_name: path("BAR/src/main.rs"),
+ },
+ &working_directory
+ ),
+ RealFileName::Remapped { local_path: None, virtual_name: path("BAR/src/main.rs") }
+ );
+
+ // Already remapped relative path
+ assert_eq!(
+ mapping.to_embeddable_absolute_path(
+ RealFileName::Remapped { local_path: None, virtual_name: path("XYZ/src/main.rs") },
+ &working_directory
+ ),
+ RealFileName::Remapped { local_path: None, virtual_name: path("XYZ/src/main.rs") }
+ );
+}