Skip to main content

miden_diagnostics/
codemap.rs

1use std::ops::Range;
2use std::path::{Path, PathBuf};
3use std::sync::atomic::{AtomicU32, Ordering};
4use std::sync::Arc;
5
6use rustc_hash::FxHasher;
7
8use super::*;
9
10type HashMap<K, V> = flurry::HashMap<K, V, core::hash::BuildHasherDefault<FxHasher>>;
11
12/// [CodeMap] is a thread-safe structure for recording source code files and their
13/// contents for use in diagnostics and parsing/compilation.
14///
15/// The [CodeMap] maintains a set of [SourceFile] entries corresponding to the sources
16/// added to it, with various auxiliary structures for tracking the [FileName] under which
17/// each source was added, the [SourceId] assigned to it, and which files on disk have
18/// been read into memory and added to it.
19///
20/// The [CodeMap] is designed to de-duplicate files and avoid reading from disk multiple
21/// times for the same [Path]. It is also designed to live for the entire lifetime of the
22/// compilation pipeline, so that at any point, diagnostics may be generated which refer
23/// to the original sources.
24///
25/// It is generally advised to allocate the [CodeMap] in an [std::sync::Arc], so that
26/// it may be freely passed around and accessed from multiple threads and/or contexts
27/// which need it. Internally it uses thread-safe datastructures, so there isn't any
28/// reason to prefer passing it around by reference.
29#[derive(Debug)]
30pub struct CodeMap {
31    files: HashMap<SourceId, Arc<SourceFile>>,
32    names: HashMap<FileName, SourceId>,
33    seen: HashMap<PathBuf, SourceId>,
34    next_file_id: AtomicU32,
35}
36impl CodeMap {
37    /// Creates an empty `CodeMap`.
38    pub fn new() -> Self {
39        Self {
40            files: HashMap::default(),
41            names: HashMap::default(),
42            seen: HashMap::default(),
43            next_file_id: AtomicU32::new(1),
44        }
45    }
46
47    /// Add a file to this [CodeMap], returning the [SourceId] assigned to it.
48    ///
49    /// The [SourceId] acts as a unique identifier for the file and content.
50    /// However, it is not guaranteed that a [FileName] always maps to a single
51    /// [SourceId], as multiple threads may attempt to add the same file at the
52    /// same time, which in some cases may result in a duplicate entry. In general
53    /// though, they are 1:1.
54    pub fn add(&self, name: impl Into<FileName>, source: String) -> SourceId {
55        // De-duplicate real files on add; it _may_ be possible for concurrent
56        // adds to add the same file more than once, since we're working across
57        // two maps; but that's not really an issue as long as a given SourceId
58        // always maps to the correct file.
59        //
60        // We don't de-duplicate virtual files, because the same name could be used
61        // for different content, and its unlikely that we'd be adding the same content
62        // over and over again with the same virtual file name
63        let name = name.into();
64        if let FileName::Real(ref path) = name {
65            let guard = self.seen.guard();
66            match self.seen.get(path, &guard) {
67                Some(id) => *id,
68                None => {
69                    let path = path.clone();
70                    let source_id = self.insert_file(name, source, None);
71                    match self.seen.try_insert(path, source_id, &guard) {
72                        Ok(id) => *id,
73                        Err(err) => *err.current,
74                    }
75                }
76            }
77        } else {
78            self.insert_file(name, source, None)
79        }
80    }
81
82    /// Adds a file to the map from the given `path`, if not already present.
83    ///
84    /// Returns `Ok` if successfully added, or `Err` if an error occurred
85    /// while reading the file from disk.
86    pub fn add_file<P: AsRef<Path>>(&self, path: P) -> std::io::Result<SourceId> {
87        let path = path.as_ref();
88        let name = path.into();
89        let guard = self.seen.guard();
90        match self.seen.get(path, &guard) {
91            Some(id) => Ok(*id),
92            None => {
93                let source = std::fs::read_to_string(path)?;
94                let source_id = self.insert_file(name, source, None);
95                match self.seen.try_insert(path.to_path_buf(), source_id, &guard) {
96                    Ok(id) => Ok(*id),
97                    Err(err) => Ok(*err.current),
98                }
99            }
100        }
101    }
102
103    /// Add a file to the map with the given [SourceSpan] as a parent.
104    ///
105    /// This is intended for use cases such as a preprocessor which needs
106    /// to include content from another file directly into the content of
107    /// its parent, as if they are part of the same logical file. When a
108    /// [SourceSpan] spans the region in which the included content occurs,
109    /// it only gets the content in the original parent file.
110    ///
111    /// NOTE: This always results in a new entry in the map in order to
112    /// record the lineage of the source content.
113    pub fn add_child(
114        &self,
115        name: impl Into<FileName>,
116        source: String,
117        parent: SourceSpan,
118    ) -> SourceId {
119        self.insert_file(name.into(), source, Some(parent))
120    }
121
122    fn insert_file(&self, name: FileName, source: String, parent: Option<SourceSpan>) -> SourceId {
123        let file_id = self.next_file_id();
124        let filename = name.clone();
125        let name_guard = self.names.guard();
126        self.names.insert(filename, file_id, &name_guard);
127        let file_guard = self.files.guard();
128        self.files.insert(
129            file_id,
130            Arc::new(SourceFile::new(file_id, name, source, parent)),
131            &file_guard,
132        );
133        file_id
134    }
135
136    /// Get the [SourceFile] corresponding to the given [SourceId]
137    pub fn get(&self, file_id: SourceId) -> Result<Arc<SourceFile>, Error> {
138        if file_id == SourceId::UNKNOWN {
139            Err(Error::FileMissing)
140        } else {
141            let guard = self.files.guard();
142            self.files
143                .get(&file_id, &guard)
144                .cloned()
145                .ok_or(Error::FileMissing)
146        }
147    }
148
149    /// Get the [SourceFile] corresponding to the given [SourceSpan]
150    ///
151    /// Returns `Err` if the span is `SourceSpan::UNKNOWN`
152    pub fn get_with_span(&self, span: SourceSpan) -> Result<Arc<SourceFile>, Error> {
153        self.get(span.source_id)
154    }
155
156    /// Get the [SourceSpan] corresponding to the parent of a given [SourceId].
157    ///
158    /// Returns `None` if the given [SourceId] has no parent
159    pub fn parent(&self, file_id: SourceId) -> Option<SourceSpan> {
160        self.get(file_id).ok().and_then(|f| f.parent())
161    }
162
163    /// Get the [SourceId] corresponding to the given [FileName]
164    pub fn get_file_id(&self, filename: &FileName) -> Option<SourceId> {
165        let guard = self.names.guard();
166        self.names.get(filename, &guard).copied()
167    }
168
169    /// Get the [SourceFile] corresponding to the given [FileName]
170    pub fn get_by_name(&self, filename: &FileName) -> Option<Arc<SourceFile>> {
171        self.get_file_id(filename).and_then(|id| self.get(id).ok())
172    }
173
174    /// Get the [FileName] corresponding to the given [SourceId]
175    ///
176    /// Returns `Err` if `file_id` is not in this map.
177    pub fn name(&self, file_id: SourceId) -> Result<FileName, Error> {
178        let file = self.get(file_id)?;
179        Ok(file.name().clone())
180    }
181
182    /// Get the [FileName] associated with the given [SourceSpan]
183    ///
184    /// Returns `Err` if `span` is [SourceSpan::UNKNOWN].
185    pub fn name_for_spanned<S: Spanned>(&self, spanned: &S) -> Result<FileName, Error> {
186        self.name(spanned.span().source_id)
187    }
188
189    /// Get a [SourceSpan] corresponding to the given line:column
190    ///
191    /// NOTE: The returned [SourceSpan] points only to line:column, it does not
192    /// span any neighboring source locations, callers must extend the returned
193    /// span if so desired.
194    pub fn line_column_to_span(
195        &self,
196        file_id: SourceId,
197        line: impl Into<LineIndex>,
198        column: impl Into<ColumnIndex>,
199    ) -> Result<SourceSpan, Error> {
200        let f = self.get(file_id)?;
201        let span = f.line_column_to_span(line.into(), column.into())?;
202        let start = SourceIndex::new(file_id, span.start());
203        let end = SourceIndex::new(file_id, span.end());
204        Ok(SourceSpan::new(start, end))
205    }
206
207    fn line_span(
208        &self,
209        file_id: SourceId,
210        line_index: impl Into<LineIndex>,
211    ) -> Result<codespan::Span, Error> {
212        let f = self.get(file_id)?;
213        f.line_span(line_index.into())
214    }
215
216    fn line_index(
217        &self,
218        file_id: SourceId,
219        byte_index: impl Into<ByteIndex>,
220    ) -> Result<LineIndex, Error> {
221        Ok(self.get(file_id)?.line_index(byte_index.into()))
222    }
223
224    /// Get a [Location] from a [SourceSpan]
225    ///
226    /// Returns `Err` if `span` is [SourceSpan::UNKNOWN].
227    pub fn location<S: Spanned>(&self, spanned: &S) -> Result<Location, Error> {
228        let span = spanned.span();
229        self.location_at_index(span.source_id, span.start)
230    }
231
232    /// Get a [Location] from a given [SourceId] and byte index.
233    pub fn location_at_index(
234        &self,
235        file_id: SourceId,
236        byte_index: impl Into<ByteIndex>,
237    ) -> Result<Location, Error> {
238        self.get(file_id)?.location(byte_index)
239    }
240
241    /// Get a [SourceSpan] representing the entire content of `file_id`
242    pub fn source_span(&self, file_id: SourceId) -> Result<SourceSpan, Error> {
243        Ok(self.get(file_id)?.source_span())
244    }
245
246    /// Get the original source content corresponding to `spanned` as a `&str`
247    pub fn source_slice<'a, S: Spanned>(&'a self, spanned: &S) -> Result<&'a str, Error> {
248        let span = spanned.span();
249        let f = self.get(span.source_id)?;
250        let slice = f.source_slice(span)?;
251        unsafe { Ok(std::mem::transmute::<&str, &'a str>(slice)) }
252    }
253
254    #[inline(always)]
255    fn next_file_id(&self) -> SourceId {
256        let id = self.next_file_id.fetch_add(1, Ordering::Relaxed);
257        SourceId::new(id)
258    }
259}
260impl Default for CodeMap {
261    fn default() -> Self {
262        Self::new()
263    }
264}
265impl<'a> Files<'a> for CodeMap {
266    type FileId = SourceId;
267    type Name = String;
268    type Source = &'a str;
269
270    fn name(&self, file_id: Self::FileId) -> Result<Self::Name, Error> {
271        Ok(format!("{}", self.get(file_id)?.name()))
272    }
273
274    fn source(&self, file_id: Self::FileId) -> Result<&'a str, Error> {
275        use std::mem;
276
277        let f = self.get(file_id)?;
278        Ok(unsafe { mem::transmute::<&str, &'a str>(f.source()) })
279    }
280
281    fn line_index(&self, file_id: Self::FileId, byte_index: usize) -> Result<usize, Error> {
282        Ok(self.line_index(file_id, byte_index as u32)?.to_usize())
283    }
284
285    fn line_range(&self, file_id: Self::FileId, line_index: usize) -> Result<Range<usize>, Error> {
286        let span = self.line_span(file_id, line_index as u32)?;
287
288        Ok(span.start().to_usize()..span.end().to_usize())
289    }
290}