1use std::{any::Any, collections::hash_map::Entry, mem, path::Path, sync};
6
7use crossbeam_channel::{Receiver, unbounded};
8use hir_expand::proc_macro::{
9 ProcMacro, ProcMacroExpander, ProcMacroExpansionError, ProcMacroKind, ProcMacroLoadResult,
10 ProcMacrosBuilder,
11};
12use ide_db::{
13 ChangeWithProcMacros, FxHashMap, RootDatabase,
14 base_db::{CrateGraphBuilder, Env, ProcMacroLoadingError, SourceRoot, SourceRootId},
15 prime_caches,
16};
17use itertools::Itertools;
18use proc_macro_api::{MacroDylib, ProcMacroClient};
19use project_model::{CargoConfig, PackageRoot, ProjectManifest, ProjectWorkspace};
20use span::Span;
21use vfs::{
22 AbsPath, AbsPathBuf, VfsPath,
23 file_set::FileSetConfig,
24 loader::{Handle, LoadingProgress},
25};
26
27#[derive(Debug)]
28pub struct LoadCargoConfig {
29 pub load_out_dirs_from_check: bool,
30 pub with_proc_macro_server: ProcMacroServerChoice,
31 pub prefill_caches: bool,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq)]
35pub enum ProcMacroServerChoice {
36 Sysroot,
37 Explicit(AbsPathBuf),
38 None,
39}
40
41pub fn load_workspace_at(
42 root: &Path,
43 cargo_config: &CargoConfig,
44 load_config: &LoadCargoConfig,
45 progress: &(dyn Fn(String) + Sync),
46) -> anyhow::Result<(RootDatabase, vfs::Vfs, Option<ProcMacroClient>)> {
47 let root = AbsPathBuf::assert_utf8(std::env::current_dir()?.join(root));
48 let root = ProjectManifest::discover_single(&root)?;
49 let manifest_path = root.manifest_path().clone();
50 let mut workspace = ProjectWorkspace::load(root, cargo_config, progress)?;
51
52 if load_config.load_out_dirs_from_check {
53 let build_scripts = workspace.run_build_scripts(cargo_config, progress)?;
54 if let Some(error) = build_scripts.error() {
55 tracing::debug!(
56 "Errors occurred while running build scripts for {}: {}",
57 manifest_path,
58 error
59 );
60 }
61 workspace.set_build_scripts(build_scripts)
62 }
63
64 load_workspace(workspace, &cargo_config.extra_env, load_config)
65}
66
67pub fn load_workspace(
68 ws: ProjectWorkspace,
69 extra_env: &FxHashMap<String, Option<String>>,
70 load_config: &LoadCargoConfig,
71) -> anyhow::Result<(RootDatabase, vfs::Vfs, Option<ProcMacroClient>)> {
72 let lru_cap = std::env::var("RA_LRU_CAP").ok().and_then(|it| it.parse::<u16>().ok());
73 let mut db = RootDatabase::new(lru_cap);
74
75 let (vfs, proc_macro_server) = load_workspace_into_db(ws, extra_env, load_config, &mut db)?;
76
77 Ok((db, vfs, proc_macro_server))
78}
79
80pub fn load_workspace_into_db(
84 ws: ProjectWorkspace,
85 extra_env: &FxHashMap<String, Option<String>>,
86 load_config: &LoadCargoConfig,
87 db: &mut RootDatabase,
88) -> anyhow::Result<(vfs::Vfs, Option<ProcMacroClient>)> {
89 let (sender, receiver) = unbounded();
90 let mut vfs = vfs::Vfs::default();
91 let mut loader = {
92 let loader = vfs_notify::NotifyHandle::spawn(sender);
93 Box::new(loader)
94 };
95
96 tracing::debug!(?load_config, "LoadCargoConfig");
97 let proc_macro_server = match &load_config.with_proc_macro_server {
98 ProcMacroServerChoice::Sysroot => ws.find_sysroot_proc_macro_srv().map(|it| {
99 it.and_then(|it| {
100 ProcMacroClient::spawn(&it, extra_env, ws.toolchain.as_ref()).map_err(Into::into)
101 })
102 .map_err(|e| ProcMacroLoadingError::ProcMacroSrvError(e.to_string().into_boxed_str()))
103 }),
104 ProcMacroServerChoice::Explicit(path) => {
105 Some(ProcMacroClient::spawn(path, extra_env, ws.toolchain.as_ref()).map_err(|e| {
106 ProcMacroLoadingError::ProcMacroSrvError(e.to_string().into_boxed_str())
107 }))
108 }
109 ProcMacroServerChoice::None => Some(Err(ProcMacroLoadingError::Disabled)),
110 };
111 match &proc_macro_server {
112 Some(Ok(server)) => {
113 tracing::info!(manifest=%ws.manifest_or_root(), path=%server.server_path(), "Proc-macro server started")
114 }
115 Some(Err(e)) => {
116 tracing::info!(manifest=%ws.manifest_or_root(), %e, "Failed to start proc-macro server")
117 }
118 None => {
119 tracing::info!(manifest=%ws.manifest_or_root(), "No proc-macro server started")
120 }
121 }
122
123 let (crate_graph, proc_macros) = ws.to_crate_graph(
124 &mut |path: &AbsPath| {
125 let contents = loader.load_sync(path);
126 let path = vfs::VfsPath::from(path.to_path_buf());
127 vfs.set_file_contents(path.clone(), contents);
128 vfs.file_id(&path).and_then(|(file_id, excluded)| {
129 (excluded == vfs::FileExcluded::No).then_some(file_id)
130 })
131 },
132 extra_env,
133 );
134 let proc_macros = {
135 let proc_macro_server = match &proc_macro_server {
136 Some(Ok(it)) => Ok(it),
137 Some(Err(e)) => {
138 Err(ProcMacroLoadingError::ProcMacroSrvError(e.to_string().into_boxed_str()))
139 }
140 None => Err(ProcMacroLoadingError::ProcMacroSrvError(
141 "proc-macro-srv is not running, workspace is missing a sysroot".into(),
142 )),
143 };
144 proc_macros
145 .into_iter()
146 .map(|(crate_id, path)| {
147 (
148 crate_id,
149 path.map_or_else(Err, |(_, path)| {
150 proc_macro_server.as_ref().map_err(Clone::clone).and_then(
151 |proc_macro_server| load_proc_macro(proc_macro_server, &path, &[]),
152 )
153 }),
154 )
155 })
156 .collect()
157 };
158
159 let project_folders = ProjectFolders::new(std::slice::from_ref(&ws), &[], None);
160 loader.set_config(vfs::loader::Config {
161 load: project_folders.load,
162 watch: vec![],
163 version: 0,
164 });
165
166 load_crate_graph_into_db(
167 crate_graph,
168 proc_macros,
169 project_folders.source_root_config,
170 &mut vfs,
171 &receiver,
172 db,
173 );
174
175 if load_config.prefill_caches {
176 prime_caches::parallel_prime_caches(db, 1, &|_| ());
177 }
178
179 Ok((vfs, proc_macro_server.and_then(Result::ok)))
180}
181
182#[derive(Default)]
183pub struct ProjectFolders {
184 pub load: Vec<vfs::loader::Entry>,
185 pub watch: Vec<usize>,
186 pub source_root_config: SourceRootConfig,
187}
188
189impl ProjectFolders {
190 pub fn new(
191 workspaces: &[ProjectWorkspace],
192 global_excludes: &[AbsPathBuf],
193 user_config_dir_path: Option<&AbsPath>,
194 ) -> ProjectFolders {
195 let mut res = ProjectFolders::default();
196 let mut fsc = FileSetConfig::builder();
197 let mut local_filesets = vec![];
198
199 let mut roots: Vec<_> = workspaces
217 .iter()
218 .flat_map(|ws| ws.to_roots())
219 .update(|root| root.include.sort())
220 .sorted_by(|a, b| a.include.cmp(&b.include))
221 .collect();
222
223 let mut overlap_map = FxHashMap::<_, Vec<_>>::default();
225 let mut done = false;
226
227 while !mem::replace(&mut done, true) {
228 let mut include_to_idx = FxHashMap::default();
230 for (idx, root) in roots.iter().enumerate().filter(|(_, it)| !it.include.is_empty()) {
232 for include in &root.include {
233 match include_to_idx.entry(include) {
234 Entry::Occupied(e) => {
235 overlap_map.entry(*e.get()).or_default().push(idx);
236 }
237 Entry::Vacant(e) => {
238 e.insert(idx);
239 }
240 }
241 }
242 }
243 for (k, v) in overlap_map.drain() {
244 done = false;
245 for v in v {
246 let r = mem::replace(
247 &mut roots[v],
248 PackageRoot { is_local: false, include: vec![], exclude: vec![] },
249 );
250 roots[k].is_local |= r.is_local;
251 roots[k].include.extend(r.include);
252 roots[k].exclude.extend(r.exclude);
253 }
254 roots[k].include.sort();
255 roots[k].exclude.sort();
256 roots[k].include.dedup();
257 roots[k].exclude.dedup();
258 }
259 }
260
261 for root in roots.into_iter().filter(|it| !it.include.is_empty()) {
262 let file_set_roots: Vec<VfsPath> =
263 root.include.iter().cloned().map(VfsPath::from).collect();
264
265 let entry = {
266 let mut dirs = vfs::loader::Directories::default();
267 dirs.extensions.push("rs".into());
268 dirs.extensions.push("toml".into());
269 dirs.include.extend(root.include);
270 dirs.exclude.extend(root.exclude);
271 for excl in global_excludes {
272 if dirs
273 .include
274 .iter()
275 .any(|incl| incl.starts_with(excl) || excl.starts_with(incl))
276 {
277 dirs.exclude.push(excl.clone());
278 }
279 }
280
281 vfs::loader::Entry::Directories(dirs)
282 };
283
284 if root.is_local {
285 res.watch.push(res.load.len());
286 }
287 res.load.push(entry);
288
289 if root.is_local {
290 local_filesets.push(fsc.len() as u64);
291 }
292 fsc.add_file_set(file_set_roots)
293 }
294
295 for ws in workspaces.iter() {
296 let mut file_set_roots: Vec<VfsPath> = vec![];
297 let mut entries = vec![];
298
299 for buildfile in ws.buildfiles() {
300 file_set_roots.push(VfsPath::from(buildfile.to_owned()));
301 entries.push(buildfile.to_owned());
302 }
303
304 if !file_set_roots.is_empty() {
305 let entry = vfs::loader::Entry::Files(entries);
306 res.watch.push(res.load.len());
307 res.load.push(entry);
308 local_filesets.push(fsc.len() as u64);
309 fsc.add_file_set(file_set_roots)
310 }
311 }
312
313 if let Some(user_config_path) = user_config_dir_path {
314 let ratoml_path = {
315 let mut p = user_config_path.to_path_buf();
316 p.push("rust-analyzer.toml");
317 p
318 };
319
320 let file_set_roots = vec![VfsPath::from(ratoml_path.to_owned())];
321 let entry = vfs::loader::Entry::Files(vec![ratoml_path]);
322
323 res.watch.push(res.load.len());
324 res.load.push(entry);
325 local_filesets.push(fsc.len() as u64);
326 fsc.add_file_set(file_set_roots)
327 }
328
329 let fsc = fsc.build();
330 res.source_root_config = SourceRootConfig { fsc, local_filesets };
331
332 res
333 }
334}
335
336#[derive(Default, Debug)]
337pub struct SourceRootConfig {
338 pub fsc: FileSetConfig,
339 pub local_filesets: Vec<u64>,
340}
341
342impl SourceRootConfig {
343 pub fn partition(&self, vfs: &vfs::Vfs) -> Vec<SourceRoot> {
344 self.fsc
345 .partition(vfs)
346 .into_iter()
347 .enumerate()
348 .map(|(idx, file_set)| {
349 let is_local = self.local_filesets.contains(&(idx as u64));
350 if is_local {
351 SourceRoot::new_local(file_set)
352 } else {
353 SourceRoot::new_library(file_set)
354 }
355 })
356 .collect()
357 }
358
359 pub fn source_root_parent_map(&self) -> FxHashMap<SourceRootId, SourceRootId> {
362 let roots = self.fsc.roots();
363
364 let mut map = FxHashMap::default();
365
366 let mut dsu = FxHashMap::default();
377 fn find_parent(dsu: &mut FxHashMap<u64, u64>, id: u64) -> u64 {
378 if let Some(&parent) = dsu.get(&id) {
379 let parent = find_parent(dsu, parent);
380 dsu.insert(id, parent);
381 parent
382 } else {
383 id
384 }
385 }
386
387 for (idx, (root, root_id)) in roots.iter().enumerate() {
388 if !self.local_filesets.contains(root_id)
389 || map.contains_key(&SourceRootId(*root_id as u32))
390 {
391 continue;
392 }
393
394 for (root2, root2_id) in roots[..idx].iter().rev() {
395 if self.local_filesets.contains(root2_id)
396 && root_id != root2_id
397 && root.starts_with(root2)
398 {
399 if find_parent(&mut dsu, *root_id) != find_parent(&mut dsu, *root2_id) {
401 map.insert(SourceRootId(*root_id as u32), SourceRootId(*root2_id as u32));
402 dsu.insert(*root_id, *root2_id);
403 }
404
405 break;
406 }
407 }
408 }
409
410 map
411 }
412}
413
414pub fn load_proc_macro(
416 server: &ProcMacroClient,
417 path: &AbsPath,
418 ignored_macros: &[Box<str>],
419) -> ProcMacroLoadResult {
420 let res: Result<Vec<_>, _> = (|| {
421 let dylib = MacroDylib::new(path.to_path_buf());
422 let vec = server.load_dylib(dylib).map_err(|e| {
423 ProcMacroLoadingError::ProcMacroSrvError(format!("{e}").into_boxed_str())
424 })?;
425 if vec.is_empty() {
426 return Err(ProcMacroLoadingError::NoProcMacros);
427 }
428 Ok(vec
429 .into_iter()
430 .map(|expander| expander_to_proc_macro(expander, ignored_macros))
431 .collect())
432 })();
433 match res {
434 Ok(proc_macros) => {
435 tracing::info!(
436 "Loaded proc-macros for {path}: {:?}",
437 proc_macros.iter().map(|it| it.name.clone()).collect::<Vec<_>>()
438 );
439 Ok(proc_macros)
440 }
441 Err(e) => {
442 tracing::warn!("proc-macro loading for {path} failed: {e}");
443 Err(e)
444 }
445 }
446}
447
448fn load_crate_graph_into_db(
449 crate_graph: CrateGraphBuilder,
450 proc_macros: ProcMacrosBuilder,
451 source_root_config: SourceRootConfig,
452 vfs: &mut vfs::Vfs,
453 receiver: &Receiver<vfs::loader::Message>,
454 db: &mut RootDatabase,
455) {
456 let mut analysis_change = ChangeWithProcMacros::default();
457
458 db.enable_proc_attr_macros();
459
460 for task in receiver {
462 match task {
463 vfs::loader::Message::Progress { n_done, .. } => {
464 if n_done == LoadingProgress::Finished {
465 break;
466 }
467 }
468 vfs::loader::Message::Loaded { files } | vfs::loader::Message::Changed { files } => {
469 let _p =
470 tracing::info_span!("load_cargo::load_crate_craph/LoadedChanged").entered();
471 for (path, contents) in files {
472 vfs.set_file_contents(path.into(), contents);
473 }
474 }
475 }
476 }
477 let changes = vfs.take_changes();
478 for (_, file) in changes {
479 if let vfs::Change::Create(v, _) | vfs::Change::Modify(v, _) = file.change
480 && let Ok(text) = String::from_utf8(v)
481 {
482 analysis_change.change_file(file.file_id, Some(text))
483 }
484 }
485 let source_roots = source_root_config.partition(vfs);
486 analysis_change.set_roots(source_roots);
487
488 analysis_change.set_crate_graph(crate_graph);
489 analysis_change.set_proc_macros(proc_macros);
490
491 db.apply_change(analysis_change);
492}
493
494fn expander_to_proc_macro(
495 expander: proc_macro_api::ProcMacro,
496 ignored_macros: &[Box<str>],
497) -> ProcMacro {
498 let name = expander.name();
499 let kind = match expander.kind() {
500 proc_macro_api::ProcMacroKind::CustomDerive => ProcMacroKind::CustomDerive,
501 proc_macro_api::ProcMacroKind::Bang => ProcMacroKind::Bang,
502 proc_macro_api::ProcMacroKind::Attr => ProcMacroKind::Attr,
503 };
504 let disabled = ignored_macros.iter().any(|replace| **replace == *name);
505 ProcMacro {
506 name: intern::Symbol::intern(name),
507 kind,
508 expander: sync::Arc::new(Expander(expander)),
509 disabled,
510 }
511}
512
513#[derive(Debug, PartialEq, Eq)]
514struct Expander(proc_macro_api::ProcMacro);
515
516impl ProcMacroExpander for Expander {
517 fn expand(
518 &self,
519 subtree: &tt::TopSubtree<Span>,
520 attrs: Option<&tt::TopSubtree<Span>>,
521 env: &Env,
522 def_site: Span,
523 call_site: Span,
524 mixed_site: Span,
525 current_dir: String,
526 ) -> Result<tt::TopSubtree<Span>, ProcMacroExpansionError> {
527 match self.0.expand(
528 subtree.view(),
529 attrs.map(|attrs| attrs.view()),
530 env.clone().into(),
531 def_site,
532 call_site,
533 mixed_site,
534 current_dir,
535 ) {
536 Ok(Ok(subtree)) => Ok(subtree),
537 Ok(Err(err)) => Err(ProcMacroExpansionError::Panic(err)),
538 Err(err) => Err(ProcMacroExpansionError::System(err.to_string())),
539 }
540 }
541
542 fn eq_dyn(&self, other: &dyn ProcMacroExpander) -> bool {
543 (other as &dyn Any).downcast_ref::<Self>() == Some(self)
544 }
545}
546
547#[cfg(test)]
548mod tests {
549 use ide_db::base_db::RootQueryDb;
550 use vfs::file_set::FileSetConfigBuilder;
551
552 use super::*;
553
554 #[test]
555 fn test_loading_rust_analyzer() {
556 let path = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent().unwrap();
557 let cargo_config = CargoConfig { set_test: true, ..CargoConfig::default() };
558 let load_cargo_config = LoadCargoConfig {
559 load_out_dirs_from_check: false,
560 with_proc_macro_server: ProcMacroServerChoice::None,
561 prefill_caches: false,
562 };
563 let (db, _vfs, _proc_macro) =
564 load_workspace_at(path, &cargo_config, &load_cargo_config, &|_| {}).unwrap();
565
566 let n_crates = db.all_crates().len();
567 assert!(n_crates > 20);
569 }
570
571 #[test]
572 fn unrelated_sources() {
573 let mut builder = FileSetConfigBuilder::default();
574 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
575 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def".to_owned())]);
576 let fsc = builder.build();
577 let src = SourceRootConfig { fsc, local_filesets: vec![0, 1] };
578 let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
579
580 assert_eq!(vc, vec![])
581 }
582
583 #[test]
584 fn unrelated_source_sharing_dirname() {
585 let mut builder = FileSetConfigBuilder::default();
586 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
587 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/abc".to_owned())]);
588 let fsc = builder.build();
589 let src = SourceRootConfig { fsc, local_filesets: vec![0, 1] };
590 let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
591
592 assert_eq!(vc, vec![])
593 }
594
595 #[test]
596 fn basic_child_parent() {
597 let mut builder = FileSetConfigBuilder::default();
598 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
599 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc/def".to_owned())]);
600 let fsc = builder.build();
601 let src = SourceRootConfig { fsc, local_filesets: vec![0, 1] };
602 let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
603
604 assert_eq!(vc, vec![(SourceRootId(1), SourceRootId(0))])
605 }
606
607 #[test]
608 fn basic_child_parent_with_unrelated_parents_sib() {
609 let mut builder = FileSetConfigBuilder::default();
610 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
611 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def".to_owned())]);
612 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/abc".to_owned())]);
613 let fsc = builder.build();
614 let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 2] };
615 let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
616
617 assert_eq!(vc, vec![(SourceRootId(2), SourceRootId(1))])
618 }
619
620 #[test]
621 fn deep_sources_with_parent_missing() {
622 let mut builder = FileSetConfigBuilder::default();
623 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
624 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/ghi".to_owned())]);
625 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/abc".to_owned())]);
626 let fsc = builder.build();
627 let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 2] };
628 let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
629
630 assert_eq!(vc, vec![])
631 }
632
633 #[test]
634 fn ancestor_can_be_parent() {
635 let mut builder = FileSetConfigBuilder::default();
636 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
637 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def".to_owned())]);
638 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/ghi/jkl".to_owned())]);
639 let fsc = builder.build();
640 let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 2] };
641 let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
642
643 assert_eq!(vc, vec![(SourceRootId(2), SourceRootId(1))])
644 }
645
646 #[test]
647 fn ancestor_can_be_parent_2() {
648 let mut builder = FileSetConfigBuilder::default();
649 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
650 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def".to_owned())]);
651 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/ghi/jkl".to_owned())]);
652 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/ghi/klm".to_owned())]);
653 let fsc = builder.build();
654 let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 2, 3] };
655 let mut vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
656 vc.sort_by(|x, y| x.0.0.cmp(&y.0.0));
657
658 assert_eq!(vc, vec![(SourceRootId(2), SourceRootId(1)), (SourceRootId(3), SourceRootId(1))])
659 }
660
661 #[test]
662 fn non_locals_are_skipped() {
663 let mut builder = FileSetConfigBuilder::default();
664 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
665 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def".to_owned())]);
666 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/ghi/jkl".to_owned())]);
667 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/klm".to_owned())]);
668 let fsc = builder.build();
669 let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 3] };
670 let mut vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
671 vc.sort_by(|x, y| x.0.0.cmp(&y.0.0));
672
673 assert_eq!(vc, vec![(SourceRootId(3), SourceRootId(1)),])
674 }
675
676 #[test]
677 fn child_binds_ancestor_if_parent_nonlocal() {
678 let mut builder = FileSetConfigBuilder::default();
679 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
680 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def".to_owned())]);
681 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/klm".to_owned())]);
682 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/klm/jkl".to_owned())]);
683 let fsc = builder.build();
684 let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 3] };
685 let mut vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
686 vc.sort_by(|x, y| x.0.0.cmp(&y.0.0));
687
688 assert_eq!(vc, vec![(SourceRootId(3), SourceRootId(1)),])
689 }
690
691 #[test]
692 fn parents_with_identical_root_id() {
693 let mut builder = FileSetConfigBuilder::default();
694 builder.add_file_set(vec![
695 VfsPath::new_virtual_path("/ROOT/def".to_owned()),
696 VfsPath::new_virtual_path("/ROOT/def/abc/def".to_owned()),
697 ]);
698 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/abc/def/ghi".to_owned())]);
699 let fsc = builder.build();
700 let src = SourceRootConfig { fsc, local_filesets: vec![0, 1] };
701 let mut vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
702 vc.sort_by(|x, y| x.0.0.cmp(&y.0.0));
703
704 assert_eq!(vc, vec![(SourceRootId(1), SourceRootId(0)),])
705 }
706
707 #[test]
708 fn circular_reference() {
709 let mut builder = FileSetConfigBuilder::default();
710 builder.add_file_set(vec![
711 VfsPath::new_virtual_path("/ROOT/def".to_owned()),
712 VfsPath::new_virtual_path("/ROOT/def/abc/def".to_owned()),
713 ]);
714 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/abc".to_owned())]);
715 let fsc = builder.build();
716 let src = SourceRootConfig { fsc, local_filesets: vec![0, 1] };
717 let mut vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
718 vc.sort_by(|x, y| x.0.0.cmp(&y.0.0));
719
720 assert_eq!(vc, vec![(SourceRootId(1), SourceRootId(0)),])
721 }
722}