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