]> git.lizzy.rs Git - rust.git/commitdiff
Implement basic completion for fields
authorFlorian Diebold <flodiebold@gmail.com>
Tue, 25 Dec 2018 14:15:40 +0000 (15:15 +0100)
committerFlorian Diebold <flodiebold@gmail.com>
Tue, 25 Dec 2018 14:27:15 +0000 (15:27 +0100)
crates/ra_analysis/src/completion.rs
crates/ra_analysis/src/completion/complete_dot.rs [new file with mode: 0644]
crates/ra_analysis/src/completion/completion_context.rs
crates/ra_analysis/src/completion/completion_item.rs
crates/ra_hir/src/adt.rs
crates/ra_hir/src/lib.rs
crates/ra_hir/src/ty.rs
crates/ra_lsp_server/src/conv.rs
crates/ra_syntax/src/ast/generated.rs
crates/ra_syntax/src/grammar.ron

index d742d62955a05ed186d825d3de43db1c9dfa14d6..fe580700ff942c27c2767c9d57b8487e2c9fc25e 100644 (file)
@@ -1,6 +1,7 @@
 mod completion_item;
 mod completion_context;
 
+mod complete_dot;
 mod complete_fn_param;
 mod complete_keyword;
 mod complete_snippet;
 
 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 (file)
index 0000000..fa62da2
--- /dev/null
@@ -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#""#,
+        );
+    }
+}
index 064fbc6f7be89751ea910863e9b784e3224691b3..12e98e870cd281f4c18f1a8f68a77d185d39cde5 100644 (file)
@@ -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<ast::Expr<'a>>,
+    /// 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 @@ pub(super) fn new(
             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 @@ fn classify_name_ref(&mut self, file: &SourceFileNode, name_ref: ast::NameRef) {
             _ => (),
         }
 
+        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 @@ fn classify_name_ref(&mut self, file: &SourceFileNode, name_ref: ast::NameRef) {
             }
             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 @@ fn classify_name_ref(&mut self, file: &SourceFileNode, name_ref: ast::NameRef) {
                 }
             }
         }
+        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;
+        }
     }
 }
 
index 6d466c8bdd40dfcc897291d271db0809b32f16a5..c9f9f495da88098b5ee7a96765c7d1efd2c1d003 100644 (file)
@@ -30,6 +30,7 @@ pub enum CompletionItemKind {
     Struct,
     Enum,
     Binding,
+    Field,
 }
 
 #[derive(Debug, PartialEq, Eq)]
index 03770ed7db4ae6515b3fadfe14285fef608e15e6..e65f8deb866cc171d59290266bdbb82362ef4aab 100644 (file)
@@ -124,6 +124,15 @@ pub struct StructField {
     ty: Ty,
 }
 
+impl StructField {
+    pub fn name(&self) -> SmolStr {
+        self.name.clone()
+    }
+    pub fn ty(&self) -> Ty {
+        self.ty.clone()
+    }
+}
+
 /// Fields of an enum variant or struct
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub enum VariantData {
@@ -168,7 +177,10 @@ pub fn new(db: &impl HirDatabase, module: &Module, flavor: StructFlavor) -> Canc
     }
 
     pub(crate) fn get_field_ty(&self, field_name: &str) -> Option<Ty> {
-        self.fields().iter().find(|f| f.name == field_name).map(|f| f.ty.clone())
+        self.fields()
+            .iter()
+            .find(|f| f.name == field_name)
+            .map(|f| f.ty.clone())
     }
 
     pub fn fields(&self) -> &[StructField] {
index 796970d8a2de608194c257dcac6028955c453249..68fdbb7ea1857de3c19bfa3843567093b692371d 100644 (file)
@@ -44,6 +44,7 @@ macro_rules! ctry {
     module::{Module, ModuleId, Problem, nameres::{ItemMap, PerNs, Namespace}, ModuleScope, Resolution},
     function::{Function, FnScopes},
     adt::{Struct, Enum},
+    ty::Ty,
 };
 
 pub use self::function::FnSignatureInfo;
index f931f3c87217e043caa90d90c1f40110a76a2481..83da13f1a0a3d333fbb054484e833697e03d47bc 100644 (file)
@@ -574,7 +574,8 @@ fn infer_expr(&mut self, expr: ast::Expr) -> Cancelable<Ty> {
                     match receiver_ty {
                         Ty::Tuple(fields) => {
                             let i = text.parse::<usize>().ok();
-                            i.and_then(|i| fields.get(i).cloned()).unwrap_or(Ty::Unknown)
+                            i.and_then(|i| fields.get(i).cloned())
+                                .unwrap_or(Ty::Unknown)
                         }
                         Ty::Adt { def_id, .. } => {
                             let field_ty = match def_id.resolve(self.db)? {
@@ -589,7 +590,7 @@ fn infer_expr(&mut self, expr: ast::Expr) -> Cancelable<Ty> {
                 } else {
                     Ty::Unknown
                 }
-            },
+            }
             ast::Expr::TryExpr(e) => {
                 let _inner_ty = if let Some(e) = e.expr() {
                     self.infer_expr(e)?
index af52893114c5a01c3d26dabb709d3cdaa80561bf..c0e4e3a36a86aaec491c34a865549e683752a4bf 100644 (file)
@@ -58,6 +58,7 @@ fn conv(self) -> <Self as Conv>::Output {
             CompletionItemKind::Struct => Struct,
             CompletionItemKind::Enum => Enum,
             CompletionItemKind::Binding => Variable,
+            CompletionItemKind::Field => Field,
         }
     }
 }
index 4e0550487f793609ce6815831d45dd64476ad9f7..6b2800a0e4d2e7e338bdefbeb2671beedf44b85f 100644 (file)
@@ -2030,6 +2030,10 @@ impl<'a> MethodCallExpr<'a> {
     pub fn expr(self) -> Option<Expr<'a>> {
         super::child_opt(self)
     }
+
+    pub fn name_ref(self) -> Option<NameRef<'a>> {
+        super::child_opt(self)
+    }
 }
 
 // Module
index 923da032400034030c99dfd83d1f122401dde5a2..dcde32923ba93c5f459681c329303c5ddb74af01 100644 (file)
@@ -403,7 +403,7 @@ Grammar(
         ),
         "MethodCallExpr": (
             traits: ["ArgListOwner"],
-            options: [ "Expr" ],
+            options: [ "Expr", "NameRef" ],
         ),
         "IndexExpr": (),
         "FieldExpr": (options: ["Expr", "NameRef"]),