Skip to main content

mir_analyzer/
source_provider.rs

1//! Abstraction over how the analyzer obtains file source text it doesn't yet
2//! have in its in-memory salsa inputs.
3//!
4//! Today the analyzer occasionally needs to fault in a file (lazy-load of a
5//! referenced class). The default implementation [`FsSourceProvider`] reads
6//! disk; LSPs swap in a VFS-backed provider so unsaved editor buffers
7//! authoritatively override the on-disk content.
8//!
9//! Boundary: the analyzer never invents file paths. Paths come from one of
10//! - a class resolver (PSR-4, classmap) it was configured with, or
11//! - `AnalysisSession::set_file_text` registrations from the consumer.
12//!
13//! Consumers can therefore reason about exactly which paths the analyzer
14//! might ask for, and serve them from whatever source they prefer.
15
16use std::sync::Arc;
17
18/// Read a file's source text on demand. Returns `None` if the path is
19/// unreadable or doesn't exist — the analyzer treats that as "this class is
20/// unresolvable" and (negative-)caches the failure.
21pub trait SourceProvider: Send + Sync {
22    fn read(&self, path: &str) -> Option<Arc<str>>;
23
24    /// Hint that `paths` will be read soon, so they may be fetched concurrently.
25    fn prefetch(&self, _paths: &[String]) {}
26
27    /// Drop hinted text that was never read.
28    fn clear_prefetch(&self) {}
29}
30
31/// Reads source text from the local filesystem. The default provider for
32/// CLI batch contexts.
33#[derive(Default)]
34pub struct FsSourceProvider {
35    prefetched: parking_lot::Mutex<rustc_hash::FxHashMap<String, Option<Arc<str>>>>,
36}
37
38impl FsSourceProvider {
39    pub fn new() -> Self {
40        Self::default()
41    }
42}
43
44impl SourceProvider for FsSourceProvider {
45    fn read(&self, path: &str) -> Option<Arc<str>> {
46        if let Some(text) = self.prefetched.lock().remove(path) {
47            return text;
48        }
49        std::fs::read_to_string(path).ok().map(Arc::from)
50    }
51
52    // Opens right after a burst of file opens are slow on macOS but scale across threads.
53    fn prefetch(&self, paths: &[String]) {
54        use rayon::prelude::*;
55        let fetched: Vec<(String, Option<Arc<str>>)> = paths
56            .par_iter()
57            .map(|path| {
58                (
59                    path.clone(),
60                    std::fs::read_to_string(path).ok().map(Arc::from),
61                )
62            })
63            .collect();
64        self.prefetched.lock().extend(fetched);
65    }
66
67    fn clear_prefetch(&self) {
68        self.prefetched.lock().clear();
69    }
70}