Skip to main content

ruff_db/
lib.rs

1#![warn(
2    clippy::disallowed_methods,
3    reason = "Prefer System trait methods over std methods"
4)]
5
6use crate::files::{File, Files};
7use crate::system::System;
8use crate::vendored::VendoredFileSystem;
9use ruff_python_ast::PythonVersion;
10use rustc_hash::FxHasher;
11use std::hash::BuildHasherDefault;
12use std::num::NonZeroUsize;
13use ty_static::EnvVars;
14
15pub mod cancellation;
16pub mod diagnostic;
17pub mod display;
18pub mod file_revision;
19pub mod files;
20pub mod panic;
21pub mod parsed;
22pub mod source;
23pub mod system;
24#[cfg(feature = "testing")]
25pub mod testing;
26pub mod vendored;
27
28/// A file paired with the Python version used to parse its contents.
29///
30/// This is the key for [`parsed::parsed_module`]. Including the Python version allows the same
31/// file to be parsed for different versions within a single Salsa revision without sharing an
32/// incompatible AST or syntax diagnostics.
33#[salsa::interned(debug, heap_size = ruff_memory_usage::heap_size)]
34pub struct PythonFile<'db> {
35    #[returns(copy)]
36    pub file: File,
37    #[returns(copy)]
38    pub python_version: PythonVersion,
39}
40
41// The Salsa heap is tracked separately.
42impl get_size2::GetSize for PythonFile<'_> {}
43
44#[cfg(not(target_arch = "wasm32"))]
45pub use std::time::{Instant, SystemTime, SystemTimeError};
46
47#[cfg(target_arch = "wasm32")]
48pub use web_time::{Instant, SystemTime, SystemTimeError};
49
50pub type FxDashMap<K, V> = dashmap::DashMap<K, V, BuildHasherDefault<FxHasher>>;
51static VERSION: std::sync::OnceLock<String> = std::sync::OnceLock::new();
52
53/// Returns the version of the executing program if set.
54pub fn program_version() -> Option<&'static str> {
55    VERSION.get().map(|version| version.as_str())
56}
57
58/// Sets the version of the executing program.
59///
60/// ## Errors
61/// If the version has already been initialized (can only be set once).
62pub fn set_program_version(version: String) -> Result<(), String> {
63    VERSION.set(version)
64}
65
66/// Disables LRU bookkeeping for all queries defined by this crate.
67///
68/// This is useful for short-lived database users that don't need to evict query results across
69/// revisions.
70pub fn disable_lru(db: &mut dyn Db) {
71    parsed::disable_lru(db);
72}
73
74/// Most basic database that gives access to files, the host system, source code, and parsed AST.
75#[salsa::db]
76pub trait Db: salsa::Database {
77    fn vendored(&self) -> &VendoredFileSystem;
78    fn system(&self) -> &dyn System;
79    fn files(&self) -> &Files;
80}
81
82/// Returns the maximum number of tasks that ty is allowed
83/// to process in parallel.
84///
85/// Returns [`std::thread::available_parallelism`], unless the environment
86/// variable `TY_MAX_PARALLELISM` or `RAYON_NUM_THREADS` is set. `TY_MAX_PARALLELISM` takes
87/// precedence over `RAYON_NUM_THREADS`.
88///
89/// Falls back to `1` if `available_parallelism` is not available.
90///
91/// Setting `TY_MAX_PARALLELISM` to `2` only restricts the number of threads that ty spawns
92/// to process work in parallel. For example, to index a directory or checking the files of a project.
93/// ty can still spawn more threads for other tasks, e.g. to wait for a Ctrl+C signal or
94/// watching the files for changes.
95#[expect(
96    clippy::disallowed_methods,
97    reason = "We don't have access to System here, but this is also only used by the CLI and the server which always run on a real system."
98)]
99pub fn max_parallelism() -> NonZeroUsize {
100    std::env::var(EnvVars::TY_MAX_PARALLELISM)
101        .or_else(|_| std::env::var(EnvVars::RAYON_NUM_THREADS))
102        .ok()
103        .and_then(|s| s.parse().ok())
104        .unwrap_or_else(|| {
105            std::thread::available_parallelism().unwrap_or_else(|_| NonZeroUsize::new(1).unwrap())
106        })
107}
108
109// Use a reasonably large stack size to avoid running into stack overflows too easily. The
110// size was chosen in such a way as to still be able to handle large expressions involving
111// binary operators (x + x + … + x) both during the AST walk in semantic index building as
112// well as during type checking. Using this stack size, we can handle handle expressions
113// that are several times larger than the corresponding limits in existing type checkers.
114pub const STACK_SIZE: usize = 16 * 1024 * 1024;
115
116/// Trait for types that can provide Rust documentation.
117///
118/// Use `derive(RustDoc)` to automatically implement this trait for types that have a static string documentation.
119pub trait RustDoc {
120    fn rust_doc() -> &'static str;
121}
122
123#[cfg(test)]
124mod tests {
125    use std::sync::{Arc, Mutex};
126
127    use crate::Db;
128    use crate::files::Files;
129    use crate::system::TestSystem;
130    use crate::system::{DbWithTestSystem, System};
131    use crate::vendored::VendoredFileSystem;
132
133    type Events = Arc<Mutex<Vec<salsa::Event>>>;
134
135    /// Database that can be used for testing.
136    ///
137    /// Uses an in memory filesystem and it stubs out the vendored files by default.
138    #[salsa::db]
139    #[derive(Default, Clone)]
140    pub(crate) struct TestDb {
141        storage: salsa::Storage<Self>,
142        files: Files,
143        system: TestSystem,
144        vendored: VendoredFileSystem,
145        events: Events,
146    }
147
148    impl TestDb {
149        pub(crate) fn new() -> Self {
150            let events = Events::default();
151            Self {
152                storage: salsa::Storage::new(Some(Box::new({
153                    let events = events.clone();
154                    move |event| {
155                        tracing::trace!("event: {:?}", event);
156                        let mut events = events.lock().unwrap();
157                        events.push(event);
158                    }
159                }))),
160                system: TestSystem::default(),
161                vendored: VendoredFileSystem::default(),
162                events,
163                files: Files::default(),
164            }
165        }
166
167        /// Empties the internal store of salsa events that have been emitted,
168        /// and returns them as a `Vec` (equivalent to [`std::mem::take`]).
169        pub(crate) fn take_salsa_events(&mut self) -> Vec<salsa::Event> {
170            let mut events = self.events.lock().unwrap();
171
172            std::mem::take(&mut *events)
173        }
174
175        /// Clears the emitted salsa events.
176        pub(crate) fn clear_salsa_events(&mut self) {
177            self.take_salsa_events();
178        }
179
180        pub(crate) fn with_vendored(&mut self, vendored_file_system: VendoredFileSystem) {
181            self.vendored = vendored_file_system;
182        }
183    }
184
185    #[salsa::db]
186    impl Db for TestDb {
187        fn vendored(&self) -> &VendoredFileSystem {
188            &self.vendored
189        }
190
191        fn system(&self) -> &dyn System {
192            &self.system
193        }
194
195        fn files(&self) -> &Files {
196            &self.files
197        }
198    }
199
200    impl DbWithTestSystem for TestDb {
201        fn test_system(&self) -> &TestSystem {
202            &self.system
203        }
204
205        fn test_system_mut(&mut self) -> &mut TestSystem {
206            &mut self.system
207        }
208    }
209
210    #[salsa::db]
211    impl salsa::Database for TestDb {}
212}