Skip to main content

litcheck_core/diagnostics/
source_manager.rs

1use core::{error::Error, fmt::Debug};
2use std::{collections::BTreeMap, sync::Arc};
3
4use serde::{Deserialize, Serialize};
5
6use super::*;
7use crate::range::Range;
8
9#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
10#[repr(u32)]
11pub enum SourceGroup {
12    File = 0,
13    Argument = ((u8::MAX - 1) as u32) << 24,
14    Unknown = (u8::MAX as u32) << 24,
15}
16
17/// A [SourceId] represents the index/identifier associated with a unique source file in a
18/// [SourceManager] implementation.
19#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
20pub struct SourceId(u32);
21
22impl From<u32> for SourceId {
23    fn from(value: u32) -> Self {
24        SourceId::new_unchecked(value)
25    }
26}
27
28impl Default for SourceId {
29    fn default() -> Self {
30        Self::UNKNOWN
31    }
32}
33
34impl SourceId {
35    pub const UNKNOWN: Self = Self(u32::MAX);
36
37    const GROUP_MASK: u32 = (u8::MAX as u32) << 24;
38    const INDEX_MASK: u32 = !Self::GROUP_MASK;
39    const MAX_INDEX: u32 = Self::INDEX_MASK;
40
41    /// Create a new [SourceId] representing a file, from a `u32` value, but assert if out of range
42    pub fn file(id: u32) -> Self {
43        assert!(
44            id <= Self::MAX_INDEX,
45            "source id out of range: only 2^24 unique ids can be represented"
46        );
47
48        Self(id | SourceGroup::File as u32)
49    }
50
51    /// Create a new [SourceId] representing a command-line argument, from a `u32` value, but assert
52    /// if out of range
53    pub fn argument(id: u32) -> Self {
54        assert!(
55            id <= Self::MAX_INDEX,
56            "source id out of range: only 2^24 unique ids can be represented"
57        );
58
59        Self(id | SourceGroup::Argument as u32)
60    }
61
62    /// Create a new [SourceId] from a raw `u32` value
63    #[inline(always)]
64    pub const fn new_unchecked(id: u32) -> Self {
65        assert!(
66            id & Self::GROUP_MASK == (SourceGroup::File as u32),
67            "invalid source id group: expected file"
68        );
69        Self(id)
70    }
71
72    #[inline(always)]
73    pub const fn to_index(self) -> usize {
74        (self.0 & Self::INDEX_MASK) as usize
75    }
76
77    #[inline(always)]
78    pub const fn group(&self) -> SourceGroup {
79        let group = self.0 & Self::GROUP_MASK;
80        // SAFETY: We ensure SourceId is only ever constructed with a valid group
81        unsafe { *(&group as *const u32).cast::<SourceGroup>() }
82    }
83
84    #[inline(always)]
85    pub const fn to_u32(self) -> u32 {
86        self.0
87    }
88
89    pub const fn is_unknown(&self) -> bool {
90        self.0 == Self::UNKNOWN.0
91    }
92
93    pub const fn is_cli_argument(&self) -> bool {
94        matches!(self.group(), SourceGroup::Argument)
95    }
96}
97
98impl TryFrom<usize> for SourceId {
99    type Error = ();
100
101    #[inline]
102    fn try_from(id: usize) -> Result<Self, Self::Error> {
103        match u32::try_from(id) {
104            Ok(n) if n & SourceId::GROUP_MASK == 0 => Ok(Self(n)),
105            _ => Err(()),
106        }
107    }
108}
109
110// SOURCE MANAGER
111// ================================================================================================
112
113/// The set of errors which may be raised by a [SourceManager]
114#[derive(Debug, thiserror::Error)]
115pub enum SourceManagerError {
116    /// A [SourceId] was provided to a [SourceManager] which was allocated by a different
117    /// [SourceManager]
118    #[error("attempted to use an invalid source id")]
119    InvalidSourceId,
120    /// An attempt was made to read content using invalid byte indices
121    #[error("attempted to read content out of bounds")]
122    InvalidBounds,
123    #[error(transparent)]
124    InvalidContentUpdate(#[from] SourceContentUpdateError),
125    /// Custom error variant for implementors of the trait.
126    #[error("{error_msg}")]
127    Custom {
128        error_msg: Box<str>,
129        // thiserror will return this when calling Error::source on SourceManagerError.
130        source: Option<Box<dyn Error + Send + Sync + 'static>>,
131    },
132}
133
134impl SourceManagerError {
135    pub fn custom(message: String) -> Self {
136        Self::Custom {
137            error_msg: message.into(),
138            source: None,
139        }
140    }
141
142    pub fn custom_with_source(message: String, source: impl Error + Send + Sync + 'static) -> Self {
143        Self::Custom {
144            error_msg: message.into(),
145            source: Some(Box::new(source)),
146        }
147    }
148}
149
150pub trait SourceManager: Debug {
151    /// Returns true if `file` is managed by this source manager
152    fn is_manager_of(&self, file: &SourceFile) -> bool {
153        match self.get(file.id()) {
154            Ok(found) => core::ptr::addr_eq(Arc::as_ptr(&found), file),
155            Err(_) => false,
156        }
157    }
158    /// Copies `file` into this source manager (if not already managed by this manager).
159    ///
160    /// The returned source file is guaranteed to be owned by this manager.
161    fn copy_into(&self, file: &SourceFile) -> Arc<SourceFile> {
162        if let Ok(found) = self.get(file.id())
163            && core::ptr::addr_eq(Arc::as_ptr(&found), file)
164        {
165            return found;
166        }
167        self.load_from_raw_parts(
168            file.id().group(),
169            file.uri().clone(),
170            file.content().clone(),
171        )
172    }
173    fn load_argument(&self, name: &'static str, content: String) -> Arc<SourceFile> {
174        let content = SourceContent::new(SourceLanguage::Unknown, name, content);
175        self.load_from_raw_parts(
176            SourceGroup::Argument,
177            FileName::from_static_str(name),
178            content,
179        )
180    }
181    /// Load the given `content` into this [SourceManager] with `name`
182    fn load(&self, lang: SourceLanguage, name: FileName, content: String) -> Arc<SourceFile> {
183        let content = SourceContent::new(lang, name.clone(), content);
184        self.load_from_raw_parts(SourceGroup::File, name, content)
185    }
186    /// Load content into this [SourceManager] from raw [SourceFile] components
187    fn load_from_raw_parts(
188        &self,
189        group: SourceGroup,
190        name: FileName,
191        content: SourceContent,
192    ) -> Arc<SourceFile>;
193    /// Update the source file corresponding to `id` after being notified of a change event.
194    ///
195    /// The `version` indicates the new version of the document
196    fn update(
197        &self,
198        id: SourceId,
199        text: String,
200        range: Option<Selection>,
201        version: i32,
202    ) -> Result<(), SourceManagerError>;
203    /// Get the [SourceFile] corresponding to `id`
204    fn get(&self, id: SourceId) -> Result<Arc<SourceFile>, SourceManagerError>;
205    /// Get the most recent [SourceFile] whose URI is `uri`
206    fn get_by_uri(&self, uri: &FileName) -> Option<Arc<SourceFile>> {
207        self.find(uri).and_then(|id| self.get(id).ok())
208    }
209    /// Search for a source file whose URI is `uri`, and return its [SourceId] if found.
210    fn find(&self, uri: &FileName) -> Option<SourceId>;
211    /// Convert a [FileLineCol] to an equivalent [SourceSpan], if the referenced file is available
212    fn file_line_col_to_span(&self, loc: FileLineCol) -> Option<SourceSpan>;
213    /// Convert a [SourceSpan] to an equivalent [FileLineCol], if the span is valid
214    fn file_line_col(&self, span: SourceSpan) -> Result<FileLineCol, SourceManagerError>;
215    /// Convert a [Location] to an equivalent [SourceSpan], if the referenced file is available
216    fn location_to_span(&self, loc: Location) -> Option<SourceSpan>;
217    /// Convert a [SourceSpan] to an equivalent [Location], if the span is valid
218    fn location(&self, span: SourceSpan) -> Result<Location, SourceManagerError>;
219    /// Get the source associated with `id` as a string slice
220    fn source(&self, id: SourceId) -> Result<&str, SourceManagerError>;
221    /// Get the source corresponding to `span` as a string slice
222    fn source_slice(&self, span: SourceSpan) -> Result<&str, SourceManagerError>;
223}
224
225impl<T: ?Sized + SourceManager> SourceManager for Arc<T> {
226    #[inline(always)]
227    fn is_manager_of(&self, file: &SourceFile) -> bool {
228        (**self).is_manager_of(file)
229    }
230    #[inline(always)]
231    fn copy_into(&self, file: &SourceFile) -> Arc<SourceFile> {
232        (**self).copy_into(file)
233    }
234    #[inline(always)]
235    fn load(&self, lang: SourceLanguage, uri: FileName, content: String) -> Arc<SourceFile> {
236        (**self).load(lang, uri, content)
237    }
238    #[inline(always)]
239    fn load_from_raw_parts(
240        &self,
241        group: SourceGroup,
242        uri: FileName,
243        content: SourceContent,
244    ) -> Arc<SourceFile> {
245        (**self).load_from_raw_parts(group, uri, content)
246    }
247    #[inline(always)]
248    fn update(
249        &self,
250        id: SourceId,
251        text: String,
252        range: Option<Selection>,
253        version: i32,
254    ) -> Result<(), SourceManagerError> {
255        (**self).update(id, text, range, version)
256    }
257    #[inline(always)]
258    fn get(&self, id: SourceId) -> Result<Arc<SourceFile>, SourceManagerError> {
259        (**self).get(id)
260    }
261    #[inline(always)]
262    fn get_by_uri(&self, uri: &FileName) -> Option<Arc<SourceFile>> {
263        (**self).get_by_uri(uri)
264    }
265    #[inline(always)]
266    fn find(&self, uri: &FileName) -> Option<SourceId> {
267        (**self).find(uri)
268    }
269    #[inline(always)]
270    fn file_line_col_to_span(&self, loc: FileLineCol) -> Option<SourceSpan> {
271        (**self).file_line_col_to_span(loc)
272    }
273    #[inline(always)]
274    fn file_line_col(&self, span: SourceSpan) -> Result<FileLineCol, SourceManagerError> {
275        (**self).file_line_col(span)
276    }
277    #[inline(always)]
278    fn location_to_span(&self, loc: Location) -> Option<SourceSpan> {
279        (**self).location_to_span(loc)
280    }
281    #[inline(always)]
282    fn location(&self, span: SourceSpan) -> Result<Location, SourceManagerError> {
283        (**self).location(span)
284    }
285    #[inline(always)]
286    fn source(&self, id: SourceId) -> Result<&str, SourceManagerError> {
287        (**self).source(id)
288    }
289    #[inline(always)]
290    fn source_slice(&self, span: SourceSpan) -> Result<&str, SourceManagerError> {
291        (**self).source_slice(span)
292    }
293}
294
295pub trait SourceManagerExt: SourceManager {
296    /// Load the content of `path` into this [SourceManager]
297    fn load_file(&self, path: &std::path::Path) -> Result<Arc<SourceFile>, SourceManagerError> {
298        let uri = FileName::from(path);
299        if let Some(existing) = self.get_by_uri(&uri) {
300            return Ok(existing);
301        }
302
303        let lang = SourceLanguage::from_path(path);
304        let content = std::fs::read_to_string(path)
305            .map(|s| SourceContent::new(lang, uri.clone(), s))
306            .map_err(|source| {
307                SourceManagerError::custom_with_source(
308                    format!("failed to load filed at `{}`", path.display()),
309                    source,
310                )
311            })?;
312
313        Ok(self.load_from_raw_parts(SourceGroup::File, uri, content))
314    }
315}
316
317impl<T: ?Sized + SourceManager> SourceManagerExt for T {}
318
319/// [SourceManagerSync] is a marker trait for [SourceManager] implementations that are also Send +
320/// Sync, and is automatically implemented for any [SourceManager] that meets those requirements.
321///
322/// [SourceManager] is a supertrait of [SourceManagerSync], so you may use instances of the
323/// [SourceManagerSync] where the [SourceManager] is required, either implicitly or via explicit
324/// downcasting, e.g. `Arc<dyn SourceManagerSync> as Arc<dyn SourceManager>`.
325pub trait SourceManagerSync: SourceManager + Send + Sync {}
326
327impl<T: ?Sized + SourceManager + Send + Sync> SourceManagerSync for T {}
328
329// DEFAULT SOURCE MANAGER
330// ================================================================================================
331
332use parking_lot::RwLock;
333
334#[derive(Debug, Default)]
335pub struct DefaultSourceManager(RwLock<DefaultSourceManagerImpl>);
336
337impl Clone for DefaultSourceManager {
338    fn clone(&self) -> Self {
339        let manager = self.0.read();
340        Self(RwLock::new(manager.clone()))
341    }
342}
343
344impl Clone for DefaultSourceManagerImpl {
345    fn clone(&self) -> Self {
346        Self {
347            files: self.files.clone(),
348            arguments: self.arguments.clone(),
349            uris: self.uris.clone(),
350        }
351    }
352}
353
354#[derive(Debug, Default)]
355struct DefaultSourceManagerImpl {
356    files: Vec<Arc<SourceFile>>,
357    arguments: Vec<Arc<SourceFile>>,
358    uris: BTreeMap<FileName, SourceId>,
359}
360
361impl DefaultSourceManagerImpl {
362    fn insert(
363        &mut self,
364        group: SourceGroup,
365        uri: FileName,
366        content: SourceContent,
367    ) -> Arc<SourceFile> {
368        // If we have previously inserted the same content with `name`, return the previously
369        // inserted source id
370        if let Some(file) = self.uris.get(&uri).copied().and_then(|id| {
371            let file = if id.is_cli_argument() {
372                &self.arguments[id.to_index()]
373            } else {
374                &self.files[id.to_index()]
375            };
376            if file.as_str() == content.as_str() {
377                Some(Arc::clone(file))
378            } else {
379                None
380            }
381        }) {
382            return file;
383        }
384        match group {
385            SourceGroup::File => {
386                let index = u32::try_from(self.files.len())
387                    .expect("system limit: too many source files tracked");
388                let id = SourceId::file(index);
389                let file = Arc::new(SourceFile::from_raw_parts(id, content));
390                self.files.push(Arc::clone(&file));
391                self.uris.insert(uri, id);
392                file
393            }
394            SourceGroup::Argument => {
395                let index = u32::try_from(self.arguments.len())
396                    .expect("system limit: too many argument sources tracked");
397                let id = SourceId::argument(index);
398                let file = Arc::new(SourceFile::from_raw_parts(id, content));
399                self.arguments.push(Arc::clone(&file));
400                self.uris.insert(uri, id);
401                file
402            }
403            SourceGroup::Unknown => unreachable!(),
404        }
405    }
406
407    fn get(&self, id: SourceId) -> Result<Arc<SourceFile>, SourceManagerError> {
408        if id.is_cli_argument() {
409            self.arguments
410                .get(id.to_index())
411                .cloned()
412                .ok_or(SourceManagerError::InvalidSourceId)
413        } else {
414            self.files
415                .get(id.to_index())
416                .cloned()
417                .ok_or(SourceManagerError::InvalidSourceId)
418        }
419    }
420
421    fn get_by_uri(&self, uri: &FileName) -> Option<Arc<SourceFile>> {
422        self.find(uri).and_then(|id| self.get(id).ok())
423    }
424
425    fn find(&self, uri: &FileName) -> Option<SourceId> {
426        self.uris.get(uri).copied()
427    }
428
429    fn file_line_col_to_span(&self, loc: FileLineCol) -> Option<SourceSpan> {
430        let file = self
431            .uris
432            .get(&loc.uri)
433            .copied()
434            .and_then(|id| self.get(id).ok())?;
435        file.line_column_to_span(loc.line, loc.column)
436    }
437
438    fn file_line_col(&self, span: SourceSpan) -> Result<FileLineCol, SourceManagerError> {
439        self.get(span.source_id()).map(|file| file.location(span))
440    }
441
442    fn location_to_span(&self, loc: Location) -> Option<SourceSpan> {
443        let file = self
444            .uris
445            .get(&loc.uri)
446            .copied()
447            .and_then(|id| self.get(id).ok())?;
448
449        let max_len = ByteIndex::from(file.as_str().len() as u32);
450        if loc.start >= max_len || loc.end > max_len {
451            return None;
452        }
453
454        Some(SourceSpan::new(file.id(), Range::new(loc.start, loc.end)))
455    }
456
457    fn location(&self, span: SourceSpan) -> Result<Location, SourceManagerError> {
458        self.get(span.source_id())
459            .map(|file| Location::new(file.uri().clone(), span.start(), span.end()))
460    }
461}
462
463impl SourceManager for DefaultSourceManager {
464    fn load_from_raw_parts(
465        &self,
466        group: SourceGroup,
467        uri: FileName,
468        content: SourceContent,
469    ) -> Arc<SourceFile> {
470        let mut manager = self.0.write();
471        manager.insert(group, uri, content)
472    }
473
474    fn update(
475        &self,
476        id: SourceId,
477        text: String,
478        range: Option<Selection>,
479        version: i32,
480    ) -> Result<(), SourceManagerError> {
481        let mut manager = self.0.write();
482        match id.group() {
483            SourceGroup::File => {
484                let source_file = &mut manager.files[id.to_index()];
485                let source_file_cloned = Arc::make_mut(source_file);
486                source_file_cloned
487                    .content_mut()
488                    .update(text, range, version)
489                    .map_err(SourceManagerError::InvalidContentUpdate)
490            }
491            SourceGroup::Argument => {
492                let source_file = &mut manager.arguments[id.to_index()];
493                let source_file_cloned = Arc::make_mut(source_file);
494                source_file_cloned
495                    .content_mut()
496                    .update(text, range, version)
497                    .map_err(SourceManagerError::InvalidContentUpdate)
498            }
499            SourceGroup::Unknown => Err(SourceManagerError::InvalidSourceId),
500        }
501    }
502
503    fn get(&self, id: SourceId) -> Result<Arc<SourceFile>, SourceManagerError> {
504        let manager = self.0.read();
505        manager.get(id)
506    }
507
508    fn get_by_uri(&self, uri: &FileName) -> Option<Arc<SourceFile>> {
509        let manager = self.0.read();
510        manager.get_by_uri(uri)
511    }
512
513    fn find(&self, uri: &FileName) -> Option<SourceId> {
514        let manager = self.0.read();
515        manager.find(uri)
516    }
517
518    fn file_line_col_to_span(&self, loc: FileLineCol) -> Option<SourceSpan> {
519        let manager = self.0.read();
520        manager.file_line_col_to_span(loc)
521    }
522
523    fn file_line_col(&self, span: SourceSpan) -> Result<FileLineCol, SourceManagerError> {
524        let manager = self.0.read();
525        manager.file_line_col(span)
526    }
527
528    fn location_to_span(&self, loc: Location) -> Option<SourceSpan> {
529        let manager = self.0.read();
530        manager.location_to_span(loc)
531    }
532
533    fn location(&self, span: SourceSpan) -> Result<Location, SourceManagerError> {
534        let manager = self.0.read();
535        manager.location(span)
536    }
537
538    fn source(&self, id: SourceId) -> Result<&str, SourceManagerError> {
539        let manager = self.0.read();
540        let ptr = manager.get(id).map(|file| file.as_str() as *const str)?;
541        drop(manager);
542        // SAFETY: Because the lifetime of the returned reference is bound to the manager, and
543        // because we can only ever add files, not modify/remove them, this is safe. Exclusive
544        // access to the manager does _not_ mean exclusive access to the contents of previously
545        // added source files
546        Ok(unsafe { &*ptr })
547    }
548
549    fn source_slice(&self, span: SourceSpan) -> Result<&str, SourceManagerError> {
550        self.source(span.source_id())?
551            .get(span.into_slice_index().into_range())
552            .ok_or(SourceManagerError::InvalidBounds)
553    }
554}
555
556#[cfg(test)]
557mod error_assertions {
558    use super::*;
559
560    /// Asserts at compile time that the passed error has Send + Sync + 'static bounds.
561    fn _assert_error_is_send_sync_static<E: core::error::Error + Send + Sync + 'static>(_: E) {}
562
563    fn _assert_source_manager_error_bounds(err: SourceManagerError) {
564        _assert_error_is_send_sync_static(err);
565    }
566}