Unnamed repository; edit this file 'description' to name the repository.
Auto merge of #16742 - alibektas:13529/source_root_tree, r=Veykril
internal: Implement parent-child relation for `SourceRoot`s This commit adds the said relation by keeping a map of type `FxHashMap<SourceRootId,Option<SourceRootId>>` inside the `GlobalState`. Its primary use case is reading `rust-analyzer.toml`(#13529) files that can be placed in every local source root. As a config will be found by traversing this "tree" we need the parent information for every local source root. This commit omits defining this relation for library source roots entirely.
bors 2024-03-07
parent 00f6a7a · parent 9c50d12 · commit a1fda64
-rw-r--r--crates/load-cargo/src/lib.rs158
-rw-r--r--crates/rust-analyzer/src/global_state.rs5
-rw-r--r--crates/rust-analyzer/src/reload.rs1
-rw-r--r--crates/vfs/src/file_set.rs5
4 files changed, 164 insertions, 5 deletions
diff --git a/crates/load-cargo/src/lib.rs b/crates/load-cargo/src/lib.rs
index 29c1251cce..a1c089520d 100644
--- a/crates/load-cargo/src/lib.rs
+++ b/crates/load-cargo/src/lib.rs
@@ -10,7 +10,7 @@ use hir_expand::proc_macro::{
ProcMacros,
};
use ide_db::{
- base_db::{CrateGraph, Env, SourceRoot},
+ base_db::{CrateGraph, Env, SourceRoot, SourceRootId},
prime_caches, ChangeWithProcMacros, FxHashMap, RootDatabase,
};
use itertools::Itertools;
@@ -231,7 +231,7 @@ impl ProjectFolders {
res.load.push(entry);
if root.is_local {
- local_filesets.push(fsc.len());
+ local_filesets.push(fsc.len() as u64);
}
fsc.add_file_set(file_set_roots)
}
@@ -246,7 +246,7 @@ impl ProjectFolders {
#[derive(Default, Debug)]
pub struct SourceRootConfig {
pub fsc: FileSetConfig,
- pub local_filesets: Vec<usize>,
+ pub local_filesets: Vec<u64>,
}
impl SourceRootConfig {
@@ -256,7 +256,7 @@ impl SourceRootConfig {
.into_iter()
.enumerate()
.map(|(idx, file_set)| {
- let is_local = self.local_filesets.contains(&idx);
+ let is_local = self.local_filesets.contains(&(idx as u64));
if is_local {
SourceRoot::new_local(file_set)
} else {
@@ -265,6 +265,31 @@ impl SourceRootConfig {
})
.collect()
}
+
+ /// Maps local source roots to their parent source roots by bytewise comparing of root paths .
+ /// If a `SourceRoot` doesn't have a parent and is local then it is not contained in this mapping but it can be asserted that it is a root `SourceRoot`.
+ pub fn source_root_parent_map(&self) -> FxHashMap<SourceRootId, SourceRootId> {
+ let roots = self.fsc.roots();
+ let mut map = FxHashMap::<SourceRootId, SourceRootId>::default();
+ roots
+ .iter()
+ .enumerate()
+ .filter(|(_, (_, id))| self.local_filesets.contains(id))
+ .filter_map(|(idx, (root, root_id))| {
+ // We are interested in parents if they are also local source roots.
+ // So instead of a non-local parent we may take a local ancestor as a parent to a node.
+ roots.iter().take(idx).find_map(|(root2, root2_id)| {
+ if self.local_filesets.contains(root2_id) && root.starts_with(root2) {
+ return Some((root_id, root2_id));
+ }
+ None
+ })
+ })
+ .for_each(|(child, parent)| {
+ map.insert(SourceRootId(*child as u32), SourceRootId(*parent as u32));
+ });
+ map
+ }
}
/// Load the proc-macros for the given lib path, replacing all expanders whose names are in `dummy_replace`
@@ -397,6 +422,11 @@ mod tests {
use super::*;
+ use ide_db::base_db::SourceRootId;
+ use vfs::{file_set::FileSetConfigBuilder, VfsPath};
+
+ use crate::SourceRootConfig;
+
#[test]
fn test_loading_rust_analyzer() {
let path = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent().unwrap();
@@ -413,4 +443,124 @@ mod tests {
// RA has quite a few crates, but the exact count doesn't matter
assert!(n_crates > 20);
}
+
+ #[test]
+ fn unrelated_sources() {
+ let mut builder = FileSetConfigBuilder::default();
+ builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
+ builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def".to_owned())]);
+ let fsc = builder.build();
+ let src = SourceRootConfig { fsc, local_filesets: vec![0, 1] };
+ let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
+
+ assert_eq!(vc, vec![])
+ }
+
+ #[test]
+ fn unrelated_source_sharing_dirname() {
+ let mut builder = FileSetConfigBuilder::default();
+ builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
+ builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/abc".to_owned())]);
+ let fsc = builder.build();
+ let src = SourceRootConfig { fsc, local_filesets: vec![0, 1] };
+ let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
+
+ assert_eq!(vc, vec![])
+ }
+
+ #[test]
+ fn basic_child_parent() {
+ let mut builder = FileSetConfigBuilder::default();
+ builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
+ builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc/def".to_owned())]);
+ let fsc = builder.build();
+ let src = SourceRootConfig { fsc, local_filesets: vec![0, 1] };
+ let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
+
+ assert_eq!(vc, vec![(SourceRootId(1), SourceRootId(0))])
+ }
+
+ #[test]
+ fn basic_child_parent_with_unrelated_parents_sib() {
+ let mut builder = FileSetConfigBuilder::default();
+ builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
+ builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def".to_owned())]);
+ builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/abc".to_owned())]);
+ let fsc = builder.build();
+ let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 2] };
+ let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
+
+ assert_eq!(vc, vec![(SourceRootId(2), SourceRootId(1))])
+ }
+
+ #[test]
+ fn deep_sources_with_parent_missing() {
+ let mut builder = FileSetConfigBuilder::default();
+ builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
+ builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/ghi".to_owned())]);
+ builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/abc".to_owned())]);
+ let fsc = builder.build();
+ let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 2] };
+ let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
+
+ assert_eq!(vc, vec![])
+ }
+
+ #[test]
+ fn ancestor_can_be_parent() {
+ let mut builder = FileSetConfigBuilder::default();
+ builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
+ builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def".to_owned())]);
+ builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/ghi/jkl".to_owned())]);
+ let fsc = builder.build();
+ let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 2] };
+ let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
+
+ assert_eq!(vc, vec![(SourceRootId(2), SourceRootId(1))])
+ }
+
+ #[test]
+ fn ancestor_can_be_parent_2() {
+ let mut builder = FileSetConfigBuilder::default();
+ builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
+ builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def".to_owned())]);
+ builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/ghi/jkl".to_owned())]);
+ builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/ghi/klm".to_owned())]);
+ let fsc = builder.build();
+ let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 2, 3] };
+ let mut vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
+ vc.sort_by(|x, y| x.0 .0.cmp(&y.0 .0));
+
+ assert_eq!(vc, vec![(SourceRootId(2), SourceRootId(1)), (SourceRootId(3), SourceRootId(1))])
+ }
+
+ #[test]
+ fn non_locals_are_skipped() {
+ let mut builder = FileSetConfigBuilder::default();
+ builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
+ builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def".to_owned())]);
+ builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/ghi/jkl".to_owned())]);
+ builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/klm".to_owned())]);
+ let fsc = builder.build();
+ let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 3] };
+ let mut vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
+ vc.sort_by(|x, y| x.0 .0.cmp(&y.0 .0));
+
+ assert_eq!(vc, vec![(SourceRootId(3), SourceRootId(1)),])
+ }
+
+ #[test]
+ fn child_binds_ancestor_if_parent_nonlocal() {
+ let mut builder = FileSetConfigBuilder::default();
+ builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
+ builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def".to_owned())]);
+ builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/klm".to_owned())]);
+ builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/klm/jkl".to_owned())]);
+ let fsc = builder.build();
+ let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 3] };
+ let mut vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
+ vc.sort_by(|x, y| x.0 .0.cmp(&y.0 .0));
+
+ assert_eq!(vc, vec![(SourceRootId(3), SourceRootId(1)),])
+ }
}
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index 560410e332..0e560e54ed 100644
--- a/crates/rust-analyzer/src/global_state.rs
+++ b/crates/rust-analyzer/src/global_state.rs
@@ -8,7 +8,7 @@ use std::{collections::hash_map::Entry, time::Instant};
use crossbeam_channel::{unbounded, Receiver, Sender};
use flycheck::FlycheckHandle;
use hir::ChangeWithProcMacros;
-use ide::{Analysis, AnalysisHost, Cancellable, FileId};
+use ide::{Analysis, AnalysisHost, Cancellable, FileId, SourceRootId};
use ide_db::base_db::{CrateId, ProcMacroPaths};
use load_cargo::SourceRootConfig;
use lsp_types::{SemanticTokens, Url};
@@ -66,6 +66,8 @@ pub(crate) struct GlobalState {
pub(crate) diagnostics: DiagnosticCollection,
pub(crate) mem_docs: MemDocs,
pub(crate) source_root_config: SourceRootConfig,
+ /// A mapping that maps a local source root's `SourceRootId` to it parent's `SourceRootId`, if it has one.
+ pub(crate) local_roots_parent_map: FxHashMap<SourceRootId, SourceRootId>,
pub(crate) semantic_tokens_cache: Arc<Mutex<FxHashMap<Url, SemanticTokens>>>,
// status
@@ -204,6 +206,7 @@ impl GlobalState {
send_hint_refresh_query: false,
last_reported_status: None,
source_root_config: SourceRootConfig::default(),
+ local_roots_parent_map: FxHashMap::default(),
config_errors: Default::default(),
proc_macro_clients: Arc::from_iter([]),
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index 95d51baab3..c2725e1fad 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -515,6 +515,7 @@ impl GlobalState {
version: self.vfs_config_version,
});
self.source_root_config = project_folders.source_root_config;
+ self.local_roots_parent_map = self.source_root_config.source_root_parent_map();
self.recreate_crate_graph(cause);
diff --git a/crates/vfs/src/file_set.rs b/crates/vfs/src/file_set.rs
index 0392ef3ceb..7eeb10d544 100644
--- a/crates/vfs/src/file_set.rs
+++ b/crates/vfs/src/file_set.rs
@@ -123,6 +123,11 @@ impl FileSetConfig {
self.n_file_sets
}
+ /// Get the lexicographically ordered vector of the underlying map.
+ pub fn roots(&self) -> Vec<(Vec<u8>, u64)> {
+ self.map.stream().into_byte_vec()
+ }
+
/// Returns the set index for the given `path`.
///
/// `scratch_space` is used as a buffer and will be entirely replaced.