Skip to content

Commit a1fda64

Browse files
committed
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.
2 parents 00f6a7a + 9c50d12 commit a1fda64

File tree

4 files changed

+164
-5
lines changed

4 files changed

+164
-5
lines changed

crates/load-cargo/src/lib.rs

+154-4
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use hir_expand::proc_macro::{
1010
ProcMacros,
1111
};
1212
use ide_db::{
13-
base_db::{CrateGraph, Env, SourceRoot},
13+
base_db::{CrateGraph, Env, SourceRoot, SourceRootId},
1414
prime_caches, ChangeWithProcMacros, FxHashMap, RootDatabase,
1515
};
1616
use itertools::Itertools;
@@ -231,7 +231,7 @@ impl ProjectFolders {
231231
res.load.push(entry);
232232

233233
if root.is_local {
234-
local_filesets.push(fsc.len());
234+
local_filesets.push(fsc.len() as u64);
235235
}
236236
fsc.add_file_set(file_set_roots)
237237
}
@@ -246,7 +246,7 @@ impl ProjectFolders {
246246
#[derive(Default, Debug)]
247247
pub struct SourceRootConfig {
248248
pub fsc: FileSetConfig,
249-
pub local_filesets: Vec<usize>,
249+
pub local_filesets: Vec<u64>,
250250
}
251251

252252
impl SourceRootConfig {
@@ -256,7 +256,7 @@ impl SourceRootConfig {
256256
.into_iter()
257257
.enumerate()
258258
.map(|(idx, file_set)| {
259-
let is_local = self.local_filesets.contains(&idx);
259+
let is_local = self.local_filesets.contains(&(idx as u64));
260260
if is_local {
261261
SourceRoot::new_local(file_set)
262262
} else {
@@ -265,6 +265,31 @@ impl SourceRootConfig {
265265
})
266266
.collect()
267267
}
268+
269+
/// Maps local source roots to their parent source roots by bytewise comparing of root paths .
270+
/// 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`.
271+
pub fn source_root_parent_map(&self) -> FxHashMap<SourceRootId, SourceRootId> {
272+
let roots = self.fsc.roots();
273+
let mut map = FxHashMap::<SourceRootId, SourceRootId>::default();
274+
roots
275+
.iter()
276+
.enumerate()
277+
.filter(|(_, (_, id))| self.local_filesets.contains(id))
278+
.filter_map(|(idx, (root, root_id))| {
279+
// We are interested in parents if they are also local source roots.
280+
// So instead of a non-local parent we may take a local ancestor as a parent to a node.
281+
roots.iter().take(idx).find_map(|(root2, root2_id)| {
282+
if self.local_filesets.contains(root2_id) && root.starts_with(root2) {
283+
return Some((root_id, root2_id));
284+
}
285+
None
286+
})
287+
})
288+
.for_each(|(child, parent)| {
289+
map.insert(SourceRootId(*child as u32), SourceRootId(*parent as u32));
290+
});
291+
map
292+
}
268293
}
269294

270295
/// Load the proc-macros for the given lib path, replacing all expanders whose names are in `dummy_replace`
@@ -397,6 +422,11 @@ mod tests {
397422

398423
use super::*;
399424

425+
use ide_db::base_db::SourceRootId;
426+
use vfs::{file_set::FileSetConfigBuilder, VfsPath};
427+
428+
use crate::SourceRootConfig;
429+
400430
#[test]
401431
fn test_loading_rust_analyzer() {
402432
let path = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent().unwrap();
@@ -413,4 +443,124 @@ mod tests {
413443
// RA has quite a few crates, but the exact count doesn't matter
414444
assert!(n_crates > 20);
415445
}
446+
447+
#[test]
448+
fn unrelated_sources() {
449+
let mut builder = FileSetConfigBuilder::default();
450+
builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
451+
builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def".to_owned())]);
452+
let fsc = builder.build();
453+
let src = SourceRootConfig { fsc, local_filesets: vec![0, 1] };
454+
let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
455+
456+
assert_eq!(vc, vec![])
457+
}
458+
459+
#[test]
460+
fn unrelated_source_sharing_dirname() {
461+
let mut builder = FileSetConfigBuilder::default();
462+
builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
463+
builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/abc".to_owned())]);
464+
let fsc = builder.build();
465+
let src = SourceRootConfig { fsc, local_filesets: vec![0, 1] };
466+
let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
467+
468+
assert_eq!(vc, vec![])
469+
}
470+
471+
#[test]
472+
fn basic_child_parent() {
473+
let mut builder = FileSetConfigBuilder::default();
474+
builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
475+
builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc/def".to_owned())]);
476+
let fsc = builder.build();
477+
let src = SourceRootConfig { fsc, local_filesets: vec![0, 1] };
478+
let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
479+
480+
assert_eq!(vc, vec![(SourceRootId(1), SourceRootId(0))])
481+
}
482+
483+
#[test]
484+
fn basic_child_parent_with_unrelated_parents_sib() {
485+
let mut builder = FileSetConfigBuilder::default();
486+
builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
487+
builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def".to_owned())]);
488+
builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/abc".to_owned())]);
489+
let fsc = builder.build();
490+
let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 2] };
491+
let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
492+
493+
assert_eq!(vc, vec![(SourceRootId(2), SourceRootId(1))])
494+
}
495+
496+
#[test]
497+
fn deep_sources_with_parent_missing() {
498+
let mut builder = FileSetConfigBuilder::default();
499+
builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
500+
builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/ghi".to_owned())]);
501+
builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/abc".to_owned())]);
502+
let fsc = builder.build();
503+
let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 2] };
504+
let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
505+
506+
assert_eq!(vc, vec![])
507+
}
508+
509+
#[test]
510+
fn ancestor_can_be_parent() {
511+
let mut builder = FileSetConfigBuilder::default();
512+
builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
513+
builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def".to_owned())]);
514+
builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/ghi/jkl".to_owned())]);
515+
let fsc = builder.build();
516+
let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 2] };
517+
let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
518+
519+
assert_eq!(vc, vec![(SourceRootId(2), SourceRootId(1))])
520+
}
521+
522+
#[test]
523+
fn ancestor_can_be_parent_2() {
524+
let mut builder = FileSetConfigBuilder::default();
525+
builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
526+
builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def".to_owned())]);
527+
builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/ghi/jkl".to_owned())]);
528+
builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/ghi/klm".to_owned())]);
529+
let fsc = builder.build();
530+
let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 2, 3] };
531+
let mut vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
532+
vc.sort_by(|x, y| x.0 .0.cmp(&y.0 .0));
533+
534+
assert_eq!(vc, vec![(SourceRootId(2), SourceRootId(1)), (SourceRootId(3), SourceRootId(1))])
535+
}
536+
537+
#[test]
538+
fn non_locals_are_skipped() {
539+
let mut builder = FileSetConfigBuilder::default();
540+
builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
541+
builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def".to_owned())]);
542+
builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/ghi/jkl".to_owned())]);
543+
builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/klm".to_owned())]);
544+
let fsc = builder.build();
545+
let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 3] };
546+
let mut vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
547+
vc.sort_by(|x, y| x.0 .0.cmp(&y.0 .0));
548+
549+
assert_eq!(vc, vec![(SourceRootId(3), SourceRootId(1)),])
550+
}
551+
552+
#[test]
553+
fn child_binds_ancestor_if_parent_nonlocal() {
554+
let mut builder = FileSetConfigBuilder::default();
555+
builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
556+
builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def".to_owned())]);
557+
builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/klm".to_owned())]);
558+
builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/klm/jkl".to_owned())]);
559+
let fsc = builder.build();
560+
let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 3] };
561+
let mut vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
562+
vc.sort_by(|x, y| x.0 .0.cmp(&y.0 .0));
563+
564+
assert_eq!(vc, vec![(SourceRootId(3), SourceRootId(1)),])
565+
}
416566
}

crates/rust-analyzer/src/global_state.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::{collections::hash_map::Entry, time::Instant};
88
use crossbeam_channel::{unbounded, Receiver, Sender};
99
use flycheck::FlycheckHandle;
1010
use hir::ChangeWithProcMacros;
11-
use ide::{Analysis, AnalysisHost, Cancellable, FileId};
11+
use ide::{Analysis, AnalysisHost, Cancellable, FileId, SourceRootId};
1212
use ide_db::base_db::{CrateId, ProcMacroPaths};
1313
use load_cargo::SourceRootConfig;
1414
use lsp_types::{SemanticTokens, Url};
@@ -66,6 +66,8 @@ pub(crate) struct GlobalState {
6666
pub(crate) diagnostics: DiagnosticCollection,
6767
pub(crate) mem_docs: MemDocs,
6868
pub(crate) source_root_config: SourceRootConfig,
69+
/// A mapping that maps a local source root's `SourceRootId` to it parent's `SourceRootId`, if it has one.
70+
pub(crate) local_roots_parent_map: FxHashMap<SourceRootId, SourceRootId>,
6971
pub(crate) semantic_tokens_cache: Arc<Mutex<FxHashMap<Url, SemanticTokens>>>,
7072

7173
// status
@@ -204,6 +206,7 @@ impl GlobalState {
204206
send_hint_refresh_query: false,
205207
last_reported_status: None,
206208
source_root_config: SourceRootConfig::default(),
209+
local_roots_parent_map: FxHashMap::default(),
207210
config_errors: Default::default(),
208211

209212
proc_macro_clients: Arc::from_iter([]),

crates/rust-analyzer/src/reload.rs

+1
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,7 @@ impl GlobalState {
515515
version: self.vfs_config_version,
516516
});
517517
self.source_root_config = project_folders.source_root_config;
518+
self.local_roots_parent_map = self.source_root_config.source_root_parent_map();
518519

519520
self.recreate_crate_graph(cause);
520521

crates/vfs/src/file_set.rs

+5
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,11 @@ impl FileSetConfig {
123123
self.n_file_sets
124124
}
125125

126+
/// Get the lexicographically ordered vector of the underlying map.
127+
pub fn roots(&self) -> Vec<(Vec<u8>, u64)> {
128+
self.map.stream().into_byte_vec()
129+
}
130+
126131
/// Returns the set index for the given `path`.
127132
///
128133
/// `scratch_space` is used as a buffer and will be entirely replaced.

0 commit comments

Comments
 (0)