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::path::Path;
17use std::sync::Arc;
18
19/// Read a PHP source file as text. PHP doesn't care about encoding, so a file
20/// with non-UTF-8 bytes (e.g. a Latin-1 string literal) is still valid PHP:
21/// rather than rejecting it, each invalid sequence becomes U+FFFD.
22pub fn read_php_source(path: impl AsRef<Path>) -> std::io::Result<String> {
23    let bytes = std::fs::read(path)?;
24    Ok(match String::from_utf8(bytes) {
25        Ok(text) => text,
26        Err(e) => String::from_utf8_lossy(e.as_bytes()).into_owned(),
27    })
28}
29
30/// Read a file's source text on demand. Returns `None` if the path is
31/// unreadable or doesn't exist — the analyzer treats that as "this class is
32/// unresolvable" and (negative-)caches the failure.
33pub trait SourceProvider: Send + Sync {
34    fn read(&self, path: &str) -> Option<Arc<str>>;
35
36    /// Hint that `paths` will be read soon, so they may be fetched concurrently.
37    fn prefetch(&self, _paths: &[String]) {}
38
39    /// Drop hinted text that was never read.
40    fn clear_prefetch(&self) {}
41}
42
43/// Reads source text from the local filesystem. The default provider for
44/// CLI batch contexts.
45#[derive(Default)]
46pub struct FsSourceProvider {
47    prefetched: parking_lot::Mutex<rustc_hash::FxHashMap<String, Option<Arc<str>>>>,
48}
49
50impl FsSourceProvider {
51    pub fn new() -> Self {
52        Self::default()
53    }
54}
55
56impl SourceProvider for FsSourceProvider {
57    fn read(&self, path: &str) -> Option<Arc<str>> {
58        if let Some(text) = self.prefetched.lock().remove(path) {
59            return text;
60        }
61        read_php_source(path).ok().map(Arc::from)
62    }
63
64    // Opens right after a burst of file opens are slow on macOS but scale across threads.
65    fn prefetch(&self, paths: &[String]) {
66        use rayon::prelude::*;
67        let fetched: Vec<(String, Option<Arc<str>>)> = paths
68            .par_iter()
69            .map(|path| (path.clone(), read_php_source(path).ok().map(Arc::from)))
70            .collect();
71        self.prefetched.lock().extend(fetched);
72    }
73
74    fn clear_prefetch(&self) {
75        self.prefetched.lock().clear();
76    }
77}