Repository: rust-analyzer/rust-analyzer
Pull request #11043 (2021-12-17 17:46:28)

fix: fix incorrect mismatched argument count diagnostic with `std::arch` functions
Adds basic support for `#[rustc_legacy_const_generics]`.

Fixes https://github.com/rust-analyzer/rust-analyzer/issues/10009

Full support would involve actually checking call arguments against the right expected types.

bors r+


Patch:
diff --git a/crates/hir_def/src/data.rs b/crates/hir_def/src/data.rs
index b9f8c2f297..bf8bd931a5 100644
--- a/crates/hir_def/src/data.rs
+++ b/crates/hir_def/src/data.rs
@@ -26,6 +26,7 @@ pub struct FunctionData {
     pub attrs: Attrs,
     pub visibility: RawVisibility,
     pub abi: Option<Interned<str>>,
+    pub legacy_const_generics_indices: Vec<u32>,
     flags: FnFlags,
 }
 
@@ -58,6 +59,14 @@ impl FunctionData {
             flags.bits |= FnFlags::IS_IN_EXTERN_BLOCK;
         }
 
+        let legacy_const_generics_indices = item_tree
+            .attrs(db, krate, ModItem::from(loc.id.value).into())
+            .by_key("rustc_legacy_const_generics")
+            .tt_values()
+            .next()
+            .map(|arg| parse_rustc_legacy_const_generics(arg))
+            .unwrap_or_default();
+
         Arc::new(FunctionData {
             name: func.name.clone(),
             params: enabled_params
@@ -72,6 +81,7 @@ impl FunctionData {
             attrs: item_tree.attrs(db, krate, ModItem::from(loc.id.value).into()),
             visibility: item_tree[func.visibility].clone(),
             abi: func.abi.clone(),
+            legacy_const_generics_indices,
             flags,
         })
     }
@@ -111,6 +121,28 @@ impl FunctionData {
     }
 }
 
+fn parse_rustc_legacy_const_generics(tt: &tt::Subtree) -> Vec<u32> {
+    let mut indices = Vec::new();
+    for args in tt.token_trees.chunks(2) {
+        match &args[0] {
+            tt::TokenTree::Leaf(tt::Leaf::Literal(lit)) => match lit.text.parse() {
+                Ok(index) => indices.push(index),
+                Err(_) => break,
+            },
+            _ => break,
+        }
+
+        if let Some(comma) = args.get(1) {
+            match comma {
+                tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if punct.char == ',' => {}
+                _ => break,
+            }
+        }
+    }
+
+    indices
+}
+
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub struct TypeAliasData {
     pub name: Name,
diff --git a/crates/hir_def/src/item_tree.rs b/crates/hir_def/src/item_tree.rs
index 2449e4f3e2..68d77084a3 100644
--- a/crates/hir_def/src/item_tree.rs
+++ b/crates/hir_def/src/item_tree.rs
@@ -148,7 +148,7 @@ impl ItemTree {
         let loc = db.lookup_intern_block(block);
         let block = loc.ast_id.to_node(db.upcast());
         let hygiene = Hygiene::new(db.upcast(), loc.ast_id.file_id);
-        let ctx = lower::Ctx::new(db, hygiene, loc.ast_id.file_id);
+        let ctx = lower::Ctx::new(db, hygiene.clone(), loc.ast_id.file_id);
         Arc::new(ctx.lower_block(&block))
     }
 
@@ -216,7 +216,7 @@ impl ItemTree {
         self.attrs.get(&of).unwrap_or(&RawAttrs::EMPTY)
     }
 
-    pub(crate) fn attrs(&self, db: &dyn DefDatabase, krate: CrateId, of: AttrOwner) -> Attrs {
+    pub fn attrs(&self, db: &dyn DefDatabase, krate: CrateId, of: AttrOwner) -> Attrs {
         self.raw_attrs(of).clone().filter(db, krate)
     }
 
diff --git a/crates/hir_ty/src/diagnostics/expr.rs b/crates/hir_ty/src/diagnostics/expr.rs
index 195c53c17e..7e1bf9ceea 100644
--- a/crates/hir_ty/src/diagnostics/expr.rs
+++ b/crates/hir_ty/src/diagnostics/expr.rs
@@ -235,20 +235,33 @@ impl ExprValidator {
             return;
         }
 
-        let params = sig.params();
+        if sig.legacy_const_generics_indices.is_empty() {
+            let mut param_count = sig.params().len();
 
-        let mut param_count = params.len();
-
-        if arg_count != param_count {
-            if is_method_call {
-                param_count -= 1;
-                arg_count -= 1;
+            if arg_count != param_count {
+                if is_method_call {
+                    param_count -= 1;
+                    arg_count -= 1;
+                }
+                self.diagnostics.push(BodyValidationDiagnostic::MismatchedArgCount {
+                    call_expr: call_id,
+                    expected: param_count,
+                    found: arg_count,
+                });
+            }
+        } else {
+            // With `#[rustc_legacy_const_generics]` there are basically two parameter counts that
+            // are allowed.
+            let count_non_legacy = sig.params().len();
+            let count_legacy = sig.params().len() + sig.legacy_const_generics_indices.len();
+            if arg_count != count_non_legacy && arg_count != count_legacy {
+                self.diagnostics.push(BodyValidationDiagnostic::MismatchedArgCount {
+                    call_expr: call_id,
+                    // Since most users will use the legacy way to call them, report against that.
+                    expected: count_legacy,
+                    found: arg_count,
+                });
             }
-            self.diagnostics.push(BodyValidationDiagnostic::MismatchedArgCount {
-                call_expr: call_id,
-                expected: param_count,
-                found: arg_count,
-            });
         }
     }
 
diff --git a/crates/hir_ty/src/lib.rs b/crates/hir_ty/src/lib.rs
index 320b170ee4..3e5f2d29df 100644
--- a/crates/hir_ty/src/lib.rs
+++ b/crates/hir_ty/src/lib.rs
@@ -175,6 +175,7 @@ pub fn make_canonical<T: HasInterner<Interner = Interner>>(
 pub struct CallableSig {
     params_and_return: Arc<[Ty]>,
     is_varargs: bool,
+    legacy_const_generics_indices: Arc<[u32]>,
 }
 
 has_interner!(CallableSig);
@@ -185,7 +186,11 @@ pub type PolyFnSig = Binders<CallableSig>;
 impl CallableSig {
     pub fn from_params_and_return(mut params: Vec<Ty>, ret: Ty, is_varargs: bool) -> CallableSig {
         params.push(ret);
-        CallableSig { params_and_return: params.into(), is_varargs }
+        CallableSig {
+            params_and_return: params.into(),
+            is_varargs,
+            legacy_const_generics_indices: Arc::new([]),
+        }
     }
 
     pub fn from_fn_ptr(fn_ptr: &FnPointer) -> CallableSig {
@@ -202,9 +207,14 @@ impl CallableSig {
                 .map(|arg| arg.assert_ty_ref(&Interner).clone())
                 .collect(),
             is_varargs: fn_ptr.sig.variadic,
+            legacy_const_generics_indices: Arc::new([]),
         }
     }
 
+    pub fn set_legacy_const_generics_indices(&mut self, indices: &[u32]) {
+        self.legacy_const_generics_indices = indices.into();
+    }
+
     pub fn to_fn_ptr(&self) -> FnPointer {
         FnPointer {
             num_binders: 0,
@@ -238,7 +248,11 @@ impl Fold<Interner> for CallableSig {
     {
         let vec = self.params_and_return.to_vec();
         let folded = vec.fold_with(folder, outer_binder)?;
-        Ok(CallableSig { params_and_return: folded.into(), is_varargs: self.is_varargs })
+        Ok(CallableSig {
+            params_and_return: folded.into(),
+            is_varargs: self.is_varargs,
+            legacy_const_generics_indices: self.legacy_const_generics_indices,
+        })
     }
 }
 
diff --git a/crates/hir_ty/src/lower.rs b/crates/hir_ty/src/lower.rs
index 7373c9eb8b..596bf9ee0f 100644
--- a/crates/hir_ty/src/lower.rs
+++ b/crates/hir_ty/src/lower.rs
@@ -1286,7 +1286,11 @@ fn fn_sig_for_fn(db: &dyn HirDatabase, def: FunctionId) -> PolyFnSig {
         .with_type_param_mode(TypeParamLoweringMode::Variable);
     let ret = ctx_ret.lower_ty(&data.ret_type);
     let generics = generics(db.upcast(), def.into());
-    make_binders(&generics, CallableSig::from_params_and_return(params, ret, data.is_varargs()))
+    let mut sig = CallableSig::from_params_and_return(params, ret, data.is_varargs());
+    if !data.legacy_const_generics_indices.is_empty() {
+        sig.set_legacy_const_generics_indices(&data.legacy_const_generics_indices);
+    }
+    make_binders(&generics, sig)
 }
 
 /// Build the declared type of a function. This should not need to look at the
diff --git a/crates/ide/src/highlight_related.rs b/crates/ide/src/highlight_related.rs
index de6afaef8e..aac23be777 100644
--- a/crates/ide/src/highlight_related.rs
+++ b/crates/ide/src/highlight_related.rs
@@ -78,7 +78,7 @@ fn highlight_references(
     token: SyntaxToken,
     file_id: FileId,
 ) -> Option<Vec<HighlightedRange>> {
-    let defs = find_defs(sema, token);
+    let defs = find_defs(sema, token.clone());
     let usages = defs
         .iter()
         .filter_map(|&d| {
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index 9d19d7c1c0..7d5cfaa937 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -94,11 +94,10 @@ pub(crate) fn hover(
     let sema = &hir::Semantics::new(db);
     let file = sema.parse(file_id).syntax().clone();
 
-    let offset = if !range.is_empty() {
+    if !range.is_empty() {
         return hover_ranged(&file, range, sema, config);
-    } else {
-        range.start()
-    };
+    }
+    let offset = range.start();
 
     let original_token = pick_best_token(file.token_at_offset(offset), |kind| match kind {
         IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] => 3,
diff --git a/crates/ide_assists/src/handlers/convert_comment_block.rs b/crates/ide_assists/src/handlers/convert_comment_block.rs
index 3261f56525..472aef2648 100644
--- a/crates/ide_assists/src/handlers/convert_comment_block.rs
+++ b/crates/ide_assists/src/handlers/convert_comment_block.rs
@@ -96,7 +96,8 @@ fn line_to_block(acc: &mut Assists, comment: ast::Comment) -> Option<()> {
             let block_prefix =
                 CommentKind { shape: CommentShape::Block, ..comment.kind() }.prefix();
 
-            let output = format!("{}\n{}\n{}*/", block_prefix, block_comment_body, indentation);
+            let output =
+                format!("{}\n{}\n{}*/", block_prefix, block_comment_body, indentation.to_string());
 
             edit.replace(target, output)
         },
diff --git a/crates/ide_assists/src/handlers/convert_into_to_from.rs b/crates/ide_assists/src/handlers/convert_into_to_from.rs
index 7f27b507ba..29555a5111 100644
--- a/crates/ide_assists/src/handlers/convert_into_to_from.rs
+++ b/crates/ide_assists/src/handlers/convert_into_to_from.rs
@@ -91,7 +91,10 @@ pub(crate) fn convert_into_to_from(acc: &mut Assists, ctx: &AssistContext) -> Op
             builder.replace(src_type.syntax().text_range(), dest_type.to_string());
             builder.replace(ast_trait.syntax().text_range(), format!("From<{}>", src_type));
             builder.replace(into_fn_return.syntax().text_range(), "-> Self");
-            builder.replace(into_fn_params.syntax().text_range(), format!("(val: {})", src_type));
+            builder.replace(
+                into_fn_params.syntax().text_range(),
+                format!("(val: {})", src_type.to_string()),
+            );
             builder.replace(into_fn_name.syntax().text_range(), "from");
 
             for s in selfs {
diff --git a/crates/ide_assists/src/handlers/destructure_tuple_binding.rs b/crates/ide_assists/src/handlers/destructure_tuple_binding.rs
index c21badb1e1..fa85e48c66 100644
--- a/crates/ide_assists/src/handlers/destructure_tuple_binding.rs
+++ b/crates/ide_assists/src/handlers/destructure_tuple_binding.rs
@@ -174,7 +174,7 @@ fn edit_tuple_assignment(
 
     // with sub_pattern: keep original tuple and add subpattern: `tup @ (_0, _1)`
     if in_sub_pattern {
-        let text = format!(" @ {}", tuple_pat);
+        let text = format!(" @ {}", tuple_pat.to_string());
         match ctx.config.snippet_cap {
             Some(cap) => {
                 let snip = add_cursor(&text);
diff --git a/crates/ide_assists/src/handlers/extract_module.rs b/crates/ide_assists/src/handlers/extract_module.rs
index 4bbfdae1d2..80fc7946ce 100644
--- a/crates/ide_assists/src/handlers/extract_module.rs
+++ b/crates/ide_assists/src/handlers/extract_module.rs
@@ -151,7 +151,7 @@ fn extract_target(node: &SyntaxNode, selection_range: TextRange) -> Option<Modul
     let mut body_items: Vec<ast::Item> = node
         .children()
         .filter_map(|child| {
-            if let Some(item) = ast::Item::cast(child) {
+            if let Some(item) = ast::Item::cast(child.clone()) {
                 if selection_range.contains_range(item.syntax().text_range()) {
                     return Some(item);
                 }
@@ -298,7 +298,7 @@ impl Module {
                 if let Some(name_ref) = ast::NameRef::cast(desc) {
                     return Some((
                         name_ref.syntax().text_range(),
-                        format!("{}::{}", self.name, name_ref),
+                        format!("{}::{}", self.name, name_ref.to_string()),
                     ));
                 }
             }
@@ -312,20 +312,20 @@ impl Module {
             get_replacements_for_visibilty_change(self.body_items.clone(), false);
 
         let impl_items = impls.into_iter().fold(Vec::new(), |mut impl_items, x| {
-            let mut this_impl_items =
+            let this_impl_items =
                 x.syntax().descendants().fold(Vec::new(), |mut this_impl_items, x| {
-                    if let Some(item) = ast::Item::cast(x) {
+                    if let Some(item) = ast::Item::cast(x.clone()) {
                         this_impl_items.push(item);
                     }
                     return this_impl_items;
                 });
 
-            impl_items.append(&mut this_impl_items);
+            impl_items.append(&mut this_impl_items.clone());
             return impl_items;
         });
 
         let (_, mut impl_item_replacements, _, _) =
-            get_replacements_for_visibilty_change(impl_items, true);
+            get_replacements_for_visibilty_change(impl_items.clone(), true);
 
         replacements.append(&mut impl_item_replacements);
 
@@ -337,7 +337,7 @@ impl Module {
                     .find(|x| x.to_string() == desc.to_string())
                     .is_some();
                 if is_record_field_present {
-                    replacements.push((desc.visibility(), desc.syntax().clone()));
+                    replacements.push((desc.visibility().clone(), desc.syntax().clone()));
                 }
             });
         });
@@ -472,7 +472,7 @@ impl Module {
                 (&x.1).into_iter().for_each(|x| {
                     let node_opt: Option<ast::Use> = find_node_at_range(file.syntax(), x.range);
                     if let Some(node) = node_opt {
-                        use_opt = Some(node);
+                        use_opt = Some(node.clone());
                     }
                 });
             }
@@ -529,7 +529,7 @@ impl Module {
         }
 
         if let Some(use_tree_str) = use_tree_str_opt {
-            let mut use_tree_str = use_tree_str;
+            let mut use_tree_str = use_tree_str.clone();
             use_tree_str.reverse();
             if use_tree_str[0].to_string().contains("super") {
                 let super_path = make::ext::ident_path("super");
@@ -776,24 +776,42 @@ fn get_replacements_for_visibilty_change(
         body_items.push(item.clone());
         //Use stmts are ignored
         match item {
-            ast::Item::Const(it) => replacements.push((it.visibility(), it.syntax().clone())),
-            ast::Item::Enum(it) => replacements.push((it.visibility(), it.syntax().clone())),
-            ast::Item::ExternCrate(it) => replacements.push((it.visibility(), it.syntax().clone())),
-            ast::Item::Fn(it) => replacements.push((it.visibility(), it.syntax().clone())),
+            ast::Item::Const(it) => {
+                replacements.push((it.visibility().clone(), it.syntax().clone()))
+            }
+            ast::Item::Enum(it) => {
+                replacements.push((it.visibility().clone(), it.syntax().clone()))
+            }
+            ast::Item::ExternCrate(it) => {
+                replacements.push((it.visibility().clone(), it.syntax().clone()))
+            }
+            ast::Item::Fn(it) => replacements.push((it.visibility().clone(), it.syntax().clone())),
             ast::Item::Impl(it) => impls.push(it),
-            ast::Item::MacroRules(it) => replacements.push((it.visibility(), it.syntax().clone())),
-            ast::Item::MacroDef(it) => replacements.push((it.visibility(), it.syntax().clone())),
-            ast::Item::Module(it) => replacements.push((it.visibility(), it.syntax().clone())),
-            ast::Item::Static(it) => replacements.push((it.visibility(), it.syntax().clone())),
+            ast::Item::MacroRules(it) => {
+                replacements.push((it.visibility().clone(), it.syntax().clone()))
+            }
+            ast::Item::MacroDef(it) => {
+                replacements.push((it.visibility().clone(), it.syntax().clone()))
+            }
+            ast::Item::Module(it) => {
+                replacements.push((it.visibility().clone(), it.syntax().clone()))
+            }
+            ast::Item::Static(it) => {
+                replacements.push((it.visibility().clone(), it.syntax().clone()))
+            }
             ast::Item::Struct(it) => {
-                replacements.push((it.visibility(), it.syntax().clone()));
-                record_field_parents.push((it.visibility(), it.syntax().clone()));
+                replacements.push((it.visibility().clone(), it.syntax().clone()));
+                record_field_parents.push((it.visibility().clone(), it.syntax().clone()));
+            }
+            ast::Item::Trait(it) => {
+                replacements.push((it.visibility().clone(), it.syntax().clone()))
+            }
+            ast::Item::TypeAlias(it) => {
+                replacements.push((it.visibility().clone(), it.syntax().clone()))
             }
-            ast::Item::Trait(it) => replacements.push((it.visibility(), it.syntax().clone())),
-            ast::Item::TypeAlias(it) => replacements.push((it.visibility(), it.syntax().clone())),
             ast::Item::Union(it) => {
-                replacements.push((it.visibility(), it.syntax().clone()));
-                record_field_parents.push((it.visibility(), it.syntax().clone()));
+                replacements.push((it.visibility().clone(), it.syntax().clone()));
+                record_field_parents.push((it.visibility().clone(), it.syntax().clone()));
             }
             _ => (),
         }
@@ -807,7 +825,7 @@ fn get_use_tree_paths_from_path(
     use_tree_str: &mut Vec<ast::Path>,
 ) -> Option<&mut Vec<ast::Path>> {
     path.syntax().ancestors().filter(|x| x.to_string() != path.to_string()).find_map(|x| {
-        if let Some(use_tree) = ast::UseTree::cast(x) {
+        if let Some(use_tree) = ast::UseTree::cast(x.clone()) {
             if let Some(upper_tree_path) = use_tree.path() {
                 if upper_tree_path.to_string() != path.to_string() {
                     use_tree_str.push(upper_tree_path.clone());
diff --git a/crates/ide_assists/src/handlers/generate_documentation_template.rs b/crates/ide_assists/src/handlers/generate_documentation_template.rs
index 1481eadb51..cfc7e9d043 100644
--- a/crates/ide_assists/src/handlers/generate_documentation_template.rs
+++ b/crates/ide_assists/src/handlers/generate_documentation_template.rs
@@ -59,8 +59,9 @@ pub(crate) fn generate_documentation_template(
         "Generate a documentation template",
         text_range,
         |builder| {
+            let mut doc_lines = Vec::new();
             // Introduction / short function description before the sections
-            let mut doc_lines = vec![introduction_builder(&ast_func, ctx)];
+            doc_lines.push(introduction_builder(&ast_func, ctx));
             // Then come the sections
             if let Some(mut lines) = examples_builder(&ast_func, ctx) {
                 doc_lines.push("".into());
@@ -302,7 +303,7 @@ fn arguments_from_params(param_list: &ast::ParamList) -> String {
         // instance `TuplePat`) could be managed later.
         Some(ast::Pat::IdentPat(ident_pat)) => match ident_pat.name() {
             Some(name) => match is_a_ref_mut_param(&param) {
-                true => format!("&mut {}", name),
+                true => format!("&mut {}", name.to_string()),
                 false => name.to_string(),
             },
             None => "_".to_string(),
diff --git a/crates/ide_assists/src/handlers/qualify_path.rs b/crates/ide_assists/src/handlers/qualify_path.rs
index ae29068bd7..23b642fcb1 100644
--- a/crates/ide_assists/src/handlers/qualify_path.rs
+++ b/crates/ide_assists/src/handlers/qualify_path.rs
@@ -121,7 +121,7 @@ impl QualifyCandidate<'_> {
             }
             QualifyCandidate::UnqualifiedName(generics) => {
                 let generics = generics.as_ref().map_or_else(String::new, ToString::to_string);
-                replacer(format!("{}{}", import, generics));
+                replacer(format!("{}{}", import.to_string(), generics));
             }
             QualifyCandidate::TraitAssocItem(qualifier, segment) => {
                 replacer(format!("<{} as {}>::{}", qualifier, import, segment));
diff --git a/crates/ide_assists/src/utils.rs b/crates/ide_assists/src/utils.rs
index 8a443ab089..90ec710c8e 100644
--- a/crates/ide_assists/src/utils.rs
+++ b/crates/ide_assists/src/utils.rs
@@ -431,7 +431,7 @@ fn generate_impl_text_inner(adt: &ast::Adt, trait_text: Option<&str>, code: &str
     buf.push_str("\n\n");
     adt.attrs()
         .filter(|attr| attr.as_simple_call().map(|(name, _arg)| name == "cfg").unwrap_or(false))
-        .for_each(|attr| buf.push_str(format!("{}\n", attr).as_str()));
+        .for_each(|attr| buf.push_str(format!("{}\n", attr.to_string()).as_str()));
     buf.push_str("impl");
     if let Some(generic_params) = &generic_params {
         let lifetimes = generic_params.lifetime_params().map(|lt| format!("{}", lt.syntax()));
diff --git a/crates/ide_completion/src/completions/attribute.rs b/crates/ide_completion/src/completions/attribute.rs
index 8222d3cbf4..d92e311915 100644
--- a/crates/ide_completion/src/completions/attribute.rs
+++ b/crates/ide_completion/src/completions/attribute.rs
@@ -2,8 +2,8 @@
 //!
 //! This module uses a bit of static metadata to provide completions
 //! for built-in attributes.
-//! Non-built-in attribute (excluding derives attributes) completions are done in [`super::unqualified_path`].
 
+use hir::HasAttrs;
 use ide_db::{
     helpers::{
         generated_lints::{CLIPPY_LINTS, DEFAULT_LINTS, FEATURES, RUSTDOC_LINTS},
@@ -93,6 +93,23 @@ fn complete_new_attribute(acc: &mut Completions, ctx: &CompletionContext, attrib
         None if is_inner => ATTRIBUTES.iter().for_each(add_completion),
         None => ATTRIBUTES.iter().filter(|compl| !compl.prefer_inner).for_each(add_completion),
     }
+
+    // FIXME: write a test for this when we can
+    ctx.scope.process_all_names(&mut |name, scope_def| {
+        if let hir::ScopeDef::MacroDef(mac) = scope_def {
+            if mac.kind() == hir::MacroKind::Attr {
+                let mut item = CompletionItem::new(
+                    SymbolKind::Attribute,
+                    ctx.source_range(),
+                    name.to_smol_str(),
+                );
+                if let Some(docs) = mac.docs(ctx.sema.db) {
+                    item.documentation(docs);
+                }
+                item.add_to(acc);
+            }
+        }
+    });
 }
 
 struct AttrCompletion {
diff --git a/crates/ide_completion/src/completions/keyword.rs b/crates/ide_completion/src/completions/keyword.rs
index 90e06221db..1c686dc0a6 100644
--- a/crates/ide_completion/src/completions/keyword.rs
+++ b/crates/ide_completion/src/completions/keyword.rs
@@ -229,6 +229,9 @@ fn foo(a: A) { a.$0 }
                 sn refm  &mut expr
                 sn match match expr {}
                 sn box   Box::new(expr)
+                sn ok    Ok(expr)
+                sn err   Err(expr)
+                sn some  Some(expr)
                 sn dbg   dbg!(expr)
                 sn dbgr  dbg!(&expr)
                 sn call  function(expr)
@@ -252,6 +255,9 @@ fn foo() {
                 sn refm  &mut expr
                 sn match match expr {}
                 sn box   Box::new(expr)
+                sn ok    Ok(expr)
+                sn err   Err(expr)
+                sn some  Some(expr)
                 sn dbg   dbg!(expr)
                 sn dbgr  dbg!(&expr)
                 sn call  function(expr)
diff --git a/crates/ide_completion/src/completions/postfix.rs b/crates/ide_completion/src/completions/postfix.rs
index 8a25da7ddb..c239401e48 100644
--- a/crates/ide_completion/src/completions/postfix.rs
+++ b/crates/ide_completion/src/completions/postfix.rs
@@ -163,6 +163,9 @@ pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
     }
 
     postfix_snippet("box", "Box::new(expr)", &format!("Box::new({})", receiver_text)).add_to(acc);
+    postfix_snippet("ok", "Ok(expr)", &format!("Ok({})", receiver_text)).add_to(acc);
+    postfix_snippet("err", "Err(expr)", &format!("Err({})", receiver_text)).add_to(acc);
+    postfix_snippet("some", "Some(expr)", &format!("Some({})", receiver_text)).add_to(acc);
     postfix_snippet("dbg", "dbg!(expr)", &format!("dbg!({})", receiver_text)).add_to(acc);
     postfix_snippet("dbgr", "dbg!(&expr)", &format!("dbg!(&{})", receiver_text)).add_to(acc);
     postfix_snippet("call", "function(expr)", &format!("${{1}}({})", receiver_text)).add_to(acc);
@@ -292,6 +295,9 @@ fn main() {
                 sn refm  &mut expr
                 sn match match expr {}
                 sn box   Box::new(expr)
+                sn ok    Ok(expr)
+                sn err   Err(expr)
+                sn some  Some(expr)
                 sn dbg   dbg!(expr)
                 sn dbgr  dbg!(&expr)
                 sn call  function(expr)
@@ -322,6 +328,9 @@ fn main() {
                 sn refm  &mut expr
                 sn match match expr {}
                 sn box   Box::new(expr)
+                sn ok    Ok(expr)
+                sn err   Err(expr)
+                sn some  Some(expr)
                 sn dbg   dbg!(expr)
                 sn dbgr  dbg!(&expr)
                 sn call  function(expr)
@@ -343,6 +352,9 @@ fn main() {
                 sn refm  &mut expr
                 sn match match expr {}
                 sn box   Box::new(expr)
+                sn ok    Ok(expr)
+                sn err   Err(expr)
+                sn some  Some(expr)
                 sn dbg   dbg!(expr)
                 sn dbgr  dbg!(&expr)
                 sn call  function(expr)
@@ -369,6 +381,9 @@ fn main() {
                 sn refm  &mut expr
                 sn match match expr {}
                 sn box   Box::new(expr)
+                sn ok    Ok(expr)
+                sn err   Err(expr)
+                sn some  Some(expr)
                 sn dbg   dbg!(expr)
                 sn dbgr  dbg!(&expr)
                 sn call  function(expr)
diff --git a/crates/ide_completion/src/render.rs b/crates/ide_completion/src/render.rs
index 4b4fbb3ed9..d4f35b5dcd 100644
--- a/crates/ide_completion/src/render.rs
+++ b/crates/ide_completion/src/render.rs
@@ -1437,6 +1437,9 @@ fn main() {
                 sn refm []
                 sn match []
                 sn box []
+                sn ok []
+                sn err []
+                sn some []
                 sn dbg []
                 sn dbgr []
                 sn call []
diff --git a/crates/ide_completion/src/snippet.rs b/crates/ide_completion/src/snippet.rs
index c5e2b009c7..1a3f42b839 100644
--- a/crates/ide_completion/src/snippet.rs
+++ b/crates/ide_completion/src/snippet.rs
@@ -52,56 +52,8 @@ use std::ops::Deref;
 // These placeholders take the form of `$number` or `${number:placeholder_text}` which can be traversed as tabstop in ascending order starting from 1,
 // with `$0` being a special case that always comes last.
 //
-// There is also a special placeholder, `${receiver}`, which will be replaced by the receiver expression for postfix snippets, or a `$0` tabstop in case of normal snippets.
-// This replacement for normal snippets allows you to reuse a snippet for both post- and prefix in a single definition.
-//
-// For the VSCode editor, rust-analyzer also ships with a small set of defaults which can be removed
-// by overwriting the settings object mentioned above, the defaults are:
-// [source,json]
-// ----
-// {
-//     "Arc::new": {
-//         "postfix": "arc",
-//         "body": "Arc::new(${receiver})",
-//         "requires": "std::sync::Arc",
-//         "description": "Put the expression into an `Arc`",
-//         "scope": "expr"
-//     },
-//     "Rc::new": {
-//         "postfix": "rc",
-//         "body": "Rc::new(${receiver})",
-//         "requires": "std::rc::Rc",
-//         "description": "Put the expression into an `Rc`",
-//         "scope": "expr"
-//     },
-//     "Box::pin": {
-//         "postfix": "pinbox",
-//         "body": "Box::pin(${receiver})",
-//         "requires": "std::boxed::Box",
-//         "description": "Put the expression into a pinned `Box`",
-//         "scope": "expr"
-//     },
-//     "Ok": {
-//         "postfix": "ok",
-//         "body": "Ok(${receiver})",
-//         "description": "Wrap the expression in a `Result::Ok`",
-//         "scope": "expr"
-//     },
-//     "Err": {
-//         "postfix": "err",
-//         "body": "Err(${receiver})",
-//         "description": "Wrap the expression in a `Result::Err`",
-//         "scope": "expr"
-//     },
-//     "Some": {
-//         "postfix": "some",
-//         "body": "Some(${receiver})",
-//         "description": "Wrap the expression in an `Option::Some`",
-//         "scope": "expr"
-//     }
-// }
-// ----
-
+// There is also a special placeholder, `${receiver}`, which will be replaced by the receiver expression for postfix snippets, or nothing in case of normal snippets.
+// It does not act as a tabstop.
 use ide_db::helpers::{import_assets::LocatedImport, insert_use::ImportScope};
 use itertools::Itertools;
 use syntax::{ast, AstNode, GreenNode, SyntaxNode};
@@ -165,7 +117,7 @@ impl Snippet {
     }
 
     pub fn snippet(&self) -> String {
-        self.snippet.replace("${receiver}", "$0")
+        self.snippet.replace("${receiver}", "")
     }
 
     pub fn postfix_snippet(&self, receiver: &str) -> String {
diff --git a/crates/ide_diagnostics/src/handlers/mismatched_arg_count.rs b/crates/ide_diagnostics/src/handlers/mismatched_arg_count.rs
index 78716fc099..95a3ac1d51 100644
--- a/crates/ide_diagnostics/src/handlers/mismatched_arg_count.rs
+++ b/crates/ide_diagnostics/src/handlers/mismatched_arg_count.rs
@@ -305,6 +305,38 @@ fn main() {
         fixed(0);
         varargs(1, 2, 3);
     }
+}
+            "#,
+        )
+    }
+
+    #[test]
+    fn legacy_const_generics() {
+        check_diagnostics(
+            r#"
+#[rustc_legacy_const_generics(1, 3)]
+fn mixed<const N1: &'static str, const N2: bool>(
+    a: u8,
+    b: i8,
+) {}
+
+fn f() {
+    mixed(0, "", -1, true);
+    mixed::<"", true>(0, -1);
+}
+
+#[rustc_legacy_const_generics(1, 3)]
+fn b<const N1: u8, const N2: u8>(
+    a: u8,
+    b: u8,
+) {}
+
+fn g() {
+    b(0, 1, 2, 3);
+    b::<1, 3>(0, 2);
+
+    b(0, 1, 2);
+           //^ error: expected 4 arguments, found 3
 }
             "#,
         )
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 867f2bd1f0..5826b6b114 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -116,48 +116,7 @@ config_data! {
         /// Whether to add parenthesis when completing functions.
         completion_addCallParenthesis: bool      = "true",
         /// Custom completion snippets.
-        // NOTE: Keep this list in sync with the feature docs of user snippets.
-        completion_snippets: FxHashMap<String, SnippetDef> = r#"{
-            "Arc::new": {
-                "postfix": "arc",
-                "body": "Arc::new(${receiver})",
-                "requires": "std::sync::Arc",
-                "description": "Put the expression into an `Arc`",
-                "scope": "expr"
-            },
-            "Rc::new": {
-                "postfix": "rc",
-                "body": "Rc::new(${receiver})",
-                "requires": "std::rc::Rc",
-                "description": "Put the expression into an `Rc`",
-                "scope": "expr"
-            },
-            "Box::pin": {
-                "postfix": "pinbox",
-                "body": "Box::pin(${receiver})",
-                "requires": "std::boxed::Box",
-                "description": "Put the expression into a pinned `Box`",
-                "scope": "expr"
-            },
-            "Ok": {
-                "postfix": "ok",
-                "body": "Ok(${receiver})",
-                "description": "Wrap the expression in a `Result::Ok`",
-                "scope": "expr"
-            },
-            "Err": {
-                "postfix": "err",
-                "body": "Err(${receiver})",
-                "description": "Wrap the expression in a `Result::Err`",
-                "scope": "expr"
-            },
-            "Some": {
-                "postfix": "some",
-                "body": "Some(${receiver})",
-                "description": "Wrap the expression in an `Option::Some`",
-                "scope": "expr"
-            }
-        }"#,
+        completion_snippets: FxHashMap<String, SnippetDef> = "{}",
         /// Whether to show postfix snippets like `dbg`, `if`, `not`, etc.
         completion_postfix_enable: bool          = "true",
         /// Toggles the additional completions that automatically add imports when completed.
diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc
index 494051b306..9f6f046014 100644
--- a/docs/user/generated_config.adoc
+++ b/docs/user/generated_config.adoc
@@ -141,47 +141,7 @@ Only applies when `#rust-analyzer.completion.addCallParenthesis#` is set.
 --
 Whether to add parenthesis when completing functions.
 --
-[[rust-analyzer.completion.snippets]]rust-analyzer.completion.snippets (default: `{
-            "Arc::new": {
-                "postfix": "arc",
-                "body": "Arc::new(${receiver})",
-                "requires": "std::sync::Arc",
-                "description": "Put the expression into an `Arc`",
-                "scope": "expr"
-            },
-            "Rc::new": {
-                "postfix": "rc",
-                "body": "Rc::new(${receiver})",
-                "requires": "std::rc::Rc",
-                "description": "Put the expression into an `Rc`",
-                "scope": "expr"
-            },
-            "Box::pin": {
-                "postfix": "pinbox",
-                "body": "Box::pin(${receiver})",
-                "requires": "std::boxed::Box",
-                "description": "Put the expression into a pinned `Box`",
-                "scope": "expr"
-            },
-            "Ok": {
-                "postfix": "ok",
-                "body": "Ok(${receiver})",
-                "description": "Wrap the expression in a `Result::Ok`",
-                "scope": "expr"
-            },
-            "Err": {
-                "postfix": "err",
-                "body": "Err(${receiver})",
-                "description": "Wrap the expression in a `Result::Err`",
-                "scope": "expr"
-            },
-            "Some": {
-                "postfix": "some",
-                "body": "Some(${receiver})",
-                "description": "Wrap the expression in an `Option::Some`",
-                "scope": "expr"
-            }
-        }`)::
+[[rust-analyzer.completion.snippets]]rust-analyzer.completion.snippets (default: `{}`)::
 +
 --
 Custom completion snippets.
diff --git a/docs/user/manual.adoc b/docs/user/manual.adoc
index 4fbe2379c3..1db6cb5cd4 100644
--- a/docs/user/manual.adoc
+++ b/docs/user/manual.adoc
@@ -445,7 +445,7 @@ If the date is more than a week ago, it's better to update rust-analyzer version
 
 The next thing to check would be panic messages in rust-analyzer's log.
 Log messages are printed to stderr, in VS Code you can see then in the `Output > Rust Analyzer Language Server` tab of the panel.
-To see more logs, set the `RA_LOG=info` environment variable.
+To see more logs, set `RA_LOG=info` environmental variable.
 
 To fully capture LSP messages between the editor and the server, set `"rust-analyzer.trace.server": "verbose"` config and check
 `Output > Rust Analyzer Language Server Trace`.
@@ -624,7 +624,7 @@ Relative paths are interpreted relative to `rust-project.json` file location or
 
 See https://github.com/rust-analyzer/rust-project.json-example for a small example.
 
-You can set the `RA_LOG` environment variable to `rust_analyzer=info` to inspect how rust-analyzer handles config and project loading.
+You can set `RA_LOG` environmental variable to `rust_analyzer=info` to inspect how rust-analyzer handles config and project loading.
 
 Note that calls to `cargo check` are disabled when using `rust-project.json` by default, so compilation errors and warnings will no longer be sent to your LSP client. To enable these compilation errors you will need to specify explicitly what command rust-analyzer should run to perform the checks using the `checkOnSave.overrideCommand` configuration. As an example, the following configuration explicitly sets `cargo check` as the `checkOnSave` command.
 
diff --git a/editors/code/package.json b/editors/code/package.json
index d70ac9aacb..2460308820 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -592,47 +592,7 @@
                 },
                 "rust-analyzer.completion.snippets": {
                     "markdownDescription": "Custom completion snippets.",
-                    "default": {
-                        "Arc::new": {
-                            "postfix": "arc",
-                            "body": "Arc::new(${receiver})",
-                            "requires": "std::sync::Arc",
-                            "description": "Put the expression into an `Arc`",
-                            "scope": "expr"
-                        },
-                        "Rc::new": {
-                            "postfix": "rc",
-                            "body": "Rc::new(${receiver})",
-                            "requires": "std::rc::Rc",
-                            "description": "Put the expression into an `Rc`",
-                            "scope": "expr"
-                        },
-                        "Box::pin": {
-                            "postfix": "pinbox",
-                            "body": "Box::pin(${receiver})",
-                            "requires": "std::boxed::Box",
-                            "description": "Put the expression into a pinned `Box`",
-                            "scope": "expr"
-                        },
-                        "Ok": {
-                            "postfix": "ok",
-                            "body": "Ok(${receiver})",
-                            "description": "Wrap the expression in a `Result::Ok`",
-                            "scope": "expr"
-                        },
-                        "Err": {
-                            "postfix": "err",
-                            "body": "Err(${receiver})",
-                            "description": "Wrap the expression in a `Result::Err`",
-                            "scope": "expr"
-                        },
-                        "Some": {
-                            "postfix": "some",
-                            "body": "Some(${receiver})",
-                            "description": "Wrap the expression in an `Option::Some`",
-                            "scope": "expr"
-                        }
-                    },
+                    "default": {},
                     "type": "object"
                 },
                 "rust-analyzer.completion.postfix.enable": {


Test patch:
diff --git a/crates/ide_completion/src/tests/attribute.rs b/crates/ide_completion/src/tests/attribute.rs
index c2bd26e2d8..c90d4966f3 100644
--- a/crates/ide_completion/src/tests/attribute.rs
+++ b/crates/ide_completion/src/tests/attribute.rs
@@ -283,11 +283,7 @@ fn attr_on_type_alias() {
 #[test]
 fn attr_on_struct() {
     check(
-        r#"
-//- minicore:derive
-#[$0]
-struct Foo;
-"#,
+        r#"#[$0] struct Foo;"#,
         expect![[r#"
             at allow(…)
             at cfg(…)
@@ -307,8 +303,6 @@ struct Foo;
             kw self
             kw super
             kw crate
-            md core
-            at derive           pub macro derive
         "#]],
     );
 }
diff --git a/crates/ide_completion/src/tests/proc_macros.rs b/crates/ide_completion/src/tests/proc_macros.rs
index f54286fe59..73fc293526 100644
--- a/crates/ide_completion/src/tests/proc_macros.rs
+++ b/crates/ide_completion/src/tests/proc_macros.rs
@@ -29,6 +29,9 @@ fn main() {
             sn refm  &mut expr
             sn match match expr {}
             sn box   Box::new(expr)
+            sn ok    Ok(expr)
+            sn err   Err(expr)
+            sn some  Some(expr)
             sn dbg   dbg!(expr)
             sn dbgr  dbg!(&expr)
             sn call  function(expr)
@@ -59,6 +62,9 @@ fn main() {
             sn refm  &mut expr
             sn match match expr {}
             sn box   Box::new(expr)
+            sn ok    Ok(expr)
+            sn err   Err(expr)
+            sn some  Some(expr)
             sn dbg   dbg!(expr)
             sn dbgr  dbg!(&expr)
             sn call  function(expr)
@@ -91,6 +97,9 @@ fn main() {}
             sn refm  &mut expr
             sn match match expr {}
             sn box   Box::new(expr)
+            sn ok    Ok(expr)
+            sn err   Err(expr)
+            sn some  Some(expr)
             sn dbg   dbg!(expr)
             sn dbgr  dbg!(&expr)
             sn call  function(expr)
@@ -123,6 +132,9 @@ fn main() {}
             sn refm  &mut expr
             sn match match expr {}
             sn box   Box::new(expr)
+            sn ok    Ok(expr)
+            sn err   Err(expr)
+            sn some  Some(expr)
             sn dbg   dbg!(expr)
             sn dbgr  dbg!(&expr)
             sn call  function(expr)
diff --git a/crates/test_utils/src/fixture.rs b/crates/test_utils/src/fixture.rs
index 8c806e7925..3d303a1237 100644
--- a/crates/test_utils/src/fixture.rs
+++ b/crates/test_utils/src/fixture.rs
@@ -147,7 +147,7 @@ impl Fixture {
                 if line.starts_with("// ")
                     && line.contains(':')
                     && !line.contains("::")
-                    && !line.contains('.')
+                    && !line.contains(".")
                     && line.chars().all(|it| !it.is_uppercase())
                 {
                     panic!("looks like invalid metadata line: {:?}", line);