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