diff --git a/Cargo.lock b/Cargo.lock index b1fef2e80a6..b2d009e38fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -486,6 +486,7 @@ dependencies = [ "log", "profile", "rustc-hash", + "smallvec", "stdx", "syntax", "tt", diff --git a/crates/hir/Cargo.toml b/crates/hir/Cargo.toml index d4ea7327ebe..55e9c3f0cf9 100644 --- a/crates/hir/Cargo.toml +++ b/crates/hir/Cargo.toml @@ -15,6 +15,7 @@ rustc-hash = "1.1.0" either = "1.5.3" arrayvec = "0.5.1" itertools = "0.10.0" +smallvec = "1.4.0" stdx = { path = "../stdx", version = "0.0.0" } syntax = { path = "../syntax", version = "0.0.0" } diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index 945638cc565..519339c0c86 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -259,6 +259,10 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> { } pub fn to_module_def(&self, file: FileId) -> Option<Module> { + self.imp.to_module_def(file).next() + } + + pub fn to_module_defs(&self, file: FileId) -> impl Iterator<Item = Module> { self.imp.to_module_def(file) } @@ -537,8 +541,8 @@ impl<'db> SemanticsImpl<'db> { f(&mut ctx) } - fn to_module_def(&self, file: FileId) -> Option<Module> { - self.with_ctx(|ctx| ctx.file_to_def(file)).map(Module::from) + fn to_module_def(&self, file: FileId) -> impl Iterator<Item = Module> { + self.with_ctx(|ctx| ctx.file_to_def(file)).into_iter().map(Module::from) } fn scope(&self, node: &SyntaxNode) -> SemanticsScope<'db> { diff --git a/crates/hir/src/semantics/source_to_def.rs b/crates/hir/src/semantics/source_to_def.rs index 6c612ee8635..e9d8201403c 100644 --- a/crates/hir/src/semantics/source_to_def.rs +++ b/crates/hir/src/semantics/source_to_def.rs @@ -12,6 +12,7 @@ use hir_def::{ }; use hir_expand::{name::AsName, AstId, MacroDefKind}; use rustc_hash::FxHashMap; +use smallvec::SmallVec; use stdx::impl_from; use syntax::{ ast::{self, NameOwner}, @@ -28,14 +29,19 @@ pub(super) struct SourceToDefCtx<'a, 'b> { } impl SourceToDefCtx<'_, '_> { - pub(super) fn file_to_def(&mut self, file: FileId) -> Option<ModuleId> { + pub(super) fn file_to_def(&mut self, file: FileId) -> SmallVec<[ModuleId; 1]> { let _p = profile::span("SourceBinder::to_module_def"); - self.db.relevant_crates(file).iter().find_map(|&crate_id| { + let mut mods = SmallVec::new(); + for &crate_id in self.db.relevant_crates(file).iter() { // FIXME: inner items let crate_def_map = self.db.crate_def_map(crate_id); - let local_id = crate_def_map.modules_for_file(file).next()?; - Some(crate_def_map.module_id(local_id)) - }) + mods.extend( + crate_def_map + .modules_for_file(file) + .map(|local_id| crate_def_map.module_id(local_id)), + ) + } + mods } pub(super) fn module_to_def(&mut self, src: InFile<ast::Module>) -> Option<ModuleId> { @@ -55,7 +61,7 @@ impl SourceToDefCtx<'_, '_> { Some(parent_declaration) => self.module_to_def(parent_declaration), None => { let file_id = src.file_id.original_file(self.db.upcast()); - self.file_to_def(file_id) + self.file_to_def(file_id).get(0).copied() } }?; @@ -185,7 +191,7 @@ impl SourceToDefCtx<'_, '_> { ) -> Option<MacroDefId> { let kind = MacroDefKind::Declarative; let file_id = src.file_id.original_file(self.db.upcast()); - let krate = self.file_to_def(file_id)?.krate(); + let krate = self.file_to_def(file_id).get(0).copied()?.krate(); let file_ast_id = self.db.ast_id_map(src.file_id).ast_id(&src.value); let ast_id = Some(AstId::new(src.file_id, file_ast_id.upcast())); Some(MacroDefId { krate, ast_id, kind, local_inner: false }) @@ -245,7 +251,7 @@ impl SourceToDefCtx<'_, '_> { return Some(res); } - let def = self.file_to_def(src.file_id.original_file(self.db.upcast()))?; + let def = self.file_to_def(src.file_id.original_file(self.db.upcast())).get(0).copied()?; Some(def.into()) } diff --git a/crates/ide/src/parent_module.rs b/crates/ide/src/parent_module.rs index 03d71b3807a..22b0d6ecbef 100644 --- a/crates/ide/src/parent_module.rs +++ b/crates/ide/src/parent_module.rs @@ -1,6 +1,7 @@ use hir::Semantics; use ide_db::base_db::{CrateId, FileId, FilePosition}; use ide_db::RootDatabase; +use itertools::Itertools; use syntax::{ algo::find_node_at_offset, ast::{self, AstNode}, @@ -18,8 +19,7 @@ use crate::NavigationTarget; // | VS Code | **Rust Analyzer: Locate parent module** // |=== -/// This returns `Vec` because a module may be included from several places. We -/// don't handle this case yet though, so the Vec has length at most one. +/// This returns `Vec` because a module may be included from several places. pub(crate) fn parent_module(db: &RootDatabase, position: FilePosition) -> Vec<NavigationTarget> { let sema = Semantics::new(db); let source_file = sema.parse(position.file_id); @@ -37,27 +37,23 @@ pub(crate) fn parent_module(db: &RootDatabase, position: FilePosition) -> Vec<Na } } - let module = match module { - Some(module) => sema.to_def(&module), - None => sema.to_module_def(position.file_id), - }; - let module = match module { - None => return Vec::new(), - Some(it) => it, - }; - let nav = NavigationTarget::from_module_to_decl(db, module); - vec![nav] + match module { + Some(module) => sema + .to_def(&module) + .into_iter() + .map(|module| NavigationTarget::from_module_to_decl(db, module)) + .collect(), + None => sema + .to_module_defs(position.file_id) + .map(|module| NavigationTarget::from_module_to_decl(db, module)) + .collect(), + } } /// Returns `Vec` for the same reason as `parent_module` pub(crate) fn crate_for(db: &RootDatabase, file_id: FileId) -> Vec<CrateId> { let sema = Semantics::new(db); - let module = match sema.to_module_def(file_id) { - Some(it) => it, - None => return Vec::new(), - }; - let krate = module.krate(); - vec![krate.into()] + sema.to_module_defs(file_id).map(|module| module.krate().into()).unique().collect() } #[cfg(test)] @@ -67,11 +63,13 @@ mod tests { use crate::fixture; fn check(ra_fixture: &str) { - let (analysis, position, expected) = fixture::nav_target_annotation(ra_fixture); - let mut navs = analysis.parent_module(position).unwrap(); - assert_eq!(navs.len(), 1); - let nav = navs.pop().unwrap(); - assert_eq!(expected, FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() }); + let (analysis, position, expected) = fixture::annotations(ra_fixture); + let navs = analysis.parent_module(position).unwrap(); + let navs = navs + .iter() + .map(|nav| FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() }) + .collect::<Vec<_>>(); + assert_eq!(expected.into_iter().map(|(fr, _)| fr).collect::<Vec<_>>(), navs); } #[test] @@ -120,15 +118,46 @@ mod foo { } #[test] - fn test_resolve_crate_root() { - let (analysis, file_id) = fixture::file( + fn test_resolve_multi_parent_module() { + check( r#" //- /main.rs mod foo; + //^^^ +#[path = "foo.rs"] +mod bar; + //^^^ //- /foo.rs $0 +"#, + ); + } + + #[test] + fn test_resolve_crate_root() { + let (analysis, file_id) = fixture::file( + r#" +//- /foo.rs +$0 +//- /main.rs +mod foo; "#, ); assert_eq!(analysis.crate_for(file_id).unwrap().len(), 1); } + + #[test] + fn test_resolve_multi_parent_crate() { + let (analysis, file_id) = fixture::file( + r#" +//- /baz.rs +$0 +//- /foo.rs crate:foo +mod baz; +//- /bar.rs crate:bar +mod baz; +"#, + ); + assert_eq!(analysis.crate_for(file_id).unwrap().len(), 2); + } } diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts index 694f445bc36..bed1f01169c 100644 --- a/editors/code/src/commands.ts +++ b/editors/code/src/commands.ts @@ -170,22 +170,28 @@ export function parentModule(ctx: Ctx): Cmd { const client = ctx.client; if (!editor || !client) return; - const response = await client.sendRequest(ra.parentModule, { + const locations = await client.sendRequest(ra.parentModule, { textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document), position: client.code2ProtocolConverter.asPosition( editor.selection.active, ), }); - const loc = response[0]; - if (!loc) return; - const uri = client.protocol2CodeConverter.asUri(loc.targetUri); - const range = client.protocol2CodeConverter.asRange(loc.targetRange); + if (locations.length === 1) { + const loc = locations[0]; - const doc = await vscode.workspace.openTextDocument(uri); - const e = await vscode.window.showTextDocument(doc); - e.selection = new vscode.Selection(range.start, range.start); - e.revealRange(range, vscode.TextEditorRevealType.InCenter); + const uri = client.protocol2CodeConverter.asUri(loc.targetUri); + const range = client.protocol2CodeConverter.asRange(loc.targetRange); + + const doc = await vscode.workspace.openTextDocument(uri); + const e = await vscode.window.showTextDocument(doc); + e.selection = new vscode.Selection(range.start, range.start); + e.revealRange(range, vscode.TextEditorRevealType.InCenter); + } else { + const uri = editor.document.uri.toString(); + const position = client.code2ProtocolConverter.asPosition(editor.selection.active); + await showReferencesImpl(client, uri, position, locations.map(loc => lc.Location.create(loc.targetUri, loc.targetRange))); + } }; }