1 //! Types for tracking pieces of source code within a crate.
3 //! The [`SourceMap`] tracks all the source code used within a single crate, mapping
4 //! from integer byte positions to the original source code location. Each bit
5 //! of source parsed during crate parsing (typically files, in-memory strings,
6 //! or various bits of macro expansion) cover a continuous range of bytes in the
7 //! `SourceMap` and are represented by [`SourceFile`]s. Byte positions are stored in
8 //! [`Span`] and used pervasively in the compiler. They are absolute positions
9 //! within the `SourceMap`, which upon request can be converted to line and column
10 //! information, source code snippets, etc.
12 pub use crate::hygiene
::{ExpnData, ExpnKind}
;
15 use rustc_data_structures
::fx
::FxHashMap
;
16 use rustc_data_structures
::stable_hasher
::StableHasher
;
17 use rustc_data_structures
::sync
::{AtomicU32, Lrc, MappedReadGuard, ReadGuard, RwLock}
;
20 use std
::path
::{self, Path, PathBuf}
;
21 use std
::sync
::atomic
::Ordering
;
29 /// Returns the span itself if it doesn't come from a macro expansion,
30 /// otherwise return the call site span up to the `enclosing_sp` by
31 /// following the `expn_data` chain.
32 pub fn original_sp(sp
: Span
, enclosing_sp
: Span
) -> Span
{
33 let expn_data1
= sp
.ctxt().outer_expn_data();
34 let expn_data2
= enclosing_sp
.ctxt().outer_expn_data();
35 if expn_data1
.is_root() || !expn_data2
.is_root() && expn_data1
.call_site
== expn_data2
.call_site
39 original_sp(expn_data1
.call_site
, enclosing_sp
)
44 use std
::ops
::{Deref, DerefMut}
;
46 /// A `MonotonicVec` is a `Vec` which can only be grown.
47 /// Once inserted, an element can never be removed or swapped,
48 /// guaranteeing that any indices into a `MonotonicVec` are stable
49 // This is declared in its own module to ensure that the private
50 // field is inaccessible
51 pub struct MonotonicVec
<T
>(Vec
<T
>);
52 impl<T
> MonotonicVec
<T
> {
53 pub fn new(val
: Vec
<T
>) -> MonotonicVec
<T
> {
57 pub fn push(&mut self, val
: T
) {
62 impl<T
> Default
for MonotonicVec
<T
> {
63 fn default() -> Self {
64 MonotonicVec
::new(vec
![])
68 impl<T
> Deref
for MonotonicVec
<T
> {
70 fn deref(&self) -> &Self::Target
{
75 impl<T
> !DerefMut
for MonotonicVec
<T
> {}
78 #[derive(Clone, Encodable, Decodable, Debug, Copy, HashStable_Generic)]
79 pub struct Spanned
<T
> {
84 pub fn respan
<T
>(sp
: Span
, t
: T
) -> Spanned
<T
> {
85 Spanned { node: t, span: sp }
88 pub fn dummy_spanned
<T
>(t
: T
) -> Spanned
<T
> {
92 // _____________________________________________________________________________
93 // SourceFile, MultiByteChar, FileName, FileLines
96 /// An abstraction over the fs operations used by the Parser.
97 pub trait FileLoader
{
98 /// Query the existence of a file.
99 fn file_exists(&self, path
: &Path
) -> bool
;
101 /// Read the contents of a UTF-8 file into memory.
102 fn read_file(&self, path
: &Path
) -> io
::Result
<String
>;
105 /// A FileLoader that uses std::fs to load real files.
106 pub struct RealFileLoader
;
108 impl FileLoader
for RealFileLoader
{
109 fn file_exists(&self, path
: &Path
) -> bool
{
113 fn read_file(&self, path
: &Path
) -> io
::Result
<String
> {
114 fs
::read_to_string(path
)
118 /// This is a [SourceFile] identifier that is used to correlate source files between
119 /// subsequent compilation sessions (which is something we need to do during
120 /// incremental compilation).
122 /// The [StableSourceFileId] also contains the CrateNum of the crate the source
123 /// file was originally parsed for. This way we get two separate entries in
124 /// the [SourceMap] if the same file is part of both the local and an upstream
125 /// crate. Trying to only have one entry for both cases is problematic because
126 /// at the point where we discover that there's a local use of the file in
127 /// addition to the upstream one, we might already have made decisions based on
128 /// the assumption that it's an upstream file. Treating the two files as
129 /// different has no real downsides.
130 #[derive(Copy, Clone, PartialEq, Eq, Hash, Encodable, Decodable, Debug)]
131 pub struct StableSourceFileId
{
132 /// A hash of the source file's [`FileName`]. This is hash so that it's size
133 /// is more predictable than if we included the actual [`FileName`] value.
134 pub file_name_hash
: u64,
136 /// The [`CrateNum`] of the crate this source file was originally parsed for.
137 /// We cannot include this information in the hash because at the time
138 /// of hashing we don't have the context to map from the [`CrateNum`]'s numeric
139 /// value to a `StableCrateId`.
143 // FIXME: we need a more globally consistent approach to the problem solved by
144 // StableSourceFileId, perhaps built atop source_file.name_hash.
145 impl StableSourceFileId
{
146 pub fn new(source_file
: &SourceFile
) -> StableSourceFileId
{
147 StableSourceFileId
::new_from_name(&source_file
.name
, source_file
.cnum
)
150 fn new_from_name(name
: &FileName
, cnum
: CrateNum
) -> StableSourceFileId
{
151 let mut hasher
= StableHasher
::new();
152 name
.hash(&mut hasher
);
153 StableSourceFileId { file_name_hash: hasher.finish(), cnum }
157 // _____________________________________________________________________________
162 pub(super) struct SourceMapFiles
{
163 source_files
: monotonic
::MonotonicVec
<Lrc
<SourceFile
>>,
164 stable_id_to_source_file
: FxHashMap
<StableSourceFileId
, Lrc
<SourceFile
>>,
167 pub struct SourceMap
{
168 /// The address space below this value is currently used by the files in the source map.
169 used_address_space
: AtomicU32
,
171 files
: RwLock
<SourceMapFiles
>,
172 file_loader
: Box
<dyn FileLoader
+ Sync
+ Send
>,
173 // This is used to apply the file path remapping as specified via
174 // `--remap-path-prefix` to all `SourceFile`s allocated within this `SourceMap`.
175 path_mapping
: FilePathMapping
,
177 /// The algorithm used for hashing the contents of each source file.
178 hash_kind
: SourceFileHashAlgorithm
,
182 pub fn new(path_mapping
: FilePathMapping
) -> SourceMap
{
183 Self::with_file_loader_and_hash_kind(
184 Box
::new(RealFileLoader
),
186 SourceFileHashAlgorithm
::Md5
,
190 pub fn with_file_loader_and_hash_kind(
191 file_loader
: Box
<dyn FileLoader
+ Sync
+ Send
>,
192 path_mapping
: FilePathMapping
,
193 hash_kind
: SourceFileHashAlgorithm
,
196 used_address_space
: AtomicU32
::new(0),
197 files
: Default
::default(),
204 pub fn path_mapping(&self) -> &FilePathMapping
{
208 pub fn file_exists(&self, path
: &Path
) -> bool
{
209 self.file_loader
.file_exists(path
)
212 pub fn load_file(&self, path
: &Path
) -> io
::Result
<Lrc
<SourceFile
>> {
213 let src
= self.file_loader
.read_file(path
)?
;
214 let filename
= path
.to_owned().into();
215 Ok(self.new_source_file(filename
, src
))
218 /// Loads source file as a binary blob.
220 /// Unlike `load_file`, guarantees that no normalization like BOM-removal
222 pub fn load_binary_file(&self, path
: &Path
) -> io
::Result
<Vec
<u8>> {
223 // Ideally, this should use `self.file_loader`, but it can't
224 // deal with binary files yet.
225 let bytes
= fs
::read(path
)?
;
227 // We need to add file to the `SourceMap`, so that it is present
228 // in dep-info. There's also an edge case that file might be both
229 // loaded as a binary via `include_bytes!` and as proper `SourceFile`
230 // via `mod`, so we try to use real file contents and not just an
232 let text
= std
::str::from_utf8(&bytes
).unwrap_or("").to_string();
233 self.new_source_file(path
.to_owned().into(), text
);
237 // By returning a `MonotonicVec`, we ensure that consumers cannot invalidate
238 // any existing indices pointing into `files`.
239 pub fn files(&self) -> MappedReadGuard
<'_
, monotonic
::MonotonicVec
<Lrc
<SourceFile
>>> {
240 ReadGuard
::map(self.files
.borrow(), |files
| &files
.source_files
)
243 pub fn source_file_by_stable_id(
245 stable_id
: StableSourceFileId
,
246 ) -> Option
<Lrc
<SourceFile
>> {
247 self.files
.borrow().stable_id_to_source_file
.get(&stable_id
).cloned()
250 fn allocate_address_space(&self, size
: usize) -> Result
<usize, OffsetOverflowError
> {
251 let size
= u32::try_from(size
).map_err(|_
| OffsetOverflowError
)?
;
254 let current
= self.used_address_space
.load(Ordering
::Relaxed
);
257 // Add one so there is some space between files. This lets us distinguish
258 // positions in the `SourceMap`, even in the presence of zero-length files.
259 .and_then(|next
| next
.checked_add(1))
260 .ok_or(OffsetOverflowError
)?
;
264 .compare_exchange(current
, next
, Ordering
::Relaxed
, Ordering
::Relaxed
)
267 return Ok(usize::try_from(current
).unwrap());
272 /// Creates a new `SourceFile`.
273 /// If a file already exists in the `SourceMap` with the same ID, that file is returned
275 pub fn new_source_file(&self, filename
: FileName
, src
: String
) -> Lrc
<SourceFile
> {
276 self.try_new_source_file(filename
, src
).unwrap_or_else(|OffsetOverflowError
| {
277 eprintln
!("fatal error: rustc does not support files larger than 4GB");
278 crate::fatal_error
::FatalError
.raise()
282 fn try_new_source_file(
286 ) -> Result
<Lrc
<SourceFile
>, OffsetOverflowError
> {
287 // Note that filename may not be a valid path, eg it may be `<anon>` etc,
288 // but this is okay because the directory determined by `path.pop()` will
289 // be empty, so the working directory will be used.
290 let (filename
, _
) = self.path_mapping
.map_filename_prefix(&filename
);
292 let file_id
= StableSourceFileId
::new_from_name(&filename
, LOCAL_CRATE
);
294 let lrc_sf
= match self.source_file_by_stable_id(file_id
) {
295 Some(lrc_sf
) => lrc_sf
,
297 let start_pos
= self.allocate_address_space(src
.len())?
;
299 let source_file
= Lrc
::new(SourceFile
::new(
302 Pos
::from_usize(start_pos
),
306 // Let's make sure the file_id we generated above actually matches
307 // the ID we generate for the SourceFile we just created.
308 debug_assert_eq
!(StableSourceFileId
::new(&source_file
), file_id
);
310 let mut files
= self.files
.borrow_mut();
312 files
.source_files
.push(source_file
.clone());
313 files
.stable_id_to_source_file
.insert(file_id
, source_file
.clone());
321 /// Allocates a new `SourceFile` representing a source file from an external
322 /// crate. The source code of such an "imported `SourceFile`" is not available,
323 /// but we still know enough to generate accurate debuginfo location
324 /// information for things inlined from other crates.
325 pub fn new_imported_source_file(
328 src_hash
: SourceFileHash
,
332 file_local_lines
: Lock
<SourceFileLines
>,
333 mut file_local_multibyte_chars
: Vec
<MultiByteChar
>,
334 mut file_local_non_narrow_chars
: Vec
<NonNarrowChar
>,
335 mut file_local_normalized_pos
: Vec
<NormalizedPos
>,
336 original_start_pos
: BytePos
,
338 ) -> Lrc
<SourceFile
> {
340 .allocate_address_space(source_len
)
341 .expect("not enough address space for imported source file");
343 let end_pos
= Pos
::from_usize(start_pos
+ source_len
);
344 let start_pos
= Pos
::from_usize(start_pos
);
346 // Translate these positions into the new global frame of reference,
347 // now that the offset of the SourceFile is known.
349 // These are all unsigned values. `original_start_pos` may be larger or
350 // smaller than `start_pos`, but `pos` is always larger than both.
351 // Therefore, `(pos - original_start_pos) + start_pos` won't overflow
352 // but `start_pos - original_start_pos` might. So we use the former
353 // form rather than pre-computing the offset into a local variable. The
354 // compiler backend can optimize away the repeated computations in a
355 // way that won't trigger overflow checks.
356 match &mut *file_local_lines
.borrow_mut() {
357 SourceFileLines
::Lines(lines
) => {
359 *pos
= (*pos
- original_start_pos
) + start_pos
;
362 SourceFileLines
::Diffs(SourceFileDiffs { line_start, .. }
) => {
363 *line_start
= (*line_start
- original_start_pos
) + start_pos
;
366 for mbc
in &mut file_local_multibyte_chars
{
367 mbc
.pos
= (mbc
.pos
- original_start_pos
) + start_pos
;
369 for swc
in &mut file_local_non_narrow_chars
{
370 *swc
= (*swc
- original_start_pos
) + start_pos
;
372 for nc
in &mut file_local_normalized_pos
{
373 nc
.pos
= (nc
.pos
- original_start_pos
) + start_pos
;
376 let source_file
= Lrc
::new(SourceFile
{
380 external_src
: Lock
::new(ExternalSource
::Foreign
{
381 kind
: ExternalSourceKind
::AbsentOk
,
386 lines
: file_local_lines
,
387 multibyte_chars
: file_local_multibyte_chars
,
388 non_narrow_chars
: file_local_non_narrow_chars
,
389 normalized_pos
: file_local_normalized_pos
,
394 let mut files
= self.files
.borrow_mut();
396 files
.source_files
.push(source_file
.clone());
398 .stable_id_to_source_file
399 .insert(StableSourceFileId
::new(&source_file
), source_file
.clone());
404 /// If there is a doctest offset, applies it to the line.
405 pub fn doctest_offset_line(&self, file
: &FileName
, orig
: usize) -> usize {
407 FileName
::DocTest(_
, offset
) => {
409 orig
- (-(*offset
)) as usize
411 orig
+ *offset
as usize
418 /// Return the SourceFile that contains the given `BytePos`
419 pub fn lookup_source_file(&self, pos
: BytePos
) -> Lrc
<SourceFile
> {
420 let idx
= self.lookup_source_file_idx(pos
);
421 (*self.files
.borrow().source_files
)[idx
].clone()
424 /// Looks up source information about a `BytePos`.
425 pub fn lookup_char_pos(&self, pos
: BytePos
) -> Loc
{
426 let sf
= self.lookup_source_file(pos
);
427 let (line
, col
, col_display
) = sf
.lookup_file_pos_with_col_display(pos
);
428 Loc { file: sf, line, col, col_display }
431 /// If the corresponding `SourceFile` is empty, does not return a line number.
432 pub fn lookup_line(&self, pos
: BytePos
) -> Result
<SourceFileAndLine
, Lrc
<SourceFile
>> {
433 let f
= self.lookup_source_file(pos
);
435 match f
.lookup_line(pos
) {
436 Some(line
) => Ok(SourceFileAndLine { sf: f, line }
),
441 pub fn span_to_string(
444 filename_display_pref
: FileNameDisplayPreference
,
446 if self.files
.borrow().source_files
.is_empty() || sp
.is_dummy() {
447 return "no-location".to_string();
450 let lo
= self.lookup_char_pos(sp
.lo());
451 let hi
= self.lookup_char_pos(sp
.hi());
454 lo
.file
.name
.display(filename_display_pref
),
456 lo
.col
.to_usize() + 1,
457 if let FileNameDisplayPreference
::Short
= filename_display_pref
{
460 format
!(": {}:{}", hi
.line
, hi
.col
.to_usize() + 1)
465 /// Format the span location suitable for embedding in build artifacts
466 pub fn span_to_embeddable_string(&self, sp
: Span
) -> String
{
467 self.span_to_string(sp
, FileNameDisplayPreference
::Remapped
)
470 /// Format the span location suitable for pretty printing anotations with relative line numbers
471 pub fn span_to_relative_line_string(&self, sp
: Span
, relative_to
: Span
) -> String
{
472 if self.files
.borrow().source_files
.is_empty() || sp
.is_dummy() || relative_to
.is_dummy() {
473 return "no-location".to_string();
476 let lo
= self.lookup_char_pos(sp
.lo());
477 let hi
= self.lookup_char_pos(sp
.hi());
478 let offset
= self.lookup_char_pos(relative_to
.lo());
480 if lo
.file
.name
!= offset
.file
.name
|| !relative_to
.contains(sp
) {
481 return self.span_to_embeddable_string(sp
);
484 let lo_line
= lo
.line
.saturating_sub(offset
.line
);
485 let hi_line
= hi
.line
.saturating_sub(offset
.line
);
489 lo
.file
.name
.display(FileNameDisplayPreference
::Remapped
),
491 lo
.col
.to_usize() + 1,
493 hi
.col
.to_usize() + 1,
497 /// Format the span location to be printed in diagnostics. Must not be emitted
498 /// to build artifacts as this may leak local file paths. Use span_to_embeddable_string
499 /// for string suitable for embedding.
500 pub fn span_to_diagnostic_string(&self, sp
: Span
) -> String
{
501 self.span_to_string(sp
, self.path_mapping
.filename_display_for_diagnostics
)
504 pub fn span_to_filename(&self, sp
: Span
) -> FileName
{
505 self.lookup_char_pos(sp
.lo()).file
.name
.clone()
508 pub fn filename_for_diagnostics
<'a
>(&self, filename
: &'a FileName
) -> FileNameDisplay
<'a
> {
509 filename
.display(self.path_mapping
.filename_display_for_diagnostics
)
512 pub fn is_multiline(&self, sp
: Span
) -> bool
{
513 let lo
= self.lookup_source_file_idx(sp
.lo());
514 let hi
= self.lookup_source_file_idx(sp
.hi());
518 let f
= (*self.files
.borrow().source_files
)[lo
].clone();
519 f
.lookup_line(sp
.lo()) != f
.lookup_line(sp
.hi())
522 #[instrument(skip(self), level = "trace")]
523 pub fn is_valid_span(&self, sp
: Span
) -> Result
<(Loc
, Loc
), SpanLinesError
> {
524 let lo
= self.lookup_char_pos(sp
.lo());
526 let hi
= self.lookup_char_pos(sp
.hi());
528 if lo
.file
.start_pos
!= hi
.file
.start_pos
{
529 return Err(SpanLinesError
::DistinctSources(DistinctSources
{
530 begin
: (lo
.file
.name
.clone(), lo
.file
.start_pos
),
531 end
: (hi
.file
.name
.clone(), hi
.file
.start_pos
),
537 pub fn is_line_before_span_empty(&self, sp
: Span
) -> bool
{
538 match self.span_to_prev_source(sp
) {
539 Ok(s
) => s
.rsplit_once('
\n'
).unwrap_or(("", &s
)).1.trim_start().is_empty(),
544 pub fn span_to_lines(&self, sp
: Span
) -> FileLinesResult
{
545 debug
!("span_to_lines(sp={:?})", sp
);
546 let (lo
, hi
) = self.is_valid_span(sp
)?
;
547 assert
!(hi
.line
>= lo
.line
);
550 return Ok(FileLines { file: lo.file, lines: Vec::new() }
);
553 let mut lines
= Vec
::with_capacity(hi
.line
- lo
.line
+ 1);
555 // The span starts partway through the first line,
556 // but after that it starts from offset 0.
557 let mut start_col
= lo
.col
;
559 // For every line but the last, it extends from `start_col`
560 // and to the end of the line. Be careful because the line
561 // numbers in Loc are 1-based, so we subtract 1 to get 0-based
564 // FIXME: now that we handle DUMMY_SP up above, we should consider
565 // asserting that the line numbers here are all indeed 1-based.
566 let hi_line
= hi
.line
.saturating_sub(1);
567 for line_index
in lo
.line
.saturating_sub(1)..hi_line
{
568 let line_len
= lo
.file
.get_line(line_index
).map_or(0, |s
| s
.chars().count());
569 lines
.push(LineInfo { line_index, start_col, end_col: CharPos::from_usize(line_len) }
);
570 start_col
= CharPos
::from_usize(0);
573 // For the last line, it extends from `start_col` to `hi.col`:
574 lines
.push(LineInfo { line_index: hi_line, start_col, end_col: hi.col }
);
576 Ok(FileLines { file: lo.file, lines }
)
579 /// Extracts the source surrounding the given `Span` using the `extract_source` function. The
580 /// extract function takes three arguments: a string slice containing the source, an index in
581 /// the slice for the beginning of the span and an index in the slice for the end of the span.
582 fn span_to_source
<F
, T
>(&self, sp
: Span
, extract_source
: F
) -> Result
<T
, SpanSnippetError
>
584 F
: Fn(&str, usize, usize) -> Result
<T
, SpanSnippetError
>,
586 let local_begin
= self.lookup_byte_offset(sp
.lo());
587 let local_end
= self.lookup_byte_offset(sp
.hi());
589 if local_begin
.sf
.start_pos
!= local_end
.sf
.start_pos
{
590 Err(SpanSnippetError
::DistinctSources(DistinctSources
{
591 begin
: (local_begin
.sf
.name
.clone(), local_begin
.sf
.start_pos
),
592 end
: (local_end
.sf
.name
.clone(), local_end
.sf
.start_pos
),
595 self.ensure_source_file_source_present(local_begin
.sf
.clone());
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();
601 if start_index
> end_index
|| end_index
> source_len
{
602 return Err(SpanSnippetError
::MalformedForSourcemap(MalformedSourceMapPositions
{
603 name
: local_begin
.sf
.name
.clone(),
605 begin_pos
: local_begin
.pos
,
606 end_pos
: local_end
.pos
,
610 if let Some(ref src
) = local_begin
.sf
.src
{
611 extract_source(src
, start_index
, end_index
)
612 } else if let Some(src
) = local_begin
.sf
.external_src
.borrow().get_source() {
613 extract_source(src
, start_index
, end_index
)
615 Err(SpanSnippetError
::SourceNotAvailable { filename: local_begin.sf.name.clone() }
)
620 pub fn is_span_accessible(&self, sp
: Span
) -> bool
{
621 self.span_to_source(sp
, |src
, start_index
, end_index
| {
622 Ok(src
.get(start_index
..end_index
).is_some())
624 .map_or(false, |is_accessible
| is_accessible
)
627 /// Returns the source snippet as `String` corresponding to the given `Span`.
628 pub fn span_to_snippet(&self, sp
: Span
) -> Result
<String
, SpanSnippetError
> {
629 self.span_to_source(sp
, |src
, start_index
, end_index
| {
630 src
.get(start_index
..end_index
)
631 .map(|s
| s
.to_string())
632 .ok_or(SpanSnippetError
::IllFormedSpan(sp
))
636 pub fn span_to_margin(&self, sp
: Span
) -> Option
<usize> {
637 Some(self.indentation_before(sp
)?
.len())
640 pub fn indentation_before(&self, sp
: Span
) -> Option
<String
> {
641 self.span_to_source(sp
, |src
, start_index
, _
| {
642 let before
= &src
[..start_index
];
643 let last_line
= before
.rsplit_once('
\n'
).map_or(before
, |(_
, last
)| last
);
645 .split_once(|c
: char| !c
.is_whitespace())
646 .map_or(last_line
, |(indent
, _
)| indent
)
652 /// Returns the source snippet as `String` before the given `Span`.
653 pub fn span_to_prev_source(&self, sp
: Span
) -> Result
<String
, SpanSnippetError
> {
654 self.span_to_source(sp
, |src
, start_index
, _
| {
655 src
.get(..start_index
).map(|s
| s
.to_string()).ok_or(SpanSnippetError
::IllFormedSpan(sp
))
659 /// Extends the given `Span` to just after the previous occurrence of `c`. Return the same span
660 /// if no character could be found or if an error occurred while retrieving the code snippet.
661 pub fn span_extend_to_prev_char(&self, sp
: Span
, c
: char, accept_newlines
: bool
) -> Span
{
662 if let Ok(prev_source
) = self.span_to_prev_source(sp
) {
663 let prev_source
= prev_source
.rsplit(c
).next().unwrap_or("");
664 if !prev_source
.is_empty() && (accept_newlines
|| !prev_source
.contains('
\n'
)) {
665 return sp
.with_lo(BytePos(sp
.lo().0 - prev_source
.len() as u32));
672 /// Extends the given `Span` to just after the previous occurrence of `pat` when surrounded by
673 /// whitespace. Returns None if the pattern could not be found or if an error occurred while
674 /// retrieving the code snippet.
675 pub fn span_extend_to_prev_str(
679 accept_newlines
: bool
,
680 include_whitespace
: bool
,
682 // assure that the pattern is delimited, to avoid the following
684 // ^^^^ returned span without the check
685 // ---------- correct span
686 let prev_source
= self.span_to_prev_source(sp
).ok()?
;
687 for ws
in &[" ", "\t", "\n"] {
688 let pat
= pat
.to_owned() + ws
;
689 if let Some(pat_pos
) = prev_source
.rfind(&pat
) {
690 let just_after_pat_pos
= pat_pos
+ pat
.len() - 1;
691 let just_after_pat_plus_ws
= if include_whitespace
{
693 + prev_source
[just_after_pat_pos
..]
694 .find(|c
: char| !c
.is_whitespace())
699 let len
= prev_source
.len() - just_after_pat_plus_ws
;
700 let prev_source
= &prev_source
[just_after_pat_plus_ws
..];
701 if accept_newlines
|| !prev_source
.trim_start().contains('
\n'
) {
702 return Some(sp
.with_lo(BytePos(sp
.lo().0 - len
as u32)));
710 /// Returns the source snippet as `String` after the given `Span`.
711 pub fn span_to_next_source(&self, sp
: Span
) -> Result
<String
, SpanSnippetError
> {
712 self.span_to_source(sp
, |src
, _
, end_index
| {
713 src
.get(end_index
..).map(|s
| s
.to_string()).ok_or(SpanSnippetError
::IllFormedSpan(sp
))
717 /// Extends the given `Span` while the next character matches the predicate
718 pub fn span_extend_while(
721 f
: impl Fn(char) -> bool
,
722 ) -> Result
<Span
, SpanSnippetError
> {
723 self.span_to_source(span
, |s
, _start
, end
| {
724 let n
= s
[end
..].char_indices().find(|&(_
, c
)| !f(c
)).map_or(s
.len() - end
, |(i
, _
)| i
);
725 Ok(span
.with_hi(span
.hi() + BytePos(n
as u32)))
729 /// Extends the given `Span` to just before the next occurrence of `c`.
730 pub fn span_extend_to_next_char(&self, sp
: Span
, c
: char, accept_newlines
: bool
) -> Span
{
731 if let Ok(next_source
) = self.span_to_next_source(sp
) {
732 let next_source
= next_source
.split(c
).next().unwrap_or("");
733 if !next_source
.is_empty() && (accept_newlines
|| !next_source
.contains('
\n'
)) {
734 return sp
.with_hi(BytePos(sp
.hi().0 + next_source
.len() as u32));
741 /// Extends the given `Span` to contain the entire line it is on.
742 pub fn span_extend_to_line(&self, sp
: Span
) -> Span
{
743 self.span_extend_to_prev_char(self.span_extend_to_next_char(sp
, '
\n'
, true), '
\n'
, true)
746 /// Given a `Span`, tries to get a shorter span ending before the first occurrence of `char`
748 pub fn span_until_char(&self, sp
: Span
, c
: char) -> Span
{
749 match self.span_to_snippet(sp
) {
751 let snippet
= snippet
.split(c
).next().unwrap_or("").trim_end();
752 if !snippet
.is_empty() && !snippet
.contains('
\n'
) {
753 sp
.with_hi(BytePos(sp
.lo().0 + snippet
.len() as u32))
762 /// Given a 'Span', tries to tell if it's wrapped by "<>" or "()"
763 /// the algorithm searches if the next character is '>' or ')' after skipping white space
764 /// then searches the previous charactoer to match '<' or '(' after skipping white space
765 /// return true if wrapped by '<>' or '()'
766 pub fn span_wrapped_by_angle_or_parentheses(&self, span
: Span
) -> bool
{
767 self.span_to_source(span
, |src
, start_index
, end_index
| {
768 if src
.get(start_index
..end_index
).is_none() {
771 // test the right side to match '>' after skipping white space
772 let end_src
= &src
[end_index
..];
774 let mut found_right_parentheses
= false;
775 let mut found_right_angle
= false;
776 while let Some(cc
) = end_src
.chars().nth(i
) {
779 } else if cc
== '
>'
{
780 // found > in the right;
781 found_right_angle
= true;
783 } else if cc
== '
)'
{
784 found_right_parentheses
= true;
787 // failed to find '>' return false immediately
791 // test the left side to match '<' after skipping white space
793 let start_src
= &src
[0..start_index
];
794 while let Some(cc
) = start_src
.chars().nth(i
) {
800 } else if cc
== '
<'
{
801 // found < in the left
802 if !found_right_angle
{
803 // skip something like "(< )>"
807 } else if cc
== '
('
{
808 if !found_right_parentheses
{
809 // skip something like "<(>)"
814 // failed to find '<' return false immediately
820 .map_or(false, |is_accessible
| is_accessible
)
823 /// Given a `Span`, tries to get a shorter span ending just after the first occurrence of `char`
825 pub fn span_through_char(&self, sp
: Span
, c
: char) -> Span
{
826 if let Ok(snippet
) = self.span_to_snippet(sp
) {
827 if let Some(offset
) = snippet
.find(c
) {
828 return sp
.with_hi(BytePos(sp
.lo().0 + (offset
+ c
.len_utf8()) as u32));
834 /// Given a `Span`, gets a new `Span` covering the first token and all its trailing whitespace
835 /// or the original `Span`.
837 /// If `sp` points to `"let mut x"`, then a span pointing at `"let "` will be returned.
838 pub fn span_until_non_whitespace(&self, sp
: Span
) -> Span
{
839 let mut whitespace_found
= false;
841 self.span_take_while(sp
, |c
| {
842 if !whitespace_found
&& c
.is_whitespace() {
843 whitespace_found
= true;
846 !whitespace_found
|| c
.is_whitespace()
850 /// Given a `Span`, gets a new `Span` covering the first token without its trailing whitespace
851 /// or the original `Span` in case of error.
853 /// If `sp` points to `"let mut x"`, then a span pointing at `"let"` will be returned.
854 pub fn span_until_whitespace(&self, sp
: Span
) -> Span
{
855 self.span_take_while(sp
, |c
| !c
.is_whitespace())
858 /// Given a `Span`, gets a shorter one until `predicate` yields `false`.
859 pub fn span_take_while
<P
>(&self, sp
: Span
, predicate
: P
) -> Span
861 P
: for<'r
> FnMut(&'r
char) -> bool
,
863 if let Ok(snippet
) = self.span_to_snippet(sp
) {
864 let offset
= snippet
.chars().take_while(predicate
).map(|c
| c
.len_utf8()).sum
::<usize>();
866 sp
.with_hi(BytePos(sp
.lo().0 + (offset
as u32)))
872 /// Given a `Span`, return a span ending in the closest `{`. This is useful when you have a
873 /// `Span` enclosing a whole item but we need to point at only the head (usually the first
874 /// line) of that item.
876 /// *Only suitable for diagnostics.*
877 pub fn guess_head_span(&self, sp
: Span
) -> Span
{
878 // FIXME: extend the AST items to have a head span, or replace callers with pointing at
879 // the item's ident when appropriate.
880 self.span_until_char(sp
, '
{'
)
883 /// Returns a new span representing just the first character of the given span.
884 pub fn start_point(&self, sp
: Span
) -> Span
{
887 let local_begin
= self.lookup_byte_offset(sp
.lo
);
888 let start_index
= local_begin
.pos
.to_usize();
889 let src
= local_begin
.sf
.external_src
.borrow();
891 let snippet
= if let Some(ref src
) = local_begin
.sf
.src
{
892 Some(&src
[start_index
..])
893 } else if let Some(src
) = src
.get_source() {
894 Some(&src
[start_index
..])
901 Some(snippet
) => match snippet
.chars().next() {
903 Some(c
) => c
.len_utf8(),
908 sp
.with_hi(BytePos(sp
.lo().0 + width
as u32))
911 /// Returns a new span representing just the last character of this span.
912 pub fn end_point(&self, sp
: Span
) -> Span
{
915 let width
= self.find_width_of_character_at_span(sp
, false);
916 let corrected_end_position
= pos
.checked_sub(width
).unwrap_or(pos
);
918 let end_point
= BytePos(cmp
::max(corrected_end_position
, sp
.lo().0));
919 sp
.with_lo(end_point
)
922 /// Returns a new span representing the next character after the end-point of this span.
924 /// - if span is a dummy one, returns the same span
925 /// - if next_point reached the end of source, return a span exceeding the end of source,
926 /// which means sm.span_to_snippet(next_point) will get `Err`
927 /// - respect multi-byte characters
928 pub fn next_point(&self, sp
: Span
) -> Span
{
932 let start_of_next_point
= sp
.hi().0;
934 let width
= self.find_width_of_character_at_span(sp
, true);
935 // If the width is 1, then the next span should only contain the next char besides current ending.
936 // However, in the case of a multibyte character, where the width != 1, the next span should
937 // span multiple bytes to include the whole character.
938 let end_of_next_point
=
939 start_of_next_point
.checked_add(width
).unwrap_or(start_of_next_point
);
941 let end_of_next_point
= BytePos(cmp
::max(start_of_next_point
+ 1, end_of_next_point
));
942 Span
::new(BytePos(start_of_next_point
), end_of_next_point
, sp
.ctxt(), None
)
945 /// Returns a new span to check next none-whitespace character or some specified expected character
946 /// If `expect` is none, the first span of non-whitespace character is returned.
947 /// If `expect` presented, the first span of the character `expect` is returned
948 /// Otherwise, the span reached to limit is returned.
949 pub fn span_look_ahead(&self, span
: Span
, expect
: Option
<&str>, limit
: Option
<usize>) -> Span
{
951 for _
in 0..limit
.unwrap_or(100_usize
) {
952 sp
= self.next_point(sp
);
953 if let Ok(ref snippet
) = self.span_to_snippet(sp
) {
954 if expect
.map_or(false, |es
| snippet
== es
) {
957 if expect
.is_none() && snippet
.chars().any(|c
| !c
.is_whitespace()) {
965 /// Finds the width of the character, either before or after the end of provided span,
966 /// depending on the `forwards` parameter.
967 #[instrument(skip(self, sp))]
968 fn find_width_of_character_at_span(&self, sp
: Span
, forwards
: bool
) -> u32 {
971 if sp
.lo
== sp
.hi
&& !forwards
{
972 debug
!("early return empty span");
976 let local_begin
= self.lookup_byte_offset(sp
.lo
);
977 let local_end
= self.lookup_byte_offset(sp
.hi
);
978 debug
!("local_begin=`{:?}`, local_end=`{:?}`", local_begin
, local_end
);
980 if local_begin
.sf
.start_pos
!= local_end
.sf
.start_pos
{
981 debug
!("begin and end are in different files");
985 let start_index
= local_begin
.pos
.to_usize();
986 let end_index
= local_end
.pos
.to_usize();
987 debug
!("start_index=`{:?}`, end_index=`{:?}`", start_index
, end_index
);
989 // Disregard indexes that are at the start or end of their spans, they can't fit bigger
991 if (!forwards
&& end_index
== usize::MIN
) || (forwards
&& start_index
== usize::MAX
) {
992 debug
!("start or end of span, cannot be multibyte");
996 let source_len
= (local_begin
.sf
.end_pos
- local_begin
.sf
.start_pos
).to_usize();
997 debug
!("source_len=`{:?}`", source_len
);
998 // Ensure indexes are also not malformed.
999 if start_index
> end_index
|| end_index
> source_len
- 1 {
1000 debug
!("source indexes are malformed");
1004 let src
= local_begin
.sf
.external_src
.borrow();
1006 // We need to extend the snippet to the end of the src rather than to end_index so when
1007 // searching forwards for boundaries we've got somewhere to search.
1008 let snippet
= if let Some(ref src
) = local_begin
.sf
.src
{
1010 } else if let Some(src
) = src
.get_source() {
1015 debug
!("snippet=`{:?}`", snippet
);
1017 let mut target
= if forwards { end_index + 1 }
else { end_index - 1 }
;
1018 debug
!("initial target=`{:?}`", target
);
1020 while !snippet
.is_char_boundary(target
- start_index
) && target
< source_len
{
1021 target
= if forwards
{
1024 match target
.checked_sub(1) {
1025 Some(target
) => target
,
1031 debug
!("target=`{:?}`", target
);
1033 debug
!("final target=`{:?}`", target
);
1035 if forwards { (target - end_index) as u32 }
else { (end_index - target) as u32 }
1038 pub fn get_source_file(&self, filename
: &FileName
) -> Option
<Lrc
<SourceFile
>> {
1039 // Remap filename before lookup
1040 let filename
= self.path_mapping().map_filename_prefix(filename
).0;
1041 for sf
in self.files
.borrow().source_files
.iter() {
1042 if filename
== sf
.name
{
1043 return Some(sf
.clone());
1049 /// For a global `BytePos`, computes the local offset within the containing `SourceFile`.
1050 pub fn lookup_byte_offset(&self, bpos
: BytePos
) -> SourceFileAndBytePos
{
1051 let idx
= self.lookup_source_file_idx(bpos
);
1052 let sf
= (*self.files
.borrow().source_files
)[idx
].clone();
1053 let offset
= bpos
- sf
.start_pos
;
1054 SourceFileAndBytePos { sf, pos: offset }
1057 /// Returns the index of the [`SourceFile`] (in `self.files`) that contains `pos`.
1058 /// This index is guaranteed to be valid for the lifetime of this `SourceMap`,
1059 /// since `source_files` is a `MonotonicVec`
1060 pub fn lookup_source_file_idx(&self, pos
: BytePos
) -> usize {
1064 .binary_search_by_key(&pos
, |key
| key
.start_pos
)
1065 .unwrap_or_else(|p
| p
- 1)
1068 pub fn count_lines(&self) -> usize {
1069 self.files().iter().fold(0, |a
, f
| a
+ f
.count_lines())
1072 pub fn ensure_source_file_source_present(&self, source_file
: Lrc
<SourceFile
>) -> bool
{
1073 source_file
.add_external_src(|| {
1074 let FileName
::Real(ref name
) = source_file
.name
else {
1078 let local_path
: Cow
<'_
, Path
> = match name
{
1079 RealFileName
::LocalPath(local_path
) => local_path
.into(),
1080 RealFileName
::Remapped { local_path: Some(local_path), .. }
=> local_path
.into(),
1081 RealFileName
::Remapped { local_path: None, virtual_name }
=> {
1082 // The compiler produces better error messages if the sources of dependencies
1083 // are available. Attempt to undo any path mapping so we can find remapped
1085 // We can only use the heuristic because `add_external_src` checks the file
1087 self.path_mapping
.reverse_map_prefix_heuristically(virtual_name
)?
.into()
1091 self.file_loader
.read_file(&local_path
).ok()
1095 pub fn is_imported(&self, sp
: Span
) -> bool
{
1096 let source_file_index
= self.lookup_source_file_idx(sp
.lo());
1097 let source_file
= &self.files()[source_file_index
];
1098 source_file
.is_imported()
1101 /// Gets the span of a statement. If the statement is a macro expansion, the
1102 /// span in the context of the block span is found. The trailing semicolon is included
1103 /// on a best-effort basis.
1104 pub fn stmt_span(&self, stmt_span
: Span
, block_span
: Span
) -> Span
{
1105 if !stmt_span
.from_expansion() {
1108 let mac_call
= original_sp(stmt_span
, block_span
);
1109 self.mac_call_stmt_semi_span(mac_call
).map_or(mac_call
, |s
| mac_call
.with_hi(s
.hi()))
1112 /// Tries to find the span of the semicolon of a macro call statement.
1113 /// The input must be the *call site* span of a statement from macro expansion.
1114 /// ```ignore (illustrative)
1119 pub fn mac_call_stmt_semi_span(&self, mac_call
: Span
) -> Option
<Span
> {
1120 let span
= self.span_extend_while(mac_call
, char::is_whitespace
).ok()?
;
1121 let span
= span
.shrink_to_hi().with_hi(BytePos(span
.hi().0.checked_add(1)?
));
1122 if self.span_to_snippet(span
).as_deref() != Ok(";") {
1130 pub struct FilePathMapping
{
1131 mapping
: Vec
<(PathBuf
, PathBuf
)>,
1132 filename_display_for_diagnostics
: FileNameDisplayPreference
,
1135 impl FilePathMapping
{
1136 pub fn empty() -> FilePathMapping
{
1137 FilePathMapping
::new(Vec
::new())
1140 pub fn new(mapping
: Vec
<(PathBuf
, PathBuf
)>) -> FilePathMapping
{
1141 let filename_display_for_diagnostics
= if mapping
.is_empty() {
1142 FileNameDisplayPreference
::Local
1144 FileNameDisplayPreference
::Remapped
1147 FilePathMapping { mapping, filename_display_for_diagnostics }
1150 /// Applies any path prefix substitution as defined by the mapping.
1151 /// The return value is the remapped path and a boolean indicating whether
1152 /// the path was affected by the mapping.
1153 pub fn map_prefix
<'a
>(&'a
self, path
: impl Into
<Cow
<'a
, Path
>>) -> (Cow
<'a
, Path
>, bool
) {
1154 let path
= path
.into();
1155 if path
.as_os_str().is_empty() {
1156 // Exit early if the path is empty and therefore there's nothing to remap.
1157 // This is mostly to reduce spam for `RUSTC_LOG=[remap_path_prefix]`.
1158 return (path
, false);
1161 return remap_path_prefix(&self.mapping
, path
);
1163 #[instrument(level = "debug", skip(mapping), ret)]
1164 fn remap_path_prefix
<'a
>(
1165 mapping
: &'a
[(PathBuf
, PathBuf
)],
1166 path
: Cow
<'a
, Path
>,
1167 ) -> (Cow
<'a
, Path
>, bool
) {
1168 // NOTE: We are iterating over the mapping entries from last to first
1169 // because entries specified later on the command line should
1171 for (from
, to
) in mapping
.iter().rev() {
1172 debug
!("Trying to apply {from:?} => {to:?}");
1174 if let Ok(rest
) = path
.strip_prefix(from
) {
1175 let remapped
= if rest
.as_os_str().is_empty() {
1176 // This is subtle, joining an empty path onto e.g. `foo/bar` will
1177 // result in `foo/bar/`, that is, there'll be an additional directory
1178 // separator at the end. This can lead to duplicated directory separators
1179 // in remapped paths down the line.
1180 // So, if we have an exact match, we just return that without a call
1181 // to `Path::join()`.
1184 to
.join(rest
).into()
1186 debug
!("Match - remapped");
1188 return (remapped
, true);
1190 debug
!("No match - prefix {from:?} does not match");
1194 debug
!("not remapped");
1199 fn map_filename_prefix(&self, file
: &FileName
) -> (FileName
, bool
) {
1201 FileName
::Real(realfile
) if let RealFileName
::LocalPath(local_path
) = realfile
=> {
1202 let (mapped_path
, mapped
) = self.map_prefix(local_path
);
1203 let realfile
= if mapped
{
1204 RealFileName
::Remapped
{
1205 local_path
: Some(local_path
.clone()),
1206 virtual_name
: mapped_path
.into_owned(),
1211 (FileName
::Real(realfile
), mapped
)
1213 FileName
::Real(_
) => unreachable
!("attempted to remap an already remapped filename"),
1214 other
=> (other
.clone(), false),
1218 /// Expand a relative path to an absolute path with remapping taken into account.
1219 /// Use this when absolute paths are required (e.g. debuginfo or crate metadata).
1221 /// The resulting `RealFileName` will have its `local_path` portion erased if
1222 /// possible (i.e. if there's also a remapped path).
1223 pub fn to_embeddable_absolute_path(
1225 file_path
: RealFileName
,
1226 working_directory
: &RealFileName
,
1229 // Anything that's already remapped we don't modify, except for erasing
1230 // the `local_path` portion.
1231 RealFileName
::Remapped { local_path: _, virtual_name }
=> {
1232 RealFileName
::Remapped
{
1233 // We do not want any local path to be exported into metadata
1235 // We use the remapped name verbatim, even if it looks like a relative
1236 // path. The assumption is that the user doesn't want us to further
1237 // process paths that have gone through remapping.
1242 RealFileName
::LocalPath(unmapped_file_path
) => {
1243 // If no remapping has been applied yet, try to do so
1244 let (new_path
, was_remapped
) = self.map_prefix(unmapped_file_path
);
1246 // It was remapped, so don't modify further
1247 return RealFileName
::Remapped
{
1249 virtual_name
: new_path
.into_owned(),
1253 if new_path
.is_absolute() {
1254 // No remapping has applied to this path and it is absolute,
1255 // so the working directory cannot influence it either, so
1257 return RealFileName
::LocalPath(new_path
.into_owned());
1260 debug_assert
!(new_path
.is_relative());
1261 let unmapped_file_path_rel
= new_path
;
1263 match working_directory
{
1264 RealFileName
::LocalPath(unmapped_working_dir_abs
) => {
1265 let file_path_abs
= unmapped_working_dir_abs
.join(unmapped_file_path_rel
);
1267 // Although neither `working_directory` nor the file name were subject
1268 // to path remapping, the concatenation between the two may be. Hence
1269 // we need to do a remapping here.
1270 let (file_path_abs
, was_remapped
) = self.map_prefix(file_path_abs
);
1272 RealFileName
::Remapped
{
1273 // Erase the actual path
1275 virtual_name
: file_path_abs
.into_owned(),
1278 // No kind of remapping applied to this path, so
1279 // we leave it as it is.
1280 RealFileName
::LocalPath(file_path_abs
.into_owned())
1283 RealFileName
::Remapped
{
1285 virtual_name
: remapped_working_dir_abs
,
1287 // If working_directory has been remapped, then we emit
1288 // Remapped variant as the expanded path won't be valid
1289 RealFileName
::Remapped
{
1291 virtual_name
: Path
::new(remapped_working_dir_abs
)
1292 .join(unmapped_file_path_rel
),
1300 /// Attempts to (heuristically) reverse a prefix mapping.
1302 /// Returns [`Some`] if there is exactly one mapping where the "to" part is
1303 /// a prefix of `path` and has at least one non-empty
1304 /// [`Normal`](path::Component::Normal) component. The component
1305 /// restriction exists to avoid reverse mapping overly generic paths like
1308 /// This is a heuristic and not guaranteed to return the actual original
1309 /// path! Do not rely on the result unless you have other means to verify
1310 /// that the mapping is correct (e.g. by checking the file content hash).
1311 #[instrument(level = "debug", skip(self), ret)]
1312 fn reverse_map_prefix_heuristically(&self, path
: &Path
) -> Option
<PathBuf
> {
1313 let mut found
= None
;
1315 for (from
, to
) in self.mapping
.iter() {
1316 let has_normal_component
= to
.components().any(|c
| match c
{
1317 path
::Component
::Normal(s
) => !s
.is_empty(),
1321 if !has_normal_component
{
1325 let Ok(rest
) = path
.strip_prefix(to
) else {
1329 if found
.is_some() {
1333 found
= Some(from
.join(rest
));