]> git.lizzy.rs Git - rust.git/blobdiff - crates/ide_completion/src/context.rs
internal: Refactor lifetime completion context fields
[rust.git] / crates / ide_completion / src / context.rs
index d9024157ce458e826a1e41c7f557859a04b1419d..25da46f1d7e00d3cc95338532539c983e043348b 100644 (file)
@@ -9,7 +9,7 @@
 };
 use syntax::{
     algo::find_node_at_offset,
-    ast::{self, NameOrNameRef, NameOwner},
+    ast::{self, HasName, NameOrNameRef},
     match_ast, AstNode, NodeOrToken,
     SyntaxKind::{self, *},
     SyntaxNode, SyntaxToken, TextRange, TextSize, T,
@@ -60,6 +60,14 @@ pub(super) struct PatternContext {
     pub(super) is_param: Option<ParamKind>,
 }
 
+#[derive(Debug)]
+pub(super) enum LifetimeContext {
+    LifetimeParam(Option<ast::LifetimeParam>),
+    Lifetime,
+    LabelRef,
+    LabelDef,
+}
+
 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
 pub(crate) enum CallKind {
     Pat,
@@ -85,6 +93,7 @@ pub(crate) struct CompletionContext<'a> {
     pub(super) original_token: SyntaxToken,
     /// The token before the cursor, in the macro-expanded file.
     pub(super) token: SyntaxToken,
+    /// The crate of the current file.
     pub(super) krate: Option<hir::Crate>,
     pub(super) expected_name: Option<NameOrNameRef>,
     pub(super) expected_type: Option<Type>,
@@ -93,22 +102,16 @@ pub(crate) struct CompletionContext<'a> {
     pub(super) function_def: Option<ast::Fn>,
     /// The parent impl of the cursor position if it exists.
     pub(super) impl_def: Option<ast::Impl>,
-    pub(super) name_ref_syntax: Option<ast::NameRef>,
-
-    // 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,
+    pub(super) name_syntax: Option<ast::NameLike>,
 
     pub(super) completion_location: Option<ImmediateLocation>,
     pub(super) prev_sibling: Option<ImmediatePrevSibling>,
     pub(super) attribute_under_caret: Option<ast::Attr>,
     pub(super) previous_token: Option<SyntaxToken>,
 
+    pub(super) lifetime_ctx: Option<LifetimeContext>,
     pub(super) pattern_ctx: Option<PatternContext>,
     pub(super) path_context: Option<PathCompletionContext>,
-    pub(super) active_parameter: Option<ActiveParameter>,
     pub(super) locals: Vec<(String, Local)>,
 
     pub(super) incomplete_let: bool,
@@ -137,11 +140,11 @@ pub(super) fn new(
         let fake_ident_token =
             file_with_fake_ident.syntax().token_at_offset(position.offset).right_biased().unwrap();
 
-        let krate = sema.to_module_def(position.file_id).map(|m| m.krate());
         let original_token =
             original_file.syntax().token_at_offset(position.offset).left_biased()?;
-        let token = sema.descend_into_macros(original_token.clone());
+        let token = sema.descend_into_macros_single(original_token.clone());
         let scope = sema.scope_at_offset(&token, position.offset);
+        let krate = scope.krate();
         let mut locals = vec![];
         scope.process_all_names(&mut |name, scope| {
             if let ScopeDef::Local(local) = scope {
@@ -161,64 +164,107 @@ pub(super) fn new(
             expected_type: None,
             function_def: None,
             impl_def: None,
-            name_ref_syntax: None,
-            lifetime_syntax: None,
-            lifetime_param_syntax: None,
-            lifetime_allowed: false,
-            is_label_ref: false,
+            name_syntax: None,
+            lifetime_ctx: None,
             pattern_ctx: None,
             completion_location: None,
             prev_sibling: None,
             attribute_under_caret: None,
             previous_token: None,
             path_context: None,
-            active_parameter: ActiveParameter::at(db, position),
             locals,
             incomplete_let: false,
             no_completion_required: false,
         };
+        ctx.expand_and_fill(
+            original_file.syntax().clone(),
+            file_with_fake_ident.syntax().clone(),
+            position.offset,
+            fake_ident_token,
+        );
+        Some(ctx)
+    }
 
-        let mut original_file = original_file.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>(&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;
+    /// Do the attribute expansion at the current cursor position for both original file and fake file
+    /// as long as possible. As soon as one of the two expansions fail we stop to stay in sync.
+    fn expand_and_fill(
+        &mut self,
+        mut original_file: SyntaxNode,
+        mut speculative_file: SyntaxNode,
+        mut offset: TextSize,
+        mut fake_ident_token: SyntaxToken,
+    ) {
+        loop {
+            // Expand attributes
+            if let (Some(actual_item), Some(item_with_fake_ident)) = (
+                find_node_at_offset::<ast::Item>(&original_file, offset),
+                find_node_at_offset::<ast::Item>(&speculative_file, offset),
+            ) {
+                match (
+                    self.sema.expand_attr_macro(&actual_item),
+                    self.sema.speculative_expand_attr_macro(
+                        &actual_item,
+                        &item_with_fake_ident,
+                        fake_ident_token.clone(),
+                    ),
+                ) {
+                    (Some(actual_expansion), Some(speculative_expansion)) => {
+                        let new_offset = speculative_expansion.1.text_range().start();
+                        if new_offset > actual_expansion.text_range().end() {
+                            break;
+                        }
+                        original_file = actual_expansion;
+                        speculative_file = speculative_expansion.0;
+                        fake_ident_token = speculative_expansion.1;
+                        offset = new_offset;
+                        continue;
+                    }
+                    (None, None) => (),
+                    _ => break,
+                }
             }
-            let speculative_args = match macro_call_with_fake_ident.token_tree() {
-                Some(tt) => tt,
-                None => break,
-            };
-            if let (Some(actual_expansion), Some(speculative_expansion)) = (
-                ctx.sema.expand(&actual_macro_call),
-                ctx.sema.speculative_expand(
-                    &actual_macro_call,
-                    &speculative_args,
-                    fake_ident_token,
-                ),
+
+            // Expand fn-like macro calls
+            if 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>(&speculative_file, offset),
             ) {
-                let new_offset = speculative_expansion.1.text_range().start();
-                if new_offset > actual_expansion.text_range().end() {
+                let mac_call_path0 = actual_macro_call.path().as_ref().map(|s| s.syntax().text());
+                let mac_call_path1 =
+                    macro_call_with_fake_ident.path().as_ref().map(|s| s.syntax().text());
+                if mac_call_path0 != mac_call_path1 {
+                    break;
+                }
+                let speculative_args = match macro_call_with_fake_ident.token_tree() {
+                    Some(tt) => tt,
+                    None => break,
+                };
+
+                if let (Some(actual_expansion), Some(speculative_expansion)) = (
+                    self.sema.expand(&actual_macro_call),
+                    self.sema.speculative_expand(
+                        &actual_macro_call,
+                        &speculative_args,
+                        fake_ident_token,
+                    ),
+                ) {
+                    let new_offset = speculative_expansion.1.text_range().start();
+                    if new_offset > actual_expansion.text_range().end() {
+                        break;
+                    }
+                    original_file = actual_expansion;
+                    speculative_file = speculative_expansion.0;
+                    fake_ident_token = speculative_expansion.1;
+                    offset = new_offset;
+                } else {
                     break;
                 }
-                original_file = actual_expansion;
-                speculative_file = speculative_expansion.0;
-                fake_ident_token = speculative_expansion.1;
-                offset = new_offset;
             } else {
                 break;
             }
         }
-        ctx.fill(&original_file, speculative_file, offset);
-        Some(ctx)
+
+        self.fill(&original_file, speculative_file, offset);
     }
 
     /// Checks whether completions in that particular case don't make much sense.
@@ -236,29 +282,28 @@ pub(crate) fn source_range(&self) -> TextRange {
         let kind = self.token.kind();
         if kind == IDENT || kind == LIFETIME_IDENT || kind == UNDERSCORE || kind.is_keyword() {
             cov_mark::hit!(completes_if_prefix_is_keyword);
-            return self.original_token.text_range();
+            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);
-            return TextRange::at(self.original_token.text_range().start(), TextSize::from(1));
-        } else if kind == BANG {
-            if let Some(n) = self.token.parent() {
-                if n.kind() == SyntaxKind::MACRO_CALL {
-                    cov_mark::hit!(completes_macro_call_if_cursor_at_bang_token);
-                    return n.text_range();
-                }
-            }
+            TextRange::at(self.original_token.text_range().start(), TextSize::from(1))
+        } else {
+            TextRange::empty(self.position.offset)
         }
-
-        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 expects_assoc_item(&self) -> bool {
-        matches!(self.completion_location, Some(ImmediateLocation::Trait | ImmediateLocation::Impl))
+    pub(crate) fn dot_receiver(&self) -> Option<&ast::Expr> {
+        match &self.completion_location {
+            Some(
+                ImmediateLocation::MethodCall { receiver, .. }
+                | ImmediateLocation::FieldAccess { receiver, .. },
+            ) => receiver.as_ref(),
+            _ => None,
+        }
     }
 
     pub(crate) fn has_dot_receiver(&self) -> bool {
@@ -269,14 +314,8 @@ pub(crate) fn has_dot_receiver(&self) -> bool {
         )
     }
 
-    pub(crate) fn dot_receiver(&self) -> Option<&ast::Expr> {
-        match &self.completion_location {
-            Some(
-                ImmediateLocation::MethodCall { receiver, .. }
-                | ImmediateLocation::FieldAccess { receiver, .. },
-            ) => receiver.as_ref(),
-            _ => None,
-        }
+    pub(crate) fn expects_assoc_item(&self) -> bool {
+        matches!(self.completion_location, Some(ImmediateLocation::Trait | ImmediateLocation::Impl))
     }
 
     pub(crate) fn expects_non_trait_assoc_item(&self) -> bool {
@@ -292,7 +331,7 @@ pub(crate) fn expects_generic_arg(&self) -> bool {
     }
 
     pub(crate) fn has_block_expr_parent(&self) -> bool {
-        matches!(self.completion_location, Some(ImmediateLocation::BlockExpr))
+        matches!(self.completion_location, Some(ImmediateLocation::StmtList))
     }
 
     pub(crate) fn expects_ident_pat_or_ref_expr(&self) -> bool {
@@ -349,6 +388,7 @@ pub(crate) fn is_path_disallowed(&self) -> bool {
                         | ImmediateLocation::ModDeclaration(_)
                         | ImmediateLocation::RecordPat(_)
                         | ImmediateLocation::RecordExpr(_)
+                        | ImmediateLocation::Rename
                 )
             )
     }
@@ -393,6 +433,7 @@ pub(crate) fn is_scope_def_hidden(&self, scope_def: &ScopeDef) -> bool {
         false
     }
 
+    /// Check if an item is `#[doc(hidden)]`.
     pub(crate) fn is_item_hidden(&self, item: &hir::ItemInNs) -> bool {
         let attrs = item.attrs(self.db);
         let krate = item.krate(self.db);
@@ -402,6 +443,10 @@ pub(crate) fn is_item_hidden(&self, item: &hir::ItemInNs) -> bool {
         }
     }
 
+    pub(crate) fn is_immediately_after_macro_bang(&self) -> bool {
+        self.token.kind() == BANG && self.token.parent().map_or(false, |it| it.kind() == MACRO_CALL)
+    }
+
     /// A version of [`SemanticsScope::process_all_names`] that filters out `#[doc(hidden)]` items.
     pub(crate) fn process_all_names(&self, f: &mut dyn FnMut(Name, ScopeDef)) {
         self.scope.process_all_names(&mut |name, def| {
@@ -435,11 +480,11 @@ fn is_visible_impl(
     }
 
     fn is_doc_hidden(&self, attrs: &hir::Attrs, defining_crate: hir::Crate) -> bool {
-        let module = match self.scope.module() {
+        let krate = match self.krate {
             Some(it) => it,
             None => return true,
         };
-        if module.krate() != defining_crate && attrs.has_doc_hidden() {
+        if krate != defining_crate && attrs.has_doc_hidden() {
             // `doc(hidden)` items are only completed within the defining crate.
             return true;
         }
@@ -470,10 +515,9 @@ fn expected_type_and_name(&self) -> (Option<Type>, Option<NameOrNameRef>) {
                             .and_then(|pat| self.sema.type_of_pat(&pat))
                             .or_else(|| it.initializer().and_then(|it| self.sema.type_of_expr(&it)))
                             .map(TypeInfo::original);
-                        let name = if let Some(ast::Pat::IdentPat(ident)) = it.pat() {
-                            ident.name().map(NameOrNameRef::Name)
-                        } else {
-                            None
+                        let name = match it.pat() {
+                            Some(ast::Pat::IdentPat(ident)) => ident.name().map(NameOrNameRef::Name),
+                            Some(_) | None => None,
                         };
 
                         (ty, name)
@@ -608,6 +652,8 @@ fn fill(
         self.completion_location =
             determine_location(&self.sema, original_file, offset, &name_like);
         self.prev_sibling = determine_prev_sibling(&name_like);
+        self.name_syntax =
+            find_node_at_offset(original_file, name_like.syntax().text_range().start());
         match name_like {
             ast::NameLike::Lifetime(lifetime) => {
                 self.classify_lifetime(original_file, lifetime, offset);
@@ -627,26 +673,20 @@ fn classify_lifetime(
         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;
             }
 
-            match_ast! {
+            self.lifetime_ctx = Some(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,
+                    ast::LifetimeParam(_it) => LifetimeContext::LifetimeParam(self.sema.find_node_at_offset_with_macros(original_file, offset)),
+                    ast::BreakExpr(_it) => LifetimeContext::LabelRef,
+                    ast::ContinueExpr(_it) => LifetimeContext::LabelRef,
+                    ast::Label(_it) => LifetimeContext::LabelDef,
+                    _ => LifetimeContext::Lifetime,
                 }
-            }
+            });
         }
     }
 
@@ -702,9 +742,6 @@ fn classify_name(&mut self, name: ast::Name) {
     fn classify_name_ref(&mut self, original_file: &SyntaxNode, name_ref: ast::NameRef) {
         self.fill_impl_def();
 
-        self.name_ref_syntax =
-            find_node_at_offset(original_file, name_ref.syntax().text_range().start());
-
         self.function_def = self
             .sema
             .token_ancestors_with_macros(self.token.clone())
@@ -783,9 +820,9 @@ fn classify_name_ref(&mut self, original_file: &SyntaxNode, name_ref: ast::NameR
                     if let Some(stmt) = ast::ExprStmt::cast(node.clone()) {
                         return Some(stmt.syntax().text_range() == name_ref.syntax().text_range());
                     }
-                    if let Some(block) = ast::BlockExpr::cast(node) {
+                    if let Some(stmt_list) = ast::StmtList::cast(node) {
                         return Some(
-                            block.tail_expr().map(|e| e.syntax().text_range())
+                            stmt_list.tail_expr().map(|e| e.syntax().text_range())
                                 == Some(name_ref.syntax().text_range()),
                         );
                     }
@@ -833,7 +870,8 @@ mod tests {
 
     fn check_expected_type_and_name(ra_fixture: &str, expect: Expect) {
         let (db, pos) = position(ra_fixture);
-        let completion_context = CompletionContext::new(&db, pos, &TEST_CONFIG).unwrap();
+        let config = TEST_CONFIG;
+        let completion_context = CompletionContext::new(&db, pos, &config).unwrap();
 
         let ty = completion_context
             .expected_type