Repository: rust-analyzer/rust-analyzer
Pull request #11193 (2022-01-04 19:40:48)

feat: Add config to replace specific proc-macros with dummy expanders
With this one can specify proc-macros from crates to expand into their input as a (temporary) workaround for the current completion problems with some of the bigger attribute proc-macros like `async_trait`.

This could've been done by just not expanding these macros, but that would require fiddling with nameres. I felt like this approach was simpler to pull off while also keeping the behaviour of the attributes/proc-macro in that they still expand instead of being dead syntax to us.

Fixes https://github.com/rust-analyzer/rust-analyzer/issues/11052

Usage(`async_trait` as example):
```jsonc
    "rust-analyzer.procMacro.dummies": {
        "async-trait": [ // crate name(as per its cargo.toml definition, not the dependency name)
            "async_trait" // exported proc-macro name
        ]
    },
```

Veykril: Well, that's odd, using the dummy expander doesn't fix the completion problems 🤔
Veykril: r? @matklad

Since you have a better view on the `project_model` crate.
matklad: Make sense!

I wonder if, as a follow up, we should allow to specify this in metadata section of cargo-toml? 
Veykril: > I wonder if, as a follow up, we should allow to specify this in metadata section of cargo-toml?

As in proc-macro crates can tell r-a to not have their macros expand(be dummy expanded)?

Patch:
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 9a1a893650..6306ae534d 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -1540,11 +1540,11 @@ impl Const {
         let infer = infer.as_ref();
         let result = eval_const(
             root,
-            &mut ConstEvalCtx {
+            ConstEvalCtx {
                 exprs: &body.exprs,
                 pats: &body.pats,
                 local_data: HashMap::default(),
-                infer: &mut |x| infer[x].clone(),
+                infer,
             },
         );
         result
diff --git a/crates/hir_def/src/attr.rs b/crates/hir_def/src/attr.rs
index b0be892c7d..ca87b501aa 100644
--- a/crates/hir_def/src/attr.rs
+++ b/crates/hir_def/src/attr.rs
@@ -737,7 +737,6 @@ impl Attr {
                 matches!(tt, tt::TokenTree::Leaf(tt::Leaf::Punct(Punct { char: ',', .. })))
             })
             .into_iter()
-            .filter(|(comma, _)| !*comma)
             .map(|(_, tts)| {
                 let segments = tts.filter_map(|tt| match tt {
                     tt::TokenTree::Leaf(tt::Leaf::Ident(id)) => Some(id.as_name()),
diff --git a/crates/hir_def/src/body.rs b/crates/hir_def/src/body.rs
index 505b07cc8a..2634c52a0b 100644
--- a/crates/hir_def/src/body.rs
+++ b/crates/hir_def/src/body.rs
@@ -12,8 +12,7 @@ use cfg::{CfgExpr, CfgOptions};
 use drop_bomb::DropBomb;
 use either::Either;
 use hir_expand::{
-    ast_id_map::AstIdMap, hygiene::Hygiene, AstId, ExpandError, ExpandResult, HirFileId, InFile,
-    MacroCallId, MacroDefId,
+    ast_id_map::AstIdMap, hygiene::Hygiene, AstId, ExpandResult, HirFileId, InFile, MacroDefId,
 };
 use la_arena::{Arena, ArenaMap};
 use limit::Limit;
@@ -125,23 +124,6 @@ impl Expander {
             }
         };
 
-        Ok(self.enter_expand_inner(db, call_id, err))
-    }
-
-    pub fn enter_expand_id<T: ast::AstNode>(
-        &mut self,
-        db: &dyn DefDatabase,
-        call_id: MacroCallId,
-    ) -> ExpandResult<Option<(Mark, T)>> {
-        self.enter_expand_inner(db, call_id, None)
-    }
-
-    fn enter_expand_inner<T: ast::AstNode>(
-        &mut self,
-        db: &dyn DefDatabase,
-        call_id: MacroCallId,
-        mut err: Option<ExpandError>,
-    ) -> ExpandResult<Option<(Mark, T)>> {
         if err.is_none() {
             err = db.macro_expand_error(call_id);
         }
@@ -156,9 +138,9 @@ impl Expander {
                     tracing::warn!("no error despite `parse_or_expand` failing");
                 }
 
-                return ExpandResult::only_err(err.unwrap_or_else(|| {
+                return Ok(ExpandResult::only_err(err.unwrap_or_else(|| {
                     mbe::ExpandError::Other("failed to parse macro invocation".into())
-                }));
+                })));
             }
         };
 
@@ -166,7 +148,7 @@ impl Expander {
             Some(it) => it,
             None => {
                 // This can happen without being an error, so only forward previous errors.
-                return ExpandResult { value: None, err };
+                return Ok(ExpandResult { value: None, err });
             }
         };
 
@@ -182,7 +164,7 @@ impl Expander {
         self.current_file_id = file_id;
         self.ast_id_map = db.ast_id_map(file_id);
 
-        ExpandResult { value: Some((mark, node)), err }
+        Ok(ExpandResult { value: Some((mark, node)), err })
     }
 
     pub fn exit(&mut self, db: &dyn DefDatabase, mut mark: Mark) {
diff --git a/crates/hir_def/src/child_by_source.rs b/crates/hir_def/src/child_by_source.rs
index 7a9e414ece..5ab236aa3d 100644
--- a/crates/hir_def/src/child_by_source.rs
+++ b/crates/hir_def/src/child_by_source.rs
@@ -30,7 +30,6 @@ pub trait ChildBySource {
 impl ChildBySource for TraitId {
     fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId) {
         let data = db.trait_data(*self);
-        // FIXME attribute macros
         for (_name, item) in data.items.iter() {
             match *item {
                 AssocItemId::FunctionId(func) => {
@@ -62,7 +61,6 @@ impl ChildBySource for TraitId {
 impl ChildBySource for ImplId {
     fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId) {
         let data = db.impl_data(*self);
-        // FIXME attribute macros
         for &item in data.items.iter() {
             match item {
                 AssocItemId::FunctionId(func) => {
diff --git a/crates/hir_def/src/data.rs b/crates/hir_def/src/data.rs
index 753084fb4b..aa2844461b 100644
--- a/crates/hir_def/src/data.rs
+++ b/crates/hir_def/src/data.rs
@@ -2,20 +2,19 @@
 
 use std::sync::Arc;
 
-use hir_expand::{name::Name, AstId, ExpandResult, InFile};
+use hir_expand::{name::Name, InFile};
 use syntax::ast;
 
 use crate::{
     attr::Attrs,
-    body::{Expander, Mark},
+    body::Expander,
     db::DefDatabase,
     intern::Interned,
     item_tree::{self, AssocItem, FnFlags, ItemTreeId, ModItem, Param},
-    nameres::attr_resolution::ResolvedAttr,
     type_ref::{TraitRef, TypeBound, TypeRef},
     visibility::RawVisibility,
-    AssocItemId, AstIdWithPath, ConstId, ConstLoc, FunctionId, FunctionLoc, HasModule, ImplId,
-    Intern, ItemContainerId, Lookup, ModuleId, StaticId, TraitId, TypeAliasId, TypeAliasLoc,
+    AssocItemId, ConstId, ConstLoc, FunctionId, FunctionLoc, HasModule, ImplId, Intern,
+    ItemContainerId, Lookup, ModuleId, StaticId, TraitId, TypeAliasId, TypeAliasLoc,
 };
 
 #[derive(Debug, Clone, PartialEq, Eq)]
@@ -349,29 +348,14 @@ fn collect_items(
     let item_tree = tree_id.item_tree(db);
     let crate_graph = db.crate_graph();
     let cfg_options = &crate_graph[module.krate].cfg_options;
-    let def_map = module.def_map(db);
 
     let mut items = Vec::new();
-    'items: for item in assoc_items {
+    for item in assoc_items {
         let attrs = item_tree.attrs(db, module.krate, ModItem::from(item).into());
         if !attrs.is_cfg_enabled(cfg_options) {
             continue;
         }
 
-        for attr in &*attrs {
-            let ast_id = AstIdWithPath {
-                path: (*attr.path).clone(),
-                ast_id: AstId::new(expander.current_file_id(), item.ast_id(&item_tree).upcast()),
-            };
-            if let Ok(ResolvedAttr::Macro(call_id)) =
-                def_map.resolve_attr_macro(db, module.local_id, ast_id, attr)
-            {
-                let res = expander.enter_expand_id(db, call_id);
-                items.extend(collect_macro_items(db, module, expander, container, limit, res));
-                continue 'items;
-            }
-        }
-
         match item {
             AssocItem::Function(id) => {
                 let item = &item_tree[id];
@@ -401,7 +385,24 @@ fn collect_items(
                 let res = expander.enter_expand(db, call);
 
                 if let Ok(res) = res {
-                    items.extend(collect_macro_items(db, module, expander, container, limit, res));
+                    if let Some((mark, mac)) = res.value {
+                        let src: InFile<ast::MacroItems> = expander.to_source(mac);
+                        let tree_id = item_tree::TreeId::new(src.file_id, None);
+                        let item_tree = tree_id.item_tree(db);
+                        let iter =
+                            item_tree.top_level_items().iter().filter_map(ModItem::as_assoc_item);
+                        items.extend(collect_items(
+                            db,
+                            module,
+                            expander,
+                            iter,
+                            tree_id,
+                            container,
+                            limit - 1,
+                        ));
+
+                        expander.exit(db, mark);
+                    }
                 }
             }
         }
@@ -409,26 +410,3 @@ fn collect_items(
 
     items
 }
-
-fn collect_macro_items(
-    db: &dyn DefDatabase,
-    module: ModuleId,
-    expander: &mut Expander,
-    container: ItemContainerId,
-    limit: usize,
-    res: ExpandResult<Option<(Mark, ast::MacroItems)>>,
-) -> Vec<(Name, AssocItemId)> {
-    if let Some((mark, mac)) = res.value {
-        let src: InFile<ast::MacroItems> = expander.to_source(mac);
-        let tree_id = item_tree::TreeId::new(src.file_id, None);
-        let item_tree = tree_id.item_tree(db);
-        let iter = item_tree.top_level_items().iter().filter_map(ModItem::as_assoc_item);
-        let items = collect_items(db, module, expander, iter, tree_id, container, limit - 1);
-
-        expander.exit(db, mark);
-
-        return items;
-    }
-
-    Vec::new()
-}
diff --git a/crates/hir_def/src/item_tree.rs b/crates/hir_def/src/item_tree.rs
index c51201f675..45b374f338 100644
--- a/crates/hir_def/src/item_tree.rs
+++ b/crates/hir_def/src/item_tree.rs
@@ -920,17 +920,6 @@ impl From<AssocItem> for ModItem {
     }
 }
 
-impl AssocItem {
-    pub fn ast_id(self, tree: &ItemTree) -> FileAstId<ast::AssocItem> {
-        match self {
-            AssocItem::Function(id) => tree[id].ast_id.upcast(),
-            AssocItem::TypeAlias(id) => tree[id].ast_id.upcast(),
-            AssocItem::Const(id) => tree[id].ast_id.upcast(),
-            AssocItem::MacroCall(id) => tree[id].ast_id.upcast(),
-        }
-    }
-}
-
 #[derive(Debug, Eq, PartialEq)]
 pub struct Variant {
     pub name: Name,
diff --git a/crates/hir_def/src/lib.rs b/crates/hir_def/src/lib.rs
index 730523252c..c8e60032df 100644
--- a/crates/hir_def/src/lib.rs
+++ b/crates/hir_def/src/lib.rs
@@ -781,10 +781,12 @@ fn attr_macro_as_call_id(
     macro_attr: &Attr,
     db: &dyn db::DefDatabase,
     krate: CrateId,
-    def: MacroDefId,
-) -> MacroCallId {
+    def: Option<MacroDefId>,
+) -> Result<MacroCallId, UnresolvedMacro> {
     let attr_path = &item_attr.path;
-    let last_segment = attr_path.segments().last().expect("empty attribute path");
+    let def = def.ok_or_else(|| UnresolvedMacro { path: attr_path.clone() })?;
+    let last_segment =
+        attr_path.segments().last().ok_or_else(|| UnresolvedMacro { path: attr_path.clone() })?;
     let mut arg = match macro_attr.input.as_deref() {
         Some(attr::AttrInput::TokenTree(tt, map)) => (tt.clone(), map.clone()),
         _ => Default::default(),
@@ -803,5 +805,5 @@ fn attr_macro_as_call_id(
             invoc_attr_index: macro_attr.id.ast_index,
         },
     );
-    res
+    Ok(res)
 }
diff --git a/crates/hir_def/src/nameres.rs b/crates/hir_def/src/nameres.rs
index f793f0e002..cf670e5cc1 100644
--- a/crates/hir_def/src/nameres.rs
+++ b/crates/hir_def/src/nameres.rs
@@ -47,7 +47,6 @@
 //! path and, upon success, we run macro expansion and "collect module" phase on
 //! the result
 
-pub mod attr_resolution;
 pub mod diagnostics;
 mod collector;
 mod mod_resolution;
@@ -65,7 +64,7 @@ use la_arena::Arena;
 use profile::Count;
 use rustc_hash::FxHashMap;
 use stdx::format_to;
-use syntax::{ast, SmolStr};
+use syntax::ast;
 
 use crate::{
     db::DefDatabase,
@@ -108,11 +107,6 @@ pub struct DefMap {
     /// (the primary purpose is to resolve derive helpers and fetch a proc-macros name)
     exported_proc_macros: FxHashMap<MacroDefId, ProcMacroDef>,
 
-    /// Custom attributes registered with `#![register_attr]`.
-    registered_attrs: Vec<SmolStr>,
-    /// Custom tool modules registered with `#![register_tool]`.
-    registered_tools: Vec<SmolStr>,
-
     edition: Edition,
     diagnostics: Vec<DefDiagnostic>,
 }
@@ -277,8 +271,6 @@ impl DefMap {
             prelude: None,
             root,
             modules,
-            registered_attrs: Vec::new(),
-            registered_tools: Vec::new(),
             diagnostics: Vec::new(),
         }
     }
@@ -451,8 +443,6 @@ impl DefMap {
             extern_prelude,
             diagnostics,
             modules,
-            registered_attrs,
-            registered_tools,
             block: _,
             edition: _,
             krate: _,
@@ -464,8 +454,6 @@ impl DefMap {
         exported_proc_macros.shrink_to_fit();
         diagnostics.shrink_to_fit();
         modules.shrink_to_fit();
-        registered_attrs.shrink_to_fit();
-        registered_tools.shrink_to_fit();
         for (_, module) in modules.iter_mut() {
             module.children.shrink_to_fit();
             module.scope.shrink_to_fit();
diff --git a/crates/hir_def/src/nameres/attr_resolution.rs b/crates/hir_def/src/nameres/attr_resolution.rs
deleted file mode 100644
index 4a7211b5c9..0000000000
--- a/crates/hir_def/src/nameres/attr_resolution.rs
+++ /dev/null
@@ -1,90 +0,0 @@
-//! Post-nameres attribute resolution.
-
-use hir_expand::MacroCallId;
-use syntax::{ast, SmolStr};
-
-use crate::{
-    attr::Attr,
-    attr_macro_as_call_id, builtin_attr,
-    db::DefDatabase,
-    item_scope::BuiltinShadowMode,
-    nameres::path_resolution::ResolveMode,
-    path::{ModPath, PathKind},
-    AstIdWithPath, LocalModuleId, UnresolvedMacro,
-};
-
-use super::DefMap;
-
-pub enum ResolvedAttr {
-    /// Attribute resolved to an attribute macro.
-    Macro(MacroCallId),
-    /// Attribute resolved to something else that does not require expansion.
-    Other,
-}
-
-impl DefMap {
-    pub(crate) fn resolve_attr_macro(
-        &self,
-        db: &dyn DefDatabase,
-        original_module: LocalModuleId,
-        ast_id: AstIdWithPath<ast::Item>,
-        attr: &Attr,
-    ) -> Result<ResolvedAttr, UnresolvedMacro> {
-        // NB: does not currently work for derive helpers as they aren't recorded in the `DefMap`
-
-        if self.is_builtin_or_registered_attr(&ast_id.path) {
-            return Ok(ResolvedAttr::Other);
-        }
-
-        let resolved_res = self.resolve_path_fp_with_macro(
-            db,
-            ResolveMode::Other,
-            original_module,
-            &ast_id.path,
-            BuiltinShadowMode::Module,
-        );
-        let def = match resolved_res.resolved_def.take_macros() {
-            Some(def) => {
-                if def.is_attribute() {
-                    def
-                } else {
-                    return Ok(ResolvedAttr::Other);
-                }
-            }
-            None => return Err(UnresolvedMacro { path: ast_id.path.clone() }),
-        };
-
-        Ok(ResolvedAttr::Macro(attr_macro_as_call_id(&ast_id, attr, db, self.krate, def)))
-    }
-
-    pub(crate) fn is_builtin_or_registered_attr(&self, path: &ModPath) -> bool {
-        if path.kind != PathKind::Plain {
-            return false;
-        }
-
-        let segments = path.segments();
-
-        if let Some(name) = segments.first() {
-            let name = name.to_smol_str();
-            let pred = |n: &_| *n == name;
-
-            let registered = self.registered_tools.iter().map(SmolStr::as_str);
-            let is_tool = builtin_attr::TOOL_MODULES.iter().copied().chain(registered).any(pred);
-            // FIXME: tool modules can be shadowed by actual modules
-            if is_tool {
-                return true;
-            }
-
-            if segments.len() == 1 {
-                let registered = self.registered_attrs.iter().map(SmolStr::as_str);
-                let is_inert = builtin_attr::INERT_ATTRIBUTES
-                    .iter()
-                    .map(|it| it.name)
-                    .chain(registered)
-                    .any(pred);
-                return is_inert;
-            }
-        }
-        false
-    }
-}
diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs
index e8246d4fb9..be749c3b46 100644
--- a/crates/hir_def/src/nameres/collector.rs
+++ b/crates/hir_def/src/nameres/collector.rs
@@ -20,11 +20,11 @@ use itertools::Itertools;
 use la_arena::Idx;
 use limit::Limit;
 use rustc_hash::{FxHashMap, FxHashSet};
-use syntax::ast;
+use syntax::{ast, SmolStr};
 
 use crate::{
     attr::{Attr, AttrId, AttrInput, Attrs},
-    attr_macro_as_call_id,
+    attr_macro_as_call_id, builtin_attr,
     db::DefDatabase,
     derive_macro_as_call_id,
     intern::Interned,
@@ -97,6 +97,8 @@ pub(super) fn collect_defs(db: &dyn DefDatabase, mut def_map: DefMap, tree_id: T
         from_glob_import: Default::default(),
         skip_attrs: Default::default(),
         derive_helpers_in_scope: Default::default(),
+        registered_attrs: Default::default(),
+        registered_tools: Default::default(),
     };
     if tree_id.is_block() {
         collector.seed_with_inner(tree_id);
@@ -249,6 +251,10 @@ struct DefCollector<'a> {
     /// Tracks which custom derives are in scope for an item, to allow resolution of derive helper
     /// attributes.
     derive_helpers_in_scope: FxHashMap<AstId<ast::Item>, Vec<Name>>,
+    /// Custom attributes registered with `#![register_attr]`.
+    registered_attrs: Vec<SmolStr>,
+    /// Custom tool modules registered with `#![register_tool]`.
+    registered_tools: Vec<SmolStr>,
 }
 
 impl DefCollector<'_> {
@@ -285,10 +291,10 @@ impl DefCollector<'_> {
                 };
 
                 if *attr_name == hir_expand::name![register_attr] {
-                    self.def_map.registered_attrs.push(registered_name.to_smol_str());
+                    self.registered_attrs.push(registered_name.to_smol_str());
                     cov_mark::hit!(register_attr);
                 } else {
-                    self.def_map.registered_tools.push(registered_name.to_smol_str());
+                    self.registered_tools.push(registered_name.to_smol_str());
                     cov_mark::hit!(register_tool);
                 }
             }
@@ -1118,13 +1124,10 @@ impl DefCollector<'_> {
                         }
                     }
 
-                    let def = match resolver(path.clone()) {
-                        Some(def) if def.is_attribute() => def,
-                        _ => return true,
-                    };
+                    let def = resolver(path.clone()).filter(MacroDefId::is_attribute);
                     if matches!(
                         def,
-                        MacroDefId {  kind:MacroDefKind::BuiltInAttr(expander, _),.. }
+                        Some(MacroDefId {  kind:MacroDefKind::BuiltInAttr(expander, _),.. })
                         if expander.is_derive()
                     ) {
                         // Resolved to `#[derive]`
@@ -1181,46 +1184,52 @@ impl DefCollector<'_> {
                         return true;
                     }
 
-                    // Not resolved to a derive helper or the derive attribute, so try to treat as a normal attribute.
-                    let call_id =
-                        attr_macro_as_call_id(file_ast_id, attr, self.db, self.def_map.krate, def);
-                    let loc: MacroCallLoc = self.db.lookup_intern_macro_call(call_id);
+                    // Not resolved to a derive helper or the derive attribute, so try to resolve as a normal attribute.
+                    match attr_macro_as_call_id(file_ast_id, attr, self.db, self.def_map.krate, def)
+                    {
+                        Ok(call_id) => {
+                            let loc: MacroCallLoc = self.db.lookup_intern_macro_call(call_id);
+
+                            // Skip #[test]/#[bench] expansion, which would merely result in more memory usage
+                            // due to duplicating functions into macro expansions
+                            if matches!(
+                                loc.def.kind,
+                                MacroDefKind::BuiltInAttr(expander, _)
+                                if expander.is_test() || expander.is_bench()
+                            ) {
+                                return recollect_without(self);
+                            }
 
-                    // Skip #[test]/#[bench] expansion, which would merely result in more memory usage
-                    // due to duplicating functions into macro expansions
-                    if matches!(
-                        loc.def.kind,
-                        MacroDefKind::BuiltInAttr(expander, _)
-                        if expander.is_test() || expander.is_bench()
-                    ) {
-                        return recollect_without(self);
-                    }
+                            if let MacroDefKind::ProcMacro(exp, ..) = loc.def.kind {
+                                if exp.is_dummy() {
+                                    // Proc macros that cannot be expanded are treated as not
+                                    // resolved, in order to fall back later.
+                                    self.def_map.diagnostics.push(
+                                        DefDiagnostic::unresolved_proc_macro(
+                                            directive.module_id,
+                                            loc.kind,
+                                        ),
+                                    );
+
+                                    return recollect_without(self);
+                                }
+                            }
+
+                            self.def_map.modules[directive.module_id]
+                                .scope
+                                .add_attr_macro_invoc(ast_id, call_id);
 
-                    if let MacroDefKind::ProcMacro(exp, ..) = loc.def.kind {
-                        if exp.is_dummy() {
-                            // Proc macros that cannot be expanded are treated as not
-                            // resolved, in order to fall back later.
-                            self.def_map.diagnostics.push(DefDiagnostic::unresolved_proc_macro(
+                            resolved.push((
                                 directive.module_id,
-                                loc.kind,
+                                call_id,
+                                directive.depth,
+                                directive.container,
                             ));
-
-                            return recollect_without(self);
+                            res = ReachedFixedPoint::No;
+                            return false;
                         }
+                        Err(UnresolvedMacro { .. }) => (),
                     }
-
-                    self.def_map.modules[directive.module_id]
-                        .scope
-                        .add_attr_macro_invoc(ast_id, call_id);
-
-                    resolved.push((
-                        directive.module_id,
-                        call_id,
-                        directive.depth,
-                        directive.container,
-                    ));
-                    res = ReachedFixedPoint::No;
-                    return false;
                 }
             }
 
@@ -1785,7 +1794,7 @@ impl ModCollector<'_, '_> {
             });
 
         for attr in iter {
-            if self.def_collector.def_map.is_builtin_or_registered_attr(&attr.path) {
+            if self.is_builtin_or_registered_attr(&attr.path) {
                 continue;
             }
             tracing::debug!("non-builtin attribute {}", attr.path);
@@ -1813,6 +1822,37 @@ impl ModCollector<'_, '_> {
         Ok(())
     }
 
+    fn is_builtin_or_registered_attr(&self, path: &ModPath) -> bool {
+        if path.kind != PathKind::Plain {
+            return false;
+        }
+
+        let segments = path.segments();
+
+        if let Some(name) = segments.first() {
+            let name = name.to_smol_str();
+            let pred = |n: &_| *n == name;
+
+            let registered = self.def_collector.registered_tools.iter().map(SmolStr::as_str);
+            let is_tool = builtin_attr::TOOL_MODULES.iter().copied().chain(registered).any(pred);
+            // FIXME: tool modules can be shadowed by actual modules
+            if is_tool {
+                return true;
+            }
+
+            if segments.len() == 1 {
+                let registered = self.def_collector.registered_attrs.iter().map(SmolStr::as_str);
+                let is_inert = builtin_attr::INERT_ATTRIBUTES
+                    .iter()
+                    .map(|it| it.name)
+                    .chain(registered)
+                    .any(pred);
+                return is_inert;
+            }
+        }
+        false
+    }
+
     /// If `attrs` registers a procedural macro, collects its definition.
     fn collect_proc_macro_def(&mut self, func_name: &Name, ast_id: AstId<ast::Fn>, attrs: &Attrs) {
         // FIXME: this should only be done in the root module of `proc-macro` crates, not everywhere
@@ -2064,6 +2104,8 @@ mod tests {
             from_glob_import: Default::default(),
             skip_attrs: Default::default(),
             derive_helpers_in_scope: Default::default(),
+            registered_attrs: Default::default(),
+            registered_tools: Default::default(),
         };
         collector.seed_with_top_level();
         collector.collect();
diff --git a/crates/hir_ty/src/consteval.rs b/crates/hir_ty/src/consteval.rs
index 68e6e0582e..0005a86b7f 100644
--- a/crates/hir_ty/src/consteval.rs
+++ b/crates/hir_ty/src/consteval.rs
@@ -4,13 +4,14 @@ use std::{collections::HashMap, convert::TryInto, fmt::Display};
 
 use chalk_ir::{IntTy, Scalar};
 use hir_def::{
+    builtin_type::BuiltinUint,
     expr::{ArithOp, BinaryOp, Expr, Literal, Pat},
     type_ref::ConstScalar,
 };
 use hir_expand::name::Name;
-use la_arena::{Arena, Idx};
+use la_arena::Arena;
 
-use crate::{Const, ConstData, ConstValue, Interner, Ty, TyKind};
+use crate::{Const, ConstData, ConstValue, InferenceResult, Interner, TyKind};
 
 /// Extension trait for [`Const`]
 pub trait ConstExt {
@@ -40,11 +41,12 @@ impl ConstExt for Const {
     }
 }
 
+#[derive(Clone)]
 pub struct ConstEvalCtx<'a> {
     pub exprs: &'a Arena<Expr>,
     pub pats: &'a Arena<Pat>,
     pub local_data: HashMap<Name, ComputedExpr>,
-    pub infer: &'a mut dyn FnMut(Idx<Expr>) -> Ty,
+    pub infer: &'a InferenceResult,
 }
 
 #[derive(Debug, Clone)]
@@ -55,7 +57,7 @@ pub enum ConstEvalError {
     Panic(String),
 }
 
-#[derive(Debug, Clone)]
+#[derive(Clone)]
 pub enum ComputedExpr {
     Literal(Literal),
     Tuple(Box<[ComputedExpr]>),
@@ -128,11 +130,11 @@ fn is_valid(scalar: &Scalar, value: i128) -> bool {
     }
 }
 
-pub fn eval_const(expr: &Expr, ctx: &mut ConstEvalCtx<'_>) -> Result<ComputedExpr, ConstEvalError> {
+pub fn eval_const(expr: &Expr, mut ctx: ConstEvalCtx<'_>) -> Result<ComputedExpr, ConstEvalError> {
     match expr {
         Expr::Literal(l) => Ok(ComputedExpr::Literal(l.clone())),
         &Expr::UnaryOp { expr, op } => {
-            let ty = &(ctx.infer)(expr);
+            let ty = &ctx.infer[expr];
             let ev = eval_const(&ctx.exprs[expr], ctx)?;
             match op {
                 hir_def::expr::UnaryOp::Deref => Err(ConstEvalError::NotSupported("deref")),
@@ -188,9 +190,9 @@ pub fn eval_const(expr: &Expr, ctx: &mut ConstEvalCtx<'_>) -> Result<ComputedExp
             }
         }
         &Expr::BinaryOp { lhs, rhs, op } => {
-            let ty = &(ctx.infer)(lhs);
-            let lhs = eval_const(&ctx.exprs[lhs], ctx)?;
-            let rhs = eval_const(&ctx.exprs[rhs], ctx)?;
+            let ty = &ctx.infer[lhs];
+            let lhs = eval_const(&ctx.exprs[lhs], ctx.clone())?;
+            let rhs = eval_const(&ctx.exprs[rhs], ctx.clone())?;
             let op = op.ok_or(ConstEvalError::IncompleteExpr)?;
             let v1 = match lhs {
                 ComputedExpr::Literal(Literal::Int(v, _)) => v,
@@ -239,7 +241,6 @@ pub fn eval_const(expr: &Expr, ctx: &mut ConstEvalCtx<'_>) -> Result<ComputedExp
             }
         }
         Expr::Block { statements, tail, .. } => {
-            let mut prev_values = HashMap::<Name, Option<ComputedExpr>>::default();
             for statement in &**statements {
                 match statement {
                     &hir_def::expr::Statement::Let { pat, initializer, .. } => {
@@ -251,33 +252,21 @@ pub fn eval_const(expr: &Expr, ctx: &mut ConstEvalCtx<'_>) -> Result<ComputedExp
                             }
                         };
                         let value = match initializer {
-                            Some(x) => eval_const(&ctx.exprs[x], ctx)?,
+                            Some(x) => eval_const(&ctx.exprs[x], ctx.clone())?,
                             None => continue,
                         };
-                        if !prev_values.contains_key(&name) {
-                            let prev = ctx.local_data.insert(name.clone(), value);
-                            prev_values.insert(name, prev);
-                        } else {
-                            ctx.local_data.insert(name, value);
-                        }
+                        ctx.local_data.insert(name, value);
                     }
                     &hir_def::expr::Statement::Expr { .. } => {
                         return Err(ConstEvalError::NotSupported("this kind of statement"))
                     }
                 }
             }
-            let r = match tail {
-                &Some(x) => eval_const(&ctx.exprs[x], ctx),
-                None => Ok(ComputedExpr::Tuple(Box::new([]))),
+            let tail_expr = match tail {
+                &Some(x) => &ctx.exprs[x],
+                None => return Ok(ComputedExpr::Tuple(Box::new([]))),
             };
-            // clean up local data, so caller will receive the exact map that passed to us
-            for (name, val) in prev_values {
-                match val {
-                    Some(x) => ctx.local_data.insert(name, x),
-                    None => ctx.local_data.remove(&name),
-                };
-            }
-            r
+            eval_const(tail_expr, ctx)
         }
         Expr::Path(p) => {
             let name = p.mod_path().as_ident().ok_or(ConstEvalError::NotSupported("big paths"))?;
@@ -291,16 +280,12 @@ pub fn eval_const(expr: &Expr, ctx: &mut ConstEvalCtx<'_>) -> Result<ComputedExp
     }
 }
 
-pub fn eval_usize(expr: Idx<Expr>, mut ctx: ConstEvalCtx<'_>) -> Option<u64> {
-    let expr = &ctx.exprs[expr];
-    if let Ok(ce) = eval_const(&expr, &mut ctx) {
-        match ce {
-            ComputedExpr::Literal(Literal::Int(x, _)) => return x.try_into().ok(),
-            ComputedExpr::Literal(Literal::Uint(x, _)) => return x.try_into().ok(),
-            _ => {}
-        }
+// FIXME: support more than just evaluating literals
+pub fn eval_usize(expr: &Expr) -> Option<u64> {
+    match expr {
+        Expr::Literal(Literal::Uint(v, None | Some(BuiltinUint::Usize))) => (*v).try_into().ok(),
+        _ => None,
     }
-    None
 }
 
 /// Interns a possibly-unknown target usize
diff --git a/crates/hir_ty/src/infer/expr.rs b/crates/hir_ty/src/infer/expr.rs
index 54b1680214..a892a680d7 100644
--- a/crates/hir_ty/src/infer/expr.rs
+++ b/crates/hir_ty/src/infer/expr.rs
@@ -799,15 +799,8 @@ impl<'a> InferenceContext<'a> {
                             ),
                         );
 
-                        consteval::eval_usize(
-                            repeat,
-                            consteval::ConstEvalCtx {
-                                exprs: &body.exprs,
-                                pats: &body.pats,
-                                local_data: Default::default(),
-                                infer: &mut |x| self.infer_expr(x, &expected),
-                            },
-                        )
+                        let repeat_expr = &self.body.exprs[repeat];
+                        consteval::eval_usize(repeat_expr)
                     }
                 };
 
diff --git a/crates/hir_ty/src/lib.rs b/crates/hir_ty/src/lib.rs
index f1280fcc10..1950820a37 100644
--- a/crates/hir_ty/src/lib.rs
+++ b/crates/hir_ty/src/lib.rs
@@ -358,7 +358,7 @@ where
     struct ErrorReplacer {
         vars: usize,
     }
-    impl Folder<Interner> for ErrorReplacer {
+    impl<'i> Folder<Interner> for ErrorReplacer {
         type Error = NoSolution;
 
         fn as_dyn(&mut self) -> &mut dyn Folder<Interner, Error = Self::Error> {
diff --git a/crates/ide/src/expand_macro.rs b/crates/ide/src/expand_macro.rs
index 69d2454cf7..949744c01b 100644
--- a/crates/ide/src/expand_macro.rs
+++ b/crates/ide/src/expand_macro.rs
@@ -319,7 +319,7 @@ struct Foo {}
 "#,
             expect![[r#"
                 Clone
-                impl < >core::clone::Clone for Foo< >{}
+                impl< >core::clone::Clone for Foo< >{}
 
             "#]],
         );
@@ -337,7 +337,7 @@ struct Foo {}
 "#,
             expect![[r#"
                 Copy
-                impl < >core::marker::Copy for Foo< >{}
+                impl< >core::marker::Copy for Foo< >{}
 
             "#]],
         );
@@ -354,9 +354,9 @@ struct Foo {}
 "#,
             expect![[r#"
                 Copy, Clone
-                impl < >core::marker::Copy for Foo< >{}
+                impl< >core::marker::Copy for Foo< >{}
 
-                impl < >core::clone::Clone for Foo< >{}
+                impl< >core::clone::Clone for Foo< >{}
 
             "#]],
         );
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs
index 29fc1fd2e0..4d638e687a 100644
--- a/crates/ide/src/goto_definition.rs
+++ b/crates/ide/src/goto_definition.rs
@@ -4,7 +4,7 @@ use crate::{doc_links::token_as_doc_comment, FilePosition, NavigationTarget, Ran
 use hir::{AsAssocItem, Semantics};
 use ide_db::{
     base_db::{AnchoredPath, FileId, FileLoader},
-    defs::{Definition, IdentClass},
+    defs::Definition,
     helpers::pick_best_token,
     RootDatabase,
 };
@@ -46,20 +46,20 @@ pub(crate) fn goto_definition(
         .filter_map(|token| {
             let parent = token.parent()?;
             if let Some(tt) = ast::TokenTree::cast(parent) {
-                if let Some(x) = try_lookup_include_path(sema, tt, token.clone(), position.file_id)
+                if let x @ Some(_) =
+                    try_lookup_include_path(sema, tt, token.clone(), position.file_id)
                 {
-                    return Some(vec![x]);
+                    return x;
                 }
             }
             Some(
-                IdentClass::classify_token(sema, &token)?
-                    .definitions()
+                Definition::from_token(sema, &token)
                     .into_iter()
                     .flat_map(|def| {
                         try_find_trait_item_definition(sema.db, &def)
                             .unwrap_or_else(|| def_to_nav(sema.db, def))
                     })
-                    .collect(),
+                    .collect::<Vec<_>>(),
             )
         })
         .flatten()
@@ -74,7 +74,7 @@ fn try_lookup_include_path(
     tt: ast::TokenTree,
     token: SyntaxToken,
     file_id: FileId,
-) -> Option<NavigationTarget> {
+) -> Option<Vec<NavigationTarget>> {
     let token = ast::String::cast(token)?;
     let path = token.value()?.into_owned();
     let macro_call = tt.syntax().parent().and_then(ast::MacroCall::cast)?;
@@ -84,7 +84,7 @@ fn try_lookup_include_path(
     }
     let file_id = sema.db.resolve_path(AnchoredPath { anchor: file_id, path: &path })?;
     let size = sema.db.file_text(file_id).len().try_into().ok()?;
-    Some(NavigationTarget {
+    Some(vec![NavigationTarget {
         file_id,
         full_range: TextRange::new(0.into(), size),
         name: path.into(),
@@ -93,7 +93,7 @@ fn try_lookup_include_path(
         container_name: None,
         description: None,
         docs: None,
-    })
+    }])
 }
 
 /// finds the trait definition of an impl'd item
diff --git a/crates/ide/src/highlight_related.rs b/crates/ide/src/highlight_related.rs
index b6d9e4021d..054b8f297b 100644
--- a/crates/ide/src/highlight_related.rs
+++ b/crates/ide/src/highlight_related.rs
@@ -1,7 +1,7 @@
 use hir::Semantics;
 use ide_db::{
     base_db::{FileId, FilePosition},
-    defs::{Definition, IdentClass},
+    defs::Definition,
     helpers::{for_each_break_expr, for_each_tail_expr, node_ext::walk_expr, pick_best_token},
     search::{FileReference, ReferenceCategory, SearchScope},
     RootDatabase,
@@ -293,8 +293,7 @@ fn cover_range(r0: Option<TextRange>, r1: Option<TextRange>) -> Option<TextRange
 fn find_defs(sema: &Semantics<RootDatabase>, token: SyntaxToken) -> FxHashSet<Definition> {
     sema.descend_into_macros(token)
         .into_iter()
-        .filter_map(|token| IdentClass::classify_token(sema, &token).map(IdentClass::definitions))
-        .flatten()
+        .flat_map(|token| Definition::from_token(sema, &token))
         .collect()
 }
 
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index 21bea25a5f..9d19d7c1c0 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -9,7 +9,7 @@ use either::Either;
 use hir::{HasSource, Semantics};
 use ide_db::{
     base_db::FileRange,
-    defs::{Definition, IdentClass},
+    defs::Definition,
     helpers::{pick_best_token, FamousDefs},
     FxIndexSet, RootDatabase,
 };
@@ -129,8 +129,8 @@ pub(crate) fn hover(
         .iter()
         .filter_map(|token| {
             let node = token.parent()?;
-            let class = IdentClass::classify_token(sema, token)?;
-            Some(class.definitions().into_iter().zip(iter::once(node).cycle()))
+            let defs = Definition::from_token(sema, token);
+            Some(defs.into_iter().zip(iter::once(node).cycle()))
         })
         .flatten()
         .unique_by(|&(def, _)| def)
diff --git a/crates/ide/src/moniker.rs b/crates/ide/src/moniker.rs
index 53f6d7ec7a..c93c471f49 100644
--- a/crates/ide/src/moniker.rs
+++ b/crates/ide/src/moniker.rs
@@ -4,7 +4,7 @@
 use hir::{db::DefDatabase, AsAssocItem, AssocItemContainer, Crate, Name, Semantics};
 use ide_db::{
     base_db::{CrateOrigin, FileId, FileLoader, FilePosition},
-    defs::{Definition, IdentClass},
+    defs::Definition,
     helpers::pick_best_token,
     RootDatabase,
 };
@@ -82,10 +82,11 @@ pub(crate) fn moniker(
     let navs = sema
         .descend_into_macros(original_token.clone())
         .into_iter()
-        .filter_map(|token| {
-            IdentClass::classify_token(sema, &token).map(IdentClass::definitions).map(|it| {
-                it.into_iter().flat_map(|def| def_to_moniker(sema.db, def, current_crate))
-            })
+        .map(|token| {
+            Definition::from_token(sema, &token)
+                .into_iter()
+                .flat_map(|def| def_to_moniker(sema.db, def, current_crate))
+                .collect::<Vec<_>>()
         })
         .flatten()
         .unique()
diff --git a/crates/ide/src/static_index.rs b/crates/ide/src/static_index.rs
index d5bfbd1894..77d202cc39 100644
--- a/crates/ide/src/static_index.rs
+++ b/crates/ide/src/static_index.rs
@@ -6,7 +6,7 @@ use std::collections::HashMap;
 use hir::{db::HirDatabase, Crate, Module, Semantics};
 use ide_db::{
     base_db::{FileId, FileRange, SourceDatabaseExt},
-    defs::{Definition, IdentClass},
+    defs::Definition,
     RootDatabase,
 };
 use rustc_hash::FxHashSet;
@@ -195,9 +195,9 @@ impl StaticIndex<'_> {
 
 fn get_definition(sema: &Semantics<RootDatabase>, token: SyntaxToken) -> Option<Definition> {
     for token in sema.descend_into_macros(token) {
-        let def = IdentClass::classify_token(sema, &token).map(IdentClass::definitions);
-        if let Some(&[x]) = def.as_deref() {
-            return Some(x);
+        let def = Definition::from_token(sema, &token);
+        if let [x] = def.as_slice() {
+            return Some(*x);
         } else {
             continue;
         };
diff --git a/crates/ide_assists/src/handlers/add_explicit_type.rs b/crates/ide_assists/src/handlers/add_explicit_type.rs
index 945edfd999..d7e1be900f 100644
--- a/crates/ide_assists/src/handlers/add_explicit_type.rs
+++ b/crates/ide_assists/src/handlers/add_explicit_type.rs
@@ -208,10 +208,8 @@ fn main() {
         check_assist_not_applicable(
             add_explicit_type,
             r#"
-//- minicore: option
-
 fn main() {
-    let $0l = [0.0; Some(2).unwrap()];
+    let $0l = [0.0; 2+2];
 }
 "#,
         );
diff --git a/crates/ide_assists/src/handlers/add_missing_impl_members.rs b/crates/ide_assists/src/handlers/add_missing_impl_members.rs
index b6af72cbac..a10eca10d1 100644
--- a/crates/ide_assists/src/handlers/add_missing_impl_members.rs
+++ b/crates/ide_assists/src/handlers/add_missing_impl_members.rs
@@ -938,47 +938,6 @@ struct Foo(usize);
 impl FooB for Foo {
     $0fn foo< 'lt>(& 'lt self){}
 
-}
-"#,
-        )
-    }
-
-    #[test]
-    fn macro_trait_dyn_absolute_path() {
-        // https://github.com/rust-analyzer/rust-analyzer/issues/11100
-        check_assist(
-            add_missing_impl_members,
-            r#"
-macro_rules! foo {
-    () => {
-        trait MacroTrait {
-            fn trait_method(_: &dyn ::core::marker::Sized);
-        }
-    }
-}
-foo!();
-struct Foo;
-
-impl MacroTrait for Foo {
-    $0
-}
-"#,
-            r#"
-macro_rules! foo {
-    () => {
-        trait MacroTrait {
-            fn trait_method(_: &dyn ::core::marker::Sized);
-        }
-    }
-}
-foo!();
-struct Foo;
-
-impl MacroTrait for Foo {
-    fn trait_method(_: &dyn ::core::marker::Sized) {
-        ${0:todo!()}
-    }
-
 }
 "#,
         )
diff --git a/crates/ide_assists/src/handlers/apply_demorgan.rs b/crates/ide_assists/src/handlers/apply_demorgan.rs
index 21907ab41f..b3fcf6578a 100644
--- a/crates/ide_assists/src/handlers/apply_demorgan.rs
+++ b/crates/ide_assists/src/handlers/apply_demorgan.rs
@@ -42,11 +42,10 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<(
 
     // Walk up the tree while we have the same binary operator
     while let Some(parent_expr) = expr.syntax().parent().and_then(ast::BinExpr::cast) {
-        match expr.op_kind() {
-            Some(parent_op) if parent_op == op => {
-                expr = parent_expr;
+        if let Some(parent_op) = expr.op_kind() {
+            if parent_op == op {
+                expr = parent_expr
             }
-            _ => break,
         }
     }
 
@@ -221,14 +220,4 @@ fn f() { !(S <= S || S < S) }
         cov_mark::check!(demorgan_double_parens);
         check_assist(apply_demorgan, "fn f() { (x ||$0 x) }", "fn f() { !(!x && !x) }")
     }
-
-    // https://github.com/rust-analyzer/rust-analyzer/issues/10963
-    #[test]
-    fn demorgan_doesnt_hang() {
-        check_assist(
-            apply_demorgan,
-            "fn f() { 1 || 3 &&$0 4 || 5 }",
-            "fn f() { !(!1 || !3 || !4) || 5 }",
-        )
-    }
 }
diff --git a/crates/ide_assists/src/handlers/extract_variable.rs b/crates/ide_assists/src/handlers/extract_variable.rs
index aaed2b67fe..7a57ab3b9b 100644
--- a/crates/ide_assists/src/handlers/extract_variable.rs
+++ b/crates/ide_assists/src/handlers/extract_variable.rs
@@ -52,12 +52,6 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext) -> Option
         }
     }
 
-    let reference_modifier = match get_receiver_type(&ctx, &to_extract) {
-        Some(receiver_type) if receiver_type.is_mutable_reference() => "&mut ",
-        Some(receiver_type) if receiver_type.is_reference() => "&",
-        _ => "",
-    };
-
     let anchor = Anchor::from(&to_extract)?;
     let indent = anchor.syntax().prev_sibling_or_token()?.as_token()?.clone();
     let target = to_extract.syntax().text_range();
@@ -85,11 +79,9 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext) -> Option
 
             match anchor {
                 Anchor::Before(_) | Anchor::Replace(_) => {
-                    format_to!(buf, "let {} = {}", var_name, reference_modifier)
-                }
-                Anchor::WrapInBlock(_) => {
-                    format_to!(buf, "{{ let {} = {}", var_name, reference_modifier)
+                    format_to!(buf, "let {} = ", var_name)
                 }
+                Anchor::WrapInBlock(_) => format_to!(buf, "{{ let {} = ", var_name),
             };
             format_to!(buf, "{}", to_extract.syntax());
 
@@ -154,22 +146,6 @@ fn valid_target_expr(node: SyntaxNode) -> Option<ast::Expr> {
     }
 }
 
-fn get_receiver_type(ctx: &AssistContext, expression: &ast::Expr) -> Option<hir::Type> {
-    let receiver = get_receiver(expression.clone())?;
-    Some(ctx.sema.type_of_expr(&receiver)?.original())
-}
-
-/// In the expression `a.b.c.x()`, find `a`
-fn get_receiver(expression: ast::Expr) -> Option<ast::Expr> {
-    match expression {
-        ast::Expr::FieldExpr(field) if field.expr().is_some() => {
-            let nested_expression = &field.expr()?;
-            get_receiver(nested_expression.to_owned())
-        }
-        _ => Some(expression),
-    }
-}
-
 #[derive(Debug)]
 enum Anchor {
     Before(SyntaxNode),
@@ -924,330 +900,4 @@ const X: usize = $0100$0;
 ",
         );
     }
-
-    #[test]
-    fn test_extract_var_mutable_reference_parameter() {
-        check_assist(
-            extract_variable,
-            r#"
-struct S {
-    vec: Vec<u8>
-}
-
-fn foo(s: &mut S) {
-    $0s.vec$0.push(0);
-}"#,
-            r#"
-struct S {
-    vec: Vec<u8>
-}
-
-fn foo(s: &mut S) {
-    let $0var_name = &mut s.vec;
-    var_name.push(0);
-}"#,
-        );
-    }
-
-    #[test]
-    fn test_extract_var_mutable_reference_parameter_deep_nesting() {
-        check_assist(
-            extract_variable,
-            r#"
-struct Y {
-    field: X
-}
-struct X {
-    field: S
-}
-struct S {
-    vec: Vec<u8>
-}
-
-fn foo(f: &mut Y) {
-    $0f.field.field.vec$0.push(0);
-}"#,
-            r#"
-struct Y {
-    field: X
-}
-struct X {
-    field: S
-}
-struct S {
-    vec: Vec<u8>
-}
-
-fn foo(f: &mut Y) {
-    let $0var_name = &mut f.field.field.vec;
-    var_name.push(0);
-}"#,
-        );
-    }
-
-    #[test]
-    fn test_extract_var_reference_parameter() {
-        check_assist(
-            extract_variable,
-            r#"
-struct X;
-
-impl X {
-    fn do_thing(&self) {
-
-    }
-}
-
-struct S {
-    sub: X
-}
-
-fn foo(s: &S) {
-    $0s.sub$0.do_thing();
-}"#,
-            r#"
-struct X;
-
-impl X {
-    fn do_thing(&self) {
-
-    }
-}
-
-struct S {
-    sub: X
-}
-
-fn foo(s: &S) {
-    let $0x = &s.sub;
-    x.do_thing();
-}"#,
-        );
-    }
-
-    #[test]
-    fn test_extract_var_reference_parameter_deep_nesting() {
-        check_assist(
-            extract_variable,
-            r#"
-struct Z;
-impl Z {
-    fn do_thing(&self) {
-
-    }
-}
-
-struct Y {
-    field: Z
-}
-
-struct X {
-    field: Y
-}
-
-struct S {
-    sub: X
-}
-
-fn foo(s: &S) {
-    $0s.sub.field.field$0.do_thing();
-}"#,
-            r#"
-struct Z;
-impl Z {
-    fn do_thing(&self) {
-
-    }
-}
-
-struct Y {
-    field: Z
-}
-
-struct X {
-    field: Y
-}
-
-struct S {
-    sub: X
-}
-
-fn foo(s: &S) {
-    let $0z = &s.sub.field.field;
-    z.do_thing();
-}"#,
-        );
-    }
-
-    #[test]
-    fn test_extract_var_regular_parameter() {
-        check_assist(
-            extract_variable,
-            r#"
-struct X;
-
-impl X {
-    fn do_thing(&self) {
-
-    }
-}
-
-struct S {
-    sub: X
-}
-
-fn foo(s: S) {
-    $0s.sub$0.do_thing();
-}"#,
-            r#"
-struct X;
-
-impl X {
-    fn do_thing(&self) {
-
-    }
-}
-
-struct S {
-    sub: X
-}
-
-fn foo(s: S) {
-    let $0x = s.sub;
-    x.do_thing();
-}"#,
-        );
-    }
-
-    #[test]
-    fn test_extract_var_mutable_reference_local() {
-        check_assist(
-            extract_variable,
-            r#"
-struct X;
-
-struct S {
-    sub: X
-}
-
-impl S {
-    fn new() -> S {
-        S {
-            sub: X::new()
-        }
-    }
-}
-
-impl X {
-    fn new() -> X {
-        X { }
-    }
-    fn do_thing(&self) {
-
-    }
-}
-
-
-fn foo() {
-    let local = &mut S::new();
-    $0local.sub$0.do_thing();
-}"#,
-            r#"
-struct X;
-
-struct S {
-    sub: X
-}
-
-impl S {
-    fn new() -> S {
-        S {
-            sub: X::new()
-        }
-    }
-}
-
-impl X {
-    fn new() -> X {
-        X { }
-    }
-    fn do_thing(&self) {
-
-    }
-}
-
-
-fn foo() {
-    let local = &mut S::new();
-    let $0x = &mut local.sub;
-    x.do_thing();
-}"#,
-        );
-    }
-
-    #[test]
-    fn test_extract_var_reference_local() {
-        check_assist(
-            extract_variable,
-            r#"
-struct X;
-
-struct S {
-    sub: X
-}
-
-impl S {
-    fn new() -> S {
-        S {
-            sub: X::new()
-        }
-    }
-}
-
-impl X {
-    fn new() -> X {
-        X { }
-    }
-    fn do_thing(&self) {
-
-    }
-}
-
-
-fn foo() {
-    let local = &S::new();
-    $0local.sub$0.do_thing();
-}"#,
-            r#"
-struct X;
-
-struct S {
-    sub: X
-}
-
-impl S {
-    fn new() -> S {
-        S {
-            sub: X::new()
-        }
-    }
-}
-
-impl X {
-    fn new() -> X {
-        X { }
-    }
-    fn do_thing(&self) {
-
-    }
-}
-
-
-fn foo() {
-    let local = &S::new();
-    let $0x = &local.sub;
-    x.do_thing();
-}"#,
-        );
-    }
 }
diff --git a/crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs b/crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs
index b0b9ad440c..2c540dc80d 100644
--- a/crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs
+++ b/crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs
@@ -84,12 +84,12 @@ pub(crate) fn replace_qualified_name_with_use(
                 ImportScope::Module(it) => ImportScope::Module(builder.make_mut(it)),
                 ImportScope::Block(it) => ImportScope::Block(builder.make_mut(it)),
             };
-            shorten_paths(scope.as_syntax_node(), &path.clone_for_update());
             // stick the found import in front of the to be replaced path
             let path = match path_to_qualifier.and_then(|it| mod_path_to_ast(&it).qualifier()) {
                 Some(qualifier) => make::path_concat(qualifier, path),
                 None => path,
             };
+            shorten_paths(scope.as_syntax_node(), &path.clone_for_update());
             insert_use(&scope, path, &ctx.config.insert_use);
         },
     )
@@ -356,39 +356,6 @@ mod bar {
 fn main() {
     Foo;
 }
-",
-        );
-    }
-
-    #[test]
-    fn replace_does_not_always_try_to_replace_by_full_item_path() {
-        check_assist(
-            replace_qualified_name_with_use,
-            r"
-use std::mem;
-
-mod std {
-    pub mod mem {
-        pub fn drop<T>(_: T) {}
-    }
-}
-
-fn main() {
-    mem::drop$0(0);
-}
-",
-            r"
-use std::mem::{self, drop};
-
-mod std {
-    pub mod mem {
-        pub fn drop<T>(_: T) {}
-    }
-}
-
-fn main() {
-    drop(0);
-}
 ",
         );
     }
diff --git a/crates/ide_completion/src/completions/fn_param.rs b/crates/ide_completion/src/completions/fn_param.rs
index bb4ce0a24d..5acda46498 100644
--- a/crates/ide_completion/src/completions/fn_param.rs
+++ b/crates/ide_completion/src/completions/fn_param.rs
@@ -3,7 +3,7 @@
 use rustc_hash::FxHashMap;
 use syntax::{
     ast::{self, HasModuleItem},
-    match_ast, AstNode, SyntaxKind,
+    match_ast, AstNode,
 };
 
 use crate::{
@@ -16,22 +16,24 @@ use crate::{
 /// `spam: &mut Spam` insert text/label and `spam` lookup string will be
 /// suggested.
 pub(crate) fn complete_fn_param(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
-    let param_of_fn =
-        matches!(ctx.pattern_ctx, Some(PatternContext { is_param: Some(ParamKind::Function), .. }));
-
-    if !param_of_fn {
+    if !matches!(ctx.pattern_ctx, Some(PatternContext { is_param: Some(ParamKind::Function), .. }))
+    {
         return None;
     }
 
-    let mut file_params = FxHashMap::default();
+    let mut params = FxHashMap::default();
 
-    let mut extract_params = |f: ast::Fn| {
-        f.param_list().into_iter().flat_map(|it| it.params()).for_each(|param| {
+    let me = ctx.token.ancestors().find_map(ast::Fn::cast);
+    let mut process_fn = |func: ast::Fn| {
+        if Some(&func) == me.as_ref() {
+            return;
+        }
+        func.param_list().into_iter().flat_map(|it| it.params()).for_each(|param| {
             if let Some(pat) = param.pat() {
                 // FIXME: We should be able to turn these into SmolStr without having to allocate a String
-                let whole_param = param.syntax().text().to_string();
-                let binding = pat.syntax().text().to_string();
-                file_params.entry(whole_param).or_insert(binding);
+                let text = param.syntax().text().to_string();
+                let lookup = pat.syntax().text().to_string();
+                params.entry(text).or_insert(lookup);
             }
         });
     };
@@ -42,93 +44,32 @@ pub(crate) fn complete_fn_param(acc: &mut Completions, ctx: &CompletionContext)
                 ast::SourceFile(it) => it.items().filter_map(|item| match item {
                     ast::Item::Fn(it) => Some(it),
                     _ => None,
-                }).for_each(&mut extract_params),
+                }).for_each(&mut process_fn),
                 ast::ItemList(it) => it.items().filter_map(|item| match item {
                     ast::Item::Fn(it) => Some(it),
                     _ => None,
-                }).for_each(&mut extract_params),
+                }).for_each(&mut process_fn),
                 ast::AssocItemList(it) => it.assoc_items().filter_map(|item| match item {
                     ast::AssocItem::Fn(it) => Some(it),
                     _ => None,
-                }).for_each(&mut extract_params),
+                }).for_each(&mut process_fn),
                 _ => continue,
             }
         };
     }
 
-    let function = ctx.token.ancestors().find_map(ast::Fn::cast)?;
-    let param_list = function.param_list()?;
-
-    remove_duplicated(&mut file_params, param_list.params());
-
     let self_completion_items = ["self", "&self", "mut self", "&mut self"];
-    if should_add_self_completions(ctx, param_list) {
+    if ctx.impl_def.is_some() && me?.param_list()?.params().next().is_none() {
         self_completion_items.into_iter().for_each(|self_item| {
             add_new_item_to_acc(ctx, acc, self_item.to_string(), self_item.to_string())
         });
     }
 
-    file_params.into_iter().try_for_each(|(whole_param, binding)| {
-        Some(add_new_item_to_acc(ctx, acc, surround_with_commas(ctx, whole_param), binding))
-    })?;
+    params.into_iter().for_each(|(label, lookup)| add_new_item_to_acc(ctx, acc, label, lookup));
 
     Some(())
 }
 
-fn remove_duplicated(
-    file_params: &mut FxHashMap<String, String>,
-    fn_params: ast::AstChildren<ast::Param>,
-) {
-    fn_params.for_each(|param| {
-        let whole_param = param.syntax().text().to_string();
-        file_params.remove(&whole_param);
-
-        if let Some(pattern) = param.pat() {
-            let binding = pattern.syntax().text().to_string();
-            file_params.retain(|_, v| v != &binding);
-        }
-    })
-}
-
-fn should_add_self_completions(ctx: &CompletionContext, param_list: ast::ParamList) -> bool {
-    let inside_impl = ctx.impl_def.is_some();
-    let no_params = param_list.params().next().is_none() && param_list.self_param().is_none();
-
-    inside_impl && no_params
-}
-
-fn surround_with_commas(ctx: &CompletionContext, param: String) -> String {
-    match fallible_surround_with_commas(ctx, &param) {
-        Some(surrounded) => surrounded,
-        // fallback to the original parameter
-        None => param,
-    }
-}
-
-fn fallible_surround_with_commas(ctx: &CompletionContext, param: &str) -> Option<String> {
-    let next_token = {
-        let t = ctx.token.next_token()?;
-        match t.kind() {
-            SyntaxKind::WHITESPACE => t.next_token()?,
-            _ => t,
-        }
-    };
-
-    let trailing_comma_missing = matches!(next_token.kind(), SyntaxKind::IDENT);
-    let trailing = if trailing_comma_missing { "," } else { "" };
-
-    let previous_token = if matches!(ctx.token.kind(), SyntaxKind::IDENT | SyntaxKind::WHITESPACE) {
-        ctx.previous_token.as_ref()?
-    } else {
-        &ctx.token
-    };
-
-    let needs_leading = !matches!(previous_token.kind(), SyntaxKind::L_PAREN | SyntaxKind::COMMA);
-    let leading = if needs_leading { ", " } else { "" };
-
-    Some(format!("{}{}{}", leading, param, trailing))
-}
-
 fn add_new_item_to_acc(
     ctx: &CompletionContext,
     acc: &mut Completions,
diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs
index 2374d689cb..6e43aa608a 100644
--- a/crates/ide_completion/src/context.rs
+++ b/crates/ide_completion/src/context.rs
@@ -1,7 +1,5 @@
 //! See `CompletionContext` structure.
 
-use std::iter;
-
 use base_db::SourceDatabaseExt;
 use hir::{Local, Name, ScopeDef, Semantics, SemanticsScope, Type, TypeInfo};
 use ide_db::{
@@ -433,17 +431,12 @@ impl<'a> CompletionContext<'a> {
         mut fake_ident_token: SyntaxToken,
     ) {
         let _p = profile::span("CompletionContext::expand_and_fill");
-        'expansion: loop {
-            let parent_item =
-                |item: &ast::Item| item.syntax().ancestors().skip(1).find_map(ast::Item::cast);
-            let ancestor_items = iter::successors(
-                Option::zip(
-                    find_node_at_offset::<ast::Item>(&original_file, offset),
-                    find_node_at_offset::<ast::Item>(&speculative_file, offset),
-                ),
-                |(a, b)| parent_item(a).zip(parent_item(b)),
-            );
-            for (actual_item, item_with_fake_ident) in ancestor_items {
+        loop {
+            // Expand attributes
+            if let (Some(actual_item), Some(item_with_fake_ident)) = (
+                find_node_at_offset::<ast::Item>(&original_file, offset),
+                find_node_at_offset::<ast::Item>(&speculative_file, offset),
+            ) {
                 match (
                     self.sema.expand_attr_macro(&actual_item),
                     self.sema.speculative_expand_attr_macro(
@@ -452,22 +445,19 @@ impl<'a> CompletionContext<'a> {
                         fake_ident_token.clone(),
                     ),
                 ) {
-                    // maybe parent items have attributes
-                    (None, None) => (),
-                    // successful expansions
-                    (Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) => {
-                        let new_offset = fake_mapped_token.text_range().start();
+                    (Some(actual_expansion), Some(speculative_expansion)) => {
+                        let new_offset = speculative_expansion.1.text_range().start();
                         if new_offset > actual_expansion.text_range().end() {
-                            break 'expansion;
+                            break;
                         }
                         original_file = actual_expansion;
-                        speculative_file = fake_expansion;
-                        fake_ident_token = fake_mapped_token;
+                        speculative_file = speculative_expansion.0;
+                        fake_ident_token = speculative_expansion.1;
                         offset = new_offset;
-                        continue 'expansion;
+                        continue;
                     }
-                    // exactly one expansion failed, inconsistent state so stop expanding completely
-                    _ => break 'expansion,
+                    (None, None) => (),
+                    _ => break,
                 }
             }
 
@@ -487,31 +477,28 @@ impl<'a> CompletionContext<'a> {
                     None => break,
                 };
 
-                match (
+                if let (Some(actual_expansion), Some(speculative_expansion)) = (
                     self.sema.expand(&actual_macro_call),
                     self.sema.speculative_expand(
                         &actual_macro_call,
                         &speculative_args,
-                        fake_ident_token.clone(),
+                        fake_ident_token,
                     ),
                 ) {
-                    // successful expansions
-                    (Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) => {
-                        let new_offset = fake_mapped_token.text_range().start();
-                        if new_offset > actual_expansion.text_range().end() {
-                            break;
-                        }
-                        original_file = actual_expansion;
-                        speculative_file = fake_expansion;
-                        fake_ident_token = fake_mapped_token;
-                        offset = new_offset;
-                        continue;
+                    let new_offset = speculative_expansion.1.text_range().start();
+                    if new_offset > actual_expansion.text_range().end() {
+                        break;
                     }
-                    _ => break,
+                    original_file = actual_expansion;
+                    speculative_file = speculative_expansion.0;
+                    fake_ident_token = speculative_expansion.1;
+                    offset = new_offset;
+                } else {
+                    break;
                 }
+            } else {
+                break;
             }
-
-            break;
         }
 
         self.fill(&original_file, speculative_file, offset);
diff --git a/crates/ide_db/src/defs.rs b/crates/ide_db/src/defs.rs
index 172acdbc3c..1501c4eda5 100644
--- a/crates/ide_db/src/defs.rs
+++ b/crates/ide_db/src/defs.rs
@@ -42,6 +42,74 @@ pub enum Definition {
 }
 
 impl Definition {
+    pub fn from_token(
+        sema: &Semantics<RootDatabase>,
+        token: &SyntaxToken,
+    ) -> ArrayVec<Definition, 2> {
+        let parent = match token.parent() {
+            Some(parent) => parent,
+            None => return Default::default(),
+        };
+        // resolve derives if possible
+        if let Some(ident) = ast::Ident::cast(token.clone()) {
+            let attr = ast::TokenTree::cast(parent.clone())
+                .and_then(|tt| tt.parent_meta())
+                .and_then(|meta| meta.parent_attr());
+            if let Some(attr) = attr {
+                return sema
+                    .resolve_derive_ident(&attr, &ident)
+                    .map(Into::into)
+                    .into_iter()
+                    .collect();
+            }
+        }
+        Self::from_node(sema, &parent)
+    }
+
+    pub fn from_node(sema: &Semantics<RootDatabase>, node: &SyntaxNode) -> ArrayVec<Definition, 2> {
+        let mut res = ArrayVec::new();
+        (|| {
+            match_ast! {
+                match node {
+                    ast::Name(name) => {
+                        match NameClass::classify(&sema, &name)? {
+                            NameClass::Definition(it) | NameClass::ConstReference(it) => res.push(it),
+                            NameClass::PatFieldShorthand { local_def, field_ref } => {
+                                res.push(Definition::Local(local_def));
+                                res.push(Definition::Field(field_ref));
+                            }
+                        }
+                    },
+                    ast::NameRef(name_ref) => {
+                        match NameRefClass::classify(sema, &name_ref)? {
+                            NameRefClass::Definition(it) => res.push(it),
+                            NameRefClass::FieldShorthand { local_ref, field_ref } => {
+                                res.push(Definition::Local(local_ref));
+                                res.push(Definition::Field(field_ref));
+                            }
+                        }
+                    },
+                    ast::Lifetime(lifetime) => {
+                        let def = if let Some(x) = NameClass::classify_lifetime(&sema, &lifetime) {
+                            NameClass::defined(x)
+                        } else {
+                            NameRefClass::classify_lifetime(&sema, &lifetime).and_then(|class| match class {
+                                NameRefClass::Definition(it) => Some(it),
+                                _ => None,
+                            })
+                        };
+                        if let Some(def) = def {
+                            res.push(def);
+                        }
+                    },
+                    _ => (),
+                }
+            }
+            Some(())
+        })();
+        res
+    }
+
     pub fn canonical_module_path(&self, db: &RootDatabase) -> Option<impl Iterator<Item = Module>> {
         self.module(db).map(|it| it.path_to_root(db).into_iter().rev())
     }
@@ -116,65 +184,6 @@ impl Definition {
     }
 }
 
-pub enum IdentClass {
-    NameClass(NameClass),
-    NameRefClass(NameRefClass),
-}
-
-impl IdentClass {
-    pub fn classify_node(sema: &Semantics<RootDatabase>, node: &SyntaxNode) -> Option<IdentClass> {
-        match_ast! {
-            match node {
-                ast::Name(name) => NameClass::classify(sema, &name).map(IdentClass::NameClass),
-                ast::NameRef(name_ref) => NameRefClass::classify(sema, &name_ref).map(IdentClass::NameRefClass),
-                ast::Lifetime(lifetime) => {
-                    NameClass::classify_lifetime(sema, &lifetime)
-                        .map(IdentClass::NameClass)
-                        .or_else(|| NameRefClass::classify_lifetime(sema, &lifetime).map(IdentClass::NameRefClass))
-                },
-                _ => None,
-            }
-        }
-    }
-
-    pub fn classify_token(
-        sema: &Semantics<RootDatabase>,
-        token: &SyntaxToken,
-    ) -> Option<IdentClass> {
-        let parent = token.parent()?;
-        // resolve derives if possible
-        if let Some(ident) = ast::Ident::cast(token.clone()) {
-            let attr = ast::TokenTree::cast(parent.clone())
-                .and_then(|tt| tt.parent_meta())
-                .and_then(|meta| meta.parent_attr());
-            if let Some(attr) = attr {
-                return NameRefClass::classify_derive(sema, &attr, &ident)
-                    .map(IdentClass::NameRefClass);
-            }
-        }
-        Self::classify_node(sema, &parent)
-    }
-
-    pub fn definitions(self) -> ArrayVec<Definition, 2> {
-        let mut res = ArrayVec::new();
-        match self {
-            IdentClass::NameClass(NameClass::Definition(it) | NameClass::ConstReference(it)) => {
-                res.push(it)
-            }
-            IdentClass::NameClass(NameClass::PatFieldShorthand { local_def, field_ref }) => {
-                res.push(Definition::Local(local_def));
-                res.push(Definition::Field(field_ref));
-            }
-            IdentClass::NameRefClass(NameRefClass::Definition(it)) => res.push(it),
-            IdentClass::NameRefClass(NameRefClass::FieldShorthand { local_ref, field_ref }) => {
-                res.push(Definition::Local(local_ref));
-                res.push(Definition::Field(field_ref));
-            }
-        }
-        res
-    }
-}
-
 /// On a first blush, a single `ast::Name` defines a single definition at some
 /// scope. That is, that, by just looking at the syntactical category, we can
 /// unambiguously define the semantic category.
@@ -456,14 +465,6 @@ impl NameRefClass {
             _ => None,
         }
     }
-
-    pub fn classify_derive(
-        sema: &Semantics<RootDatabase>,
-        attr: &ast::Attr,
-        ident: &ast::Ident,
-    ) -> Option<NameRefClass> {
-        sema.resolve_derive_ident(&attr, &ident).map(Definition::from).map(NameRefClass::Definition)
-    }
 }
 
 impl_from!(
diff --git a/crates/ide_db/src/helpers/insert_whitespace_into_node.rs b/crates/ide_db/src/helpers/insert_whitespace_into_node.rs
index d59f13b9a1..251a4caa13 100644
--- a/crates/ide_db/src/helpers/insert_whitespace_into_node.rs
+++ b/crates/ide_db/src/helpers/insert_whitespace_into_node.rs
@@ -88,7 +88,7 @@ pub fn insert_ws_into(syn: SyntaxNode) -> SyntaxNode {
             LIFETIME_IDENT if is_next(|it| is_text(it), true) => {
                 mods.push(do_ws(after, tok));
             }
-            AS_KW | DYN_KW | IMPL_KW => {
+            AS_KW => {
                 mods.push(do_ws(after, tok));
             }
             T![;] => {
diff --git a/crates/project_model/src/workspace.rs b/crates/project_model/src/workspace.rs
index 60f99f368b..869b064e1d 100644
--- a/crates/project_model/src/workspace.rs
+++ b/crates/project_model/src/workspace.rs
@@ -387,10 +387,14 @@ impl ProjectWorkspace {
 
     pub fn to_crate_graph(
         &self,
-        load_proc_macro: &mut dyn FnMut(&AbsPath) -> Vec<ProcMacro>,
+        dummy_replace: &FxHashMap<Box<str>, Box<[Box<str>]>>,
+        load_proc_macro: &mut dyn FnMut(&AbsPath, &[Box<str>]) -> Vec<ProcMacro>,
         load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
     ) -> CrateGraph {
         let _p = profile::span("ProjectWorkspace::to_crate_graph");
+        let load_proc_macro = &mut |crate_name: &_, path: &_| {
+            load_proc_macro(path, dummy_replace.get(crate_name).map(|it| &**it).unwrap_or_default())
+        };
 
         let mut crate_graph = match self {
             ProjectWorkspace::Json { project, sysroot, rustc_cfg } => project_json_to_crate_graph(
@@ -432,7 +436,7 @@ impl ProjectWorkspace {
 
 fn project_json_to_crate_graph(
     rustc_cfg: Vec<CfgFlag>,
-    load_proc_macro: &mut dyn FnMut(&AbsPath) -> Vec<ProcMacro>,
+    load_proc_macro: &mut dyn FnMut(&str, &AbsPath) -> Vec<ProcMacro>,
     load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
     project: &ProjectJson,
     sysroot: &Option<Sysroot>,
@@ -452,7 +456,12 @@ fn project_json_to_crate_graph(
         })
         .map(|(crate_id, krate, file_id)| {
             let env = krate.env.clone().into_iter().collect();
-            let proc_macro = krate.proc_macro_dylib_path.clone().map(|it| load_proc_macro(&it));
+            let proc_macro = krate.proc_macro_dylib_path.clone().map(|it| {
+                load_proc_macro(
+                    krate.display_name.as_ref().map(|it| it.canonical_name()).unwrap_or(""),
+                    &it,
+                )
+            });
 
             let target_cfgs = match krate.target.as_deref() {
                 Some(target) => {
@@ -513,7 +522,7 @@ fn project_json_to_crate_graph(
 fn cargo_to_crate_graph(
     rustc_cfg: Vec<CfgFlag>,
     override_cfg: &CfgOverrides,
-    load_proc_macro: &mut dyn FnMut(&AbsPath) -> Vec<ProcMacro>,
+    load_proc_macro: &mut dyn FnMut(&str, &AbsPath) -> Vec<ProcMacro>,
     load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
     cargo: &CargoWorkspace,
     build_scripts: &WorkspaceBuildScripts,
@@ -571,7 +580,7 @@ fn cargo_to_crate_graph(
                     &cargo[pkg],
                     build_scripts.outputs.get(pkg),
                     cfg_options,
-                    load_proc_macro,
+                    &mut |path| load_proc_macro(&cargo[tgt].name, path),
                     file_id,
                     &cargo[tgt].name,
                 );
@@ -702,7 +711,7 @@ fn handle_rustc_crates(
     load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
     crate_graph: &mut CrateGraph,
     cfg_options: &CfgOptions,
-    load_proc_macro: &mut dyn FnMut(&AbsPath) -> Vec<ProcMacro>,
+    load_proc_macro: &mut dyn FnMut(&str, &AbsPath) -> Vec<ProcMacro>,
     pkg_to_lib_crate: &mut FxHashMap<la_arena::Idx<crate::PackageData>, CrateId>,
     public_deps: &SysrootPublicDeps,
     cargo: &CargoWorkspace,
@@ -738,7 +747,7 @@ fn handle_rustc_crates(
                         &rustc_workspace[pkg],
                         None,
                         cfg_options,
-                        load_proc_macro,
+                        &mut |path| load_proc_macro(&rustc_workspace[tgt].name, path),
                         file_id,
                         &rustc_workspace[tgt].name,
                     );
diff --git a/crates/rust-analyzer/src/cli/load_cargo.rs b/crates/rust-analyzer/src/cli/load_cargo.rs
index d60861a627..19ce86e3ff 100644
--- a/crates/rust-analyzer/src/cli/load_cargo.rs
+++ b/crates/rust-analyzer/src/cli/load_cargo.rs
@@ -66,7 +66,8 @@ pub fn load_workspace(
     };
 
     let crate_graph = ws.to_crate_graph(
-        &mut |path: &AbsPath| load_proc_macro(proc_macro_client.as_ref(), path),
+        &Default::default(),
+        &mut |path: &AbsPath, _| load_proc_macro(proc_macro_client.as_ref(), path, &[]),
         &mut |path: &AbsPath| {
             let contents = loader.load_sync(path);
             let path = vfs::VfsPath::from(path.to_path_buf());
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 818aab3515..a704139255 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -301,6 +301,10 @@ config_data! {
         /// Internal config, path to proc-macro server executable (typically,
         /// this is rust-analyzer itself, but we override this in tests).
         procMacro_server: Option<PathBuf>          = "null",
+        /// These proc-macros will be ignored when trying to expand them.
+        ///
+        /// This config takes a map of crate names with the exported proc-macro names to ignore as values.
+        procMacro_ignored: FxHashMap<Box<str>, Box<[Box<str>]>>          = "{}",
 
         /// Command to be executed instead of 'cargo' for runnables.
         runnables_overrideCargo: Option<String> = "null",
@@ -716,6 +720,9 @@ impl Config {
         };
         Some((path, vec!["proc-macro".into()]))
     }
+    pub fn dummy_replacements(&self) -> &FxHashMap<Box<str>, Box<[Box<str>]>> {
+        &self.data.procMacro_ignored
+    }
     pub fn expand_proc_attr_macros(&self) -> bool {
         self.data.experimental_procAttrMacros
     }
@@ -1163,7 +1170,13 @@ fn get_field<T: DeserializeOwned>(
         .find_map(move |field| {
             let mut pointer = field.replace('_', "/");
             pointer.insert(0, '/');
-            json.pointer_mut(&pointer).and_then(|it| serde_json::from_value(it.take()).ok())
+            json.pointer_mut(&pointer).and_then(|it| match serde_json::from_value(it.take()) {
+                Ok(it) => Some(it),
+                Err(e) => {
+                    tracing::warn!("Failed to deserialize config field at {}: {:?}", pointer, e);
+                    None
+                }
+            })
         })
         .unwrap_or(default)
 }
@@ -1224,6 +1237,9 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json
             "items": { "type": "string" },
             "uniqueItems": true,
         },
+        "FxHashMap<Box<str>, Box<[Box<str>]>>" => set! {
+            "type": "object",
+        },
         "FxHashMap<String, SnippetDef>" => set! {
             "type": "object",
         },
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index b14b3dbcfa..eecc83e02a 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -10,6 +10,7 @@ use ide_db::base_db::{
 };
 use proc_macro_api::{MacroDylib, ProcMacroServer};
 use project_model::{ProjectWorkspace, WorkspaceBuildScripts};
+use syntax::SmolStr;
 use vfs::{file_set::FileSetConfig, AbsPath, AbsPathBuf, ChangeKind};
 
 use crate::{
@@ -306,8 +307,9 @@ impl GlobalState {
         // Create crate graph from all the workspaces
         let crate_graph = {
             let proc_macro_client = self.proc_macro_client.as_ref();
-            let mut load_proc_macro =
-                move |path: &AbsPath| load_proc_macro(proc_macro_client, path);
+            let mut load_proc_macro = move |path: &AbsPath, dummy_replace: &_| {
+                load_proc_macro(proc_macro_client, path, dummy_replace)
+            };
 
             let vfs = &mut self.vfs.write().0;
             let loader = &mut self.loader;
@@ -328,7 +330,11 @@ impl GlobalState {
 
             let mut crate_graph = CrateGraph::default();
             for ws in self.workspaces.iter() {
-                crate_graph.extend(ws.to_crate_graph(&mut load_proc_macro, &mut load));
+                crate_graph.extend(ws.to_crate_graph(
+                    self.config.dummy_replacements(),
+                    &mut load_proc_macro,
+                    &mut load,
+                ));
             }
             crate_graph
         };
@@ -505,7 +511,13 @@ impl SourceRootConfig {
     }
 }
 
-pub(crate) fn load_proc_macro(client: Option<&ProcMacroServer>, path: &AbsPath) -> Vec<ProcMacro> {
+/// Load the proc-macros for the given lib path, replacing all expanders whose names are in `dummy_replace`
+/// with an identity dummy expander.
+pub(crate) fn load_proc_macro(
+    client: Option<&ProcMacroServer>,
+    path: &AbsPath,
+    dummy_replace: &[Box<str>],
+) -> Vec<ProcMacro> {
     let dylib = match MacroDylib::new(path.to_path_buf()) {
         Ok(it) => it,
         Err(err) => {
@@ -532,17 +544,25 @@ pub(crate) fn load_proc_macro(client: Option<&ProcMacroServer>, path: &AbsPath)
                 Vec::new()
             }
         })
-        .map(expander_to_proc_macro)
+        .map(|expander| expander_to_proc_macro(expander, dummy_replace))
         .collect();
 
-    fn expander_to_proc_macro(expander: proc_macro_api::ProcMacro) -> ProcMacro {
-        let name = expander.name().into();
+    fn expander_to_proc_macro(
+        expander: proc_macro_api::ProcMacro,
+        dummy_replace: &[Box<str>],
+    ) -> ProcMacro {
+        let name = SmolStr::from(expander.name());
         let kind = match expander.kind() {
             proc_macro_api::ProcMacroKind::CustomDerive => ProcMacroKind::CustomDerive,
             proc_macro_api::ProcMacroKind::FuncLike => ProcMacroKind::FuncLike,
             proc_macro_api::ProcMacroKind::Attr => ProcMacroKind::Attr,
         };
-        let expander = Arc::new(Expander(expander));
+        let expander: Arc<dyn ProcMacroExpander> =
+            if dummy_replace.iter().any(|replace| &**replace == name) {
+                Arc::new(DummyExpander)
+            } else {
+                Arc::new(Expander(expander))
+            };
         ProcMacro { name, kind, expander }
     }
 
@@ -564,6 +584,21 @@ pub(crate) fn load_proc_macro(client: Option<&ProcMacroServer>, path: &AbsPath)
             }
         }
     }
+
+    /// Dummy identity expander, used for proc-macros that are deliberately ignored by the user.
+    #[derive(Debug)]
+    struct DummyExpander;
+
+    impl ProcMacroExpander for DummyExpander {
+        fn expand(
+            &self,
+            subtree: &tt::Subtree,
+            _: Option<&tt::Subtree>,
+            _: &Env,
+        ) -> Result<tt::Subtree, ProcMacroExpansionError> {
+            Ok(subtree.clone())
+        }
+    }
 }
 
 pub(crate) fn should_refresh_for_change(path: &AbsPath, change_kind: ChangeKind) -> bool {
diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs
index 7dd9ac1e9e..98d142da3f 100644
--- a/crates/syntax/src/ast/node_ext.rs
+++ b/crates/syntax/src/ast/node_ext.rs
@@ -108,17 +108,6 @@ impl HasName for Macro {
 
 impl HasAttrs for Macro {}
 
-impl From<ast::AssocItem> for ast::Item {
-    fn from(assoc: ast::AssocItem) -> Self {
-        match assoc {
-            ast::AssocItem::Const(it) => ast::Item::Const(it),
-            ast::AssocItem::Fn(it) => ast::Item::Fn(it),
-            ast::AssocItem::MacroCall(it) => ast::Item::MacroCall(it),
-            ast::AssocItem::TypeAlias(it) => ast::Item::TypeAlias(it),
-        }
-    }
-}
-
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub enum AttrKind {
     Inner,
@@ -445,10 +434,13 @@ impl ast::AstNode for NameLike {
     }
 }
 
-const _: () = {
-    use ast::{Lifetime, Name, NameRef};
+mod __ {
+    use super::{
+        ast::{Lifetime, Name, NameRef},
+        NameLike,
+    };
     stdx::impl_from!(NameRef, Name, Lifetime for NameLike);
-};
+}
 
 #[derive(Debug, Clone, PartialEq)]
 pub enum NameOrNameRef {
diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc
index 494051b306..f46dda2351 100644
--- a/docs/user/generated_config.adoc
+++ b/docs/user/generated_config.adoc
@@ -455,6 +455,13 @@ Enable support for procedural macros, implies `#rust-analyzer.cargo.runBuildScri
 Internal config, path to proc-macro server executable (typically,
 this is rust-analyzer itself, but we override this in tests).
 --
+[[rust-analyzer.procMacro.ignored]]rust-analyzer.procMacro.ignored (default: `{}`)::
++
+--
+These proc-macros will be ignored when trying to expand them.
+
+This config takes a map of crate names with the exported proc-macro names to ignore as values.
+--
 [[rust-analyzer.runnables.overrideCargo]]rust-analyzer.runnables.overrideCargo (default: `null`)::
 +
 --
diff --git a/editors/code/package.json b/editors/code/package.json
index 57757553a7..d418868474 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -880,6 +880,11 @@
                         "string"
                     ]
                 },
+                "rust-analyzer.procMacro.ignored": {
+                    "markdownDescription": "These proc-macros will be ignored when trying to expand them.\n\nThis config takes a map of crate names with the exported proc-macro names to ignore as values.",
+                    "default": {},
+                    "type": "object"
+                },
                 "rust-analyzer.runnables.overrideCargo": {
                     "markdownDescription": "Command to be executed instead of 'cargo' for runnables.",
                     "default": null,


Test patch:
diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs
index ed76c84ab4..2bfda1aff2 100644
--- a/crates/ide/src/hover/tests.rs
+++ b/crates/ide/src/hover/tests.rs
@@ -3350,31 +3350,6 @@ const FOO$0: usize = 1 << 10;
     check(
         r#"
 /// This is a doc
-const FOO$0: usize = {
-    let b = 4;
-    let a = { let b = 2; let a = b; a } + { let a = 1; a + b };
-    a
-};
-"#,
-        expect![[r#"
-            *FOO*
-
-            ```rust
-            test
-            ```
-
-            ```rust
-            const FOO: usize = 7
-            ```
-
-            ---
-
-            This is a doc
-        "#]],
-    );
-    check(
-        r#"
-/// This is a doc
 const FOO$0: usize = 2 - 3;
 "#,
         expect![[r#"
@@ -3468,24 +3443,6 @@ fn foo() {
     );
 }
 
-#[test]
-fn array_repeat_exp() {
-    check(
-        r#"
-fn main() {
-    let til$0e4 = [0_u32; (4 * 8 * 8) / 32];
-}
-        "#,
-        expect![[r#"
-            *tile4*
-
-            ```rust
-            let tile4: [u32; 8]
-            ```
-            "#]],
-    );
-}
-
 #[test]
 fn hover_mod_def() {
     check(
diff --git a/crates/ide_completion/src/tests/fn_param.rs b/crates/ide_completion/src/tests/fn_param.rs
index 940cecf395..8a07aefafe 100644
--- a/crates/ide_completion/src/tests/fn_param.rs
+++ b/crates/ide_completion/src/tests/fn_param.rs
@@ -46,20 +46,7 @@ fn bar(file_id: usize) {}
 fn baz(file$0 id: u32) {}
 "#,
         expect![[r#"
-            bn file_id: usize,
-            kw mut
-        "#]],
-    );
-}
-
-#[test]
-fn repeated_param_name() {
-    check(
-        r#"
-fn foo(file_id: usize) {}
-fn bar(file_id: u32, $0) {}
-"#,
-        expect![[r#"
+            bn file_id: usize
             kw mut
         "#]],
     );
@@ -139,6 +126,7 @@ impl A {
 
 #[test]
 fn in_impl_after_self() {
+    // FIXME: self completions should not be here
     check(
         r#"
 struct A {}
@@ -149,6 +137,10 @@ impl A {
 }
 "#,
         expect![[r#"
+            bn self
+            bn &self
+            bn mut self
+            bn &mut self
             bn file_id: usize
             kw mut
             sp Self
diff --git a/crates/project_model/src/tests.rs b/crates/project_model/src/tests.rs
index edf2673661..2977ff74e2 100644
--- a/crates/project_model/src/tests.rs
+++ b/crates/project_model/src/tests.rs
@@ -88,7 +88,7 @@ fn rooted_project_json(data: ProjectJsonData) -> ProjectJson {
 }
 
 fn to_crate_graph(project_workspace: ProjectWorkspace) -> CrateGraph {
-    project_workspace.to_crate_graph(&mut |_| Vec::new(), &mut {
+    project_workspace.to_crate_graph(&Default::default(), &mut |_, _| Vec::new(), &mut {
         let mut counter = 0;
         move |_path| {
             counter += 1;