Repository: rust-lang/rust-analyzer
Pull request #12604 (2022-06-20 19:55:54)

internal: Simplify some completions


Patch:
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs
index 592e7d3b97..aa10b0f878 100644
--- a/crates/hir/src/semantics.rs
+++ b/crates/hir/src/semantics.rs
@@ -2,7 +2,7 @@
 
 mod source_to_def;
 
-use std::{cell::RefCell, fmt, iter};
+use std::{cell::RefCell, fmt, iter, ops};
 
 use base_db::{FileId, FileRange};
 use hir_def::{
@@ -1449,3 +1449,11 @@ impl<'a> SemanticsScope<'a> {
 }
 
 pub struct VisibleTraits(pub FxHashSet<TraitId>);
+
+impl ops::Deref for VisibleTraits {
+    type Target = FxHashSet<TraitId>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
diff --git a/crates/ide-completion/src/completions.rs b/crates/ide-completion/src/completions.rs
index 147563ef10..81bb59681b 100644
--- a/crates/ide-completion/src/completions.rs
+++ b/crates/ide-completion/src/completions.rs
@@ -36,7 +36,7 @@ use crate::{
         const_::render_const,
         function::{render_fn, render_method},
         literal::{render_struct_literal, render_variant_lit},
-        macro_::{render_macro, render_macro_pat},
+        macro_::render_macro,
         pattern::{render_struct_pat, render_variant_pat},
         render_field, render_path_resolution, render_pattern_resolution, render_tuple_field,
         type_alias::{render_type_alias, render_type_alias_with_eq},
@@ -101,15 +101,15 @@ impl Completions {
     pub(crate) fn add_keyword_snippet_expr(
         &mut self,
         ctx: &CompletionContext,
+        incomplete_let: bool,
         kw: &str,
         snippet: &str,
-        incomplete_let: bool,
     ) {
         let mut item = CompletionItem::new(CompletionItemKind::Keyword, ctx.source_range(), kw);
 
         match ctx.config.snippet_cap {
             Some(cap) => {
-                if snippet.ends_with('}') && incomplete_let {
+                if incomplete_let && snippet.ends_with('}') {
                     // complete block expression snippets with a trailing semicolon, if inside an incomplete let
                     cov_mark::hit!(let_semi);
                     item.insert_snippet(cap, format!("{};", snippet));
@@ -181,6 +181,17 @@ impl Completions {
         );
     }
 
+    pub(crate) fn add_enum_variants(
+        &mut self,
+        ctx: &CompletionContext,
+        path_ctx: &PathCompletionCtx,
+        e: hir::Enum,
+    ) {
+        e.variants(ctx.db)
+            .into_iter()
+            .for_each(|variant| self.add_enum_variant(ctx, path_ctx, variant, None));
+    }
+
     pub(crate) fn add_module(
         &mut self,
         ctx: &CompletionContext,
@@ -219,29 +230,6 @@ impl Completions {
         );
     }
 
-    pub(crate) fn add_macro_pat(
-        &mut self,
-        ctx: &CompletionContext,
-        pattern_ctx: &PatternContext,
-        mac: hir::Macro,
-        local_name: hir::Name,
-    ) {
-        let is_private_editable = match ctx.is_visible(&mac) {
-            Visible::Yes => false,
-            Visible::Editable => true,
-            Visible::No => return,
-        };
-        self.add(
-            render_macro_pat(
-                RenderContext::new(ctx).private_editable(is_private_editable),
-                pattern_ctx,
-                local_name,
-                mac,
-            )
-            .build(),
-        );
-    }
-
     pub(crate) fn add_function(
         &mut self,
         ctx: &CompletionContext,
diff --git a/crates/ide-completion/src/completions/dot.rs b/crates/ide-completion/src/completions/dot.rs
index bf0bce2198..911ef60930 100644
--- a/crates/ide-completion/src/completions/dot.rs
+++ b/crates/ide-completion/src/completions/dot.rs
@@ -121,7 +121,7 @@ fn complete_methods(
     receiver.iterate_method_candidates(
         ctx.db,
         &ctx.scope,
-        &ctx.traits_in_scope().0,
+        &ctx.traits_in_scope(),
         Some(ctx.module),
         None,
         |func| {
diff --git a/crates/ide-completion/src/completions/expr.rs b/crates/ide-completion/src/completions/expr.rs
index 6b36801205..058d0ab7bb 100644
--- a/crates/ide-completion/src/completions/expr.rs
+++ b/crates/ide-completion/src/completions/expr.rs
@@ -1,7 +1,6 @@
 //! Completion of names from the current scope in expression position.
 
 use hir::ScopeDef;
-use ide_db::FxHashSet;
 
 use crate::{
     context::{ExprCtx, PathCompletionCtx, Qualified},
@@ -33,24 +32,24 @@ pub(crate) fn complete_expr_path(
     let wants_mut_token =
         ref_expr_parent.as_ref().map(|it| it.mut_token().is_none()).unwrap_or(false);
 
-    let scope_def_applicable = |def| {
-        match def {
-            ScopeDef::GenericParam(hir::GenericParam::LifetimeParam(_)) | ScopeDef::Label(_) => {
-                false
-            }
-            // Don't suggest attribute macros and derives.
-            ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac)) => mac.is_fn_like(ctx.db),
-            _ => true,
-        }
+    let scope_def_applicable = |def| match def {
+        ScopeDef::GenericParam(hir::GenericParam::LifetimeParam(_)) | ScopeDef::Label(_) => false,
+        ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac)) => mac.is_fn_like(ctx.db),
+        _ => true,
+    };
+
+    let add_assoc_item = |acc: &mut Completions, item| match item {
+        hir::AssocItem::Function(func) => acc.add_function(ctx, path_ctx, func, None),
+        hir::AssocItem::Const(ct) => acc.add_const(ctx, ct),
+        hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty),
     };
 
     match qualified {
         Qualified::Infer => ctx
             .traits_in_scope()
-            .0
-            .into_iter()
-            .flat_map(|it| hir::Trait::from(it).items(ctx.sema.db))
-            .for_each(|item| add_assoc_item(acc, ctx, path_ctx, item)),
+            .iter()
+            .flat_map(|&it| hir::Trait::from(it).items(ctx.sema.db))
+            .for_each(|item| add_assoc_item(acc, item)),
         Qualified::With { resolution: None, .. } => {}
         Qualified::With { resolution: Some(resolution), .. } => {
             // Add associated types on type parameters and `Self`.
@@ -67,46 +66,32 @@ pub(crate) fn complete_expr_path(
                         }
                     }
                 }
-
                 hir::PathResolution::Def(
                     def @ (hir::ModuleDef::Adt(_)
                     | hir::ModuleDef::TypeAlias(_)
                     | hir::ModuleDef::BuiltinType(_)),
                 ) => {
-                    if let &hir::ModuleDef::Adt(hir::Adt::Enum(e)) = def {
-                        add_enum_variants(acc, ctx, path_ctx, e);
-                    }
                     let ty = match def {
                         hir::ModuleDef::Adt(adt) => adt.ty(ctx.db),
-                        hir::ModuleDef::TypeAlias(a) => {
-                            let ty = a.ty(ctx.db);
-                            if let Some(hir::Adt::Enum(e)) = ty.as_adt() {
-                                cov_mark::hit!(completes_variant_through_alias);
-                                add_enum_variants(acc, ctx, path_ctx, e);
-                            }
-                            ty
-                        }
+                        hir::ModuleDef::TypeAlias(a) => a.ty(ctx.db),
                         hir::ModuleDef::BuiltinType(builtin) => {
                             cov_mark::hit!(completes_primitive_assoc_const);
                             builtin.ty(ctx.db)
                         }
-                        _ => unreachable!(),
+                        _ => return,
                     };
 
+                    if let Some(hir::Adt::Enum(e)) = ty.as_adt() {
+                        cov_mark::hit!(completes_variant_through_alias);
+                        acc.add_enum_variants(ctx, path_ctx, e);
+                    }
+
                     // XXX: For parity with Rust bug #22519, this does not complete Ty::AssocType.
                     // (where AssocType is defined on a trait, not an inherent impl)
 
-                    ty.iterate_path_candidates(
-                        ctx.db,
-                        &ctx.scope,
-                        &ctx.traits_in_scope().0,
-                        Some(ctx.module),
-                        None,
-                        |item| {
-                            add_assoc_item(acc, ctx, path_ctx, item);
-                            None::<()>
-                        },
-                    );
+                    ctx.iterate_path_candidates(&ty, |item| {
+                        add_assoc_item(acc, item);
+                    });
 
                     // Iterate assoc types separately
                     ty.iterate_assoc_items(ctx.db, ctx.krate, |item| {
@@ -119,7 +104,7 @@ pub(crate) fn complete_expr_path(
                 hir::PathResolution::Def(hir::ModuleDef::Trait(t)) => {
                     // Handles `Trait::assoc` as well as `<Ty as Trait>::assoc`.
                     for item in t.items(ctx.db) {
-                        add_assoc_item(acc, ctx, path_ctx, item);
+                        add_assoc_item(acc, item);
                     }
                 }
                 hir::PathResolution::TypeParam(_) | hir::PathResolution::SelfType(_) => {
@@ -130,24 +115,12 @@ pub(crate) fn complete_expr_path(
                     };
 
                     if let Some(hir::Adt::Enum(e)) = ty.as_adt() {
-                        add_enum_variants(acc, ctx, path_ctx, e);
+                        acc.add_enum_variants(ctx, path_ctx, e);
                     }
-                    let mut seen = FxHashSet::default();
-                    ty.iterate_path_candidates(
-                        ctx.db,
-                        &ctx.scope,
-                        &ctx.traits_in_scope().0,
-                        Some(ctx.module),
-                        None,
-                        |item| {
-                            // We might iterate candidates of a trait multiple times here, so deduplicate
-                            // them.
-                            if seen.insert(item) {
-                                add_assoc_item(acc, ctx, path_ctx, item);
-                            }
-                            None::<()>
-                        },
-                    );
+
+                    ctx.iterate_path_candidates(&ty, |item| {
+                        add_assoc_item(acc, item);
+                    });
                 }
                 _ => (),
             }
@@ -212,7 +185,7 @@ pub(crate) fn complete_expr_path(
 
             if is_func_update.is_none() {
                 let mut add_keyword =
-                    |kw, snippet| acc.add_keyword_snippet_expr(ctx, kw, snippet, incomplete_let);
+                    |kw, snippet| acc.add_keyword_snippet_expr(ctx, incomplete_let, kw, snippet);
 
                 if !in_block_expr {
                     add_keyword("unsafe", "unsafe {\n    $0\n}");
@@ -265,27 +238,3 @@ pub(crate) fn complete_expr_path(
         }
     }
 }
-
-fn add_assoc_item(
-    acc: &mut Completions,
-    ctx: &CompletionContext,
-    path_ctx: &PathCompletionCtx,
-    item: hir::AssocItem,
-) {
-    match item {
-        hir::AssocItem::Function(func) => acc.add_function(ctx, path_ctx, func, None),
-        hir::AssocItem::Const(ct) => acc.add_const(ctx, ct),
-        hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty),
-    }
-}
-
-fn add_enum_variants(
-    acc: &mut Completions,
-    ctx: &CompletionContext,
-    path_ctx: &PathCompletionCtx,
-    e: hir::Enum,
-) {
-    e.variants(ctx.db)
-        .into_iter()
-        .for_each(|variant| acc.add_enum_variant(ctx, path_ctx, variant, None));
-}
diff --git a/crates/ide-completion/src/completions/pattern.rs b/crates/ide-completion/src/completions/pattern.rs
index 4ea80a5077..7a344c2c7b 100644
--- a/crates/ide-completion/src/completions/pattern.rs
+++ b/crates/ide-completion/src/completions/pattern.rs
@@ -1,7 +1,6 @@
 //! Completes constants and paths in unqualified patterns.
 
 use hir::{db::DefDatabase, AssocItem, ScopeDef};
-use ide_db::FxHashSet;
 use syntax::ast::Pat;
 
 use crate::{
@@ -81,9 +80,7 @@ pub(crate) fn complete_pattern(
                 hir::ModuleDef::Adt(hir::Adt::Enum(e)) => refutable || single_variant_enum(e),
                 hir::ModuleDef::Const(..) => refutable,
                 hir::ModuleDef::Module(..) => true,
-                hir::ModuleDef::Macro(mac) if mac.is_fn_like(ctx.db) => {
-                    return acc.add_macro_pat(ctx, pattern_ctx, mac, name);
-                }
+                hir::ModuleDef::Macro(mac) => mac.is_fn_like(ctx.db),
                 _ => false,
             },
             hir::ScopeDef::ImplSelfType(impl_) => match impl_.self_ty(ctx.db).as_adt() {
@@ -136,12 +133,7 @@ pub(crate) fn complete_pattern_path(
                         }
                     }
                 }
-                res @ (hir::PathResolution::TypeParam(_)
-                | hir::PathResolution::SelfType(_)
-                | hir::PathResolution::Def(hir::ModuleDef::Adt(hir::Adt::Struct(_)))
-                | hir::PathResolution::Def(hir::ModuleDef::Adt(hir::Adt::Enum(_)))
-                | hir::PathResolution::Def(hir::ModuleDef::Adt(hir::Adt::Union(_)))
-                | hir::PathResolution::Def(hir::ModuleDef::BuiltinType(_))) => {
+                res => {
                     let ty = match res {
                         hir::PathResolution::TypeParam(param) => param.ty(ctx.db),
                         hir::PathResolution::SelfType(impl_def) => impl_def.self_ty(ctx.db),
@@ -149,10 +141,6 @@ pub(crate) fn complete_pattern_path(
                             s.ty(ctx.db)
                         }
                         hir::PathResolution::Def(hir::ModuleDef::Adt(hir::Adt::Enum(e))) => {
-                            cov_mark::hit!(enum_plain_qualified_use_tree);
-                            e.variants(ctx.db).into_iter().for_each(|variant| {
-                                acc.add_enum_variant(ctx, path_ctx, variant, None)
-                            });
                             e.ty(ctx.db)
                         }
                         hir::PathResolution::Def(hir::ModuleDef::Adt(hir::Adt::Union(u))) => {
@@ -162,41 +150,33 @@ pub(crate) fn complete_pattern_path(
                         _ => return,
                     };
 
-                    let mut seen = FxHashSet::default();
-                    ty.iterate_path_candidates(
-                        ctx.db,
-                        &ctx.scope,
-                        &ctx.scope.visible_traits().0,
-                        Some(ctx.module),
-                        None,
-                        |item| {
-                            match item {
-                                AssocItem::TypeAlias(ta) => {
-                                    // We might iterate candidates of a trait multiple times here, so deduplicate them.
-                                    if seen.insert(item) {
-                                        acc.add_type_alias(ctx, ta);
-                                    }
-                                }
-                                AssocItem::Const(c) => {
-                                    if seen.insert(item) {
-                                        acc.add_const(ctx, c);
-                                    }
-                                }
-                                _ => {}
-                            }
-                            None::<()>
-                        },
-                    );
+                    if let Some(hir::Adt::Enum(e)) = ty.as_adt() {
+                        cov_mark::hit!(enum_plain_qualified_use_tree);
+                        acc.add_enum_variants(ctx, path_ctx, e);
+                    }
+
+                    ctx.iterate_path_candidates(&ty, |item| match item {
+                        AssocItem::TypeAlias(ta) => acc.add_type_alias(ctx, ta),
+                        AssocItem::Const(c) => acc.add_const(ctx, c),
+                        _ => {}
+                    });
                 }
-                _ => {}
             }
         }
-        // qualifier can only be none here if we are in a TuplePat or RecordPat in which case special characters have to follow the path
         Qualified::Absolute => acc.add_crate_roots(ctx, path_ctx),
         Qualified::No => {
+            // this will only be hit if there are brackets or braces, otherwise this will be parsed as an ident pattern
             ctx.process_all_names(&mut |name, res| {
-                // FIXME: properly filter here
-                if let ScopeDef::ModuleDef(_) = res {
+                // FIXME: we should check what kind of pattern we are in and filter accordingly
+                let add_completion = match res {
+                    ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac)) => mac.is_fn_like(ctx.db),
+                    ScopeDef::ModuleDef(hir::ModuleDef::Adt(_)) => true,
+                    ScopeDef::ModuleDef(hir::ModuleDef::Variant(_)) => true,
+                    ScopeDef::ModuleDef(hir::ModuleDef::Module(_)) => true,
+                    ScopeDef::ImplSelfType(_) => true,
+                    _ => false,
+                };
+                if add_completion {
                     acc.add_path_resolution(ctx, path_ctx, name, res);
                 }
             });
diff --git a/crates/ide-completion/src/completions/record.rs b/crates/ide-completion/src/completions/record.rs
index 8cef3a7018..9f5922c5b0 100644
--- a/crates/ide-completion/src/completions/record.rs
+++ b/crates/ide-completion/src/completions/record.rs
@@ -17,6 +17,7 @@ pub(crate) fn complete_record_pattern_fields(
         complete_fields(acc, ctx, ctx.sema.record_pattern_missing_fields(record_pat));
     }
 }
+
 pub(crate) fn complete_record_expr_fields(
     acc: &mut Completions,
     ctx: &CompletionContext,
@@ -41,7 +42,6 @@ pub(crate) fn complete_record_expr_fields(
         }
         _ => {
             let missing_fields = ctx.sema.record_literal_missing_fields(record_expr);
-
             add_default_update(acc, ctx, ty, &missing_fields);
             if dot_prefix {
                 let mut item =
@@ -56,6 +56,29 @@ pub(crate) fn complete_record_expr_fields(
     complete_fields(acc, ctx, missing_fields);
 }
 
+// FIXME: This should probably be part of complete_path_expr
+pub(crate) fn complete_record_expr_func_update(
+    acc: &mut Completions,
+    ctx: &CompletionContext,
+    path_ctx: &PathCompletionCtx,
+    expr_ctx: &ExprCtx,
+) {
+    if !matches!(path_ctx.qualified, Qualified::No) {
+        return;
+    }
+    if let ExprCtx { is_func_update: Some(record_expr), .. } = expr_ctx {
+        let ty = ctx.sema.type_of_expr(&Expr::RecordExpr(record_expr.clone()));
+
+        match ty.as_ref().and_then(|t| t.original.as_adt()) {
+            Some(hir::Adt::Union(_)) => (),
+            _ => {
+                let missing_fields = ctx.sema.record_literal_missing_fields(record_expr);
+                add_default_update(acc, ctx, ty, &missing_fields);
+            }
+        };
+    }
+}
+
 fn add_default_update(
     acc: &mut Completions,
     ctx: &CompletionContext,
@@ -67,6 +90,7 @@ fn add_default_update(
         .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() {
+        // FIXME: This should make use of scope_def like completions so we get all the other goodies
         let completion_text = "..Default::default()";
         let mut item = CompletionItem::new(SymbolKind::Field, ctx.source_range(), completion_text);
         let completion_text =
@@ -79,28 +103,6 @@ fn add_default_update(
     }
 }
 
-pub(crate) fn complete_record_expr_func_update(
-    acc: &mut Completions,
-    ctx: &CompletionContext,
-    path_ctx: &PathCompletionCtx,
-    expr_ctx: &ExprCtx,
-) {
-    if !matches!(path_ctx.qualified, Qualified::No) {
-        return;
-    }
-    if let ExprCtx { is_func_update: Some(record_expr), .. } = expr_ctx {
-        let ty = ctx.sema.type_of_expr(&Expr::RecordExpr(record_expr.clone()));
-
-        match ty.as_ref().and_then(|t| t.original.as_adt()) {
-            Some(hir::Adt::Union(_)) => (),
-            _ => {
-                let missing_fields = ctx.sema.record_literal_missing_fields(record_expr);
-                add_default_update(acc, ctx, ty, &missing_fields);
-            }
-        };
-    }
-}
-
 fn complete_fields(
     acc: &mut Completions,
     ctx: &CompletionContext,
diff --git a/crates/ide-completion/src/completions/snippet.rs b/crates/ide-completion/src/completions/snippet.rs
index 9992a81fe0..a530f4262e 100644
--- a/crates/ide-completion/src/completions/snippet.rs
+++ b/crates/ide-completion/src/completions/snippet.rs
@@ -9,12 +9,6 @@ use crate::{
     CompletionContext, CompletionItem, CompletionItemKind, Completions, SnippetScope,
 };
 
-fn snippet(ctx: &CompletionContext, cap: SnippetCap, label: &str, snippet: &str) -> Builder {
-    let mut item = CompletionItem::new(CompletionItemKind::Snippet, ctx.source_range(), label);
-    item.insert_snippet(cap, snippet);
-    item
-}
-
 pub(crate) fn complete_expr_snippet(
     acc: &mut Completions,
     ctx: &CompletionContext,
@@ -124,6 +118,12 @@ macro_rules! $1 {
     }
 }
 
+fn snippet(ctx: &CompletionContext, cap: SnippetCap, label: &str, snippet: &str) -> Builder {
+    let mut item = CompletionItem::new(CompletionItemKind::Snippet, ctx.source_range(), label);
+    item.insert_snippet(cap, snippet);
+    item
+}
+
 fn add_custom_completions(
     acc: &mut Completions,
     ctx: &CompletionContext,
diff --git a/crates/ide-completion/src/completions/type.rs b/crates/ide-completion/src/completions/type.rs
index c5b65d36ae..616d862154 100644
--- a/crates/ide-completion/src/completions/type.rs
+++ b/crates/ide-completion/src/completions/type.rs
@@ -1,7 +1,6 @@
 //! Completion of names from the current scope in type position.
 
 use hir::{HirDisplay, ScopeDef};
-use ide_db::FxHashSet;
 use syntax::{ast, AstNode};
 
 use crate::{
@@ -52,9 +51,8 @@ pub(crate) fn complete_type_path(
     match qualified {
         Qualified::Infer => ctx
             .traits_in_scope()
-            .0
-            .into_iter()
-            .flat_map(|it| hir::Trait::from(it).items(ctx.sema.db))
+            .iter()
+            .flat_map(|&it| hir::Trait::from(it).items(ctx.sema.db))
             .for_each(|item| add_assoc_item(acc, item)),
         Qualified::With { resolution: None, .. } => {}
         Qualified::With { resolution: Some(resolution), .. } => {
@@ -88,17 +86,9 @@ pub(crate) fn complete_type_path(
                     // XXX: For parity with Rust bug #22519, this does not complete Ty::AssocType.
                     // (where AssocType is defined on a trait, not an inherent impl)
 
-                    ty.iterate_path_candidates(
-                        ctx.db,
-                        &ctx.scope,
-                        &ctx.traits_in_scope().0,
-                        Some(ctx.module),
-                        None,
-                        |item| {
-                            add_assoc_item(acc, item);
-                            None::<()>
-                        },
-                    );
+                    ctx.iterate_path_candidates(&ty, |item| {
+                        add_assoc_item(acc, item);
+                    });
 
                     // Iterate assoc types separately
                     ty.iterate_assoc_items(ctx.db, ctx.krate, |item| {
@@ -121,22 +111,9 @@ pub(crate) fn complete_type_path(
                         _ => return,
                     };
 
-                    let mut seen = FxHashSet::default();
-                    ty.iterate_path_candidates(
-                        ctx.db,
-                        &ctx.scope,
-                        &ctx.traits_in_scope().0,
-                        Some(ctx.module),
-                        None,
-                        |item| {
-                            // We might iterate candidates of a trait multiple times here, so deduplicate
-                            // them.
-                            if seen.insert(item) {
-                                add_assoc_item(acc, item);
-                            }
-                            None::<()>
-                        },
-                    );
+                    ctx.iterate_path_candidates(&ty, |item| {
+                        add_assoc_item(acc, item);
+                    });
                 }
                 _ => (),
             }
diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs
index 8ea03358ae..01a2f96fd1 100644
--- a/crates/ide-completion/src/context.rs
+++ b/crates/ide-completion/src/context.rs
@@ -371,7 +371,9 @@ impl<'a> CompletionContext<'a> {
     where
         I: hir::HasVisibility + hir::HasAttrs + hir::HasCrate + Copy,
     {
-        self.is_visible_impl(&item.visibility(self.db), &item.attrs(self.db), item.krate(self.db))
+        let vis = item.visibility(self.db);
+        let attrs = item.attrs(self.db);
+        self.is_visible_impl(&vis, &attrs, item.krate(self.db))
     }
 
     pub(crate) fn is_scope_def_hidden(&self, scope_def: ScopeDef) -> bool {
@@ -391,6 +393,7 @@ impl<'a> CompletionContext<'a> {
             _ => false,
         }
     }
+
     /// Whether the given trait is an operator trait or not.
     pub(crate) fn is_ops_trait(&self, trait_: hir::Trait) -> bool {
         match trait_.attrs(self.db).lang() {
@@ -408,6 +411,29 @@ impl<'a> CompletionContext<'a> {
         traits_in_scope
     }
 
+    pub(crate) fn iterate_path_candidates(
+        &self,
+        ty: &hir::Type,
+        mut cb: impl FnMut(hir::AssocItem),
+    ) {
+        let mut seen = FxHashSet::default();
+        ty.iterate_path_candidates(
+            self.db,
+            &self.scope,
+            &self.traits_in_scope(),
+            Some(self.module),
+            None,
+            |item| {
+                // We might iterate candidates of a trait multiple times here, so deduplicate
+                // them.
+                if seen.insert(item) {
+                    cb(item)
+                }
+                None::<()>
+            },
+        );
+    }
+
     /// 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)) {
         let _p = profile::span("CompletionContext::process_all_names");
diff --git a/crates/ide-completion/src/lib.rs b/crates/ide-completion/src/lib.rs
index fe02f05fd1..b806d955d9 100644
--- a/crates/ide-completion/src/lib.rs
+++ b/crates/ide-completion/src/lib.rs
@@ -148,7 +148,7 @@ pub fn completions(
     config: &CompletionConfig,
     position: FilePosition,
     trigger_character: Option<char>,
-) -> Option<Completions> {
+) -> Option<Vec<CompletionItem>> {
     let (ctx, analysis) = &CompletionContext::new(db, position, config)?;
     let mut completions = Completions::default();
 
@@ -163,7 +163,7 @@ pub fn completions(
             }
         }
         // prevent `(` from triggering unwanted completion noise
-        return Some(completions);
+        return Some(completions.into());
     }
 
     {
@@ -197,7 +197,7 @@ pub fn completions(
         }
     }
 
-    Some(completions)
+    Some(completions.into())
 }
 
 /// Resolves additional completion data at the position given.
diff --git a/crates/project-model/src/workspace.rs b/crates/project-model/src/workspace.rs
index 6aefb84db8..8982a9904e 100644
--- a/crates/project-model/src/workspace.rs
+++ b/crates/project-model/src/workspace.rs
@@ -541,6 +541,8 @@ fn cargo_to_crate_graph(
 
     let mut pkg_to_lib_crate = FxHashMap::default();
 
+    // Add test cfg for non-sysroot crates
+    cfg_options.insert_atom("test".into());
     cfg_options.insert_atom("debug_assertions".into());
 
     let mut pkg_crates = FxHashMap::default();
@@ -548,18 +550,14 @@ fn cargo_to_crate_graph(
     let mut has_private = false;
     // Next, create crates for each package, target pair
     for pkg in cargo.packages() {
-        let mut cfg_options = cfg_options.clone();
+        let mut cfg_options = &cfg_options;
+        let mut replaced_cfg_options;
 
         let overrides = match override_cfg {
             CfgOverrides::Wildcard(cfg_diff) => Some(cfg_diff),
             CfgOverrides::Selective(cfg_overrides) => cfg_overrides.get(&cargo[pkg].name),
         };
 
-        // Add test cfg for local crates
-        if cargo[pkg].is_local {
-            cfg_options.insert_atom("test".into());
-        }
-
         if let Some(overrides) = overrides {
             // FIXME: this is sort of a hack to deal with #![cfg(not(test))] vanishing such as seen
             // in ed25519_dalek (#7243), and libcore (#9203) (although you only hit that one while
@@ -568,7 +566,9 @@ fn cargo_to_crate_graph(
             // A more ideal solution might be to reanalyze crates based on where the cursor is and
             // figure out the set of cfgs that would have to apply to make it active.
 
-            cfg_options.apply_diff(overrides.clone());
+            replaced_cfg_options = cfg_options.clone();
+            replaced_cfg_options.apply_diff(overrides.clone());
+            cfg_options = &replaced_cfg_options;
         };
 
         has_private |= cargo[pkg].metadata.rustc_private;
@@ -588,7 +588,7 @@ fn cargo_to_crate_graph(
                     &mut crate_graph,
                     &cargo[pkg],
                     build_scripts.get_output(pkg),
-                    cfg_options.clone(),
+                    cfg_options,
                     &mut |path| load_proc_macro(&cargo[tgt].name, path),
                     file_id,
                     &cargo[tgt].name,
@@ -753,7 +753,8 @@ fn handle_rustc_crates(
                 queue.push_back(dep.pkg);
             }
 
-            let mut cfg_options = cfg_options.clone();
+            let mut cfg_options = cfg_options;
+            let mut replaced_cfg_options;
 
             let overrides = match override_cfg {
                 CfgOverrides::Wildcard(cfg_diff) => Some(cfg_diff),
@@ -770,7 +771,9 @@ fn handle_rustc_crates(
                 // A more ideal solution might be to reanalyze crates based on where the cursor is and
                 // figure out the set of cfgs that would have to apply to make it active.
 
-                cfg_options.apply_diff(overrides.clone());
+                replaced_cfg_options = cfg_options.clone();
+                replaced_cfg_options.apply_diff(overrides.clone());
+                cfg_options = &replaced_cfg_options;
             };
 
             for &tgt in rustc_workspace[pkg].targets.iter() {
@@ -782,7 +785,7 @@ fn handle_rustc_crates(
                         crate_graph,
                         &rustc_workspace[pkg],
                         build_scripts.get_output(pkg),
-                        cfg_options.clone(),
+                        cfg_options,
                         &mut |path| load_proc_macro(&rustc_workspace[tgt].name, path),
                         file_id,
                         &rustc_workspace[tgt].name,
@@ -837,21 +840,15 @@ fn add_target_crate_root(
     crate_graph: &mut CrateGraph,
     pkg: &PackageData,
     build_data: Option<&BuildScriptOutput>,
-    cfg_options: CfgOptions,
+    cfg_options: &CfgOptions,
     load_proc_macro: &mut dyn FnMut(&AbsPath) -> ProcMacroLoadResult,
     file_id: FileId,
     cargo_name: &str,
     is_proc_macro: bool,
 ) -> CrateId {
     let edition = pkg.edition;
-    let mut potential_cfg_options = cfg_options.clone();
-    potential_cfg_options.extend(
-        pkg.features
-            .iter()
-            .map(|feat| CfgFlag::KeyValue { key: "feature".into(), value: feat.0.into() }),
-    );
     let cfg_options = {
-        let mut opts = cfg_options;
+        let mut opts = cfg_options.clone();
         for feature in pkg.active_features.iter() {
             opts.insert_key_value("feature".into(), feature.into());
         }
@@ -876,6 +873,12 @@ fn add_target_crate_root(
     };
 
     let display_name = CrateDisplayName::from_canonical_name(cargo_name.to_string());
+    let mut potential_cfg_options = cfg_options.clone();
+    potential_cfg_options.extend(
+        pkg.features
+            .iter()
+            .map(|feat| CfgFlag::KeyValue { key: "feature".into(), value: feat.0.into() }),
+    );
     crate_graph.add_crate_root(
         file_id,
         edition,
diff --git a/xtask/src/release.rs b/xtask/src/release.rs
index 1c5fc64c24..04544d6f6b 100644
--- a/xtask/src/release.rs
+++ b/xtask/src/release.rs
@@ -36,15 +36,7 @@ impl flags::Release {
 
         let today = date_iso(sh)?;
         let commit = cmd!(sh, "git rev-parse HEAD").read()?;
-        let changelog_n = sh
-            .read_dir(changelog_dir.as_path())?
-            .into_iter()
-            .filter_map(|p| p.file_stem().map(|s| s.to_string_lossy().to_string()))
-            .filter_map(|s| s.splitn(5, '-').last().map(|n| n.replace('-', ".")))
-            .filter_map(|s| s.parse::<f32>().ok())
-            .map(|n| 1 + n.floor() as usize)
-            .max()
-            .unwrap_or_default();
+        let changelog_n = sh.read_dir(changelog_dir.as_path())?.len();
 
         for adoc in [
             "manual.adoc",


Test patch:
diff --git a/crates/ide-completion/src/tests/pattern.rs b/crates/ide-completion/src/tests/pattern.rs
index 8953f1b2fd..7169209d81 100644
--- a/crates/ide-completion/src/tests/pattern.rs
+++ b/crates/ide-completion/src/tests/pattern.rs
@@ -399,7 +399,6 @@ fn foo() {
 
 #[test]
 fn completes_no_delims_if_existing() {
-    // FIXME: We should not complete functions here
     check_empty(
         r#"
 struct Bar(u32);
@@ -410,9 +409,7 @@ fn foo() {
 }
 "#,
         expect![[r#"
-            fn foo     fn()
             st Bar
-            bt u32
             kw crate::
             kw self::
             kw super::
@@ -428,9 +425,7 @@ fn foo() {
 }
 "#,
         expect![[r#"
-            fn foo     fn()
             st Foo
-            bt u32
             kw crate::
             kw self::
             kw super::
diff --git a/crates/project-model/src/tests.rs b/crates/project-model/src/tests.rs
index 6f883b9e84..1a43935470 100644
--- a/crates/project-model/src/tests.rs
+++ b/crates/project-model/src/tests.rs
@@ -1041,6 +1041,7 @@ fn cargo_hello_world_project_model() {
                                 "debug_assertions",
                                 "feature=default",
                                 "feature=std",
+                                "test",
                             ],
                         ),
                         potential_cfg_options: CfgOptions(
@@ -1053,6 +1054,7 @@ fn cargo_hello_world_project_model() {
                                 "feature=rustc-dep-of-std",
                                 "feature=std",
                                 "feature=use_std",
+                                "test",
                             ],
                         ),
                         env: Env {