Skip to content

Commit

Permalink
Auto merge of #16742 - alibektas:13529/source_root_tree, r=Veykril
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
bors committed Mar 7, 2024
2 parents 00f6a7a + 9c50d12 commit a1fda64
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 5 deletions.
158 changes: 154 additions & 4 deletions crates/load-cargo/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
}
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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`
Expand Down Expand Up @@ -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();
Expand All @@ -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)),])
}
}
5 changes: 4 additions & 1 deletion crates/rust-analyzer/src/global_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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([]),
Expand Down
1 change: 1 addition & 0 deletions crates/rust-analyzer/src/reload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
5 changes: 5 additions & 0 deletions crates/vfs/src/file_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit a1fda64

Please sign in to comment.