Skip to content

Commit a15cc86

Browse files
committed
Implement parent-child relation for SourceRoots
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 the rust-analyzer.toml files that can be placed under 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.
1 parent ce15e73 commit a15cc86

File tree

4 files changed

+249
-5
lines changed

4 files changed

+249
-5
lines changed

crates/load-cargo/src/lib.rs

Lines changed: 233 additions & 4 deletions
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,36 @@ 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 source root doesn't have a parent then its parent is declared as None.
271+
pub fn source_root_parent_map(&self) -> FxHashMap<SourceRootId, Option<SourceRootId>> {
272+
let roots = self.fsc.roots();
273+
let mut map = FxHashMap::<SourceRootId, Option<SourceRootId>>::default();
274+
275+
'outer: for (idx, (root, root_id)) in roots.iter().enumerate() {
276+
if !self.local_filesets.contains(root_id) {
277+
continue;
278+
}
279+
280+
for (_, (root2, root2_id)) in roots.iter().enumerate().take(idx).rev() {
281+
if root2.iter().enumerate().all(|(i, c)| &root[i] == c) {
282+
// We are interested in parents if they are also local source roots.
283+
// So instead of a non-local parent we may take a local ancestor as a parent to a node.
284+
if self.local_filesets.contains(root2_id) {
285+
map.insert(
286+
SourceRootId(root_id.to_owned() as u32),
287+
Some(SourceRootId(root2_id.to_owned() as u32)),
288+
);
289+
continue 'outer;
290+
}
291+
}
292+
}
293+
map.insert(SourceRootId(idx as u32), None);
294+
}
295+
296+
map
297+
}
268298
}
269299

270300
/// Load the proc-macros for the given lib path, replacing all expanders whose names are in `dummy_replace`
@@ -413,4 +443,203 @@ mod tests {
413443
// RA has quite a few crates, but the exact count doesn't matter
414444
assert!(n_crates > 20);
415445
}
446+
447+
mod source_root_parent {
448+
use ide_db::base_db::SourceRootId;
449+
use vfs::{file_set::FileSetConfigBuilder, VfsPath};
450+
451+
use crate::SourceRootConfig;
452+
453+
macro_rules! virp {
454+
($s : literal) => {
455+
VfsPath::new_virtual_path(format!($s))
456+
};
457+
}
458+
459+
#[test]
460+
fn test1() {
461+
let mut builder = FileSetConfigBuilder::default();
462+
let root = vec![virp!("/ROOT/abc")];
463+
let root2 = vec![virp!("/ROOT/def")];
464+
builder.add_file_set(root);
465+
builder.add_file_set(root2);
466+
let fsc = builder.build();
467+
let src = SourceRootConfig { fsc, local_filesets: vec![0, 1] };
468+
let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
469+
470+
assert_eq!(vc, vec![(SourceRootId(0), None), (SourceRootId(1), None)])
471+
}
472+
473+
#[test]
474+
fn test2() {
475+
let mut builder = FileSetConfigBuilder::default();
476+
let root = vec![virp!("/ROOT/abc")];
477+
let root2 = vec![virp!("/ROOT/def/abc")];
478+
builder.add_file_set(root);
479+
builder.add_file_set(root2);
480+
let fsc = builder.build();
481+
let src = SourceRootConfig { fsc, local_filesets: vec![0, 1] };
482+
let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
483+
484+
assert_eq!(vc, vec![(SourceRootId(0), None), (SourceRootId(1), None)])
485+
}
486+
487+
#[test]
488+
fn test3() {
489+
let mut builder = FileSetConfigBuilder::default();
490+
let root = vec![virp!("/ROOT/abc")];
491+
let root2 = vec![virp!("/ROOT/abc/def")];
492+
builder.add_file_set(root);
493+
builder.add_file_set(root2);
494+
let fsc = builder.build();
495+
let src = SourceRootConfig { fsc, local_filesets: vec![0, 1] };
496+
let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
497+
498+
assert_eq!(vc, vec![(SourceRootId(0), None), (SourceRootId(1), Some(SourceRootId(0)))])
499+
}
500+
501+
#[test]
502+
fn test4() {
503+
let mut builder = FileSetConfigBuilder::default();
504+
let root = vec![virp!("/ROOT/abc")];
505+
let root2 = vec![virp!("/ROOT/def")];
506+
let root3 = vec![virp!("/ROOT/def/abc")];
507+
builder.add_file_set(root);
508+
builder.add_file_set(root2);
509+
builder.add_file_set(root3);
510+
let fsc = builder.build();
511+
let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 2] };
512+
let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
513+
514+
assert_eq!(
515+
vc,
516+
vec![
517+
(SourceRootId(0), None),
518+
(SourceRootId(1), None),
519+
(SourceRootId(2), Some(SourceRootId(1)))
520+
]
521+
)
522+
}
523+
524+
#[test]
525+
fn test5() {
526+
let mut builder = FileSetConfigBuilder::default();
527+
let root = vec![virp!("/ROOT/abc")];
528+
let root2 = vec![virp!("/ROOT/ghi")];
529+
let root3 = vec![virp!("/ROOT/def/abc")];
530+
builder.add_file_set(root);
531+
builder.add_file_set(root2);
532+
builder.add_file_set(root3);
533+
let fsc = builder.build();
534+
let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 2] };
535+
let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
536+
537+
assert_eq!(
538+
vc,
539+
vec![(SourceRootId(0), None), (SourceRootId(1), None), (SourceRootId(2), None)]
540+
)
541+
}
542+
543+
#[test]
544+
fn test6() {
545+
let mut builder = FileSetConfigBuilder::default();
546+
let root = vec![virp!("/ROOT/abc")];
547+
let root2 = vec![virp!("/ROOT/def")];
548+
let root3 = vec![virp!("/ROOT/def/ghi/jkl")];
549+
builder.add_file_set(root);
550+
builder.add_file_set(root2);
551+
builder.add_file_set(root3);
552+
let fsc = builder.build();
553+
let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 2] };
554+
let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
555+
556+
assert_eq!(
557+
vc,
558+
vec![
559+
(SourceRootId(0), None),
560+
(SourceRootId(1), None),
561+
(SourceRootId(2), Some(SourceRootId(1)))
562+
]
563+
)
564+
}
565+
566+
#[test]
567+
fn test7() {
568+
let mut builder = FileSetConfigBuilder::default();
569+
let root = vec![virp!("/ROOT/abc")];
570+
let root2 = vec![virp!("/ROOT/def")];
571+
let root3 = vec![virp!("/ROOT/def/ghi/jkl")];
572+
let root4 = vec![virp!("/ROOT/def/ghi/klm")];
573+
builder.add_file_set(root);
574+
builder.add_file_set(root2);
575+
builder.add_file_set(root3);
576+
builder.add_file_set(root4);
577+
let fsc = builder.build();
578+
let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 2, 3] };
579+
let mut vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
580+
vc.sort_by(|x, y| x.0 .0.cmp(&y.0 .0));
581+
582+
assert_eq!(
583+
vc,
584+
vec![
585+
(SourceRootId(0), None),
586+
(SourceRootId(1), None),
587+
(SourceRootId(2), Some(SourceRootId(1))),
588+
(SourceRootId(3), Some(SourceRootId(1)))
589+
]
590+
)
591+
}
592+
593+
#[test]
594+
fn test8() {
595+
let mut builder = FileSetConfigBuilder::default();
596+
let root = vec![virp!("/ROOT/abc")];
597+
let root2 = vec![virp!("/ROOT/def")];
598+
let root3 = vec![virp!("/ROOT/def/ghi/jkl")];
599+
let root4 = vec![virp!("/ROOT/def/klm")];
600+
builder.add_file_set(root);
601+
builder.add_file_set(root2);
602+
builder.add_file_set(root3);
603+
builder.add_file_set(root4);
604+
let fsc = builder.build();
605+
let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 3] };
606+
let mut vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
607+
vc.sort_by(|x, y| x.0 .0.cmp(&y.0 .0));
608+
609+
assert_eq!(
610+
vc,
611+
vec![
612+
(SourceRootId(0), None),
613+
(SourceRootId(1), None),
614+
(SourceRootId(3), Some(SourceRootId(1))),
615+
]
616+
)
617+
}
618+
619+
#[test]
620+
fn test9() {
621+
let mut builder = FileSetConfigBuilder::default();
622+
let root = vec![virp!("/ROOT/abc")];
623+
let root2 = vec![virp!("/ROOT/def")];
624+
let root3 = vec![virp!("/ROOT/def/klm")];
625+
let root4 = vec![virp!("/ROOT/def/klm/jkl")];
626+
builder.add_file_set(root);
627+
builder.add_file_set(root2);
628+
builder.add_file_set(root3);
629+
builder.add_file_set(root4);
630+
let fsc = builder.build();
631+
let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 3] };
632+
let mut vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
633+
vc.sort_by(|x, y| x.0 .0.cmp(&y.0 .0));
634+
635+
assert_eq!(
636+
vc,
637+
vec![
638+
(SourceRootId(0), None),
639+
(SourceRootId(1), None),
640+
(SourceRootId(3), Some(SourceRootId(1))),
641+
]
642+
)
643+
}
644+
}
416645
}

crates/rust-analyzer/src/global_state.rs

Lines changed: 4 additions & 1 deletion
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, Option<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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,17 @@ 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+
let mut stream = self.map.stream();
129+
let mut vc = vec![];
130+
while let Some((pth, idx)) = stream.next() {
131+
vc.push((pth.to_vec(), idx));
132+
}
133+
134+
vc
135+
}
136+
126137
/// Returns the set index for the given `path`.
127138
///
128139
/// `scratch_space` is used as a buffer and will be entirely replaced.

0 commit comments

Comments
 (0)