Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/load-cargo/src/lib.rs')
-rw-r--r--crates/load-cargo/src/lib.rs116
1 files changed, 95 insertions, 21 deletions
diff --git a/crates/load-cargo/src/lib.rs b/crates/load-cargo/src/lib.rs
index 76940ab57a..de68b86714 100644
--- a/crates/load-cargo/src/lib.rs
+++ b/crates/load-cargo/src/lib.rs
@@ -15,9 +15,9 @@ use ide_db::{
};
use itertools::Itertools;
use proc_macro_api::{MacroDylib, ProcMacroServer};
-use project_model::{CargoConfig, PackageRoot, ProjectManifest, ProjectWorkspace};
+use project_model::{CargoConfig, ManifestPath, PackageRoot, ProjectManifest, ProjectWorkspace};
use span::Span;
-use tracing::{instrument, Level};
+use tracing::instrument;
use vfs::{file_set::FileSetConfig, loader::Handle, AbsPath, AbsPathBuf, VfsPath};
pub struct LoadCargoConfig {
@@ -238,6 +238,19 @@ impl ProjectFolders {
fsc.add_file_set(file_set_roots)
}
+ // register the workspace manifest as well, note that this currently causes duplicates for
+ // non-virtual cargo workspaces! We ought to fix that
+ for manifest in workspaces.iter().filter_map(|ws| ws.manifest().map(ManifestPath::as_ref)) {
+ let file_set_roots: Vec<VfsPath> = vec![VfsPath::from(manifest.to_owned())];
+
+ let entry = vfs::loader::Entry::Files(vec![manifest.to_owned()]);
+
+ res.watch.push(res.load.len());
+ res.load.push(entry);
+ local_filesets.push(fsc.len() as u64);
+ fsc.add_file_set(file_set_roots)
+ }
+
let fsc = fsc.build();
res.source_root_config = SourceRootConfig { fsc, local_filesets };
@@ -272,24 +285,53 @@ impl SourceRootConfig {
/// 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`.
pub fn source_root_parent_map(&self) -> FxHashMap<SourceRootId, SourceRootId> {
let roots = self.fsc.roots();
- let mut map = FxHashMap::<SourceRootId, SourceRootId>::default();
- roots
- .iter()
- .enumerate()
- .filter(|(_, (_, id))| self.local_filesets.contains(id))
- .filter_map(|(idx, (root, root_id))| {
- // We are interested in parents if they are also local source roots.
- // So instead of a non-local parent we may take a local ancestor as a parent to a node.
- roots.iter().take(idx).find_map(|(root2, root2_id)| {
- if self.local_filesets.contains(root2_id) && root.starts_with(root2) {
- return Some((root_id, root2_id));
+
+ let mut map = FxHashMap::default();
+
+ // See https://github.com/rust-lang/rust-analyzer/issues/17409
+ //
+ // We can view the connections between roots as a graph. The problem is
+ // that this graph may contain cycles, so when adding edges, it is necessary
+ // to check whether it will lead to a cycle.
+ //
+ // Since we ensure that each node has at most one outgoing edge (because
+ // each SourceRoot can have only one parent), we can use a disjoint-set to
+ // maintain the connectivity between nodes. If an edge’s two nodes belong
+ // to the same set, they are already connected.
+ let mut dsu = FxHashMap::default();
+ fn find_parent(dsu: &mut FxHashMap<u64, u64>, id: u64) -> u64 {
+ if let Some(&parent) = dsu.get(&id) {
+ let parent = find_parent(dsu, parent);
+ dsu.insert(id, parent);
+ parent
+ } else {
+ id
+ }
+ }
+
+ for (idx, (root, root_id)) in roots.iter().enumerate() {
+ if !self.local_filesets.contains(root_id)
+ || map.contains_key(&SourceRootId(*root_id as u32))
+ {
+ continue;
+ }
+
+ for (root2, root2_id) in roots[..idx].iter().rev() {
+ if self.local_filesets.contains(root2_id)
+ && root_id != root2_id
+ && root.starts_with(root2)
+ {
+ // check if the edge will create a cycle
+ if find_parent(&mut dsu, *root_id) != find_parent(&mut dsu, *root2_id) {
+ map.insert(SourceRootId(*root_id as u32), SourceRootId(*root2_id as u32));
+ dsu.insert(*root_id, *root2_id);
}
- None
- })
- })
- .for_each(|(child, parent)| {
- map.insert(SourceRootId(*child as u32), SourceRootId(*parent as u32));
- });
+
+ break;
+ }
+ }
+ }
+
map
}
}
@@ -352,8 +394,8 @@ fn load_crate_graph(
}
}
vfs::loader::Message::Loaded { files } | vfs::loader::Message::Changed { files } => {
- let _p = tracing::span!(Level::INFO, "load_cargo::load_crate_craph/LoadedChanged")
- .entered();
+ let _p =
+ tracing::info_span!("load_cargo::load_crate_craph/LoadedChanged").entered();
for (path, contents) in files {
vfs.set_file_contents(path.into(), contents);
}
@@ -560,4 +602,36 @@ mod tests {
assert_eq!(vc, vec![(SourceRootId(3), SourceRootId(1)),])
}
+
+ #[test]
+ fn parents_with_identical_root_id() {
+ let mut builder = FileSetConfigBuilder::default();
+ builder.add_file_set(vec![
+ VfsPath::new_virtual_path("/ROOT/def".to_owned()),
+ VfsPath::new_virtual_path("/ROOT/def/abc/def".to_owned()),
+ ]);
+ builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/abc/def/ghi".to_owned())]);
+ let fsc = builder.build();
+ let src = SourceRootConfig { fsc, local_filesets: vec![0, 1] };
+ let mut vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
+ vc.sort_by(|x, y| x.0 .0.cmp(&y.0 .0));
+
+ assert_eq!(vc, vec![(SourceRootId(1), SourceRootId(0)),])
+ }
+
+ #[test]
+ fn circular_reference() {
+ let mut builder = FileSetConfigBuilder::default();
+ builder.add_file_set(vec![
+ VfsPath::new_virtual_path("/ROOT/def".to_owned()),
+ VfsPath::new_virtual_path("/ROOT/def/abc/def".to_owned()),
+ ]);
+ builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/abc".to_owned())]);
+ let fsc = builder.build();
+ let src = SourceRootConfig { fsc, local_filesets: vec![0, 1] };
+ let mut vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
+ vc.sort_by(|x, y| x.0 .0.cmp(&y.0 .0));
+
+ assert_eq!(vc, vec![(SourceRootId(1), SourceRootId(0)),])
+ }
}