]>
Commit | Line | Data |
---|---|---|
5869c6ff XL |
1 | //! Types for tracking pieces of source code within a crate. |
2 | //! | |
3 | //! The [`SourceMap`] tracks all the source code used within a single crate, mapping | |
1a4d82fc JJ |
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 | |
5869c6ff XL |
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 | |
e1599b0c | 9 | //! within the `SourceMap`, which upon request can be converted to line and column |
1a4d82fc | 10 | //! information, source code snippets, etc. |
223e47cc | 11 | |
dfeec247 | 12 | pub use crate::hygiene::{ExpnData, ExpnKind}; |
60c5eb7d | 13 | pub use crate::*; |
223e47cc | 14 | |
abe05a73 XL |
15 | use rustc_data_structures::fx::FxHashMap; |
16 | use rustc_data_structures::stable_hasher::StableHasher; | |
29967ef6 | 17 | use rustc_data_structures::sync::{AtomicU32, Lrc, MappedReadGuard, ReadGuard, RwLock}; |
abe05a73 | 18 | use std::hash::Hash; |
7cac9316 | 19 | use std::path::{Path, PathBuf}; |
74b04a01 | 20 | use std::sync::atomic::Ordering; |
17df50a5 XL |
21 | use std::{clone::Clone, cmp}; |
22 | use std::{convert::TryFrom, unreachable}; | |
223e47cc | 23 | |
3157f602 | 24 | use std::fs; |
0731742a | 25 | use std::io; |
9fa01778 | 26 | |
416331ca XL |
27 | #[cfg(test)] |
28 | mod tests; | |
29 | ||
9fa01778 | 30 | /// Returns the span itself if it doesn't come from a macro expansion, |
1a4d82fc | 31 | /// otherwise return the call site span up to the `enclosing_sp` by |
e1599b0c | 32 | /// following the `expn_data` chain. |
cc61c64b | 33 | pub fn original_sp(sp: Span, enclosing_sp: Span) -> Span { |
e1599b0c XL |
34 | let expn_data1 = sp.ctxt().outer_expn_data(); |
35 | let expn_data2 = enclosing_sp.ctxt().outer_expn_data(); | |
dfeec247 XL |
36 | if expn_data1.is_root() || !expn_data2.is_root() && expn_data1.call_site == expn_data2.call_site |
37 | { | |
e1599b0c XL |
38 | sp |
39 | } else { | |
40 | original_sp(expn_data1.call_site, enclosing_sp) | |
1a4d82fc JJ |
41 | } |
42 | } | |
223e47cc | 43 | |
f035d41b XL |
44 | pub mod monotonic { |
45 | use std::ops::{Deref, DerefMut}; | |
46 | ||
47 | /// A `MonotonicVec` is a `Vec` which can only be grown. | |
48 | /// Once inserted, an element can never be removed or swapped, | |
49 | /// guaranteeing that any indices into a `MonotonicVec` are stable | |
50 | // This is declared in its own module to ensure that the private | |
51 | // field is inaccessible | |
52 | pub struct MonotonicVec<T>(Vec<T>); | |
53 | impl<T> MonotonicVec<T> { | |
54 | pub fn new(val: Vec<T>) -> MonotonicVec<T> { | |
55 | MonotonicVec(val) | |
56 | } | |
57 | ||
58 | pub fn push(&mut self, val: T) { | |
59 | self.0.push(val); | |
60 | } | |
61 | } | |
62 | ||
63 | impl<T> Default for MonotonicVec<T> { | |
64 | fn default() -> Self { | |
65 | MonotonicVec::new(vec![]) | |
66 | } | |
67 | } | |
68 | ||
69 | impl<T> Deref for MonotonicVec<T> { | |
70 | type Target = Vec<T>; | |
71 | fn deref(&self) -> &Self::Target { | |
72 | &self.0 | |
73 | } | |
74 | } | |
75 | ||
76 | impl<T> !DerefMut for MonotonicVec<T> {} | |
77 | } | |
78 | ||
3dfed10e | 79 | #[derive(Clone, Encodable, Decodable, Debug, Copy, HashStable_Generic)] |
3157f602 XL |
80 | pub struct Spanned<T> { |
81 | pub node: T, | |
82 | pub span: Span, | |
7453a54e SL |
83 | } |
84 | ||
3157f602 | 85 | pub fn respan<T>(sp: Span, t: T) -> Spanned<T> { |
dfeec247 | 86 | Spanned { node: t, span: sp } |
223e47cc LB |
87 | } |
88 | ||
3157f602 XL |
89 | pub fn dummy_spanned<T>(t: T) -> Spanned<T> { |
90 | respan(DUMMY_SP, t) | |
1a4d82fc JJ |
91 | } |
92 | ||
c34b1796 | 93 | // _____________________________________________________________________________ |
b7449926 | 94 | // SourceFile, MultiByteChar, FileName, FileLines |
c34b1796 AL |
95 | // |
96 | ||
62682a34 SL |
97 | /// An abstraction over the fs operations used by the Parser. |
98 | pub trait FileLoader { | |
99 | /// Query the existence of a file. | |
100 | fn file_exists(&self, path: &Path) -> bool; | |
101 | ||
94222f64 | 102 | /// Read the contents of a UTF-8 file into memory. |
62682a34 SL |
103 | fn read_file(&self, path: &Path) -> io::Result<String>; |
104 | } | |
105 | ||
106 | /// A FileLoader that uses std::fs to load real files. | |
107 | pub struct RealFileLoader; | |
108 | ||
109 | impl FileLoader for RealFileLoader { | |
110 | fn file_exists(&self, path: &Path) -> bool { | |
17df50a5 | 111 | path.exists() |
62682a34 SL |
112 | } |
113 | ||
114 | fn read_file(&self, path: &Path) -> io::Result<String> { | |
0731742a | 115 | fs::read_to_string(path) |
62682a34 SL |
116 | } |
117 | } | |
c34b1796 | 118 | |
17df50a5 XL |
119 | /// This is a [SourceFile] identifier that is used to correlate source files between |
120 | /// subsequent compilation sessions (which is something we need to do during | |
121 | /// incremental compilation). | |
122 | /// | |
123 | /// The [StableSourceFileId] also contains the CrateNum of the crate the source | |
124 | /// file was originally parsed for. This way we get two separate entries in | |
125 | /// the [SourceMap] if the same file is part of both the local and an upstream | |
126 | /// crate. Trying to only have one entry for both cases is problematic because | |
127 | /// at the point where we discover that there's a local use of the file in | |
128 | /// addition to the upstream one, we might already have made decisions based on | |
129 | /// the assumption that it's an upstream file. Treating the two files as | |
130 | /// different has no real downsides. | |
3dfed10e | 131 | #[derive(Copy, Clone, PartialEq, Eq, Hash, Encodable, Decodable, Debug)] |
17df50a5 XL |
132 | pub struct StableSourceFileId { |
133 | // A hash of the source file's FileName. This is hash so that it's size | |
134 | // is more predictable than if we included the actual FileName value. | |
135 | pub file_name_hash: u64, | |
136 | ||
137 | // The CrateNum of the crate this source file was originally parsed for. | |
138 | // We cannot include this information in the hash because at the time | |
139 | // of hashing we don't have the context to map from the CrateNum's numeric | |
140 | // value to a StableCrateId. | |
141 | pub cnum: CrateNum, | |
142 | } | |
abe05a73 | 143 | |
ba9703b0 XL |
144 | // FIXME: we need a more globally consistent approach to the problem solved by |
145 | // StableSourceFileId, perhaps built atop source_file.name_hash. | |
a1dfa0c6 XL |
146 | impl StableSourceFileId { |
147 | pub fn new(source_file: &SourceFile) -> StableSourceFileId { | |
17df50a5 | 148 | StableSourceFileId::new_from_name(&source_file.name, source_file.cnum) |
0731742a XL |
149 | } |
150 | ||
17df50a5 | 151 | fn new_from_name(name: &FileName, cnum: CrateNum) -> StableSourceFileId { |
abe05a73 | 152 | let mut hasher = StableHasher::new(); |
17df50a5 XL |
153 | name.hash(&mut hasher); |
154 | StableSourceFileId { file_name_hash: hasher.finish(), cnum } | |
abe05a73 XL |
155 | } |
156 | } | |
157 | ||
c34b1796 | 158 | // _____________________________________________________________________________ |
b7449926 | 159 | // SourceMap |
c34b1796 AL |
160 | // |
161 | ||
0bf4aa26 | 162 | #[derive(Default)] |
b7449926 | 163 | pub(super) struct SourceMapFiles { |
f035d41b | 164 | source_files: monotonic::MonotonicVec<Lrc<SourceFile>>, |
dfeec247 | 165 | stable_id_to_source_file: FxHashMap<StableSourceFileId, Lrc<SourceFile>>, |
0531ce1d XL |
166 | } |
167 | ||
b7449926 | 168 | pub struct SourceMap { |
74b04a01 XL |
169 | /// The address space below this value is currently used by the files in the source map. |
170 | used_address_space: AtomicU32, | |
171 | ||
29967ef6 | 172 | files: RwLock<SourceMapFiles>, |
8faf50e0 | 173 | file_loader: Box<dyn FileLoader + Sync + Send>, |
7cac9316 | 174 | // This is used to apply the file path remapping as specified via |
e1599b0c | 175 | // `--remap-path-prefix` to all `SourceFile`s allocated within this `SourceMap`. |
7cac9316 | 176 | path_mapping: FilePathMapping, |
ba9703b0 XL |
177 | |
178 | /// The algorithm used for hashing the contents of each source file. | |
179 | hash_kind: SourceFileHashAlgorithm, | |
223e47cc LB |
180 | } |
181 | ||
b7449926 XL |
182 | impl SourceMap { |
183 | pub fn new(path_mapping: FilePathMapping) -> SourceMap { | |
ba9703b0 XL |
184 | Self::with_file_loader_and_hash_kind( |
185 | Box::new(RealFileLoader), | |
74b04a01 | 186 | path_mapping, |
ba9703b0 XL |
187 | SourceFileHashAlgorithm::Md5, |
188 | ) | |
62682a34 SL |
189 | } |
190 | ||
ba9703b0 | 191 | pub fn with_file_loader_and_hash_kind( |
dfeec247 XL |
192 | file_loader: Box<dyn FileLoader + Sync + Send>, |
193 | path_mapping: FilePathMapping, | |
ba9703b0 | 194 | hash_kind: SourceFileHashAlgorithm, |
dfeec247 | 195 | ) -> SourceMap { |
74b04a01 XL |
196 | SourceMap { |
197 | used_address_space: AtomicU32::new(0), | |
198 | files: Default::default(), | |
199 | file_loader, | |
200 | path_mapping, | |
ba9703b0 | 201 | hash_kind, |
74b04a01 | 202 | } |
223e47cc LB |
203 | } |
204 | ||
7cac9316 XL |
205 | pub fn path_mapping(&self) -> &FilePathMapping { |
206 | &self.path_mapping | |
207 | } | |
208 | ||
62682a34 SL |
209 | pub fn file_exists(&self, path: &Path) -> bool { |
210 | self.file_loader.file_exists(path) | |
211 | } | |
212 | ||
b7449926 | 213 | pub fn load_file(&self, path: &Path) -> io::Result<Lrc<SourceFile>> { |
54a0048b | 214 | let src = self.file_loader.read_file(path)?; |
0731742a | 215 | let filename = path.to_owned().into(); |
b7449926 | 216 | Ok(self.new_source_file(filename, src)) |
7cac9316 XL |
217 | } |
218 | ||
e1599b0c XL |
219 | /// Loads source file as a binary blob. |
220 | /// | |
221 | /// Unlike `load_file`, guarantees that no normalization like BOM-removal | |
222 | /// takes place. | |
223 | pub fn load_binary_file(&self, path: &Path) -> io::Result<Vec<u8>> { | |
224 | // Ideally, this should use `self.file_loader`, but it can't | |
225 | // deal with binary files yet. | |
226 | let bytes = fs::read(path)?; | |
227 | ||
228 | // We need to add file to the `SourceMap`, so that it is present | |
229 | // in dep-info. There's also an edge case that file might be both | |
230 | // loaded as a binary via `include_bytes!` and as proper `SourceFile` | |
231 | // via `mod`, so we try to use real file contents and not just an | |
232 | // empty string. | |
dfeec247 | 233 | let text = std::str::from_utf8(&bytes).unwrap_or("").to_string(); |
e1599b0c XL |
234 | self.new_source_file(path.to_owned().into(), text); |
235 | Ok(bytes) | |
236 | } | |
237 | ||
f035d41b XL |
238 | // By returning a `MonotonicVec`, we ensure that consumers cannot invalidate |
239 | // any existing indices pointing into `files`. | |
29967ef6 XL |
240 | pub fn files(&self) -> MappedReadGuard<'_, monotonic::MonotonicVec<Lrc<SourceFile>>> { |
241 | ReadGuard::map(self.files.borrow(), |files| &files.source_files) | |
62682a34 SL |
242 | } |
243 | ||
dfeec247 XL |
244 | pub fn source_file_by_stable_id( |
245 | &self, | |
246 | stable_id: StableSourceFileId, | |
247 | ) -> Option<Lrc<SourceFile>> { | |
74b04a01 XL |
248 | self.files.borrow().stable_id_to_source_file.get(&stable_id).cloned() |
249 | } | |
250 | ||
251 | fn allocate_address_space(&self, size: usize) -> Result<usize, OffsetOverflowError> { | |
252 | let size = u32::try_from(size).map_err(|_| OffsetOverflowError)?; | |
253 | ||
254 | loop { | |
255 | let current = self.used_address_space.load(Ordering::Relaxed); | |
256 | let next = current | |
257 | .checked_add(size) | |
258 | // Add one so there is some space between files. This lets us distinguish | |
259 | // positions in the `SourceMap`, even in the presence of zero-length files. | |
260 | .and_then(|next| next.checked_add(1)) | |
261 | .ok_or(OffsetOverflowError)?; | |
262 | ||
263 | if self | |
264 | .used_address_space | |
265 | .compare_exchange(current, next, Ordering::Relaxed, Ordering::Relaxed) | |
266 | .is_ok() | |
267 | { | |
268 | return Ok(usize::try_from(current).unwrap()); | |
269 | } | |
c1a9b12d SL |
270 | } |
271 | } | |
272 | ||
e1599b0c XL |
273 | /// Creates a new `SourceFile`. |
274 | /// If a file already exists in the `SourceMap` with the same ID, that file is returned | |
275 | /// unmodified. | |
b7449926 | 276 | pub fn new_source_file(&self, filename: FileName, src: String) -> Lrc<SourceFile> { |
dfeec247 XL |
277 | self.try_new_source_file(filename, src).unwrap_or_else(|OffsetOverflowError| { |
278 | eprintln!("fatal error: rustc does not support files larger than 4GB"); | |
279 | crate::fatal_error::FatalError.raise() | |
280 | }) | |
dc9dc135 XL |
281 | } |
282 | ||
283 | fn try_new_source_file( | |
284 | &self, | |
17df50a5 | 285 | filename: FileName, |
dfeec247 | 286 | src: String, |
dc9dc135 | 287 | ) -> Result<Lrc<SourceFile>, OffsetOverflowError> { |
ea8adc8c XL |
288 | // Note that filename may not be a valid path, eg it may be `<anon>` etc, |
289 | // but this is okay because the directory determined by `path.pop()` will | |
290 | // be empty, so the working directory will be used. | |
17df50a5 | 291 | let (filename, _) = self.path_mapping.map_filename_prefix(&filename); |
223e47cc | 292 | |
17df50a5 | 293 | let file_id = StableSourceFileId::new_from_name(&filename, LOCAL_CRATE); |
1a4d82fc | 294 | |
dc9dc135 | 295 | let lrc_sf = match self.source_file_by_stable_id(file_id) { |
0731742a XL |
296 | Some(lrc_sf) => lrc_sf, |
297 | None => { | |
74b04a01 XL |
298 | let start_pos = self.allocate_address_space(src.len())?; |
299 | ||
0731742a XL |
300 | let source_file = Lrc::new(SourceFile::new( |
301 | filename, | |
0731742a XL |
302 | src, |
303 | Pos::from_usize(start_pos), | |
ba9703b0 | 304 | self.hash_kind, |
74b04a01 | 305 | )); |
abe05a73 | 306 | |
17df50a5 XL |
307 | // Let's make sure the file_id we generated above actually matches |
308 | // the ID we generate for the SourceFile we just created. | |
309 | debug_assert_eq!(StableSourceFileId::new(&source_file), file_id); | |
310 | ||
0731742a XL |
311 | let mut files = self.files.borrow_mut(); |
312 | ||
313 | files.source_files.push(source_file.clone()); | |
314 | files.stable_id_to_source_file.insert(file_id, source_file.clone()); | |
315 | ||
316 | source_file | |
317 | } | |
dc9dc135 XL |
318 | }; |
319 | Ok(lrc_sf) | |
223e47cc LB |
320 | } |
321 | ||
e1599b0c XL |
322 | /// Allocates a new `SourceFile` representing a source file from an external |
323 | /// crate. The source code of such an "imported `SourceFile`" is not available, | |
c34b1796 AL |
324 | /// but we still know enough to generate accurate debuginfo location |
325 | /// information for things inlined from other crates. | |
b7449926 XL |
326 | pub fn new_imported_source_file( |
327 | &self, | |
328 | filename: FileName, | |
ba9703b0 | 329 | src_hash: SourceFileHash, |
b7449926 XL |
330 | name_hash: u128, |
331 | source_len: usize, | |
ba9703b0 | 332 | cnum: CrateNum, |
923072b8 | 333 | file_local_lines: Lock<SourceFileLines>, |
b7449926 XL |
334 | mut file_local_multibyte_chars: Vec<MultiByteChar>, |
335 | mut file_local_non_narrow_chars: Vec<NonNarrowChar>, | |
e74abb32 | 336 | mut file_local_normalized_pos: Vec<NormalizedPos>, |
ba9703b0 | 337 | original_start_pos: BytePos, |
f2b60f7d | 338 | metadata_index: u32, |
b7449926 | 339 | ) -> Lrc<SourceFile> { |
74b04a01 XL |
340 | let start_pos = self |
341 | .allocate_address_space(source_len) | |
342 | .expect("not enough address space for imported source file"); | |
c34b1796 AL |
343 | |
344 | let end_pos = Pos::from_usize(start_pos + source_len); | |
345 | let start_pos = Pos::from_usize(start_pos); | |
346 | ||
923072b8 FG |
347 | // Translate these positions into the new global frame of reference, |
348 | // now that the offset of the SourceFile is known. | |
349 | // | |
350 | // These are all unsigned values. `original_start_pos` may be larger or | |
351 | // smaller than `start_pos`, but `pos` is always larger than both. | |
352 | // Therefore, `(pos - original_start_pos) + start_pos` won't overflow | |
353 | // but `start_pos - original_start_pos` might. So we use the former | |
354 | // form rather than pre-computing the offset into a local variable. The | |
355 | // compiler backend can optimize away the repeated computations in a | |
356 | // way that won't trigger overflow checks. | |
357 | match &mut *file_local_lines.borrow_mut() { | |
358 | SourceFileLines::Lines(lines) => { | |
359 | for pos in lines { | |
360 | *pos = (*pos - original_start_pos) + start_pos; | |
361 | } | |
362 | } | |
363 | SourceFileLines::Diffs(SourceFileDiffs { line_start, .. }) => { | |
364 | *line_start = (*line_start - original_start_pos) + start_pos; | |
365 | } | |
d9579d0f | 366 | } |
d9579d0f | 367 | for mbc in &mut file_local_multibyte_chars { |
923072b8 | 368 | mbc.pos = (mbc.pos - original_start_pos) + start_pos; |
d9579d0f | 369 | } |
abe05a73 | 370 | for swc in &mut file_local_non_narrow_chars { |
923072b8 | 371 | *swc = (*swc - original_start_pos) + start_pos; |
abe05a73 | 372 | } |
e74abb32 | 373 | for nc in &mut file_local_normalized_pos { |
923072b8 | 374 | nc.pos = (nc.pos - original_start_pos) + start_pos; |
e74abb32 XL |
375 | } |
376 | ||
b7449926 | 377 | let source_file = Lrc::new(SourceFile { |
c34b1796 AL |
378 | name: filename, |
379 | src: None, | |
3b2f2976 | 380 | src_hash, |
ba9703b0 XL |
381 | external_src: Lock::new(ExternalSource::Foreign { |
382 | kind: ExternalSourceKind::AbsentOk, | |
f2b60f7d | 383 | metadata_index, |
ba9703b0 | 384 | }), |
3b2f2976 XL |
385 | start_pos, |
386 | end_pos, | |
8faf50e0 XL |
387 | lines: file_local_lines, |
388 | multibyte_chars: file_local_multibyte_chars, | |
389 | non_narrow_chars: file_local_non_narrow_chars, | |
e74abb32 | 390 | normalized_pos: file_local_normalized_pos, |
ff7c6d11 | 391 | name_hash, |
ba9703b0 | 392 | cnum, |
c34b1796 AL |
393 | }); |
394 | ||
0531ce1d | 395 | let mut files = self.files.borrow_mut(); |
c34b1796 | 396 | |
a1dfa0c6 | 397 | files.source_files.push(source_file.clone()); |
dfeec247 XL |
398 | files |
399 | .stable_id_to_source_file | |
400 | .insert(StableSourceFileId::new(&source_file), source_file.clone()); | |
abe05a73 | 401 | |
b7449926 | 402 | source_file |
c34b1796 AL |
403 | } |
404 | ||
e1599b0c | 405 | // If there is a doctest offset, applies it to the line. |
0731742a | 406 | pub fn doctest_offset_line(&self, file: &FileName, orig: usize) -> usize { |
ba9703b0 | 407 | match file { |
0731742a | 408 | FileName::DocTest(_, offset) => { |
ba9703b0 | 409 | if *offset < 0 { |
0731742a | 410 | orig - (-(*offset)) as usize |
ba9703b0 XL |
411 | } else { |
412 | orig + *offset as usize | |
413 | } | |
dfeec247 XL |
414 | } |
415 | _ => orig, | |
ba9703b0 | 416 | } |
2c00a5a8 XL |
417 | } |
418 | ||
29967ef6 XL |
419 | /// Return the SourceFile that contains the given `BytePos` |
420 | pub fn lookup_source_file(&self, pos: BytePos) -> Lrc<SourceFile> { | |
421 | let idx = self.lookup_source_file_idx(pos); | |
422 | (*self.files.borrow().source_files)[idx].clone() | |
423 | } | |
424 | ||
e1599b0c | 425 | /// Looks up source information about a `BytePos`. |
970d7e83 | 426 | pub fn lookup_char_pos(&self, pos: BytePos) -> Loc { |
29967ef6 XL |
427 | let sf = self.lookup_source_file(pos); |
428 | let (line, col, col_display) = sf.lookup_file_pos_with_col_display(pos); | |
429 | Loc { file: sf, line, col, col_display } | |
d9579d0f AL |
430 | } |
431 | ||
e1599b0c | 432 | // If the corresponding `SourceFile` is empty, does not return a line number. |
b7449926 | 433 | pub fn lookup_line(&self, pos: BytePos) -> Result<SourceFileAndLine, Lrc<SourceFile>> { |
29967ef6 | 434 | let f = self.lookup_source_file(pos); |
c1a9b12d | 435 | |
9e0c209e | 436 | match f.lookup_line(pos) { |
dc9dc135 | 437 | Some(line) => Ok(SourceFileAndLine { sf: f, line }), |
dfeec247 | 438 | None => Err(f), |
c1a9b12d | 439 | } |
223e47cc LB |
440 | } |
441 | ||
94222f64 | 442 | fn span_to_string(&self, sp: Span, filename_display_pref: FileNameDisplayPreference) -> String { |
17df50a5 | 443 | if self.files.borrow().source_files.is_empty() || sp.is_dummy() { |
1a4d82fc | 444 | return "no-location".to_string(); |
223e47cc LB |
445 | } |
446 | ||
48663c56 XL |
447 | let lo = self.lookup_char_pos(sp.lo()); |
448 | let hi = self.lookup_char_pos(sp.hi()); | |
dfeec247 XL |
449 | format!( |
450 | "{}:{}:{}: {}:{}", | |
94222f64 | 451 | lo.file.name.display(filename_display_pref), |
e1599b0c XL |
452 | lo.line, |
453 | lo.col.to_usize() + 1, | |
454 | hi.line, | |
455 | hi.col.to_usize() + 1, | |
456 | ) | |
223e47cc LB |
457 | } |
458 | ||
17df50a5 XL |
459 | /// Format the span location suitable for embedding in build artifacts |
460 | pub fn span_to_embeddable_string(&self, sp: Span) -> String { | |
94222f64 | 461 | self.span_to_string(sp, FileNameDisplayPreference::Remapped) |
ea8adc8c XL |
462 | } |
463 | ||
064997fb FG |
464 | /// Format the span location suitable for pretty printing anotations with relative line numbers |
465 | pub fn span_to_relative_line_string(&self, sp: Span, relative_to: Span) -> String { | |
466 | if self.files.borrow().source_files.is_empty() || sp.is_dummy() || relative_to.is_dummy() { | |
467 | return "no-location".to_string(); | |
468 | } | |
469 | ||
470 | let lo = self.lookup_char_pos(sp.lo()); | |
471 | let hi = self.lookup_char_pos(sp.hi()); | |
472 | let offset = self.lookup_char_pos(relative_to.lo()); | |
473 | ||
f2b60f7d | 474 | if lo.file.name != offset.file.name || !relative_to.contains(sp) { |
064997fb FG |
475 | return self.span_to_embeddable_string(sp); |
476 | } | |
477 | ||
478 | let lo_line = lo.line.saturating_sub(offset.line); | |
479 | let hi_line = hi.line.saturating_sub(offset.line); | |
480 | ||
481 | format!( | |
482 | "{}:+{}:{}: +{}:{}", | |
483 | lo.file.name.display(FileNameDisplayPreference::Remapped), | |
484 | lo_line, | |
485 | lo.col.to_usize() + 1, | |
486 | hi_line, | |
487 | hi.col.to_usize() + 1, | |
488 | ) | |
489 | } | |
490 | ||
17df50a5 XL |
491 | /// Format the span location to be printed in diagnostics. Must not be emitted |
492 | /// to build artifacts as this may leak local file paths. Use span_to_embeddable_string | |
493 | /// for string suitable for embedding. | |
494 | pub fn span_to_diagnostic_string(&self, sp: Span) -> String { | |
94222f64 | 495 | self.span_to_string(sp, self.path_mapping.filename_display_for_diagnostics) |
17df50a5 XL |
496 | } |
497 | ||
498 | pub fn span_to_filename(&self, sp: Span) -> FileName { | |
499 | self.lookup_char_pos(sp.lo()).file.name.clone() | |
223e47cc LB |
500 | } |
501 | ||
94222f64 XL |
502 | pub fn filename_for_diagnostics<'a>(&self, filename: &'a FileName) -> FileNameDisplay<'a> { |
503 | filename.display(self.path_mapping.filename_display_for_diagnostics) | |
504 | } | |
505 | ||
ff7c6d11 | 506 | pub fn is_multiline(&self, sp: Span) -> bool { |
136023e0 XL |
507 | let lo = self.lookup_source_file_idx(sp.lo()); |
508 | let hi = self.lookup_source_file_idx(sp.hi()); | |
509 | if lo != hi { | |
510 | return true; | |
511 | } | |
512 | let f = (*self.files.borrow().source_files)[lo].clone(); | |
513 | f.lookup_line(sp.lo()) != f.lookup_line(sp.hi()) | |
ff7c6d11 XL |
514 | } |
515 | ||
c295e0f8 | 516 | #[instrument(skip(self), level = "trace")] |
60c5eb7d | 517 | pub fn is_valid_span(&self, sp: Span) -> Result<(Loc, Loc), SpanLinesError> { |
ea8adc8c | 518 | let lo = self.lookup_char_pos(sp.lo()); |
c295e0f8 | 519 | trace!(?lo); |
ea8adc8c | 520 | let hi = self.lookup_char_pos(sp.hi()); |
c295e0f8 | 521 | trace!(?hi); |
d9579d0f AL |
522 | if lo.file.start_pos != hi.file.start_pos { |
523 | return Err(SpanLinesError::DistinctSources(DistinctSources { | |
524 | begin: (lo.file.name.clone(), lo.file.start_pos), | |
525 | end: (hi.file.name.clone(), hi.file.start_pos), | |
526 | })); | |
527 | } | |
60c5eb7d XL |
528 | Ok((lo, hi)) |
529 | } | |
530 | ||
ba9703b0 XL |
531 | pub fn is_line_before_span_empty(&self, sp: Span) -> bool { |
532 | match self.span_to_prev_source(sp) { | |
6a06907d | 533 | Ok(s) => s.rsplit_once('\n').unwrap_or(("", &s)).1.trim_start().is_empty(), |
ba9703b0 XL |
534 | Err(_) => false, |
535 | } | |
536 | } | |
537 | ||
60c5eb7d XL |
538 | pub fn span_to_lines(&self, sp: Span) -> FileLinesResult { |
539 | debug!("span_to_lines(sp={:?})", sp); | |
540 | let (lo, hi) = self.is_valid_span(sp)?; | |
d9579d0f AL |
541 | assert!(hi.line >= lo.line); |
542 | ||
ba9703b0 XL |
543 | if sp.is_dummy() { |
544 | return Ok(FileLines { file: lo.file, lines: Vec::new() }); | |
545 | } | |
546 | ||
9346a6ac AL |
547 | let mut lines = Vec::with_capacity(hi.line - lo.line + 1); |
548 | ||
549 | // The span starts partway through the first line, | |
550 | // but after that it starts from offset 0. | |
551 | let mut start_col = lo.col; | |
552 | ||
553 | // For every line but the last, it extends from `start_col` | |
554 | // and to the end of the line. Be careful because the line | |
555 | // numbers in Loc are 1-based, so we subtract 1 to get 0-based | |
556 | // lines. | |
ba9703b0 XL |
557 | // |
558 | // FIXME: now that we handle DUMMY_SP up above, we should consider | |
559 | // asserting that the line numbers here are all indeed 1-based. | |
dfeec247 XL |
560 | let hi_line = hi.line.saturating_sub(1); |
561 | for line_index in lo.line.saturating_sub(1)..hi_line { | |
5869c6ff | 562 | let line_len = lo.file.get_line(line_index).map_or(0, |s| s.chars().count()); |
dfeec247 | 563 | lines.push(LineInfo { line_index, start_col, end_col: CharPos::from_usize(line_len) }); |
9346a6ac AL |
564 | start_col = CharPos::from_usize(0); |
565 | } | |
566 | ||
567 | // For the last line, it extends from `start_col` to `hi.col`: | |
dfeec247 | 568 | lines.push(LineInfo { line_index: hi_line, start_col, end_col: hi.col }); |
9346a6ac | 569 | |
dfeec247 | 570 | Ok(FileLines { file: lo.file, lines }) |
223e47cc LB |
571 | } |
572 | ||
9fa01778 | 573 | /// Extracts the source surrounding the given `Span` using the `extract_source` function. The |
0531ce1d XL |
574 | /// extract function takes three arguments: a string slice containing the source, an index in |
575 | /// the slice for the beginning of the span and an index in the slice for the end of the span. | |
5869c6ff | 576 | fn span_to_source<F, T>(&self, sp: Span, extract_source: F) -> Result<T, SpanSnippetError> |
dfeec247 | 577 | where |
5869c6ff | 578 | F: Fn(&str, usize, usize) -> Result<T, SpanSnippetError>, |
0531ce1d | 579 | { |
ea8adc8c XL |
580 | let local_begin = self.lookup_byte_offset(sp.lo()); |
581 | let local_end = self.lookup_byte_offset(sp.hi()); | |
1a4d82fc | 582 | |
a1dfa0c6 | 583 | if local_begin.sf.start_pos != local_end.sf.start_pos { |
ba9703b0 | 584 | Err(SpanSnippetError::DistinctSources(DistinctSources { |
dfeec247 XL |
585 | begin: (local_begin.sf.name.clone(), local_begin.sf.start_pos), |
586 | end: (local_end.sf.name.clone(), local_end.sf.start_pos), | |
ba9703b0 | 587 | })) |
1a4d82fc | 588 | } else { |
a1dfa0c6 | 589 | self.ensure_source_file_source_present(local_begin.sf.clone()); |
041b39d2 XL |
590 | |
591 | let start_index = local_begin.pos.to_usize(); | |
592 | let end_index = local_end.pos.to_usize(); | |
dfeec247 | 593 | let source_len = (local_begin.sf.end_pos - local_begin.sf.start_pos).to_usize(); |
041b39d2 XL |
594 | |
595 | if start_index > end_index || end_index > source_len { | |
dfeec247 XL |
596 | return Err(SpanSnippetError::MalformedForSourcemap(MalformedSourceMapPositions { |
597 | name: local_begin.sf.name.clone(), | |
598 | source_len, | |
599 | begin_pos: local_begin.pos, | |
600 | end_pos: local_end.pos, | |
601 | })); | |
041b39d2 XL |
602 | } |
603 | ||
a1dfa0c6 | 604 | if let Some(ref src) = local_begin.sf.src { |
ba9703b0 | 605 | extract_source(src, start_index, end_index) |
a1dfa0c6 | 606 | } else if let Some(src) = local_begin.sf.external_src.borrow().get_source() { |
ba9703b0 | 607 | extract_source(src, start_index, end_index) |
041b39d2 | 608 | } else { |
ba9703b0 | 609 | Err(SpanSnippetError::SourceNotAvailable { filename: local_begin.sf.name.clone() }) |
c34b1796 | 610 | } |
1a4d82fc | 611 | } |
223e47cc LB |
612 | } |
613 | ||
064997fb FG |
614 | pub fn is_span_accessible(&self, sp: Span) -> bool { |
615 | self.span_to_source(sp, |src, start_index, end_index| { | |
616 | Ok(src.get(start_index..end_index).is_some()) | |
617 | }) | |
618 | .map_or(false, |is_accessible| is_accessible) | |
94222f64 XL |
619 | } |
620 | ||
e1599b0c | 621 | /// Returns the source snippet as `String` corresponding to the given `Span`. |
0531ce1d | 622 | pub fn span_to_snippet(&self, sp: Span) -> Result<String, SpanSnippetError> { |
dfeec247 XL |
623 | self.span_to_source(sp, |src, start_index, end_index| { |
624 | src.get(start_index..end_index) | |
625 | .map(|s| s.to_string()) | |
fc512014 | 626 | .ok_or(SpanSnippetError::IllFormedSpan(sp)) |
dfeec247 | 627 | }) |
0531ce1d XL |
628 | } |
629 | ||
b7449926 | 630 | pub fn span_to_margin(&self, sp: Span) -> Option<usize> { |
3c0e092e XL |
631 | Some(self.indentation_before(sp)?.len()) |
632 | } | |
6a06907d | 633 | |
3c0e092e XL |
634 | pub fn indentation_before(&self, sp: Span) -> Option<String> { |
635 | self.span_to_source(sp, |src, start_index, _| { | |
636 | let before = &src[..start_index]; | |
637 | let last_line = before.rsplit_once('\n').map_or(before, |(_, last)| last); | |
638 | Ok(last_line | |
639 | .split_once(|c: char| !c.is_whitespace()) | |
640 | .map_or(last_line, |(indent, _)| indent) | |
641 | .to_string()) | |
642 | }) | |
643 | .ok() | |
b7449926 XL |
644 | } |
645 | ||
e1599b0c | 646 | /// Returns the source snippet as `String` before the given `Span`. |
0531ce1d | 647 | pub fn span_to_prev_source(&self, sp: Span) -> Result<String, SpanSnippetError> { |
dfeec247 | 648 | self.span_to_source(sp, |src, start_index, _| { |
fc512014 | 649 | src.get(..start_index).map(|s| s.to_string()).ok_or(SpanSnippetError::IllFormedSpan(sp)) |
dfeec247 | 650 | }) |
0531ce1d XL |
651 | } |
652 | ||
e1599b0c | 653 | /// Extends the given `Span` to just after the previous occurrence of `c`. Return the same span |
0531ce1d | 654 | /// if no character could be found or if an error occurred while retrieving the code snippet. |
5869c6ff | 655 | pub fn span_extend_to_prev_char(&self, sp: Span, c: char, accept_newlines: bool) -> Span { |
0531ce1d | 656 | if let Ok(prev_source) = self.span_to_prev_source(sp) { |
5869c6ff | 657 | let prev_source = prev_source.rsplit(c).next().unwrap_or(""); |
6a06907d | 658 | if !prev_source.is_empty() && (accept_newlines || !prev_source.contains('\n')) { |
0531ce1d XL |
659 | return sp.with_lo(BytePos(sp.lo().0 - prev_source.len() as u32)); |
660 | } | |
661 | } | |
662 | ||
663 | sp | |
664 | } | |
665 | ||
e1599b0c | 666 | /// Extends the given `Span` to just after the previous occurrence of `pat` when surrounded by |
5099ac24 FG |
667 | /// whitespace. Returns None if the pattern could not be found or if an error occurred while |
668 | /// retrieving the code snippet. | |
669 | pub fn span_extend_to_prev_str( | |
670 | &self, | |
671 | sp: Span, | |
672 | pat: &str, | |
673 | accept_newlines: bool, | |
674 | include_whitespace: bool, | |
675 | ) -> Option<Span> { | |
0531ce1d XL |
676 | // assure that the pattern is delimited, to avoid the following |
677 | // fn my_fn() | |
678 | // ^^^^ returned span without the check | |
679 | // ---------- correct span | |
5099ac24 | 680 | let prev_source = self.span_to_prev_source(sp).ok()?; |
0531ce1d XL |
681 | for ws in &[" ", "\t", "\n"] { |
682 | let pat = pat.to_owned() + ws; | |
5099ac24 FG |
683 | if let Some(pat_pos) = prev_source.rfind(&pat) { |
684 | let just_after_pat_pos = pat_pos + pat.len() - 1; | |
685 | let just_after_pat_plus_ws = if include_whitespace { | |
686 | just_after_pat_pos | |
687 | + prev_source[just_after_pat_pos..] | |
688 | .find(|c: char| !c.is_whitespace()) | |
689 | .unwrap_or(0) | |
690 | } else { | |
691 | just_after_pat_pos | |
692 | }; | |
693 | let len = prev_source.len() - just_after_pat_plus_ws; | |
694 | let prev_source = &prev_source[just_after_pat_plus_ws..]; | |
695 | if accept_newlines || !prev_source.trim_start().contains('\n') { | |
696 | return Some(sp.with_lo(BytePos(sp.lo().0 - len as u32))); | |
0531ce1d XL |
697 | } |
698 | } | |
699 | } | |
700 | ||
5099ac24 | 701 | None |
0531ce1d XL |
702 | } |
703 | ||
5869c6ff XL |
704 | /// Returns the source snippet as `String` after the given `Span`. |
705 | pub fn span_to_next_source(&self, sp: Span) -> Result<String, SpanSnippetError> { | |
706 | self.span_to_source(sp, |src, _, end_index| { | |
707 | src.get(end_index..).map(|s| s.to_string()).ok_or(SpanSnippetError::IllFormedSpan(sp)) | |
708 | }) | |
709 | } | |
710 | ||
c295e0f8 XL |
711 | /// Extends the given `Span` while the next character matches the predicate |
712 | pub fn span_extend_while( | |
713 | &self, | |
714 | span: Span, | |
715 | f: impl Fn(char) -> bool, | |
716 | ) -> Result<Span, SpanSnippetError> { | |
717 | self.span_to_source(span, |s, _start, end| { | |
718 | let n = s[end..].char_indices().find(|&(_, c)| !f(c)).map_or(s.len() - end, |(i, _)| i); | |
719 | Ok(span.with_hi(span.hi() + BytePos(n as u32))) | |
720 | }) | |
721 | } | |
722 | ||
f2b60f7d | 723 | /// Extends the given `Span` to just before the next occurrence of `c`. |
5869c6ff XL |
724 | pub fn span_extend_to_next_char(&self, sp: Span, c: char, accept_newlines: bool) -> Span { |
725 | if let Ok(next_source) = self.span_to_next_source(sp) { | |
726 | let next_source = next_source.split(c).next().unwrap_or(""); | |
6a06907d | 727 | if !next_source.is_empty() && (accept_newlines || !next_source.contains('\n')) { |
5869c6ff XL |
728 | return sp.with_hi(BytePos(sp.hi().0 + next_source.len() as u32)); |
729 | } | |
730 | } | |
731 | ||
732 | sp | |
733 | } | |
734 | ||
064997fb FG |
735 | /// Extends the given `Span` to contain the entire line it is on. |
736 | pub fn span_extend_to_line(&self, sp: Span) -> Span { | |
737 | self.span_extend_to_prev_char(self.span_extend_to_next_char(sp, '\n', true), '\n', true) | |
738 | } | |
739 | ||
e1599b0c XL |
740 | /// Given a `Span`, tries to get a shorter span ending before the first occurrence of `char` |
741 | /// `c`. | |
cc61c64b XL |
742 | pub fn span_until_char(&self, sp: Span, c: char) -> Span { |
743 | match self.span_to_snippet(sp) { | |
744 | Ok(snippet) => { | |
74b04a01 | 745 | let snippet = snippet.split(c).next().unwrap_or("").trim_end(); |
7cac9316 | 746 | if !snippet.is_empty() && !snippet.contains('\n') { |
ea8adc8c | 747 | sp.with_hi(BytePos(sp.lo().0 + snippet.len() as u32)) |
cc61c64b XL |
748 | } else { |
749 | sp | |
750 | } | |
751 | } | |
752 | _ => sp, | |
753 | } | |
754 | } | |
755 | ||
e1599b0c | 756 | /// Given a `Span`, tries to get a shorter span ending just after the first occurrence of `char` |
0531ce1d XL |
757 | /// `c`. |
758 | pub fn span_through_char(&self, sp: Span, c: char) -> Span { | |
759 | if let Ok(snippet) = self.span_to_snippet(sp) { | |
760 | if let Some(offset) = snippet.find(c) { | |
761 | return sp.with_hi(BytePos(sp.lo().0 + (offset + c.len_utf8()) as u32)); | |
762 | } | |
763 | } | |
764 | sp | |
765 | } | |
766 | ||
e1599b0c XL |
767 | /// Given a `Span`, gets a new `Span` covering the first token and all its trailing whitespace |
768 | /// or the original `Span`. | |
2c00a5a8 XL |
769 | /// |
770 | /// If `sp` points to `"let mut x"`, then a span pointing at `"let "` will be returned. | |
771 | pub fn span_until_non_whitespace(&self, sp: Span) -> Span { | |
0531ce1d XL |
772 | let mut whitespace_found = false; |
773 | ||
774 | self.span_take_while(sp, |c| { | |
775 | if !whitespace_found && c.is_whitespace() { | |
776 | whitespace_found = true; | |
2c00a5a8 | 777 | } |
0531ce1d | 778 | |
74b04a01 | 779 | !whitespace_found || c.is_whitespace() |
0531ce1d | 780 | }) |
2c00a5a8 XL |
781 | } |
782 | ||
e1599b0c XL |
783 | /// Given a `Span`, gets a new `Span` covering the first token without its trailing whitespace |
784 | /// or the original `Span` in case of error. | |
0531ce1d XL |
785 | /// |
786 | /// If `sp` points to `"let mut x"`, then a span pointing at `"let"` will be returned. | |
787 | pub fn span_until_whitespace(&self, sp: Span) -> Span { | |
788 | self.span_take_while(sp, |c| !c.is_whitespace()) | |
789 | } | |
790 | ||
e1599b0c | 791 | /// Given a `Span`, gets a shorter one until `predicate` yields `false`. |
0531ce1d | 792 | pub fn span_take_while<P>(&self, sp: Span, predicate: P) -> Span |
dfeec247 XL |
793 | where |
794 | P: for<'r> FnMut(&'r char) -> bool, | |
0531ce1d | 795 | { |
abe05a73 | 796 | if let Ok(snippet) = self.span_to_snippet(sp) { |
dfeec247 | 797 | let offset = snippet.chars().take_while(predicate).map(|c| c.len_utf8()).sum::<usize>(); |
0531ce1d XL |
798 | |
799 | sp.with_hi(BytePos(sp.lo().0 + (offset as u32))) | |
800 | } else { | |
801 | sp | |
abe05a73 | 802 | } |
abe05a73 XL |
803 | } |
804 | ||
ba9703b0 XL |
805 | /// Given a `Span`, return a span ending in the closest `{`. This is useful when you have a |
806 | /// `Span` enclosing a whole item but we need to point at only the head (usually the first | |
807 | /// line) of that item. | |
808 | /// | |
809 | /// *Only suitable for diagnostics.* | |
810 | pub fn guess_head_span(&self, sp: Span) -> Span { | |
811 | // FIXME: extend the AST items to have a head span, or replace callers with pointing at | |
812 | // the item's ident when appropriate. | |
cc61c64b XL |
813 | self.span_until_char(sp, '{') |
814 | } | |
815 | ||
6a06907d | 816 | /// Returns a new span representing just the first character of the given span. |
8faf50e0 | 817 | pub fn start_point(&self, sp: Span) -> Span { |
6a06907d XL |
818 | let width = { |
819 | let sp = sp.data(); | |
820 | let local_begin = self.lookup_byte_offset(sp.lo); | |
821 | let start_index = local_begin.pos.to_usize(); | |
822 | let src = local_begin.sf.external_src.borrow(); | |
823 | ||
824 | let snippet = if let Some(ref src) = local_begin.sf.src { | |
825 | Some(&src[start_index..]) | |
826 | } else if let Some(src) = src.get_source() { | |
827 | Some(&src[start_index..]) | |
828 | } else { | |
829 | None | |
830 | }; | |
831 | ||
832 | match snippet { | |
833 | None => 1, | |
834 | Some(snippet) => match snippet.chars().next() { | |
835 | None => 1, | |
836 | Some(c) => c.len_utf8(), | |
837 | }, | |
838 | } | |
839 | }; | |
840 | ||
841 | sp.with_hi(BytePos(sp.lo().0 + width as u32)) | |
8faf50e0 XL |
842 | } |
843 | ||
6a06907d | 844 | /// Returns a new span representing just the last character of this span. |
2c00a5a8 XL |
845 | pub fn end_point(&self, sp: Span) -> Span { |
846 | let pos = sp.hi().0; | |
847 | ||
848 | let width = self.find_width_of_character_at_span(sp, false); | |
849 | let corrected_end_position = pos.checked_sub(width).unwrap_or(pos); | |
850 | ||
851 | let end_point = BytePos(cmp::max(corrected_end_position, sp.lo().0)); | |
852 | sp.with_lo(end_point) | |
853 | } | |
854 | ||
e1599b0c | 855 | /// Returns a new span representing the next character after the end-point of this span. |
2b03887a FG |
856 | /// Special cases: |
857 | /// - if span is a dummy one, returns the same span | |
858 | /// - if next_point reached the end of source, return span with lo = hi | |
859 | /// - respect multi-byte characters | |
2c00a5a8 | 860 | pub fn next_point(&self, sp: Span) -> Span { |
5869c6ff XL |
861 | if sp.is_dummy() { |
862 | return sp; | |
863 | } | |
2c00a5a8 XL |
864 | let start_of_next_point = sp.hi().0; |
865 | ||
2b03887a FG |
866 | let width = self.find_width_of_character_at_span(sp, true); |
867 | if width == 0 { | |
868 | return Span::new(sp.hi(), sp.hi(), sp.ctxt(), None); | |
869 | } | |
870 | // If the width is 1, then the next span should only contain the next char besides current ending. | |
871 | // However, in the case of a multibyte character, where the width != 1, the next span should | |
2c00a5a8 | 872 | // span multiple bytes to include the whole character. |
dfeec247 | 873 | let end_of_next_point = |
2b03887a | 874 | start_of_next_point.checked_add(width).unwrap_or(start_of_next_point); |
2c00a5a8 | 875 | |
2b03887a | 876 | let end_of_next_point = BytePos(cmp::max(start_of_next_point + 1, end_of_next_point)); |
c295e0f8 | 877 | Span::new(BytePos(start_of_next_point), end_of_next_point, sp.ctxt(), None) |
2c00a5a8 XL |
878 | } |
879 | ||
2b03887a FG |
880 | /// Returns a new span to check next none-whitespace character or some specified expected character |
881 | /// If `expect` is none, the first span of non-whitespace character is returned. | |
882 | /// If `expect` presented, the first span of the character `expect` is returned | |
883 | /// Otherwise, the span reached to limit is returned. | |
884 | pub fn span_look_ahead(&self, span: Span, expect: Option<&str>, limit: Option<usize>) -> Span { | |
885 | let mut sp = span; | |
886 | for _ in 0..limit.unwrap_or(100 as usize) { | |
887 | sp = self.next_point(sp); | |
888 | if let Ok(ref snippet) = self.span_to_snippet(sp) { | |
889 | if expect.map_or(false, |es| snippet == es) { | |
890 | break; | |
891 | } | |
892 | if expect.is_none() && snippet.chars().any(|c| !c.is_whitespace()) { | |
893 | break; | |
894 | } | |
895 | } | |
896 | } | |
897 | sp | |
898 | } | |
899 | ||
6a06907d XL |
900 | /// Finds the width of the character, either before or after the end of provided span, |
901 | /// depending on the `forwards` parameter. | |
2c00a5a8 | 902 | fn find_width_of_character_at_span(&self, sp: Span, forwards: bool) -> u32 { |
60c5eb7d | 903 | let sp = sp.data(); |
2b03887a FG |
904 | |
905 | if sp.lo == sp.hi && !forwards { | |
60c5eb7d | 906 | debug!("find_width_of_character_at_span: early return empty span"); |
2c00a5a8 XL |
907 | return 1; |
908 | } | |
909 | ||
60c5eb7d XL |
910 | let local_begin = self.lookup_byte_offset(sp.lo); |
911 | let local_end = self.lookup_byte_offset(sp.hi); | |
dfeec247 XL |
912 | debug!( |
913 | "find_width_of_character_at_span: local_begin=`{:?}`, local_end=`{:?}`", | |
914 | local_begin, local_end | |
915 | ); | |
2c00a5a8 | 916 | |
dc9dc135 XL |
917 | if local_begin.sf.start_pos != local_end.sf.start_pos { |
918 | debug!("find_width_of_character_at_span: begin and end are in different files"); | |
919 | return 1; | |
920 | } | |
921 | ||
2c00a5a8 XL |
922 | let start_index = local_begin.pos.to_usize(); |
923 | let end_index = local_end.pos.to_usize(); | |
dfeec247 XL |
924 | debug!( |
925 | "find_width_of_character_at_span: start_index=`{:?}`, end_index=`{:?}`", | |
926 | start_index, end_index | |
927 | ); | |
2c00a5a8 XL |
928 | |
929 | // Disregard indexes that are at the start or end of their spans, they can't fit bigger | |
930 | // characters. | |
f035d41b | 931 | if (!forwards && end_index == usize::MIN) || (forwards && start_index == usize::MAX) { |
2c00a5a8 XL |
932 | debug!("find_width_of_character_at_span: start or end of span, cannot be multibyte"); |
933 | return 1; | |
934 | } | |
935 | ||
a1dfa0c6 | 936 | let source_len = (local_begin.sf.end_pos - local_begin.sf.start_pos).to_usize(); |
2c00a5a8 XL |
937 | debug!("find_width_of_character_at_span: source_len=`{:?}`", source_len); |
938 | // Ensure indexes are also not malformed. | |
2b03887a | 939 | if start_index > end_index || end_index > source_len - 1 { |
2c00a5a8 | 940 | debug!("find_width_of_character_at_span: source indexes are malformed"); |
2b03887a | 941 | return 0; |
2c00a5a8 XL |
942 | } |
943 | ||
a1dfa0c6 | 944 | let src = local_begin.sf.external_src.borrow(); |
2c00a5a8 XL |
945 | |
946 | // We need to extend the snippet to the end of the src rather than to end_index so when | |
947 | // searching forwards for boundaries we've got somewhere to search. | |
a1dfa0c6 | 948 | let snippet = if let Some(ref src) = local_begin.sf.src { |
6a06907d | 949 | &src[start_index..] |
2c00a5a8 | 950 | } else if let Some(src) = src.get_source() { |
6a06907d | 951 | &src[start_index..] |
2c00a5a8 XL |
952 | } else { |
953 | return 1; | |
954 | }; | |
955 | debug!("find_width_of_character_at_span: snippet=`{:?}`", snippet); | |
956 | ||
2c00a5a8 XL |
957 | let mut target = if forwards { end_index + 1 } else { end_index - 1 }; |
958 | debug!("find_width_of_character_at_span: initial target=`{:?}`", target); | |
959 | ||
0531ce1d XL |
960 | while !snippet.is_char_boundary(target - start_index) && target < source_len { |
961 | target = if forwards { | |
962 | target + 1 | |
963 | } else { | |
964 | match target.checked_sub(1) { | |
965 | Some(target) => target, | |
966 | None => { | |
967 | break; | |
968 | } | |
969 | } | |
970 | }; | |
2c00a5a8 XL |
971 | debug!("find_width_of_character_at_span: target=`{:?}`", target); |
972 | } | |
973 | debug!("find_width_of_character_at_span: final target=`{:?}`", target); | |
974 | ||
dfeec247 | 975 | if forwards { (target - end_index) as u32 } else { (end_index - target) as u32 } |
2c00a5a8 XL |
976 | } |
977 | ||
b7449926 | 978 | pub fn get_source_file(&self, filename: &FileName) -> Option<Lrc<SourceFile>> { |
5869c6ff XL |
979 | // Remap filename before lookup |
980 | let filename = self.path_mapping().map_filename_prefix(filename).0; | |
a1dfa0c6 | 981 | for sf in self.files.borrow().source_files.iter() { |
5869c6ff | 982 | if filename == sf.name { |
a1dfa0c6 | 983 | return Some(sf.clone()); |
1a4d82fc JJ |
984 | } |
985 | } | |
3157f602 | 986 | None |
1a4d82fc JJ |
987 | } |
988 | ||
e1599b0c | 989 | /// For a global `BytePos`, computes the local offset within the containing `SourceFile`. |
b7449926 XL |
990 | pub fn lookup_byte_offset(&self, bpos: BytePos) -> SourceFileAndBytePos { |
991 | let idx = self.lookup_source_file_idx(bpos); | |
a1dfa0c6 XL |
992 | let sf = (*self.files.borrow().source_files)[idx].clone(); |
993 | let offset = bpos - sf.start_pos; | |
dfeec247 | 994 | SourceFileAndBytePos { sf, pos: offset } |
1a4d82fc JJ |
995 | } |
996 | ||
e1599b0c | 997 | // Returns the index of the `SourceFile` (in `self.files`) that contains `pos`. |
f035d41b XL |
998 | // This index is guaranteed to be valid for the lifetime of this `SourceMap`, |
999 | // since `source_files` is a `MonotonicVec` | |
b7449926 | 1000 | pub fn lookup_source_file_idx(&self, pos: BytePos) -> usize { |
dfeec247 XL |
1001 | self.files |
1002 | .borrow() | |
1003 | .source_files | |
1004 | .binary_search_by_key(&pos, |key| key.start_pos) | |
e74abb32 | 1005 | .unwrap_or_else(|p| p - 1) |
223e47cc LB |
1006 | } |
1007 | ||
92a42be0 | 1008 | pub fn count_lines(&self) -> usize { |
7cac9316 | 1009 | self.files().iter().fold(0, |a, f| a + f.count_lines()) |
92a42be0 | 1010 | } |
94b46f34 | 1011 | |
60c5eb7d | 1012 | pub fn ensure_source_file_source_present(&self, source_file: Lrc<SourceFile>) -> bool { |
94222f64 XL |
1013 | source_file.add_external_src(|| { |
1014 | match source_file.name { | |
1015 | FileName::Real(ref name) if let Some(local_path) = name.local_path() => { | |
17df50a5 | 1016 | self.file_loader.read_file(local_path).ok() |
17df50a5 | 1017 | } |
94222f64 | 1018 | _ => None, |
17df50a5 | 1019 | } |
dfeec247 | 1020 | }) |
9e0c209e | 1021 | } |
ba9703b0 XL |
1022 | |
1023 | pub fn is_imported(&self, sp: Span) -> bool { | |
1024 | let source_file_index = self.lookup_source_file_idx(sp.lo()); | |
1025 | let source_file = &self.files()[source_file_index]; | |
1026 | source_file.is_imported() | |
1027 | } | |
c295e0f8 XL |
1028 | |
1029 | /// Gets the span of a statement. If the statement is a macro expansion, the | |
1030 | /// span in the context of the block span is found. The trailing semicolon is included | |
1031 | /// on a best-effort basis. | |
1032 | pub fn stmt_span(&self, stmt_span: Span, block_span: Span) -> Span { | |
1033 | if !stmt_span.from_expansion() { | |
1034 | return stmt_span; | |
1035 | } | |
1036 | let mac_call = original_sp(stmt_span, block_span); | |
1037 | self.mac_call_stmt_semi_span(mac_call).map_or(mac_call, |s| mac_call.with_hi(s.hi())) | |
1038 | } | |
1039 | ||
1040 | /// Tries to find the span of the semicolon of a macro call statement. | |
1041 | /// The input must be the *call site* span of a statement from macro expansion. | |
04454e1e FG |
1042 | /// ```ignore (illustrative) |
1043 | /// // v output | |
1044 | /// mac!(); | |
1045 | /// // ^^^^^^ input | |
1046 | /// ``` | |
c295e0f8 XL |
1047 | pub fn mac_call_stmt_semi_span(&self, mac_call: Span) -> Option<Span> { |
1048 | let span = self.span_extend_while(mac_call, char::is_whitespace).ok()?; | |
1049 | let span = span.shrink_to_hi().with_hi(BytePos(span.hi().0.checked_add(1)?)); | |
1050 | if self.span_to_snippet(span).as_deref() != Ok(";") { | |
1051 | return None; | |
1052 | } | |
1053 | Some(span) | |
1054 | } | |
7cac9316 XL |
1055 | } |
1056 | ||
1057 | #[derive(Clone)] | |
1058 | pub struct FilePathMapping { | |
ff7c6d11 | 1059 | mapping: Vec<(PathBuf, PathBuf)>, |
94222f64 | 1060 | filename_display_for_diagnostics: FileNameDisplayPreference, |
7cac9316 XL |
1061 | } |
1062 | ||
1063 | impl FilePathMapping { | |
1064 | pub fn empty() -> FilePathMapping { | |
94222f64 | 1065 | FilePathMapping::new(Vec::new()) |
7cac9316 XL |
1066 | } |
1067 | ||
ff7c6d11 | 1068 | pub fn new(mapping: Vec<(PathBuf, PathBuf)>) -> FilePathMapping { |
94222f64 XL |
1069 | let filename_display_for_diagnostics = if mapping.is_empty() { |
1070 | FileNameDisplayPreference::Local | |
1071 | } else { | |
1072 | FileNameDisplayPreference::Remapped | |
1073 | }; | |
1074 | ||
1075 | FilePathMapping { mapping, filename_display_for_diagnostics } | |
7cac9316 XL |
1076 | } |
1077 | ||
1078 | /// Applies any path prefix substitution as defined by the mapping. | |
1079 | /// The return value is the remapped path and a boolean indicating whether | |
1080 | /// the path was affected by the mapping. | |
ff7c6d11 | 1081 | pub fn map_prefix(&self, path: PathBuf) -> (PathBuf, bool) { |
923072b8 FG |
1082 | if path.as_os_str().is_empty() { |
1083 | // Exit early if the path is empty and therefore there's nothing to remap. | |
1084 | // This is mostly to reduce spam for `RUSTC_LOG=[remap_path_prefix]`. | |
1085 | return (path, false); | |
1086 | } | |
04454e1e | 1087 | |
923072b8 FG |
1088 | return remap_path_prefix(&self.mapping, path); |
1089 | ||
f2b60f7d | 1090 | #[instrument(level = "debug", skip(mapping), ret)] |
923072b8 FG |
1091 | fn remap_path_prefix(mapping: &[(PathBuf, PathBuf)], path: PathBuf) -> (PathBuf, bool) { |
1092 | // NOTE: We are iterating over the mapping entries from last to first | |
1093 | // because entries specified later on the command line should | |
1094 | // take precedence. | |
1095 | for &(ref from, ref to) in mapping.iter().rev() { | |
f2b60f7d | 1096 | debug!("Trying to apply {from:?} => {to:?}"); |
923072b8 FG |
1097 | |
1098 | if let Ok(rest) = path.strip_prefix(from) { | |
1099 | let remapped = if rest.as_os_str().is_empty() { | |
1100 | // This is subtle, joining an empty path onto e.g. `foo/bar` will | |
1101 | // result in `foo/bar/`, that is, there'll be an additional directory | |
1102 | // separator at the end. This can lead to duplicated directory separators | |
1103 | // in remapped paths down the line. | |
1104 | // So, if we have an exact match, we just return that without a call | |
1105 | // to `Path::join()`. | |
1106 | to.clone() | |
1107 | } else { | |
1108 | to.join(rest) | |
1109 | }; | |
f2b60f7d | 1110 | debug!("Match - remapped"); |
923072b8 FG |
1111 | |
1112 | return (remapped, true); | |
1113 | } else { | |
f2b60f7d | 1114 | debug!("No match - prefix {from:?} does not match"); |
923072b8 | 1115 | } |
7cac9316 | 1116 | } |
7cac9316 | 1117 | |
f2b60f7d | 1118 | debug!("not remapped"); |
923072b8 FG |
1119 | (path, false) |
1120 | } | |
7cac9316 | 1121 | } |
5869c6ff XL |
1122 | |
1123 | fn map_filename_prefix(&self, file: &FileName) -> (FileName, bool) { | |
1124 | match file { | |
94222f64 XL |
1125 | FileName::Real(realfile) if let RealFileName::LocalPath(local_path) = realfile => { |
1126 | let (mapped_path, mapped) = self.map_prefix(local_path.to_path_buf()); | |
1127 | let realfile = if mapped { | |
1128 | RealFileName::Remapped { | |
1129 | local_path: Some(local_path.clone()), | |
1130 | virtual_name: mapped_path, | |
1131 | } | |
17df50a5 | 1132 | } else { |
94222f64 XL |
1133 | realfile.clone() |
1134 | }; | |
1135 | (FileName::Real(realfile), mapped) | |
5869c6ff | 1136 | } |
94222f64 | 1137 | FileName::Real(_) => unreachable!("attempted to remap an already remapped filename"), |
5869c6ff XL |
1138 | other => (other.clone(), false), |
1139 | } | |
1140 | } | |
923072b8 FG |
1141 | |
1142 | /// Expand a relative path to an absolute path with remapping taken into account. | |
1143 | /// Use this when absolute paths are required (e.g. debuginfo or crate metadata). | |
1144 | /// | |
1145 | /// The resulting `RealFileName` will have its `local_path` portion erased if | |
1146 | /// possible (i.e. if there's also a remapped path). | |
1147 | pub fn to_embeddable_absolute_path( | |
1148 | &self, | |
1149 | file_path: RealFileName, | |
1150 | working_directory: &RealFileName, | |
1151 | ) -> RealFileName { | |
1152 | match file_path { | |
1153 | // Anything that's already remapped we don't modify, except for erasing | |
1154 | // the `local_path` portion. | |
1155 | RealFileName::Remapped { local_path: _, virtual_name } => { | |
1156 | RealFileName::Remapped { | |
1157 | // We do not want any local path to be exported into metadata | |
1158 | local_path: None, | |
1159 | // We use the remapped name verbatim, even if it looks like a relative | |
1160 | // path. The assumption is that the user doesn't want us to further | |
1161 | // process paths that have gone through remapping. | |
1162 | virtual_name, | |
1163 | } | |
1164 | } | |
1165 | ||
1166 | RealFileName::LocalPath(unmapped_file_path) => { | |
1167 | // If no remapping has been applied yet, try to do so | |
1168 | let (new_path, was_remapped) = self.map_prefix(unmapped_file_path); | |
1169 | if was_remapped { | |
1170 | // It was remapped, so don't modify further | |
1171 | return RealFileName::Remapped { local_path: None, virtual_name: new_path }; | |
1172 | } | |
1173 | ||
1174 | if new_path.is_absolute() { | |
1175 | // No remapping has applied to this path and it is absolute, | |
1176 | // so the working directory cannot influence it either, so | |
1177 | // we are done. | |
1178 | return RealFileName::LocalPath(new_path); | |
1179 | } | |
1180 | ||
1181 | debug_assert!(new_path.is_relative()); | |
1182 | let unmapped_file_path_rel = new_path; | |
1183 | ||
1184 | match working_directory { | |
1185 | RealFileName::LocalPath(unmapped_working_dir_abs) => { | |
1186 | let file_path_abs = unmapped_working_dir_abs.join(unmapped_file_path_rel); | |
1187 | ||
1188 | // Although neither `working_directory` nor the file name were subject | |
1189 | // to path remapping, the concatenation between the two may be. Hence | |
1190 | // we need to do a remapping here. | |
1191 | let (file_path_abs, was_remapped) = self.map_prefix(file_path_abs); | |
1192 | if was_remapped { | |
1193 | RealFileName::Remapped { | |
1194 | // Erase the actual path | |
1195 | local_path: None, | |
1196 | virtual_name: file_path_abs, | |
1197 | } | |
1198 | } else { | |
1199 | // No kind of remapping applied to this path, so | |
1200 | // we leave it as it is. | |
1201 | RealFileName::LocalPath(file_path_abs) | |
1202 | } | |
1203 | } | |
1204 | RealFileName::Remapped { | |
1205 | local_path: _, | |
1206 | virtual_name: remapped_working_dir_abs, | |
1207 | } => { | |
1208 | // If working_directory has been remapped, then we emit | |
1209 | // Remapped variant as the expanded path won't be valid | |
1210 | RealFileName::Remapped { | |
1211 | local_path: None, | |
1212 | virtual_name: Path::new(remapped_working_dir_abs) | |
1213 | .join(unmapped_file_path_rel), | |
1214 | } | |
1215 | } | |
1216 | } | |
1217 | } | |
1218 | } | |
1219 | } | |
85aaf69f | 1220 | } |