1use crate::{BytePos, CharPos, Span};
4use solar_data_structures::{
5 map::{FxHashMap, StdEntry},
6 sync::{ReadGuard, RwLock},
7};
8use std::{
9 io::{self, Read},
10 path::Path,
11 sync::Arc,
12};
13
14mod analyze;
15
16mod file;
17pub use file::*;
18
19mod file_resolver;
20pub use file_resolver::{FileResolver, ResolveError};
21
22#[cfg(test)]
23mod tests;
24
25pub type FileLinesResult = Result<FileLines, SpanLinesError>;
26
27#[derive(Clone, PartialEq, Eq, Debug)]
28pub enum SpanLinesError {
29 DistinctSources(Box<DistinctSources>),
30}
31
32#[derive(Clone, PartialEq, Eq, Debug)]
37pub enum SpanSnippetError {
38 IllFormedSpan(Span),
39 DistinctSources(Box<DistinctSources>),
40 MalformedForSourcemap(MalformedSourceMapPositions),
41 SourceNotAvailable { filename: FileName },
42}
43
44#[derive(Clone, PartialEq, Eq, Debug)]
45pub struct DistinctSources {
46 pub begin: (FileName, BytePos),
47 pub end: (FileName, BytePos),
48}
49
50#[derive(Clone, PartialEq, Eq, Debug)]
51pub struct MalformedSourceMapPositions {
52 pub name: FileName,
53 pub source_len: usize,
54 pub begin_pos: BytePos,
55 pub end_pos: BytePos,
56}
57
58#[derive(Clone, Debug)]
60pub struct Loc {
61 pub file: Arc<SourceFile>,
63 pub line: usize,
65 pub col: CharPos,
67 pub col_display: usize,
69}
70
71#[derive(Debug)]
73pub struct SourceFileAndLine {
74 pub sf: Arc<SourceFile>,
75 pub line: usize,
77}
78
79#[derive(Debug)]
80pub struct SourceFileAndBytePos {
81 pub sf: Arc<SourceFile>,
82 pub pos: BytePos,
83}
84
85#[derive(Copy, Clone, Debug, PartialEq, Eq)]
86pub struct LineInfo {
87 pub line_index: usize,
89
90 pub start_col: CharPos,
92
93 pub end_col: CharPos,
95}
96
97pub struct FileLines {
98 pub file: Arc<SourceFile>,
99 pub lines: Vec<LineInfo>,
100}
101
102#[derive(Default, derive_more::Debug)]
103struct SourceMapFiles {
104 source_files: Vec<Arc<SourceFile>>,
106 #[debug(skip)]
107 stable_id_to_source_file: FxHashMap<StableSourceFileId, Arc<SourceFile>>,
108}
109
110#[derive(Debug)]
112pub struct SourceMap {
113 files: RwLock<SourceMapFiles>,
114 hash_kind: SourceFileHashAlgorithm,
115}
116
117impl Default for SourceMap {
118 fn default() -> Self {
119 Self::empty()
120 }
121}
122
123impl SourceMap {
124 pub fn new(hash_kind: SourceFileHashAlgorithm) -> Self {
126 Self { files: Default::default(), hash_kind }
127 }
128
129 pub fn empty() -> Self {
131 Self::new(SourceFileHashAlgorithm::default())
132 }
133
134 pub fn get_file(&self, path: &Path) -> Option<Arc<SourceFile>> {
137 self.source_file_by_file_name(&path.to_path_buf().into())
138 }
139
140 #[deprecated = "use `source_file_by_file_name` instead"]
143 pub fn get_file_by_name(&self, name: &FileName) -> Option<Arc<SourceFile>> {
144 self.source_file_by_file_name(name)
145 }
146
147 pub fn load_file(&self, path: &Path) -> io::Result<Arc<SourceFile>> {
149 self.load_file_with_name(path.to_owned().into(), path)
150 }
151
152 pub fn load_file_with_name(&self, name: FileName, path: &Path) -> io::Result<Arc<SourceFile>> {
154 self.new_source_file_with(name, || std::fs::read_to_string(path))
155 }
156
157 pub fn load_stdin(&self) -> io::Result<Arc<SourceFile>> {
159 self.new_source_file_with(FileName::Stdin, || {
160 let mut src = String::new();
161 io::stdin().read_to_string(&mut src)?;
162 Ok(src)
163 })
164 }
165
166 pub fn new_source_file(
170 &self,
171 name: impl Into<FileName>,
172 src: impl Into<String>,
173 ) -> io::Result<Arc<SourceFile>> {
174 self.new_source_file_with(name.into(), || Ok(src.into()))
175 }
176
177 #[instrument(level = "debug", skip_all, fields(filename = %filename.display()))]
185 pub fn new_source_file_with(
186 &self,
187 filename: FileName,
188 get_src: impl FnOnce() -> io::Result<String>,
189 ) -> io::Result<Arc<SourceFile>> {
190 let stable_id = StableSourceFileId::from_filename_in_current_crate(&filename);
191 let files = &mut *self.files.write();
192 match files.stable_id_to_source_file.entry(stable_id) {
193 StdEntry::Occupied(entry) => Ok(entry.get().clone()),
194 StdEntry::Vacant(entry) => {
195 let file = SourceFile::new(filename, get_src()?, self.hash_kind)?;
196 let file = Self::append_source_file(&mut files.source_files, file, stable_id)?;
197 entry.insert(file.clone());
198 Ok(file)
199 }
200 }
201 }
202
203 fn append_source_file(
204 source_files: &mut Vec<Arc<SourceFile>>,
205 mut file: SourceFile,
206 stable_id: StableSourceFileId,
207 ) -> io::Result<Arc<SourceFile>> {
208 debug_assert_eq!(file.stable_id, stable_id);
211
212 trace!(name=%file.name.display(), len=file.src.len(), loc=file.count_lines(), "adding to source map");
213
214 file.start_pos = BytePos(if let Some(last_file) = source_files.last() {
215 last_file.end_position().0.checked_add(1).ok_or(OffsetOverflowError(()))?
218 } else {
219 0
220 });
221
222 let file = Arc::new(file);
223 source_files.push(file.clone());
224
225 Ok(file)
226 }
227
228 pub fn files(&self) -> impl std::ops::Deref<Target = [Arc<SourceFile>]> + '_ {
230 ReadGuard::map(self.files.read(), |f| f.source_files.as_slice())
231 }
232
233 pub fn source_file_by_file_name(&self, filename: &FileName) -> Option<Arc<SourceFile>> {
234 let stable_id = StableSourceFileId::from_filename_in_current_crate(filename);
235 self.source_file_by_stable_id(stable_id)
236 }
237
238 pub fn source_file_by_stable_id(
239 &self,
240 stable_id: StableSourceFileId,
241 ) -> Option<Arc<SourceFile>> {
242 self.files.read().stable_id_to_source_file.get(&stable_id).cloned()
243 }
244
245 pub fn filename_for_diagnostics<'a>(&self, filename: &'a FileName) -> FileNameDisplay<'a> {
246 filename.display()
247 }
248
249 pub fn is_multiline(&self, span: Span) -> bool {
251 let lo = self.lookup_source_file_idx(span.lo());
252 let hi = self.lookup_source_file_idx(span.hi());
253 if lo != hi {
254 return true;
255 }
256 let f = self.files()[lo].clone();
257 let lo = f.relative_position(span.lo());
258 let hi = f.relative_position(span.hi());
259 f.lookup_line(lo) != f.lookup_line(hi)
260 }
261
262 pub fn span_to_snippet(&self, span: Span) -> Result<String, SpanSnippetError> {
264 let (sf, range) = self.span_to_source(span)?;
265 sf.src.get(range).map(|s| s.to_string()).ok_or(SpanSnippetError::IllFormedSpan(span))
266 }
267
268 pub fn span_to_prev_source(&self, sp: Span) -> Result<String, SpanSnippetError> {
270 let (sf, range) = self.span_to_source(sp)?;
271 sf.src.get(..range.start).map(|s| s.to_string()).ok_or(SpanSnippetError::IllFormedSpan(sp))
272 }
273
274 pub fn lookup_byte_offset(&self, bpos: BytePos) -> SourceFileAndBytePos {
276 let sf = self.lookup_source_file(bpos);
277 let offset = bpos - sf.start_pos;
278 SourceFileAndBytePos { sf, pos: offset }
279 }
280
281 pub fn lookup_source_file_idx(&self, pos: BytePos) -> usize {
285 Self::lookup_sf_idx(&self.files(), pos)
286 }
287
288 pub fn lookup_source_file(&self, pos: BytePos) -> Arc<SourceFile> {
290 let files = &*self.files();
291 let idx = Self::lookup_sf_idx(files, pos);
292 files[idx].clone()
293 }
294
295 fn lookup_sf_idx(files: &[Arc<SourceFile>], pos: BytePos) -> usize {
296 assert!(!files.is_empty(), "attempted to lookup source file in empty `SourceMap`");
297 files.partition_point(|x| x.start_pos <= pos) - 1
298 }
299
300 pub fn lookup_char_pos(&self, pos: BytePos) -> Loc {
302 let sf = self.lookup_source_file(pos);
303 let (line, col, col_display) = sf.lookup_file_pos_with_col_display(pos);
304 Loc { file: sf, line, col, col_display }
305 }
306
307 pub fn lookup_line(&self, pos: BytePos) -> Result<SourceFileAndLine, Arc<SourceFile>> {
309 let f = self.lookup_source_file(pos);
310 let pos = f.relative_position(pos);
311 match f.lookup_line(pos) {
312 Some(line) => Ok(SourceFileAndLine { sf: f, line }),
313 None => Err(f),
314 }
315 }
316
317 pub fn is_valid_span(&self, sp: Span) -> Result<(Loc, Loc), SpanLinesError> {
318 let lo = self.lookup_char_pos(sp.lo());
319 let hi = self.lookup_char_pos(sp.hi());
320 if lo.file.start_pos != hi.file.start_pos {
321 return Err(SpanLinesError::DistinctSources(Box::new(DistinctSources {
322 begin: (lo.file.name.clone(), lo.file.start_pos),
323 end: (hi.file.name.clone(), hi.file.start_pos),
324 })));
325 }
326 Ok((lo, hi))
327 }
328
329 pub fn is_line_before_span_empty(&self, sp: Span) -> bool {
330 match self.span_to_prev_source(sp) {
331 Ok(s) => s.rsplit_once('\n').unwrap_or(("", &s)).1.trim_start().is_empty(),
332 Err(_) => false,
333 }
334 }
335
336 pub fn span_to_lines(&self, sp: Span) -> FileLinesResult {
337 let (lo, hi) = self.is_valid_span(sp)?;
338 assert!(hi.line >= lo.line);
339
340 if sp.is_dummy() {
341 return Ok(FileLines { file: lo.file, lines: Vec::new() });
342 }
343
344 let mut lines = Vec::with_capacity(hi.line - lo.line + 1);
345
346 let mut start_col = lo.col;
349
350 let hi_line = hi.line.saturating_sub(1);
358 for line_index in lo.line.saturating_sub(1)..hi_line {
359 let line_len = lo.file.get_line(line_index).map_or(0, |s| s.chars().count());
360 lines.push(LineInfo { line_index, start_col, end_col: CharPos::from_usize(line_len) });
361 start_col = CharPos::from_usize(0);
362 }
363
364 lines.push(LineInfo { line_index: hi_line, start_col, end_col: hi.col });
366
367 Ok(FileLines { file: lo.file, lines })
368 }
369
370 pub fn span_to_source(
372 &self,
373 sp: Span,
374 ) -> Result<(Arc<SourceFile>, std::ops::Range<usize>), SpanSnippetError> {
375 let local_begin = self.lookup_byte_offset(sp.lo());
376 let local_end = self.lookup_byte_offset(sp.hi());
377
378 if local_begin.sf.start_pos != local_end.sf.start_pos {
379 return Err(SpanSnippetError::DistinctSources(Box::new(DistinctSources {
380 begin: (local_begin.sf.name.clone(), local_begin.sf.start_pos),
381 end: (local_end.sf.name.clone(), local_end.sf.start_pos),
382 })));
383 }
384
385 let start_index = local_begin.pos.to_usize();
388 let end_index = local_end.pos.to_usize();
389 let source_len = local_begin.sf.source_len.to_usize();
390
391 if start_index > end_index || end_index > source_len {
392 return Err(SpanSnippetError::MalformedForSourcemap(MalformedSourceMapPositions {
393 name: local_begin.sf.name.clone(),
394 source_len,
395 begin_pos: local_begin.pos,
396 end_pos: local_end.pos,
397 }));
398 }
399
400 Ok((local_begin.sf, start_index..end_index))
401 }
402
403 pub fn span_to_diagnostic_string(&self, sp: Span) -> String {
407 self.span_to_string(sp)
408 }
409
410 pub fn span_to_string(&self, sp: Span) -> String {
411 let (source_file, lo_line, lo_col, hi_line, hi_col) = self.span_to_location_info(sp);
412
413 let file_name = match source_file {
414 Some(sf) => sf.name.display().to_string(),
415 None => return "no-location".to_string(),
416 };
417
418 format!("{file_name}:{lo_line}:{lo_col}: {hi_line}:{hi_col}")
419 }
420
421 pub fn span_to_location_info(
422 &self,
423 sp: Span,
424 ) -> (Option<Arc<SourceFile>>, usize, usize, usize, usize) {
425 if self.files().is_empty() || sp.is_dummy() {
426 return (None, 0, 0, 0, 0);
427 }
428
429 let lo = self.lookup_char_pos(sp.lo());
430 let hi = self.lookup_char_pos(sp.hi());
431 (Some(lo.file), lo.line, lo.col.to_usize() + 1, hi.line, hi.col.to_usize() + 1)
432 }
433}