about summary refs log tree commit diff
diff options
context:
space:
mode:
authorLukas Wirth <lukastw97@gmail.com>2022-05-24 13:24:36 +0200
committerLukas Wirth <lukastw97@gmail.com>2022-05-24 13:24:36 +0200
commit6a8b8a60393f52de2df3d9ce81c0a303e535b80b (patch)
tree7855d1146f6c38eac53d63ae6216cf0624790706
parent697ade6f8d98462df6548effbc3da74302f34108 (diff)
downloadrust-6a8b8a60393f52de2df3d9ce81c0a303e535b80b.tar.gz
rust-6a8b8a60393f52de2df3d9ce81c0a303e535b80b.zip
internal: Refactor our record pat/expr handling in completion context
-rw-r--r--crates/ide-completion/src/completions/expr.rs29
-rw-r--r--crates/ide-completion/src/completions/keyword.rs6
-rw-r--r--crates/ide-completion/src/completions/pattern.rs6
-rw-r--r--crates/ide-completion/src/completions/record.rs110
-rw-r--r--crates/ide-completion/src/context.rs75
-rw-r--r--crates/ide-completion/src/patterns.rs39
-rw-r--r--crates/ide-completion/src/tests/pattern.rs5
-rw-r--r--crates/ide-completion/src/tests/record.rs6
-rw-r--r--crates/syntax/src/ast/node_ext.rs4
9 files changed, 153 insertions, 127 deletions
diff --git a/crates/ide-completion/src/completions/expr.rs b/crates/ide-completion/src/completions/expr.rs
index 87fae18f4cd..ae7b42e3056 100644
--- a/crates/ide-completion/src/completions/expr.rs
+++ b/crates/ide-completion/src/completions/expr.rs
@@ -5,7 +5,7 @@ use ide_db::FxHashSet;
 use syntax::T;
 
 use crate::{
-    context::{PathCompletionCtx, PathKind, PathQualifierCtx},
+    context::{NameRefContext, PathCompletionCtx, PathKind, PathQualifierCtx},
     CompletionContext, Completions,
 };
 
@@ -15,14 +15,25 @@ pub(crate) fn complete_expr_path(acc: &mut Completions, ctx: &CompletionContext)
         return;
     }
 
-    let (is_absolute_path, qualifier, in_block_expr, in_loop_body, in_functional_update) =
-        match ctx.path_context() {
-            Some(&PathCompletionCtx {
-                kind: PathKind::Expr { in_block_expr, in_loop_body, in_functional_update },
-                is_absolute_path,
-                ref qualifier,
+    let (is_absolute_path, qualifier, in_block_expr, in_loop_body, is_func_update) =
+        match ctx.nameref_ctx() {
+            Some(NameRefContext {
+                path_ctx:
+                    Some(PathCompletionCtx {
+                        kind: PathKind::Expr { in_block_expr, in_loop_body },
+                        is_absolute_path,
+                        qualifier,
+                        ..
+                    }),
+                record_expr,
                 ..
-            }) => (is_absolute_path, qualifier, in_block_expr, in_loop_body, in_functional_update),
+            }) => (
+                *is_absolute_path,
+                qualifier,
+                *in_block_expr,
+                *in_loop_body,
+                record_expr.as_ref().map_or(false, |&(_, it)| it),
+            ),
             _ => return,
         };
 
@@ -165,7 +176,7 @@ pub(crate) fn complete_expr_path(acc: &mut Completions, ctx: &CompletionContext)
                 }
             });
 
-            if !in_functional_update {
+            if !is_func_update {
                 let mut add_keyword =
                     |kw, snippet| super::keyword::add_keyword(acc, ctx, kw, snippet);
 
diff --git a/crates/ide-completion/src/completions/keyword.rs b/crates/ide-completion/src/completions/keyword.rs
index 14211f86baa..5917da9b7f3 100644
--- a/crates/ide-completion/src/completions/keyword.rs
+++ b/crates/ide-completion/src/completions/keyword.rs
@@ -5,12 +5,12 @@
 use syntax::T;
 
 use crate::{
-    context::PathKind, patterns::ImmediateLocation, CompletionContext, CompletionItem,
-    CompletionItemKind, Completions,
+    context::{NameRefContext, PathKind},
+    CompletionContext, CompletionItem, CompletionItemKind, Completions,
 };
 
 pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) {
-    if matches!(ctx.completion_location, Some(ImmediateLocation::RecordExpr(_))) {
+    if matches!(ctx.nameref_ctx(), Some(NameRefContext { record_expr: Some(_), .. })) {
         cov_mark::hit!(no_keyword_completion_in_record_lit);
         return;
     }
diff --git a/crates/ide-completion/src/completions/pattern.rs b/crates/ide-completion/src/completions/pattern.rs
index 211ca4e531b..ab35dadf92b 100644
--- a/crates/ide-completion/src/completions/pattern.rs
+++ b/crates/ide-completion/src/completions/pattern.rs
@@ -15,7 +15,6 @@ pub(crate) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) {
         Some(ctx) => ctx,
         _ => return,
     };
-    let refutable = patctx.refutability == PatternRefutability::Refutable;
 
     if let Some(path_ctx) = ctx.path_context() {
         pattern_path_completion(acc, ctx, path_ctx);
@@ -47,6 +46,11 @@ pub(crate) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) {
         }
     }
 
+    if patctx.record_pat.is_some() {
+        return;
+    }
+
+    let refutable = patctx.refutability == PatternRefutability::Refutable;
     let single_variant_enum = |enum_: hir::Enum| ctx.db.enum_data(enum_.into()).variants.len() == 1;
 
     if let Some(hir::Adt::Enum(e)) =
diff --git a/crates/ide-completion/src/completions/record.rs b/crates/ide-completion/src/completions/record.rs
index 05fbe8513e7..6717ca0a0e2 100644
--- a/crates/ide-completion/src/completions/record.rs
+++ b/crates/ide-completion/src/completions/record.rs
@@ -3,67 +3,65 @@ use ide_db::SymbolKind;
 use syntax::{ast::Expr, T};
 
 use crate::{
-    patterns::ImmediateLocation, CompletionContext, CompletionItem, CompletionItemKind,
-    CompletionRelevance, CompletionRelevancePostfixMatch, Completions,
+    context::{NameRefContext, PatternContext},
+    CompletionContext, CompletionItem, CompletionItemKind, CompletionRelevance,
+    CompletionRelevancePostfixMatch, Completions,
 };
 
 pub(crate) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
-    let missing_fields = match &ctx.completion_location {
-        Some(
-            ImmediateLocation::RecordExpr(record_expr)
-            | ImmediateLocation::RecordExprUpdate(record_expr),
-        ) => {
-            let ty = ctx.sema.type_of_expr(&Expr::RecordExpr(record_expr.clone()));
-
-            if let Some(hir::Adt::Union(un)) = ty.as_ref().and_then(|t| t.original.as_adt()) {
-                // ctx.sema.record_literal_missing_fields will always return
-                // an empty Vec on a union literal. This is normally
-                // reasonable, but here we'd like to present the full list
-                // of fields if the literal is empty.
-                let were_fields_specified = record_expr
-                    .record_expr_field_list()
-                    .and_then(|fl| fl.fields().next())
-                    .is_some();
-
-                match were_fields_specified {
-                    false => un.fields(ctx.db).into_iter().map(|f| (f, f.ty(ctx.db))).collect(),
-                    true => vec![],
-                }
-            } else {
-                let missing_fields = ctx.sema.record_literal_missing_fields(record_expr);
-
-                let default_trait = ctx.famous_defs().core_default_Default();
-                let impl_default_trait =
-                    default_trait.zip(ty.as_ref()).map_or(false, |(default_trait, ty)| {
-                        ty.original.impls_trait(ctx.db, default_trait, &[])
-                    });
-
-                if impl_default_trait && !missing_fields.is_empty() && ctx.path_qual().is_none() {
-                    let completion_text = "..Default::default()";
-                    let mut item =
-                        CompletionItem::new(SymbolKind::Field, ctx.source_range(), completion_text);
-                    let completion_text =
-                        completion_text.strip_prefix(ctx.token.text()).unwrap_or(completion_text);
-                    item.insert_text(completion_text).set_relevance(CompletionRelevance {
-                        postfix_match: Some(CompletionRelevancePostfixMatch::Exact),
-                        ..Default::default()
-                    });
-                    item.add_to(acc);
-                }
-                if ctx.previous_token_is(T![.]) {
-                    let mut item =
-                        CompletionItem::new(CompletionItemKind::Snippet, ctx.source_range(), "..");
-                    item.insert_text(".");
-                    item.add_to(acc);
-                    return None;
-                }
-                missing_fields
+    let missing_fields = if let Some(PatternContext { record_pat: Some(record_pat), .. }) =
+        &ctx.pattern_ctx
+    {
+        ctx.sema.record_pattern_missing_fields(record_pat)
+    } else if let Some(NameRefContext { record_expr: Some((record_expr, _)), .. }) =
+        ctx.nameref_ctx()
+    {
+        let ty = ctx.sema.type_of_expr(&Expr::RecordExpr(record_expr.clone()));
+
+        if let Some(hir::Adt::Union(un)) = ty.as_ref().and_then(|t| t.original.as_adt()) {
+            // ctx.sema.record_literal_missing_fields will always return
+            // an empty Vec on a union literal. This is normally
+            // reasonable, but here we'd like to present the full list
+            // of fields if the literal is empty.
+            let were_fields_specified =
+                record_expr.record_expr_field_list().and_then(|fl| fl.fields().next()).is_some();
+
+            match were_fields_specified {
+                false => un.fields(ctx.db).into_iter().map(|f| (f, f.ty(ctx.db))).collect(),
+                true => vec![],
             }
+        } else {
+            let missing_fields = ctx.sema.record_literal_missing_fields(record_expr);
+
+            let default_trait = ctx.famous_defs().core_default_Default();
+            let impl_default_trait =
+                default_trait.zip(ty.as_ref()).map_or(false, |(default_trait, ty)| {
+                    ty.original.impls_trait(ctx.db, default_trait, &[])
+                });
+
+            if impl_default_trait && !missing_fields.is_empty() && ctx.path_qual().is_none() {
+                let completion_text = "..Default::default()";
+                let mut item =
+                    CompletionItem::new(SymbolKind::Field, ctx.source_range(), completion_text);
+                let completion_text =
+                    completion_text.strip_prefix(ctx.token.text()).unwrap_or(completion_text);
+                item.insert_text(completion_text).set_relevance(CompletionRelevance {
+                    postfix_match: Some(CompletionRelevancePostfixMatch::Exact),
+                    ..Default::default()
+                });
+                item.add_to(acc);
+            }
+            if ctx.previous_token_is(T![.]) {
+                let mut item =
+                    CompletionItem::new(CompletionItemKind::Snippet, ctx.source_range(), "..");
+                item.insert_text(".");
+                item.add_to(acc);
+                return None;
+            }
+            missing_fields
         }
-        Some(ImmediateLocation::RecordPat(record_pat)) => {
-            ctx.sema.record_pattern_missing_fields(record_pat)
-        }
-        _ => return None,
+    } else {
+        return None;
     };
 
     for (field, ty) in missing_fields {
diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs
index 47b37f6d73b..4530d88af8c 100644
--- a/crates/ide-completion/src/context.rs
+++ b/crates/ide-completion/src/context.rs
@@ -48,7 +48,6 @@ pub(super) enum PathKind {
     Expr {
         in_block_expr: bool,
         in_loop_body: bool,
-        in_functional_update: bool,
     },
     Type,
     Attr {
@@ -115,6 +114,8 @@ pub(super) struct PatternContext {
     pub(super) parent_pat: Option<ast::Pat>,
     pub(super) ref_token: Option<SyntaxToken>,
     pub(super) mut_token: Option<SyntaxToken>,
+    /// The record pattern this name or ref is a field of
+    pub(super) record_pat: Option<ast::RecordPat>,
 }
 
 #[derive(Debug)]
@@ -166,8 +167,11 @@ pub(super) enum NameKind {
 pub(super) struct NameRefContext {
     /// NameRef syntax in the original file
     pub(super) nameref: Option<ast::NameRef>,
+    // FIXME: these fields are actually disjoint -> enum
     pub(super) dot_access: Option<DotAccess>,
     pub(super) path_ctx: Option<PathCompletionCtx>,
+    /// The record expression this nameref is a field of
+    pub(super) record_expr: Option<(ast::RecordExpr, bool)>,
 }
 
 #[derive(Debug)]
@@ -376,13 +380,14 @@ impl<'a> CompletionContext<'a> {
         self.previous_token_is(T![unsafe])
             || matches!(self.prev_sibling, Some(ImmediatePrevSibling::Visibility))
             || matches!(
-                self.completion_location,
-                Some(ImmediateLocation::RecordPat(_) | ImmediateLocation::RecordExpr(_))
-            )
-            || matches!(
                 self.name_ctx(),
                 Some(NameContext { kind: NameKind::Module(_) | NameKind::Rename, .. })
             )
+            || matches!(self.pattern_ctx, Some(PatternContext { record_pat: Some(_), .. }))
+            || matches!(
+                self.nameref_ctx(),
+                Some(NameRefContext { record_expr: Some((_, false)), .. })
+            )
     }
 
     pub(crate) fn path_context(&self) -> Option<&PathCompletionCtx> {
@@ -1023,14 +1028,13 @@ impl<'a> CompletionContext<'a> {
                 ast::Enum(_) => NameKind::Enum,
                 ast::Fn(_) => NameKind::Function,
                 ast::IdentPat(bind_pat) => {
-                    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 {
-                        pat_ctx = Some(pattern_context_for(original_file, bind_pat.into()));
-                    }
+                    pat_ctx = Some({
+                        let mut pat_ctx = pattern_context_for(original_file, bind_pat.into());
+                        if let Some(record_field) = ast::RecordPatField::for_field_name(&name) {
+                            pat_ctx.record_pat = find_node_in_file_compensated(original_file, &record_field.parent_record_pat());
+                        }
+                        pat_ctx
+                    });
 
                     NameKind::IdentPat
                 },
@@ -1062,7 +1066,33 @@ impl<'a> CompletionContext<'a> {
     ) -> (NameRefContext, Option<PatternContext>) {
         let nameref = find_node_at_offset(&original_file, name_ref.syntax().text_range().start());
 
-        let mut nameref_ctx = NameRefContext { dot_access: None, path_ctx: None, nameref };
+        let mut nameref_ctx =
+            NameRefContext { dot_access: None, path_ctx: None, nameref, record_expr: None };
+
+        if let Some(record_field) = ast::RecordExprField::for_field_name(&name_ref) {
+            nameref_ctx.record_expr =
+                find_node_in_file_compensated(original_file, &record_field.parent_record_lit())
+                    .zip(Some(false));
+            return (nameref_ctx, None);
+        }
+        if let Some(record_field) = ast::RecordPatField::for_field_name_ref(&name_ref) {
+            let pat_ctx =
+                pattern_context_for(original_file, record_field.parent_record_pat().clone().into());
+            return (
+                nameref_ctx,
+                Some(PatternContext {
+                    param_ctx: None,
+                    has_type_ascription: false,
+                    ref_token: None,
+                    mut_token: None,
+                    record_pat: find_node_in_file_compensated(
+                        original_file,
+                        &record_field.parent_record_pat(),
+                    ),
+                    ..pat_ctx
+                }),
+            );
+        }
 
         let segment = match_ast! {
             match parent {
@@ -1115,8 +1145,11 @@ impl<'a> CompletionContext<'a> {
                 })
                 .unwrap_or(false)
         };
-        let is_in_func_update = |it: &SyntaxNode| {
-            it.parent().map_or(false, |it| ast::RecordExprFieldList::can_cast(it.kind()))
+        let mut fill_record_expr = |syn: &SyntaxNode| {
+            if let Some(record_expr) = syn.ancestors().nth(2).and_then(ast::RecordExpr::cast) {
+                nameref_ctx.record_expr =
+                    find_node_in_file_compensated(original_file, &record_expr).zip(Some(true));
+            }
         };
 
         let kind = path.syntax().ancestors().find_map(|it| {
@@ -1125,11 +1158,12 @@ impl<'a> CompletionContext<'a> {
                 match it {
                     ast::PathType(_) => Some(PathKind::Type),
                     ast::PathExpr(it) => {
+                        fill_record_expr(it.syntax());
+
                         path_ctx.has_call_parens = it.syntax().parent().map_or(false, |it| ast::CallExpr::can_cast(it.kind()));
                         let in_block_expr = is_in_block(it.syntax());
                         let in_loop_body = is_in_loop_body(it.syntax());
-                        let in_functional_update = is_in_func_update(it.syntax());
-                        Some(PathKind::Expr { in_block_expr, in_loop_body, in_functional_update })
+                        Some(PathKind::Expr { in_block_expr, in_loop_body })
                     },
                     ast::TupleStructPat(it) => {
                         path_ctx.has_call_parens = true;
@@ -1163,8 +1197,8 @@ impl<'a> CompletionContext<'a> {
                                return Some(parent.and_then(ast::MacroExpr::cast).map(|it| {
                                     let in_loop_body = is_in_loop_body(it.syntax());
                                     let in_block_expr = is_in_block(it.syntax());
-                                    let in_functional_update = is_in_func_update(it.syntax());
-                                    PathKind::Expr { in_block_expr, in_loop_body, in_functional_update }
+                                    fill_record_expr(it.syntax());
+                                    PathKind::Expr { in_block_expr, in_loop_body }
                                 }));
                             },
                         }
@@ -1299,6 +1333,7 @@ fn pattern_context_for(original_file: &SyntaxNode, pat: ast::Pat) -> PatternCont
         parent_pat: pat.syntax().parent().and_then(ast::Pat::cast),
         mut_token,
         ref_token,
+        record_pat: None,
     }
 }
 
diff --git a/crates/ide-completion/src/patterns.rs b/crates/ide-completion/src/patterns.rs
index b2bf895743f..0e146df712d 100644
--- a/crates/ide-completion/src/patterns.rs
+++ b/crates/ide-completion/src/patterns.rs
@@ -53,19 +53,6 @@ pub(crate) enum ImmediateLocation {
     // Only set from a type arg
     /// Original file ast node
     GenericArgList(ast::GenericArgList),
-    /// The record expr of the field name we are completing
-    ///
-    /// Original file ast node
-    RecordExpr(ast::RecordExpr),
-    /// The record expr of the functional update syntax we are completing
-    ///
-    /// Original file ast node
-    RecordExprUpdate(ast::RecordExpr),
-    /// The record pat of the field name we are completing
-    ///
-    /// Original file ast node
-    // FIXME: This should be moved to pattern_ctx
-    RecordPat(ast::RecordPat),
 }
 
 pub(crate) fn determine_prev_sibling(name_like: &ast::NameLike) -> Option<ImmediatePrevSibling> {
@@ -136,27 +123,8 @@ pub(crate) fn determine_location(
     name_like: &ast::NameLike,
 ) -> Option<ImmediateLocation> {
     let node = match name_like {
-        ast::NameLike::NameRef(name_ref) => {
-            if ast::RecordExprField::for_field_name(name_ref).is_some() {
-                return sema
-                    .find_node_at_offset_with_macros(original_file, offset)
-                    .map(ImmediateLocation::RecordExpr);
-            }
-            if ast::RecordPatField::for_field_name_ref(name_ref).is_some() {
-                return sema
-                    .find_node_at_offset_with_macros(original_file, offset)
-                    .map(ImmediateLocation::RecordPat);
-            }
-            maximize_name_ref(name_ref)
-        }
-        ast::NameLike::Name(name) => {
-            if ast::RecordPatField::for_field_name(name).is_some() {
-                return sema
-                    .find_node_at_offset_with_macros(original_file, offset)
-                    .map(ImmediateLocation::RecordPat);
-            }
-            name.syntax().clone()
-        }
+        ast::NameLike::NameRef(name_ref) => maximize_name_ref(name_ref),
+        ast::NameLike::Name(name) => name.syntax().clone(),
         ast::NameLike::Lifetime(lt) => lt.syntax().clone(),
     };
 
@@ -199,9 +167,6 @@ pub(crate) fn determine_location(
             ast::SourceFile(_) => ImmediateLocation::ItemList,
             ast::ItemList(_) => ImmediateLocation::ItemList,
             ast::RefExpr(_) => ImmediateLocation::RefExpr,
-            ast::RecordExprFieldList(_) => sema
-                .find_node_at_offset_with_macros(original_file, offset)
-                .map(ImmediateLocation::RecordExprUpdate)?,
             ast::TupleField(_) => ImmediateLocation::TupleField,
             ast::TupleFieldList(_) => ImmediateLocation::TupleField,
             ast::TypeBound(_) => ImmediateLocation::TypeBound,
diff --git a/crates/ide-completion/src/tests/pattern.rs b/crates/ide-completion/src/tests/pattern.rs
index 3ecb2f16374..bf88070769e 100644
--- a/crates/ide-completion/src/tests/pattern.rs
+++ b/crates/ide-completion/src/tests/pattern.rs
@@ -338,7 +338,10 @@ struct Foo { bar: Bar }
 struct Bar(u32);
 fn outer(Foo { bar$0 }: Foo) {}
 "#,
-        expect![[r#""#]],
+        expect![[r#"
+            kw mut
+            kw ref
+        "#]],
     )
 }
 
diff --git a/crates/ide-completion/src/tests/record.rs b/crates/ide-completion/src/tests/record.rs
index 2c497617484..9e442dbbc56 100644
--- a/crates/ide-completion/src/tests/record.rs
+++ b/crates/ide-completion/src/tests/record.rs
@@ -41,6 +41,8 @@ fn foo(s: Struct) {
 "#,
         expect![[r#"
             fd bar u32
+            kw mut
+            kw ref
         "#]],
     );
 }
@@ -58,6 +60,8 @@ fn foo(e: Enum) {
 "#,
         expect![[r#"
             fd bar u32
+            kw mut
+            kw ref
         "#]],
     );
 }
@@ -93,6 +97,8 @@ fn foo(f: Struct) {
 ",
         expect![[r#"
             fd field u32
+            kw mut
+            kw ref
         "#]],
     );
 }
diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs
index b7bf45c04c9..2a41d0a701f 100644
--- a/crates/syntax/src/ast/node_ext.rs
+++ b/crates/syntax/src/ast/node_ext.rs
@@ -510,6 +510,10 @@ impl ast::RecordPatField {
         }
     }
 
+    pub fn parent_record_pat(&self) -> ast::RecordPat {
+        self.syntax().ancestors().find_map(ast::RecordPat::cast).unwrap()
+    }
+
     /// Deals with field init shorthand
     pub fn field_name(&self) -> Option<NameOrNameRef> {
         if let Some(name_ref) = self.name_ref() {