Skip to content

Query-based module scopes #171

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 30, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 23 additions & 12 deletions crates/ra_analysis/src/completion.rs
Original file line number Diff line number Diff line change
@@ -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<Option<Vec<CompletionItem>>> {
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<ModuleId> {


pub(crate) fn find_target_module(module_tree: &ModuleTree, module_id: ModuleId, file: &File, offset: TextUnit) -> Option<ModuleId> {
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 {
Expand Down
7 changes: 6 additions & 1 deletion crates/ra_analysis/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand Down Expand Up @@ -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;
}
}
}
Expand Down
14 changes: 13 additions & 1 deletion crates/ra_analysis/src/descriptors/module/imp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::{
};

use super::{
ModuleData, ModuleTree, ModuleId, LinkId, LinkData, Problem, ModulesDatabase
ModuleData, ModuleTree, ModuleId, LinkId, LinkData, Problem, ModulesDatabase, ModuleScope
};


Expand All @@ -35,6 +35,18 @@ pub(super) fn modules(root: ast::Root<'_>) -> impl Iterator<Item = (SmolStr, ast
})
}

pub(super) fn module_scope(
db: &impl ModulesDatabase,
source_root_id: SourceRootId,
module_id: ModuleId,
) -> Cancelable<Arc<ModuleScope>> {
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,
Expand Down
8 changes: 8 additions & 0 deletions crates/ra_analysis/src/descriptors/module/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod imp;
pub(crate) mod scope;

use std::sync::Arc;

Expand All @@ -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<Arc<ModuleTree>> {
Expand All @@ -21,6 +24,10 @@ salsa::query_group! {
type SubmodulesQuery;
use fn imp::submodules;
}
fn module_scope(source_root_id: SourceRootId, module_id: ModuleId) -> Cancelable<Arc<ModuleScope>> {
type ModuleScopeQuery;
use fn imp::module_scope;
}
}
}

Expand Down Expand Up @@ -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;
}
Expand Down
128 changes: 128 additions & 0 deletions crates/ra_analysis/src/descriptors/module/scope.rs
Original file line number Diff line number Diff line change
@@ -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>,
}

/// `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<Entry> {
let name = item.name()?;
Some(Entry {
name: name.text(),
ptr: LocalSyntaxPtr::new(name.syntax()),
kind: EntryKind::Item,
})
}
fn new_import(path: ast::Path) -> Option<Entry> {
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<Entry>) {
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::<Vec<_>>();
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"],
)
}
}
45 changes: 37 additions & 8 deletions crates/ra_analysis/src/syntax_ptr.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::marker::PhantomData;

use ra_syntax::{
File, TextRange, SyntaxKind, SyntaxNode, SyntaxNodeRef,
ast::{self, AstNode},
Expand All @@ -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,
Expand All @@ -20,30 +36,43 @@ impl SyntaxPtr {
let local = LocalSyntaxPtr::new(node);
SyntaxPtr { file_id, local }
}
}

struct OwnedAst<T> {
syntax: SyntaxNode,
phantom: PhantomData<T>,
}

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<ast::FnDef<'static>> {
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 {
Expand Down
2 changes: 2 additions & 0 deletions crates/ra_editor/src/completion.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/// FIXME: move completion from ra_editor to ra_analysis

use rustc_hash::{FxHashMap, FxHashSet};

use ra_syntax::{
Expand Down
6 changes: 6 additions & 0 deletions crates/ra_editor/src/scope/mod_scope.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
/// FIXME: this is now moved to ra_analysis::descriptors::module::scope.
///
/// Current copy will be deleted as soon as we move the rest of the completion
/// to the analyezer.


use ra_syntax::{
ast::{self, AstChildren},
AstNode, SmolStr, SyntaxNode, SyntaxNodeRef,
Expand Down