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::{
15 db::ExpandDatabase,
16 proc_macro::{
17 ProcMacro, ProcMacroExpander, ProcMacroExpansionError, ProcMacroKind, ProcMacroLoadResult,
18 ProcMacrosBuilder,
19 },
20};
21use ide_db::{
22 ChangeWithProcMacros, FxHashMap, RootDatabase,
23 base_db::{CrateGraphBuilder, Env, ProcMacroLoadingError, SourceRoot, SourceRootId},
24 prime_caches,
25};
26use itertools::Itertools;
27use proc_macro_api::{
28 MacroDylib, ProcMacroClient,
29 bidirectional_protocol::msg::{ParentSpan, SubRequest, SubResponse},
30};
31use project_model::{CargoConfig, PackageRoot, ProjectManifest, ProjectWorkspace};
32use span::{Span, SpanAnchor, SyntaxContext};
33use tt::{TextRange, TextSize};
34use vfs::{
35 AbsPath, AbsPathBuf, FileId, VfsPath,
36 file_set::FileSetConfig,
37 loader::{Handle, LoadingProgress},
38};
39
40#[derive(Debug)]
41pub struct LoadCargoConfig {
42 pub load_out_dirs_from_check: bool,
43 pub with_proc_macro_server: ProcMacroServerChoice,
44 pub prefill_caches: bool,
45 pub num_worker_threads: usize,
46 pub proc_macro_processes: usize,
47}
48
49#[derive(Debug, Clone, PartialEq, Eq)]
50pub enum ProcMacroServerChoice {
51 Sysroot,
52 Explicit(AbsPathBuf),
53 None,
54}
55
56pub fn load_workspace_at(
57 root: &Path,
58 cargo_config: &CargoConfig,
59 load_config: &LoadCargoConfig,
60 progress: &(dyn Fn(String) + Sync),
61) -> anyhow::Result<(RootDatabase, vfs::Vfs, Option<ProcMacroClient>)> {
62 let root = AbsPathBuf::assert_utf8(std::env::current_dir()?.join(root));
63 let root = ProjectManifest::discover_single(&root)?;
64 let manifest_path = root.manifest_path().clone();
65 let mut workspace = ProjectWorkspace::load(root, cargo_config, progress)?;
66
67 if load_config.load_out_dirs_from_check {
68 let build_scripts = workspace.run_build_scripts(cargo_config, progress)?;
69 if let Some(error) = build_scripts.error() {
70 tracing::debug!(
71 "Errors occurred while running build scripts for {}: {}",
72 manifest_path,
73 error
74 );
75 }
76 workspace.set_build_scripts(build_scripts)
77 }
78
79 load_workspace(workspace, &cargo_config.extra_env, load_config)
80}
81
82pub fn load_workspace(
83 ws: ProjectWorkspace,
84 extra_env: &FxHashMap<String, Option<String>>,
85 load_config: &LoadCargoConfig,
86) -> anyhow::Result<(RootDatabase, vfs::Vfs, Option<ProcMacroClient>)> {
87 let lru_cap = std::env::var("RA_LRU_CAP").ok().and_then(|it| it.parse::<u16>().ok());
88 let mut db = RootDatabase::new(lru_cap);
89
90 let (vfs, proc_macro_server) = load_workspace_into_db(ws, extra_env, load_config, &mut db)?;
91
92 Ok((db, vfs, proc_macro_server))
93}
94
95pub fn load_workspace_into_db(
99 ws: ProjectWorkspace,
100 extra_env: &FxHashMap<String, Option<String>>,
101 load_config: &LoadCargoConfig,
102 db: &mut RootDatabase,
103) -> anyhow::Result<(vfs::Vfs, Option<ProcMacroClient>)> {
104 let (sender, receiver) = unbounded();
105 let mut vfs = vfs::Vfs::default();
106 let mut loader = {
107 let loader = vfs_notify::NotifyHandle::spawn(sender);
108 Box::new(loader)
109 };
110
111 tracing::debug!(?load_config, "LoadCargoConfig");
112 let proc_macro_server = match &load_config.with_proc_macro_server {
113 ProcMacroServerChoice::Sysroot => ws.find_sysroot_proc_macro_srv().map(|it| {
114 it.and_then(|it| {
115 ProcMacroClient::spawn(
116 &it,
117 extra_env,
118 ws.toolchain.as_ref(),
119 load_config.proc_macro_processes,
120 )
121 .map_err(Into::into)
122 })
123 .map_err(|e| ProcMacroLoadingError::ProcMacroSrvError(e.to_string().into_boxed_str()))
124 }),
125 ProcMacroServerChoice::Explicit(path) => Some(
126 ProcMacroClient::spawn(
127 path,
128 extra_env,
129 ws.toolchain.as_ref(),
130 load_config.proc_macro_processes,
131 )
132 .map_err(|e| ProcMacroLoadingError::ProcMacroSrvError(e.to_string().into_boxed_str())),
133 ),
134 ProcMacroServerChoice::None => Some(Err(ProcMacroLoadingError::Disabled)),
135 };
136 match &proc_macro_server {
137 Some(Ok(server)) => {
138 tracing::info!(manifest=%ws.manifest_or_root(), path=%server.server_path(), "Proc-macro server started")
139 }
140 Some(Err(e)) => {
141 tracing::info!(manifest=%ws.manifest_or_root(), %e, "Failed to start proc-macro server")
142 }
143 None => {
144 tracing::info!(manifest=%ws.manifest_or_root(), "No proc-macro server started")
145 }
146 }
147
148 let (crate_graph, proc_macros) = ws.to_crate_graph(
149 &mut |path: &AbsPath| {
150 let contents = loader.load_sync(path);
151 let path = vfs::VfsPath::from(path.to_path_buf());
152 vfs.set_file_contents(path.clone(), contents);
153 vfs.file_id(&path).and_then(|(file_id, excluded)| {
154 (excluded == vfs::FileExcluded::No).then_some(file_id)
155 })
156 },
157 extra_env,
158 );
159 let proc_macros = {
160 let proc_macro_server = match &proc_macro_server {
161 Some(Ok(it)) => Ok(it),
162 Some(Err(e)) => {
163 Err(ProcMacroLoadingError::ProcMacroSrvError(e.to_string().into_boxed_str()))
164 }
165 None => Err(ProcMacroLoadingError::ProcMacroSrvError(
166 "proc-macro-srv is not running, workspace is missing a sysroot".into(),
167 )),
168 };
169 proc_macros
170 .into_iter()
171 .map(|(crate_id, path)| {
172 (
173 crate_id,
174 path.map_or_else(Err, |(_, path)| {
175 proc_macro_server.as_ref().map_err(Clone::clone).and_then(
176 |proc_macro_server| load_proc_macro(proc_macro_server, &path, &[]),
177 )
178 }),
179 )
180 })
181 .collect()
182 };
183
184 let project_folders = ProjectFolders::new(std::slice::from_ref(&ws), &[], None);
185 loader.set_config(vfs::loader::Config {
186 load: project_folders.load,
187 watch: vec![],
188 version: 0,
189 });
190
191 load_crate_graph_into_db(
192 crate_graph,
193 proc_macros,
194 project_folders.source_root_config,
195 &mut vfs,
196 &receiver,
197 db,
198 );
199
200 if load_config.prefill_caches {
201 prime_caches::parallel_prime_caches(db, load_config.num_worker_threads, &|_| ());
202 }
203
204 Ok((vfs, proc_macro_server.and_then(Result::ok)))
205}
206
207#[derive(Default)]
208pub struct ProjectFolders {
209 pub load: Vec<vfs::loader::Entry>,
210 pub watch: Vec<usize>,
211 pub source_root_config: SourceRootConfig,
212}
213
214impl ProjectFolders {
215 pub fn new(
216 workspaces: &[ProjectWorkspace],
217 global_excludes: &[AbsPathBuf],
218 user_config_dir_path: Option<&AbsPath>,
219 ) -> ProjectFolders {
220 let mut res = ProjectFolders::default();
221 let mut fsc = FileSetConfig::builder();
222 let mut local_filesets = vec![];
223
224 let mut roots: Vec<_> = workspaces
242 .iter()
243 .flat_map(|ws| ws.to_roots())
244 .update(|root| root.include.sort())
245 .sorted_by(|a, b| a.include.cmp(&b.include))
246 .collect();
247
248 let mut overlap_map = FxHashMap::<_, Vec<_>>::default();
250 let mut done = false;
251
252 while !mem::replace(&mut done, true) {
253 let mut include_to_idx = FxHashMap::default();
255 for (idx, root) in roots.iter().enumerate().filter(|(_, it)| !it.include.is_empty()) {
257 for include in &root.include {
258 match include_to_idx.entry(include) {
259 Entry::Occupied(e) => {
260 overlap_map.entry(*e.get()).or_default().push(idx);
261 }
262 Entry::Vacant(e) => {
263 e.insert(idx);
264 }
265 }
266 }
267 }
268 for (k, v) in overlap_map.drain() {
269 done = false;
270 for v in v {
271 let r = mem::replace(
272 &mut roots[v],
273 PackageRoot { is_local: false, include: vec![], exclude: vec![] },
274 );
275 roots[k].is_local |= r.is_local;
276 roots[k].include.extend(r.include);
277 roots[k].exclude.extend(r.exclude);
278 }
279 roots[k].include.sort();
280 roots[k].exclude.sort();
281 roots[k].include.dedup();
282 roots[k].exclude.dedup();
283 }
284 }
285
286 let uncovered_ws_roots: Vec<AbsPathBuf> = workspaces
290 .iter()
291 .filter_map(|ws| {
292 let ws_root = ws.workspace_root().to_path_buf();
293 let dominated =
294 roots.iter().any(|root| root.is_local && root.include.contains(&ws_root));
295 (!dominated).then_some(ws_root)
296 })
297 .collect();
298
299 for root in roots.into_iter().filter(|it| !it.include.is_empty()) {
300 let file_set_roots: Vec<VfsPath> =
301 root.include.iter().cloned().map(VfsPath::from).collect();
302
303 let entry = {
304 let mut dirs = vfs::loader::Directories::default();
305 dirs.extensions.push("rs".into());
306 dirs.extensions.push("toml".into());
307 dirs.extensions.push("md".into());
308 dirs.include.extend(root.include);
309 dirs.exclude.extend(root.exclude);
310 for excl in global_excludes {
311 if dirs
312 .include
313 .iter()
314 .any(|incl| incl.starts_with(excl) || excl.starts_with(incl))
315 {
316 dirs.exclude.push(excl.clone());
317 }
318 }
319
320 vfs::loader::Entry::Directories(dirs)
321 };
322
323 if root.is_local {
324 res.watch.push(res.load.len());
325 }
326 res.load.push(entry);
327
328 if root.is_local {
329 local_filesets.push(fsc.len() as u64);
330 }
331 fsc.add_file_set(file_set_roots)
332 }
333
334 for ws in workspaces.iter() {
335 let mut file_set_roots: Vec<VfsPath> = vec![];
336 let mut entries = vec![];
337
338 for buildfile in ws.buildfiles() {
339 file_set_roots.push(VfsPath::from(buildfile.to_owned()));
340 entries.push(buildfile.to_owned());
341 }
342
343 if !file_set_roots.is_empty() {
344 let entry = vfs::loader::Entry::Files(entries);
345 res.watch.push(res.load.len());
346 res.load.push(entry);
347 local_filesets.push(fsc.len() as u64);
348 fsc.add_file_set(file_set_roots)
349 }
350 }
351
352 for ws_root in &uncovered_ws_roots {
357 let ratoml_path = ws_root.join("rust-analyzer.toml");
358 let file_set_roots = vec![VfsPath::from(ws_root.clone())];
359 let entry = vfs::loader::Entry::Files(vec![ratoml_path]);
360 res.watch.push(res.load.len());
361 res.load.push(entry);
362 local_filesets.push(fsc.len() as u64);
363 fsc.add_file_set(file_set_roots);
364 }
365
366 if let Some(user_config_path) = user_config_dir_path {
367 let ratoml_path = {
368 let mut p = user_config_path.to_path_buf();
369 p.push("rust-analyzer.toml");
370 p
371 };
372
373 let file_set_roots = vec![VfsPath::from(ratoml_path.to_owned())];
374 let entry = vfs::loader::Entry::Files(vec![ratoml_path]);
375
376 res.watch.push(res.load.len());
377 res.load.push(entry);
378 local_filesets.push(fsc.len() as u64);
379 fsc.add_file_set(file_set_roots)
380 }
381
382 let fsc = fsc.build();
383 res.source_root_config = SourceRootConfig { fsc, local_filesets };
384
385 res
386 }
387}
388
389#[derive(Default, Debug)]
390pub struct SourceRootConfig {
391 pub fsc: FileSetConfig,
392 pub local_filesets: Vec<u64>,
393}
394
395impl SourceRootConfig {
396 pub fn partition(&self, vfs: &vfs::Vfs) -> Vec<SourceRoot> {
397 self.fsc
398 .partition(vfs)
399 .into_iter()
400 .enumerate()
401 .map(|(idx, file_set)| {
402 let is_local = self.local_filesets.contains(&(idx as u64));
403 if is_local {
404 SourceRoot::new_local(file_set)
405 } else {
406 SourceRoot::new_library(file_set)
407 }
408 })
409 .collect()
410 }
411
412 pub fn source_root_parent_map(&self) -> FxHashMap<SourceRootId, SourceRootId> {
415 let roots = self.fsc.roots();
416
417 let mut map = FxHashMap::default();
418
419 let mut dsu = FxHashMap::default();
430 fn find_parent(dsu: &mut FxHashMap<u64, u64>, id: u64) -> u64 {
431 if let Some(&parent) = dsu.get(&id) {
432 let parent = find_parent(dsu, parent);
433 dsu.insert(id, parent);
434 parent
435 } else {
436 id
437 }
438 }
439
440 for (idx, (root, root_id)) in roots.iter().enumerate() {
441 if !self.local_filesets.contains(root_id)
442 || map.contains_key(&SourceRootId(*root_id as u32))
443 {
444 continue;
445 }
446
447 for (root2, root2_id) in roots[..idx].iter().rev() {
448 if self.local_filesets.contains(root2_id)
449 && root_id != root2_id
450 && root.starts_with(root2)
451 {
452 if find_parent(&mut dsu, *root_id) != find_parent(&mut dsu, *root2_id) {
454 map.insert(SourceRootId(*root_id as u32), SourceRootId(*root2_id as u32));
455 dsu.insert(*root_id, *root2_id);
456 }
457
458 break;
459 }
460 }
461 }
462
463 map
464 }
465}
466
467pub fn load_proc_macro(
469 server: &ProcMacroClient,
470 path: &AbsPath,
471 ignored_macros: &[Box<str>],
472) -> ProcMacroLoadResult {
473 let res: Result<Vec<_>, _> = (|| {
474 let dylib = MacroDylib::new(path.to_path_buf());
475 let vec = server.load_dylib(dylib).map_err(|e| {
476 ProcMacroLoadingError::ProcMacroSrvError(format!("{e}").into_boxed_str())
477 })?;
478 if vec.is_empty() {
479 return Err(ProcMacroLoadingError::NoProcMacros);
480 }
481 Ok(vec
482 .into_iter()
483 .map(|expander| expander_to_proc_macro(expander, ignored_macros))
484 .collect())
485 })();
486 match res {
487 Ok(proc_macros) => {
488 tracing::info!(
489 "Loaded proc-macros for {path}: {:?}",
490 proc_macros.iter().map(|it| it.name.clone()).collect::<Vec<_>>()
491 );
492 Ok(proc_macros)
493 }
494 Err(e) => {
495 tracing::warn!("proc-macro loading for {path} failed: {e}");
496 Err(e)
497 }
498 }
499}
500
501fn load_crate_graph_into_db(
502 crate_graph: CrateGraphBuilder,
503 proc_macros: ProcMacrosBuilder,
504 source_root_config: SourceRootConfig,
505 vfs: &mut vfs::Vfs,
506 receiver: &Receiver<vfs::loader::Message>,
507 db: &mut RootDatabase,
508) {
509 let mut analysis_change = ChangeWithProcMacros::default();
510
511 db.enable_proc_attr_macros();
512
513 for task in receiver {
515 match task {
516 vfs::loader::Message::Progress { n_done, .. } => {
517 if n_done == LoadingProgress::Finished {
518 break;
519 }
520 }
521 vfs::loader::Message::Loaded { files } | vfs::loader::Message::Changed { files } => {
522 let _p =
523 tracing::info_span!("load_cargo::load_crate_craph/LoadedChanged").entered();
524 for (path, contents) in files {
525 vfs.set_file_contents(path.into(), contents);
526 }
527 }
528 }
529 }
530 let changes = vfs.take_changes();
531 for (_, file) in changes {
532 if let vfs::Change::Create(v, _) | vfs::Change::Modify(v, _) = file.change
533 && let Ok(text) = String::from_utf8(v)
534 {
535 analysis_change.change_file(file.file_id, Some(text))
536 }
537 }
538 let source_roots = source_root_config.partition(vfs);
539 analysis_change.set_roots(source_roots);
540
541 analysis_change.set_crate_graph(crate_graph);
542 analysis_change.set_proc_macros(proc_macros);
543
544 db.apply_change(analysis_change);
545}
546
547fn expander_to_proc_macro(
548 expander: proc_macro_api::ProcMacro,
549 ignored_macros: &[Box<str>],
550) -> ProcMacro {
551 let name = expander.name();
552 let kind = match expander.kind() {
553 proc_macro_api::ProcMacroKind::CustomDerive => ProcMacroKind::CustomDerive,
554 proc_macro_api::ProcMacroKind::Bang => ProcMacroKind::Bang,
555 proc_macro_api::ProcMacroKind::Attr => ProcMacroKind::Attr,
556 };
557 let disabled = ignored_macros.iter().any(|replace| **replace == *name);
558 ProcMacro {
559 name: intern::Symbol::intern(name),
560 kind,
561 expander: sync::Arc::new(Expander(expander)),
562 disabled,
563 }
564}
565
566#[derive(Debug, PartialEq, Eq)]
567struct Expander(proc_macro_api::ProcMacro);
568
569impl ProcMacroExpander for Expander {
570 fn expand(
571 &self,
572 db: &dyn ExpandDatabase,
573 subtree: &tt::TopSubtree,
574 attrs: Option<&tt::TopSubtree>,
575 env: &Env,
576 def_site: Span,
577 call_site: Span,
578 mixed_site: Span,
579 current_dir: String,
580 ) -> Result<tt::TopSubtree, ProcMacroExpansionError> {
581 let cb = |req| match req {
582 SubRequest::LocalFilePath { file_id } => {
583 let file_id = FileId::from_raw(file_id);
584 let source_root_id = db.file_source_root(file_id).source_root_id(db);
585 let source_root = db.source_root(source_root_id).source_root(db);
586 let name = source_root
587 .path_for_file(&file_id)
588 .and_then(|path| path.as_path())
589 .map(|path| path.to_string());
590
591 Ok(SubResponse::LocalFilePathResult { name })
592 }
593 SubRequest::SourceText { file_id, ast_id, start, end } => {
595 let range = resolve_sub_span(
596 db,
597 file_id,
598 ast_id,
599 TextRange::new(TextSize::from(start), TextSize::from(end)),
600 );
601 let source = db.file_text(range.file_id.file_id(db)).text(db);
602 let text = source
603 .get(usize::from(range.range.start())..usize::from(range.range.end()))
604 .map(ToOwned::to_owned);
605
606 Ok(SubResponse::SourceTextResult { text })
607 }
608 SubRequest::LineColumn { file_id, ast_id, offset } => {
610 let range =
611 resolve_sub_span(db, file_id, ast_id, TextRange::empty(TextSize::from(offset)));
612 let (line, column) = db
613 .line_column(range.file_id.file_id(db), range.range.start())
614 .map(|(line, col)| (line + 1, col + 1))
615 .unwrap_or((1, 1));
616 Ok(SubResponse::LineColumnResult { line, column })
618 }
619 SubRequest::FilePath { file_id } => {
620 let file_id = FileId::from_raw(file_id);
621 let source_root_id = db.file_source_root(file_id).source_root_id(db);
622 let source_root = db.source_root(source_root_id).source_root(db);
623 let name = source_root
624 .path_for_file(&file_id)
625 .and_then(|path| path.as_path())
626 .map(|path| path.to_string())
627 .unwrap_or_default();
628
629 Ok(SubResponse::FilePathResult { name })
630 }
631 SubRequest::ByteRange { file_id, ast_id, start, end } => {
633 let range = resolve_sub_span(
634 db,
635 file_id,
636 ast_id,
637 TextRange::new(TextSize::from(start), TextSize::from(end)),
638 );
639
640 Ok(SubResponse::ByteRangeResult { range: range.range.into() })
641 }
642 SubRequest::SpanSource { file_id, ast_id, start, end, ctx } => {
643 let span = Span {
644 range: TextRange::new(TextSize::from(start), TextSize::from(end)),
645 anchor: SpanAnchor {
646 file_id: span::EditionedFileId::from_raw(file_id),
647 ast_id: span::ErasedFileAstId::from_raw(ast_id),
648 },
649 ctx: unsafe { SyntaxContext::from_u32(ctx) },
652 };
653
654 let mut current_span = span;
655 let mut current_ctx = span.ctx;
656
657 while let Some(macro_call_id) = current_ctx.outer_expn(db) {
658 let macro_call_loc = hir_expand::MacroCallId::from(macro_call_id).loc(db);
659
660 let call_site_file = macro_call_loc.kind.file_id();
661
662 let resolved = db.resolve_span(current_span);
663
664 current_ctx = macro_call_loc.ctxt;
665 current_span = Span {
666 range: resolved.range,
667 anchor: SpanAnchor {
668 file_id: resolved.file_id.span_file_id(db),
669 ast_id: span::ROOT_ERASED_FILE_AST_ID,
670 },
671 ctx: current_ctx,
672 };
673
674 if call_site_file.file_id().is_some() {
675 break;
676 }
677 }
678
679 let resolved = db.resolve_span(current_span);
680
681 Ok(SubResponse::SpanSourceResult {
682 file_id: resolved.file_id.span_file_id(db).as_u32(),
683 ast_id: span::ROOT_ERASED_FILE_AST_ID.into_raw(),
684 start: u32::from(resolved.range.start()),
685 end: u32::from(resolved.range.end()),
686 ctx: current_span.ctx.into_u32(),
687 })
688 }
689 SubRequest::SpanParent { file_id, ast_id, start, end, ctx } => {
690 let span = Span {
691 range: TextRange::new(TextSize::from(start), TextSize::from(end)),
692 anchor: SpanAnchor {
693 file_id: span::EditionedFileId::from_raw(file_id),
694 ast_id: span::ErasedFileAstId::from_raw(ast_id),
695 },
696 ctx: unsafe { SyntaxContext::from_u32(ctx) },
699 };
700
701 if let Some(macro_call_id) = span.ctx.outer_expn(db) {
702 let macro_call_loc = hir_expand::MacroCallId::from(macro_call_id).loc(db);
703
704 let call_site_file = macro_call_loc.kind.file_id();
705 let call_site_ast_id = macro_call_loc.kind.erased_ast_id();
706
707 if let Some(editioned_file_id) = call_site_file.file_id() {
708 let range = db
709 .ast_id_map(editioned_file_id.into())
710 .get_erased(call_site_ast_id)
711 .text_range();
712
713 let parent_span = Some(ParentSpan {
714 file_id: editioned_file_id.span_file_id(db).as_u32(),
715 ast_id: span::ROOT_ERASED_FILE_AST_ID.into_raw(),
716 start: u32::from(range.start()),
717 end: u32::from(range.end()),
718 ctx: macro_call_loc.ctxt.into_u32(),
719 });
720
721 return Ok(SubResponse::SpanParentResult { parent_span });
722 }
723 }
724
725 Ok(SubResponse::SpanParentResult { parent_span: None })
726 }
727 };
728 match self.0.expand(
729 subtree.view(),
730 attrs.map(|attrs| attrs.view()),
731 env.clone().into(),
732 def_site,
733 call_site,
734 mixed_site,
735 current_dir,
736 Some(&cb),
737 ) {
738 Ok(Ok(subtree)) => Ok(subtree),
739 Ok(Err(err)) => Err(ProcMacroExpansionError::Panic(err)),
740 Err(err) => Err(ProcMacroExpansionError::System(err.to_string())),
741 }
742 }
743
744 fn eq_dyn(&self, other: &dyn ProcMacroExpander) -> bool {
745 (other as &dyn Any).downcast_ref::<Self>() == Some(self)
746 }
747}
748
749fn resolve_sub_span(
750 db: &dyn ExpandDatabase,
751 file_id: u32,
752 ast_id: u32,
753 range: TextRange,
754) -> hir_expand::FileRange {
755 let ast_id = span::ErasedFileAstId::from_raw(ast_id);
756 let editioned_file_id = span::EditionedFileId::from_raw(file_id);
757 let span = Span {
758 range,
759 anchor: SpanAnchor { file_id: editioned_file_id, ast_id },
760 ctx: SyntaxContext::root(editioned_file_id.edition()),
761 };
762 db.resolve_span(span)
763}
764
765#[cfg(test)]
766mod tests {
767 use ide_db::base_db::all_crates;
768 use vfs::file_set::FileSetConfigBuilder;
769
770 use super::*;
771
772 #[test]
773 fn test_loading_rust_analyzer() {
774 let cargo_toml_path = Path::new(env!("CARGO_MANIFEST_DIR"))
775 .parent()
776 .unwrap()
777 .parent()
778 .unwrap()
779 .join("Cargo.toml");
780 let cargo_toml_path = AbsPathBuf::assert_utf8(cargo_toml_path);
781 let manifest = ProjectManifest::from_manifest_file(cargo_toml_path).unwrap();
782
783 let cargo_config = CargoConfig { set_test: true, ..CargoConfig::default() };
784 let load_cargo_config = LoadCargoConfig {
785 load_out_dirs_from_check: false,
786 with_proc_macro_server: ProcMacroServerChoice::None,
787 prefill_caches: false,
788 num_worker_threads: 1,
789 proc_macro_processes: 1,
790 };
791 let workspace = ProjectWorkspace::load(manifest, &cargo_config, &|_| {}).unwrap();
792 let (db, _vfs, _proc_macro) =
793 load_workspace(workspace, &cargo_config.extra_env, &load_cargo_config).unwrap();
794
795 let n_crates = all_crates(&db).len();
796 assert!(n_crates > 20);
798 }
799
800 #[test]
801 fn unrelated_sources() {
802 let mut builder = FileSetConfigBuilder::default();
803 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
804 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def".to_owned())]);
805 let fsc = builder.build();
806 let src = SourceRootConfig { fsc, local_filesets: vec![0, 1] };
807 let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
808
809 assert_eq!(vc, vec![])
810 }
811
812 #[test]
813 fn unrelated_source_sharing_dirname() {
814 let mut builder = FileSetConfigBuilder::default();
815 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
816 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/abc".to_owned())]);
817 let fsc = builder.build();
818 let src = SourceRootConfig { fsc, local_filesets: vec![0, 1] };
819 let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
820
821 assert_eq!(vc, vec![])
822 }
823
824 #[test]
825 fn basic_child_parent() {
826 let mut builder = FileSetConfigBuilder::default();
827 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
828 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc/def".to_owned())]);
829 let fsc = builder.build();
830 let src = SourceRootConfig { fsc, local_filesets: vec![0, 1] };
831 let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
832
833 assert_eq!(vc, vec![(SourceRootId(1), SourceRootId(0))])
834 }
835
836 #[test]
837 fn basic_child_parent_with_unrelated_parents_sib() {
838 let mut builder = FileSetConfigBuilder::default();
839 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
840 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def".to_owned())]);
841 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/abc".to_owned())]);
842 let fsc = builder.build();
843 let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 2] };
844 let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
845
846 assert_eq!(vc, vec![(SourceRootId(2), SourceRootId(1))])
847 }
848
849 #[test]
850 fn deep_sources_with_parent_missing() {
851 let mut builder = FileSetConfigBuilder::default();
852 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
853 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/ghi".to_owned())]);
854 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/abc".to_owned())]);
855 let fsc = builder.build();
856 let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 2] };
857 let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
858
859 assert_eq!(vc, vec![])
860 }
861
862 #[test]
863 fn ancestor_can_be_parent() {
864 let mut builder = FileSetConfigBuilder::default();
865 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
866 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def".to_owned())]);
867 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/ghi/jkl".to_owned())]);
868 let fsc = builder.build();
869 let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 2] };
870 let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
871
872 assert_eq!(vc, vec![(SourceRootId(2), SourceRootId(1))])
873 }
874
875 #[test]
876 fn ancestor_can_be_parent_2() {
877 let mut builder = FileSetConfigBuilder::default();
878 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
879 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def".to_owned())]);
880 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/ghi/jkl".to_owned())]);
881 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/ghi/klm".to_owned())]);
882 let fsc = builder.build();
883 let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 2, 3] };
884 let mut vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
885 vc.sort_by_key(|x| x.0.0);
886
887 assert_eq!(vc, vec![(SourceRootId(2), SourceRootId(1)), (SourceRootId(3), SourceRootId(1))])
888 }
889
890 #[test]
891 fn non_locals_are_skipped() {
892 let mut builder = FileSetConfigBuilder::default();
893 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
894 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def".to_owned())]);
895 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/ghi/jkl".to_owned())]);
896 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/klm".to_owned())]);
897 let fsc = builder.build();
898 let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 3] };
899 let mut vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
900 vc.sort_by_key(|x| x.0.0);
901
902 assert_eq!(vc, vec![(SourceRootId(3), SourceRootId(1)),])
903 }
904
905 #[test]
906 fn child_binds_ancestor_if_parent_nonlocal() {
907 let mut builder = FileSetConfigBuilder::default();
908 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
909 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def".to_owned())]);
910 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/klm".to_owned())]);
911 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/klm/jkl".to_owned())]);
912 let fsc = builder.build();
913 let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 3] };
914 let mut vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
915 vc.sort_by_key(|x| x.0.0);
916
917 assert_eq!(vc, vec![(SourceRootId(3), SourceRootId(1)),])
918 }
919
920 #[test]
921 fn parents_with_identical_root_id() {
922 let mut builder = FileSetConfigBuilder::default();
923 builder.add_file_set(vec![
924 VfsPath::new_virtual_path("/ROOT/def".to_owned()),
925 VfsPath::new_virtual_path("/ROOT/def/abc/def".to_owned()),
926 ]);
927 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/abc/def/ghi".to_owned())]);
928 let fsc = builder.build();
929 let src = SourceRootConfig { fsc, local_filesets: vec![0, 1] };
930 let mut vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
931 vc.sort_by_key(|x| x.0.0);
932
933 assert_eq!(vc, vec![(SourceRootId(1), SourceRootId(0)),])
934 }
935
936 #[test]
937 fn circular_reference() {
938 let mut builder = FileSetConfigBuilder::default();
939 builder.add_file_set(vec![
940 VfsPath::new_virtual_path("/ROOT/def".to_owned()),
941 VfsPath::new_virtual_path("/ROOT/def/abc/def".to_owned()),
942 ]);
943 builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/abc".to_owned())]);
944 let fsc = builder.build();
945 let src = SourceRootConfig { fsc, local_filesets: vec![0, 1] };
946 let mut vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
947 vc.sort_by_key(|x| x.0.0);
948
949 assert_eq!(vc, vec![(SourceRootId(1), SourceRootId(0)),])
950 }
951}