]> git.proxmox.com Git - rustc.git/blame - compiler/rustc_span/src/source_map/tests.rs
New upstream version 1.68.2+dfsg1
[rustc.git] / compiler / rustc_span / src / source_map / tests.rs
CommitLineData
416331ca
XL
1use super::*;
2
3use rustc_data_structures::sync::Lrc;
4
5fn init_source_map() -> SourceMap {
6 let sm = SourceMap::new(FilePathMapping::empty());
dfeec247
XL
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());
416331ca
XL
10 sm
11}
12
6a06907d
XL
13impl 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
e1599b0c 57/// Tests `lookup_byte_offset`.
416331ca
XL
58#[test]
59fn t3() {
416331ca
XL
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
e1599b0c 75/// Tests `bytepos_to_file_charpos`.
416331ca
XL
76#[test]
77fn t4() {
416331ca
XL
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
e1599b0c 87/// Tests zero-length `SourceFile`s.
416331ca
XL
88#[test]
89fn t5() {
416331ca
XL
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
103fn init_source_map_mbc() -> SourceMap {
104 let sm = SourceMap::new(FilePathMapping::empty());
e1599b0c 105 // "€" is a three-byte UTF8 char.
dfeec247
XL
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 );
416331ca
XL
114 sm
115}
116
e1599b0c 117/// Tests `bytepos_to_file_charpos` in the presence of multi-byte chars.
416331ca
XL
118#[test]
119fn t6() {
416331ca
XL
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
e1599b0c 135/// Test `span_to_lines` for a span ending at the end of a `SourceFile`.
416331ca
XL
136#[test]
137fn t7() {
416331ca 138 let sm = init_source_map();
e1599b0c 139 let span = Span::with_root_ctxt(BytePos(12), BytePos(23));
416331ca
XL
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.
151fn span_from_selection(input: &str, selection: &str) -> Span {
152 assert_eq!(input.len(), selection.len());
153 let left_index = selection.find('~').unwrap() as u32;
5869c6ff 154 let right_index = selection.rfind('~').map_or(left_index, |x| x as u32);
e1599b0c 155 Span::with_root_ctxt(BytePos(left_index), BytePos(right_index + 1))
416331ca
XL
156}
157
e1599b0c 158/// Tests `span_to_snippet` and `span_to_lines` for a span converting 3
416331ca
XL
159/// lines in the middle of a file.
160#[test]
161fn 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
e1599b0c 168 // Check that we are extracting the text we thought we were extracting.
416331ca
XL
169 assert_eq!(&sm.span_to_snippet(span).unwrap(), "BB\nCCC\nDDDDD");
170
e1599b0c 171 // Check that span_to_lines gives us the complete result with the lines/cols we expected.
416331ca
XL
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) },
dfeec247
XL
176 LineInfo { line_index: 3, start_col: CharPos(0), end_col: CharPos(5) },
177 ];
416331ca
XL
178 assert_eq!(lines.lines, expected);
179}
180
e1599b0c 181/// Test span_to_snippet for a span ending at the end of a `SourceFile`.
416331ca
XL
182#[test]
183fn t8() {
416331ca 184 let sm = init_source_map();
e1599b0c 185 let span = Span::with_root_ctxt(BytePos(12), BytePos(23));
416331ca
XL
186 let snippet = sm.span_to_snippet(span);
187
188 assert_eq!(snippet, Ok("second line".to_string()));
189}
190
e1599b0c 191/// Test `span_to_str` for a span ending at the end of a `SourceFile`.
416331ca
XL
192#[test]
193fn t9() {
416331ca 194 let sm = init_source_map();
e1599b0c 195 let span = Span::with_root_ctxt(BytePos(12), BytePos(23));
17df50a5 196 let sstr = sm.span_to_diagnostic_string(span);
416331ca
XL
197
198 assert_eq!(sstr, "blork.rs:2:1: 2:12");
199}
200
e1599b0c 201/// Tests failing to merge two spans on different lines.
416331ca
XL
202#[test]
203fn span_merging_fail() {
204 let sm = SourceMap::new(FilePathMapping::empty());
dfeec247 205 let inputtext = "bbbb BB\ncc CCC\n";
416331ca
XL
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
ba9703b0
XL
215/// Tests loading an external source file that requires normalization.
216#[test]
217fn 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,
ba9703b0
XL
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,
ba9703b0
XL
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,
f2b60f7d 254 0,
ba9703b0
XL
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
e1599b0c 269/// Returns the span corresponding to the `n`th occurrence of `substring` in `source_text`.
416331ca 270trait SourceMapExtension {
e1599b0c
XL
271 fn span_substr(
272 &self,
273 file: &Lrc<SourceFile>,
274 source_text: &str,
275 substring: &str,
276 n: usize,
277 ) -> Span;
416331ca
XL
278}
279
280impl SourceMapExtension for SourceMap {
e1599b0c
XL
281 fn span_substr(
282 &self,
283 file: &Lrc<SourceFile>,
284 source_text: &str,
285 substring: &str,
286 n: usize,
287 ) -> Span {
6a06907d 288 eprintln!(
e1599b0c
XL
289 "span_substr(file={:?}/{:?}, substring={:?}, n={})",
290 file.name, file.start_pos, substring, n
291 );
416331ca
XL
292 let mut i = 0;
293 let mut hi = 0;
294 loop {
295 let offset = source_text[hi..].find(substring).unwrap_or_else(|| {
e1599b0c
XL
296 panic!(
297 "source_text `{}` does not have {} occurrences of `{}`, only {}",
298 source_text, n, substring, i
299 );
416331ca
XL
300 });
301 let lo = hi + offset;
302 hi = lo + substring.len();
303 if i == n {
e1599b0c 304 let span = Span::with_root_ctxt(
416331ca
XL
305 BytePos(lo as u32 + file.start_pos.0),
306 BytePos(hi as u32 + file.start_pos.0),
416331ca 307 );
e1599b0c 308 assert_eq!(&self.span_to_snippet(span).unwrap()[..], substring);
416331ca
XL
309 return span;
310 }
311 i += 1;
312 }
313 }
314}
04454e1e 315
923072b8
FG
316// Takes a unix-style path and returns a platform specific path.
317fn path(p: &str) -> PathBuf {
318 path_str(p).into()
319}
320
321// Takes a unix-style path and returns a platform specific path.
322fn 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
339fn map_path_prefix(mapping: &FilePathMapping, p: &str) -> String {
04454e1e
FG
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.
923072b8 344 mapping.map_prefix(path(p)).0.to_string_lossy().to_string()
04454e1e
FG
345}
346
9c376795
FG
347fn reverse_map_prefix(mapping: &FilePathMapping, p: &str) -> Option<String> {
348 mapping.reverse_map_prefix_heuristically(&path(p)).map(|q| q.to_string_lossy().to_string())
349}
350
04454e1e
FG
351#[test]
352fn path_prefix_remapping() {
353 // Relative to relative
354 {
923072b8 355 let mapping = &FilePathMapping::new(vec![(path("abc/def"), path("foo"))]);
04454e1e 356
923072b8
FG
357 assert_eq!(map_path_prefix(mapping, "abc/def/src/main.rs"), path_str("foo/src/main.rs"));
358 assert_eq!(map_path_prefix(mapping, "abc/def"), path_str("foo"));
04454e1e
FG
359 }
360
361 // Relative to absolute
362 {
923072b8 363 let mapping = &FilePathMapping::new(vec![(path("abc/def"), path("/foo"))]);
04454e1e 364
923072b8
FG
365 assert_eq!(map_path_prefix(mapping, "abc/def/src/main.rs"), path_str("/foo/src/main.rs"));
366 assert_eq!(map_path_prefix(mapping, "abc/def"), path_str("/foo"));
04454e1e
FG
367 }
368
369 // Absolute to relative
370 {
923072b8 371 let mapping = &FilePathMapping::new(vec![(path("/abc/def"), path("foo"))]);
04454e1e 372
923072b8
FG
373 assert_eq!(map_path_prefix(mapping, "/abc/def/src/main.rs"), path_str("foo/src/main.rs"));
374 assert_eq!(map_path_prefix(mapping, "/abc/def"), path_str("foo"));
04454e1e
FG
375 }
376
377 // Absolute to absolute
378 {
923072b8 379 let mapping = &FilePathMapping::new(vec![(path("/abc/def"), path("/foo"))]);
04454e1e 380
923072b8
FG
381 assert_eq!(map_path_prefix(mapping, "/abc/def/src/main.rs"), path_str("/foo/src/main.rs"));
382 assert_eq!(map_path_prefix(mapping, "/abc/def"), path_str("/foo"));
04454e1e
FG
383 }
384}
385
04454e1e 386#[test]
923072b8
FG
387fn path_prefix_remapping_expand_to_absolute() {
388 // "virtual" working directory is relative path
389 let mapping =
390 &FilePathMapping::new(vec![(path("/foo"), path("FOO")), (path("/bar"), path("BAR"))]);
391 let working_directory = path("/foo");
392 let working_directory = RealFileName::Remapped {
393 local_path: Some(working_directory.clone()),
9c376795 394 virtual_name: mapping.map_prefix(working_directory).0.into_owned(),
923072b8
FG
395 };
396
397 assert_eq!(working_directory.remapped_path_if_available(), path("FOO"));
398
399 // Unmapped absolute path
400 assert_eq!(
401 mapping.to_embeddable_absolute_path(
402 RealFileName::LocalPath(path("/foo/src/main.rs")),
403 &working_directory
404 ),
405 RealFileName::Remapped { local_path: None, virtual_name: path("FOO/src/main.rs") }
406 );
04454e1e 407
923072b8
FG
408 // Unmapped absolute path with unrelated working directory
409 assert_eq!(
410 mapping.to_embeddable_absolute_path(
411 RealFileName::LocalPath(path("/bar/src/main.rs")),
412 &working_directory
413 ),
414 RealFileName::Remapped { local_path: None, virtual_name: path("BAR/src/main.rs") }
415 );
04454e1e 416
923072b8
FG
417 // Unmapped absolute path that does not match any prefix
418 assert_eq!(
419 mapping.to_embeddable_absolute_path(
420 RealFileName::LocalPath(path("/quux/src/main.rs")),
421 &working_directory
422 ),
423 RealFileName::LocalPath(path("/quux/src/main.rs")),
424 );
04454e1e 425
923072b8
FG
426 // Unmapped relative path
427 assert_eq!(
428 mapping.to_embeddable_absolute_path(
429 RealFileName::LocalPath(path("src/main.rs")),
430 &working_directory
431 ),
432 RealFileName::Remapped { local_path: None, virtual_name: path("FOO/src/main.rs") }
433 );
04454e1e 434
923072b8
FG
435 // Unmapped relative path with `./`
436 assert_eq!(
437 mapping.to_embeddable_absolute_path(
438 RealFileName::LocalPath(path("./src/main.rs")),
439 &working_directory
440 ),
441 RealFileName::Remapped { local_path: None, virtual_name: path("FOO/src/main.rs") }
442 );
04454e1e 443
923072b8
FG
444 // Unmapped relative path that does not match any prefix
445 assert_eq!(
446 mapping.to_embeddable_absolute_path(
447 RealFileName::LocalPath(path("quux/src/main.rs")),
448 &RealFileName::LocalPath(path("/abc")),
449 ),
450 RealFileName::LocalPath(path("/abc/quux/src/main.rs")),
451 );
04454e1e 452
923072b8
FG
453 // Already remapped absolute path
454 assert_eq!(
455 mapping.to_embeddable_absolute_path(
456 RealFileName::Remapped {
457 local_path: Some(path("/foo/src/main.rs")),
458 virtual_name: path("FOO/src/main.rs"),
459 },
460 &working_directory
461 ),
462 RealFileName::Remapped { local_path: None, virtual_name: path("FOO/src/main.rs") }
463 );
04454e1e 464
923072b8
FG
465 // Already remapped absolute path, with unrelated working directory
466 assert_eq!(
467 mapping.to_embeddable_absolute_path(
468 RealFileName::Remapped {
469 local_path: Some(path("/bar/src/main.rs")),
470 virtual_name: path("BAR/src/main.rs"),
471 },
472 &working_directory
473 ),
474 RealFileName::Remapped { local_path: None, virtual_name: path("BAR/src/main.rs") }
475 );
476
477 // Already remapped relative path
478 assert_eq!(
479 mapping.to_embeddable_absolute_path(
480 RealFileName::Remapped { local_path: None, virtual_name: path("XYZ/src/main.rs") },
481 &working_directory
482 ),
483 RealFileName::Remapped { local_path: None, virtual_name: path("XYZ/src/main.rs") }
484 );
04454e1e 485}
2b03887a 486
9c376795
FG
487#[test]
488fn path_prefix_remapping_reverse() {
489 // Ignores options without alphanumeric chars.
490 {
491 let mapping =
492 &FilePathMapping::new(vec![(path("abc"), path("/")), (path("def"), path("."))]);
493
494 assert_eq!(reverse_map_prefix(mapping, "/hello.rs"), None);
495 assert_eq!(reverse_map_prefix(mapping, "./hello.rs"), None);
496 }
497
498 // Returns `None` if multiple options match.
499 {
500 let mapping = &FilePathMapping::new(vec![
501 (path("abc"), path("/redacted")),
502 (path("def"), path("/redacted")),
503 ]);
504
505 assert_eq!(reverse_map_prefix(mapping, "/redacted/hello.rs"), None);
506 }
507
508 // Distinct reverse mappings.
509 {
510 let mapping = &FilePathMapping::new(vec![
511 (path("abc"), path("/redacted")),
512 (path("def/ghi"), path("/fake/dir")),
513 ]);
514
515 assert_eq!(
516 reverse_map_prefix(mapping, "/redacted/path/hello.rs"),
517 Some(path_str("abc/path/hello.rs"))
518 );
519 assert_eq!(
520 reverse_map_prefix(mapping, "/fake/dir/hello.rs"),
521 Some(path_str("def/ghi/hello.rs"))
522 );
523 }
524}
525
2b03887a
FG
526#[test]
527fn test_next_point() {
528 let sm = SourceMap::new(FilePathMapping::empty());
529 sm.new_source_file(PathBuf::from("example.rs").into(), "a…b".to_string());
530
531 // Dummy spans don't advance.
532 let span = DUMMY_SP;
533 let span = sm.next_point(span);
534 assert_eq!(span.lo().0, 0);
535 assert_eq!(span.hi().0, 0);
536
537 // Span advance respect multi-byte character
538 let span = Span::with_root_ctxt(BytePos(0), BytePos(1));
539 assert_eq!(sm.span_to_snippet(span), Ok("a".to_string()));
540 let span = sm.next_point(span);
541 assert_eq!(sm.span_to_snippet(span), Ok("…".to_string()));
542 assert_eq!(span.lo().0, 1);
543 assert_eq!(span.hi().0, 4);
544
545 // An empty span pointing just before a multi-byte character should
546 // advance to contain the multi-byte character.
547 let span = Span::with_root_ctxt(BytePos(1), BytePos(1));
548 let span = sm.next_point(span);
549 assert_eq!(span.lo().0, 1);
550 assert_eq!(span.hi().0, 4);
551
552 let span = Span::with_root_ctxt(BytePos(1), BytePos(4));
553 let span = sm.next_point(span);
554 assert_eq!(span.lo().0, 4);
555 assert_eq!(span.hi().0, 5);
556
487cf647 557 // Reaching to the end of file, return a span that will get error with `span_to_snippet`
2b03887a
FG
558 let span = Span::with_root_ctxt(BytePos(4), BytePos(5));
559 let span = sm.next_point(span);
560 assert_eq!(span.lo().0, 5);
487cf647
FG
561 assert_eq!(span.hi().0, 6);
562 assert!(sm.span_to_snippet(span).is_err());
2b03887a 563
487cf647 564 // Reaching to the end of file, return a span that will get error with `span_to_snippet`
2b03887a
FG
565 let span = Span::with_root_ctxt(BytePos(5), BytePos(5));
566 let span = sm.next_point(span);
567 assert_eq!(span.lo().0, 5);
487cf647
FG
568 assert_eq!(span.hi().0, 6);
569 assert!(sm.span_to_snippet(span).is_err());
2b03887a 570}