3 use rustc_data_structures::sync::Lrc;
5 fn init_source_map() -> SourceMap {
6 let sm = SourceMap::new(FilePathMapping::empty());
7 sm.new_source_file(PathBuf::from("blork.rs").into(), "first line.\nsecond line".to_string());
8 sm.new_source_file(PathBuf::from("empty.rs").into(), String::new());
9 sm.new_source_file(PathBuf::from("blork2.rs").into(), "first line.\nsecond line".to_string());
14 /// Returns `Some(span)`, a union of the LHS and RHS span. The LHS must precede the RHS. If
15 /// there are gaps between LHS and RHS, the resulting union will cross these gaps.
18 /// * the syntax contexts of both spans much match,
19 /// * the LHS span needs to end on the same line the RHS span begins,
20 /// * the LHS span must start at or before the RHS span.
21 fn merge_spans(&self, sp_lhs: Span, sp_rhs: Span) -> Option<Span> {
22 // Ensure we're at the same expansion ID.
23 if sp_lhs.ctxt() != sp_rhs.ctxt() {
27 let lhs_end = match self.lookup_line(sp_lhs.hi()) {
29 Err(_) => return None,
31 let rhs_begin = match self.lookup_line(sp_rhs.lo()) {
33 Err(_) => return None,
36 // If we must cross lines to merge, don't merge.
37 if lhs_end.line != rhs_begin.line {
41 // Ensure these follow the expected order and that we don't overlap.
42 if (sp_lhs.lo() <= sp_rhs.lo()) && (sp_lhs.hi() <= sp_rhs.lo()) {
43 Some(sp_lhs.to(sp_rhs))
49 /// Converts an absolute `BytePos` to a `CharPos` relative to the `SourceFile`.
50 fn bytepos_to_file_charpos(&self, bpos: BytePos) -> CharPos {
51 let idx = self.lookup_source_file_idx(bpos);
52 let sf = &(*self.files.borrow().source_files)[idx];
53 sf.bytepos_to_file_charpos(bpos)
57 /// Tests `lookup_byte_offset`.
60 let sm = init_source_map();
62 let srcfbp1 = sm.lookup_byte_offset(BytePos(23));
63 assert_eq!(srcfbp1.sf.name, PathBuf::from("blork.rs").into());
64 assert_eq!(srcfbp1.pos, BytePos(23));
66 let srcfbp1 = sm.lookup_byte_offset(BytePos(24));
67 assert_eq!(srcfbp1.sf.name, PathBuf::from("empty.rs").into());
68 assert_eq!(srcfbp1.pos, BytePos(0));
70 let srcfbp2 = sm.lookup_byte_offset(BytePos(25));
71 assert_eq!(srcfbp2.sf.name, PathBuf::from("blork2.rs").into());
72 assert_eq!(srcfbp2.pos, BytePos(0));
75 /// Tests `bytepos_to_file_charpos`.
78 let sm = init_source_map();
80 let cp1 = sm.bytepos_to_file_charpos(BytePos(22));
81 assert_eq!(cp1, CharPos(22));
83 let cp2 = sm.bytepos_to_file_charpos(BytePos(25));
84 assert_eq!(cp2, CharPos(0));
87 /// Tests zero-length `SourceFile`s.
90 let sm = init_source_map();
92 let loc1 = sm.lookup_char_pos(BytePos(22));
93 assert_eq!(loc1.file.name, PathBuf::from("blork.rs").into());
94 assert_eq!(loc1.line, 2);
95 assert_eq!(loc1.col, CharPos(10));
97 let loc2 = sm.lookup_char_pos(BytePos(25));
98 assert_eq!(loc2.file.name, PathBuf::from("blork2.rs").into());
99 assert_eq!(loc2.line, 1);
100 assert_eq!(loc2.col, CharPos(0));
103 fn init_source_map_mbc() -> SourceMap {
104 let sm = SourceMap::new(FilePathMapping::empty());
105 // "€" is a three-byte UTF8 char.
107 PathBuf::from("blork.rs").into(),
108 "fir€st €€€€ line.\nsecond line".to_string(),
111 PathBuf::from("blork2.rs").into(),
112 "first line€€.\n€ second line".to_string(),
117 /// Tests `bytepos_to_file_charpos` in the presence of multi-byte chars.
120 let sm = init_source_map_mbc();
122 let cp1 = sm.bytepos_to_file_charpos(BytePos(3));
123 assert_eq!(cp1, CharPos(3));
125 let cp2 = sm.bytepos_to_file_charpos(BytePos(6));
126 assert_eq!(cp2, CharPos(4));
128 let cp3 = sm.bytepos_to_file_charpos(BytePos(56));
129 assert_eq!(cp3, CharPos(12));
131 let cp4 = sm.bytepos_to_file_charpos(BytePos(61));
132 assert_eq!(cp4, CharPos(15));
135 /// Test `span_to_lines` for a span ending at the end of a `SourceFile`.
138 let sm = init_source_map();
139 let span = Span::with_root_ctxt(BytePos(12), BytePos(23));
140 let file_lines = sm.span_to_lines(span).unwrap();
142 assert_eq!(file_lines.file.name, PathBuf::from("blork.rs").into());
143 assert_eq!(file_lines.lines.len(), 1);
144 assert_eq!(file_lines.lines[0].line_index, 1);
147 /// Given a string like " ~~~~~~~~~~~~ ", produces a span
148 /// converting that range. The idea is that the string has the same
149 /// length as the input, and we uncover the byte positions. Note
150 /// that this can span lines and so on.
151 fn span_from_selection(input: &str, selection: &str) -> Span {
152 assert_eq!(input.len(), selection.len());
153 let left_index = selection.find('~').unwrap() as u32;
154 let right_index = selection.rfind('~').map_or(left_index, |x| x as u32);
155 Span::with_root_ctxt(BytePos(left_index), BytePos(right_index + 1))
158 /// Tests `span_to_snippet` and `span_to_lines` for a span converting 3
159 /// lines in the middle of a file.
161 fn span_to_snippet_and_lines_spanning_multiple_lines() {
162 let sm = SourceMap::new(FilePathMapping::empty());
163 let inputtext = "aaaaa\nbbbbBB\nCCC\nDDDDDddddd\neee\n";
164 let selection = " \n ~~\n~~~\n~~~~~ \n \n";
165 sm.new_source_file(Path::new("blork.rs").to_owned().into(), inputtext.to_string());
166 let span = span_from_selection(inputtext, selection);
168 // Check that we are extracting the text we thought we were extracting.
169 assert_eq!(&sm.span_to_snippet(span).unwrap(), "BB\nCCC\nDDDDD");
171 // Check that span_to_lines gives us the complete result with the lines/cols we expected.
172 let lines = sm.span_to_lines(span).unwrap();
174 LineInfo { line_index: 1, start_col: CharPos(4), end_col: CharPos(6) },
175 LineInfo { line_index: 2, start_col: CharPos(0), end_col: CharPos(3) },
176 LineInfo { line_index: 3, start_col: CharPos(0), end_col: CharPos(5) },
178 assert_eq!(lines.lines, expected);
181 /// Test span_to_snippet for a span ending at the end of a `SourceFile`.
184 let sm = init_source_map();
185 let span = Span::with_root_ctxt(BytePos(12), BytePos(23));
186 let snippet = sm.span_to_snippet(span);
188 assert_eq!(snippet, Ok("second line".to_string()));
191 /// Test `span_to_str` for a span ending at the end of a `SourceFile`.
194 let sm = init_source_map();
195 let span = Span::with_root_ctxt(BytePos(12), BytePos(23));
196 let sstr = sm.span_to_diagnostic_string(span);
198 assert_eq!(sstr, "blork.rs:2:1: 2:12");
201 /// Tests failing to merge two spans on different lines.
203 fn span_merging_fail() {
204 let sm = SourceMap::new(FilePathMapping::empty());
205 let inputtext = "bbbb BB\ncc CCC\n";
206 let selection1 = " ~~\n \n";
207 let selection2 = " \n ~~~\n";
208 sm.new_source_file(Path::new("blork.rs").to_owned().into(), inputtext.to_owned());
209 let span1 = span_from_selection(inputtext, selection1);
210 let span2 = span_from_selection(inputtext, selection2);
212 assert!(sm.merge_spans(span1, span2).is_none());
215 /// Tests loading an external source file that requires normalization.
218 let sm = SourceMap::new(FilePathMapping::empty());
219 let unnormalized = "first line.\r\nsecond line";
220 let normalized = "first line.\nsecond line";
222 let src_file = sm.new_source_file(PathBuf::from("blork.rs").into(), unnormalized.to_string());
224 assert_eq!(src_file.src.as_ref().unwrap().as_ref(), normalized);
226 src_file.src_hash.matches(unnormalized),
227 "src_hash should use the source before normalization"
241 } = (*src_file).clone();
243 let imported_src_file = sm.new_imported_source_file(
247 (end_pos - start_pos).to_usize(),
258 imported_src_file.external_src.borrow().get_source().is_none(),
259 "imported source file should not have source yet"
261 imported_src_file.add_external_src(|| Some(unnormalized.to_string()));
263 imported_src_file.external_src.borrow().get_source().unwrap().as_ref(),
265 "imported source file should be normalized"
269 /// Returns the span corresponding to the `n`th occurrence of `substring` in `source_text`.
270 trait SourceMapExtension {
273 file: &Lrc<SourceFile>,
280 impl SourceMapExtension for SourceMap {
283 file: &Lrc<SourceFile>,
289 "span_substr(file={:?}/{:?}, substring={:?}, n={})",
290 file.name, file.start_pos, substring, n
295 let offset = source_text[hi..].find(substring).unwrap_or_else(|| {
297 "source_text `{}` does not have {} occurrences of `{}`, only {}",
298 source_text, n, substring, i
301 let lo = hi + offset;
302 hi = lo + substring.len();
304 let span = Span::with_root_ctxt(
305 BytePos(lo as u32 + file.start_pos.0),
306 BytePos(hi as u32 + file.start_pos.0),
308 assert_eq!(&self.span_to_snippet(span).unwrap()[..], substring);