diff --git a/Cargo.toml b/Cargo.toml index 8e4e84729a..5cfc064b51 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,5 @@ [workspace] members = [ "crates/*" ] -exclude = [ "crates/rowan"] [profile.release] debug = true diff --git a/crates/ra_db/src/input.rs b/crates/ra_db/src/input.rs index 7d9faa43c2..ac144b9918 100644 --- a/crates/ra_db/src/input.rs +++ b/crates/ra_db/src/input.rs @@ -1,7 +1,7 @@ use std::sync::Arc; -use rustc_hash::FxHashMap; -use rustc_hash::FxHashSet; +use rustc_hash::{FxHashSet, FxHashMap}; +use ra_syntax::SmolStr; use salsa; use crate::file_resolver::FileResolverImp; @@ -20,25 +20,32 @@ pub struct CrateGraph { #[derive(Debug, Clone, PartialEq, Eq)] struct CrateData { file_id: FileId, - deps: Vec, + dependencies: Vec, } impl CrateData { fn new(file_id: FileId) -> CrateData { CrateData { file_id, - deps: Vec::new(), + dependencies: Vec::new(), } } - fn add_dep(&mut self, dep: CrateId) { - self.deps.push(Dependency { crate_: dep }) + fn add_dep(&mut self, name: SmolStr, crate_id: CrateId) { + self.dependencies.push(Dependency { name, crate_id }) } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct Dependency { - crate_: CrateId, + pub crate_id: CrateId, + pub name: SmolStr, +} + +impl Dependency { + pub fn crate_id(&self) -> CrateId { + self.crate_id + } } impl CrateGraph { @@ -48,8 +55,11 @@ impl CrateGraph { assert!(prev.is_none()); crate_id } - pub fn add_dep(&mut self, from: CrateId, to: CrateId) { - self.arena.get_mut(&from).unwrap().add_dep(to) + //FIXME: check that we don't have cycles here. + // Just a simple depth first search from `to` should work, + // the graph is small. + pub fn add_dep(&mut self, from: CrateId, name: SmolStr, to: CrateId) { + self.arena.get_mut(&from).unwrap().add_dep(name, to) } pub fn crate_root(&self, crate_id: CrateId) -> FileId { self.arena[&crate_id].file_id @@ -61,6 +71,12 @@ impl CrateGraph { .find(|(_crate_id, data)| data.file_id == file_id)?; Some(crate_id) } + pub fn dependencies<'a>( + &'a self, + crate_id: CrateId, + ) -> impl Iterator + 'a { + self.arena[&crate_id].dependencies.iter() + } } salsa::query_group! { diff --git a/crates/ra_db/src/mock.rs b/crates/ra_db/src/mock.rs index 2840f96558..2f7551597f 100644 --- a/crates/ra_db/src/mock.rs +++ b/crates/ra_db/src/mock.rs @@ -5,7 +5,7 @@ use relative_path::{RelativePath, RelativePathBuf}; use crate::{FileId, FileResolver, SourceRoot, FileResolverImp}; -#[derive(Default, Debug)] +#[derive(Default, Debug, Clone)] pub struct FileMap(Vec<(FileId, RelativePathBuf)>); impl FileMap { @@ -28,6 +28,11 @@ impl FileMap { self.iter().map(|(id, _)| id).collect() } + pub fn file_id(&self, path: &str) -> FileId { + assert!(path.starts_with('/')); + self.iter().find(|(_, p)| p == &path[1..]).unwrap().0 + } + fn iter<'a>(&'a self) -> impl Iterator + 'a { self.0 .iter() diff --git a/crates/ra_hir/src/krate.rs b/crates/ra_hir/src/krate.rs new file mode 100644 index 0000000000..1196dcef17 --- /dev/null +++ b/crates/ra_hir/src/krate.rs @@ -0,0 +1,48 @@ +use ra_syntax::SmolStr; +pub use ra_db::CrateId; + +use crate::{HirDatabase, Module, Cancelable}; + +/// hir::Crate describes a single crate. It's the main inteface with which +/// crate's dependencies interact. Mostly, it should be just a proxy for the +/// root module. +#[derive(Debug)] +pub struct Crate { + crate_id: CrateId, +} + +#[derive(Debug)] +pub struct CrateDependency { + pub krate: Crate, + pub name: SmolStr, +} + +impl Crate { + pub(crate) fn new(crate_id: CrateId) -> Crate { + Crate { crate_id } + } + pub fn dependencies(&self, db: &impl HirDatabase) -> Vec { + let crate_graph = db.crate_graph(); + crate_graph + .dependencies(self.crate_id) + .map(|dep| { + let krate = Crate::new(dep.crate_id()); + let name = dep.name.clone(); + CrateDependency { krate, name } + }) + .collect() + } + pub fn root_module(&self, db: &impl HirDatabase) -> Cancelable> { + let crate_graph = db.crate_graph(); + let file_id = crate_graph.crate_root(self.crate_id); + let source_root_id = db.file_source_root(file_id); + let module_tree = db.module_tree(source_root_id)?; + // FIXME: teach module tree about crate roots instead of guessing + let (module_id, _) = ctry!(module_tree + .modules_with_sources() + .find(|(_, src)| src.file_id() == file_id)); + + let module = Module::new(db, source_root_id, module_id)?; + Ok(Some(module)) + } +} diff --git a/crates/ra_hir/src/lib.rs b/crates/ra_hir/src/lib.rs index f50b922af2..578fde2595 100644 --- a/crates/ra_hir/src/lib.rs +++ b/crates/ra_hir/src/lib.rs @@ -18,12 +18,14 @@ pub mod db; #[cfg(test)] mod mock; mod query_definitions; -mod function; -mod module; mod path; mod arena; pub mod source_binder; +mod krate; +mod module; +mod function; + use std::ops::Index; use ra_syntax::{SyntaxNodeRef, SyntaxNode}; @@ -36,6 +38,7 @@ use crate::{ pub use self::{ path::{Path, PathKind}, + krate::Crate, module::{Module, ModuleId, Problem, nameres::ItemMap}, function::{Function, FnScopes}, }; diff --git a/crates/ra_hir/src/mock.rs b/crates/ra_hir/src/mock.rs index e855df11d1..b7193c4f3f 100644 --- a/crates/ra_hir/src/mock.rs +++ b/crates/ra_hir/src/mock.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use parking_lot::Mutex; use salsa::{self, Database}; -use ra_db::{LocationIntener, BaseDatabase, FilePosition, mock::FileMap, FileId, WORKSPACE}; +use ra_db::{LocationIntener, BaseDatabase, FilePosition, mock::FileMap, FileId, WORKSPACE, CrateGraph}; use relative_path::RelativePathBuf; use test_utils::{parse_fixture, CURSOR_MARKER, extract_offset}; @@ -16,7 +16,24 @@ pub(crate) struct MockDatabase { } impl MockDatabase { + pub(crate) fn with_files(fixture: &str) -> (MockDatabase, FileMap) { + let (db, file_map, position) = MockDatabase::from_fixture(fixture); + assert!(position.is_none()); + (db, file_map) + } + pub(crate) fn with_position(fixture: &str) -> (MockDatabase, FilePosition) { + let (db, _, position) = MockDatabase::from_fixture(fixture); + let position = position.expect("expected a marker ( <|> )"); + (db, position) + } + + pub(crate) fn set_crate_graph(&mut self, crate_graph: CrateGraph) { + self.query_mut(ra_db::CrateGraphQuery) + .set((), Arc::new(crate_graph)); + } + + fn from_fixture(fixture: &str) -> (MockDatabase, FileMap, Option) { let mut db = MockDatabase::default(); let mut position = None; @@ -32,11 +49,10 @@ impl MockDatabase { db.add_file(&mut file_map, &entry.meta, &entry.text); } } - let position = position.expect("expected a marker (<|>)"); - let source_root = file_map.into_source_root(); + let source_root = file_map.clone().into_source_root(); db.query_mut(ra_db::SourceRootQuery) .set(WORKSPACE, Arc::new(source_root)); - (db, position) + (db, file_map, position) } fn add_file(&mut self, file_map: &mut FileMap, path: &str, text: &str) -> FileId { diff --git a/crates/ra_hir/src/module.rs b/crates/ra_hir/src/module.rs index e7a49f83ab..c6bb76d56d 100644 --- a/crates/ra_hir/src/module.rs +++ b/crates/ra_hir/src/module.rs @@ -12,7 +12,7 @@ use ra_db::{SourceRootId, FileId, Cancelable}; use relative_path::RelativePathBuf; use crate::{ - DefKind, DefLoc, DefId, Path, PathKind, HirDatabase, SourceItemId, SourceFileItemId, + DefKind, DefLoc, DefId, Path, PathKind, HirDatabase, SourceItemId, SourceFileItemId, Crate, arena::{Arena, Id}, }; @@ -64,6 +64,15 @@ impl Module { }) } + /// Returns the crate this module is part of. + pub fn krate(&self, db: &impl HirDatabase) -> Option { + let root_id = self.module_id.crate_root(&self.tree); + let file_id = root_id.source(&self.tree).file_id(); + let crate_graph = db.crate_graph(); + let crate_id = crate_graph.crate_id_for_crate_root(file_id)?; + Some(Crate::new(crate_id)) + } + /// The root of the tree this module is part of pub fn crate_root(&self) -> Module { let root_id = self.module_id.crate_root(&self.tree); diff --git a/crates/ra_hir/src/module/nameres.rs b/crates/ra_hir/src/module/nameres.rs index 6511359d08..9afeade9e5 100644 --- a/crates/ra_hir/src/module/nameres.rs +++ b/crates/ra_hir/src/module/nameres.rs @@ -31,7 +31,7 @@ use crate::{ DefId, DefLoc, DefKind, SourceItemId, SourceFileItemId, SourceFileItems, Path, PathKind, - HirDatabase, + HirDatabase, Crate, module::{ModuleId, ModuleTree}, }; @@ -200,34 +200,63 @@ impl ModuleItem { } pub(crate) struct Resolver<'a, DB> { - pub(crate) db: &'a DB, - pub(crate) input: &'a FxHashMap>, - pub(crate) source_root: SourceRootId, - pub(crate) module_tree: Arc, - pub(crate) result: ItemMap, + db: &'a DB, + input: &'a FxHashMap>, + source_root: SourceRootId, + module_tree: Arc, + result: ItemMap, } impl<'a, DB> Resolver<'a, DB> where DB: HirDatabase, { + pub(crate) fn new( + db: &'a DB, + input: &'a FxHashMap>, + source_root: SourceRootId, + module_tree: Arc, + ) -> Resolver<'a, DB> { + Resolver { + db, + input, + source_root, + module_tree, + result: ItemMap::default(), + } + } + pub(crate) fn resolve(mut self) -> Cancelable { for (&module_id, items) in self.input.iter() { - self.populate_module(module_id, items) + self.populate_module(module_id, items)?; } for &module_id in self.input.keys() { self.db.check_canceled()?; - self.resolve_imports(module_id); + self.resolve_imports(module_id)?; } Ok(self.result) } - fn populate_module(&mut self, module_id: ModuleId, input: &InputModuleItems) { + fn populate_module(&mut self, module_id: ModuleId, input: &InputModuleItems) -> Cancelable<()> { let file_id = module_id.source(&self.module_tree).file_id(); let mut module_items = ModuleScope::default(); + // Populate extern crates prelude + { + let root_id = module_id.crate_root(&self.module_tree); + let file_id = root_id.source(&self.module_tree).file_id(); + let crate_graph = self.db.crate_graph(); + if let Some(crate_id) = crate_graph.crate_id_for_crate_root(file_id) { + let krate = Crate::new(crate_id); + for dep in krate.dependencies(self.db) { + if let Some(module) = dep.krate.root_module(self.db)? { + self.add_module_item(&mut module_items, dep.name, module.module_id); + } + } + }; + } for import in input.imports.iter() { if let Some(name) = import.path.segments.iter().last() { if let ImportKind::Named(import) = import.kind { @@ -241,10 +270,9 @@ where } } } - + // Populate explicitelly declared items, except modules for item in input.items.iter() { if item.kind == MODULE { - // handle submodules separatelly continue; } let def_loc = DefLoc { @@ -264,45 +292,50 @@ where module_items.items.insert(item.name.clone(), resolution); } + // Populate modules for (name, module_id) in module_id.children(&self.module_tree) { - let def_loc = DefLoc { - kind: DefKind::Module, - source_root_id: self.source_root, - module_id, - source_item_id: module_id.source(&self.module_tree).0, - }; - let def_id = def_loc.id(self.db); - let resolution = Resolution { - def_id: Some(def_id), - import: None, - }; - module_items.items.insert(name, resolution); + self.add_module_item(&mut module_items, name, module_id); } self.result.per_module.insert(module_id, module_items); + Ok(()) } - fn resolve_imports(&mut self, module_id: ModuleId) { + fn add_module_item(&self, module_items: &mut ModuleScope, name: SmolStr, module_id: ModuleId) { + let def_loc = DefLoc { + kind: DefKind::Module, + source_root_id: self.source_root, + module_id, + source_item_id: module_id.source(&self.module_tree).0, + }; + let def_id = def_loc.id(self.db); + let resolution = Resolution { + def_id: Some(def_id), + import: None, + }; + module_items.items.insert(name, resolution); + } + + fn resolve_imports(&mut self, module_id: ModuleId) -> Cancelable<()> { for import in self.input[&module_id].imports.iter() { - self.resolve_import(module_id, import); + self.resolve_import(module_id, import)?; } + Ok(()) } - fn resolve_import(&mut self, module_id: ModuleId, import: &Import) { + fn resolve_import(&mut self, module_id: ModuleId, import: &Import) -> Cancelable<()> { let ptr = match import.kind { - ImportKind::Glob => return, + ImportKind::Glob => return Ok(()), ImportKind::Named(ptr) => ptr, }; let mut curr = match import.path.kind { - // TODO: handle extern crates - PathKind::Plain => return, - PathKind::Self_ => module_id, + PathKind::Plain | PathKind::Self_ => module_id, PathKind::Super => { match module_id.parent(&self.module_tree) { Some(it) => it, // TODO: error - None => return, + None => return Ok(()), } } PathKind::Crate => module_id.crate_root(&self.module_tree), @@ -312,10 +345,10 @@ where let is_last = i == import.path.segments.len() - 1; let def_id = match self.result.per_module[&curr].items.get(name) { - None => return, + None => return Ok(()), Some(res) => match res.def_id { Some(it) => it, - None => return, + None => return Ok(()), }, }; @@ -326,7 +359,7 @@ where module_id, .. } => module_id, - _ => return, + _ => return Ok(()), } } else { self.update(module_id, |items| { @@ -338,6 +371,7 @@ where }) } } + Ok(()) } fn update(&mut self, module_id: ModuleId, f: impl FnOnce(&mut ModuleScope)) { @@ -347,99 +381,4 @@ where } #[cfg(test)] -mod tests { - use std::sync::Arc; - - use salsa::Database; - use ra_db::FilesDatabase; - use ra_syntax::SmolStr; - - use crate::{ - self as hir, - db::HirDatabase, - mock::MockDatabase, -}; - - fn item_map(fixture: &str) -> (Arc, hir::ModuleId) { - let (db, pos) = MockDatabase::with_position(fixture); - let source_root = db.file_source_root(pos.file_id); - let module = hir::source_binder::module_from_position(&db, pos) - .unwrap() - .unwrap(); - let module_id = module.module_id; - (db.item_map(source_root).unwrap(), module_id) - } - - #[test] - fn test_item_map() { - let (item_map, module_id) = item_map( - " - //- /lib.rs - mod foo; - - use crate::foo::bar::Baz; - <|> - - //- /foo/mod.rs - pub mod bar; - - //- /foo/bar.rs - pub struct Baz; - ", - ); - let name = SmolStr::from("Baz"); - let resolution = &item_map.per_module[&module_id].items[&name]; - assert!(resolution.def_id.is_some()); - } - - #[test] - fn typing_inside_a_function_should_not_invalidate_item_map() { - let (mut db, pos) = MockDatabase::with_position( - " - //- /lib.rs - mod foo;<|> - - use crate::foo::bar::Baz; - - fn foo() -> i32 { - 1 + 1 - } - //- /foo/mod.rs - pub mod bar; - - //- /foo/bar.rs - pub struct Baz; - ", - ); - let source_root = db.file_source_root(pos.file_id); - { - let events = db.log_executed(|| { - db.item_map(source_root).unwrap(); - }); - assert!(format!("{:?}", events).contains("item_map")) - } - - let new_text = " - mod foo; - - use crate::foo::bar::Baz; - - fn foo() -> i32 { 92 } - " - .to_string(); - - db.query_mut(ra_db::FileTextQuery) - .set(pos.file_id, Arc::new(new_text)); - - { - let events = db.log_executed(|| { - db.item_map(source_root).unwrap(); - }); - assert!( - !format!("{:?}", events).contains("_item_map"), - "{:#?}", - events - ) - } - } -} +mod tests; diff --git a/crates/ra_hir/src/module/nameres/tests.rs b/crates/ra_hir/src/module/nameres/tests.rs new file mode 100644 index 0000000000..9ddc32dcd9 --- /dev/null +++ b/crates/ra_hir/src/module/nameres/tests.rs @@ -0,0 +1,127 @@ +use std::sync::Arc; + +use salsa::Database; +use ra_db::{FilesDatabase, CrateGraph}; +use ra_syntax::SmolStr; + +use crate::{ + self as hir, + db::HirDatabase, + mock::MockDatabase, +}; + +fn item_map(fixture: &str) -> (Arc, hir::ModuleId) { + let (db, pos) = MockDatabase::with_position(fixture); + let source_root = db.file_source_root(pos.file_id); + let module = hir::source_binder::module_from_position(&db, pos) + .unwrap() + .unwrap(); + let module_id = module.module_id; + (db.item_map(source_root).unwrap(), module_id) +} + +#[test] +fn item_map_smoke_test() { + let (item_map, module_id) = item_map( + " + //- /lib.rs + mod foo; + + use crate::foo::bar::Baz; + <|> + + //- /foo/mod.rs + pub mod bar; + + //- /foo/bar.rs + pub struct Baz; + ", + ); + let name = SmolStr::from("Baz"); + let resolution = &item_map.per_module[&module_id].items[&name]; + assert!(resolution.def_id.is_some()); +} + +#[test] +fn item_map_across_crates() { + let (mut db, files) = MockDatabase::with_files( + " + //- /main.rs + use test_crate::Baz; + + //- /lib.rs + pub struct Baz; + ", + ); + let main_id = files.file_id("/main.rs"); + let lib_id = files.file_id("/lib.rs"); + + let mut crate_graph = CrateGraph::default(); + let main_crate = crate_graph.add_crate_root(main_id); + let lib_crate = crate_graph.add_crate_root(lib_id); + crate_graph.add_dep(main_crate, "test_crate".into(), lib_crate); + + db.set_crate_graph(crate_graph); + + let source_root = db.file_source_root(main_id); + let module = hir::source_binder::module_from_file_id(&db, main_id) + .unwrap() + .unwrap(); + let module_id = module.module_id; + let item_map = db.item_map(source_root).unwrap(); + + let name = SmolStr::from("Baz"); + let resolution = &item_map.per_module[&module_id].items[&name]; + assert!(resolution.def_id.is_some()); +} + +#[test] +fn typing_inside_a_function_should_not_invalidate_item_map() { + let (mut db, pos) = MockDatabase::with_position( + " + //- /lib.rs + mod foo;<|> + + use crate::foo::bar::Baz; + + fn foo() -> i32 { + 1 + 1 + } + //- /foo/mod.rs + pub mod bar; + + //- /foo/bar.rs + pub struct Baz; + ", + ); + let source_root = db.file_source_root(pos.file_id); + { + let events = db.log_executed(|| { + db.item_map(source_root).unwrap(); + }); + assert!(format!("{:?}", events).contains("item_map")) + } + + let new_text = " + mod foo; + + use crate::foo::bar::Baz; + + fn foo() -> i32 { 92 } + " + .to_string(); + + db.query_mut(ra_db::FileTextQuery) + .set(pos.file_id, Arc::new(new_text)); + + { + let events = db.log_executed(|| { + db.item_map(source_root).unwrap(); + }); + assert!( + !format!("{:?}", events).contains("_item_map"), + "{:#?}", + events + ) + } +} diff --git a/crates/ra_hir/src/query_definitions.rs b/crates/ra_hir/src/query_definitions.rs index bb4457d072..37c4f9e4fc 100644 --- a/crates/ra_hir/src/query_definitions.rs +++ b/crates/ra_hir/src/query_definitions.rs @@ -141,13 +141,8 @@ pub(super) fn item_map( Ok((id, items)) }) .collect::>>()?; - let resolver = Resolver { - db: db, - input: &input, - source_root, - module_tree, - result: ItemMap::default(), - }; + + let resolver = Resolver::new(db, &input, source_root, module_tree); let res = resolver.resolve()?; let elapsed = start.elapsed(); log::info!("item_map: {:?}", elapsed); diff --git a/crates/ra_lsp_server/src/project_model.rs b/crates/ra_lsp_server/src/project_model.rs index 3305d468aa..cb91ada90a 100644 --- a/crates/ra_lsp_server/src/project_model.rs +++ b/crates/ra_lsp_server/src/project_model.rs @@ -1,6 +1,5 @@ use std::path::{Path, PathBuf}; -use serde_derive::Serialize; use cargo_metadata::{metadata_run, CargoOpt}; use ra_syntax::SmolStr; use rustc_hash::{FxHashMap, FxHashSet}; @@ -11,15 +10,22 @@ use crate::{ thread_watcher::{ThreadWatcher, Worker}, }; +/// `CargoWorksapce` represents the logical structure of, well, a Cargo +/// workspace. It pretty closely mirrors `cargo metadata` output. +/// +/// Note that internally, rust analyzer uses a differnet structure: +/// `CrateGraph`. `CrateGraph` is lower-level: it knows only about the crates, +/// while this knows about `Pacakges` & `Targets`: purely cargo-related +/// concepts. #[derive(Debug, Clone)] pub struct CargoWorkspace { packages: Vec, targets: Vec, } -#[derive(Clone, Copy, Debug, Serialize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct Package(usize); -#[derive(Clone, Copy, Debug, Serialize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct Target(usize); #[derive(Debug, Clone)] @@ -28,6 +34,13 @@ struct PackageData { manifest: PathBuf, targets: Vec, is_member: bool, + dependencies: Vec, +} + +#[derive(Debug, Clone)] +pub struct PackageDependency { + pub pkg: Package, + pub name: SmolStr, } #[derive(Debug, Clone)] @@ -61,6 +74,12 @@ impl Package { pub fn is_member(self, ws: &CargoWorkspace) -> bool { ws.pkg(self).is_member } + pub fn dependencies<'a>( + self, + ws: &'a CargoWorkspace, + ) -> impl Iterator + 'a { + ws.pkg(self).dependencies.iter() + } } impl Target { @@ -106,6 +125,7 @@ impl CargoWorkspace { manifest: PathBuf::from(meta_pkg.manifest_path), targets: Vec::new(), is_member, + dependencies: Vec::new(), }; for meta_tgt in meta_pkg.targets { let tgt = Target(targets.len()); @@ -119,6 +139,16 @@ impl CargoWorkspace { } packages.push(pkg_data) } + let resolve = meta.resolve.expect("metadata executed with deps"); + for node in resolve.nodes { + let source = pkg_by_id[&node.id]; + for id in node.dependencies { + let target = pkg_by_id[&id]; + let name: SmolStr = packages[target.0].name.replace('-', "_").into(); + let dep = PackageDependency { name, pkg: target }; + packages[source.0].dependencies.push(dep); + } + } Ok(CargoWorkspace { packages, targets }) } diff --git a/crates/ra_lsp_server/src/server_world.rs b/crates/ra_lsp_server/src/server_world.rs index c3f89ad5f0..ab4c2c8aad 100644 --- a/crates/ra_lsp_server/src/server_world.rs +++ b/crates/ra_lsp_server/src/server_world.rs @@ -13,7 +13,7 @@ use failure::{bail, format_err}; use crate::{ path_map::{PathMap, Root}, - project_model::CargoWorkspace, + project_model::{CargoWorkspace, TargetKind}, vfs::{FileEvent, FileEventKind}, Result, }; @@ -142,17 +142,34 @@ impl ServerWorldState { } pub fn set_workspaces(&mut self, ws: Vec) { let mut crate_graph = CrateGraph::default(); - ws.iter() - .flat_map(|ws| { - ws.packages() - .flat_map(move |pkg| pkg.targets(ws)) - .map(move |tgt| tgt.root(ws)) - }) - .for_each(|root| { - if let Some(file_id) = self.path_map.get_id(root) { - crate_graph.add_crate_root(file_id); + let mut pkg_to_lib_crate = FxHashMap::default(); + let mut pkg_crates = FxHashMap::default(); + for ws in ws.iter() { + for pkg in ws.packages() { + for tgt in pkg.targets(ws) { + let root = tgt.root(ws); + if let Some(file_id) = self.path_map.get_id(root) { + let crate_id = crate_graph.add_crate_root(file_id); + if tgt.kind(ws) == TargetKind::Lib { + pkg_to_lib_crate.insert(pkg, crate_id); + } + pkg_crates + .entry(pkg) + .or_insert_with(Vec::new) + .push(crate_id); + } } - }); + } + for pkg in ws.packages() { + for dep in pkg.dependencies(ws) { + if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) { + for &from in pkg_crates.get(&pkg).into_iter().flatten() { + crate_graph.add_dep(from, dep.name.clone(), to); + } + } + } + } + } self.workspaces = Arc::new(ws); let mut change = AnalysisChange::new(); change.set_crate_graph(crate_graph);