From fbbee537228538f448a335bb0b2dabec2b3d443e Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 31 Oct 2018 02:08:54 +0300 Subject: Add ModuleScope as a query This is a first step towards queryifing completion and resolve. Some code currently duplicates ra_editor: the plan is to move all completion from ra_editor, but it'll take more than one commit. --- crates/ra_analysis/src/completion.rs | 35 ++++-- crates/ra_analysis/src/db.rs | 7 +- crates/ra_analysis/src/descriptors/module/imp.rs | 14 ++- crates/ra_analysis/src/descriptors/module/mod.rs | 8 ++ crates/ra_analysis/src/descriptors/module/scope.rs | 128 +++++++++++++++++++++ crates/ra_analysis/src/syntax_ptr.rs | 45 ++++++-- 6 files changed, 215 insertions(+), 22 deletions(-) create mode 100644 crates/ra_analysis/src/descriptors/module/scope.rs (limited to 'crates/ra_analysis') diff --git a/crates/ra_analysis/src/completion.rs b/crates/ra_analysis/src/completion.rs index 0a2f99575..0141d132e 100644 --- a/crates/ra_analysis/src/completion.rs +++ b/crates/ra_analysis/src/completion.rs @@ -1,40 +1,51 @@ -use ra_editor::{CompletionItem, find_node_at_offset, complete_module_items}; +use ra_editor::{CompletionItem, find_node_at_offset}; use ra_syntax::{ AtomEdit, File, TextUnit, AstNode, - ast::{self, ModuleItemOwner}, + ast::{self, ModuleItemOwner, AstChildren}, }; use crate::{ FileId, Cancelable, input::FilesDatabase, db::{self, SyntaxDatabase}, - descriptors::module::{ModulesDatabase, ModuleTree, ModuleId}, + descriptors::module::{ModulesDatabase, ModuleTree, ModuleId, scope::ModuleScope}, }; pub(crate) fn resolve_based_completion(db: &db::RootDatabase, file_id: FileId, offset: TextUnit) -> Cancelable>> { 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) { + None => return Ok(None), + Some(it) => it, + }; let file = { let edit = AtomEdit::insert(offset, "intellijRulezz".to_string()); file.reparse(&edit) }; - let target_file = match find_target_module(&module_tree, file_id, &file, offset) { + let target_module_id = match find_target_module(&module_tree, module_id, &file, offset) { None => return Ok(None), - Some(target_module) => { - let file_id = target_module.file_id(&module_tree); - db.file_syntax(file_id) - } + Some(it) => it, }; - let mut res = Vec::new(); - complete_module_items(target_file.ast().items(), None, &mut res); + let module_scope = db.module_scope(source_root_id, target_module_id)?; + let res: Vec<_> = module_scope + .entries() + .iter() + .map(|entry| CompletionItem { + label: entry.name().to_string(), + lookup: None, + snippet: None, + }) + .collect(); Ok(Some(res)) } -pub(crate) fn find_target_module(module_tree: &ModuleTree, file_id: FileId, file: &File, offset: TextUnit) -> Option { + + +pub(crate) fn find_target_module(module_tree: &ModuleTree, module_id: ModuleId, file: &File, offset: TextUnit) -> Option { let name_ref: ast::NameRef = find_node_at_offset(file.syntax(), offset)?; let mut crate_path = crate_path(name_ref)?; - let module_id = module_tree.any_module_for_file(file_id)?; + crate_path.pop(); let mut target_module = module_id.root(&module_tree); for name in crate_path { diff --git a/crates/ra_analysis/src/db.rs b/crates/ra_analysis/src/db.rs index 3ca14af79..e7a5d5e2f 100644 --- a/crates/ra_analysis/src/db.rs +++ b/crates/ra_analysis/src/db.rs @@ -9,8 +9,9 @@ use salsa; use crate::{ db, Cancelable, Canceled, - descriptors::module::{SubmodulesQuery, ModuleTreeQuery, ModulesDatabase}, + descriptors::module::{SubmodulesQuery, ModuleTreeQuery, ModulesDatabase, ModuleScopeQuery}, symbol_index::SymbolIndex, + syntax_ptr::{SyntaxPtrDatabase, ResolveSyntaxPtrQuery}, FileId, }; @@ -65,6 +66,10 @@ salsa::database_storage! { impl ModulesDatabase { fn module_tree() for ModuleTreeQuery; fn module_descriptor() for SubmodulesQuery; + fn module_scope() for ModuleScopeQuery; + } + impl SyntaxPtrDatabase { + fn resolve_syntax_ptr() for ResolveSyntaxPtrQuery; } } } diff --git a/crates/ra_analysis/src/descriptors/module/imp.rs b/crates/ra_analysis/src/descriptors/module/imp.rs index aecf6e29a..5fdaad137 100644 --- a/crates/ra_analysis/src/descriptors/module/imp.rs +++ b/crates/ra_analysis/src/descriptors/module/imp.rs @@ -13,7 +13,7 @@ use crate::{ }; use super::{ - ModuleData, ModuleTree, ModuleId, LinkId, LinkData, Problem, ModulesDatabase + ModuleData, ModuleTree, ModuleId, LinkId, LinkData, Problem, ModulesDatabase, ModuleScope }; @@ -35,6 +35,18 @@ pub(super) fn modules(root: ast::Root<'_>) -> impl Iterator Cancelable> { + let tree = db.module_tree(source_root_id)?; + let file_id = module_id.file_id(&tree); + let syntax = db.file_syntax(file_id); + let res = ModuleScope::new(&syntax); + Ok(Arc::new(res)) +} + pub(super) fn module_tree( db: &impl ModulesDatabase, source_root: SourceRootId, diff --git a/crates/ra_analysis/src/descriptors/module/mod.rs b/crates/ra_analysis/src/descriptors/module/mod.rs index 8968c4afd..9e5d73f94 100644 --- a/crates/ra_analysis/src/descriptors/module/mod.rs +++ b/crates/ra_analysis/src/descriptors/module/mod.rs @@ -1,4 +1,5 @@ mod imp; +pub(crate) mod scope; use std::sync::Arc; @@ -11,6 +12,8 @@ use crate::{ input::SourceRootId, }; +pub(crate) use self::scope::ModuleScope; + salsa::query_group! { pub(crate) trait ModulesDatabase: SyntaxDatabase { fn module_tree(source_root_id: SourceRootId) -> Cancelable> { @@ -21,6 +24,10 @@ salsa::query_group! { type SubmodulesQuery; use fn imp::submodules; } + fn module_scope(source_root_id: SourceRootId, module_id: ModuleId) -> Cancelable> { + type ModuleScopeQuery; + use fn imp::module_scope; + } } } @@ -78,6 +85,7 @@ impl ModuleId { while let Some(next) = curr.parent(tree) { curr = next; i += 1; + // simplistic cycle detection if i > 100 { return self; } diff --git a/crates/ra_analysis/src/descriptors/module/scope.rs b/crates/ra_analysis/src/descriptors/module/scope.rs new file mode 100644 index 000000000..da58ddce0 --- /dev/null +++ b/crates/ra_analysis/src/descriptors/module/scope.rs @@ -0,0 +1,128 @@ +//! Backend for module-level scope resolution & completion + + +use ra_syntax::{ + ast::{self, AstChildren, ModuleItemOwner}, + File, AstNode, SmolStr, SyntaxNode, SyntaxNodeRef, +}; + +use crate::syntax_ptr::LocalSyntaxPtr; + +/// `ModuleScope` contains all named items declared in the scope. +#[derive(Debug, PartialEq, Eq)] +pub(crate) struct ModuleScope { + entries: Vec, +} + +/// `Entry` is a single named declaration iside a module. +#[derive(Debug, PartialEq, Eq)] +pub(crate) struct Entry { + ptr: LocalSyntaxPtr, + kind: EntryKind, + name: SmolStr, +} + +#[derive(Debug, PartialEq, Eq)] +enum EntryKind { + Item, + Import, +} + +impl ModuleScope { + pub fn new(file: &File) -> ModuleScope { + let mut entries = Vec::new(); + for item in file.ast().items() { + let entry = match item { + ast::ModuleItem::StructDef(item) => Entry::new(item), + ast::ModuleItem::EnumDef(item) => Entry::new(item), + ast::ModuleItem::FnDef(item) => Entry::new(item), + ast::ModuleItem::ConstDef(item) => Entry::new(item), + ast::ModuleItem::StaticDef(item) => Entry::new(item), + ast::ModuleItem::TraitDef(item) => Entry::new(item), + ast::ModuleItem::TypeDef(item) => Entry::new(item), + ast::ModuleItem::Module(item) => Entry::new(item), + ast::ModuleItem::UseItem(item) => { + if let Some(tree) = item.use_tree() { + collect_imports(tree, &mut entries); + } + continue; + } + ast::ModuleItem::ExternCrateItem(_) | ast::ModuleItem::ImplItem(_) => continue, + }; + entries.extend(entry) + } + + ModuleScope { entries } + } + + pub fn entries(&self) -> &[Entry] { + self.entries.as_slice() + } +} + +impl Entry { + fn new<'a>(item: impl ast::NameOwner<'a>) -> Option { + let name = item.name()?; + Some(Entry { + name: name.text(), + ptr: LocalSyntaxPtr::new(name.syntax()), + kind: EntryKind::Item, + }) + } + fn new_import(path: ast::Path) -> Option { + let name_ref = path.segment()?.name_ref()?; + Some(Entry { + name: name_ref.text(), + ptr: LocalSyntaxPtr::new(name_ref.syntax()), + kind: EntryKind::Import, + }) + } + pub fn name(&self) -> &SmolStr { + &self.name + } + pub fn ptr(&self) -> LocalSyntaxPtr { + self.ptr + } +} + +fn collect_imports(tree: ast::UseTree, acc: &mut Vec) { + if let Some(use_tree_list) = tree.use_tree_list() { + return use_tree_list + .use_trees() + .for_each(|it| collect_imports(it, acc)); + } + if let Some(path) = tree.path() { + acc.extend(Entry::new_import(path)); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ra_syntax::{ast::ModuleItemOwner, File}; + + fn do_check(code: &str, expected: &[&str]) { + let file = File::parse(&code); + let scope = ModuleScope::new(&file); + let actual = scope.entries.iter().map(|it| it.name()).collect::>(); + assert_eq!(expected, actual.as_slice()); + } + + #[test] + fn test_module_scope() { + do_check( + " + struct Foo; + enum Bar {} + mod baz {} + fn quux() {} + use x::{ + y::z, + t, + }; + type T = (); + ", + &["Foo", "Bar", "baz", "quux", "z", "t", "T"], + ) + } +} diff --git a/crates/ra_analysis/src/syntax_ptr.rs b/crates/ra_analysis/src/syntax_ptr.rs index 863ad2672..adbff4806 100644 --- a/crates/ra_analysis/src/syntax_ptr.rs +++ b/crates/ra_analysis/src/syntax_ptr.rs @@ -1,3 +1,5 @@ +use std::marker::PhantomData; + use ra_syntax::{ File, TextRange, SyntaxKind, SyntaxNode, SyntaxNodeRef, ast::{self, AstNode}, @@ -6,10 +8,24 @@ use ra_syntax::{ use crate::FileId; use crate::db::SyntaxDatabase; +salsa::query_group! { + pub(crate) trait SyntaxPtrDatabase: SyntaxDatabase { + fn resolve_syntax_ptr(ptr: SyntaxPtr) -> SyntaxNode { + type ResolveSyntaxPtrQuery; + storage volatile; + } + } +} + +fn resolve_syntax_ptr(db: &impl SyntaxDatabase, ptr: SyntaxPtr) -> SyntaxNode { + let syntax = db.file_syntax(ptr.file_id); + ptr.local.resolve(&syntax) +} + /// SyntaxPtr is a cheap `Copy` id which identifies a particular syntax node, /// without retainig syntax tree in memory. You need to explicitelly `resovle` /// `SyntaxPtr` to get a `SyntaxNode` -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub(crate) struct SyntaxPtr { file_id: FileId, local: LocalSyntaxPtr, @@ -20,30 +36,43 @@ impl SyntaxPtr { let local = LocalSyntaxPtr::new(node); SyntaxPtr { file_id, local } } +} + +struct OwnedAst { + syntax: SyntaxNode, + phantom: PhantomData, +} + +trait ToAst { + type Ast; + fn to_ast(self) -> Self::Ast; +} - pub(crate) fn resolve(self, db: &impl SyntaxDatabase) -> SyntaxNode { - let syntax = db.file_syntax(self.file_id); - self.local.resolve(&syntax) +impl<'a> ToAst for &'a OwnedAst> { + type Ast = ast::FnDef<'a>; + fn to_ast(self) -> ast::FnDef<'a> { + ast::FnDef::cast(self.syntax.borrowed()) + .unwrap() } } /// A pionter to a syntax node inside a file. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -struct LocalSyntaxPtr { +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct LocalSyntaxPtr { range: TextRange, kind: SyntaxKind, } impl LocalSyntaxPtr { - fn new(node: SyntaxNodeRef) -> LocalSyntaxPtr { + pub(crate) fn new(node: SyntaxNodeRef) -> LocalSyntaxPtr { LocalSyntaxPtr { range: node.range(), kind: node.kind(), } } - fn resolve(self, file: &File) -> SyntaxNode { + pub(crate) fn resolve(self, file: &File) -> SyntaxNode { let mut curr = file.syntax(); loop { if curr.range() == self.range && curr.kind() == self.kind { -- cgit v1.2.3