]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_span/src/source_map/tests.rs
Rollup merge of #99588 - ehuss:update-books, r=ehuss
[rust.git] / compiler / rustc_span / src / source_map / tests.rs
1 use super::*;
2
3 use rustc_data_structures::sync::Lrc;
4
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());
10     sm
11 }
12
13 impl SourceMap {
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.
16     /// For this to work,
17     ///
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() {
24             return None;
25         }
26
27         let lhs_end = match self.lookup_line(sp_lhs.hi()) {
28             Ok(x) => x,
29             Err(_) => return None,
30         };
31         let rhs_begin = match self.lookup_line(sp_rhs.lo()) {
32             Ok(x) => x,
33             Err(_) => return None,
34         };
35
36         // If we must cross lines to merge, don't merge.
37         if lhs_end.line != rhs_begin.line {
38             return None;
39         }
40
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))
44         } else {
45             None
46         }
47     }
48
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)
54     }
55 }
56
57 /// Tests `lookup_byte_offset`.
58 #[test]
59 fn t3() {
60     let sm = init_source_map();
61
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));
65
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));
69
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));
73 }
74
75 /// Tests `bytepos_to_file_charpos`.
76 #[test]
77 fn t4() {
78     let sm = init_source_map();
79
80     let cp1 = sm.bytepos_to_file_charpos(BytePos(22));
81     assert_eq!(cp1, CharPos(22));
82
83     let cp2 = sm.bytepos_to_file_charpos(BytePos(25));
84     assert_eq!(cp2, CharPos(0));
85 }
86
87 /// Tests zero-length `SourceFile`s.
88 #[test]
89 fn t5() {
90     let sm = init_source_map();
91
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));
96
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));
101 }
102
103 fn init_source_map_mbc() -> SourceMap {
104     let sm = SourceMap::new(FilePathMapping::empty());
105     // "€" is a three-byte UTF8 char.
106     sm.new_source_file(
107         PathBuf::from("blork.rs").into(),
108         "fir€st €€€€ line.\nsecond line".to_string(),
109     );
110     sm.new_source_file(
111         PathBuf::from("blork2.rs").into(),
112         "first line€€.\n€ second line".to_string(),
113     );
114     sm
115 }
116
117 /// Tests `bytepos_to_file_charpos` in the presence of multi-byte chars.
118 #[test]
119 fn t6() {
120     let sm = init_source_map_mbc();
121
122     let cp1 = sm.bytepos_to_file_charpos(BytePos(3));
123     assert_eq!(cp1, CharPos(3));
124
125     let cp2 = sm.bytepos_to_file_charpos(BytePos(6));
126     assert_eq!(cp2, CharPos(4));
127
128     let cp3 = sm.bytepos_to_file_charpos(BytePos(56));
129     assert_eq!(cp3, CharPos(12));
130
131     let cp4 = sm.bytepos_to_file_charpos(BytePos(61));
132     assert_eq!(cp4, CharPos(15));
133 }
134
135 /// Test `span_to_lines` for a span ending at the end of a `SourceFile`.
136 #[test]
137 fn t7() {
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();
141
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);
145 }
146
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))
156 }
157
158 /// Tests `span_to_snippet` and `span_to_lines` for a span converting 3
159 /// lines in the middle of a file.
160 #[test]
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);
167
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");
170
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();
173     let expected = vec![
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) },
177     ];
178     assert_eq!(lines.lines, expected);
179 }
180
181 /// Test span_to_snippet for a span ending at the end of a `SourceFile`.
182 #[test]
183 fn t8() {
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);
187
188     assert_eq!(snippet, Ok("second line".to_string()));
189 }
190
191 /// Test `span_to_str` for a span ending at the end of a `SourceFile`.
192 #[test]
193 fn t9() {
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);
197
198     assert_eq!(sstr, "blork.rs:2:1: 2:12");
199 }
200
201 /// Tests failing to merge two spans on different lines.
202 #[test]
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);
211
212     assert!(sm.merge_spans(span1, span2).is_none());
213 }
214
215 /// Tests loading an external source file that requires normalization.
216 #[test]
217 fn t10() {
218     let sm = SourceMap::new(FilePathMapping::empty());
219     let unnormalized = "first line.\r\nsecond line";
220     let normalized = "first line.\nsecond line";
221
222     let src_file = sm.new_source_file(PathBuf::from("blork.rs").into(), unnormalized.to_string());
223
224     assert_eq!(src_file.src.as_ref().unwrap().as_ref(), normalized);
225     assert!(
226         src_file.src_hash.matches(unnormalized),
227         "src_hash should use the source before normalization"
228     );
229
230     let SourceFile {
231         name,
232         src_hash,
233         start_pos,
234         end_pos,
235         lines,
236         multibyte_chars,
237         non_narrow_chars,
238         normalized_pos,
239         name_hash,
240         ..
241     } = (*src_file).clone();
242
243     let imported_src_file = sm.new_imported_source_file(
244         name,
245         src_hash,
246         name_hash,
247         (end_pos - start_pos).to_usize(),
248         CrateNum::new(0),
249         lines,
250         multibyte_chars,
251         non_narrow_chars,
252         normalized_pos,
253         start_pos,
254         end_pos,
255     );
256
257     assert!(
258         imported_src_file.external_src.borrow().get_source().is_none(),
259         "imported source file should not have source yet"
260     );
261     imported_src_file.add_external_src(|| Some(unnormalized.to_string()));
262     assert_eq!(
263         imported_src_file.external_src.borrow().get_source().unwrap().as_ref(),
264         normalized,
265         "imported source file should be normalized"
266     );
267 }
268
269 /// Returns the span corresponding to the `n`th occurrence of `substring` in `source_text`.
270 trait SourceMapExtension {
271     fn span_substr(
272         &self,
273         file: &Lrc<SourceFile>,
274         source_text: &str,
275         substring: &str,
276         n: usize,
277     ) -> Span;
278 }
279
280 impl SourceMapExtension for SourceMap {
281     fn span_substr(
282         &self,
283         file: &Lrc<SourceFile>,
284         source_text: &str,
285         substring: &str,
286         n: usize,
287     ) -> Span {
288         eprintln!(
289             "span_substr(file={:?}/{:?}, substring={:?}, n={})",
290             file.name, file.start_pos, substring, n
291         );
292         let mut i = 0;
293         let mut hi = 0;
294         loop {
295             let offset = source_text[hi..].find(substring).unwrap_or_else(|| {
296                 panic!(
297                     "source_text `{}` does not have {} occurrences of `{}`, only {}",
298                     source_text, n, substring, i
299                 );
300             });
301             let lo = hi + offset;
302             hi = lo + substring.len();
303             if i == n {
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),
307                 );
308                 assert_eq!(&self.span_to_snippet(span).unwrap()[..], substring);
309                 return span;
310             }
311             i += 1;
312         }
313     }
314 }
315
316 // Takes a unix-style path and returns a platform specific path.
317 fn path(p: &str) -> PathBuf {
318     path_str(p).into()
319 }
320
321 // Takes a unix-style path and returns a platform specific path.
322 fn path_str(p: &str) -> String {
323     #[cfg(not(windows))]
324     {
325         return p.into();
326     }
327
328     #[cfg(windows)]
329     {
330         let mut path = p.replace('/', "\\");
331         if let Some(rest) = path.strip_prefix('\\') {
332             path = ["X:\\", rest].concat();
333         }
334
335         path
336     }
337 }
338
339 fn map_path_prefix(mapping: &FilePathMapping, p: &str) -> String {
340     // It's important that we convert to a string here because that's what
341     // later stages do too (e.g. in the backend), and comparing `Path` values
342     // won't catch some differences at the string level, e.g. "abc" and "abc/"
343     // compare as equal.
344     mapping.map_prefix(path(p)).0.to_string_lossy().to_string()
345 }
346
347 #[test]
348 fn path_prefix_remapping() {
349     // Relative to relative
350     {
351         let mapping = &FilePathMapping::new(vec![(path("abc/def"), path("foo"))]);
352
353         assert_eq!(map_path_prefix(mapping, "abc/def/src/main.rs"), path_str("foo/src/main.rs"));
354         assert_eq!(map_path_prefix(mapping, "abc/def"), path_str("foo"));
355     }
356
357     // Relative to absolute
358     {
359         let mapping = &FilePathMapping::new(vec![(path("abc/def"), path("/foo"))]);
360
361         assert_eq!(map_path_prefix(mapping, "abc/def/src/main.rs"), path_str("/foo/src/main.rs"));
362         assert_eq!(map_path_prefix(mapping, "abc/def"), path_str("/foo"));
363     }
364
365     // Absolute to relative
366     {
367         let mapping = &FilePathMapping::new(vec![(path("/abc/def"), path("foo"))]);
368
369         assert_eq!(map_path_prefix(mapping, "/abc/def/src/main.rs"), path_str("foo/src/main.rs"));
370         assert_eq!(map_path_prefix(mapping, "/abc/def"), path_str("foo"));
371     }
372
373     // Absolute to absolute
374     {
375         let mapping = &FilePathMapping::new(vec![(path("/abc/def"), path("/foo"))]);
376
377         assert_eq!(map_path_prefix(mapping, "/abc/def/src/main.rs"), path_str("/foo/src/main.rs"));
378         assert_eq!(map_path_prefix(mapping, "/abc/def"), path_str("/foo"));
379     }
380 }
381
382 #[test]
383 fn path_prefix_remapping_expand_to_absolute() {
384     // "virtual" working directory is relative path
385     let mapping =
386         &FilePathMapping::new(vec![(path("/foo"), path("FOO")), (path("/bar"), path("BAR"))]);
387     let working_directory = path("/foo");
388     let working_directory = RealFileName::Remapped {
389         local_path: Some(working_directory.clone()),
390         virtual_name: mapping.map_prefix(working_directory).0,
391     };
392
393     assert_eq!(working_directory.remapped_path_if_available(), path("FOO"));
394
395     // Unmapped absolute path
396     assert_eq!(
397         mapping.to_embeddable_absolute_path(
398             RealFileName::LocalPath(path("/foo/src/main.rs")),
399             &working_directory
400         ),
401         RealFileName::Remapped { local_path: None, virtual_name: path("FOO/src/main.rs") }
402     );
403
404     // Unmapped absolute path with unrelated working directory
405     assert_eq!(
406         mapping.to_embeddable_absolute_path(
407             RealFileName::LocalPath(path("/bar/src/main.rs")),
408             &working_directory
409         ),
410         RealFileName::Remapped { local_path: None, virtual_name: path("BAR/src/main.rs") }
411     );
412
413     // Unmapped absolute path that does not match any prefix
414     assert_eq!(
415         mapping.to_embeddable_absolute_path(
416             RealFileName::LocalPath(path("/quux/src/main.rs")),
417             &working_directory
418         ),
419         RealFileName::LocalPath(path("/quux/src/main.rs")),
420     );
421
422     // Unmapped relative path
423     assert_eq!(
424         mapping.to_embeddable_absolute_path(
425             RealFileName::LocalPath(path("src/main.rs")),
426             &working_directory
427         ),
428         RealFileName::Remapped { local_path: None, virtual_name: path("FOO/src/main.rs") }
429     );
430
431     // Unmapped relative path with `./`
432     assert_eq!(
433         mapping.to_embeddable_absolute_path(
434             RealFileName::LocalPath(path("./src/main.rs")),
435             &working_directory
436         ),
437         RealFileName::Remapped { local_path: None, virtual_name: path("FOO/src/main.rs") }
438     );
439
440     // Unmapped relative path that does not match any prefix
441     assert_eq!(
442         mapping.to_embeddable_absolute_path(
443             RealFileName::LocalPath(path("quux/src/main.rs")),
444             &RealFileName::LocalPath(path("/abc")),
445         ),
446         RealFileName::LocalPath(path("/abc/quux/src/main.rs")),
447     );
448
449     // Already remapped absolute path
450     assert_eq!(
451         mapping.to_embeddable_absolute_path(
452             RealFileName::Remapped {
453                 local_path: Some(path("/foo/src/main.rs")),
454                 virtual_name: path("FOO/src/main.rs"),
455             },
456             &working_directory
457         ),
458         RealFileName::Remapped { local_path: None, virtual_name: path("FOO/src/main.rs") }
459     );
460
461     // Already remapped absolute path, with unrelated working directory
462     assert_eq!(
463         mapping.to_embeddable_absolute_path(
464             RealFileName::Remapped {
465                 local_path: Some(path("/bar/src/main.rs")),
466                 virtual_name: path("BAR/src/main.rs"),
467             },
468             &working_directory
469         ),
470         RealFileName::Remapped { local_path: None, virtual_name: path("BAR/src/main.rs") }
471     );
472
473     // Already remapped relative path
474     assert_eq!(
475         mapping.to_embeddable_absolute_path(
476             RealFileName::Remapped { local_path: None, virtual_name: path("XYZ/src/main.rs") },
477             &working_directory
478         ),
479         RealFileName::Remapped { local_path: None, virtual_name: path("XYZ/src/main.rs") }
480     );
481 }