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