solar_interface/source_map/
mod.rs1use crate::{BytePos, CharPos, Span};
4use once_map::OnceMap;
5use solar_data_structures::{
6 fmt,
7 map::FxBuildHasher,
8 sync::{ReadGuard, RwLock},
9};
10use std::{
11 io::{self, Read},
12 path::{Path, PathBuf},
13 sync::{Arc, OnceLock},
14};
15
16mod analyze;
17
18mod file;
19pub use file::*;
20
21mod file_resolver;
22pub use file_resolver::{FileResolver, ResolveError};
23
24#[cfg(test)]
25mod tests;
26
27pub type FileLinesResult = Result<FileLines, SpanLinesError>;
28
29#[derive(Clone, PartialEq, Eq, Debug)]
30pub enum SpanLinesError {
31 DistinctSources(Box<DistinctSources>),
32}
33
34#[derive(Clone, PartialEq, Eq, Debug)]
39pub enum SpanSnippetError {
40 IllFormedSpan(Span),
41 DistinctSources(Box<DistinctSources>),
42 MalformedForSourcemap(MalformedSourceMapPositions),
43 SourceNotAvailable { filename: FileName },
44}
45
46#[derive(Clone, PartialEq, Eq, Debug)]
47pub struct DistinctSources {
48 pub begin: (FileName, BytePos),
49 pub end: (FileName, BytePos),
50}
51
52#[derive(Clone, PartialEq, Eq, Debug)]
53pub struct MalformedSourceMapPositions {
54 pub name: FileName,
55 pub source_len: usize,
56 pub begin_pos: BytePos,
57 pub end_pos: BytePos,
58}
59
60#[derive(Clone, Debug)]
62pub struct Loc {
63 pub file: Arc<SourceFile>,
65 pub line: usize,
67 pub col: CharPos,
69 pub col_display: usize,
71}
72
73#[derive(Debug)]
75pub struct SourceFileAndLine {
76 pub sf: Arc<SourceFile>,
77 pub line: usize,
79}
80
81#[derive(Debug)]
82pub struct SourceFileAndBytePos {
83 pub sf: Arc<SourceFile>,
84 pub pos: BytePos,
85}
86
87#[derive(Copy, Clone, Debug, PartialEq, Eq)]
88pub struct LineInfo {
89 pub line_index: usize,
91
92 pub start_col: CharPos,
94
95 pub end_col: CharPos,
97}
98
99pub struct FileLines {
100 pub file: Arc<SourceFile>,
101 pub lines: Vec<LineInfo>,
102}
103
104pub trait FileLoader: Send + Sync + 'static {
110 fn canonicalize_path(&self, path: &Path) -> io::Result<PathBuf>;
111 fn load_stdin(&self) -> io::Result<String>;
112 fn load_file(&self, path: &Path) -> io::Result<String>;
113 fn load_binary_file(&self, path: &Path) -> io::Result<Vec<u8>>;
114}
115
116pub struct RealFileLoader;
118
119#[allow(clippy::disallowed_methods)] impl FileLoader for RealFileLoader {
121 fn canonicalize_path(&self, path: &Path) -> io::Result<PathBuf> {
122 crate::canonicalize(path)
123 }
124
125 fn load_stdin(&self) -> io::Result<String> {
126 let mut src = String::new();
127 io::stdin().read_to_string(&mut src)?;
128 Ok(src)
129 }
130
131 fn load_file(&self, path: &Path) -> io::Result<String> {
132 std::fs::read_to_string(path)
133 }
134
135 fn load_binary_file(&self, path: &Path) -> io::Result<Vec<u8>> {
136 std::fs::read(path)
137 }
138}
139
140#[derive(derive_more::Debug)]
142pub struct SourceMap {
143 source_files: RwLock<Vec<Arc<SourceFile>>>,
145 #[debug(skip)]
146 stable_id_to_source_file: OnceMap<StableSourceFileId, Arc<SourceFile>, FxBuildHasher>,
147
148 hash_kind: SourceFileHashAlgorithm,
149 base_path: OnceLock<PathBuf>,
150 #[debug(skip)]
151 file_loader: OnceLock<Box<dyn FileLoader>>,
152}
153
154impl Default for SourceMap {
155 fn default() -> Self {
156 Self::empty()
157 }
158}
159
160impl SourceMap {
161 pub fn new(hash_kind: SourceFileHashAlgorithm) -> Self {
163 Self {
164 source_files: Default::default(),
165 stable_id_to_source_file: Default::default(),
166 hash_kind,
167 base_path: OnceLock::new(),
168 file_loader: OnceLock::new(),
169 }
170 }
171
172 pub fn empty() -> Self {
174 Self::new(SourceFileHashAlgorithm::default())
175 }
176
177 pub fn set_file_loader(&self, file_loader: impl FileLoader) {
181 let _ = self.file_loader.set(Box::new(file_loader));
182 }
183
184 pub fn file_loader(&self) -> &dyn FileLoader {
188 self.file_loader.get().map(std::ops::Deref::deref).unwrap_or(&RealFileLoader)
189 }
190
191 pub(crate) fn set_base_path(&self, base_path: PathBuf) {
193 let _ = self.base_path.set(base_path);
194 }
195
196 pub fn is_empty(&self) -> bool {
198 self.files().is_empty()
199 }
200
201 pub fn get_file(&self, path: impl Into<FileName>) -> Option<Arc<SourceFile>> {
204 self.source_file_by_file_name(&path.into())
205 }
206
207 pub fn load_file(&self, path: &Path) -> io::Result<Arc<SourceFile>> {
209 self.load_file_with_name(path.to_owned().into(), path)
210 }
211
212 pub fn load_file_with_name(&self, name: FileName, path: &Path) -> io::Result<Arc<SourceFile>> {
214 self.new_source_file_with(name, || self.file_loader().load_file(path))
215 }
216
217 pub fn load_stdin(&self) -> io::Result<Arc<SourceFile>> {
219 self.new_source_file_with(FileName::Stdin, || self.file_loader().load_stdin())
220 }
221
222 pub fn new_source_file(
226 &self,
227 name: impl Into<FileName>,
228 src: impl Into<String>,
229 ) -> io::Result<Arc<SourceFile>> {
230 self.new_source_file_with(name.into(), || Ok(src.into()))
231 }
232
233 #[instrument(level = "debug", skip_all, fields(filename = %filename.display()))]
243 pub fn new_source_file_with(
244 &self,
245 filename: FileName,
246 get_src: impl FnOnce() -> io::Result<String>,
247 ) -> io::Result<Arc<SourceFile>> {
248 let stable_id = StableSourceFileId::from_filename_in_current_crate(&filename);
249 self.stable_id_to_source_file.try_insert_cloned(stable_id, |&stable_id| {
250 let file = SourceFile::new(filename, get_src()?, self.hash_kind)?;
251 self.append_source_file(file, stable_id)
252 })
253 }
254
255 fn append_source_file(
256 &self,
257 mut file: SourceFile,
258 stable_id: StableSourceFileId,
259 ) -> io::Result<Arc<SourceFile>> {
260 debug_assert_eq!(file.stable_id, stable_id);
263
264 trace!(name=%file.name.display(), len=file.src.len(), loc=file.count_lines(), "adding to source map");
265
266 let source_files = &mut *self.source_files.write();
267 file.start_pos = BytePos(if let Some(last_file) = source_files.last() {
268 last_file.end_position().0.checked_add(1).ok_or(OffsetOverflowError(()))?
271 } else {
272 0
273 });
274
275 let file = Arc::new(file);
276 source_files.push(file.clone());
277
278 Ok(file)
279 }
280
281 pub fn files(&self) -> impl std::ops::Deref<Target = [Arc<SourceFile>]> + '_ {
283 ReadGuard::map(self.source_files.read(), |f| f.as_slice())
284 }
285
286 pub fn source_file_by_file_name(&self, filename: &FileName) -> Option<Arc<SourceFile>> {
287 let stable_id = StableSourceFileId::from_filename_in_current_crate(filename);
288 self.source_file_by_stable_id(stable_id)
289 }
290
291 pub fn source_file_by_stable_id(
292 &self,
293 stable_id: StableSourceFileId,
294 ) -> Option<Arc<SourceFile>> {
295 self.stable_id_to_source_file.get_cloned(&stable_id)
296 }
297
298 pub fn filename_for_diagnostics<'a>(&self, filename: &'a FileName) -> FileNameDisplay<'a> {
299 filename.display()
300 }
301
302 pub fn is_multiline(&self, span: Span) -> bool {
304 let lo = self.lookup_source_file_idx(span.lo());
305 let hi = self.lookup_source_file_idx(span.hi());
306 if lo != hi {
307 return true;
308 }
309 let f = self.files()[lo].clone();
310 let lo = f.relative_position(span.lo());
311 let hi = f.relative_position(span.hi());
312 f.lookup_line(lo) != f.lookup_line(hi)
313 }
314
315 pub fn span_to_snippet(&self, span: Span) -> Result<String, SpanSnippetError> {
317 let (sf, range) = self.span_to_source(span)?;
318 sf.src.get(range).map(|s| s.to_string()).ok_or(SpanSnippetError::IllFormedSpan(span))
319 }
320
321 pub fn span_to_prev_source(&self, sp: Span) -> Result<String, SpanSnippetError> {
323 let (sf, range) = self.span_to_source(sp)?;
324 sf.src.get(..range.start).map(|s| s.to_string()).ok_or(SpanSnippetError::IllFormedSpan(sp))
325 }
326
327 pub fn lookup_byte_offset(&self, bpos: BytePos) -> SourceFileAndBytePos {
329 let sf = self.lookup_source_file(bpos);
330 let offset = bpos - sf.start_pos;
331 SourceFileAndBytePos { sf, pos: offset }
332 }
333
334 pub fn lookup_source_file_idx(&self, pos: BytePos) -> usize {
338 Self::lookup_sf_idx(&self.files(), pos)
339 }
340
341 pub fn lookup_source_file(&self, pos: BytePos) -> Arc<SourceFile> {
343 let files = &*self.files();
344 let idx = Self::lookup_sf_idx(files, pos);
345 files[idx].clone()
346 }
347
348 fn lookup_sf_idx(files: &[Arc<SourceFile>], pos: BytePos) -> usize {
349 assert!(!files.is_empty(), "attempted to lookup source file in empty `SourceMap`");
350 files.partition_point(|x| x.start_pos <= pos) - 1
351 }
352
353 pub fn lookup_char_pos(&self, pos: BytePos) -> Loc {
355 let sf = self.lookup_source_file(pos);
356 let (line, col, col_display) = sf.lookup_file_pos_with_col_display(pos);
357 Loc { file: sf, line, col, col_display }
358 }
359
360 pub fn lookup_line(&self, pos: BytePos) -> Result<SourceFileAndLine, Arc<SourceFile>> {
362 let f = self.lookup_source_file(pos);
363 let pos = f.relative_position(pos);
364 match f.lookup_line(pos) {
365 Some(line) => Ok(SourceFileAndLine { sf: f, line }),
366 None => Err(f),
367 }
368 }
369
370 pub fn is_valid_span(&self, sp: Span) -> Result<(Loc, Loc), SpanLinesError> {
371 let lo = self.lookup_char_pos(sp.lo());
372 let hi = self.lookup_char_pos(sp.hi());
373 if lo.file.start_pos != hi.file.start_pos {
374 return Err(SpanLinesError::DistinctSources(Box::new(DistinctSources {
375 begin: (lo.file.name.clone(), lo.file.start_pos),
376 end: (hi.file.name.clone(), hi.file.start_pos),
377 })));
378 }
379 Ok((lo, hi))
380 }
381
382 pub fn is_line_before_span_empty(&self, sp: Span) -> bool {
383 match self.span_to_prev_source(sp) {
384 Ok(s) => s.rsplit_once('\n').unwrap_or(("", &s)).1.trim_start().is_empty(),
385 Err(_) => false,
386 }
387 }
388
389 pub fn span_to_lines(&self, sp: Span) -> FileLinesResult {
390 let (lo, hi) = self.is_valid_span(sp)?;
391 assert!(hi.line >= lo.line);
392
393 if sp.is_dummy() {
394 return Ok(FileLines { file: lo.file, lines: Vec::new() });
395 }
396
397 let mut lines = Vec::with_capacity(hi.line - lo.line + 1);
398
399 let mut start_col = lo.col;
402
403 let hi_line = hi.line.saturating_sub(1);
411 for line_index in lo.line.saturating_sub(1)..hi_line {
412 let line_len = lo.file.get_line(line_index).map_or(0, |s| s.chars().count());
413 lines.push(LineInfo { line_index, start_col, end_col: CharPos::from_usize(line_len) });
414 start_col = CharPos::from_usize(0);
415 }
416
417 lines.push(LineInfo { line_index: hi_line, start_col, end_col: hi.col });
419
420 Ok(FileLines { file: lo.file, lines })
421 }
422
423 pub fn span_to_source(
425 &self,
426 sp: Span,
427 ) -> Result<(Arc<SourceFile>, std::ops::Range<usize>), SpanSnippetError> {
428 let local_begin = self.lookup_byte_offset(sp.lo());
429 let local_end = self.lookup_byte_offset(sp.hi());
430
431 if local_begin.sf.start_pos != local_end.sf.start_pos {
432 return Err(SpanSnippetError::DistinctSources(Box::new(DistinctSources {
433 begin: (local_begin.sf.name.clone(), local_begin.sf.start_pos),
434 end: (local_end.sf.name.clone(), local_end.sf.start_pos),
435 })));
436 }
437
438 let start_index = local_begin.pos.to_usize();
441 let end_index = local_end.pos.to_usize();
442 let source_len = local_begin.sf.source_len.to_usize();
443
444 if start_index > end_index || end_index > source_len {
445 return Err(SpanSnippetError::MalformedForSourcemap(MalformedSourceMapPositions {
446 name: local_begin.sf.name.clone(),
447 source_len,
448 begin_pos: local_begin.pos,
449 end_pos: local_end.pos,
450 }));
451 }
452
453 Ok((local_begin.sf, start_index..end_index))
454 }
455
456 pub fn span_to_diagnostic_string(&self, sp: Span) -> impl fmt::Display {
460 self.span_to_string(sp)
461 }
462
463 pub fn span_to_string(&self, sp: Span) -> impl fmt::Display {
464 let (source_file, lo_line, lo_col, hi_line, hi_col) = self.span_to_location_info(sp);
465 fmt::from_fn(move |f| {
466 let file_name = match &source_file {
467 Some(sf) => self.filename_for_diagnostics(&sf.name),
468 None => return f.write_str("no-location"),
469 };
470 write!(f, "{file_name}:{lo_line}:{lo_col}: {hi_line}:{hi_col}")
471 })
472 }
473
474 pub fn span_to_location_info(
475 &self,
476 sp: Span,
477 ) -> (Option<Arc<SourceFile>>, usize, usize, usize, usize) {
478 if self.files().is_empty() || sp.is_dummy() {
479 return (None, 0, 0, 0, 0);
480 }
481
482 let lo = self.lookup_char_pos(sp.lo());
483 let hi = self.lookup_char_pos(sp.hi());
484 (Some(lo.file), lo.line, lo.col.to_usize() + 1, hi.line, hi.col.to_usize() + 1)
485 }
486}