]> git.lizzy.rs Git - rust.git/blobdiff - crates/ide_completion/src/context.rs
simplify
[rust.git] / crates / ide_completion / src / context.rs
index 89e9bda7839290a465f92d6b306c1febe4f5c72f..66577df941da0e19e48c1f98c348994b24a6ee2c 100644 (file)
@@ -1,26 +1,53 @@
 //! See `CompletionContext` structure.
 
 use hir::{Local, ScopeDef, Semantics, SemanticsScope, Type};
-use ide_db::base_db::{FilePosition, SourceDatabase};
-use ide_db::{call_info::ActiveParameter, RootDatabase};
+use ide_db::{
+    base_db::{FilePosition, SourceDatabase},
+    call_info::ActiveParameter,
+    RootDatabase,
+};
 use syntax::{
-    algo::find_node_at_offset, ast, match_ast, AstNode, NodeOrToken, SyntaxKind::*, SyntaxNode,
-    SyntaxToken, TextRange, TextSize,
+    algo::find_node_at_offset,
+    ast::{self, NameOrNameRef, NameOwner},
+    match_ast, AstNode, NodeOrToken,
+    SyntaxKind::{self, *},
+    SyntaxNode, SyntaxToken, TextRange, TextSize, T,
 };
-
 use text_edit::Indel;
 
 use crate::{
     patterns::{
-        fn_is_prev, for_is_prev2, has_bind_pat_parent, has_block_expr_parent,
-        has_field_list_parent, has_impl_as_prev_sibling, has_impl_parent,
-        has_item_list_or_source_file_parent, has_ref_parent, has_trait_as_prev_sibling,
-        has_trait_parent, if_is_prev, inside_impl_trait_block, is_in_loop_body, is_match_arm,
-        unsafe_is_prev,
+        for_is_prev2, has_bind_pat_parent, has_block_expr_parent, has_field_list_parent,
+        has_impl_parent, has_item_list_or_source_file_parent, has_prev_sibling, has_ref_parent,
+        has_trait_parent, inside_impl_trait_block, is_in_loop_body, is_match_arm, previous_token,
     },
     CompletionConfig,
 };
 
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub(crate) enum PatternRefutability {
+    Refutable,
+    Irrefutable,
+}
+
+/// Direct parent container of the cursor position
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub(crate) enum ImmediateLocation {
+    Impl,
+    Trait,
+    RecordFieldList,
+    RefPatOrExpr,
+    IdentPat,
+    BlockExpr,
+    ItemList,
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub(crate) enum PrevSibling {
+    Trait,
+    Impl,
+}
+
 /// `CompletionContext` is created early during completion to figure out, where
 /// exactly is the cursor, syntax-wise.
 #[derive(Debug)]
@@ -35,22 +62,36 @@ pub(crate) struct CompletionContext<'a> {
     /// The token before the cursor, in the macro-expanded file.
     pub(super) token: SyntaxToken,
     pub(super) krate: Option<hir::Crate>,
-    pub(super) expected_name: Option<String>,
+    pub(super) expected_name: Option<NameOrNameRef>,
     pub(super) expected_type: Option<Type>,
     pub(super) name_ref_syntax: Option<ast::NameRef>,
-    pub(super) function_syntax: Option<ast::Fn>,
+
     pub(super) use_item_syntax: Option<ast::Use>,
+
+    /// The parent function of the cursor position if it exists.
+    pub(super) function_def: Option<ast::Fn>,
+    /// The parent impl of the cursor position if it exists.
+    pub(super) impl_def: Option<ast::Impl>,
+
+    /// RecordExpr the token is a field of
     pub(super) record_lit_syntax: Option<ast::RecordExpr>,
+    /// RecordPat the token is a field of
     pub(super) record_pat_syntax: Option<ast::RecordPat>,
-    pub(super) record_field_syntax: Option<ast::RecordExprField>,
-    pub(super) impl_def: Option<ast::Impl>,
+
+    // potentially set if we are completing a lifetime
+    pub(super) lifetime_syntax: Option<ast::Lifetime>,
+    pub(super) lifetime_param_syntax: Option<ast::LifetimeParam>,
+    pub(super) lifetime_allowed: bool,
+    pub(super) is_label_ref: bool,
+
+    // potentially set if we are completing a name
+    pub(super) is_pat_or_const: Option<PatternRefutability>,
+    pub(super) is_param: bool,
+
+    pub(super) completion_location: Option<ImmediateLocation>,
+
     /// FIXME: `ActiveParameter` is string-based, which is very very wrong
     pub(super) active_parameter: Option<ActiveParameter>,
-    pub(super) is_param: bool,
-    /// If a name-binding or reference to a const in a pattern.
-    /// Irrefutable patterns (like let) are excluded.
-    pub(super) is_pat_binding_or_const: bool,
-    pub(super) is_irrefutable_pat_binding: bool,
     /// A single-indent path, like `foo`. `::foo` should not be considered a trivial path.
     pub(super) is_trivial_path: bool,
     /// If not a trivial path, the prefix (qualifier).
@@ -75,26 +116,17 @@ pub(crate) struct CompletionContext<'a> {
     pub(super) has_type_args: bool,
     pub(super) attribute_under_caret: Option<ast::Attr>,
     pub(super) mod_declaration_under_caret: Option<ast::Module>,
-    pub(super) unsafe_is_prev: bool,
-    pub(super) if_is_prev: bool,
-    pub(super) block_expr_parent: bool,
-    pub(super) bind_pat_parent: bool,
-    pub(super) ref_pat_parent: bool,
+    pub(super) locals: Vec<(String, Local)>,
+
+    // keyword patterns
+    pub(super) previous_token: Option<SyntaxToken>,
+    pub(super) prev_sibling: Option<PrevSibling>,
     pub(super) in_loop_body: bool,
-    pub(super) has_trait_parent: bool,
-    pub(super) has_impl_parent: bool,
-    pub(super) inside_impl_trait_block: bool,
-    pub(super) has_field_list_parent: bool,
-    pub(super) trait_as_prev_sibling: bool,
-    pub(super) impl_as_prev_sibling: bool,
     pub(super) is_match_arm: bool,
-    pub(super) has_item_list_or_source_file_parent: bool,
-    pub(super) for_is_prev2: bool,
-    pub(super) fn_is_prev: bool,
     pub(super) incomplete_let: bool,
-    pub(super) locals: Vec<(String, Local)>,
-}
 
+    no_completion_required: bool,
+}
 impl<'a> CompletionContext<'a> {
     pub(super) fn new(
         db: &'a RootDatabase,
@@ -120,7 +152,7 @@ pub(super) fn new(
         let original_token =
             original_file.syntax().token_at_offset(position.offset).left_biased()?;
         let token = sema.descend_into_macros(original_token.clone());
-        let scope = sema.scope_at_offset(&token.parent(), position.offset);
+        let scope = sema.scope_at_offset(&token, position.offset);
         let mut locals = vec![];
         scope.process_all_names(&mut |name, scope| {
             if let ScopeDef::Local(local) = scope {
@@ -136,19 +168,21 @@ pub(super) fn new(
             original_token,
             token,
             krate,
+            lifetime_allowed: false,
             expected_name: None,
             expected_type: None,
             name_ref_syntax: None,
-            function_syntax: None,
+            lifetime_syntax: None,
+            lifetime_param_syntax: None,
+            function_def: None,
             use_item_syntax: None,
             record_lit_syntax: None,
             record_pat_syntax: None,
-            record_field_syntax: None,
             impl_def: None,
             active_parameter: ActiveParameter::at(db, position),
+            is_label_ref: false,
             is_param: false,
-            is_pat_binding_or_const: false,
-            is_irrefutable_pat_binding: false,
+            is_pat_or_const: None,
             is_trivial_path: false,
             path_qual: None,
             after_if: false,
@@ -164,67 +198,57 @@ pub(super) fn new(
             has_type_args: false,
             attribute_under_caret: None,
             mod_declaration_under_caret: None,
-            unsafe_is_prev: false,
-            if_is_prev: false,
-            block_expr_parent: false,
-            bind_pat_parent: false,
-            ref_pat_parent: false,
+            previous_token: None,
             in_loop_body: false,
-            has_trait_parent: false,
-            has_impl_parent: false,
-            inside_impl_trait_block: false,
-            has_field_list_parent: false,
-            trait_as_prev_sibling: false,
-            impl_as_prev_sibling: false,
+            completion_location: None,
+            prev_sibling: None,
             is_match_arm: false,
-            has_item_list_or_source_file_parent: false,
-            for_is_prev2: false,
-            fn_is_prev: false,
+            no_completion_required: false,
             incomplete_let: false,
             locals,
         };
 
         let mut original_file = original_file.syntax().clone();
-        let mut hypothetical_file = file_with_fake_ident.syntax().clone();
+        let mut speculative_file = file_with_fake_ident.syntax().clone();
         let mut offset = position.offset;
         let mut fake_ident_token = fake_ident_token;
 
         // Are we inside a macro call?
         while let (Some(actual_macro_call), Some(macro_call_with_fake_ident)) = (
             find_node_at_offset::<ast::MacroCall>(&original_file, offset),
-            find_node_at_offset::<ast::MacroCall>(&hypothetical_file, offset),
+            find_node_at_offset::<ast::MacroCall>(&speculative_file, offset),
         ) {
             if actual_macro_call.path().as_ref().map(|s| s.syntax().text())
                 != macro_call_with_fake_ident.path().as_ref().map(|s| s.syntax().text())
             {
                 break;
             }
-            let hypothetical_args = match macro_call_with_fake_ident.token_tree() {
+            let speculative_args = match macro_call_with_fake_ident.token_tree() {
                 Some(tt) => tt,
                 None => break,
             };
-            if let (Some(actual_expansion), Some(hypothetical_expansion)) = (
+            if let (Some(actual_expansion), Some(speculative_expansion)) = (
                 ctx.sema.expand(&actual_macro_call),
                 ctx.sema.speculative_expand(
                     &actual_macro_call,
-                    &hypothetical_args,
+                    &speculative_args,
                     fake_ident_token,
                 ),
             ) {
-                let new_offset = hypothetical_expansion.1.text_range().start();
+                let new_offset = speculative_expansion.1.text_range().start();
                 if new_offset > actual_expansion.text_range().end() {
                     break;
                 }
                 original_file = actual_expansion;
-                hypothetical_file = hypothetical_expansion.0;
-                fake_ident_token = hypothetical_expansion.1;
+                speculative_file = speculative_expansion.0;
+                fake_ident_token = speculative_expansion.1;
                 offset = new_offset;
             } else {
                 break;
             }
         }
-        ctx.fill_keyword_patterns(&hypothetical_file, offset);
-        ctx.fill(&original_file, hypothetical_file, offset);
+        ctx.fill_keyword_patterns(&speculative_file, offset);
+        ctx.fill(&original_file, speculative_file, offset);
         Some(ctx)
     }
 
@@ -234,187 +258,289 @@ pub(super) fn new(
     ///   Exception for this case is `impl Trait for Foo`, where we would like to hint trait method names.
     /// - `for _ i$0` -- obviously, it'll be "in" keyword.
     pub(crate) fn no_completion_required(&self) -> bool {
-        (self.fn_is_prev && !self.inside_impl_trait_block) || self.for_is_prev2
+        self.no_completion_required
     }
 
     /// The range of the identifier that is being completed.
     pub(crate) fn source_range(&self) -> TextRange {
         // check kind of macro-expanded token, but use range of original token
         let kind = self.token.kind();
-        if kind == IDENT || kind == UNDERSCORE || kind.is_keyword() {
+        if kind == IDENT || kind == LIFETIME_IDENT || kind == UNDERSCORE || kind.is_keyword() {
             cov_mark::hit!(completes_if_prefix_is_keyword);
             self.original_token.text_range()
+        } else if kind == CHAR {
+            // assume we are completing a lifetime but the user has only typed the '
+            cov_mark::hit!(completes_if_lifetime_without_idents);
+            TextRange::at(self.original_token.text_range().start(), TextSize::from(1))
         } else {
             TextRange::empty(self.position.offset)
         }
     }
 
+    pub(crate) fn previous_token_is(&self, kind: SyntaxKind) -> bool {
+        self.previous_token.as_ref().map_or(false, |tok| tok.kind() == kind)
+    }
+
+    pub(crate) fn has_impl_or_trait_parent(&self) -> bool {
+        matches!(
+            self.completion_location,
+            Some(ImmediateLocation::Trait) | Some(ImmediateLocation::Impl)
+        )
+    }
+
+    pub(crate) fn has_block_expr_parent(&self) -> bool {
+        matches!(self.completion_location, Some(ImmediateLocation::BlockExpr))
+    }
+
+    pub(crate) fn has_item_list_parent(&self) -> bool {
+        matches!(self.completion_location, Some(ImmediateLocation::ItemList))
+    }
+
+    pub(crate) fn has_ident_or_ref_pat_parent(&self) -> bool {
+        matches!(
+            self.completion_location,
+            Some(ImmediateLocation::IdentPat) | Some(ImmediateLocation::RefPatOrExpr)
+        )
+    }
+
+    pub(crate) fn has_impl_parent(&self) -> bool {
+        matches!(self.completion_location, Some(ImmediateLocation::Impl))
+    }
+
+    pub(crate) fn has_field_list_parent(&self) -> bool {
+        matches!(self.completion_location, Some(ImmediateLocation::RecordFieldList))
+    }
+
+    pub(crate) fn has_impl_or_trait_prev_sibling(&self) -> bool {
+        self.prev_sibling.is_some()
+    }
+
+    pub(crate) fn is_path_disallowed(&self) -> bool {
+        self.record_lit_syntax.is_some()
+            || self.record_pat_syntax.is_some()
+            || self.attribute_under_caret.is_some()
+            || self.mod_declaration_under_caret.is_some()
+            || self.has_impl_or_trait_parent()
+    }
+
     fn fill_keyword_patterns(&mut self, file_with_fake_ident: &SyntaxNode, offset: TextSize) {
         let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap();
         let syntax_element = NodeOrToken::Token(fake_ident_token);
-        self.block_expr_parent = has_block_expr_parent(syntax_element.clone());
-        self.unsafe_is_prev = unsafe_is_prev(syntax_element.clone());
-        self.if_is_prev = if_is_prev(syntax_element.clone());
-        self.bind_pat_parent = has_bind_pat_parent(syntax_element.clone());
-        self.ref_pat_parent = has_ref_parent(syntax_element.clone());
+        self.previous_token = previous_token(syntax_element.clone());
         self.in_loop_body = is_in_loop_body(syntax_element.clone());
-        self.has_trait_parent = has_trait_parent(syntax_element.clone());
-        self.has_impl_parent = has_impl_parent(syntax_element.clone());
-        self.inside_impl_trait_block = inside_impl_trait_block(syntax_element.clone());
-        self.has_field_list_parent = has_field_list_parent(syntax_element.clone());
-        self.impl_as_prev_sibling = has_impl_as_prev_sibling(syntax_element.clone());
-        self.trait_as_prev_sibling = has_trait_as_prev_sibling(syntax_element.clone());
         self.is_match_arm = is_match_arm(syntax_element.clone());
-        self.has_item_list_or_source_file_parent =
-            has_item_list_or_source_file_parent(syntax_element.clone());
+        if has_prev_sibling(syntax_element.clone(), IMPL) {
+            self.prev_sibling = Some(PrevSibling::Impl)
+        } else if has_prev_sibling(syntax_element.clone(), TRAIT) {
+            self.prev_sibling = Some(PrevSibling::Trait)
+        }
+
+        if has_block_expr_parent(syntax_element.clone()) {
+            self.completion_location = Some(ImmediateLocation::BlockExpr);
+        } else if has_bind_pat_parent(syntax_element.clone()) {
+            self.completion_location = Some(ImmediateLocation::IdentPat);
+        } else if has_ref_parent(syntax_element.clone()) {
+            self.completion_location = Some(ImmediateLocation::RefPatOrExpr);
+        } else if has_impl_parent(syntax_element.clone()) {
+            self.completion_location = Some(ImmediateLocation::Impl);
+        } else if has_field_list_parent(syntax_element.clone()) {
+            self.completion_location = Some(ImmediateLocation::RecordFieldList);
+        } else if has_trait_parent(syntax_element.clone()) {
+            self.completion_location = Some(ImmediateLocation::Trait);
+        } else if has_item_list_or_source_file_parent(syntax_element.clone()) {
+            self.completion_location = Some(ImmediateLocation::ItemList);
+        }
+
         self.mod_declaration_under_caret =
             find_node_at_offset::<ast::Module>(&file_with_fake_ident, offset)
                 .filter(|module| module.item_list().is_none());
-        self.for_is_prev2 = for_is_prev2(syntax_element.clone());
-        self.fn_is_prev = fn_is_prev(syntax_element.clone());
         self.incomplete_let =
             syntax_element.ancestors().take(6).find_map(ast::LetStmt::cast).map_or(false, |it| {
                 it.syntax().text_range().end() == syntax_element.text_range().end()
             });
+
+        let inside_impl_trait_block = inside_impl_trait_block(syntax_element.clone());
+        let fn_is_prev = self.previous_token_is(T![fn]);
+        let for_is_prev2 = for_is_prev2(syntax_element.clone());
+        self.no_completion_required = (fn_is_prev && !inside_impl_trait_block) || for_is_prev2;
     }
 
     fn fill_impl_def(&mut self) {
         self.impl_def = self
             .sema
-            .ancestors_with_macros(self.token.parent())
+            .token_ancestors_with_macros(self.token.clone())
             .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
             .find_map(ast::Impl::cast);
     }
 
+    fn expected_type_and_name(&self) -> (Option<Type>, Option<NameOrNameRef>) {
+        let mut node = match self.token.parent() {
+            Some(it) => it,
+            None => return (None, None),
+        };
+        loop {
+            break match_ast! {
+                match node {
+                    ast::LetStmt(it) => {
+                        cov_mark::hit!(expected_type_let_with_leading_char);
+                        cov_mark::hit!(expected_type_let_without_leading_char);
+                        let ty = it.pat()
+                            .and_then(|pat| self.sema.type_of_pat(&pat))
+                            .or_else(|| it.initializer().and_then(|it| self.sema.type_of_expr(&it)));
+                        let name = if let Some(ast::Pat::IdentPat(ident)) = it.pat() {
+                            ident.name().map(NameOrNameRef::Name)
+                        } else {
+                            None
+                        };
+
+                        (ty, name)
+                    },
+                    ast::ArgList(_it) => {
+                        cov_mark::hit!(expected_type_fn_param_with_leading_char);
+                        cov_mark::hit!(expected_type_fn_param_without_leading_char);
+                        ActiveParameter::at_token(
+                            &self.sema,
+                            self.token.clone(),
+                        ).map(|ap| {
+                            let name = ap.ident().map(NameOrNameRef::Name);
+                            (Some(ap.ty), name)
+                        })
+                        .unwrap_or((None, None))
+                    },
+                    ast::RecordExprFieldList(_it) => {
+                        cov_mark::hit!(expected_type_struct_field_without_leading_char);
+                        // wouldn't try {} be nice...
+                        (|| {
+                            let expr_field = self.token.prev_sibling_or_token()?
+                                      .into_node()
+                                      .and_then(|node| ast::RecordExprField::cast(node))?;
+                            let (_, _, ty) = self.sema.resolve_record_field(&expr_field)?;
+                            Some((
+                                Some(ty),
+                                expr_field.field_name().map(NameOrNameRef::NameRef),
+                            ))
+                        })().unwrap_or((None, None))
+                    },
+                    ast::RecordExprField(it) => {
+                        cov_mark::hit!(expected_type_struct_field_with_leading_char);
+                        (
+                            it.expr().as_ref().and_then(|e| self.sema.type_of_expr(e)),
+                            it.field_name().map(NameOrNameRef::NameRef),
+                        )
+                    },
+                    ast::MatchExpr(it) => {
+                        cov_mark::hit!(expected_type_match_arm_without_leading_char);
+                        let ty = it.expr()
+                            .and_then(|e| self.sema.type_of_expr(&e));
+                        (ty, None)
+                    },
+                    ast::IfExpr(it) => {
+                        cov_mark::hit!(expected_type_if_let_without_leading_char);
+                        let ty = it.condition()
+                            .and_then(|cond| cond.expr())
+                            .and_then(|e| self.sema.type_of_expr(&e));
+                        (ty, None)
+                    },
+                    ast::IdentPat(it) => {
+                        cov_mark::hit!(expected_type_if_let_with_leading_char);
+                        cov_mark::hit!(expected_type_match_arm_with_leading_char);
+                        let ty = self.sema.type_of_pat(&ast::Pat::from(it));
+                        (ty, None)
+                    },
+                    ast::Fn(it) => {
+                        cov_mark::hit!(expected_type_fn_ret_with_leading_char);
+                        cov_mark::hit!(expected_type_fn_ret_without_leading_char);
+                        let def = self.sema.to_def(&it);
+                        (def.map(|def| def.ret_type(self.db)), None)
+                    },
+                    ast::ClosureExpr(it) => {
+                        let ty = self.sema.type_of_expr(&it.into());
+                        ty.and_then(|ty| ty.as_callable(self.db))
+                            .map(|c| (Some(c.return_type()), None))
+                            .unwrap_or((None, None))
+                    },
+                    ast::Stmt(_it) => (None, None),
+                    _ => {
+                        match node.parent() {
+                            Some(n) => {
+                                node = n;
+                                continue;
+                            },
+                            None => (None, None),
+                        }
+                    },
+                }
+            };
+        }
+    }
+
     fn fill(
         &mut self,
         original_file: &SyntaxNode,
         file_with_fake_ident: SyntaxNode,
         offset: TextSize,
     ) {
-        let expected = {
-            let mut node = self.token.parent();
-            loop {
-                let ret = match_ast! {
-                    match node {
-                        ast::LetStmt(it) => {
-                            cov_mark::hit!(expected_type_let_with_leading_char);
-                            cov_mark::hit!(expected_type_let_without_leading_char);
-                            let ty = it.pat()
-                                .and_then(|pat| self.sema.type_of_pat(&pat));
-                            let name = if let Some(ast::Pat::IdentPat(ident)) = it.pat() {
-                                Some(ident.syntax().text().to_string())
-                            } else {
-                                None
-                            };
-
-                            (ty, name)
-                        },
-                        ast::ArgList(it) => {
-                            cov_mark::hit!(expected_type_fn_param_with_leading_char);
-                            cov_mark::hit!(expected_type_fn_param_without_leading_char);
-                            ActiveParameter::at_token(
-                                &self.sema,
-                                self.token.clone(),
-                            ).map(|ap| (Some(ap.ty), Some(ap.name)))
-                            .unwrap_or((None, None))
-                        },
-                        ast::RecordExprFieldList(it) => {
-                            cov_mark::hit!(expected_type_struct_field_without_leading_char);
-                            self.token.prev_sibling_or_token()
-                                .and_then(|se| se.into_node())
-                                .and_then(|node| ast::RecordExprField::cast(node))
-                                .and_then(|rf| self.sema.resolve_record_field(&rf))
-                                .map(|f|(
-                                    Some(f.0.signature_ty(self.db)),
-                                    Some(f.0.name(self.db).to_string()),
-                                ))
-                                .unwrap_or((None, None))
-                        },
-                        ast::RecordExprField(it) => {
-                            cov_mark::hit!(expected_type_struct_field_with_leading_char);
-                            self.sema
-                                .resolve_record_field(&it)
-                                .map(|f|(
-                                    Some(f.0.signature_ty(self.db)),
-                                    Some(f.0.name(self.db).to_string()),
-                                ))
-                                .unwrap_or((None, None))
-                        },
-                        ast::MatchExpr(it) => {
-                            cov_mark::hit!(expected_type_match_arm_without_leading_char);
-                            let ty = it.expr()
-                                .and_then(|e| self.sema.type_of_expr(&e));
-
-                            (ty, None)
-                        },
-                        ast::IdentPat(it) => {
-                            cov_mark::hit!(expected_type_if_let_with_leading_char);
-                            cov_mark::hit!(expected_type_if_let_without_leading_char);
-                            cov_mark::hit!(expected_type_match_arm_with_leading_char);
-                            let ty = self.sema.type_of_pat(&ast::Pat::from(it));
-
-                            (ty, None)
-                        },
-                        ast::Fn(it) => {
-                            cov_mark::hit!(expected_type_fn_ret_with_leading_char);
-                            cov_mark::hit!(expected_type_fn_ret_without_leading_char);
-                            let ty = self.token.ancestors()
-                                .find_map(|ancestor| ast::Expr::cast(ancestor))
-                                .and_then(|expr| self.sema.type_of_expr(&expr));
-
-                            (ty, None)
-                        },
-                        _ => {
-                            match node.parent() {
-                                Some(n) => {
-                                    node = n;
-                                    continue;
-                                },
-                                None => (None, None),
-                            }
-                        },
-                    }
-                };
-
-                break ret;
-            }
-        };
-        self.expected_type = expected.0;
-        self.expected_name = expected.1;
+        let (expected_type, expected_name) = self.expected_type_and_name();
+        self.expected_type = expected_type;
+        self.expected_name = expected_name;
         self.attribute_under_caret = find_node_at_offset(&file_with_fake_ident, offset);
+        let name_like = match find_node_at_offset(&&file_with_fake_ident, offset) {
+            Some(it) => it,
+            None => return,
+        };
+        match name_like {
+            ast::NameLike::Lifetime(lifetime) => {
+                self.classify_lifetime(original_file, lifetime, offset);
+            }
+            ast::NameLike::NameRef(name_ref) => {
+                self.classify_name_ref(original_file, name_ref, offset);
+            }
+            ast::NameLike::Name(name) => {
+                self.classify_name(original_file, name, offset);
+            }
+        }
+    }
 
-        // First, let's try to complete a reference to some declaration.
-        if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&file_with_fake_ident, offset) {
-            // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`.
-            // See RFC#1685.
-            if is_node::<ast::Param>(name_ref.syntax()) {
-                self.is_param = true;
+    fn classify_lifetime(
+        &mut self,
+        original_file: &SyntaxNode,
+        lifetime: ast::Lifetime,
+        offset: TextSize,
+    ) {
+        self.lifetime_syntax =
+            find_node_at_offset(original_file, lifetime.syntax().text_range().start());
+        if let Some(parent) = lifetime.syntax().parent() {
+            if parent.kind() == ERROR {
                 return;
             }
-            // FIXME: remove this (V) duplication and make the check more precise
-            if name_ref.syntax().ancestors().find_map(ast::RecordPatFieldList::cast).is_some() {
-                self.record_pat_syntax =
-                    self.sema.find_node_at_offset_with_macros(&original_file, offset);
+
+            match_ast! {
+                match parent {
+                    ast::LifetimeParam(_it) => {
+                        self.lifetime_allowed = true;
+                        self.lifetime_param_syntax =
+                            self.sema.find_node_at_offset_with_macros(original_file, offset);
+                    },
+                    ast::BreakExpr(_it) => self.is_label_ref = true,
+                    ast::ContinueExpr(_it) => self.is_label_ref = true,
+                    ast::Label(_it) => (),
+                    _ => self.lifetime_allowed = true,
+                }
             }
-            self.classify_name_ref(original_file, name_ref, offset);
         }
+    }
 
-        // Otherwise, see if this is a declaration. We can use heuristics to
-        // suggest declaration names, see `CompletionKind::Magic`.
-        if let Some(name) = find_node_at_offset::<ast::Name>(&file_with_fake_ident, offset) {
-            if let Some(bind_pat) = name.syntax().ancestors().find_map(ast::IdentPat::cast) {
-                self.is_pat_binding_or_const = true;
-                if bind_pat.at_token().is_some()
-                    || bind_pat.ref_token().is_some()
-                    || bind_pat.mut_token().is_some()
-                {
-                    self.is_pat_binding_or_const = false;
-                }
-                if bind_pat.syntax().parent().and_then(ast::RecordPatFieldList::cast).is_some() {
-                    self.is_pat_binding_or_const = false;
-                }
-                if let Some(Some(pat)) = bind_pat.syntax().ancestors().find_map(|node| {
+    fn classify_name(&mut self, original_file: &SyntaxNode, name: ast::Name, offset: TextSize) {
+        if let Some(bind_pat) = name.syntax().parent().and_then(ast::IdentPat::cast) {
+            self.is_pat_or_const = Some(PatternRefutability::Refutable);
+            // if any of these is here our bind pat can't be a const pat anymore
+            let complex_ident_pat = bind_pat.at_token().is_some()
+                || bind_pat.ref_token().is_some()
+                || bind_pat.mut_token().is_some();
+            if complex_ident_pat {
+                self.is_pat_or_const = None;
+            } else {
+                let irrefutable_pat = bind_pat.syntax().ancestors().find_map(|node| {
                     match_ast! {
                         match node {
                             ast::LetStmt(it) => Some(it.pat()),
@@ -422,24 +548,30 @@ fn fill(
                             _ => None,
                         }
                     }
-                }) {
+                });
+                if let Some(Some(pat)) = irrefutable_pat {
+                    // This check is here since we could be inside a pattern in the initializer expression of the let statement.
                     if pat.syntax().text_range().contains_range(bind_pat.syntax().text_range()) {
-                        self.is_pat_binding_or_const = false;
-                        self.is_irrefutable_pat_binding = true;
+                        self.is_pat_or_const = Some(PatternRefutability::Irrefutable);
                     }
                 }
 
-                self.fill_impl_def();
-            }
-            if is_node::<ast::Param>(name.syntax()) {
-                self.is_param = true;
-                return;
-            }
-            // FIXME: remove this (^) duplication and make the check more precise
-            if name.syntax().ancestors().find_map(ast::RecordPatFieldList::cast).is_some() {
-                self.record_pat_syntax =
-                    self.sema.find_node_at_offset_with_macros(&original_file, offset);
+                let is_name_in_field_pat = bind_pat
+                    .syntax()
+                    .parent()
+                    .and_then(ast::RecordPatField::cast)
+                    .map_or(false, |pat_field| pat_field.name_ref().is_none());
+                if is_name_in_field_pat {
+                    self.is_pat_or_const = None;
+                }
             }
+
+            self.fill_impl_def();
+        }
+        self.is_param |= is_node::<ast::Param>(name.syntax());
+        if ast::RecordPatField::for_field_name(&name).is_some() {
+            self.record_pat_syntax =
+                self.sema.find_node_at_offset_with_macros(&original_file, offset);
         }
     }
 
@@ -449,16 +581,20 @@ fn classify_name_ref(
         name_ref: ast::NameRef,
         offset: TextSize,
     ) {
-        self.name_ref_syntax =
-            find_node_at_offset(&original_file, name_ref.syntax().text_range().start());
-        let name_range = name_ref.syntax().text_range();
+        self.fill_impl_def();
         if ast::RecordExprField::for_field_name(&name_ref).is_some() {
             self.record_lit_syntax =
+                self.sema.find_node_at_offset_with_macros(original_file, offset);
+        }
+        if ast::RecordPatField::for_field_name_ref(&name_ref).is_some() {
+            self.record_pat_syntax =
                 self.sema.find_node_at_offset_with_macros(&original_file, offset);
         }
 
-        self.fill_impl_def();
+        self.name_ref_syntax =
+            find_node_at_offset(original_file, name_ref.syntax().text_range().start());
 
+        let name_range = name_ref.syntax().text_range();
         let top_node = name_ref
             .syntax()
             .ancestors()
@@ -466,31 +602,20 @@ fn classify_name_ref(
             .last()
             .unwrap();
 
-        match top_node.parent().map(|it| it.kind()) {
-            Some(SOURCE_FILE) | Some(ITEM_LIST) => {
-                self.is_new_item = true;
-                return;
-            }
-            _ => (),
+        if matches!(top_node.parent().map(|it| it.kind()), Some(SOURCE_FILE) | Some(ITEM_LIST)) {
+            self.is_new_item = true;
+            return;
         }
 
         self.use_item_syntax =
-            self.sema.ancestors_with_macros(self.token.parent()).find_map(ast::Use::cast);
+            self.sema.token_ancestors_with_macros(self.token.clone()).find_map(ast::Use::cast);
 
-        self.function_syntax = self
+        self.function_def = self
             .sema
-            .ancestors_with_macros(self.token.parent())
+            .token_ancestors_with_macros(self.token.clone())
             .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
             .find_map(ast::Fn::cast);
 
-        self.record_field_syntax = self
-            .sema
-            .ancestors_with_macros(self.token.parent())
-            .take_while(|it| {
-                it.kind() != SOURCE_FILE && it.kind() != MODULE && it.kind() != CALL_EXPR
-            })
-            .find_map(ast::RecordExprField::cast);
-
         let parent = match name_ref.syntax().parent() {
             Some(it) => it,
             None => return,
@@ -563,6 +688,7 @@ fn classify_name_ref(
                 }
             }
         }
+
         if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) {
             // The receiver comes before the point of insertion of the fake
             // ident, so it should have the same range in the non-modified file
@@ -580,6 +706,7 @@ fn classify_name_ref(
                     false
                 };
         }
+
         if let Some(method_call_expr) = ast::MethodCallExpr::cast(parent) {
             // As above
             self.dot_receiver = method_call_expr
@@ -629,7 +756,9 @@ fn check_expected_type_and_name(ra_fixture: &str, expect: Expect) {
             .map(|t| t.display_test(&db).to_string())
             .unwrap_or("?".to_owned());
 
-        let name = completion_context.expected_name.unwrap_or("?".to_owned());
+        let name = completion_context
+            .expected_name
+            .map_or_else(|| "?".to_owned(), |name| name.to_string());
 
         expect.assert_eq(&format!("ty: {}, name: {}", ty, name));
     }
@@ -660,6 +789,26 @@ fn foo() {
         );
     }
 
+    #[test]
+    fn expected_type_let_pat() {
+        check_expected_type_and_name(
+            r#"
+fn foo() {
+    let x$0 = 0u32;
+}
+"#,
+            expect![[r#"ty: u32, name: ?"#]],
+        );
+        check_expected_type_and_name(
+            r#"
+fn foo() {
+    let $0 = 0u32;
+}
+"#,
+            expect![[r#"ty: u32, name: ?"#]],
+        );
+    }
+
     #[test]
     fn expected_type_fn_param_without_leading_char() {
         cov_mark::check!(expected_type_fn_param_without_leading_char);
@@ -704,6 +853,19 @@ fn foo() {
         )
     }
 
+    #[test]
+    fn expected_type_generic_struct_field() {
+        check_expected_type_and_name(
+            r#"
+struct Foo<T> { a: T }
+fn foo() -> Foo<u32> {
+    Foo { a: $0 }
+}
+"#,
+            expect![[r#"ty: u32, name: a"#]],
+        )
+    }
+
     #[test]
     fn expected_type_struct_field_with_leading_char() {
         cov_mark::check!(expected_type_struct_field_with_leading_char);
@@ -758,8 +920,8 @@ fn foo() {
     if let $0 = f { }
 }
 "#,
-            expect![[r#"ty: (), name: ?"#]],
-        ) // FIXME should be `ty: u32, name: ?`
+            expect![[r#"ty: Foo, name: ?"#]],
+        )
     }
 
     #[test]
@@ -787,8 +949,8 @@ fn foo() -> u32 {
     $0
 }
 "#,
-            expect![[r#"ty: (), name: ?"#]],
-        ) // FIXME this should be `ty: u32, name: ?`
+            expect![[r#"ty: u32, name: ?"#]],
+        )
     }
 
     #[test]
@@ -803,4 +965,64 @@ fn foo() -> u32 {
             expect![[r#"ty: u32, name: ?"#]],
         )
     }
+
+    #[test]
+    fn expected_type_fn_ret_fn_ref_fully_typed() {
+        check_expected_type_and_name(
+            r#"
+fn foo() -> u32 {
+    foo$0
+}
+"#,
+            expect![[r#"ty: u32, name: ?"#]],
+        )
+    }
+
+    #[test]
+    fn expected_type_closure_param_return() {
+        // FIXME: make this work with `|| $0`
+        check_expected_type_and_name(
+            r#"
+fn foo() {
+    bar(|| a$0);
+}
+
+fn bar(f: impl FnOnce() -> u32) {}
+#[lang = "fn_once"]
+trait FnOnce { type Output; }
+"#,
+            expect![[r#"ty: u32, name: ?"#]],
+        );
+    }
+
+    #[test]
+    fn expected_type_generic_function() {
+        check_expected_type_and_name(
+            r#"
+fn foo() {
+    bar::<u32>($0);
+}
+
+fn bar<T>(t: T) {}
+"#,
+            expect![[r#"ty: u32, name: t"#]],
+        );
+    }
+
+    #[test]
+    fn expected_type_generic_method() {
+        check_expected_type_and_name(
+            r#"
+fn foo() {
+    S(1u32).bar($0);
+}
+
+struct S<T>(T);
+impl<T> S<T> {
+    fn bar(self, t: T) {}
+}
+"#,
+            expect![[r#"ty: u32, name: t"#]],
+        );
+    }
 }