about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2023-04-25 10:07:25 +0000
committerbors <bors@rust-lang.org>2023-04-25 10:07:25 +0000
commita96bb452a75871ab376c67649eb71121c093ab34 (patch)
tree430a621e3b58801a00ac8201ae06cfc954d90fd8
parente46d7a030f0aaee80b86f35ad4c4716e59042071 (diff)
parente205af259d7518e9042adcc1852ddff4457ce3ed (diff)
downloadrust-a96bb452a75871ab376c67649eb71121c093ab34.tar.gz
rust-a96bb452a75871ab376c67649eb71121c093ab34.zip
Auto merge of #14475 - Veykril:crate-origins, r=Veykril
Handle dev-dependency cycles

cc https://github.com/rust-lang/rust-analyzer/issues/14167

This should fix cycles errors mostly (it fixes the one on rome/tools at least, but not on rustc. Though  there it might just be because of rustc workspace being rustc workspace). Unfortunately this will effectively duplicate all crates currently, since if we want to be completely correct we'd need to set the test cfg for all dev dependencies and the transitive dependencies of those, something I worry we should try to avoid.
-rw-r--r--Cargo.lock2
-rw-r--r--crates/base-db/Cargo.toml1
-rw-r--r--crates/base-db/src/input.rs11
-rw-r--r--crates/base-db/src/lib.rs25
-rw-r--r--crates/hir-def/src/test_db.rs12
-rw-r--r--crates/hir-ty/Cargo.toml1
-rw-r--r--crates/hir-ty/src/test_db.rs12
-rw-r--r--crates/hir/src/semantics/source_to_def.rs4
-rw-r--r--crates/ide-db/src/lib.rs8
-rw-r--r--crates/project-model/src/tests.rs16
-rw-r--r--crates/project-model/src/workspace.rs164
-rw-r--r--crates/project-model/test_data/cargo_dev_dependencies-crate-graph.txt609
-rw-r--r--crates/project-model/test_data/complex-with-dev-deps.json985
13 files changed, 1798 insertions, 52 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 8759782bb29..0e384a2e427 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -87,6 +87,7 @@ name = "base-db"
 version = "0.0.0"
 dependencies = [
  "cfg",
+ "indexmap",
  "la-arena",
  "profile",
  "rustc-hash",
@@ -579,6 +580,7 @@ dependencies = [
  "hir-def",
  "hir-expand",
  "hkalbasi-rustc-ap-rustc_index",
+ "indexmap",
  "intern",
  "itertools",
  "la-arena",
diff --git a/crates/base-db/Cargo.toml b/crates/base-db/Cargo.toml
index c2d021c2238..3c0aedce147 100644
--- a/crates/base-db/Cargo.toml
+++ b/crates/base-db/Cargo.toml
@@ -14,6 +14,7 @@ doctest = false
 [dependencies]
 salsa = "0.17.0-pre.2"
 rustc-hash = "1.1.0"
+indexmap = "1.6.0"
 
 la-arena = { version = "0.3.0", path = "../../lib/la-arena" }
 
diff --git a/crates/base-db/src/input.rs b/crates/base-db/src/input.rs
index dfd0b2abc33..a153e7f36cd 100644
--- a/crates/base-db/src/input.rs
+++ b/crates/base-db/src/input.rs
@@ -417,6 +417,11 @@ impl CrateGraph {
         Ok(())
     }
 
+    pub fn duplicate(&mut self, id: CrateId) -> CrateId {
+        let data = self[id].clone();
+        self.arena.alloc(data)
+    }
+
     pub fn add_dep(
         &mut self,
         from: CrateId,
@@ -612,6 +617,12 @@ impl ops::Index<CrateId> for CrateGraph {
     }
 }
 
+impl ops::IndexMut<CrateId> for CrateGraph {
+    fn index_mut(&mut self, crate_id: CrateId) -> &mut CrateData {
+        &mut self.arena[crate_id]
+    }
+}
+
 impl CrateData {
     fn add_dep(&mut self, dep: Dependency) {
         self.dependencies.push(dep)
diff --git a/crates/base-db/src/lib.rs b/crates/base-db/src/lib.rs
index b65cf7341f3..7593135c5c4 100644
--- a/crates/base-db/src/lib.rs
+++ b/crates/base-db/src/lib.rs
@@ -6,9 +6,10 @@ mod input;
 mod change;
 pub mod fixture;
 
-use std::{panic, sync::Arc};
+use std::{hash::BuildHasherDefault, panic, sync::Arc};
 
-use rustc_hash::FxHashSet;
+use indexmap::IndexSet;
+use rustc_hash::FxHasher;
 use syntax::{ast, Parse, SourceFile, TextRange, TextSize};
 
 pub use crate::{
@@ -59,7 +60,10 @@ pub trait FileLoader {
     /// Text of the file.
     fn file_text(&self, file_id: FileId) -> Arc<str>;
     fn resolve_path(&self, path: AnchoredPath<'_>) -> Option<FileId>;
-    fn relevant_crates(&self, file_id: FileId) -> Arc<FxHashSet<CrateId>>;
+    fn relevant_crates(
+        &self,
+        file_id: FileId,
+    ) -> Arc<IndexSet<CrateId, BuildHasherDefault<FxHasher>>>;
 }
 
 /// Database which stores all significant input facts: source code and project
@@ -99,10 +103,16 @@ pub trait SourceDatabaseExt: SourceDatabase {
     #[salsa::input]
     fn source_root(&self, id: SourceRootId) -> Arc<SourceRoot>;
 
-    fn source_root_crates(&self, id: SourceRootId) -> Arc<FxHashSet<CrateId>>;
+    fn source_root_crates(
+        &self,
+        id: SourceRootId,
+    ) -> Arc<IndexSet<CrateId, BuildHasherDefault<FxHasher>>>;
 }
 
-fn source_root_crates(db: &dyn SourceDatabaseExt, id: SourceRootId) -> Arc<FxHashSet<CrateId>> {
+fn source_root_crates(
+    db: &dyn SourceDatabaseExt,
+    id: SourceRootId,
+) -> Arc<IndexSet<CrateId, BuildHasherDefault<FxHasher>>> {
     let graph = db.crate_graph();
     let res = graph
         .iter()
@@ -128,7 +138,10 @@ impl<T: SourceDatabaseExt> FileLoader for FileLoaderDelegate<&'_ T> {
         source_root.resolve_path(path)
     }
 
-    fn relevant_crates(&self, file_id: FileId) -> Arc<FxHashSet<CrateId>> {
+    fn relevant_crates(
+        &self,
+        file_id: FileId,
+    ) -> Arc<IndexSet<CrateId, BuildHasherDefault<FxHasher>>> {
         let _p = profile::span("relevant_crates");
         let source_root = self.0.file_source_root(file_id);
         self.0.source_root_crates(source_root)
diff --git a/crates/hir-def/src/test_db.rs b/crates/hir-def/src/test_db.rs
index 6bfcd90970d..5a186466bc4 100644
--- a/crates/hir-def/src/test_db.rs
+++ b/crates/hir-def/src/test_db.rs
@@ -1,7 +1,9 @@
 //! Database used for testing `hir_def`.
 
 use std::{
-    fmt, panic,
+    fmt,
+    hash::BuildHasherDefault,
+    panic,
     sync::{Arc, Mutex},
 };
 
@@ -11,7 +13,8 @@ use base_db::{
     Upcast,
 };
 use hir_expand::{db::ExpandDatabase, InFile};
-use rustc_hash::FxHashSet;
+use indexmap::IndexSet;
+use rustc_hash::FxHasher;
 use syntax::{algo, ast, AstNode};
 
 use crate::{
@@ -77,7 +80,10 @@ impl FileLoader for TestDB {
     fn resolve_path(&self, path: AnchoredPath<'_>) -> Option<FileId> {
         FileLoaderDelegate(self).resolve_path(path)
     }
-    fn relevant_crates(&self, file_id: FileId) -> Arc<FxHashSet<CrateId>> {
+    fn relevant_crates(
+        &self,
+        file_id: FileId,
+    ) -> Arc<IndexSet<CrateId, BuildHasherDefault<FxHasher>>> {
         FileLoaderDelegate(self).relevant_crates(file_id)
     }
 }
diff --git a/crates/hir-ty/Cargo.toml b/crates/hir-ty/Cargo.toml
index 3ab7460e8cd..7c153e28d0f 100644
--- a/crates/hir-ty/Cargo.toml
+++ b/crates/hir-ty/Cargo.toml
@@ -30,6 +30,7 @@ la-arena = { version = "0.3.0", path = "../../lib/la-arena" }
 once_cell = "1.17.0"
 typed-arena = "2.0.1"
 rustc_index = { version = "0.0.20221221", package = "hkalbasi-rustc-ap-rustc_index", default-features = false }
+indexmap = "1.6.0"
 
 # local deps
 stdx.workspace = true
diff --git a/crates/hir-ty/src/test_db.rs b/crates/hir-ty/src/test_db.rs
index ca96a8d1724..7525106fd09 100644
--- a/crates/hir-ty/src/test_db.rs
+++ b/crates/hir-ty/src/test_db.rs
@@ -1,7 +1,9 @@
 //! Database used for testing `hir`.
 
 use std::{
-    fmt, panic,
+    fmt,
+    hash::BuildHasherDefault,
+    panic,
     sync::{Arc, Mutex},
 };
 
@@ -11,7 +13,8 @@ use base_db::{
 };
 use hir_def::{db::DefDatabase, ModuleId};
 use hir_expand::db::ExpandDatabase;
-use rustc_hash::FxHashSet;
+use indexmap::IndexSet;
+use rustc_hash::FxHasher;
 use stdx::hash::NoHashHashMap;
 use syntax::TextRange;
 use test_utils::extract_annotations;
@@ -82,7 +85,10 @@ impl FileLoader for TestDB {
     fn resolve_path(&self, path: AnchoredPath<'_>) -> Option<FileId> {
         FileLoaderDelegate(self).resolve_path(path)
     }
-    fn relevant_crates(&self, file_id: FileId) -> Arc<FxHashSet<CrateId>> {
+    fn relevant_crates(
+        &self,
+        file_id: FileId,
+    ) -> Arc<IndexSet<CrateId, BuildHasherDefault<FxHasher>>> {
         FileLoaderDelegate(self).relevant_crates(file_id)
     }
 }
diff --git a/crates/hir/src/semantics/source_to_def.rs b/crates/hir/src/semantics/source_to_def.rs
index c50ffa4f8b7..12257247c87 100644
--- a/crates/hir/src/semantics/source_to_def.rs
+++ b/crates/hir/src/semantics/source_to_def.rs
@@ -119,7 +119,9 @@ impl SourceToDefCtx<'_, '_> {
     pub(super) fn file_to_def(&self, file: FileId) -> SmallVec<[ModuleId; 1]> {
         let _p = profile::span("SourceBinder::to_module_def");
         let mut mods = SmallVec::new();
-        for &crate_id in self.db.relevant_crates(file).iter() {
+        // HACK: We iterate in reverse so that dev-dependency duplicated crates appear first in this
+        // Most code only deals with one module and we want to prefer the test enabled code where possible
+        for &crate_id in self.db.relevant_crates(file).iter().rev() {
             // FIXME: inner items
             let crate_def_map = self.db.crate_def_map(crate_id);
             mods.extend(
diff --git a/crates/ide-db/src/lib.rs b/crates/ide-db/src/lib.rs
index b0c5820fb09..879dbb74507 100644
--- a/crates/ide-db/src/lib.rs
+++ b/crates/ide-db/src/lib.rs
@@ -43,7 +43,7 @@ pub mod syntax_helpers {
     pub use parser::LexedStr;
 }
 
-use std::{fmt, mem::ManuallyDrop, sync::Arc};
+use std::{fmt, hash::BuildHasherDefault, mem::ManuallyDrop, sync::Arc};
 
 use base_db::{
     salsa::{self, Durability},
@@ -53,6 +53,7 @@ use hir::{
     db::{DefDatabase, ExpandDatabase, HirDatabase},
     symbols::FileSymbolKind,
 };
+use indexmap::IndexSet;
 
 use crate::{line_index::LineIndex, symbol_index::SymbolsDatabase};
 pub use rustc_hash::{FxHashMap, FxHashSet, FxHasher};
@@ -119,7 +120,10 @@ impl FileLoader for RootDatabase {
     fn resolve_path(&self, path: AnchoredPath<'_>) -> Option<FileId> {
         FileLoaderDelegate(self).resolve_path(path)
     }
-    fn relevant_crates(&self, file_id: FileId) -> Arc<FxHashSet<CrateId>> {
+    fn relevant_crates(
+        &self,
+        file_id: FileId,
+    ) -> Arc<IndexSet<CrateId, BuildHasherDefault<FxHasher>>> {
         FileLoaderDelegate(self).relevant_crates(file_id)
     }
 }
diff --git a/crates/project-model/src/tests.rs b/crates/project-model/src/tests.rs
index 3e5234afc81..3c0d53b26b8 100644
--- a/crates/project-model/src/tests.rs
+++ b/crates/project-model/src/tests.rs
@@ -5,7 +5,7 @@ use std::{
 
 use base_db::{CrateGraph, FileId, ProcMacroPaths};
 use cfg::{CfgAtom, CfgDiff};
-use expect_test::{expect, Expect};
+use expect_test::{expect, expect_file, Expect, ExpectFile};
 use paths::{AbsPath, AbsPathBuf};
 use serde::de::DeserializeOwned;
 
@@ -114,6 +114,11 @@ fn check_crate_graph(crate_graph: CrateGraph, expect: Expect) {
     replace_root(&mut crate_graph, false);
     expect.assert_eq(&crate_graph);
 }
+fn check_crate_graph_f(crate_graph: CrateGraph, expect: ExpectFile) {
+    let mut crate_graph = format!("{crate_graph:#?}");
+    replace_root(&mut crate_graph, false);
+    expect.assert_eq(&crate_graph);
+}
 
 #[test]
 fn cargo_hello_world_project_model_with_wildcard_overrides() {
@@ -1666,3 +1671,12 @@ fn rust_project_is_proc_macro_has_proc_macro_dep() {
     // on the proc_macro sysroot crate.
     crate_data.dependencies.iter().find(|&dep| dep.name.deref() == "proc_macro").unwrap();
 }
+
+#[test]
+fn cargo_dev_dependencies() {
+    let (crate_graph, _proc_macros) = load_cargo("complex-with-dev-deps.json");
+    check_crate_graph_f(
+        crate_graph,
+        expect_file!["../test_data/cargo_dev_dependencies-crate-graph.txt"],
+    )
+}
diff --git a/crates/project-model/src/workspace.rs b/crates/project-model/src/workspace.rs
index 5518b6bc7f5..065b25d7463 100644
--- a/crates/project-model/src/workspace.rs
+++ b/crates/project-model/src/workspace.rs
@@ -2,7 +2,12 @@
 //! metadata` or `rust-project.json`) into representation stored in the salsa
 //! database -- `CrateGraph`.
 
-use std::{collections::VecDeque, fmt, fs, process::Command, sync::Arc};
+use std::{
+    collections::{hash_map::Entry, VecDeque},
+    fmt, fs,
+    process::Command,
+    sync::Arc,
+};
 
 use anyhow::{format_err, Context, Result};
 use base_db::{
@@ -844,12 +849,12 @@ fn cargo_to_crate_graph(
         None => (SysrootPublicDeps::default(), None),
     };
 
-    let cfg_options = {
+    let cfg_options = forced_cfg.clone().unwrap_or_else(|| {
         let mut cfg_options = CfgOptions::default();
         cfg_options.extend(rustc_cfg);
         cfg_options.insert_atom("debug_assertions".into());
         cfg_options
-    };
+    });
 
     // Mapping of a package to its library target
     let mut pkg_to_lib_crate = FxHashMap::default();
@@ -861,32 +866,6 @@ fn cargo_to_crate_graph(
     for pkg in cargo.packages() {
         has_private |= cargo[pkg].metadata.rustc_private;
 
-        let cfg_options = forced_cfg.clone().unwrap_or_else(|| {
-            let mut cfg_options = cfg_options.clone();
-
-            // Add test cfg for local crates
-            if cargo[pkg].is_local {
-                cfg_options.insert_atom("test".into());
-            }
-
-            let overrides = match override_cfg {
-                CfgOverrides::Wildcard(cfg_diff) => Some(cfg_diff),
-                CfgOverrides::Selective(cfg_overrides) => cfg_overrides.get(&cargo[pkg].name),
-            };
-
-            if let Some(overrides) = overrides {
-                // FIXME: this is sort of a hack to deal with #![cfg(not(test))] vanishing such as seen
-                // in ed25519_dalek (#7243), and libcore (#9203) (although you only hit that one while
-                // working on rust-lang/rust as that's the only time it appears outside sysroot).
-                //
-                // A more ideal solution might be to reanalyze crates based on where the cursor is and
-                // figure out the set of cfgs that would have to apply to make it active.
-
-                cfg_options.apply_diff(overrides.clone());
-            };
-            cfg_options
-        });
-
         let mut lib_tgt = None;
         for &tgt in cargo[pkg].targets.iter() {
             if cargo[tgt].kind != TargetKind::Lib && !cargo[pkg].is_member {
@@ -897,7 +876,7 @@ fn cargo_to_crate_graph(
                 // https://github.com/rust-lang/rust-analyzer/issues/11300
                 continue;
             }
-            let Some(file_id) =  load(&cargo[tgt].root) else { continue };
+            let Some(file_id) = load(&cargo[tgt].root) else { continue };
 
             let crate_id = add_target_crate_root(
                 crate_graph,
@@ -925,15 +904,19 @@ fn cargo_to_crate_graph(
             pkg_crates.entry(pkg).or_insert_with(Vec::new).push((crate_id, cargo[tgt].kind));
         }
 
+        let Some(targets) = pkg_crates.get(&pkg) else { continue };
         // Set deps to the core, std and to the lib target of the current package
-        for &(from, kind) in pkg_crates.get(&pkg).into_iter().flatten() {
+        for &(from, kind) in targets {
             // Add sysroot deps first so that a lib target named `core` etc. can overwrite them.
             public_deps.add_to_crate_graph(crate_graph, from);
 
             // Add dep edge of all targets to the package's lib target
             if let Some((to, name)) = lib_tgt.clone() {
-                if to != from && kind != TargetKind::BuildScript {
-                    // (build script can not depend on its library target)
+                if to != from {
+                    if kind == TargetKind::BuildScript {
+                        // build script can not depend on its library target
+                        continue;
+                    }
 
                     // For root projects with dashes in their name,
                     // cargo metadata does not do any normalization,
@@ -945,6 +928,47 @@ fn cargo_to_crate_graph(
         }
     }
 
+    // We now need to duplicate workspace members that are used as dev-dependencies to prevent
+    // cycles from forming.
+
+    // Map from crate id to it's dev-dependency clone id
+    let mut test_dupes = FxHashMap::default();
+    let mut work = vec![];
+
+    // Get all dependencies of the workspace members that are used as dev-dependencies
+    for pkg in cargo.packages() {
+        for dep in &cargo[pkg].dependencies {
+            if dep.kind == DepKind::Dev && cargo[dep.pkg].is_member {
+                work.push(dep.pkg);
+            }
+        }
+    }
+    while let Some(pkg) = work.pop() {
+        let Some(&to) = pkg_to_lib_crate.get(&pkg) else { continue };
+        match test_dupes.entry(to) {
+            Entry::Occupied(_) => continue,
+            Entry::Vacant(v) => {
+                for dep in &cargo[pkg].dependencies {
+                    if dep.kind == DepKind::Normal && cargo[dep.pkg].is_member {
+                        work.push(dep.pkg);
+                    }
+                }
+                v.insert({
+                    let duped = crate_graph.duplicate(to);
+                    tracing::info!(
+                        "duplicating workspace crate {:?} as it is being used as a dev-dependency: {to:?} -> {duped:?}",
+                        crate_graph[to].display_name
+                    );
+                    if let Some(proc_macro) = proc_macros.get(&to).cloned() {
+                        proc_macros.insert(duped, proc_macro);
+                    }
+                    crate_graph[duped].cfg_options.insert_atom("test".into());
+                    duped
+                });
+            }
+        }
+    }
+
     // Now add a dep edge from all targets of upstream to the lib
     // target of downstream.
     for pkg in cargo.packages() {
@@ -958,12 +982,71 @@ fn cargo_to_crate_graph(
                 if (dep.kind == DepKind::Build) != (kind == TargetKind::BuildScript) {
                     continue;
                 }
+                add_dep(
+                    crate_graph,
+                    from,
+                    name.clone(),
+                    if dep.kind == DepKind::Dev {
+                        // point to the test enabled duplicate for dev-dependencies
+                        test_dupes.get(&to).copied().unwrap_or(to)
+                    } else {
+                        to
+                    },
+                );
 
-                add_dep(crate_graph, from, name.clone(), to)
+                if dep.kind == DepKind::Normal && cargo[dep.pkg].is_member {
+                    // Also apply the dependency as a test enabled dependency to the test duplicate
+                    if let Some(&dupe) = test_dupes.get(&from) {
+                        let to = test_dupes.get(&to).copied().unwrap_or_else(|| {
+                            panic!(
+                                "dependency of a dev dependency did not get duplicated! {:?} {:?}",
+                                crate_graph[to].display_name, crate_graph[from].display_name,
+                            )
+                        });
+                        add_dep(crate_graph, dupe, name.clone(), to);
+                    }
+                }
             }
         }
     }
 
+    for (&pkg, targets) in &pkg_crates {
+        for &(krate, _) in targets {
+            if let Some(&dupe) = test_dupes.get(&krate) {
+                tracing::info!(
+                    "{krate:?} {:?} {dupe:?} {:?}",
+                    crate_graph[krate].cfg_options,
+                    crate_graph[dupe].cfg_options
+                );
+                // if the crate got duped as a dev-dep the dupe already has test set
+                continue;
+            }
+            let cfg_options = &mut crate_graph[krate].cfg_options;
+
+            // Add test cfg for local crates
+            if cargo[pkg].is_local {
+                cfg_options.insert_atom("test".into());
+            }
+
+            let overrides = match override_cfg {
+                CfgOverrides::Wildcard(cfg_diff) => Some(cfg_diff),
+                CfgOverrides::Selective(cfg_overrides) => cfg_overrides.get(&cargo[pkg].name),
+            };
+
+            if let Some(overrides) = overrides {
+                // FIXME: this is sort of a hack to deal with #![cfg(not(test))] vanishing such as seen
+                // in ed25519_dalek (#7243), and libcore (#9203) (although you only hit that one while
+                // working on rust-lang/rust as that's the only time it appears outside sysroot).
+                //
+                // A more ideal solution might be to reanalyze crates based on where the cursor is and
+                // figure out the set of cfgs that would have to apply to make it active.
+
+                cfg_options.apply_diff(overrides.clone());
+            };
+        }
+    }
+
+    // FIXME: Handle rustc private crates properly when used as dev-dependencies
     if has_private {
         // If the user provided a path to rustc sources, we add all the rustc_private crates
         // and create dependencies on them for the crates which opt-in to that
@@ -1087,7 +1170,9 @@ fn handle_rustc_crates(
                 continue;
             }
             for dep in &rustc_workspace[pkg].dependencies {
-                queue.push_back(dep.pkg);
+                if dep.kind == DepKind::Normal {
+                    queue.push_back(dep.pkg);
+                }
             }
 
             let mut cfg_options = cfg_options.clone();
@@ -1397,10 +1482,12 @@ fn handle_hack_cargo_workspace(
         .collect()
 }
 
+#[track_caller]
 fn add_dep(graph: &mut CrateGraph, from: CrateId, name: CrateName, to: CrateId) {
     add_dep_inner(graph, from, Dependency::new(name, to))
 }
 
+#[track_caller]
 fn add_dep_with_prelude(
     graph: &mut CrateGraph,
     from: CrateId,
@@ -1411,13 +1498,18 @@ fn add_dep_with_prelude(
     add_dep_inner(graph, from, Dependency::with_prelude(name, to, prelude))
 }
 
+#[track_caller]
 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);
 }
 
+#[track_caller]
 fn add_dep_inner(graph: &mut CrateGraph, from: CrateId, dep: Dependency) {
     if let Err(err) = graph.add_dep(from, dep) {
-        tracing::error!("{}", err)
+        if cfg!(test) {
+            panic!("{}", err);
+        }
+        tracing::error!("{}", err);
     }
 }
 
diff --git a/crates/project-model/test_data/cargo_dev_dependencies-crate-graph.txt b/crates/project-model/test_data/cargo_dev_dependencies-crate-graph.txt
new file mode 100644
index 00000000000..10709402514
--- /dev/null
+++ b/crates/project-model/test_data/cargo_dev_dependencies-crate-graph.txt
@@ -0,0 +1,609 @@
+{
+    0: CrateData {
+        root_file_id: FileId(
+            1,
+        ),
+        edition: Edition2018,
+        version: Some(
+            "0.7.20",
+        ),
+        display_name: Some(
+            CrateDisplayName {
+                crate_name: CrateName(
+                    "aho_corasick",
+                ),
+                canonical_name: "aho_corasick",
+            },
+        ),
+        cfg_options: CfgOptions(
+            [
+                "debug_assertions",
+                "feature=default",
+                "feature=std",
+            ],
+        ),
+        potential_cfg_options: Some(
+            CfgOptions(
+                [
+                    "debug_assertions",
+                    "feature=default",
+                    "feature=std",
+                ],
+            ),
+        ),
+        env: Env {
+            entries: {
+                "CARGO_PKG_LICENSE": "",
+                "CARGO_PKG_VERSION_MAJOR": "0",
+                "CARGO_MANIFEST_DIR": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/aho-corasick-0.7.20",
+                "CARGO_PKG_VERSION": "0.7.20",
+                "CARGO_PKG_AUTHORS": "",
+                "CARGO_CRATE_NAME": "aho_corasick",
+                "CARGO_PKG_LICENSE_FILE": "",
+                "CARGO_PKG_HOMEPAGE": "",
+                "CARGO_PKG_DESCRIPTION": "",
+                "CARGO_PKG_NAME": "aho-corasick",
+                "CARGO_PKG_VERSION_PATCH": "20",
+                "CARGO": "cargo",
+                "CARGO_PKG_REPOSITORY": "",
+                "CARGO_PKG_VERSION_MINOR": "7",
+                "CARGO_PKG_VERSION_PRE": "",
+            },
+        },
+        dependencies: [
+            Dependency {
+                crate_id: Idx::<CrateData>(2),
+                name: CrateName(
+                    "memchr",
+                ),
+                prelude: true,
+            },
+        ],
+        origin: Library {
+            repo: Some(
+                "https://github.com/BurntSushi/aho-corasick",
+            ),
+            name: "aho-corasick",
+        },
+        is_proc_macro: false,
+        target_layout: Err(
+            "target_data_layout not loaded",
+        ),
+        channel: None,
+    },
+    1: CrateData {
+        root_file_id: FileId(
+            2,
+        ),
+        edition: Edition2021,
+        version: Some(
+            "0.3.0",
+        ),
+        display_name: Some(
+            CrateDisplayName {
+                crate_name: CrateName(
+                    "la_arena",
+                ),
+                canonical_name: "la-arena",
+            },
+        ),
+        cfg_options: CfgOptions(
+            [
+                "debug_assertions",
+            ],
+        ),
+        potential_cfg_options: None,
+        env: Env {
+            entries: {
+                "CARGO_PKG_LICENSE": "",
+                "CARGO_PKG_VERSION_MAJOR": "0",
+                "CARGO_MANIFEST_DIR": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/la-arena-0.3.0",
+                "CARGO_PKG_VERSION": "0.3.0",
+                "CARGO_PKG_AUTHORS": "",
+                "CARGO_CRATE_NAME": "la_arena",
+                "CARGO_PKG_LICENSE_FILE": "",
+                "CARGO_PKG_HOMEPAGE": "",
+                "CARGO_PKG_DESCRIPTION": "",
+                "CARGO_PKG_NAME": "la-arena",
+                "CARGO_PKG_VERSION_PATCH": "0",
+                "CARGO": "cargo",
+                "CARGO_PKG_REPOSITORY": "",
+                "CARGO_PKG_VERSION_MINOR": "3",
+                "CARGO_PKG_VERSION_PRE": "",
+            },
+        },
+        dependencies: [],
+        origin: Library {
+            repo: Some(
+                "https://github.com/rust-lang/rust-analyzer/tree/master/lib/la-arena",
+            ),
+            name: "la-arena",
+        },
+        is_proc_macro: false,
+        target_layout: Err(
+            "target_data_layout not loaded",
+        ),
+        channel: None,
+    },
+    2: CrateData {
+        root_file_id: FileId(
+            3,
+        ),
+        edition: Edition2018,
+        version: Some(
+            "2.5.0",
+        ),
+        display_name: Some(
+            CrateDisplayName {
+                crate_name: CrateName(
+                    "memchr",
+                ),
+                canonical_name: "memchr",
+            },
+        ),
+        cfg_options: CfgOptions(
+            [
+                "debug_assertions",
+                "feature=default",
+                "feature=std",
+            ],
+        ),
+        potential_cfg_options: Some(
+            CfgOptions(
+                [
+                    "debug_assertions",
+                    "feature=compiler_builtins",
+                    "feature=core",
+                    "feature=default",
+                    "feature=libc",
+                    "feature=rustc-dep-of-std",
+                    "feature=std",
+                    "feature=use_std",
+                ],
+            ),
+        ),
+        env: Env {
+            entries: {
+                "CARGO_PKG_LICENSE": "",
+                "CARGO_PKG_VERSION_MAJOR": "2",
+                "CARGO_MANIFEST_DIR": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/memchr-2.5.0",
+                "CARGO_PKG_VERSION": "2.5.0",
+                "CARGO_PKG_AUTHORS": "",
+                "CARGO_CRATE_NAME": "memchr",
+                "CARGO_PKG_LICENSE_FILE": "",
+                "CARGO_PKG_HOMEPAGE": "",
+                "CARGO_PKG_DESCRIPTION": "",
+                "CARGO_PKG_NAME": "memchr",
+                "CARGO_PKG_VERSION_PATCH": "0",
+                "CARGO": "cargo",
+                "CARGO_PKG_REPOSITORY": "",
+                "CARGO_PKG_VERSION_MINOR": "5",
+                "CARGO_PKG_VERSION_PRE": "",
+            },
+        },
+        dependencies: [],
+        origin: Library {
+            repo: Some(
+                "https://github.com/BurntSushi/memchr",
+            ),
+            name: "memchr",
+        },
+        is_proc_macro: false,
+        target_layout: Err(
+            "target_data_layout not loaded",
+        ),
+        channel: None,
+    },
+    3: CrateData {
+        root_file_id: FileId(
+            4,
+        ),
+        edition: Edition2021,
+        version: Some(
+            "0.1.0",
+        ),
+        display_name: Some(
+            CrateDisplayName {
+                crate_name: CrateName(
+                    "ra_playground",
+                ),
+                canonical_name: "ra-playground",
+            },
+        ),
+        cfg_options: CfgOptions(
+            [
+                "debug_assertions",
+            ],
+        ),
+        potential_cfg_options: None,
+        env: Env {
+            entries: {
+                "CARGO_PKG_LICENSE": "",
+                "CARGO_PKG_VERSION_MAJOR": "0",
+                "CARGO_MANIFEST_DIR": "$ROOT$ra-playground",
+                "CARGO_PKG_VERSION": "0.1.0",
+                "CARGO_PKG_AUTHORS": "",
+                "CARGO_CRATE_NAME": "ra_playground",
+                "CARGO_PKG_LICENSE_FILE": "",
+                "CARGO_PKG_HOMEPAGE": "",
+                "CARGO_PKG_DESCRIPTION": "",
+                "CARGO_PKG_NAME": "ra-playground",
+                "CARGO_PKG_VERSION_PATCH": "0",
+                "CARGO": "cargo",
+                "CARGO_PKG_REPOSITORY": "",
+                "CARGO_PKG_VERSION_MINOR": "1",
+                "CARGO_PKG_VERSION_PRE": "",
+            },
+        },
+        dependencies: [
+            Dependency {
+                crate_id: Idx::<CrateData>(1),
+                name: CrateName(
+                    "la_arena",
+                ),
+                prelude: true,
+            },
+            Dependency {
+                crate_id: Idx::<CrateData>(7),
+                name: CrateName(
+                    "ra_playground",
+                ),
+                prelude: true,
+            },
+            Dependency {
+                crate_id: Idx::<CrateData>(5),
+                name: CrateName(
+                    "regex",
+                ),
+                prelude: true,
+            },
+        ],
+        origin: Local {
+            repo: None,
+            name: Some(
+                "ra-playground",
+            ),
+        },
+        is_proc_macro: false,
+        target_layout: Err(
+            "target_data_layout not loaded",
+        ),
+        channel: None,
+    },
+    4: CrateData {
+        root_file_id: FileId(
+            5,
+        ),
+        edition: Edition2021,
+        version: Some(
+            "0.1.0",
+        ),
+        display_name: Some(
+            CrateDisplayName {
+                crate_name: CrateName(
+                    "ra_playground",
+                ),
+                canonical_name: "ra-playground",
+            },
+        ),
+        cfg_options: CfgOptions(
+            [
+                "debug_assertions",
+                "test",
+            ],
+        ),
+        potential_cfg_options: None,
+        env: Env {
+            entries: {
+                "CARGO_PKG_LICENSE": "",
+                "CARGO_PKG_VERSION_MAJOR": "0",
+                "CARGO_MANIFEST_DIR": "$ROOT$ra-playground",
+                "CARGO_PKG_VERSION": "0.1.0",
+                "CARGO_PKG_AUTHORS": "",
+                "CARGO_CRATE_NAME": "ra_playground",
+                "CARGO_PKG_LICENSE_FILE": "",
+                "CARGO_PKG_HOMEPAGE": "",
+                "CARGO_PKG_DESCRIPTION": "",
+                "CARGO_PKG_NAME": "ra-playground",
+                "CARGO_PKG_VERSION_PATCH": "0",
+                "CARGO": "cargo",
+                "CARGO_PKG_REPOSITORY": "",
+                "CARGO_PKG_VERSION_MINOR": "1",
+                "CARGO_PKG_VERSION_PRE": "",
+            },
+        },
+        dependencies: [
+            Dependency {
+                crate_id: Idx::<CrateData>(3),
+                name: CrateName(
+                    "ra_playground",
+                ),
+                prelude: true,
+            },
+            Dependency {
+                crate_id: Idx::<CrateData>(1),
+                name: CrateName(
+                    "la_arena",
+                ),
+                prelude: true,
+            },
+            Dependency {
+                crate_id: Idx::<CrateData>(7),
+                name: CrateName(
+                    "ra_playground",
+                ),
+                prelude: true,
+            },
+            Dependency {
+                crate_id: Idx::<CrateData>(5),
+                name: CrateName(
+                    "regex",
+                ),
+                prelude: true,
+            },
+        ],
+        origin: Local {
+            repo: None,
+            name: Some(
+                "ra-playground",
+            ),
+        },
+        is_proc_macro: false,
+        target_layout: Err(
+            "target_data_layout not loaded",
+        ),
+        channel: None,
+    },
+    5: CrateData {
+        root_file_id: FileId(
+            6,
+        ),
+        edition: Edition2018,
+        version: Some(
+            "1.7.3",
+        ),
+        display_name: Some(
+            CrateDisplayName {
+                crate_name: CrateName(
+                    "regex",
+                ),
+                canonical_name: "regex",
+            },
+        ),
+        cfg_options: CfgOptions(
+            [
+                "debug_assertions",
+                "feature=aho-corasick",
+                "feature=default",
+                "feature=memchr",
+                "feature=perf",
+                "feature=perf-cache",
+                "feature=perf-dfa",
+                "feature=perf-inline",
+                "feature=perf-literal",
+                "feature=std",
+                "feature=unicode",
+                "feature=unicode-age",
+                "feature=unicode-bool",
+                "feature=unicode-case",
+                "feature=unicode-gencat",
+                "feature=unicode-perl",
+                "feature=unicode-script",
+                "feature=unicode-segment",
+            ],
+        ),
+        potential_cfg_options: Some(
+            CfgOptions(
+                [
+                    "debug_assertions",
+                    "feature=aho-corasick",
+                    "feature=default",
+                    "feature=memchr",
+                    "feature=pattern",
+                    "feature=perf",
+                    "feature=perf-cache",
+                    "feature=perf-dfa",
+                    "feature=perf-inline",
+                    "feature=perf-literal",
+                    "feature=std",
+                    "feature=unicode",
+                    "feature=unicode-age",
+                    "feature=unicode-bool",
+                    "feature=unicode-case",
+                    "feature=unicode-gencat",
+                    "feature=unicode-perl",
+                    "feature=unicode-script",
+                    "feature=unicode-segment",
+                    "feature=unstable",
+                    "feature=use_std",
+                ],
+            ),
+        ),
+        env: Env {
+            entries: {
+                "CARGO_PKG_LICENSE": "",
+                "CARGO_PKG_VERSION_MAJOR": "1",
+                "CARGO_MANIFEST_DIR": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/regex-1.7.3",
+                "CARGO_PKG_VERSION": "1.7.3",
+                "CARGO_PKG_AUTHORS": "",
+                "CARGO_CRATE_NAME": "regex",
+                "CARGO_PKG_LICENSE_FILE": "",
+                "CARGO_PKG_HOMEPAGE": "",
+                "CARGO_PKG_DESCRIPTION": "",
+                "CARGO_PKG_NAME": "regex",
+                "CARGO_PKG_VERSION_PATCH": "3",
+                "CARGO": "cargo",
+                "CARGO_PKG_REPOSITORY": "",
+                "CARGO_PKG_VERSION_MINOR": "7",
+                "CARGO_PKG_VERSION_PRE": "",
+            },
+        },
+        dependencies: [
+            Dependency {
+                crate_id: Idx::<CrateData>(0),
+                name: CrateName(
+                    "aho_corasick",
+                ),
+                prelude: true,
+            },
+            Dependency {
+                crate_id: Idx::<CrateData>(2),
+                name: CrateName(
+                    "memchr",
+                ),
+                prelude: true,
+            },
+            Dependency {
+                crate_id: Idx::<CrateData>(6),
+                name: CrateName(
+                    "regex_syntax",
+                ),
+                prelude: true,
+            },
+        ],
+        origin: Library {
+            repo: Some(
+                "https://github.com/rust-lang/regex",
+            ),
+            name: "regex",
+        },
+        is_proc_macro: false,
+        target_layout: Err(
+            "target_data_layout not loaded",
+        ),
+        channel: None,
+    },
+    6: CrateData {
+        root_file_id: FileId(
+            7,
+        ),
+        edition: Edition2018,
+        version: Some(
+            "0.6.29",
+        ),
+        display_name: Some(
+            CrateDisplayName {
+                crate_name: CrateName(
+                    "regex_syntax",
+                ),
+                canonical_name: "regex-syntax",
+            },
+        ),
+        cfg_options: CfgOptions(
+            [
+                "debug_assertions",
+                "feature=default",
+                "feature=unicode",
+                "feature=unicode-age",
+                "feature=unicode-bool",
+                "feature=unicode-case",
+                "feature=unicode-gencat",
+                "feature=unicode-perl",
+                "feature=unicode-script",
+                "feature=unicode-segment",
+            ],
+        ),
+        potential_cfg_options: Some(
+            CfgOptions(
+                [
+                    "debug_assertions",
+                    "feature=default",
+                    "feature=unicode",
+                    "feature=unicode-age",
+                    "feature=unicode-bool",
+                    "feature=unicode-case",
+                    "feature=unicode-gencat",
+                    "feature=unicode-perl",
+                    "feature=unicode-script",
+                    "feature=unicode-segment",
+                ],
+            ),
+        ),
+        env: Env {
+            entries: {
+                "CARGO_PKG_LICENSE": "",
+                "CARGO_PKG_VERSION_MAJOR": "0",
+                "CARGO_MANIFEST_DIR": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/regex-syntax-0.6.29",
+                "CARGO_PKG_VERSION": "0.6.29",
+                "CARGO_PKG_AUTHORS": "",
+                "CARGO_CRATE_NAME": "regex_syntax",
+                "CARGO_PKG_LICENSE_FILE": "",
+                "CARGO_PKG_HOMEPAGE": "",
+                "CARGO_PKG_DESCRIPTION": "",
+                "CARGO_PKG_NAME": "regex-syntax",
+                "CARGO_PKG_VERSION_PATCH": "29",
+                "CARGO": "cargo",
+                "CARGO_PKG_REPOSITORY": "",
+                "CARGO_PKG_VERSION_MINOR": "6",
+                "CARGO_PKG_VERSION_PRE": "",
+            },
+        },
+        dependencies: [],
+        origin: Library {
+            repo: Some(
+                "https://github.com/rust-lang/regex",
+            ),
+            name: "regex-syntax",
+        },
+        is_proc_macro: false,
+        target_layout: Err(
+            "target_data_layout not loaded",
+        ),
+        channel: None,
+    },
+    7: CrateData {
+        root_file_id: FileId(
+            4,
+        ),
+        edition: Edition2021,
+        version: Some(
+            "0.1.0",
+        ),
+        display_name: Some(
+            CrateDisplayName {
+                crate_name: CrateName(
+                    "ra_playground",
+                ),
+                canonical_name: "ra-playground",
+            },
+        ),
+        cfg_options: CfgOptions(
+            [
+                "debug_assertions",
+                "test",
+            ],
+        ),
+        potential_cfg_options: None,
+        env: Env {
+            entries: {
+                "CARGO_PKG_LICENSE": "",
+                "CARGO_PKG_VERSION_MAJOR": "0",
+                "CARGO_MANIFEST_DIR": "$ROOT$ra-playground",
+                "CARGO_PKG_VERSION": "0.1.0",
+                "CARGO_PKG_AUTHORS": "",
+                "CARGO_CRATE_NAME": "ra_playground",
+                "CARGO_PKG_LICENSE_FILE": "",
+                "CARGO_PKG_HOMEPAGE": "",
+                "CARGO_PKG_DESCRIPTION": "",
+                "CARGO_PKG_NAME": "ra-playground",
+                "CARGO_PKG_VERSION_PATCH": "0",
+                "CARGO": "cargo",
+                "CARGO_PKG_REPOSITORY": "",
+                "CARGO_PKG_VERSION_MINOR": "1",
+                "CARGO_PKG_VERSION_PRE": "",
+            },
+        },
+        dependencies: [],
+        origin: Local {
+            repo: None,
+            name: Some(
+                "ra-playground",
+            ),
+        },
+        is_proc_macro: false,
+        target_layout: Err(
+            "target_data_layout not loaded",
+        ),
+        channel: None,
+    },
+}
\ No newline at end of file
diff --git a/crates/project-model/test_data/complex-with-dev-deps.json b/crates/project-model/test_data/complex-with-dev-deps.json
new file mode 100644
index 00000000000..6046e16d7d5
--- /dev/null
+++ b/crates/project-model/test_data/complex-with-dev-deps.json
@@ -0,0 +1,985 @@
+{
+    "packages": [
+        {
+            "name": "aho-corasick",
+            "version": "0.7.20",
+            "id": "aho-corasick 0.7.20 (registry+https://github.com/rust-lang/crates.io-index)",
+            "license": "Unlicense OR MIT",
+            "license_file": null,
+            "description": "Fast multiple substring searching.",
+            "source": "registry+https://github.com/rust-lang/crates.io-index",
+            "dependencies": [
+                {
+                    "name": "memchr",
+                    "source": "registry+https://github.com/rust-lang/crates.io-index",
+                    "req": "^2.4.0",
+                    "kind": null,
+                    "rename": null,
+                    "optional": false,
+                    "uses_default_features": false,
+                    "features": [],
+                    "target": null,
+                    "registry": null
+                }
+            ],
+            "targets": [
+                {
+                    "kind": [
+                        "lib"
+                    ],
+                    "crate_types": [
+                        "lib"
+                    ],
+                    "name": "aho_corasick",
+                    "src_path": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/aho-corasick-0.7.20/src/lib.rs",
+                    "edition": "2018",
+                    "doc": true,
+                    "doctest": true,
+                    "test": true
+                }
+            ],
+            "features": {
+                "default": [
+                    "std"
+                ],
+                "std": [
+                    "memchr/std"
+                ]
+            },
+            "manifest_path": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/aho-corasick-0.7.20/Cargo.toml",
+            "metadata": null,
+            "publish": null,
+            "authors": [
+                "Andrew Gallant <jamslam@gmail.com>"
+            ],
+            "categories": [
+                "text-processing"
+            ],
+            "keywords": [
+                "string",
+                "search",
+                "text",
+                "aho",
+                "multi"
+            ],
+            "readme": "README.md",
+            "repository": "https://github.com/BurntSushi/aho-corasick",
+            "homepage": "https://github.com/BurntSushi/aho-corasick",
+            "documentation": null,
+            "edition": "2018",
+            "links": null,
+            "default_run": null,
+            "rust_version": null
+        },
+        {
+            "name": "la-arena",
+            "version": "0.3.0",
+            "id": "la-arena 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+            "license": "MIT OR Apache-2.0",
+            "license_file": null,
+            "description": "Simple index-based arena without deletion.",
+            "source": "registry+https://github.com/rust-lang/crates.io-index",
+            "dependencies": [],
+            "targets": [
+                {
+                    "kind": [
+                        "lib"
+                    ],
+                    "crate_types": [
+                        "lib"
+                    ],
+                    "name": "la-arena",
+                    "src_path": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/la-arena-0.3.0/src/lib.rs",
+                    "edition": "2021",
+                    "doc": true,
+                    "doctest": true,
+                    "test": true
+                }
+            ],
+            "features": {},
+            "manifest_path": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/la-arena-0.3.0/Cargo.toml",
+            "metadata": null,
+            "publish": null,
+            "authors": [],
+            "categories": [
+                "data-structures",
+                "memory-management",
+                "rust-patterns"
+            ],
+            "keywords": [],
+            "readme": null,
+            "repository": "https://github.com/rust-lang/rust-analyzer/tree/master/lib/la-arena",
+            "homepage": null,
+            "documentation": "https://docs.rs/la-arena",
+            "edition": "2021",
+            "links": null,
+            "default_run": null,
+            "rust_version": "1.56"
+        },
+        {
+            "name": "memchr",
+            "version": "2.5.0",
+            "id": "memchr 2.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
+            "license": "Unlicense/MIT",
+            "license_file": null,
+            "description": "Safe interface to memchr.",
+            "source": "registry+https://github.com/rust-lang/crates.io-index",
+            "dependencies": [
+                {
+                    "name": "compiler_builtins",
+                    "source": "registry+https://github.com/rust-lang/crates.io-index",
+                    "req": "^0.1.2",
+                    "kind": null,
+                    "rename": null,
+                    "optional": true,
+                    "uses_default_features": true,
+                    "features": [],
+                    "target": null,
+                    "registry": null
+                },
+                {
+                    "name": "rustc-std-workspace-core",
+                    "source": "registry+https://github.com/rust-lang/crates.io-index",
+                    "req": "^1.0.0",
+                    "kind": null,
+                    "rename": "core",
+                    "optional": true,
+                    "uses_default_features": true,
+                    "features": [],
+                    "target": null,
+                    "registry": null
+                },
+                {
+                    "name": "libc",
+                    "source": "registry+https://github.com/rust-lang/crates.io-index",
+                    "req": "^0.2.18",
+                    "kind": null,
+                    "rename": null,
+                    "optional": true,
+                    "uses_default_features": false,
+                    "features": [],
+                    "target": null,
+                    "registry": null
+                },
+                {
+                    "name": "quickcheck",
+                    "source": "registry+https://github.com/rust-lang/crates.io-index",
+                    "req": "^1.0.3",
+                    "kind": "dev",
+                    "rename": null,
+                    "optional": false,
+                    "uses_default_features": false,
+                    "features": [],
+                    "target": null,
+                    "registry": null
+                }
+            ],
+            "targets": [
+                {
+                    "kind": [
+                        "lib"
+                    ],
+                    "crate_types": [
+                        "lib"
+                    ],
+                    "name": "memchr",
+                    "src_path": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/memchr-2.5.0/src/lib.rs",
+                    "edition": "2018",
+                    "doc": true,
+                    "doctest": true,
+                    "test": true
+                },
+                {
+                    "kind": [
+                        "custom-build"
+                    ],
+                    "crate_types": [
+                        "bin"
+                    ],
+                    "name": "build-script-build",
+                    "src_path": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/memchr-2.5.0/build.rs",
+                    "edition": "2018",
+                    "doc": false,
+                    "doctest": false,
+                    "test": false
+                }
+            ],
+            "features": {
+                "compiler_builtins": [
+                    "dep:compiler_builtins"
+                ],
+                "core": [
+                    "dep:core"
+                ],
+                "default": [
+                    "std"
+                ],
+                "libc": [
+                    "dep:libc"
+                ],
+                "rustc-dep-of-std": [
+                    "core",
+                    "compiler_builtins"
+                ],
+                "std": [],
+                "use_std": [
+                    "std"
+                ]
+            },
+            "manifest_path": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/memchr-2.5.0/Cargo.toml",
+            "metadata": null,
+            "publish": null,
+            "authors": [
+                "Andrew Gallant <jamslam@gmail.com>",
+                "bluss"
+            ],
+            "categories": [],
+            "keywords": [
+                "memchr",
+                "char",
+                "scan",
+                "strchr",
+                "string"
+            ],
+            "readme": "README.md",
+            "repository": "https://github.com/BurntSushi/memchr",
+            "homepage": "https://github.com/BurntSushi/memchr",
+            "documentation": "https://docs.rs/memchr/",
+            "edition": "2018",
+            "links": null,
+            "default_run": null,
+            "rust_version": null
+        },
+        {
+            "name": "ra-playground",
+            "version": "0.1.0",
+            "id": "ra-playground 0.1.0 (path+file:///$ROOT$ra-playground)",
+            "license": null,
+            "license_file": null,
+            "description": null,
+            "source": null,
+            "dependencies": [
+                {
+                    "name": "la-arena",
+                    "source": "registry+https://github.com/rust-lang/crates.io-index",
+                    "req": "*",
+                    "kind": null,
+                    "rename": null,
+                    "optional": false,
+                    "uses_default_features": true,
+                    "features": [],
+                    "target": null,
+                    "registry": null
+                },
+                {
+                    "name": "ra-playground",
+                    "source": null,
+                    "req": "*",
+                    "kind": "dev",
+                    "rename": null,
+                    "optional": false,
+                    "uses_default_features": true,
+                    "features": [],
+                    "target": null,
+                    "registry": null,
+                    "path": "$ROOT$ra-playground"
+                },
+                {
+                    "name": "regex",
+                    "source": "registry+https://github.com/rust-lang/crates.io-index",
+                    "req": "*",
+                    "kind": "dev",
+                    "rename": null,
+                    "optional": false,
+                    "uses_default_features": true,
+                    "features": [],
+                    "target": null,
+                    "registry": null
+                }
+            ],
+            "targets": [
+                {
+                    "kind": [
+                        "lib"
+                    ],
+                    "crate_types": [
+                        "lib"
+                    ],
+                    "name": "ra-playground",
+                    "src_path": "$ROOT$ra-playground/src/lib.rs",
+                    "edition": "2021",
+                    "doc": true,
+                    "doctest": true,
+                    "test": true
+                },
+                {
+                    "kind": [
+                        "bin"
+                    ],
+                    "crate_types": [
+                        "bin"
+                    ],
+                    "name": "ra-playground",
+                    "src_path": "$ROOT$ra-playground/src/main.rs",
+                    "edition": "2021",
+                    "doc": true,
+                    "doctest": false,
+                    "test": true
+                }
+            ],
+            "features": {},
+            "manifest_path": "$ROOT$ra-playground/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": "regex",
+            "version": "1.7.3",
+            "id": "regex 1.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
+            "license": "MIT OR Apache-2.0",
+            "license_file": null,
+            "description": "An implementation of regular expressions for Rust. This implementation uses\nfinite automata and guarantees linear time matching on all inputs.\n",
+            "source": "registry+https://github.com/rust-lang/crates.io-index",
+            "dependencies": [
+                {
+                    "name": "aho-corasick",
+                    "source": "registry+https://github.com/rust-lang/crates.io-index",
+                    "req": "^0.7.18",
+                    "kind": null,
+                    "rename": null,
+                    "optional": true,
+                    "uses_default_features": true,
+                    "features": [],
+                    "target": null,
+                    "registry": null
+                },
+                {
+                    "name": "memchr",
+                    "source": "registry+https://github.com/rust-lang/crates.io-index",
+                    "req": "^2.4.0",
+                    "kind": null,
+                    "rename": null,
+                    "optional": true,
+                    "uses_default_features": true,
+                    "features": [],
+                    "target": null,
+                    "registry": null
+                },
+                {
+                    "name": "regex-syntax",
+                    "source": "registry+https://github.com/rust-lang/crates.io-index",
+                    "req": "^0.6.29",
+                    "kind": null,
+                    "rename": null,
+                    "optional": false,
+                    "uses_default_features": false,
+                    "features": [],
+                    "target": null,
+                    "registry": null
+                },
+                {
+                    "name": "lazy_static",
+                    "source": "registry+https://github.com/rust-lang/crates.io-index",
+                    "req": "^1",
+                    "kind": "dev",
+                    "rename": null,
+                    "optional": false,
+                    "uses_default_features": true,
+                    "features": [],
+                    "target": null,
+                    "registry": null
+                },
+                {
+                    "name": "quickcheck",
+                    "source": "registry+https://github.com/rust-lang/crates.io-index",
+                    "req": "^1.0.3",
+                    "kind": "dev",
+                    "rename": null,
+                    "optional": false,
+                    "uses_default_features": false,
+                    "features": [],
+                    "target": null,
+                    "registry": null
+                },
+                {
+                    "name": "rand",
+                    "source": "registry+https://github.com/rust-lang/crates.io-index",
+                    "req": "^0.8.3",
+                    "kind": "dev",
+                    "rename": null,
+                    "optional": false,
+                    "uses_default_features": false,
+                    "features": [
+                        "getrandom",
+                        "small_rng"
+                    ],
+                    "target": null,
+                    "registry": null
+                }
+            ],
+            "targets": [
+                {
+                    "kind": [
+                        "lib"
+                    ],
+                    "crate_types": [
+                        "lib"
+                    ],
+                    "name": "regex",
+                    "src_path": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/regex-1.7.3/src/lib.rs",
+                    "edition": "2018",
+                    "doc": true,
+                    "doctest": false,
+                    "test": true
+                },
+                {
+                    "kind": [
+                        "example"
+                    ],
+                    "crate_types": [
+                        "bin"
+                    ],
+                    "name": "shootout-regex-dna-bytes",
+                    "src_path": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/regex-1.7.3/examples/shootout-regex-dna-bytes.rs",
+                    "edition": "2018",
+                    "doc": false,
+                    "doctest": false,
+                    "test": false
+                },
+                {
+                    "kind": [
+                        "example"
+                    ],
+                    "crate_types": [
+                        "bin"
+                    ],
+                    "name": "shootout-regex-dna-cheat",
+                    "src_path": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/regex-1.7.3/examples/shootout-regex-dna-cheat.rs",
+                    "edition": "2018",
+                    "doc": false,
+                    "doctest": false,
+                    "test": false
+                },
+                {
+                    "kind": [
+                        "example"
+                    ],
+                    "crate_types": [
+                        "bin"
+                    ],
+                    "name": "shootout-regex-dna-replace",
+                    "src_path": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/regex-1.7.3/examples/shootout-regex-dna-replace.rs",
+                    "edition": "2018",
+                    "doc": false,
+                    "doctest": false,
+                    "test": false
+                },
+                {
+                    "kind": [
+                        "example"
+                    ],
+                    "crate_types": [
+                        "bin"
+                    ],
+                    "name": "shootout-regex-dna-single-cheat",
+                    "src_path": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/regex-1.7.3/examples/shootout-regex-dna-single-cheat.rs",
+                    "edition": "2018",
+                    "doc": false,
+                    "doctest": false,
+                    "test": false
+                },
+                {
+                    "kind": [
+                        "example"
+                    ],
+                    "crate_types": [
+                        "bin"
+                    ],
+                    "name": "shootout-regex-dna-single",
+                    "src_path": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/regex-1.7.3/examples/shootout-regex-dna-single.rs",
+                    "edition": "2018",
+                    "doc": false,
+                    "doctest": false,
+                    "test": false
+                },
+                {
+                    "kind": [
+                        "example"
+                    ],
+                    "crate_types": [
+                        "bin"
+                    ],
+                    "name": "shootout-regex-dna",
+                    "src_path": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/regex-1.7.3/examples/shootout-regex-dna.rs",
+                    "edition": "2018",
+                    "doc": false,
+                    "doctest": false,
+                    "test": false
+                },
+                {
+                    "kind": [
+                        "test"
+                    ],
+                    "crate_types": [
+                        "bin"
+                    ],
+                    "name": "default",
+                    "src_path": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/regex-1.7.3/tests/test_default.rs",
+                    "edition": "2018",
+                    "doc": false,
+                    "doctest": false,
+                    "test": true
+                },
+                {
+                    "kind": [
+                        "test"
+                    ],
+                    "crate_types": [
+                        "bin"
+                    ],
+                    "name": "default-bytes",
+                    "src_path": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/regex-1.7.3/tests/test_default_bytes.rs",
+                    "edition": "2018",
+                    "doc": false,
+                    "doctest": false,
+                    "test": true
+                },
+                {
+                    "kind": [
+                        "test"
+                    ],
+                    "crate_types": [
+                        "bin"
+                    ],
+                    "name": "nfa",
+                    "src_path": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/regex-1.7.3/tests/test_nfa.rs",
+                    "edition": "2018",
+                    "doc": false,
+                    "doctest": false,
+                    "test": true
+                },
+                {
+                    "kind": [
+                        "test"
+                    ],
+                    "crate_types": [
+                        "bin"
+                    ],
+                    "name": "nfa-utf8bytes",
+                    "src_path": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/regex-1.7.3/tests/test_nfa_utf8bytes.rs",
+                    "edition": "2018",
+                    "doc": false,
+                    "doctest": false,
+                    "test": true
+                },
+                {
+                    "kind": [
+                        "test"
+                    ],
+                    "crate_types": [
+                        "bin"
+                    ],
+                    "name": "nfa-bytes",
+                    "src_path": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/regex-1.7.3/tests/test_nfa_bytes.rs",
+                    "edition": "2018",
+                    "doc": false,
+                    "doctest": false,
+                    "test": true
+                },
+                {
+                    "kind": [
+                        "test"
+                    ],
+                    "crate_types": [
+                        "bin"
+                    ],
+                    "name": "backtrack",
+                    "src_path": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/regex-1.7.3/tests/test_backtrack.rs",
+                    "edition": "2018",
+                    "doc": false,
+                    "doctest": false,
+                    "test": true
+                },
+                {
+                    "kind": [
+                        "test"
+                    ],
+                    "crate_types": [
+                        "bin"
+                    ],
+                    "name": "backtrack-utf8bytes",
+                    "src_path": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/regex-1.7.3/tests/test_backtrack_utf8bytes.rs",
+                    "edition": "2018",
+                    "doc": false,
+                    "doctest": false,
+                    "test": true
+                },
+                {
+                    "kind": [
+                        "test"
+                    ],
+                    "crate_types": [
+                        "bin"
+                    ],
+                    "name": "backtrack-bytes",
+                    "src_path": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/regex-1.7.3/tests/test_backtrack_bytes.rs",
+                    "edition": "2018",
+                    "doc": false,
+                    "doctest": false,
+                    "test": true
+                },
+                {
+                    "kind": [
+                        "test"
+                    ],
+                    "crate_types": [
+                        "bin"
+                    ],
+                    "name": "crates-regex",
+                    "src_path": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/regex-1.7.3/tests/test_crates_regex.rs",
+                    "edition": "2018",
+                    "doc": false,
+                    "doctest": false,
+                    "test": true
+                }
+            ],
+            "features": {
+                "aho-corasick": [
+                    "dep:aho-corasick"
+                ],
+                "default": [
+                    "std",
+                    "perf",
+                    "unicode",
+                    "regex-syntax/default"
+                ],
+                "memchr": [
+                    "dep:memchr"
+                ],
+                "pattern": [],
+                "perf": [
+                    "perf-cache",
+                    "perf-dfa",
+                    "perf-inline",
+                    "perf-literal"
+                ],
+                "perf-cache": [],
+                "perf-dfa": [],
+                "perf-inline": [],
+                "perf-literal": [
+                    "aho-corasick",
+                    "memchr"
+                ],
+                "std": [],
+                "unicode": [
+                    "unicode-age",
+                    "unicode-bool",
+                    "unicode-case",
+                    "unicode-gencat",
+                    "unicode-perl",
+                    "unicode-script",
+                    "unicode-segment",
+                    "regex-syntax/unicode"
+                ],
+                "unicode-age": [
+                    "regex-syntax/unicode-age"
+                ],
+                "unicode-bool": [
+                    "regex-syntax/unicode-bool"
+                ],
+                "unicode-case": [
+                    "regex-syntax/unicode-case"
+                ],
+                "unicode-gencat": [
+                    "regex-syntax/unicode-gencat"
+                ],
+                "unicode-perl": [
+                    "regex-syntax/unicode-perl"
+                ],
+                "unicode-script": [
+                    "regex-syntax/unicode-script"
+                ],
+                "unicode-segment": [
+                    "regex-syntax/unicode-segment"
+                ],
+                "unstable": [
+                    "pattern"
+                ],
+                "use_std": [
+                    "std"
+                ]
+            },
+            "manifest_path": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/regex-1.7.3/Cargo.toml",
+            "metadata": null,
+            "publish": null,
+            "authors": [
+                "The Rust Project Developers"
+            ],
+            "categories": [
+                "text-processing"
+            ],
+            "keywords": [],
+            "readme": "README.md",
+            "repository": "https://github.com/rust-lang/regex",
+            "homepage": "https://github.com/rust-lang/regex",
+            "documentation": "https://docs.rs/regex",
+            "edition": "2018",
+            "links": null,
+            "default_run": null,
+            "rust_version": null
+        },
+        {
+            "name": "regex-syntax",
+            "version": "0.6.29",
+            "id": "regex-syntax 0.6.29 (registry+https://github.com/rust-lang/crates.io-index)",
+            "license": "MIT OR Apache-2.0",
+            "license_file": null,
+            "description": "A regular expression parser.",
+            "source": "registry+https://github.com/rust-lang/crates.io-index",
+            "dependencies": [],
+            "targets": [
+                {
+                    "kind": [
+                        "lib"
+                    ],
+                    "crate_types": [
+                        "lib"
+                    ],
+                    "name": "regex-syntax",
+                    "src_path": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/regex-syntax-0.6.29/src/lib.rs",
+                    "edition": "2018",
+                    "doc": true,
+                    "doctest": true,
+                    "test": true
+                },
+                {
+                    "kind": [
+                        "bench"
+                    ],
+                    "crate_types": [
+                        "bin"
+                    ],
+                    "name": "bench",
+                    "src_path": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/regex-syntax-0.6.29/benches/bench.rs",
+                    "edition": "2018",
+                    "doc": false,
+                    "doctest": false,
+                    "test": false
+                }
+            ],
+            "features": {
+                "default": [
+                    "unicode"
+                ],
+                "unicode": [
+                    "unicode-age",
+                    "unicode-bool",
+                    "unicode-case",
+                    "unicode-gencat",
+                    "unicode-perl",
+                    "unicode-script",
+                    "unicode-segment"
+                ],
+                "unicode-age": [],
+                "unicode-bool": [],
+                "unicode-case": [],
+                "unicode-gencat": [],
+                "unicode-perl": [],
+                "unicode-script": [],
+                "unicode-segment": []
+            },
+            "manifest_path": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/regex-syntax-0.6.29/Cargo.toml",
+            "metadata": null,
+            "publish": null,
+            "authors": [
+                "The Rust Project Developers"
+            ],
+            "categories": [],
+            "keywords": [],
+            "readme": "README.md",
+            "repository": "https://github.com/rust-lang/regex",
+            "homepage": "https://github.com/rust-lang/regex",
+            "documentation": "https://docs.rs/regex-syntax",
+            "edition": "2018",
+            "links": null,
+            "default_run": null,
+            "rust_version": null
+        }
+    ],
+    "workspace_members": [
+        "ra-playground 0.1.0 (path+file:///$ROOT$ra-playground)"
+    ],
+    "resolve": {
+        "nodes": [
+            {
+                "id": "aho-corasick 0.7.20 (registry+https://github.com/rust-lang/crates.io-index)",
+                "dependencies": [
+                    "memchr 2.5.0 (registry+https://github.com/rust-lang/crates.io-index)"
+                ],
+                "deps": [
+                    {
+                        "name": "memchr",
+                        "pkg": "memchr 2.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
+                        "dep_kinds": [
+                            {
+                                "kind": null,
+                                "target": null
+                            }
+                        ]
+                    }
+                ],
+                "features": [
+                    "default",
+                    "std"
+                ]
+            },
+            {
+                "id": "la-arena 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+                "dependencies": [],
+                "deps": [],
+                "features": []
+            },
+            {
+                "id": "memchr 2.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
+                "dependencies": [],
+                "deps": [],
+                "features": [
+                    "default",
+                    "std"
+                ]
+            },
+            {
+                "id": "ra-playground 0.1.0 (path+file:///$ROOT$ra-playground)",
+                "dependencies": [
+                    "la-arena 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+                    "ra-playground 0.1.0 (path+file:///$ROOT$ra-playground)",
+                    "regex 1.7.3 (registry+https://github.com/rust-lang/crates.io-index)"
+                ],
+                "deps": [
+                    {
+                        "name": "la_arena",
+                        "pkg": "la-arena 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+                        "dep_kinds": [
+                            {
+                                "kind": null,
+                                "target": null
+                            }
+                        ]
+                    },
+                    {
+                        "name": "ra_playground",
+                        "pkg": "ra-playground 0.1.0 (path+file:///$ROOT$ra-playground)",
+                        "dep_kinds": [
+                            {
+                                "kind": "dev",
+                                "target": null
+                            }
+                        ]
+                    },
+                    {
+                        "name": "regex",
+                        "pkg": "regex 1.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
+                        "dep_kinds": [
+                            {
+                                "kind": "dev",
+                                "target": null
+                            }
+                        ]
+                    }
+                ],
+                "features": []
+            },
+            {
+                "id": "regex 1.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
+                "dependencies": [
+                    "aho-corasick 0.7.20 (registry+https://github.com/rust-lang/crates.io-index)",
+                    "memchr 2.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
+                    "regex-syntax 0.6.29 (registry+https://github.com/rust-lang/crates.io-index)"
+                ],
+                "deps": [
+                    {
+                        "name": "aho_corasick",
+                        "pkg": "aho-corasick 0.7.20 (registry+https://github.com/rust-lang/crates.io-index)",
+                        "dep_kinds": [
+                            {
+                                "kind": null,
+                                "target": null
+                            }
+                        ]
+                    },
+                    {
+                        "name": "memchr",
+                        "pkg": "memchr 2.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
+                        "dep_kinds": [
+                            {
+                                "kind": null,
+                                "target": null
+                            }
+                        ]
+                    },
+                    {
+                        "name": "regex_syntax",
+                        "pkg": "regex-syntax 0.6.29 (registry+https://github.com/rust-lang/crates.io-index)",
+                        "dep_kinds": [
+                            {
+                                "kind": null,
+                                "target": null
+                            }
+                        ]
+                    }
+                ],
+                "features": [
+                    "aho-corasick",
+                    "default",
+                    "memchr",
+                    "perf",
+                    "perf-cache",
+                    "perf-dfa",
+                    "perf-inline",
+                    "perf-literal",
+                    "std",
+                    "unicode",
+                    "unicode-age",
+                    "unicode-bool",
+                    "unicode-case",
+                    "unicode-gencat",
+                    "unicode-perl",
+                    "unicode-script",
+                    "unicode-segment"
+                ]
+            },
+            {
+                "id": "regex-syntax 0.6.29 (registry+https://github.com/rust-lang/crates.io-index)",
+                "dependencies": [],
+                "deps": [],
+                "features": [
+                    "default",
+                    "unicode",
+                    "unicode-age",
+                    "unicode-bool",
+                    "unicode-case",
+                    "unicode-gencat",
+                    "unicode-perl",
+                    "unicode-script",
+                    "unicode-segment"
+                ]
+            }
+        ],
+        "root": "ra-playground 0.1.0 (path+file:///$ROOT$ra-playground)"
+    },
+    "target_directory": "$ROOT$ra-playground/target",
+    "version": 1,
+    "workspace_root": "$ROOT$ra-playground",
+    "metadata": null
+}