Repository: rust-analyzer/rust-analyzer
Pull request #193 (2018-11-05 11:11:43)

Inline modules


Patch:
diff --git a/crates/ra_analysis/src/completion.rs b/crates/ra_analysis/src/completion.rs
index 6667c06e7d..766df1d962 100644
--- a/crates/ra_analysis/src/completion.rs
+++ b/crates/ra_analysis/src/completion.rs
@@ -11,7 +11,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
 use crate::{
     db::{self, SyntaxDatabase},
     descriptors::function::FnScopes,
-    descriptors::module::{ModuleId, ModuleScope, ModuleTree},
+    descriptors::module::{ModuleId, ModuleScope, ModuleTree, ModuleSource},
     descriptors::DescriptorDatabase,
     input::FilesDatabase,
     Cancelable, FileId,
@@ -35,7 +35,7 @@ pub(crate) fn resolve_based_completion(
     let source_root_id = db.file_source_root(file_id);
     let file = db.file_syntax(file_id);
     let module_tree = db.module_tree(source_root_id)?;
-    let module_id = match module_tree.any_module_for_file(file_id) {
+    let module_id = match module_tree.any_module_for_source(ModuleSource::File(file_id)) {
         None => return Ok(None),
         Some(it) => it,
     };
diff --git a/crates/ra_analysis/src/descriptors/mod.rs b/crates/ra_analysis/src/descriptors/mod.rs
index eaeef54c1e..56bde38492 100644
--- a/crates/ra_analysis/src/descriptors/mod.rs
+++ b/crates/ra_analysis/src/descriptors/mod.rs
@@ -5,16 +5,16 @@ use std::sync::Arc;
 
 use ra_syntax::{
     ast::{self, AstNode, FnDefNode},
-    SmolStr, TextRange,
+    TextRange,
 };
 
 use crate::{
     db::SyntaxDatabase,
     descriptors::function::{resolve_local_name, FnId, FnScopes},
-    descriptors::module::{ModuleId, ModuleScope, ModuleTree},
+    descriptors::module::{ModuleId, ModuleScope, ModuleTree, ModuleSource},
     input::SourceRootId,
     syntax_ptr::LocalSyntaxPtr,
-    Cancelable, FileId,
+    Cancelable,
 };
 
 salsa::query_group! {
@@ -23,7 +23,7 @@ salsa::query_group! {
             type ModuleTreeQuery;
             use fn module::imp::module_tree;
         }
-        fn submodules(file_id: FileId) -> Cancelable<Arc<Vec<SmolStr>>> {
+        fn submodules(source: ModuleSource) -> Cancelable<Arc<Vec<module::imp::Submodule>>> {
             type SubmodulesQuery;
             use fn module::imp::submodules;
         }
diff --git a/crates/ra_analysis/src/descriptors/module/imp.rs b/crates/ra_analysis/src/descriptors/module/imp.rs
index 3a010ecf54..b3b1f1f216 100644
--- a/crates/ra_analysis/src/descriptors/module/imp.rs
+++ b/crates/ra_analysis/src/descriptors/module/imp.rs
@@ -19,25 +19,66 @@ use super::{
     ModuleTree, Problem,
 };
 
+#[derive(Clone, Hash, PartialEq, Eq, Debug)]
+pub(crate) enum Submodule {
+    Declaration(SmolStr),
+    Definition(SmolStr, ModuleSource),
+}
+
+impl Submodule {
+    fn name(&self) -> &SmolStr {
+        match self {
+            Submodule::Declaration(name) => name,
+            Submodule::Definition(name, _) => name,
+        }
+    }
+}
+
 pub(crate) fn submodules(
     db: &impl DescriptorDatabase,
-    file_id: FileId,
-) -> Cancelable<Arc<Vec<SmolStr>>> {
+    source: ModuleSource,
+) -> Cancelable<Arc<Vec<Submodule>>> {
     db::check_canceled(db)?;
-    let file = db.file_syntax(file_id);
-    let root = file.ast();
-    let submodules = modules(root).map(|(name, _)| name).collect();
-    Ok(Arc::new(submodules))
+    let file_id = source.file_id();
+    let submodules = match source.resolve(db) {
+        ModuleSourceNode::Root(it) => collect_submodules(file_id, it.ast()),
+        ModuleSourceNode::Inline(it) => it
+            .ast()
+            .item_list()
+            .map(|it| collect_submodules(file_id, it))
+            .unwrap_or_else(Vec::new),
+    };
+    return Ok(Arc::new(submodules));
+
+    fn collect_submodules<'a>(
+        file_id: FileId,
+        root: impl ast::ModuleItemOwner<'a>,
+    ) -> Vec<Submodule> {
+        modules(root)
+            .map(|(name, m)| {
+                if m.has_semi() {
+                    Submodule::Declaration(name)
+                } else {
+                    let src = ModuleSource::new_inline(file_id, m);
+                    Submodule::Definition(name, src)
+                }
+            })
+            .collect()
+    }
 }
 
-pub(crate) fn modules(root: ast::Root<'_>) -> impl Iterator<Item = (SmolStr, ast::Module<'_>)> {
-    root.modules().filter_map(|module| {
-        let name = module.name()?.text();
-        if !module.has_semi() {
-            return None;
-        }
-        Some((name, module))
-    })
+pub(crate) fn modules<'a>(
+    root: impl ast::ModuleItemOwner<'a>,
+) -> impl Iterator<Item = (SmolStr, ast::Module<'a>)> {
+    root.items()
+        .filter_map(|item| match item {
+            ast::ModuleItem::Module(m) => Some(m),
+            _ => None,
+        })
+        .filter_map(|module| {
+            let name = module.name()?.text();
+            Some((name, module))
+        })
 }
 
 pub(crate) fn module_scope(
@@ -66,11 +107,6 @@ pub(crate) fn module_tree(
     Ok(Arc::new(res))
 }
 
-#[derive(Clone, Hash, PartialEq, Eq, Debug)]
-pub struct Submodule {
-    pub name: SmolStr,
-}
-
 fn create_module_tree<'a>(
     db: &impl DescriptorDatabase,
     source_root: SourceRootId,
@@ -85,7 +121,8 @@ fn create_module_tree<'a>(
 
     let source_root = db.source_root(source_root);
     for &file_id in source_root.files.iter() {
-        if visited.contains(&file_id) {
+        let source = ModuleSource::File(file_id);
+        if visited.contains(&source) {
             continue; // TODO: use explicit crate_roots here
         }
         assert!(!roots.contains_key(&file_id));
@@ -96,7 +133,7 @@ fn create_module_tree<'a>(
             &mut visited,
             &mut roots,
             None,
-            file_id,
+            source,
         )?;
         roots.insert(file_id, module_id);
     }
@@ -107,36 +144,63 @@ fn build_subtree(
     db: &impl DescriptorDatabase,
     source_root: &SourceRoot,
     tree: &mut ModuleTree,
-    visited: &mut FxHashSet<FileId>,
+    visited: &mut FxHashSet<ModuleSource>,
     roots: &mut FxHashMap<FileId, ModuleId>,
     parent: Option<LinkId>,
-    file_id: FileId,
+    source: ModuleSource,
 ) -> Cancelable<ModuleId> {
-    visited.insert(file_id);
+    visited.insert(source);
     let id = tree.push_mod(ModuleData {
-        source: ModuleSource::File(file_id),
+        source,
         parent,
         children: Vec::new(),
     });
-    for name in db.submodules(file_id)?.iter() {
-        let (points_to, problem) = resolve_submodule(file_id, name, &source_root.file_resolver);
+    for sub in db.submodules(source)?.iter() {
         let link = tree.push_link(LinkData {
-            name: name.clone(),
+            name: sub.name().clone(),
             owner: id,
             points_to: Vec::new(),
             problem: None,
         });
 
-        let points_to = points_to
-            .into_iter()
-            .map(|file_id| match roots.remove(&file_id) {
-                Some(module_id) => {
-                    tree.module_mut(module_id).parent = Some(link);
-                    Ok(module_id)
-                }
-                None => build_subtree(db, source_root, tree, visited, roots, Some(link), file_id),
-            })
-            .collect::<Cancelable<Vec<_>>>()?;
+        let (points_to, problem) = match sub {
+            Submodule::Declaration(name) => {
+                let (points_to, problem) =
+                    resolve_submodule(source, &name, &source_root.file_resolver);
+                let points_to = points_to
+                    .into_iter()
+                    .map(|file_id| match roots.remove(&file_id) {
+                        Some(module_id) => {
+                            tree.module_mut(module_id).parent = Some(link);
+                            Ok(module_id)
+                        }
+                        None => build_subtree(
+                            db,
+                            source_root,
+                            tree,
+                            visited,
+                            roots,
+                            Some(link),
+                            ModuleSource::File(file_id),
+                        ),
+                    })
+                    .collect::<Cancelable<Vec<_>>>()?;
+                (points_to, problem)
+            }
+            Submodule::Definition(_name, submodule_source) => {
+                let points_to = build_subtree(
+                    db,
+                    source_root,
+                    tree,
+                    visited,
+                    roots,
+                    Some(link),
+                    *submodule_source,
+                )?;
+                (vec![points_to], None)
+            }
+        };
+
         tree.link_mut(link).points_to = points_to;
         tree.link_mut(link).problem = problem;
     }
@@ -144,10 +208,17 @@ fn build_subtree(
 }
 
 fn resolve_submodule(
-    file_id: FileId,
+    source: ModuleSource,
     name: &SmolStr,
     file_resolver: &FileResolverImp,
 ) -> (Vec<FileId>, Option<Problem>) {
+    let file_id = match source {
+        ModuleSource::File(it) => it,
+        ModuleSource::Inline(..) => {
+            // TODO
+            return (Vec::new(), None);
+        }
+    };
     let mod_name = file_resolver.file_stem(file_id);
     let is_dir_owner = mod_name == "mod" || mod_name == "lib" || mod_name == "main";
 
diff --git a/crates/ra_analysis/src/descriptors/module/mod.rs b/crates/ra_analysis/src/descriptors/module/mod.rs
index e22489fc10..3d799ba05d 100644
--- a/crates/ra_analysis/src/descriptors/module/mod.rs
+++ b/crates/ra_analysis/src/descriptors/module/mod.rs
@@ -25,17 +25,17 @@ pub(crate) struct ModuleTree {
 }
 
 impl ModuleTree {
-    pub(crate) fn modules_for_file(&self, file_id: FileId) -> Vec<ModuleId> {
+    pub(crate) fn modules_for_source(&self, source: ModuleSource) -> Vec<ModuleId> {
         self.mods
             .iter()
             .enumerate()
-            .filter(|(_idx, it)| it.source.is_file(file_id))
+            .filter(|(_idx, it)| it.source == source)
             .map(|(idx, _)| ModuleId(idx as u32))
             .collect()
     }
 
-    pub(crate) fn any_module_for_file(&self, file_id: FileId) -> Option<ModuleId> {
-        self.modules_for_file(file_id).pop()
+    pub(crate) fn any_module_for_source(&self, source: ModuleSource) -> Option<ModuleId> {
+        self.modules_for_source(source).pop()
     }
 }
 
@@ -142,9 +142,7 @@ impl LinkId {
                     .1;
                 ast.into()
             }
-            ModuleSourceNode::Inline(..) => {
-                unimplemented!("https://github.com/rust-analyzer/rust-analyzer/issues/181")
-            }
+            ModuleSourceNode::Inline(it) => it,
         }
     }
 }
@@ -157,6 +155,12 @@ struct ModuleData {
 }
 
 impl ModuleSource {
+    pub(crate) fn new_inline(file_id: FileId, module: ast::Module) -> ModuleSource {
+        assert!(!module.has_semi());
+        let ptr = SyntaxPtr::new(file_id, module.syntax());
+        ModuleSource::Inline(ptr)
+    }
+
     pub(crate) fn as_file(self) -> Option<FileId> {
         match self {
             ModuleSource::File(f) => Some(f),
@@ -164,6 +168,13 @@ impl ModuleSource {
         }
     }
 
+    pub(crate) fn file_id(self) -> FileId {
+        match self {
+            ModuleSource::File(f) => f,
+            ModuleSource::Inline(ptr) => ptr.file_id(),
+        }
+    }
+
     fn resolve(self, db: &impl SyntaxDatabase) -> ModuleSourceNode {
         match self {
             ModuleSource::File(file_id) => {
@@ -178,10 +189,6 @@ impl ModuleSource {
             }
         }
     }
-
-    fn is_file(self, file_id: FileId) -> bool {
-        self.as_file() == Some(file_id)
-    }
 }
 
 #[derive(Hash, Debug, PartialEq, Eq)]
diff --git a/crates/ra_analysis/src/imp.rs b/crates/ra_analysis/src/imp.rs
index 4f337d1631..704648b595 100644
--- a/crates/ra_analysis/src/imp.rs
+++ b/crates/ra_analysis/src/imp.rs
@@ -220,27 +220,32 @@ impl AnalysisImpl {
         let source_root = self.db.file_source_root(file_id);
         self.db.module_tree(source_root)
     }
-    pub fn parent_module(&self, file_id: FileId) -> Cancelable<Vec<(FileId, FileSymbol)>> {
+    pub fn parent_module(
+        &self,
+        file_id: FileId,
+        offset: TextUnit,
+    ) -> Cancelable<Vec<(FileId, FileSymbol)>> {
         let module_tree = self.module_tree(file_id)?;
+        let file = self.db.file_syntax(file_id);
+        let module_source = match find_node_at_offset::<ast::Module>(file.syntax(), offset) {
+            Some(m) if !m.has_semi() => ModuleSource::new_inline(file_id, m),
+            _ => ModuleSource::File(file_id),
+        };
 
         let res = module_tree
-            .modules_for_file(file_id)
+            .modules_for_source(module_source)
             .into_iter()
             .filter_map(|module_id| {
                 let link = module_id.parent_link(&module_tree)?;
-                let file_id = match link.owner(&module_tree).source(&module_tree) {
-                    ModuleSource::File(file_id) => file_id,
-                    ModuleSource::Inline(..) => {
-                        //TODO: https://github.com/rust-analyzer/rust-analyzer/issues/181
-                        return None;
-                    }
-                };
+                let file_id = link.owner(&module_tree).source(&module_tree).file_id();
                 let decl = link.bind_source(&module_tree, &*self.db);
                 let decl = decl.ast();
 
+                let decl_name = decl.name().unwrap();
+
                 let sym = FileSymbol {
-                    name: decl.name().unwrap().text(),
-                    node_range: decl.syntax().range(),
+                    name: decl_name.text(),
+                    node_range: decl_name.syntax().range(),
                     kind: MODULE,
                 };
                 Some((file_id, sym))
@@ -252,7 +257,7 @@ impl AnalysisImpl {
         let module_tree = self.module_tree(file_id)?;
         let crate_graph = self.db.crate_graph();
         let res = module_tree
-            .modules_for_file(file_id)
+            .modules_for_source(ModuleSource::File(file_id))
             .into_iter()
             .map(|it| it.root(&module_tree))
             .filter_map(|it| it.source(&module_tree).as_file())
@@ -376,7 +381,7 @@ impl AnalysisImpl {
                 fix: None,
             })
             .collect::<Vec<_>>();
-        if let Some(m) = module_tree.any_module_for_file(file_id) {
+        if let Some(m) = module_tree.any_module_for_source(ModuleSource::File(file_id)) {
             for (name_node, problem) in m.problems(&module_tree, &*self.db) {
                 let diag = match problem {
                     Problem::UnresolvedModule { candidate } => {
@@ -539,7 +544,7 @@ impl AnalysisImpl {
             Some(name) => name.text(),
             None => return Vec::new(),
         };
-        let module_id = match module_tree.any_module_for_file(file_id) {
+        let module_id = match module_tree.any_module_for_source(ModuleSource::File(file_id)) {
             Some(id) => id,
             None => return Vec::new(),
         };
diff --git a/crates/ra_analysis/src/lib.rs b/crates/ra_analysis/src/lib.rs
index 4e4c65f084..fee382151a 100644
--- a/crates/ra_analysis/src/lib.rs
+++ b/crates/ra_analysis/src/lib.rs
@@ -263,8 +263,12 @@ impl Analysis {
     ) -> Cancelable<Vec<(FileId, TextRange)>> {
         Ok(self.imp.find_all_refs(file_id, offset))
     }
-    pub fn parent_module(&self, file_id: FileId) -> Cancelable<Vec<(FileId, FileSymbol)>> {
-        self.imp.parent_module(file_id)
+    pub fn parent_module(
+        &self,
+        file_id: FileId,
+        offset: TextUnit,
+    ) -> Cancelable<Vec<(FileId, FileSymbol)>> {
+        self.imp.parent_module(file_id, offset)
     }
     pub fn crate_for(&self, file_id: FileId) -> Cancelable<Vec<CrateId>> {
         self.imp.crate_for(file_id)
diff --git a/crates/ra_analysis/src/syntax_ptr.rs b/crates/ra_analysis/src/syntax_ptr.rs
index 4db1529c2c..4afb1fc930 100644
--- a/crates/ra_analysis/src/syntax_ptr.rs
+++ b/crates/ra_analysis/src/syntax_ptr.rs
@@ -22,6 +22,10 @@ impl SyntaxPtr {
         let local = LocalSyntaxPtr::new(node);
         SyntaxPtr { file_id, local }
     }
+
+    pub(crate) fn file_id(self) -> FileId {
+        self.file_id
+    }
 }
 
 /// A pionter to a syntax node inside a file.
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs
index c853ff6530..2219a00365 100644
--- a/crates/ra_lsp_server/src/main_loop/handlers.rs
+++ b/crates/ra_lsp_server/src/main_loop/handlers.rs
@@ -218,11 +218,13 @@ pub fn handle_goto_definition(
 
 pub fn handle_parent_module(
     world: ServerWorld,
-    params: TextDocumentIdentifier,
+    params: req::TextDocumentPositionParams,
 ) -> Result<Vec<Location>> {
-    let file_id = params.try_conv_with(&world)?;
+    let file_id = params.text_document.try_conv_with(&world)?;
+    let line_index = world.analysis().file_line_index(file_id);
+    let offset = params.position.conv_with(&line_index);
     let mut res = Vec::new();
-    for (file_id, symbol) in world.analysis().parent_module(file_id)? {
+    for (file_id, symbol) in world.analysis().parent_module(file_id, offset)? {
         let line_index = world.analysis().file_line_index(file_id);
         let location = to_location(file_id, symbol.node_range, &world, &line_index)?;
         res.push(location);
diff --git a/crates/ra_lsp_server/src/req.rs b/crates/ra_lsp_server/src/req.rs
index 9d911912d9..fcb7e94e16 100644
--- a/crates/ra_lsp_server/src/req.rs
+++ b/crates/ra_lsp_server/src/req.rs
@@ -93,7 +93,7 @@ pub struct Decoration {
 pub enum ParentModule {}
 
 impl Request for ParentModule {
-    type Params = TextDocumentIdentifier;
+    type Params = TextDocumentPositionParams;
     type Result = Vec<Location>;
     const METHOD: &'static str = "m/parentModule";
 }
diff --git a/editors/code/src/commands/on_enter.ts b/editors/code/src/commands/on_enter.ts
index fe6aca63d9..64401b6840 100644
--- a/editors/code/src/commands/on_enter.ts
+++ b/editors/code/src/commands/on_enter.ts
@@ -6,10 +6,6 @@ import {
     SourceChange
 } from './apply_source_change';
 
-interface OnEnterParams {
-    textDocument: lc.TextDocumentIdentifier;
-    position: lc.Position;
-}
 
 export async function handle(event: { text: string }): Promise<boolean> {
     const editor = vscode.window.activeTextEditor;
@@ -20,7 +16,7 @@ export async function handle(event: { text: string }): Promise<boolean> {
     ) {
         return false;
     }
-    const request: OnEnterParams = {
+    const request: lc.TextDocumentPositionParams = {
         textDocument: { uri: editor.document.uri.toString() },
         position: Server.client.code2ProtocolConverter.asPosition(
             editor.selection.active
diff --git a/editors/code/src/commands/parent_module.ts b/editors/code/src/commands/parent_module.ts
index 4bb92eb96a..806c3d34c1 100644
--- a/editors/code/src/commands/parent_module.ts
+++ b/editors/code/src/commands/parent_module.ts
@@ -1,6 +1,6 @@
 import * as vscode from 'vscode';
 
-import { Location, TextDocumentIdentifier } from 'vscode-languageclient';
+import * as lc from 'vscode-languageclient';
 import { Server } from '../server';
 
 export async function handle() {
@@ -8,10 +8,13 @@ export async function handle() {
     if (editor == null || editor.document.languageId !== 'rust') {
         return;
     }
-    const request: TextDocumentIdentifier = {
-        uri: editor.document.uri.toString()
+    const request: lc.TextDocumentPositionParams = {
+        textDocument: { uri: editor.document.uri.toString() },
+        position: Server.client.code2ProtocolConverter.asPosition(
+            editor.selection.active
+        )
     };
-    const response = await Server.client.sendRequest<Location[]>(
+    const response = await Server.client.sendRequest<lc.Location[]>(
         'm/parentModule',
         request
     );


Test patch:
diff --git a/crates/ra_analysis/tests/tests.rs b/crates/ra_analysis/tests/tests.rs
index c2754c8e48..7f7bb8e6bd 100644
--- a/crates/ra_analysis/tests/tests.rs
+++ b/crates/ra_analysis/tests/tests.rs
@@ -92,9 +92,28 @@ fn test_resolve_parent_module() {
         <|>// empty
     ",
     );
-    let symbols = analysis.parent_module(pos.file_id).unwrap();
+    let symbols = analysis.parent_module(pos.file_id, pos.offset).unwrap();
     assert_eq_dbg(
-        r#"[(FileId(1), FileSymbol { name: "foo", node_range: [0; 8), kind: MODULE })]"#,
+        r#"[(FileId(1), FileSymbol { name: "foo", node_range: [4; 7), kind: MODULE })]"#,
+        &symbols,
+    );
+}
+
+#[test]
+fn test_resolve_parent_module_for_inline() {
+    let (analysis, pos) = analysis_and_position(
+        "
+        //- /lib.rs
+        mod foo {
+            mod bar {
+                mod baz { <|> }
+            }
+        }
+    ",
+    );
+    let symbols = analysis.parent_module(pos.file_id, pos.offset).unwrap();
+    assert_eq_dbg(
+        r#"[(FileId(1), FileSymbol { name: "bar", node_range: [18; 21), kind: MODULE })]"#,
         &symbols,
     );
 }