From b5b68f2094d49cacde6d7f0c49f521a0b25f34bd Mon Sep 17 00:00:00 2001 From: Florian Diebold Date: Mon, 24 Dec 2018 19:07:48 +0100 Subject: Add basic HIR and types for structs/enums --- crates/ra_analysis/src/db.rs | 2 ++ 1 file changed, 2 insertions(+) (limited to 'crates/ra_analysis/src') diff --git a/crates/ra_analysis/src/db.rs b/crates/ra_analysis/src/db.rs index 780a84291..7043a0f4d 100644 --- a/crates/ra_analysis/src/db.rs +++ b/crates/ra_analysis/src/db.rs @@ -95,6 +95,8 @@ salsa::database_storage! { fn submodules() for hir::db::SubmodulesQuery; fn infer() for hir::db::InferQuery; fn type_for_def() for hir::db::TypeForDefQuery; + fn struct_data() for db::StructDataQuery; + fn enum_data() for db::EnumDataQuery; } } } -- cgit v1.2.3 From 4ff161852016c6c15954d6f30bd637834a2b2b68 Mon Sep 17 00:00:00 2001 From: Florian Diebold Date: Mon, 24 Dec 2018 20:32:39 +0100 Subject: Do name resolution by namespace (types/values) --- crates/ra_analysis/src/completion/complete_path.rs | 2 +- .../ra_analysis/src/completion/completion_item.rs | 35 +++++++++++++++------- crates/ra_analysis/src/db.rs | 4 +-- 3 files changed, 28 insertions(+), 13 deletions(-) (limited to 'crates/ra_analysis/src') diff --git a/crates/ra_analysis/src/completion/complete_path.rs b/crates/ra_analysis/src/completion/complete_path.rs index ad4d68a33..8c00be499 100644 --- a/crates/ra_analysis/src/completion/complete_path.rs +++ b/crates/ra_analysis/src/completion/complete_path.rs @@ -8,7 +8,7 @@ pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) -> C (Some(path), Some(module)) => (path.clone(), module), _ => return Ok(()), }; - let def_id = match module.resolve_path(ctx.db, path)? { + let def_id = match module.resolve_path(ctx.db, path)?.take_types() { Some(it) => it, None => return Ok(()), }; diff --git a/crates/ra_analysis/src/completion/completion_item.rs b/crates/ra_analysis/src/completion/completion_item.rs index 911f08468..6d466c8bd 100644 --- a/crates/ra_analysis/src/completion/completion_item.rs +++ b/crates/ra_analysis/src/completion/completion_item.rs @@ -1,5 +1,7 @@ use crate::db; +use hir::PerNs; + /// `CompletionItem` describes a single completion variant in the editor pop-up. /// It is basically a POD with various properties. To construct a /// `CompletionItem`, use `new` method and the `Builder` struct. @@ -25,6 +27,8 @@ pub enum CompletionItemKind { Keyword, Module, Function, + Struct, + Enum, Binding, } @@ -117,16 +121,27 @@ impl Builder { db: &db::RootDatabase, resolution: &hir::Resolution, ) -> Builder { - if let Some(def_id) = resolution.def_id { - if let Ok(def) = def_id.resolve(db) { - let kind = match def { - hir::Def::Module(..) => CompletionItemKind::Module, - hir::Def::Function(..) => CompletionItemKind::Function, - _ => return self, - }; - self.kind = Some(kind); - } - } + let resolved = resolution.def_id.and_then(|d| d.resolve(db).ok()); + let kind = match resolved { + PerNs { + types: Some(hir::Def::Module(..)), + .. + } => CompletionItemKind::Module, + PerNs { + types: Some(hir::Def::Struct(..)), + .. + } => CompletionItemKind::Struct, + PerNs { + types: Some(hir::Def::Enum(..)), + .. + } => CompletionItemKind::Enum, + PerNs { + values: Some(hir::Def::Function(..)), + .. + } => CompletionItemKind::Function, + _ => return self, + }; + self.kind = Some(kind); self } } diff --git a/crates/ra_analysis/src/db.rs b/crates/ra_analysis/src/db.rs index 7043a0f4d..677745d57 100644 --- a/crates/ra_analysis/src/db.rs +++ b/crates/ra_analysis/src/db.rs @@ -95,8 +95,8 @@ salsa::database_storage! { fn submodules() for hir::db::SubmodulesQuery; fn infer() for hir::db::InferQuery; fn type_for_def() for hir::db::TypeForDefQuery; - fn struct_data() for db::StructDataQuery; - fn enum_data() for db::EnumDataQuery; + fn struct_data() for hir::db::StructDataQuery; + fn enum_data() for hir::db::EnumDataQuery; } } } -- cgit v1.2.3 From ab0b63992be0cec4999810096a53b40f63f90349 Mon Sep 17 00:00:00 2001 From: Florian Diebold Date: Tue, 25 Dec 2018 15:15:40 +0100 Subject: Implement basic completion for fields --- crates/ra_analysis/src/completion.rs | 8 +- crates/ra_analysis/src/completion/complete_dot.rs | 98 ++++++++++++++++++++++ .../src/completion/completion_context.rs | 34 ++++++-- .../ra_analysis/src/completion/completion_item.rs | 1 + 4 files changed, 133 insertions(+), 8 deletions(-) create mode 100644 crates/ra_analysis/src/completion/complete_dot.rs (limited to 'crates/ra_analysis/src') diff --git a/crates/ra_analysis/src/completion.rs b/crates/ra_analysis/src/completion.rs index d742d6295..fe580700f 100644 --- a/crates/ra_analysis/src/completion.rs +++ b/crates/ra_analysis/src/completion.rs @@ -1,6 +1,7 @@ mod completion_item; mod completion_context; +mod complete_dot; mod complete_fn_param; mod complete_keyword; mod complete_snippet; @@ -20,13 +21,13 @@ use crate::{ pub use crate::completion::completion_item::{CompletionItem, InsertText, CompletionItemKind}; -/// Main entry point for copmletion. We run comletion as a two-phase process. +/// Main entry point for completion. We run completion as a two-phase process. /// /// First, we look at the position and collect a so-called `CompletionContext. /// This is a somewhat messy process, because, during completion, syntax tree is -/// incomplete and can look readlly weired. +/// incomplete and can look really weird. /// -/// Once the context is collected, we run a series of completion routines whihc +/// Once the context is collected, we run a series of completion routines which /// look at the context and produce completion items. pub(crate) fn completions( db: &db::RootDatabase, @@ -43,6 +44,7 @@ pub(crate) fn completions( complete_snippet::complete_item_snippet(&mut acc, &ctx); complete_path::complete_path(&mut acc, &ctx)?; complete_scope::complete_scope(&mut acc, &ctx)?; + complete_dot::complete_dot(&mut acc, &ctx)?; Ok(Some(acc)) } diff --git a/crates/ra_analysis/src/completion/complete_dot.rs b/crates/ra_analysis/src/completion/complete_dot.rs new file mode 100644 index 000000000..fa62da210 --- /dev/null +++ b/crates/ra_analysis/src/completion/complete_dot.rs @@ -0,0 +1,98 @@ +use ra_syntax::ast::AstNode; +use hir::{Ty, Def}; + +use crate::Cancelable; +use crate::completion::{CompletionContext, Completions, CompletionKind, CompletionItem, CompletionItemKind}; + +/// Complete dot accesses, i.e. fields or methods (currently only fields). +pub(super) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) -> Cancelable<()> { + let module = if let Some(module) = &ctx.module { + module + } else { + return Ok(()); + }; + let function = if let Some(fn_def) = ctx.enclosing_fn { + hir::source_binder::function_from_module(ctx.db, module, fn_def) + } else { + return Ok(()); + }; + let receiver = if let Some(receiver) = ctx.dot_receiver { + receiver + } else { + return Ok(()); + }; + let infer_result = function.infer(ctx.db)?; + let receiver_ty = if let Some(ty) = infer_result.type_of_node(receiver.syntax()) { + ty + } else { + return Ok(()); + }; + if !ctx.is_method_call { + complete_fields(acc, ctx, receiver_ty)?; + } + Ok(()) +} + +fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, ty: Ty) -> Cancelable<()> { + // TODO: autoderef etc. + match ty { + Ty::Adt { def_id, .. } => { + match def_id.resolve(ctx.db)? { + Def::Struct(s) => { + let variant_data = s.variant_data(ctx.db)?; + for field in variant_data.fields() { + CompletionItem::new(CompletionKind::Reference, field.name().to_string()) + .kind(CompletionItemKind::Field) + .add_to(acc); + } + } + // TODO unions + _ => {} + } + } + Ty::Tuple(fields) => { + for (i, _ty) in fields.iter().enumerate() { + CompletionItem::new(CompletionKind::Reference, i.to_string()) + .kind(CompletionItemKind::Field) + .add_to(acc); + } + } + _ => {} + }; + Ok(()) +} + +#[cfg(test)] +mod tests { + use crate::completion::*; + + fn check_ref_completion(code: &str, expected_completions: &str) { + check_completion(code, expected_completions, CompletionKind::Reference); + } + + #[test] + fn test_struct_field_completion() { + check_ref_completion( + r" + struct A { the_field: u32 } + fn foo(a: A) { + a.<|> + } + ", + r#"the_field"#, + ); + } + + #[test] + fn test_no_struct_field_completion_for_method_call() { + check_ref_completion( + r" + struct A { the_field: u32 } + fn foo(a: A) { + a.<|>() + } + ", + r#""#, + ); + } +} diff --git a/crates/ra_analysis/src/completion/completion_context.rs b/crates/ra_analysis/src/completion/completion_context.rs index 064fbc6f7..12e98e870 100644 --- a/crates/ra_analysis/src/completion/completion_context.rs +++ b/crates/ra_analysis/src/completion/completion_context.rs @@ -31,6 +31,10 @@ pub(super) struct CompletionContext<'a> { pub(super) is_stmt: bool, /// Something is typed at the "top" level, in module or impl/trait. pub(super) is_new_item: bool, + /// The receiver if this is a field or method access, i.e. writing something.<|> + pub(super) dot_receiver: Option>, + /// If this is a method call in particular, i.e. the () are already there. + pub(super) is_method_call: bool, } impl<'a> CompletionContext<'a> { @@ -54,6 +58,8 @@ impl<'a> CompletionContext<'a> { after_if: false, is_stmt: false, is_new_item: false, + dot_receiver: None, + is_method_call: false, }; ctx.fill(original_file, position.offset); Ok(Some(ctx)) @@ -105,6 +111,12 @@ impl<'a> CompletionContext<'a> { _ => (), } + self.enclosing_fn = self + .leaf + .ancestors() + .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) + .find_map(ast::FnDef::cast); + let parent = match name_ref.syntax().parent() { Some(it) => it, None => return, @@ -120,11 +132,6 @@ impl<'a> CompletionContext<'a> { } if path.qualifier().is_none() { self.is_trivial_path = true; - self.enclosing_fn = self - .leaf - .ancestors() - .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) - .find_map(ast::FnDef::cast); self.is_stmt = match name_ref .syntax() @@ -145,6 +152,23 @@ impl<'a> CompletionContext<'a> { } } } + if let Some(_field_expr) = ast::FieldExpr::cast(parent) { + self.dot_receiver = self + .leaf + .ancestors() + .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) + .find_map(ast::FieldExpr::cast) + .and_then(ast::FieldExpr::expr); + } + if let Some(_method_call_expr) = ast::MethodCallExpr::cast(parent) { + self.dot_receiver = self + .leaf + .ancestors() + .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) + .find_map(ast::MethodCallExpr::cast) + .and_then(ast::MethodCallExpr::expr); + self.is_method_call = true; + } } } diff --git a/crates/ra_analysis/src/completion/completion_item.rs b/crates/ra_analysis/src/completion/completion_item.rs index 6d466c8bd..c9f9f495d 100644 --- a/crates/ra_analysis/src/completion/completion_item.rs +++ b/crates/ra_analysis/src/completion/completion_item.rs @@ -30,6 +30,7 @@ pub enum CompletionItemKind { Struct, Enum, Binding, + Field, } #[derive(Debug, PartialEq, Eq)] -- cgit v1.2.3 From 3e4d41d1e409315ce42cb3c3479236b5e73d0643 Mon Sep 17 00:00:00 2001 From: Florian Diebold Date: Tue, 25 Dec 2018 17:43:58 +0100 Subject: Determine receiver for completion in a more robust way Also rename a parameter. --- crates/ra_analysis/src/completion/complete_dot.rs | 4 +- .../src/completion/completion_context.rs | 48 +++++++++++++--------- 2 files changed, 31 insertions(+), 21 deletions(-) (limited to 'crates/ra_analysis/src') diff --git a/crates/ra_analysis/src/completion/complete_dot.rs b/crates/ra_analysis/src/completion/complete_dot.rs index fa62da210..93d657576 100644 --- a/crates/ra_analysis/src/completion/complete_dot.rs +++ b/crates/ra_analysis/src/completion/complete_dot.rs @@ -33,9 +33,9 @@ pub(super) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) -> Ca Ok(()) } -fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, ty: Ty) -> Cancelable<()> { +fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: Ty) -> Cancelable<()> { // TODO: autoderef etc. - match ty { + match receiver { Ty::Adt { def_id, .. } => { match def_id.resolve(ctx.db)? { Def::Struct(s) => { diff --git a/crates/ra_analysis/src/completion/completion_context.rs b/crates/ra_analysis/src/completion/completion_context.rs index 12e98e870..978772fd4 100644 --- a/crates/ra_analysis/src/completion/completion_context.rs +++ b/crates/ra_analysis/src/completion/completion_context.rs @@ -1,12 +1,13 @@ use ra_editor::find_node_at_offset; use ra_text_edit::AtomTextEdit; use ra_syntax::{ - algo::find_leaf_at_offset, + algo::{find_leaf_at_offset, find_covering_node}, ast, AstNode, SyntaxNodeRef, SourceFileNode, TextUnit, + TextRange, SyntaxKind::*, }; use hir::source_binder; @@ -65,7 +66,7 @@ impl<'a> CompletionContext<'a> { Ok(Some(ctx)) } - fn fill(&mut self, original_file: &SourceFileNode, offset: TextUnit) { + fn fill(&mut self, original_file: &'a SourceFileNode, offset: TextUnit) { // Insert a fake ident to get a valid parse tree. We will use this file // to determine context, though the original_file will be used for // actual completion. @@ -82,7 +83,7 @@ impl<'a> CompletionContext<'a> { self.is_param = true; return; } - self.classify_name_ref(&file, name_ref); + self.classify_name_ref(original_file, name_ref); } // Otherwise, see if this is a declaration. We can use heuristics to @@ -94,7 +95,7 @@ impl<'a> CompletionContext<'a> { } } } - fn classify_name_ref(&mut self, file: &SourceFileNode, name_ref: ast::NameRef) { + fn classify_name_ref(&mut self, original_file: &'a SourceFileNode, name_ref: ast::NameRef) { let name_range = name_ref.syntax().range(); let top_node = name_ref .syntax() @@ -144,7 +145,9 @@ impl<'a> CompletionContext<'a> { }; if let Some(off) = name_ref.syntax().range().start().checked_sub(2.into()) { - if let Some(if_expr) = find_node_at_offset::(file.syntax(), off) { + if let Some(if_expr) = + find_node_at_offset::(original_file.syntax(), off) + { if if_expr.syntax().range().end() < name_ref.syntax().range().start() { self.after_if = true; } @@ -152,26 +155,33 @@ impl<'a> CompletionContext<'a> { } } } - if let Some(_field_expr) = ast::FieldExpr::cast(parent) { - self.dot_receiver = self - .leaf - .ancestors() - .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) - .find_map(ast::FieldExpr::cast) - .and_then(ast::FieldExpr::expr); + if let Some(field_expr) = ast::FieldExpr::cast(parent) { + // The receiver comes before the point of insertion of the fake + // ident, so it should have the same range in the non-modified file + self.dot_receiver = field_expr + .expr() + .map(|e| e.syntax().range()) + .and_then(|r| find_node_with_range(original_file.syntax(), r)); } - if let Some(_method_call_expr) = ast::MethodCallExpr::cast(parent) { - self.dot_receiver = self - .leaf - .ancestors() - .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) - .find_map(ast::MethodCallExpr::cast) - .and_then(ast::MethodCallExpr::expr); + if let Some(method_call_expr) = ast::MethodCallExpr::cast(parent) { + // As above + self.dot_receiver = method_call_expr + .expr() + .map(|e| e.syntax().range()) + .and_then(|r| find_node_with_range(original_file.syntax(), r)); self.is_method_call = true; } } } +fn find_node_with_range<'a, N: AstNode<'a>>( + syntax: SyntaxNodeRef<'a>, + range: TextRange, +) -> Option { + let node = find_covering_node(syntax, range); + node.ancestors().find_map(N::cast) +} + fn is_node<'a, N: AstNode<'a>>(node: SyntaxNodeRef<'a>) -> bool { match node.ancestors().filter_map(N::cast).next() { None => false, -- cgit v1.2.3 From cdca39706121b2d1734a94938a2372da881e10c6 Mon Sep 17 00:00:00 2001 From: Florian Diebold Date: Tue, 25 Dec 2018 21:14:13 +0100 Subject: Add a hir::TypeRef as an intermediate between ast::TypeRef and ty::Ty --- crates/ra_analysis/src/completion/complete_path.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'crates/ra_analysis/src') diff --git a/crates/ra_analysis/src/completion/complete_path.rs b/crates/ra_analysis/src/completion/complete_path.rs index 8c00be499..aaa2c7cee 100644 --- a/crates/ra_analysis/src/completion/complete_path.rs +++ b/crates/ra_analysis/src/completion/complete_path.rs @@ -8,7 +8,7 @@ pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) -> C (Some(path), Some(module)) => (path.clone(), module), _ => return Ok(()), }; - let def_id = match module.resolve_path(ctx.db, path)?.take_types() { + let def_id = match module.resolve_path(ctx.db, &path)?.take_types() { Some(it) => it, None => return Ok(()), }; -- cgit v1.2.3 From bc745a139674f289386f3081458793f756cab5b9 Mon Sep 17 00:00:00 2001 From: Florian Diebold Date: Tue, 25 Dec 2018 21:40:33 +0100 Subject: Resolve field types lazily I.e. not already when getting the HIR for the struct. --- crates/ra_analysis/src/db.rs | 1 + 1 file changed, 1 insertion(+) (limited to 'crates/ra_analysis/src') diff --git a/crates/ra_analysis/src/db.rs b/crates/ra_analysis/src/db.rs index 677745d57..036e284bf 100644 --- a/crates/ra_analysis/src/db.rs +++ b/crates/ra_analysis/src/db.rs @@ -95,6 +95,7 @@ salsa::database_storage! { fn submodules() for hir::db::SubmodulesQuery; fn infer() for hir::db::InferQuery; fn type_for_def() for hir::db::TypeForDefQuery; + fn type_for_field() for hir::db::TypeForFieldQuery; fn struct_data() for hir::db::StructDataQuery; fn enum_data() for hir::db::EnumDataQuery; } -- cgit v1.2.3