1use crate::{BytePos, CharPos, Span};
4use solar_data_structures::{
5 map::FxBuildHasher,
6 sync::{ReadGuard, RwLock},
7};
8use std::{
9 io::{self, Read},
10 path::{Path, PathBuf},
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)]
33pub enum SpanSnippetError {
34 IllFormedSpan(Span),
35 DistinctSources(Box<DistinctSources>),
36 MalformedForSourcemap(MalformedSourceMapPositions),
37 SourceNotAvailable { filename: FileName },
38}
39
40#[derive(Clone, PartialEq, Eq, Debug)]
41pub struct DistinctSources {
42 pub begin: (FileName, BytePos),
43 pub end: (FileName, BytePos),
44}
45
46#[derive(Clone, PartialEq, Eq, Debug)]
47pub struct MalformedSourceMapPositions {
48 pub name: FileName,
49 pub source_len: usize,
50 pub begin_pos: BytePos,
51 pub end_pos: BytePos,
52}
53
54#[derive(Clone, Debug)]
56pub struct Loc {
57 pub file: Arc<SourceFile>,
59 pub line: usize,
61 pub col: CharPos,
63 pub col_display: usize,
65}
66
67#[derive(Debug)]
69pub struct SourceFileAndLine {
70 pub sf: Arc<SourceFile>,
71 pub line: usize,
73}
74
75#[derive(Debug)]
76pub struct SourceFileAndBytePos {
77 pub sf: Arc<SourceFile>,
78 pub pos: BytePos,
79}
80
81#[derive(Copy, Clone, Debug, PartialEq, Eq)]
82pub struct LineInfo {
83 pub line_index: usize,
85
86 pub start_col: CharPos,
88
89 pub end_col: CharPos,
91}
92
93pub struct FileLines {
94 pub file: Arc<SourceFile>,
95 pub lines: Vec<LineInfo>,
96}
97
98pub struct SourceMap {
99 source_files: RwLock<Vec<Arc<SourceFile>>>,
101 stable_id_to_source_file: scc::HashIndex<StableSourceFileId, Arc<SourceFile>, FxBuildHasher>,
102 hash_kind: SourceFileHashAlgorithm,
103}
104
105impl Default for SourceMap {
106 fn default() -> Self {
107 Self::empty()
108 }
109}
110
111impl SourceMap {
112 pub fn new(hash_kind: SourceFileHashAlgorithm) -> Self {
114 Self {
115 source_files: RwLock::new(Vec::new()),
116 stable_id_to_source_file: Default::default(),
117 hash_kind,
118 }
119 }
120
121 pub fn empty() -> Self {
123 Self::new(SourceFileHashAlgorithm::default())
124 }
125
126 pub fn load_file(&self, path: &Path) -> io::Result<Arc<SourceFile>> {
128 let filename = path.to_owned().into();
129 self.new_source_file(filename, || std::fs::read_to_string(path))
130 }
131
132 pub fn load_stdin(&self) -> io::Result<Arc<SourceFile>> {
134 self.new_source_file(FileName::Stdin, || {
135 let mut src = String::new();
136 io::stdin().read_to_string(&mut src)?;
137 Ok(src)
138 })
139 }
140
141 pub fn new_dummy_source_file(&self, path: PathBuf, src: String) -> io::Result<Arc<SourceFile>> {
145 self.new_source_file(path.into(), || Ok(src))
146 }
147
148 #[instrument(level = "debug", skip_all, fields(filename = %filename.display()))]
156 pub fn new_source_file(
157 &self,
158 filename: FileName,
159 get_src: impl FnOnce() -> io::Result<String>,
160 ) -> io::Result<Arc<SourceFile>> {
161 let stable_id = StableSourceFileId::from_filename_in_current_crate(&filename);
162 match self.stable_id_to_source_file.entry(stable_id) {
163 scc::hash_index::Entry::Occupied(entry) => Ok(entry.get().clone()),
164 scc::hash_index::Entry::Vacant(entry) => {
165 let file = SourceFile::new(filename, get_src()?, self.hash_kind)?;
166 let file = self.new_source_file_inner(file, stable_id)?;
167 entry.insert_entry(file.clone());
168 Ok(file)
169 }
170 }
171 }
172
173 fn new_source_file_inner(
174 &self,
175 mut file: SourceFile,
176 stable_id: StableSourceFileId,
177 ) -> io::Result<Arc<SourceFile>> {
178 debug_assert_eq!(file.stable_id, stable_id);
181
182 trace!(name=%file.name.display(), len=file.src.len(), loc=file.count_lines(), "adding to source map");
183
184 let mut source_files = self.source_files.write();
185
186 file.start_pos = BytePos(if let Some(last_file) = source_files.last() {
187 last_file.end_position().0.checked_add(1).ok_or(OffsetOverflowError(()))?
190 } else {
191 0
192 });
193
194 let file = Arc::new(file);
195 source_files.push(file.clone());
196
197 Ok(file)
198 }
199
200 pub fn files(&self) -> ReadGuard<'_, Vec<Arc<SourceFile>>> {
201 self.source_files.read()
202 }
203
204 pub fn source_file_by_file_name(&self, filename: &FileName) -> Option<Arc<SourceFile>> {
205 let stable_id = StableSourceFileId::from_filename_in_current_crate(filename);
206 self.source_file_by_stable_id(stable_id)
207 }
208
209 pub fn source_file_by_stable_id(
210 &self,
211 stable_id: StableSourceFileId,
212 ) -> Option<Arc<SourceFile>> {
213 self.stable_id_to_source_file.get(&stable_id).as_deref().cloned()
214 }
215
216 pub fn filename_for_diagnostics<'a>(&self, filename: &'a FileName) -> FileNameDisplay<'a> {
217 filename.display()
218 }
219
220 pub fn is_multiline(&self, span: Span) -> bool {
222 let lo = self.lookup_source_file_idx(span.lo());
223 let hi = self.lookup_source_file_idx(span.hi());
224 if lo != hi {
225 return true;
226 }
227 let f = self.files()[lo].clone();
228 let lo = f.relative_position(span.lo());
229 let hi = f.relative_position(span.hi());
230 f.lookup_line(lo) != f.lookup_line(hi)
231 }
232
233 pub fn span_to_snippet(&self, span: Span) -> Result<String, SpanSnippetError> {
235 self.span_to_source(span, |src, start_index, end_index| {
236 src.get(start_index..end_index)
237 .map(|s| s.to_string())
238 .ok_or(SpanSnippetError::IllFormedSpan(span))
239 })
240 }
241
242 pub fn lookup_byte_offset(&self, bpos: BytePos) -> SourceFileAndBytePos {
244 let idx = self.lookup_source_file_idx(bpos);
245 let sf = self.files()[idx].clone();
246 let offset = bpos - sf.start_pos;
247 SourceFileAndBytePos { sf, pos: offset }
248 }
249
250 pub fn lookup_source_file_idx(&self, pos: BytePos) -> usize {
254 self.files().partition_point(|x| x.start_pos <= pos) - 1
255 }
256
257 pub fn lookup_source_file(&self, pos: BytePos) -> Arc<SourceFile> {
259 let idx = self.lookup_source_file_idx(pos);
260 self.files()[idx].clone()
261 }
262
263 pub fn lookup_char_pos(&self, pos: BytePos) -> Loc {
265 let sf = self.lookup_source_file(pos);
266 let (line, col, col_display) = sf.lookup_file_pos_with_col_display(pos);
267 Loc { file: sf, line, col, col_display }
268 }
269
270 pub fn lookup_line(&self, pos: BytePos) -> Result<SourceFileAndLine, Arc<SourceFile>> {
272 let f = self.lookup_source_file(pos);
273 let pos = f.relative_position(pos);
274 match f.lookup_line(pos) {
275 Some(line) => Ok(SourceFileAndLine { sf: f, line }),
276 None => Err(f),
277 }
278 }
279
280 pub fn span_to_prev_source(&self, sp: Span) -> Result<String, SpanSnippetError> {
282 self.span_to_source(sp, |src, start_index, _| {
283 src.get(..start_index).map(|s| s.to_string()).ok_or(SpanSnippetError::IllFormedSpan(sp))
284 })
285 }
286
287 pub fn is_valid_span(&self, sp: Span) -> Result<(Loc, Loc), SpanLinesError> {
288 let lo = self.lookup_char_pos(sp.lo());
289 let hi = self.lookup_char_pos(sp.hi());
290 if lo.file.start_pos != hi.file.start_pos {
291 return Err(SpanLinesError::DistinctSources(Box::new(DistinctSources {
292 begin: (lo.file.name.clone(), lo.file.start_pos),
293 end: (hi.file.name.clone(), hi.file.start_pos),
294 })));
295 }
296 Ok((lo, hi))
297 }
298
299 pub fn is_line_before_span_empty(&self, sp: Span) -> bool {
300 match self.span_to_prev_source(sp) {
301 Ok(s) => s.rsplit_once('\n').unwrap_or(("", &s)).1.trim_start().is_empty(),
302 Err(_) => false,
303 }
304 }
305
306 pub fn span_to_lines(&self, sp: Span) -> FileLinesResult {
307 let (lo, hi) = self.is_valid_span(sp)?;
308 assert!(hi.line >= lo.line);
309
310 if sp.is_dummy() {
311 return Ok(FileLines { file: lo.file, lines: Vec::new() });
312 }
313
314 let mut lines = Vec::with_capacity(hi.line - lo.line + 1);
315
316 let mut start_col = lo.col;
319
320 let hi_line = hi.line.saturating_sub(1);
328 for line_index in lo.line.saturating_sub(1)..hi_line {
329 let line_len = lo.file.get_line(line_index).map_or(0, |s| s.chars().count());
330 lines.push(LineInfo { line_index, start_col, end_col: CharPos::from_usize(line_len) });
331 start_col = CharPos::from_usize(0);
332 }
333
334 lines.push(LineInfo { line_index: hi_line, start_col, end_col: hi.col });
336
337 Ok(FileLines { file: lo.file, lines })
338 }
339
340 fn span_to_source<F, T>(&self, sp: Span, extract_source: F) -> Result<T, SpanSnippetError>
344 where
345 F: Fn(&str, usize, usize) -> Result<T, SpanSnippetError>,
346 {
347 let local_begin = self.lookup_byte_offset(sp.lo());
348 let local_end = self.lookup_byte_offset(sp.hi());
349
350 if local_begin.sf.start_pos != local_end.sf.start_pos {
351 Err(SpanSnippetError::DistinctSources(Box::new(DistinctSources {
352 begin: (local_begin.sf.name.clone(), local_begin.sf.start_pos),
353 end: (local_end.sf.name.clone(), local_end.sf.start_pos),
354 })))
355 } else {
356 let start_index = local_begin.pos.to_usize();
359 let end_index = local_end.pos.to_usize();
360 let source_len = local_begin.sf.source_len.to_usize();
361
362 if start_index > end_index || end_index > source_len {
363 return Err(SpanSnippetError::MalformedForSourcemap(MalformedSourceMapPositions {
364 name: local_begin.sf.name.clone(),
365 source_len,
366 begin_pos: local_begin.pos,
367 end_pos: local_end.pos,
368 }));
369 }
370
371 extract_source(&local_begin.sf.src, start_index, end_index)
372 }
373 }
374
375 pub fn span_to_diagnostic_string(&self, sp: Span) -> String {
379 self.span_to_string(sp)
380 }
381
382 pub fn span_to_string(&self, sp: Span) -> String {
383 let (source_file, lo_line, lo_col, hi_line, hi_col) = self.span_to_location_info(sp);
384
385 let file_name = match source_file {
386 Some(sf) => sf.name.display().to_string(),
387 None => return "no-location".to_string(),
388 };
389
390 format!("{file_name}:{lo_line}:{lo_col}: {hi_line}:{hi_col}")
391 }
392
393 pub fn span_to_location_info(
394 &self,
395 sp: Span,
396 ) -> (Option<Arc<SourceFile>>, usize, usize, usize, usize) {
397 if self.files().is_empty() || sp.is_dummy() {
398 return (None, 0, 0, 0, 0);
399 }
400
401 let lo = self.lookup_char_pos(sp.lo());
402 let hi = self.lookup_char_pos(sp.hi());
403 (Some(lo.file), lo.line, lo.col.to_usize() + 1, hi.line, hi.col.to_usize() + 1)
404 }
405}