about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--crates/base-db/src/fixture.rs28
-rw-r--r--crates/base-db/src/input.rs189
-rw-r--r--crates/base-db/src/lib.rs1
-rw-r--r--crates/cfg/src/lib.rs7
-rw-r--r--crates/project-model/src/project_json.rs3
-rw-r--r--crates/project-model/src/tests.rs52
-rw-r--r--crates/project-model/src/workspace.rs67
-rw-r--r--crates/project-model/test_data/deduplication_crate_graph_A.json140
-rw-r--r--crates/project-model/test_data/deduplication_crate_graph_B.json66
-rw-r--r--crates/project-model/test_data/output/cargo_hello_world_project_model.txt7
-rw-r--r--crates/project-model/test_data/output/cargo_hello_world_project_model_with_selective_overrides.txt7
-rw-r--r--crates/project-model/test_data/output/cargo_hello_world_project_model_with_wildcard_overrides.txt7
-rw-r--r--crates/project-model/test_data/output/rust_project_hello_world_project_model.txt16
13 files changed, 542 insertions, 48 deletions
diff --git a/crates/base-db/src/fixture.rs b/crates/base-db/src/fixture.rs
index 3f5ccb621c7..3da555a47ac 100644
--- a/crates/base-db/src/fixture.rs
+++ b/crates/base-db/src/fixture.rs
@@ -13,9 +13,9 @@ use vfs::{file_set::FileSet, VfsPath};
 
 use crate::{
     input::{CrateName, CrateOrigin, LangCrateOrigin},
-    Change, CrateDisplayName, CrateGraph, CrateId, Dependency, Edition, Env, FileId, FilePosition,
-    FileRange, ProcMacro, ProcMacroExpander, ProcMacroExpansionError, ProcMacros, ReleaseChannel,
-    SourceDatabaseExt, SourceRoot, SourceRootId,
+    Change, CrateDisplayName, CrateGraph, CrateId, Dependency, DependencyKind, Edition, Env,
+    FileId, FilePosition, FileRange, ProcMacro, ProcMacroExpander, ProcMacroExpansionError,
+    ProcMacros, ReleaseChannel, SourceDatabaseExt, SourceRoot, SourceRootId,
 };
 
 pub const WORKSPACE: SourceRootId = SourceRootId(0);
@@ -237,7 +237,12 @@ impl ChangeFixture {
                 crate_graph
                     .add_dep(
                         from_id,
-                        Dependency::with_prelude(CrateName::new(&to).unwrap(), to_id, prelude),
+                        Dependency::with_prelude(
+                            CrateName::new(&to).unwrap(),
+                            to_id,
+                            prelude,
+                            DependencyKind::Normal,
+                        ),
                     )
                     .unwrap();
             }
@@ -275,7 +280,14 @@ impl ChangeFixture {
 
             for krate in all_crates {
                 crate_graph
-                    .add_dep(krate, Dependency::new(CrateName::new("core").unwrap(), core_crate))
+                    .add_dep(
+                        krate,
+                        Dependency::new(
+                            CrateName::new("core").unwrap(),
+                            core_crate,
+                            DependencyKind::Normal,
+                        ),
+                    )
                     .unwrap();
             }
         }
@@ -317,7 +329,11 @@ impl ChangeFixture {
                 crate_graph
                     .add_dep(
                         krate,
-                        Dependency::new(CrateName::new("proc_macros").unwrap(), proc_macros_crate),
+                        Dependency::new(
+                            CrateName::new("proc_macros").unwrap(),
+                            proc_macros_crate,
+                            DependencyKind::Normal,
+                        ),
                     )
                     .unwrap();
             }
diff --git a/crates/base-db/src/input.rs b/crates/base-db/src/input.rs
index 65db5c0fc7d..e4f78321e21 100644
--- a/crates/base-db/src/input.rs
+++ b/crates/base-db/src/input.rs
@@ -155,6 +155,10 @@ impl CrateOrigin {
     pub fn is_local(&self) -> bool {
         matches!(self, CrateOrigin::Local { .. })
     }
+
+    pub fn is_lib(&self) -> bool {
+        matches!(self, CrateOrigin::Library { .. })
+    }
 }
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -324,6 +328,62 @@ pub struct CrateData {
     pub channel: Option<ReleaseChannel>,
 }
 
+impl CrateData {
+    /// Check if [`other`] is almost equal to [`self`] ignoring `CrateOrigin` value.
+    pub fn eq_ignoring_origin_and_deps(&self, other: &CrateData, ignore_dev_deps: bool) -> bool {
+        // This method has some obscure bits. These are mostly there to be compliant with
+        // some patches. References to the patches are given.
+        if self.root_file_id != other.root_file_id {
+            return false;
+        }
+
+        if self.display_name != other.display_name {
+            return false;
+        }
+
+        if self.is_proc_macro != other.is_proc_macro {
+            return false;
+        }
+
+        if self.edition != other.edition {
+            return false;
+        }
+
+        if self.version != other.version {
+            return false;
+        }
+
+        let mut opts = self.cfg_options.difference(&other.cfg_options);
+        if let Some(it) = opts.next() {
+            // Don't care if rust_analyzer CfgAtom is the only cfg in the difference set of self's and other's cfgs.
+            // https://github.com/rust-lang/rust-analyzer/blob/0840038f02daec6ba3238f05d8caa037d28701a0/crates/project-model/src/workspace.rs#L894
+            if it.to_string() != "rust_analyzer" {
+                return false;
+            }
+
+            if let Some(_) = opts.next() {
+                return false;
+            }
+        }
+
+        if self.env != other.env {
+            return false;
+        }
+
+        let slf_deps = self.dependencies.iter();
+        let other_deps = other.dependencies.iter();
+
+        if ignore_dev_deps {
+            return slf_deps
+                .clone()
+                .filter(|it| it.kind != DependencyKind::Dev)
+                .eq(other_deps.clone().filter(|it| it.kind != DependencyKind::Dev));
+        }
+
+        slf_deps.eq(other_deps)
+    }
+}
+
 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
 pub enum Edition {
     Edition2015,
@@ -351,26 +411,43 @@ impl Env {
     }
 }
 
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+pub enum DependencyKind {
+    Normal,
+    Dev,
+    Build,
+}
+
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct Dependency {
     pub crate_id: CrateId,
     pub name: CrateName,
+    kind: DependencyKind,
     prelude: bool,
 }
 
 impl Dependency {
-    pub fn new(name: CrateName, crate_id: CrateId) -> Self {
-        Self { name, crate_id, prelude: true }
+    pub fn new(name: CrateName, crate_id: CrateId, kind: DependencyKind) -> Self {
+        Self { name, crate_id, prelude: true, kind }
     }
 
-    pub fn with_prelude(name: CrateName, crate_id: CrateId, prelude: bool) -> Self {
-        Self { name, crate_id, prelude }
+    pub fn with_prelude(
+        name: CrateName,
+        crate_id: CrateId,
+        prelude: bool,
+        kind: DependencyKind,
+    ) -> Self {
+        Self { name, crate_id, prelude, kind }
     }
 
     /// Whether this dependency is to be added to the depending crate's extern prelude.
     pub fn is_prelude(&self) -> bool {
         self.prelude
     }
+
+    pub fn kind(&self) -> DependencyKind {
+        self.kind
+    }
 }
 
 impl CrateGraph {
@@ -574,23 +651,46 @@ impl CrateGraph {
     pub fn extend(&mut self, mut other: CrateGraph, proc_macros: &mut ProcMacroPaths) {
         let topo = other.crates_in_topological_order();
         let mut id_map: FxHashMap<CrateId, CrateId> = FxHashMap::default();
-
         for topo in topo {
             let crate_data = &mut other.arena[topo];
+
             crate_data.dependencies.iter_mut().for_each(|dep| dep.crate_id = id_map[&dep.crate_id]);
             crate_data.dependencies.sort_by_key(|dep| dep.crate_id);
-
-            let res = self.arena.iter().find_map(
-                |(id, data)| {
-                    if data == crate_data {
-                        Some(id)
-                    } else {
-                        None
+            let res = self.arena.iter().find_map(|(id, data)| {
+                match (&data.origin, &crate_data.origin) {
+                    (a, b) if a == b => {
+                        if data.eq_ignoring_origin_and_deps(&crate_data, false) {
+                            return Some((id, false));
+                        }
+                    }
+                    (a @ CrateOrigin::Local { .. }, CrateOrigin::Library { .. })
+                    | (a @ CrateOrigin::Library { .. }, CrateOrigin::Local { .. }) => {
+                        // If the origins differ, check if the two crates are equal without
+                        // considering the dev dependencies, if they are, they most likely are in
+                        // different loaded workspaces which may cause issues. We keep the local
+                        // version and discard the library one as the local version may have
+                        // dev-dependencies that we want to keep resolving. See #15656 for more
+                        // information.
+                        if data.eq_ignoring_origin_and_deps(&crate_data, true) {
+                            return Some((id, if a.is_local() { false } else { true }));
+                        }
                     }
-                },
-            );
-            if let Some(res) = res {
+                    (_, _) => return None,
+                }
+
+                None
+            });
+
+            if let Some((res, should_update_lib_to_local)) = res {
                 id_map.insert(topo, res);
+                if should_update_lib_to_local {
+                    assert!(self.arena[res].origin.is_lib());
+                    assert!(crate_data.origin.is_local());
+                    self.arena[res].origin = crate_data.origin.clone();
+
+                    // Move local's dev dependencies into the newly-local-formerly-lib crate.
+                    self.arena[res].dependencies = crate_data.dependencies.clone();
+                }
             } else {
                 let id = self.arena.alloc(crate_data.clone());
                 id_map.insert(topo, id);
@@ -636,9 +736,11 @@ impl CrateGraph {
         match (cfg_if, std) {
             (Some(cfg_if), Some(std)) => {
                 self.arena[cfg_if].dependencies.clear();
-                self.arena[std]
-                    .dependencies
-                    .push(Dependency::new(CrateName::new("cfg_if").unwrap(), cfg_if));
+                self.arena[std].dependencies.push(Dependency::new(
+                    CrateName::new("cfg_if").unwrap(),
+                    cfg_if,
+                    DependencyKind::Normal,
+                ));
                 true
             }
             _ => false,
@@ -658,6 +760,8 @@ impl ops::Index<CrateId> for CrateGraph {
 }
 
 impl CrateData {
+    /// Add a dependency to `self` without checking if the dependency
+    // is existent among `self.dependencies`.
     fn add_dep(&mut self, dep: Dependency) {
         self.dependencies.push(dep)
     }
@@ -759,7 +863,7 @@ impl fmt::Display for CyclicDependenciesError {
 
 #[cfg(test)]
 mod tests {
-    use crate::CrateOrigin;
+    use crate::{CrateOrigin, DependencyKind};
 
     use super::{CrateGraph, CrateName, Dependency, Edition::Edition2018, Env, FileId};
 
@@ -806,13 +910,22 @@ mod tests {
             None,
         );
         assert!(graph
-            .add_dep(crate1, Dependency::new(CrateName::new("crate2").unwrap(), crate2))
+            .add_dep(
+                crate1,
+                Dependency::new(CrateName::new("crate2").unwrap(), crate2, DependencyKind::Normal)
+            )
             .is_ok());
         assert!(graph
-            .add_dep(crate2, Dependency::new(CrateName::new("crate3").unwrap(), crate3))
+            .add_dep(
+                crate2,
+                Dependency::new(CrateName::new("crate3").unwrap(), crate3, DependencyKind::Normal)
+            )
             .is_ok());
         assert!(graph
-            .add_dep(crate3, Dependency::new(CrateName::new("crate1").unwrap(), crate1))
+            .add_dep(
+                crate3,
+                Dependency::new(CrateName::new("crate1").unwrap(), crate1, DependencyKind::Normal)
+            )
             .is_err());
     }
 
@@ -846,10 +959,16 @@ mod tests {
             None,
         );
         assert!(graph
-            .add_dep(crate1, Dependency::new(CrateName::new("crate2").unwrap(), crate2))
+            .add_dep(
+                crate1,
+                Dependency::new(CrateName::new("crate2").unwrap(), crate2, DependencyKind::Normal)
+            )
             .is_ok());
         assert!(graph
-            .add_dep(crate2, Dependency::new(CrateName::new("crate2").unwrap(), crate2))
+            .add_dep(
+                crate2,
+                Dependency::new(CrateName::new("crate2").unwrap(), crate2, DependencyKind::Normal)
+            )
             .is_err());
     }
 
@@ -896,10 +1015,16 @@ mod tests {
             None,
         );
         assert!(graph
-            .add_dep(crate1, Dependency::new(CrateName::new("crate2").unwrap(), crate2))
+            .add_dep(
+                crate1,
+                Dependency::new(CrateName::new("crate2").unwrap(), crate2, DependencyKind::Normal)
+            )
             .is_ok());
         assert!(graph
-            .add_dep(crate2, Dependency::new(CrateName::new("crate3").unwrap(), crate3))
+            .add_dep(
+                crate2,
+                Dependency::new(CrateName::new("crate3").unwrap(), crate3, DependencyKind::Normal)
+            )
             .is_ok());
     }
 
@@ -935,12 +1060,20 @@ mod tests {
         assert!(graph
             .add_dep(
                 crate1,
-                Dependency::new(CrateName::normalize_dashes("crate-name-with-dashes"), crate2)
+                Dependency::new(
+                    CrateName::normalize_dashes("crate-name-with-dashes"),
+                    crate2,
+                    DependencyKind::Normal
+                )
             )
             .is_ok());
         assert_eq!(
             graph[crate1].dependencies,
-            vec![Dependency::new(CrateName::new("crate_name_with_dashes").unwrap(), crate2)]
+            vec![Dependency::new(
+                CrateName::new("crate_name_with_dashes").unwrap(),
+                crate2,
+                DependencyKind::Normal
+            )]
         );
     }
 }
diff --git a/crates/base-db/src/lib.rs b/crates/base-db/src/lib.rs
index c5c4afa30f7..40cfab88afd 100644
--- a/crates/base-db/src/lib.rs
+++ b/crates/base-db/src/lib.rs
@@ -12,6 +12,7 @@ use rustc_hash::FxHashSet;
 use syntax::{ast, Parse, SourceFile, TextRange, TextSize};
 use triomphe::Arc;
 
+pub use crate::input::DependencyKind;
 pub use crate::{
     change::Change,
     input::{
diff --git a/crates/cfg/src/lib.rs b/crates/cfg/src/lib.rs
index 0aeb0b05052..8bbe5e2a8c2 100644
--- a/crates/cfg/src/lib.rs
+++ b/crates/cfg/src/lib.rs
@@ -58,6 +58,13 @@ impl CfgOptions {
         self.enabled.insert(CfgAtom::KeyValue { key, value });
     }
 
+    pub fn difference<'a>(
+        &'a self,
+        other: &'a CfgOptions,
+    ) -> impl Iterator<Item = &'a CfgAtom> + 'a {
+        self.enabled.difference(&other.enabled)
+    }
+
     pub fn apply_diff(&mut self, diff: CfgDiff) {
         for atom in diff.enable {
             self.enabled.insert(atom);
diff --git a/crates/project-model/src/project_json.rs b/crates/project-model/src/project_json.rs
index 80897f7478c..931eba11576 100644
--- a/crates/project-model/src/project_json.rs
+++ b/crates/project-model/src/project_json.rs
@@ -49,7 +49,7 @@
 //! user explores them belongs to that extension (it's totally valid to change
 //! rust-project.json over time via configuration request!)
 
-use base_db::{CrateDisplayName, CrateId, CrateName, Dependency, Edition};
+use base_db::{CrateDisplayName, CrateId, CrateName, Dependency, DependencyKind, Edition};
 use la_arena::RawIdx;
 use paths::{AbsPath, AbsPathBuf};
 use rustc_hash::FxHashMap;
@@ -135,6 +135,7 @@ impl ProjectJson {
                                 Dependency::new(
                                     dep_data.name,
                                     CrateId::from_raw(RawIdx::from(dep_data.krate as u32)),
+                                    DependencyKind::Normal,
                                 )
                             })
                             .collect::<Vec<_>>(),
diff --git a/crates/project-model/src/tests.rs b/crates/project-model/src/tests.rs
index 7815b9dda77..98f3063bb98 100644
--- a/crates/project-model/src/tests.rs
+++ b/crates/project-model/src/tests.rs
@@ -249,3 +249,55 @@ fn crate_graph_dedup() {
     crate_graph.extend(regex_crate_graph, &mut regex_proc_macros);
     assert_eq!(crate_graph.iter().count(), 118);
 }
+
+#[test]
+fn test_deduplicate_origin_dev() {
+    let path_map = &mut Default::default();
+    let (mut crate_graph, _proc_macros) =
+        load_cargo_with_sysroot(path_map, "deduplication_crate_graph_A.json");
+    crate_graph.sort_deps();
+    let (crate_graph_1, mut _proc_macros_2) =
+        load_cargo_with_sysroot(path_map, "deduplication_crate_graph_B.json");
+
+    crate_graph.extend(crate_graph_1, &mut _proc_macros_2);
+
+    let mut crates_named_p2 = vec![];
+    for id in crate_graph.iter() {
+        let krate = &crate_graph[id];
+        if let Some(name) = krate.display_name.as_ref() {
+            if name.to_string() == "p2" {
+                crates_named_p2.push(krate);
+            }
+        }
+    }
+
+    assert!(crates_named_p2.len() == 1);
+    let p2 = crates_named_p2[0];
+    assert!(p2.origin.is_local());
+}
+
+#[test]
+fn test_deduplicate_origin_dev_rev() {
+    let path_map = &mut Default::default();
+    let (mut crate_graph, _proc_macros) =
+        load_cargo_with_sysroot(path_map, "deduplication_crate_graph_B.json");
+    crate_graph.sort_deps();
+    let (crate_graph_1, mut _proc_macros_2) =
+        load_cargo_with_sysroot(path_map, "deduplication_crate_graph_A.json");
+
+    crate_graph.extend(crate_graph_1, &mut _proc_macros_2);
+
+    let mut crates_named_p2 = vec![];
+    for id in crate_graph.iter() {
+        let krate = &crate_graph[id];
+        if let Some(name) = krate.display_name.as_ref() {
+            if name.to_string() == "p2" {
+                crates_named_p2.push(krate);
+            }
+        }
+    }
+
+    assert!(crates_named_p2.len() == 1);
+    let p2 = crates_named_p2[0];
+    assert!(p2.origin.is_local());
+}
diff --git a/crates/project-model/src/workspace.rs b/crates/project-model/src/workspace.rs
index e0209ca15a5..9333570354a 100644
--- a/crates/project-model/src/workspace.rs
+++ b/crates/project-model/src/workspace.rs
@@ -6,8 +6,8 @@ use std::{collections::VecDeque, fmt, fs, iter, process::Command, str::FromStr,
 
 use anyhow::{format_err, Context};
 use base_db::{
-    CrateDisplayName, CrateGraph, CrateId, CrateName, CrateOrigin, Dependency, Edition, Env,
-    FileId, LangCrateOrigin, ProcMacroPaths, ReleaseChannel, TargetLayoutLoadResult,
+    CrateDisplayName, CrateGraph, CrateId, CrateName, CrateOrigin, Dependency, DependencyKind,
+    Edition, Env, FileId, LangCrateOrigin, ProcMacroPaths, ReleaseChannel, TargetLayoutLoadResult,
 };
 use cfg::{CfgDiff, CfgOptions};
 use paths::{AbsPath, AbsPathBuf};
@@ -834,7 +834,7 @@ fn project_json_to_crate_graph(
 
             for dep in &krate.deps {
                 if let Some(&to) = crates.get(&dep.crate_id) {
-                    add_dep(crate_graph, from, dep.name.clone(), to)
+                    add_dep(crate_graph, from, dep.name.clone(), to, dep.kind().to_owned())
                 }
             }
         }
@@ -979,7 +979,7 @@ fn cargo_to_crate_graph(
                     // cargo metadata does not do any normalization,
                     // so we do it ourselves currently
                     let name = CrateName::normalize_dashes(&name);
-                    add_dep(crate_graph, from, name, to);
+                    add_dep(crate_graph, from, name, to, DependencyKind::Normal);
                 }
             }
         }
@@ -999,7 +999,17 @@ fn cargo_to_crate_graph(
                     continue;
                 }
 
-                add_dep(crate_graph, from, name.clone(), to)
+                add_dep(
+                    crate_graph,
+                    from,
+                    name.clone(),
+                    to,
+                    match dep.kind {
+                        DepKind::Normal => DependencyKind::Normal,
+                        DepKind::Dev => DependencyKind::Dev,
+                        DepKind::Build => DependencyKind::Build,
+                    },
+                )
             }
         }
     }
@@ -1187,7 +1197,17 @@ fn handle_rustc_crates(
             let name = CrateName::new(&dep.name).unwrap();
             if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) {
                 for &from in rustc_pkg_crates.get(&pkg).into_iter().flatten() {
-                    add_dep(crate_graph, from, name.clone(), to);
+                    add_dep(
+                        crate_graph,
+                        from,
+                        name.clone(),
+                        to,
+                        match dep.kind {
+                            DepKind::Normal => DependencyKind::Normal,
+                            DepKind::Dev => DependencyKind::Dev,
+                            DepKind::Build => DependencyKind::Build,
+                        },
+                    );
                 }
             }
         }
@@ -1209,7 +1229,7 @@ fn handle_rustc_crates(
                     // `rust_analyzer` thinks that it should use the one from the `rustc_source`
                     // instead of the one from `crates.io`
                     if !crate_graph[*from].dependencies.iter().any(|d| d.name == name) {
-                        add_dep(crate_graph, *from, name.clone(), to);
+                        add_dep(crate_graph, *from, name.clone(), to, DependencyKind::Normal);
                     }
                 }
             }
@@ -1308,7 +1328,14 @@ impl SysrootPublicDeps {
     /// Makes `from` depend on the public sysroot crates.
     fn add_to_crate_graph(&self, crate_graph: &mut CrateGraph, from: CrateId) {
         for (name, krate, prelude) in &self.deps {
-            add_dep_with_prelude(crate_graph, from, name.clone(), *krate, *prelude);
+            add_dep_with_prelude(
+                crate_graph,
+                from,
+                name.clone(),
+                *krate,
+                *prelude,
+                DependencyKind::Normal,
+            );
         }
     }
 }
@@ -1363,7 +1390,7 @@ fn sysroot_to_crate_graph(
         for &to in sysroot[from].deps.iter() {
             let name = CrateName::new(&sysroot[to].name).unwrap();
             if let (Some(&from), Some(&to)) = (sysroot_crates.get(&from), sysroot_crates.get(&to)) {
-                add_dep(crate_graph, from, name, to);
+                add_dep(crate_graph, from, name, to, DependencyKind::Normal);
             }
         }
     }
@@ -1442,8 +1469,14 @@ fn handle_hack_cargo_workspace(
         .collect()
 }
 
-fn add_dep(graph: &mut CrateGraph, from: CrateId, name: CrateName, to: CrateId) {
-    add_dep_inner(graph, from, Dependency::new(name, to))
+fn add_dep(
+    graph: &mut CrateGraph,
+    from: CrateId,
+    name: CrateName,
+    to: CrateId,
+    kind: DependencyKind,
+) {
+    add_dep_inner(graph, from, Dependency::new(name, to, kind))
 }
 
 fn add_dep_with_prelude(
@@ -1452,12 +1485,20 @@ fn add_dep_with_prelude(
     name: CrateName,
     to: CrateId,
     prelude: bool,
+    kind: DependencyKind,
 ) {
-    add_dep_inner(graph, from, Dependency::with_prelude(name, to, prelude))
+    add_dep_inner(graph, from, Dependency::with_prelude(name, to, prelude, kind))
 }
 
 fn add_proc_macro_dep(crate_graph: &mut CrateGraph, from: CrateId, to: CrateId, prelude: bool) {
-    add_dep_with_prelude(crate_graph, from, CrateName::new("proc_macro").unwrap(), to, prelude);
+    add_dep_with_prelude(
+        crate_graph,
+        from,
+        CrateName::new("proc_macro").unwrap(),
+        to,
+        prelude,
+        DependencyKind::Normal,
+    );
 }
 
 fn add_dep_inner(graph: &mut CrateGraph, from: CrateId, dep: Dependency) {
diff --git a/crates/project-model/test_data/deduplication_crate_graph_A.json b/crates/project-model/test_data/deduplication_crate_graph_A.json
new file mode 100644
index 00000000000..b0fb5845cef
--- /dev/null
+++ b/crates/project-model/test_data/deduplication_crate_graph_A.json
@@ -0,0 +1,140 @@
+{
+  "packages": [
+    {
+      "name": "p1",
+      "version": "0.1.0",
+      "id": "p1 0.1.0 (path+file:///example_project/p1)",
+      "license": null,
+      "license_file": null,
+      "description": null,
+      "source": null,
+      "dependencies": [
+        {
+          "name": "p2",
+          "source": null,
+          "req": "*",
+          "kind": null,
+          "rename": null,
+          "optional": false,
+          "uses_default_features": true,
+          "features": [],
+          "target": null,
+          "registry": null,
+          "path": "$ROOT$example_project/p2"
+        }
+      ],
+      "targets": [
+        {
+          "kind": [
+            "lib"
+          ],
+          "crate_types": [
+            "lib"
+          ],
+          "name": "p1",
+          "src_path": "$ROOT$example_project/p1/src/lib.rs",
+          "edition": "2021",
+          "doc": true,
+          "doctest": true,
+          "test": true
+        }
+      ],
+      "features": {},
+      "manifest_path": "$ROOT$example_project/p1/Cargo.toml",
+      "metadata": null,
+      "publish": null,
+      "authors": [],
+      "categories": [],
+      "keywords": [],
+      "readme": null,
+      "repository": null,
+      "homepage": null,
+      "documentation": null,
+      "edition": "2021",
+      "links": null,
+      "default_run": null,
+      "rust_version": null
+    },
+    {
+      "name": "p2",
+      "version": "0.1.0",
+      "id": "p2 0.1.0 (path+file:///example_project/p2)",
+      "license": null,
+      "license_file": null,
+      "description": null,
+      "source": null,
+      "dependencies": [],
+      "targets": [
+        {
+          "kind": [
+            "lib"
+          ],
+          "crate_types": [
+            "lib"
+          ],
+          "name": "p2",
+          "src_path": "$ROOT$example_project/p2/src/lib.rs",
+          "edition": "2021",
+          "doc": true,
+          "doctest": true,
+          "test": true
+        }
+      ],
+      "features": {},
+      "manifest_path": "$ROOT$example_project/p2/Cargo.toml",
+      "metadata": null,
+      "publish": null,
+      "authors": [],
+      "categories": [],
+      "keywords": [],
+      "readme": null,
+      "repository": null,
+      "homepage": null,
+      "documentation": null,
+      "edition": "2021",
+      "links": null,
+      "default_run": null,
+      "rust_version": null
+    }
+  ],
+  "workspace_members": [
+    "p1 0.1.0 (path+file:///example_project/p1)"
+  ],
+  "workspace_default_members": [
+    "p1 0.1.0 (path+file:///example_project/p1)"
+  ],
+  "resolve": {
+    "nodes": [
+      {
+        "id": "p1 0.1.0 (path+file:///example_project/p1)",
+        "dependencies": [
+          "p2 0.1.0 (path+file:///example_project/p2)"
+        ],
+        "deps": [
+          {
+            "name": "p2",
+            "pkg": "p2 0.1.0 (path+file:///example_project/p2)",
+            "dep_kinds": [
+              {
+                "kind": null,
+                "target": null
+              }
+            ]
+          }
+        ],
+        "features": []
+      },
+      {
+        "id": "p2 0.1.0 (path+file:///example_project/p2)",
+        "dependencies": [],
+        "deps": [],
+        "features": []
+      }
+    ],
+    "root": "p1 0.1.0 (path+file:///example_project/p1)"
+  },
+  "target_directory": "$ROOT$example_project/p1/target",
+  "version": 1,
+  "workspace_root": "$ROOT$example_project/p1",
+  "metadata": null
+}
\ No newline at end of file
diff --git a/crates/project-model/test_data/deduplication_crate_graph_B.json b/crates/project-model/test_data/deduplication_crate_graph_B.json
new file mode 100644
index 00000000000..b5d1e16e62e
--- /dev/null
+++ b/crates/project-model/test_data/deduplication_crate_graph_B.json
@@ -0,0 +1,66 @@
+{
+  "packages": [
+    {
+      "name": "p2",
+      "version": "0.1.0",
+      "id": "p2 0.1.0 (path+file:///example_project/p2)",
+      "license": null,
+      "license_file": null,
+      "description": null,
+      "source": null,
+      "dependencies": [],
+      "targets": [
+        {
+          "kind": [
+            "lib"
+          ],
+          "crate_types": [
+            "lib"
+          ],
+          "name": "p2",
+          "src_path": "$ROOT$example_project/p2/src/lib.rs",
+          "edition": "2021",
+          "doc": true,
+          "doctest": true,
+          "test": true
+        }
+      ],
+      "features": {},
+      "manifest_path": "$ROOT$example_project/p2/Cargo.toml",
+      "metadata": null,
+      "publish": null,
+      "authors": [],
+      "categories": [],
+      "keywords": [],
+      "readme": null,
+      "repository": null,
+      "homepage": null,
+      "documentation": null,
+      "edition": "2021",
+      "links": null,
+      "default_run": null,
+      "rust_version": null
+    }
+  ],
+  "workspace_members": [
+    "p2 0.1.0 (path+file:///example_project/p2)"
+  ],
+  "workspace_default_members": [
+    "p2 0.1.0 (path+file:///example_project/p2)"
+  ],
+  "resolve": {
+    "nodes": [
+      {
+        "id": "p2 0.1.0 (path+file:///example_project/p2)",
+        "dependencies": [],
+        "deps": [],
+        "features": []
+      }
+    ],
+    "root": "p2 0.1.0 (path+file:///example_project/p2)"
+  },
+  "target_directory": "$ROOT$example_project/p2/target",
+  "version": 1,
+  "workspace_root": "$ROOT$example_project/p2",
+  "metadata": null
+}
\ No newline at end of file
diff --git a/crates/project-model/test_data/output/cargo_hello_world_project_model.txt b/crates/project-model/test_data/output/cargo_hello_world_project_model.txt
index 727d39a3077..e98f016ca7d 100644
--- a/crates/project-model/test_data/output/cargo_hello_world_project_model.txt
+++ b/crates/project-model/test_data/output/cargo_hello_world_project_model.txt
@@ -48,6 +48,7 @@
                 name: CrateName(
                     "libc",
                 ),
+                kind: Normal,
                 prelude: true,
             },
         ],
@@ -112,6 +113,7 @@
                 name: CrateName(
                     "hello_world",
                 ),
+                kind: Normal,
                 prelude: true,
             },
             Dependency {
@@ -119,6 +121,7 @@
                 name: CrateName(
                     "libc",
                 ),
+                kind: Normal,
                 prelude: true,
             },
         ],
@@ -183,6 +186,7 @@
                 name: CrateName(
                     "hello_world",
                 ),
+                kind: Normal,
                 prelude: true,
             },
             Dependency {
@@ -190,6 +194,7 @@
                 name: CrateName(
                     "libc",
                 ),
+                kind: Normal,
                 prelude: true,
             },
         ],
@@ -254,6 +259,7 @@
                 name: CrateName(
                     "hello_world",
                 ),
+                kind: Normal,
                 prelude: true,
             },
             Dependency {
@@ -261,6 +267,7 @@
                 name: CrateName(
                     "libc",
                 ),
+                kind: Normal,
                 prelude: true,
             },
         ],
diff --git a/crates/project-model/test_data/output/cargo_hello_world_project_model_with_selective_overrides.txt b/crates/project-model/test_data/output/cargo_hello_world_project_model_with_selective_overrides.txt
index 727d39a3077..e98f016ca7d 100644
--- a/crates/project-model/test_data/output/cargo_hello_world_project_model_with_selective_overrides.txt
+++ b/crates/project-model/test_data/output/cargo_hello_world_project_model_with_selective_overrides.txt
@@ -48,6 +48,7 @@
                 name: CrateName(
                     "libc",
                 ),
+                kind: Normal,
                 prelude: true,
             },
         ],
@@ -112,6 +113,7 @@
                 name: CrateName(
                     "hello_world",
                 ),
+                kind: Normal,
                 prelude: true,
             },
             Dependency {
@@ -119,6 +121,7 @@
                 name: CrateName(
                     "libc",
                 ),
+                kind: Normal,
                 prelude: true,
             },
         ],
@@ -183,6 +186,7 @@
                 name: CrateName(
                     "hello_world",
                 ),
+                kind: Normal,
                 prelude: true,
             },
             Dependency {
@@ -190,6 +194,7 @@
                 name: CrateName(
                     "libc",
                 ),
+                kind: Normal,
                 prelude: true,
             },
         ],
@@ -254,6 +259,7 @@
                 name: CrateName(
                     "hello_world",
                 ),
+                kind: Normal,
                 prelude: true,
             },
             Dependency {
@@ -261,6 +267,7 @@
                 name: CrateName(
                     "libc",
                 ),
+                kind: Normal,
                 prelude: true,
             },
         ],
diff --git a/crates/project-model/test_data/output/cargo_hello_world_project_model_with_wildcard_overrides.txt b/crates/project-model/test_data/output/cargo_hello_world_project_model_with_wildcard_overrides.txt
index 89728babd82..7ecd53572e2 100644
--- a/crates/project-model/test_data/output/cargo_hello_world_project_model_with_wildcard_overrides.txt
+++ b/crates/project-model/test_data/output/cargo_hello_world_project_model_with_wildcard_overrides.txt
@@ -47,6 +47,7 @@
                 name: CrateName(
                     "libc",
                 ),
+                kind: Normal,
                 prelude: true,
             },
         ],
@@ -110,6 +111,7 @@
                 name: CrateName(
                     "hello_world",
                 ),
+                kind: Normal,
                 prelude: true,
             },
             Dependency {
@@ -117,6 +119,7 @@
                 name: CrateName(
                     "libc",
                 ),
+                kind: Normal,
                 prelude: true,
             },
         ],
@@ -180,6 +183,7 @@
                 name: CrateName(
                     "hello_world",
                 ),
+                kind: Normal,
                 prelude: true,
             },
             Dependency {
@@ -187,6 +191,7 @@
                 name: CrateName(
                     "libc",
                 ),
+                kind: Normal,
                 prelude: true,
             },
         ],
@@ -250,6 +255,7 @@
                 name: CrateName(
                     "hello_world",
                 ),
+                kind: Normal,
                 prelude: true,
             },
             Dependency {
@@ -257,6 +263,7 @@
                 name: CrateName(
                     "libc",
                 ),
+                kind: Normal,
                 prelude: true,
             },
         ],
diff --git a/crates/project-model/test_data/output/rust_project_hello_world_project_model.txt b/crates/project-model/test_data/output/rust_project_hello_world_project_model.txt
index b7bf6cb2774..581a6afc148 100644
--- a/crates/project-model/test_data/output/rust_project_hello_world_project_model.txt
+++ b/crates/project-model/test_data/output/rust_project_hello_world_project_model.txt
@@ -28,6 +28,7 @@
                 name: CrateName(
                     "core",
                 ),
+                kind: Normal,
                 prelude: true,
             },
         ],
@@ -168,6 +169,7 @@
                 name: CrateName(
                     "std",
                 ),
+                kind: Normal,
                 prelude: true,
             },
             Dependency {
@@ -175,6 +177,7 @@
                 name: CrateName(
                     "core",
                 ),
+                kind: Normal,
                 prelude: true,
             },
         ],
@@ -249,6 +252,7 @@
                 name: CrateName(
                     "alloc",
                 ),
+                kind: Normal,
                 prelude: true,
             },
             Dependency {
@@ -256,6 +260,7 @@
                 name: CrateName(
                     "panic_unwind",
                 ),
+                kind: Normal,
                 prelude: true,
             },
             Dependency {
@@ -263,6 +268,7 @@
                 name: CrateName(
                     "panic_abort",
                 ),
+                kind: Normal,
                 prelude: true,
             },
             Dependency {
@@ -270,6 +276,7 @@
                 name: CrateName(
                     "core",
                 ),
+                kind: Normal,
                 prelude: true,
             },
             Dependency {
@@ -277,6 +284,7 @@
                 name: CrateName(
                     "profiler_builtins",
                 ),
+                kind: Normal,
                 prelude: true,
             },
             Dependency {
@@ -284,6 +292,7 @@
                 name: CrateName(
                     "unwind",
                 ),
+                kind: Normal,
                 prelude: true,
             },
             Dependency {
@@ -291,6 +300,7 @@
                 name: CrateName(
                     "std_detect",
                 ),
+                kind: Normal,
                 prelude: true,
             },
             Dependency {
@@ -298,6 +308,7 @@
                 name: CrateName(
                     "test",
                 ),
+                kind: Normal,
                 prelude: true,
             },
         ],
@@ -438,6 +449,7 @@
                 name: CrateName(
                     "core",
                 ),
+                kind: Normal,
                 prelude: true,
             },
             Dependency {
@@ -445,6 +457,7 @@
                 name: CrateName(
                     "alloc",
                 ),
+                kind: Normal,
                 prelude: true,
             },
             Dependency {
@@ -452,6 +465,7 @@
                 name: CrateName(
                     "std",
                 ),
+                kind: Normal,
                 prelude: true,
             },
             Dependency {
@@ -459,6 +473,7 @@
                 name: CrateName(
                     "test",
                 ),
+                kind: Normal,
                 prelude: false,
             },
             Dependency {
@@ -466,6 +481,7 @@
                 name: CrateName(
                     "proc_macro",
                 ),
+                kind: Normal,
                 prelude: false,
             },
         ],