1#![cfg_attr(feature = "in-rust-tree", feature(rustc_private))]
4
5#[cfg(feature = "in-rust-tree")]
6extern crate rustc_driver as _;
7
8pub use salsa;
9pub use salsa_macros;
10
11mod change;
13mod editioned_file_id;
14mod input;
15pub mod target;
16
17use std::{
18 cell::RefCell,
19 hash::BuildHasherDefault,
20 panic,
21 sync::{Once, atomic::AtomicUsize},
22};
23
24pub use crate::{
25 change::FileChange,
26 editioned_file_id::EditionedFileId,
27 input::{
28 BuiltCrateData, BuiltDependency, Crate, CrateBuilder, CrateBuilderId, CrateDataBuilder,
29 CrateDisplayName, CrateGraphBuilder, CrateName, CrateOrigin, CratesIdMap, CratesMap,
30 DependencyBuilder, Env, ExtraCrateData, LangCrateOrigin, ProcMacroLoadingError,
31 ProcMacroPaths, ReleaseChannel, SourceRoot, SourceRootId, UniqueCrateData,
32 },
33};
34use dashmap::{DashMap, mapref::entry::Entry};
35pub use query_group::{self};
36use rustc_hash::FxHasher;
37use salsa::{Durability, Setter};
38pub use semver::{BuildMetadata, Prerelease, Version, VersionReq};
39use syntax::{Parse, SyntaxError, ast};
40use triomphe::Arc;
41pub use vfs::{AnchoredPath, AnchoredPathBuf, FileId, VfsPath, file_set::FileSet};
42
43pub type FxIndexSet<T> = indexmap::IndexSet<T, rustc_hash::FxBuildHasher>;
44pub type FxIndexMap<K, V> =
45 indexmap::IndexMap<K, V, std::hash::BuildHasherDefault<rustc_hash::FxHasher>>;
46
47#[macro_export]
48macro_rules! impl_intern_key {
49 ($id:ident, $loc:ident) => {
50 #[salsa_macros::interned(no_lifetime, revisions = usize::MAX)]
51 #[derive(PartialOrd, Ord)]
52 pub struct $id {
53 pub loc: $loc,
54 }
55
56 impl ::std::fmt::Debug for $id {
58 fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
59 f.debug_tuple(stringify!($id))
60 .field(&format_args!("{:04x}", self.0.index()))
61 .finish()
62 }
63 }
64 };
65}
66
67pub unsafe fn unsafe_update_eq<T>(old_pointer: *mut T, new_value: T) -> bool
71where
72 T: PartialEq,
73{
74 let old_ref: &mut T = unsafe { &mut *old_pointer };
76
77 if *old_ref != new_value {
78 *old_ref = new_value;
79 true
80 } else {
81 false
86 }
87}
88
89pub const DEFAULT_FILE_TEXT_LRU_CAP: u16 = 16;
90pub const DEFAULT_PARSE_LRU_CAP: u16 = 128;
91pub const DEFAULT_BORROWCK_LRU_CAP: u16 = 2024;
92
93#[derive(Debug, Default)]
94pub struct Files {
95 files: Arc<DashMap<vfs::FileId, FileText, BuildHasherDefault<FxHasher>>>,
96 source_roots: Arc<DashMap<SourceRootId, SourceRootInput, BuildHasherDefault<FxHasher>>>,
97 file_source_roots: Arc<DashMap<vfs::FileId, FileSourceRootInput, BuildHasherDefault<FxHasher>>>,
98}
99
100impl Files {
101 pub fn file_text(&self, file_id: vfs::FileId) -> FileText {
102 match self.files.get(&file_id) {
103 Some(text) => *text,
104 None => {
105 panic!("Unable to fetch file text for `vfs::FileId`: {file_id:?}; this is a bug")
106 }
107 }
108 }
109
110 pub fn set_file_text(&self, db: &mut dyn SourceDatabase, file_id: vfs::FileId, text: &str) {
111 match self.files.entry(file_id) {
112 Entry::Occupied(mut occupied) => {
113 occupied.get_mut().set_text(db).to(Arc::from(text));
114 }
115 Entry::Vacant(vacant) => {
116 let text = FileText::new(db, Arc::from(text), file_id);
117 vacant.insert(text);
118 }
119 };
120 }
121
122 pub fn set_file_text_with_durability(
123 &self,
124 db: &mut dyn SourceDatabase,
125 file_id: vfs::FileId,
126 text: &str,
127 durability: Durability,
128 ) {
129 match self.files.entry(file_id) {
130 Entry::Occupied(mut occupied) => {
131 occupied.get_mut().set_text(db).with_durability(durability).to(Arc::from(text));
132 }
133 Entry::Vacant(vacant) => {
134 let text =
135 FileText::builder(Arc::from(text), file_id).durability(durability).new(db);
136 vacant.insert(text);
137 }
138 };
139 }
140
141 pub fn source_root(&self, source_root_id: SourceRootId) -> SourceRootInput {
143 let source_root = match self.source_roots.get(&source_root_id) {
144 Some(source_root) => source_root,
145 None => panic!(
146 "Unable to fetch `SourceRootInput` with `SourceRootId` ({source_root_id:?}); this is a bug"
147 ),
148 };
149
150 *source_root
151 }
152
153 pub fn set_source_root_with_durability(
154 &self,
155 db: &mut dyn SourceDatabase,
156 source_root_id: SourceRootId,
157 source_root: Arc<SourceRoot>,
158 durability: Durability,
159 ) {
160 match self.source_roots.entry(source_root_id) {
161 Entry::Occupied(mut occupied) => {
162 occupied.get_mut().set_source_root(db).with_durability(durability).to(source_root);
163 }
164 Entry::Vacant(vacant) => {
165 let source_root =
166 SourceRootInput::builder(source_root).durability(durability).new(db);
167 vacant.insert(source_root);
168 }
169 };
170 }
171
172 pub fn file_source_root(&self, id: vfs::FileId) -> FileSourceRootInput {
173 let file_source_root = match self.file_source_roots.get(&id) {
174 Some(file_source_root) => file_source_root,
175 None => panic!(
176 "Unable to get `FileSourceRootInput` with `vfs::FileId` ({id:?}); this is a bug",
177 ),
178 };
179 *file_source_root
180 }
181
182 pub fn set_file_source_root_with_durability(
183 &self,
184 db: &mut dyn SourceDatabase,
185 id: vfs::FileId,
186 source_root_id: SourceRootId,
187 durability: Durability,
188 ) {
189 match self.file_source_roots.entry(id) {
190 Entry::Occupied(mut occupied) => {
191 occupied
192 .get_mut()
193 .set_source_root_id(db)
194 .with_durability(durability)
195 .to(source_root_id);
196 }
197 Entry::Vacant(vacant) => {
198 let file_source_root =
199 FileSourceRootInput::builder(source_root_id).durability(durability).new(db);
200 vacant.insert(file_source_root);
201 }
202 };
203 }
204}
205
206#[salsa_macros::input(debug)]
207pub struct FileText {
208 #[returns(ref)]
209 pub text: Arc<str>,
210 pub file_id: vfs::FileId,
211}
212
213#[salsa_macros::input(debug)]
214pub struct FileSourceRootInput {
215 pub source_root_id: SourceRootId,
216}
217
218#[salsa_macros::input(debug)]
219pub struct SourceRootInput {
220 pub source_root: Arc<SourceRoot>,
221}
222
223#[query_group::query_group]
226pub trait RootQueryDb: SourceDatabase + salsa::Database {
227 #[salsa::invoke(parse)]
229 #[salsa::lru(128)]
230 fn parse(&self, file_id: EditionedFileId) -> Parse<ast::SourceFile>;
231
232 #[salsa::transparent]
234 fn parse_errors(&self, file_id: EditionedFileId) -> Option<&[SyntaxError]>;
235
236 #[salsa::transparent]
237 fn toolchain_channel(&self, krate: Crate) -> Option<ReleaseChannel>;
238
239 #[salsa::invoke_interned(source_root_crates)]
241 fn source_root_crates(&self, id: SourceRootId) -> Arc<[Crate]>;
242
243 #[salsa::transparent]
244 fn relevant_crates(&self, file_id: FileId) -> Arc<[Crate]>;
245
246 #[salsa::input]
250 fn all_crates(&self) -> Arc<Box<[Crate]>>;
251}
252
253#[salsa_macros::db]
254pub trait SourceDatabase: salsa::Database {
255 fn file_text(&self, file_id: vfs::FileId) -> FileText;
257
258 fn set_file_text(&mut self, file_id: vfs::FileId, text: &str);
259
260 fn set_file_text_with_durability(
261 &mut self,
262 file_id: vfs::FileId,
263 text: &str,
264 durability: Durability,
265 );
266
267 fn source_root(&self, id: SourceRootId) -> SourceRootInput;
269
270 fn file_source_root(&self, id: vfs::FileId) -> FileSourceRootInput;
271
272 fn set_file_source_root_with_durability(
273 &mut self,
274 id: vfs::FileId,
275 source_root_id: SourceRootId,
276 durability: Durability,
277 );
278
279 fn set_source_root_with_durability(
281 &mut self,
282 source_root_id: SourceRootId,
283 source_root: Arc<SourceRoot>,
284 durability: Durability,
285 );
286
287 fn resolve_path(&self, path: AnchoredPath<'_>) -> Option<FileId> {
288 let source_root = self.file_source_root(path.anchor);
290 let source_root = self.source_root(source_root.source_root_id(self));
291 source_root.source_root(self).resolve_path(path)
292 }
293
294 #[doc(hidden)]
295 fn crates_map(&self) -> Arc<CratesMap>;
296
297 fn nonce_and_revision(&self) -> (Nonce, salsa::Revision);
298}
299
300static NEXT_NONCE: AtomicUsize = AtomicUsize::new(0);
301
302#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
303pub struct Nonce(usize);
304
305impl Default for Nonce {
306 #[inline]
307 fn default() -> Self {
308 Nonce::new()
309 }
310}
311
312impl Nonce {
313 #[inline]
314 pub fn new() -> Nonce {
315 Nonce(NEXT_NONCE.fetch_add(1, std::sync::atomic::Ordering::SeqCst))
316 }
317}
318
319#[derive(Debug, PartialEq, Eq, Hash, Clone)]
321pub struct CrateWorkspaceData {
322 pub target: Result<target::TargetData, target::TargetLoadError>,
323 pub toolchain: Option<Version>,
325}
326
327impl CrateWorkspaceData {
328 pub fn is_atleast_187(&self) -> bool {
329 const VERSION_187: Version = Version {
330 major: 1,
331 minor: 87,
332 patch: 0,
333 pre: Prerelease::EMPTY,
334 build: BuildMetadata::EMPTY,
335 };
336 self.toolchain.as_ref().map_or(false, |v| *v >= VERSION_187)
337 }
338}
339
340fn toolchain_channel(db: &dyn RootQueryDb, krate: Crate) -> Option<ReleaseChannel> {
341 krate.workspace_data(db).toolchain.as_ref().and_then(|v| ReleaseChannel::from_str(&v.pre))
342}
343
344fn parse(db: &dyn RootQueryDb, file_id: EditionedFileId) -> Parse<ast::SourceFile> {
345 let _p = tracing::info_span!("parse", ?file_id).entered();
346 let (file_id, edition) = file_id.unpack(db.as_dyn_database());
347 let text = db.file_text(file_id).text(db);
348 ast::SourceFile::parse(text, edition)
349}
350
351fn parse_errors(db: &dyn RootQueryDb, file_id: EditionedFileId) -> Option<&[SyntaxError]> {
352 #[salsa_macros::tracked(returns(ref))]
353 fn parse_errors(db: &dyn RootQueryDb, file_id: EditionedFileId) -> Option<Box<[SyntaxError]>> {
354 let errors = db.parse(file_id).errors();
355 match &*errors {
356 [] => None,
357 [..] => Some(errors.into()),
358 }
359 }
360 parse_errors(db, file_id).as_ref().map(|it| &**it)
361}
362
363fn source_root_crates(db: &dyn RootQueryDb, id: SourceRootId) -> Arc<[Crate]> {
364 let crates = db.all_crates();
365 crates
366 .iter()
367 .copied()
368 .filter(|&krate| {
369 let root_file = krate.data(db).root_file_id;
370 db.file_source_root(root_file).source_root_id(db) == id
371 })
372 .collect()
373}
374
375fn relevant_crates(db: &dyn RootQueryDb, file_id: FileId) -> Arc<[Crate]> {
376 let _p = tracing::info_span!("relevant_crates").entered();
377
378 let source_root = db.file_source_root(file_id);
379 db.source_root_crates(source_root.source_root_id(db))
380}
381
382#[must_use]
383#[non_exhaustive]
384pub struct DbPanicContext;
385
386impl Drop for DbPanicContext {
387 fn drop(&mut self) {
388 Self::with_ctx(|ctx| assert!(ctx.pop().is_some()));
389 }
390}
391
392impl DbPanicContext {
393 pub fn enter(frame: String) -> DbPanicContext {
394 #[expect(clippy::print_stderr, reason = "already panicking anyway")]
395 fn set_hook() {
396 let default_hook = panic::take_hook();
397 panic::set_hook(Box::new(move |panic_info| {
398 default_hook(panic_info);
399 if let Some(backtrace) = salsa::Backtrace::capture() {
400 eprintln!("{backtrace:#}");
401 }
402 DbPanicContext::with_ctx(|ctx| {
403 if !ctx.is_empty() {
404 eprintln!("additional context:");
405 for (idx, frame) in ctx.iter().enumerate() {
406 eprintln!("{idx:>4}: {frame}\n");
407 }
408 }
409 });
410 }));
411 }
412
413 static SET_HOOK: Once = Once::new();
414 SET_HOOK.call_once(set_hook);
415
416 Self::with_ctx(|ctx| ctx.push(frame));
417 DbPanicContext
418 }
419
420 fn with_ctx(f: impl FnOnce(&mut Vec<String>)) {
421 thread_local! {
422 static CTX: RefCell<Vec<String>> = const { RefCell::new(Vec::new()) };
423 }
424 CTX.with(|ctx| f(&mut ctx.borrow_mut()));
425 }
426}