Skip to main content

load_cargo/
lib.rs

1//! Loads a Cargo project into a static instance of analysis, without support
2//! for incorporating changes.
3// Note, don't remove any public api from this. This API is consumed by external tools
4// to run rust-analyzer as a library.
5
6#![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
95// This variant of `load_workspace` allows deferring the loading of rust-analyzer
96// into an existing database, which is useful in certain third-party scenarios,
97// now that `salsa` supports extending foreign databases (e.g. `RootDatabase`).
98pub 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        // Dedup source roots
225        // Depending on the project setup, we can have duplicated source roots, or for example in
226        // the case of the rustc workspace, we can end up with two source roots that are almost the
227        // same but not quite, like:
228        // PackageRoot { is_local: false, include: [AbsPathBuf(".../rust/src/tools/miri/cargo-miri")], exclude: [] }
229        // PackageRoot {
230        //     is_local: true,
231        //     include: [AbsPathBuf(".../rust/src/tools/miri/cargo-miri"), AbsPathBuf(".../rust/build/x86_64-pc-windows-msvc/stage0-tools/x86_64-pc-windows-msvc/release/build/cargo-miri-85801cd3d2d1dae4/out")],
232        //     exclude: [AbsPathBuf(".../rust/src/tools/miri/cargo-miri/.git"), AbsPathBuf(".../rust/src/tools/miri/cargo-miri/target")]
233        // }
234        //
235        // The first one comes from the explicit rustc workspace which points to the rustc workspace itself
236        // The second comes from the rustc workspace that we load as the actual project workspace
237        // These `is_local` differing in this kind of way gives us problems, especially when trying to filter diagnostics as we don't report diagnostics for external libraries.
238        // So we need to deduplicate these, usually it would be enough to deduplicate by `include`, but as the rustc example shows here that doesn't work,
239        // so we need to also coalesce the includes if they overlap.
240
241        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        // map that tracks indices of overlapping roots
249        let mut overlap_map = FxHashMap::<_, Vec<_>>::default();
250        let mut done = false;
251
252        while !mem::replace(&mut done, true) {
253            // maps include paths to indices of the corresponding root
254            let mut include_to_idx = FxHashMap::default();
255            // Find and note down the indices of overlapping roots
256            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        // Collect workspace roots not already covered by a local PackageRoot
287        // (e.g. virtual workspaces where no package lives at the workspace root).
288        // We need these to load workspace-root rust-analyzer.toml into a local source root.
289        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 virtual workspaces, the workspace root has no local PackageRoot, so
353        // rust-analyzer.toml there would fall into a library source root and be
354        // ignored. Load it explicitly via Entry::Files and register the workspace
355        // root as a local file-set root so the file is classified as local.
356        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    /// Maps local source roots to their parent source roots by bytewise comparing of root paths .
413    /// 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`.
414    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        // See https://github.com/rust-lang/rust-analyzer/issues/17409
420        //
421        // We can view the connections between roots as a graph. The problem is
422        // that this graph may contain cycles, so when adding edges, it is necessary
423        // to check whether it will lead to a cycle.
424        //
425        // Since we ensure that each node has at most one outgoing edge (because
426        // each SourceRoot can have only one parent), we can use a disjoint-set to
427        // maintain the connectivity between nodes. If an edge’s two nodes belong
428        // to the same set, they are already connected.
429        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                    // check if the edge will create a cycle
453                    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
467/// Load the proc-macros for the given lib path, disabling all expanders whose names are in `ignored_macros`.
468pub 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    // wait until Vfs has loaded all roots
514    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            // Not incremental: requires full file text.
594            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            // Not incremental: requires building line index.
609            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                // proc_macro::Span line/column are 1-based
617                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            // Not incremental: requires global span resolution.
632            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                    // SAFETY: We only receive spans from the server. If someone mess up the communication UB can happen,
650                    // but that will be their problem.
651                    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                    // SAFETY: We only receive spans from the server. If someone mess up the communication UB can happen,
697                    // but that will be their problem.
698                    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        // RA has quite a few crates, but the exact count doesn't matter
797        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}