Repository: rust-analyzer/rust-analyzer
Pull request #11676 (2022-03-10 21:23:57)

internal: Expand into pseudo-derive attribute expansions in completions
With this we now properly handle qualified path completions in derives
bors r+


Patch:
diff --git a/crates/hir/src/display.rs b/crates/hir/src/display.rs
index 8f80f3a5db..6e3285fd4f 100644
--- a/crates/hir/src/display.rs
+++ b/crates/hir/src/display.rs
@@ -18,8 +18,8 @@ use syntax::SmolStr;
 
 use crate::{
     Adt, Const, ConstParam, Enum, Field, Function, GenericParam, HasCrate, HasVisibility,
-    LifetimeParam, Module, Static, Struct, Trait, TyBuilder, Type, TypeAlias, TypeOrConstParam,
-    TypeParam, Union, Variant,
+    LifetimeParam, Macro, Module, Static, Struct, Trait, TyBuilder, Type, TypeAlias,
+    TypeOrConstParam, TypeParam, Union, Variant,
 };
 
 impl HirDisplay for Function {
@@ -509,3 +509,14 @@ impl HirDisplay for Module {
         }
     }
 }
+
+impl HirDisplay for Macro {
+    fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> {
+        match self.id {
+            hir_def::MacroId::Macro2Id(_) => write!(f, "macro"),
+            hir_def::MacroId::MacroRulesId(_) => write!(f, "macro_rules!"),
+            hir_def::MacroId::ProcMacroId(_) => write!(f, "proc_macro"),
+        }?;
+        write!(f, " {}", self.name(f.db))
+    }
+}
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 0aabb415d4..a90120a467 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -1811,6 +1811,10 @@ impl Macro {
     pub fn is_attr(&self, db: &dyn HirDatabase) -> bool {
         matches!(self.kind(db), MacroKind::Attr)
     }
+
+    pub fn is_derive(&self, db: &dyn HirDatabase) -> bool {
+        matches!(self.kind(db), MacroKind::Derive)
+    }
 }
 
 impl HasVisibility for Macro {
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs
index 6d6a86fc8a..45544559ea 100644
--- a/crates/hir/src/semantics.rs
+++ b/crates/hir/src/semantics.rs
@@ -151,6 +151,10 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
         self.imp.expand_attr_macro(item)
     }
 
+    pub fn expand_derive_as_pseudo_attr_macro(&self, attr: &ast::Attr) -> Option<SyntaxNode> {
+        self.imp.expand_derive_as_pseudo_attr_macro(attr)
+    }
+
     pub fn resolve_derive_macro(&self, derive: &ast::Attr) -> Option<Vec<Option<Macro>>> {
         self.imp.resolve_derive_macro(derive)
     }
@@ -185,6 +189,19 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
         self.imp.speculative_expand_attr(actual_macro_call, speculative_args, token_to_map)
     }
 
+    pub fn speculative_expand_derive_as_pseudo_attr_macro(
+        &self,
+        actual_macro_call: &ast::Attr,
+        speculative_args: &ast::Attr,
+        token_to_map: SyntaxToken,
+    ) -> Option<(SyntaxNode, SyntaxToken)> {
+        self.imp.speculative_expand_derive_as_pseudo_attr_macro(
+            actual_macro_call,
+            speculative_args,
+            token_to_map,
+        )
+    }
+
     /// Descend the token into macrocalls to its first mapped counterpart.
     pub fn descend_into_macros_single(&self, token: SyntaxToken) -> SyntaxToken {
         self.imp.descend_into_macros_single(token)
@@ -438,9 +455,16 @@ impl<'db> SemanticsImpl<'db> {
     fn expand_attr_macro(&self, item: &ast::Item) -> Option<SyntaxNode> {
         let src = self.wrap_node_infile(item.clone());
         let macro_call_id = self.with_ctx(|ctx| ctx.item_to_macro_call(src))?;
-        let file_id = macro_call_id.as_file();
-        let node = self.parse_or_expand(file_id)?;
-        Some(node)
+        self.parse_or_expand(macro_call_id.as_file())
+    }
+
+    fn expand_derive_as_pseudo_attr_macro(&self, attr: &ast::Attr) -> Option<SyntaxNode> {
+        let src = self.wrap_node_infile(attr.clone());
+        let adt = attr.syntax().parent().and_then(ast::Adt::cast)?;
+        let call_id = self.with_ctx(|ctx| {
+            ctx.attr_to_derive_macro_call(src.with_value(&adt), src).map(|(_, it, _)| it)
+        })?;
+        self.parse_or_expand(call_id.as_file())
     }
 
     fn resolve_derive_macro(&self, attr: &ast::Attr) -> Option<Vec<Option<Macro>>> {
@@ -533,6 +557,25 @@ impl<'db> SemanticsImpl<'db> {
         )
     }
 
+    fn speculative_expand_derive_as_pseudo_attr_macro(
+        &self,
+        actual_macro_call: &ast::Attr,
+        speculative_args: &ast::Attr,
+        token_to_map: SyntaxToken,
+    ) -> Option<(SyntaxNode, SyntaxToken)> {
+        let attr = self.wrap_node_infile(actual_macro_call.clone());
+        let adt = actual_macro_call.syntax().parent().and_then(ast::Adt::cast)?;
+        let macro_call_id = self.with_ctx(|ctx| {
+            ctx.attr_to_derive_macro_call(attr.with_value(&adt), attr).map(|(_, it, _)| it)
+        })?;
+        hir_expand::db::expand_speculative(
+            self.db.upcast(),
+            macro_call_id,
+            speculative_args.syntax(),
+            token_to_map,
+        )
+    }
+
     // This might not be the correct way to do this, but it works for now
     fn descend_node_into_attributes<N: AstNode>(&self, node: N) -> SmallVec<[N; 1]> {
         let mut res = smallvec![];
diff --git a/crates/hir_expand/src/builtin_attr_macro.rs b/crates/hir_expand/src/builtin_attr_macro.rs
index 6535f27a63..0c886ac4da 100644
--- a/crates/hir_expand/src/builtin_attr_macro.rs
+++ b/crates/hir_expand/src/builtin_attr_macro.rs
@@ -1,7 +1,5 @@
 //! Builtin attributes.
 
-use itertools::Itertools;
-
 use crate::{db::AstDatabase, name, ExpandResult, MacroCallId, MacroCallKind};
 
 macro_rules! register_builtin {
@@ -98,10 +96,16 @@ fn derive_attr_expand(
 ) -> ExpandResult<tt::Subtree> {
     let loc = db.lookup_intern_macro_call(id);
     let derives = match &loc.kind {
-        MacroCallKind::Attr { attr_args, .. } => &attr_args.0,
-        _ => return ExpandResult::ok(tt.clone()),
+        MacroCallKind::Attr { attr_args, is_derive: true, .. } => &attr_args.0,
+        _ => return ExpandResult::ok(Default::default()),
     };
+    pseudo_derive_attr_expansion(tt, derives)
+}
 
+pub fn pseudo_derive_attr_expansion(
+    tt: &tt::Subtree,
+    args: &tt::Subtree,
+) -> ExpandResult<tt::Subtree> {
     let mk_leaf = |char| {
         tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct {
             char,
@@ -111,21 +115,12 @@ fn derive_attr_expand(
     };
 
     let mut token_trees = Vec::new();
-    for (comma, group) in &derives
-        .token_trees
-        .iter()
-        .filter_map(|tt| match tt {
-            tt::TokenTree::Leaf(l) => Some(l),
-            tt::TokenTree::Subtree(_) => None,
-        })
-        .group_by(|l| matches!(l, tt::Leaf::Punct(tt::Punct { char: ',', .. })))
+    for tt in (&args.token_trees)
+        .split(|tt| matches!(tt, tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { char: ',', .. }))))
     {
-        if comma {
-            continue;
-        }
         token_trees.push(mk_leaf('#'));
         token_trees.push(mk_leaf('['));
-        token_trees.extend(group.cloned().map(tt::TokenTree::Leaf));
+        token_trees.extend(tt.iter().cloned());
         token_trees.push(mk_leaf(']'));
     }
     token_trees.push(mk_leaf('('));
diff --git a/crates/hir_expand/src/db.rs b/crates/hir_expand/src/db.rs
index d6d33b4cd7..9fe414de26 100644
--- a/crates/hir_expand/src/db.rs
+++ b/crates/hir_expand/src/db.rs
@@ -14,10 +14,10 @@ use syntax::{
 };
 
 use crate::{
-    ast_id_map::AstIdMap, fixup, hygiene::HygieneFrame, BuiltinAttrExpander, BuiltinDeriveExpander,
-    BuiltinFnLikeExpander, ExpandError, ExpandResult, ExpandTo, HirFileId, HirFileIdRepr,
-    MacroCallId, MacroCallKind, MacroCallLoc, MacroDefId, MacroDefKind, MacroFile,
-    ProcMacroExpander,
+    ast_id_map::AstIdMap, builtin_attr_macro::pseudo_derive_attr_expansion, fixup,
+    hygiene::HygieneFrame, BuiltinAttrExpander, BuiltinDeriveExpander, BuiltinFnLikeExpander,
+    ExpandError, ExpandResult, ExpandTo, HirFileId, HirFileIdRepr, MacroCallId, MacroCallKind,
+    MacroCallLoc, MacroDefId, MacroDefKind, MacroFile, ProcMacroExpander,
 };
 
 /// Total limit on the number of tokens produced by any macro invocation.
@@ -161,14 +161,16 @@ pub fn expand_speculative(
     );
 
     let (attr_arg, token_id) = match loc.kind {
-        MacroCallKind::Attr { invoc_attr_index, .. } => {
-            // Attributes may have an input token tree, build the subtree and map for this as well
-            // then try finding a token id for our token if it is inside this input subtree.
-            let item = ast::Item::cast(speculative_args.clone())?;
-            let attr = item
-                .doc_comments_and_attrs()
-                .nth(invoc_attr_index as usize)
-                .and_then(Either::left)?;
+        MacroCallKind::Attr { invoc_attr_index, is_derive, .. } => {
+            let attr = if is_derive {
+                // for pseudo-derive expansion we actually pass the attribute itself only
+                ast::Attr::cast(speculative_args.clone())
+            } else {
+                // Attributes may have an input token tree, build the subtree and map for this as well
+                // then try finding a token id for our token if it is inside this input subtree.
+                let item = ast::Item::cast(speculative_args.clone())?;
+                item.doc_comments_and_attrs().nth(invoc_attr_index as usize).and_then(Either::left)
+            }?;
             match attr.token_tree() {
                 Some(token_tree) => {
                     let (mut tree, map) = syntax_node_to_token_tree(attr.token_tree()?.syntax());
@@ -205,11 +207,15 @@ pub fn expand_speculative(
 
     // Do the actual expansion, we need to directly expand the proc macro due to the attribute args
     // Otherwise the expand query will fetch the non speculative attribute args and pass those instead.
-    let mut speculative_expansion = if let MacroDefKind::ProcMacro(expander, ..) = loc.def.kind {
-        tt.delimiter = None;
-        expander.expand(db, loc.krate, &tt, attr_arg.as_ref())
-    } else {
-        macro_def.expand(db, actual_macro_call, &tt)
+    let mut speculative_expansion = match loc.def.kind {
+        MacroDefKind::ProcMacro(expander, ..) => {
+            tt.delimiter = None;
+            expander.expand(db, loc.krate, &tt, attr_arg.as_ref())
+        }
+        MacroDefKind::BuiltInAttr(BuiltinAttrExpander::Derive, _) => {
+            pseudo_derive_attr_expansion(&tt, attr_arg.as_ref()?)
+        }
+        _ => macro_def.expand(db, actual_macro_call, &tt),
     };
 
     let expand_to = macro_expand_to(db, actual_macro_call);
diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs
index c298065f4e..2e141600e8 100644
--- a/crates/ide/src/hover/render.rs
+++ b/crates/ide/src/hover/render.rs
@@ -2,7 +2,7 @@
 use std::fmt::Display;
 
 use either::Either;
-use hir::{AsAssocItem, AttributeTemplate, HasAttrs, HasSource, HirDisplay, Semantics, TypeInfo};
+use hir::{AsAssocItem, AttributeTemplate, HasAttrs, HirDisplay, Semantics, TypeInfo};
 use ide_db::{
     base_db::SourceDatabase,
     defs::Definition,
@@ -13,9 +13,7 @@ use ide_db::{
 use itertools::Itertools;
 use stdx::format_to;
 use syntax::{
-    algo, ast,
-    display::{fn_as_proc_macro_label, macro_label},
-    match_ast, AstNode, Direction,
+    algo, ast, match_ast, AstNode, Direction,
     SyntaxKind::{LET_EXPR, LET_STMT},
     SyntaxToken, T,
 };
@@ -342,14 +340,8 @@ pub(super) fn definition(
 ) -> Option<Markup> {
     let mod_path = definition_mod_path(db, &def);
     let (label, docs) = match def {
-        Definition::Macro(it) => (
-            match &it.source(db)?.value {
-                Either::Left(mac) => macro_label(mac),
-                Either::Right(mac_fn) => fn_as_proc_macro_label(mac_fn),
-            },
-            it.attrs(db).docs(),
-        ),
-        Definition::Field(def) => label_and_docs(db, def),
+        Definition::Macro(it) => label_and_docs(db, it),
+        Definition::Field(it) => label_and_docs(db, it),
         Definition::Module(it) => label_and_docs(db, it),
         Definition::Function(it) => label_and_docs(db, it),
         Definition::Adt(it) => label_and_docs(db, it),
diff --git a/crates/ide_assists/src/handlers/remove_dbg.rs b/crates/ide_assists/src/handlers/remove_dbg.rs
index 07dcfd9671..ebea2d5dea 100644
--- a/crates/ide_assists/src/handlers/remove_dbg.rs
+++ b/crates/ide_assists/src/handlers/remove_dbg.rs
@@ -32,7 +32,7 @@ pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
     }
 
     let mac_input = tt.syntax().children_with_tokens().skip(1).take_while(|it| *it != r_delim);
-    let input_expressions = mac_input.into_iter().group_by(|tok| tok.kind() == T![,]);
+    let input_expressions = mac_input.group_by(|tok| tok.kind() == T![,]);
     let input_expressions = input_expressions
         .into_iter()
         .filter_map(|(is_sep, group)| (!is_sep).then(|| group))
diff --git a/crates/ide_completion/src/completions/attribute.rs b/crates/ide_completion/src/completions/attribute.rs
index 3c5dd8f3fd..6b51e19bbb 100644
--- a/crates/ide_completion/src/completions/attribute.rs
+++ b/crates/ide_completion/src/completions/attribute.rs
@@ -29,6 +29,8 @@ mod derive;
 mod lint;
 mod repr;
 
+pub(crate) use self::derive::complete_derive;
+
 /// Complete inputs to known builtin attributes as well as derive attributes
 pub(crate) fn complete_known_attribute_input(
     acc: &mut Completions,
@@ -46,7 +48,6 @@ pub(crate) fn complete_known_attribute_input(
 
     match path.text().as_str() {
         "repr" => repr::complete_repr(acc, ctx, tt),
-        "derive" => derive::complete_derive(acc, ctx, ctx.attr.as_ref()?),
         "feature" => lint::complete_lint(acc, ctx, &parse_tt_as_comma_sep_paths(tt)?, FEATURES),
         "allow" | "warn" | "deny" | "forbid" => {
             let existing_lints = parse_tt_as_comma_sep_paths(tt)?;
@@ -62,9 +63,7 @@ pub(crate) fn complete_known_attribute_input(
 
             lint::complete_lint(acc, ctx, &existing_lints, &lints);
         }
-        "cfg" => {
-            cfg::complete_cfg(acc, ctx);
-        }
+        "cfg" => cfg::complete_cfg(acc, ctx),
         _ => (),
     }
     Some(())
@@ -347,7 +346,7 @@ fn parse_comma_sep_expr(input: ast::TokenTree) -> Option<Vec<ast::Expr>> {
         .children_with_tokens()
         .skip(1)
         .take_while(|it| it.as_token() != Some(&r_paren));
-    let input_expressions = tokens.into_iter().group_by(|tok| tok.kind() == T![,]);
+    let input_expressions = tokens.group_by(|tok| tok.kind() == T![,]);
     Some(
         input_expressions
             .into_iter()
diff --git a/crates/ide_completion/src/completions/attribute/derive.rs b/crates/ide_completion/src/completions/attribute/derive.rs
index 64f6e3989d..1edc92d5d6 100644
--- a/crates/ide_completion/src/completions/attribute/derive.rs
+++ b/crates/ide_completion/src/completions/attribute/derive.rs
@@ -1,123 +1,111 @@
 //! Completion for derives
-use hir::{HasAttrs, Macro, MacroKind};
-use ide_db::{
-    imports::{import_assets::ImportAssets, insert_use::ImportScope},
-    SymbolKind,
-};
+use hir::{HasAttrs, ScopeDef};
+use ide_db::SymbolKind;
 use itertools::Itertools;
-use rustc_hash::FxHashSet;
-use syntax::{ast, SmolStr, SyntaxKind};
+use syntax::SmolStr;
 
 use crate::{
-    completions::flyimport::compute_fuzzy_completion_order_key, context::CompletionContext,
-    item::CompletionItem, Completions, ImportEdit,
+    context::{CompletionContext, PathCompletionCtx, PathKind, PathQualifierCtx},
+    item::CompletionItem,
+    Completions,
 };
 
-pub(super) fn complete_derive(acc: &mut Completions, ctx: &CompletionContext, attr: &ast::Attr) {
+pub(crate) fn complete_derive(acc: &mut Completions, ctx: &CompletionContext) {
+    let (qualifier, is_absolute_path) = match ctx.path_context {
+        Some(PathCompletionCtx {
+            kind: Some(PathKind::Derive),
+            ref qualifier,
+            is_absolute_path,
+            ..
+        }) => (qualifier, is_absolute_path),
+        _ => return,
+    };
+
     let core = ctx.famous_defs().core();
-    let existing_derives: FxHashSet<_> =
-        ctx.sema.resolve_derive_macro(attr).into_iter().flatten().flatten().collect();
 
-    for (name, mac) in get_derives_in_scope(ctx) {
-        if existing_derives.contains(&mac) {
-            continue;
-        }
+    match qualifier {
+        Some(PathQualifierCtx { resolution, is_super_chain, .. }) => {
+            if *is_super_chain {
+                acc.add_keyword(ctx, "super::");
+            }
 
-        let name = name.to_smol_str();
-        let (label, lookup) = match (core, mac.module(ctx.db).krate()) {
-            // show derive dependencies for `core`/`std` derives
-            (Some(core), mac_krate) if core == mac_krate => {
-                if let Some(derive_completion) = DEFAULT_DERIVE_DEPENDENCIES
-                    .iter()
-                    .find(|derive_completion| derive_completion.label == name)
-                {
-                    let mut components = vec![derive_completion.label];
-                    components.extend(derive_completion.dependencies.iter().filter(
-                        |&&dependency| {
-                            !existing_derives
-                                .iter()
-                                .map(|it| it.name(ctx.db))
-                                .any(|it| it.to_smol_str() == dependency)
-                        },
-                    ));
-                    let lookup = components.join(", ");
-                    let label = Itertools::intersperse(components.into_iter().rev(), ", ");
-                    (SmolStr::from_iter(label), Some(lookup))
-                } else {
-                    (name, None)
+            let module = match resolution {
+                Some(hir::PathResolution::Def(hir::ModuleDef::Module(it))) => it,
+                _ => return,
+            };
+
+            for (name, def) in module.scope(ctx.db, ctx.module) {
+                let add_def = match def {
+                    ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac)) => {
+                        !ctx.existing_derives.contains(&mac) && mac.is_derive(ctx.db)
+                    }
+                    ScopeDef::ModuleDef(hir::ModuleDef::Module(_)) => true,
+                    _ => false,
+                };
+                if add_def {
+                    acc.add_resolution(ctx, name, def);
                 }
             }
-            _ => (name, None),
-        };
-
-        let mut item = CompletionItem::new(SymbolKind::Derive, ctx.source_range(), label);
-        if let Some(docs) = mac.docs(ctx.db) {
-            item.documentation(docs);
+            return;
         }
-        if let Some(lookup) = lookup {
-            item.lookup_by(lookup);
-        }
-        item.add_to(acc);
-    }
+        None if is_absolute_path => acc.add_crate_roots(ctx),
+        // only show modules in a fresh UseTree
+        None => {
+            ctx.process_all_names(&mut |name, def| {
+                let mac = match def {
+                    ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac))
+                        if !ctx.existing_derives.contains(&mac) && mac.is_derive(ctx.db) =>
+                    {
+                        mac
+                    }
+                    ScopeDef::ModuleDef(hir::ModuleDef::Module(_)) => {
+                        return acc.add_resolution(ctx, name, def);
+                    }
+                    _ => return,
+                };
 
-    flyimport_derive(acc, ctx);
-}
+                match (core, mac.module(ctx.db).krate()) {
+                    // show derive dependencies for `core`/`std` derives
+                    (Some(core), mac_krate) if core == mac_krate && qualifier.is_none() => {}
+                    _ => return acc.add_resolution(ctx, name, def),
+                };
 
-fn get_derives_in_scope(ctx: &CompletionContext) -> Vec<(hir::Name, Macro)> {
-    let mut result = Vec::default();
-    ctx.process_all_names(&mut |name, scope_def| {
-        if let hir::ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac)) = scope_def {
-            if mac.kind(ctx.db) == hir::MacroKind::Derive {
-                result.push((name, mac));
-            }
-        }
-    });
-    result
-}
+                let name_ = name.to_smol_str();
+                let find = DEFAULT_DERIVE_DEPENDENCIES
+                    .iter()
+                    .find(|derive_completion| derive_completion.label == name_);
 
-fn flyimport_derive(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
-    if ctx.token.kind() != SyntaxKind::IDENT {
-        return None;
-    };
-    let potential_import_name = ctx.token.to_string();
-    let module = ctx.module?;
-    let parent = ctx.token.parent()?;
-    let user_input_lowercased = potential_import_name.to_lowercase();
-    let import_assets = ImportAssets::for_fuzzy_path(
-        module,
-        None,
-        potential_import_name,
-        &ctx.sema,
-        parent.clone(),
-    )?;
-    let import_scope = ImportScope::find_insert_use_container(&parent, &ctx.sema)?;
-    acc.add_all(
-        import_assets
-            .search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind)
-            .into_iter()
-            .filter_map(|import| match import.original_item {
-                hir::ItemInNs::Macros(mac) => Some((import, mac)),
-                _ => None,
-            })
-            .filter(|&(_, mac)| mac.kind(ctx.db) == MacroKind::Derive)
-            .filter(|&(_, mac)| !ctx.is_item_hidden(&hir::ItemInNs::Macros(mac)))
-            .sorted_by_key(|(import, _)| {
-                compute_fuzzy_completion_order_key(&import.import_path, &user_input_lowercased)
-            })
-            .filter_map(|(import, mac)| {
-                let mut item = CompletionItem::new(
-                    SymbolKind::Derive,
-                    ctx.source_range(),
-                    mac.name(ctx.db).to_smol_str(),
-                );
-                item.add_import(ImportEdit { import, scope: import_scope.clone() });
-                if let Some(docs) = mac.docs(ctx.db) {
-                    item.documentation(docs);
+                match find {
+                    Some(derive_completion) => {
+                        let mut components = vec![derive_completion.label];
+                        components.extend(derive_completion.dependencies.iter().filter(
+                            |&&dependency| {
+                                !ctx.existing_derives
+                                    .iter()
+                                    .map(|it| it.name(ctx.db))
+                                    .any(|it| it.to_smol_str() == dependency)
+                            },
+                        ));
+                        let lookup = components.join(", ");
+                        let label = Itertools::intersperse(components.into_iter().rev(), ", ");
+
+                        let mut item = CompletionItem::new(
+                            SymbolKind::Derive,
+                            ctx.source_range(),
+                            SmolStr::from_iter(label),
+                        );
+                        if let Some(docs) = mac.docs(ctx.db) {
+                            item.documentation(docs);
+                        }
+                        item.lookup_by(lookup);
+                        item.add_to(acc);
+                    }
+                    None => acc.add_resolution(ctx, name, def),
                 }
-                Some(item.build())
-            }),
-    );
-    Some(())
+            });
+            acc.add_nameref_keywords(ctx);
+        }
+    }
 }
 
 struct DeriveDependencies {
diff --git a/crates/ide_completion/src/completions/flyimport.rs b/crates/ide_completion/src/completions/flyimport.rs
index b4cfc3273b..aee2bbb53c 100644
--- a/crates/ide_completion/src/completions/flyimport.rs
+++ b/crates/ide_completion/src/completions/flyimport.rs
@@ -142,7 +142,7 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext)
     )?;
 
     let ns_filter = |import: &LocatedImport| {
-        let kind = match ctx.path_kind() {
+        let path_kind = match ctx.path_kind() {
             Some(kind) => kind,
             None => {
                 return match import.original_item {
@@ -151,7 +151,7 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext)
                 }
             }
         };
-        match (kind, import.original_item) {
+        match (path_kind, import.original_item) {
             // Aren't handled in flyimport
             (PathKind::Vis { .. } | PathKind::Use, _) => false,
             // modules are always fair game
@@ -173,6 +173,11 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext)
 
             (PathKind::Attr { .. }, ItemInNs::Macros(mac)) => mac.is_attr(ctx.db),
             (PathKind::Attr { .. }, _) => false,
+
+            (PathKind::Derive, ItemInNs::Macros(mac)) => {
+                mac.is_derive(ctx.db) && !ctx.existing_derives.contains(&mac)
+            }
+            (PathKind::Derive, _) => false,
         }
     };
 
diff --git a/crates/ide_completion/src/completions/qualified_path.rs b/crates/ide_completion/src/completions/qualified_path.rs
index c4ba77b3f7..acd02616b1 100644
--- a/crates/ide_completion/src/completions/qualified_path.rs
+++ b/crates/ide_completion/src/completions/qualified_path.rs
@@ -63,7 +63,13 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
     }
 
     match kind {
-        Some(PathKind::Pat | PathKind::Attr { .. } | PathKind::Vis { .. } | PathKind::Use) => {
+        Some(
+            PathKind::Pat
+            | PathKind::Attr { .. }
+            | PathKind::Vis { .. }
+            | PathKind::Use
+            | PathKind::Derive,
+        ) => {
             return;
         }
         _ => {
@@ -415,10 +421,10 @@ macro_rules! foo { () => {} }
 
 fn main() { let _ = crate::$0 }
 "#,
-            expect![[r##"
+            expect![[r#"
                 fn main()  fn()
-                ma foo!(…) #[macro_export] macro_rules! foo
-            "##]],
+                ma foo!(…) macro_rules! foo
+            "#]],
         );
     }
 
diff --git a/crates/ide_completion/src/completions/unqualified_path.rs b/crates/ide_completion/src/completions/unqualified_path.rs
index ddd068488a..235d7870c7 100644
--- a/crates/ide_completion/src/completions/unqualified_path.rs
+++ b/crates/ide_completion/src/completions/unqualified_path.rs
@@ -19,10 +19,11 @@ pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC
         Some(PathCompletionCtx {
             kind:
                 Some(
-                    PathKind::Vis { .. }
-                    | PathKind::Attr { .. }
+                    PathKind::Attr { .. }
+                    | PathKind::Derive
+                    | PathKind::Pat
                     | PathKind::Use { .. }
-                    | PathKind::Pat,
+                    | PathKind::Vis { .. },
                 ),
             ..
         }) => return,
@@ -207,12 +208,12 @@ mod macros {
     macro_rules! concat { }
 }
 "#,
-            expect![[r##"
+            expect![[r#"
                 fn f()        fn()
-                ma concat!(…) #[macro_export] macro_rules! concat
+                ma concat!(…) macro_rules! concat
                 md std
                 bt u32
-            "##]],
+            "#]],
         );
     }
 
diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs
index da80224dd8..51bbd66ff3 100644
--- a/crates/ide_completion/src/context.rs
+++ b/crates/ide_completion/src/context.rs
@@ -12,6 +12,7 @@ use ide_db::{
     famous_defs::FamousDefs,
     RootDatabase,
 };
+use rustc_hash::FxHashSet;
 use syntax::{
     algo::{find_node_at_offset, non_trivia_sibling},
     ast::{self, AttrKind, HasName, NameOrNameRef},
@@ -43,11 +44,12 @@ pub(crate) enum Visible {
     No,
 }
 
-#[derive(Copy, Clone, Debug)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
 pub(super) enum PathKind {
     Expr,
     Type,
     Attr { kind: AttrKind, annotated_item_kind: Option<SyntaxKind> },
+    Derive,
     Mac,
     Pat,
     Vis { has_in_token: bool },
@@ -126,7 +128,6 @@ pub(crate) struct CompletionContext<'a> {
 
     /// The parent function of the cursor position if it exists.
     pub(super) function_def: Option<ast::Fn>,
-    pub(super) attr: Option<ast::Attr>,
     /// The parent impl of the cursor position if it exists.
     pub(super) impl_def: Option<ast::Impl>,
     /// The NameLike under the cursor in the original file if it exists.
@@ -142,6 +143,8 @@ pub(crate) struct CompletionContext<'a> {
     pub(super) pattern_ctx: Option<PatternContext>,
     pub(super) path_context: Option<PathCompletionCtx>,
 
+    pub(super) existing_derives: FxHashSet<hir::Macro>,
+
     pub(super) locals: Vec<(Name, Local)>,
 
     no_completion_required: bool,
@@ -439,7 +442,6 @@ impl<'a> CompletionContext<'a> {
             expected_name: None,
             expected_type: None,
             function_def: None,
-            attr: None,
             impl_def: None,
             name_syntax: None,
             lifetime_ctx: None,
@@ -452,6 +454,7 @@ impl<'a> CompletionContext<'a> {
             locals,
             incomplete_let: false,
             no_completion_required: false,
+            existing_derives: Default::default(),
         };
         ctx.expand_and_fill(
             original_file.syntax().clone(),
@@ -472,6 +475,8 @@ impl<'a> CompletionContext<'a> {
         mut fake_ident_token: SyntaxToken,
     ) {
         let _p = profile::span("CompletionContext::expand_and_fill");
+        let mut derive_ctx = None;
+
         'expansion: loop {
             let parent_item =
                 |item: &ast::Item| item.syntax().ancestors().skip(1).find_map(ast::Item::cast);
@@ -509,11 +514,45 @@ impl<'a> CompletionContext<'a> {
                     _ => break 'expansion,
                 }
             }
+            let orig_tt = match find_node_at_offset::<ast::TokenTree>(&original_file, offset) {
+                Some(it) => it,
+                None => break,
+            };
+            let spec_tt = match find_node_at_offset::<ast::TokenTree>(&speculative_file, offset) {
+                Some(it) => it,
+                None => break,
+            };
+
+            // Expand pseudo-derive expansion
+            if let (Some(orig_attr), Some(spec_attr)) = (
+                orig_tt.syntax().parent().and_then(ast::Meta::cast).and_then(|it| it.parent_attr()),
+                spec_tt.syntax().parent().and_then(ast::Meta::cast).and_then(|it| it.parent_attr()),
+            ) {
+                match (
+                    self.sema.expand_derive_as_pseudo_attr_macro(&orig_attr),
+                    self.sema.speculative_expand_derive_as_pseudo_attr_macro(
+                        &orig_attr,
+                        &spec_attr,
+                        fake_ident_token.clone(),
+                    ),
+                ) {
+                    // Clearly not a derive macro
+                    (None, None) => (),
+                    // successful expansions
+                    (Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) => {
+                        let new_offset = fake_mapped_token.text_range().start();
+                        derive_ctx = Some((actual_expansion, fake_expansion, new_offset));
+                        break 'expansion;
+                    }
+                    // exactly one expansion failed, inconsistent state so stop expanding completely
+                    _ => break 'expansion,
+                }
+            }
 
             // Expand fn-like macro calls
             if let (Some(actual_macro_call), Some(macro_call_with_fake_ident)) = (
-                find_node_at_offset::<ast::MacroCall>(&original_file, offset),
-                find_node_at_offset::<ast::MacroCall>(&speculative_file, offset),
+                orig_tt.syntax().ancestors().find_map(ast::MacroCall::cast),
+                spec_tt.syntax().ancestors().find_map(ast::MacroCall::cast),
             ) {
                 let mac_call_path0 = actual_macro_call.path().as_ref().map(|s| s.syntax().text());
                 let mac_call_path1 =
@@ -553,7 +592,7 @@ impl<'a> CompletionContext<'a> {
             break;
         }
 
-        self.fill(&original_file, speculative_file, offset);
+        self.fill(&original_file, speculative_file, offset, derive_ctx);
     }
 
     fn expected_type_and_name(&self) -> (Option<Type>, Option<NameOrNameRef>) {
@@ -697,6 +736,7 @@ impl<'a> CompletionContext<'a> {
         original_file: &SyntaxNode,
         file_with_fake_ident: SyntaxNode,
         offset: TextSize,
+        derive_ctx: Option<(SyntaxNode, SyntaxNode, TextSize)>,
     ) {
         let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap();
         let syntax_element = NodeOrToken::Token(fake_ident_token);
@@ -708,11 +748,6 @@ impl<'a> CompletionContext<'a> {
             (fn_is_prev && !inside_impl_trait_block) || for_is_prev2
         };
 
-        self.attr = self
-            .sema
-            .token_ancestors_with_macros(self.token.clone())
-            .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
-            .find_map(ast::Attr::cast);
         self.fake_attribute_under_caret = syntax_element.ancestors().find_map(ast::Attr::cast);
 
         self.incomplete_let =
@@ -724,6 +759,33 @@ impl<'a> CompletionContext<'a> {
         self.expected_type = expected_type;
         self.expected_name = expected_name;
 
+        // Overwrite the path kind for derives
+        if let Some((original_file, file_with_fake_ident, offset)) = derive_ctx {
+            let attr = self
+                .sema
+                .token_ancestors_with_macros(self.token.clone())
+                .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
+                .find_map(ast::Attr::cast);
+            if let Some(attr) = &attr {
+                self.existing_derives =
+                    self.sema.resolve_derive_macro(attr).into_iter().flatten().flatten().collect();
+            }
+
+            if let Some(ast::NameLike::NameRef(name_ref)) =
+                find_node_at_offset(&file_with_fake_ident, offset)
+            {
+                self.name_syntax =
+                    find_node_at_offset(&original_file, name_ref.syntax().text_range().start());
+                if let Some((path_ctx, _)) =
+                    Self::classify_name_ref(&self.sema, &original_file, name_ref)
+                {
+                    self.path_context =
+                        Some(PathCompletionCtx { kind: Some(PathKind::Derive), ..path_ctx });
+                }
+            }
+            return;
+        }
+
         let name_like = match find_node_at_offset(&file_with_fake_ident, offset) {
             Some(it) => it,
             None => return,
@@ -743,6 +805,7 @@ impl<'a> CompletionContext<'a> {
             .token_ancestors_with_macros(self.token.clone())
             .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
             .find_map(ast::Fn::cast);
+
         match name_like {
             ast::NameLike::Lifetime(lifetime) => {
                 self.lifetime_ctx =
diff --git a/crates/ide_completion/src/lib.rs b/crates/ide_completion/src/lib.rs
index 3225a0bc9f..86a6947b1d 100644
--- a/crates/ide_completion/src/lib.rs
+++ b/crates/ide_completion/src/lib.rs
@@ -152,6 +152,7 @@ pub fn completions(
 
     let mut acc = Completions::default();
     completions::attribute::complete_attribute(&mut acc, &ctx);
+    completions::attribute::complete_derive(&mut acc, &ctx);
     completions::attribute::complete_known_attribute_input(&mut acc, &ctx);
     completions::dot::complete_dot(&mut acc, &ctx);
     completions::extern_abi::complete_extern_abi(&mut acc, &ctx);
diff --git a/crates/ide_completion/src/render/macro_.rs b/crates/ide_completion/src/render/macro_.rs
index 6fdb622be7..d3b0de429c 100644
--- a/crates/ide_completion/src/render/macro_.rs
+++ b/crates/ide_completion/src/render/macro_.rs
@@ -1,12 +1,8 @@
 //! Renderer for macro invocations.
 
-use either::Either;
-use hir::{Documentation, HasSource, InFile, Semantics};
-use ide_db::{RootDatabase, SymbolKind};
-use syntax::{
-    display::{fn_as_proc_macro_label, macro_label},
-    SmolStr,
-};
+use hir::{Documentation, HirDisplay};
+use ide_db::SymbolKind;
+use syntax::SmolStr;
 
 use crate::{
     context::PathKind,
@@ -52,7 +48,7 @@ fn render(
         label(&ctx, needs_bang, bra, ket, &name),
     );
     item.set_deprecated(ctx.is_deprecated(macro_))
-        .set_detail(detail(&completion.sema, macro_))
+        .detail(macro_.display(completion.db).to_string())
         .set_documentation(docs)
         .set_relevance(ctx.completion_relevance());
 
@@ -103,17 +99,6 @@ fn banged_name(name: &str) -> SmolStr {
     SmolStr::from_iter([name, "!"])
 }
 
-fn detail(sema: &Semantics<RootDatabase>, macro_: hir::Macro) -> Option<String> {
-    // FIXME: This is parsing the file!
-    let InFile { file_id, value } = macro_.source(sema.db)?;
-    let _ = sema.parse_or_expand(file_id);
-    let detail = match value {
-        Either::Left(node) => macro_label(&node),
-        Either::Right(node) => fn_as_proc_macro_label(&node),
-    };
-    Some(detail)
-}
-
 fn guess_macro_braces(macro_name: &str, docs: &str) -> (&'static str, &'static str) {
     let mut votes = [0, 0, 0];
     for (idx, s) in docs.match_indices(&macro_name) {
diff --git a/crates/ide_db/src/syntax_helpers/node_ext.rs b/crates/ide_db/src/syntax_helpers/node_ext.rs
index 115d83c6e2..c0f0529966 100644
--- a/crates/ide_db/src/syntax_helpers/node_ext.rs
+++ b/crates/ide_db/src/syntax_helpers/node_ext.rs
@@ -443,7 +443,7 @@ pub fn parse_tt_as_comma_sep_paths(input: ast::TokenTree) -> Option<Vec<ast::Pat
             None => None,
             Some(tok) => Some(tok),
         });
-    let input_expressions = tokens.into_iter().group_by(|tok| tok.kind() == T![,]);
+    let input_expressions = tokens.group_by(|tok| tok.kind() == T![,]);
     let paths = input_expressions
         .into_iter()
         .filter_map(|(is_sep, group)| (!is_sep).then(|| group))
diff --git a/crates/parser/src/grammar/items.rs b/crates/parser/src/grammar/items.rs
index 7bfd9ef8c8..36d13cc977 100644
--- a/crates/parser/src/grammar/items.rs
+++ b/crates/parser/src/grammar/items.rs
@@ -290,17 +290,12 @@ fn type_alias(p: &mut Parser, m: Marker) {
         generic_params::bounds(p);
     }
 
-    // test type_item_where_clause_deprecated
+    // test type_item_where_clause
     // type Foo where Foo: Copy = ();
     generic_params::opt_where_clause(p);
     if p.eat(T![=]) {
         types::type_(p);
     }
-
-    // test type_item_where_clause
-    // type Foo = () where Foo: Copy;
-    generic_params::opt_where_clause(p);
-
     p.expect(T![;]);
     m.complete(p, TYPE_ALIAS);
 }
diff --git a/crates/syntax/src/display.rs b/crates/syntax/src/display.rs
index d03e94d058..f7322656a3 100644
--- a/crates/syntax/src/display.rs
+++ b/crates/syntax/src/display.rs
@@ -1,6 +1,6 @@
 //! This module contains utilities for rendering syntax nodes into a string representing their signature.
 
-use crate::ast::{self, HasAttrs, HasGenericParams, HasName};
+use crate::ast::{self, HasGenericParams, HasName};
 
 use ast::HasVisibility;
 use stdx::format_to;
@@ -49,37 +49,3 @@ pub fn function_declaration(node: &ast::Fn) -> String {
     }
     buf
 }
-
-pub fn macro_label(node: &ast::Macro) -> String {
-    let name = node.name();
-    let mut s = String::new();
-    match node {
-        ast::Macro::MacroRules(node) => {
-            let vis = if node.has_atom_attr("macro_export") { "#[macro_export] " } else { "" };
-            format_to!(s, "{}macro_rules!", vis);
-        }
-        ast::Macro::MacroDef(node) => {
-            if let Some(vis) = node.visibility() {
-                format_to!(s, "{} ", vis);
-            }
-            format_to!(s, "macro");
-        }
-    }
-    if let Some(name) = name {
-        format_to!(s, " {}", name);
-    }
-    s
-}
-
-pub fn fn_as_proc_macro_label(node: &ast::Fn) -> String {
-    let name = node.name();
-    let mut s = String::new();
-    if let Some(vis) = node.visibility() {
-        format_to!(s, "{} ", vis);
-    }
-    format_to!(s, "macro");
-    if let Some(name) = name {
-        format_to!(s, " {}", name);
-    }
-    s
-}


Test patch:
diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs
index 2ec7802394..df27f935c8 100644
--- a/crates/ide/src/hover/tests.rs
+++ b/crates/ide/src/hover/tests.rs
@@ -4102,16 +4102,16 @@ identity!{
 }
 "#,
         expect![[r#"
-                *Copy*
+            *Copy*
 
-                ```rust
-                test
-                ```
+            ```rust
+            test
+            ```
 
-                ```rust
-                pub macro Copy
-                ```
-            "#]],
+            ```rust
+            macro Copy
+            ```
+        "#]],
     );
 }
 
@@ -4126,16 +4126,16 @@ pub macro Copy {}
 struct Foo;
 "#,
         expect![[r#"
-                *Copy*
+            *Copy*
 
-                ```rust
-                test
-                ```
+            ```rust
+            test
+            ```
 
-                ```rust
-                pub macro Copy
-                ```
-            "#]],
+            ```rust
+            macro Copy
+            ```
+        "#]],
     );
     check(
         r#"
@@ -4148,16 +4148,16 @@ mod foo {
 struct Foo;
 "#,
         expect![[r#"
-                *Copy*
+            *Copy*
 
-                ```rust
-                test::foo
-                ```
+            ```rust
+            test::foo
+            ```
 
-                ```rust
-                pub macro Copy
-                ```
-            "#]],
+            ```rust
+            macro Copy
+            ```
+        "#]],
     );
 }
 
diff --git a/crates/ide_completion/src/tests/attribute.rs b/crates/ide_completion/src/tests/attribute.rs
index ae7ba7e055..4ee95e8928 100644
--- a/crates/ide_completion/src/tests/attribute.rs
+++ b/crates/ide_completion/src/tests/attribute.rs
@@ -62,7 +62,7 @@ fn proc_macros_qualified() {
 struct Foo;
 "#,
         expect![[r#"
-            at identity pub macro identity
+            at identity proc_macro identity
         "#]],
     )
 }
@@ -302,7 +302,7 @@ struct Foo;
 "#,
         expect![[r#"
             md core
-            at derive           pub macro derive
+            at derive           macro derive
             kw self::
             kw super::
             kw crate::
@@ -688,13 +688,17 @@ mod derive {
 #[derive($0)] struct Test;
 "#,
             expect![[r#"
-                de Default
+                md core
+                de Default                macro Default
                 de Clone, Copy
-                de PartialEq
+                de PartialEq              macro PartialEq
                 de PartialEq, Eq
                 de PartialEq, Eq, PartialOrd, Ord
-                de Clone
+                de Clone                  macro Clone
                 de PartialEq, PartialOrd
+                kw self::
+                kw super::
+                kw crate::
             "#]],
         );
     }
@@ -707,12 +711,16 @@ mod derive {
 #[derive(serde::Serialize, PartialEq, $0)] struct Test;
 "#,
             expect![[r#"
-                de Default
+                md core
+                de Default             macro Default
                 de Clone, Copy
                 de Eq
                 de Eq, PartialOrd, Ord
-                de Clone
+                de Clone               macro Clone
                 de PartialOrd
+                kw self::
+                kw super::
+                kw crate::
             "#]],
         )
     }
@@ -725,33 +733,17 @@ mod derive {
 #[derive($0 serde::Serialize, PartialEq)] struct Test;
 "#,
             expect![[r#"
-                de Default
+                md core
+                de Default             macro Default
                 de Clone, Copy
                 de Eq
                 de Eq, PartialOrd, Ord
-                de Clone
+                de Clone               macro Clone
                 de PartialOrd
+                kw self::
+                kw super::
+                kw crate::
             "#]],
-        )
-    }
-
-    #[test]
-    fn derive_no_attrs() {
-        check_derive(
-            r#"
-//- proc_macros: identity
-//- minicore: derive
-#[derive($0)] struct Test;
-"#,
-            expect![[r#""#]],
-        );
-        check_derive(
-            r#"
-//- proc_macros: identity
-//- minicore: derive
-#[derive(i$0)] struct Test;
-"#,
-            expect![[r#""#]],
         );
     }
 
@@ -760,20 +752,32 @@ mod derive {
         check_derive(
             r#"
 //- proc_macros: derive_identity
+//- minicore: derive
 #[derive(der$0)] struct Test;
 "#,
             expect![[r#"
-                de DeriveIdentity (use proc_macros::DeriveIdentity)
+                md proc_macros
+                md core
+                kw self::
+                kw super::
+                kw crate::
+                de DeriveIdentity (use proc_macros::DeriveIdentity) proc_macro DeriveIdentity
             "#]],
         );
         check_derive(
             r#"
 //- proc_macros: derive_identity
+//- minicore: derive
 use proc_macros::DeriveIdentity;
 #[derive(der$0)] struct Test;
 "#,
             expect![[r#"
-                de DeriveIdentity
+                de DeriveIdentity proc_macro DeriveIdentity
+                md proc_macros
+                md core
+                kw self::
+                kw super::
+                kw crate::
             "#]],
         );
     }
@@ -784,6 +788,7 @@ use proc_macros::DeriveIdentity;
             "DeriveIdentity",
             r#"
 //- proc_macros: derive_identity
+//- minicore: derive
 #[derive(der$0)] struct Test;
 "#,
             r#"
@@ -793,6 +798,30 @@ use proc_macros::DeriveIdentity;
 "#,
         );
     }
+
+    #[test]
+    fn qualified() {
+        check_derive(
+            r#"
+//- proc_macros: derive_identity
+//- minicore: derive, copy, clone
+#[derive(proc_macros::$0)] struct Test;
+"#,
+            expect![[r#"
+                de DeriveIdentity proc_macro DeriveIdentity
+            "#]],
+        );
+        check_derive(
+            r#"
+//- proc_macros: derive_identity
+//- minicore: derive, copy, clone
+#[derive(proc_macros::C$0)] struct Test;
+"#,
+            expect![[r#"
+                de DeriveIdentity proc_macro DeriveIdentity
+            "#]],
+        );
+    }
 }
 
 mod lint {
diff --git a/crates/ide_completion/src/tests/expression.rs b/crates/ide_completion/src/tests/expression.rs
index 5e1fae68fd..a841605e49 100644
--- a/crates/ide_completion/src/tests/expression.rs
+++ b/crates/ide_completion/src/tests/expression.rs
@@ -30,7 +30,7 @@ fn baz() {
 }
             "#,
         // This should not contain `FooDesc {…}`.
-        expect![[r##"
+        expect![[r#"
             kw unsafe
             kw match
             kw while
@@ -57,13 +57,13 @@ fn baz() {
             fn baz()         fn()
             st Unit
             md _69latrick
-            ma makro!(…)     #[macro_export] macro_rules! makro
+            ma makro!(…)     macro_rules! makro
             fn function()    fn()
             sc STATIC
             un Union
             ev TupleV(…)     (u32)
             ct CONST
-        "##]],
+        "#]],
     )
 }
 
@@ -125,7 +125,7 @@ impl Unit {
 }
 "#,
         // `self` is in here twice, once as the module, once as the local
-        expect![[r##"
+        expect![[r#"
             me self.foo()   fn(self)
             kw unsafe
             kw fn
@@ -166,14 +166,14 @@ impl Unit {
             md module
             st Unit
             md qualified
-            ma makro!(…)    #[macro_export] macro_rules! makro
+            ma makro!(…)    macro_rules! makro
             ?? Unresolved
             fn function()   fn()
             sc STATIC
             un Union
             ev TupleV(…)    (u32)
             ct CONST
-        "##]],
+        "#]],
     );
     check(
         r#"
@@ -187,7 +187,7 @@ impl Unit {
     }
 }
 "#,
-        expect![[r##"
+        expect![[r#"
             tt Trait
             en Enum
             st Record
@@ -195,14 +195,14 @@ impl Unit {
             md module
             st Unit
             md qualified
-            ma makro!(…)  #[macro_export] macro_rules! makro
+            ma makro!(…)  macro_rules! makro
             ?? Unresolved
             fn function() fn()
             sc STATIC
             un Union
             ev TupleV(…)  (u32)
             ct CONST
-        "##]],
+        "#]],
     );
 }
 
diff --git a/crates/ide_completion/src/tests/flyimport.rs b/crates/ide_completion/src/tests/flyimport.rs
index fbef6d9937..c996a5f01f 100644
--- a/crates/ide_completion/src/tests/flyimport.rs
+++ b/crates/ide_completion/src/tests/flyimport.rs
@@ -1108,7 +1108,7 @@ fn flyimport_attribute() {
 struct Foo;
 "#,
         expect![[r#"
-            at identity (use proc_macros::identity) pub macro identity
+            at identity (use proc_macros::identity) proc_macro identity
         "#]],
     );
     check_edit(
diff --git a/crates/ide_completion/src/tests/item.rs b/crates/ide_completion/src/tests/item.rs
index d94fab2f5f..1d5ddc092e 100644
--- a/crates/ide_completion/src/tests/item.rs
+++ b/crates/ide_completion/src/tests/item.rs
@@ -17,7 +17,7 @@ fn target_type_or_trait_in_impl_block() {
         r#"
 impl Tra$0
 "#,
-        expect![[r##"
+        expect![[r#"
             kw self
             kw super
             kw crate
@@ -27,10 +27,10 @@ impl Tra$0
             st Tuple
             md module
             st Unit
-            ma makro!(…) #[macro_export] macro_rules! makro
+            ma makro!(…) macro_rules! makro
             un Union
             bt u32
-        "##]],
+        "#]],
     )
 }
 
@@ -40,7 +40,7 @@ fn target_type_in_trait_impl_block() {
         r#"
 impl Trait for Str$0
 "#,
-        expect![[r##"
+        expect![[r#"
             kw self
             kw super
             kw crate
@@ -50,10 +50,10 @@ impl Trait for Str$0
             st Tuple
             md module
             st Unit
-            ma makro!(…) #[macro_export] macro_rules! makro
+            ma makro!(…) macro_rules! makro
             un Union
             bt u32
-        "##]],
+        "#]],
     )
 }
 
@@ -85,7 +85,7 @@ fn after_struct_name() {
     // FIXME: This should emit `kw where` only
     check(
         r"struct Struct $0",
-        expect![[r##"
+        expect![[r#"
             kw pub(crate)
             kw pub(super)
             kw pub
@@ -109,8 +109,8 @@ fn after_struct_name() {
             kw super
             kw crate
             md module
-            ma makro!(…)           #[macro_export] macro_rules! makro
-        "##]],
+            ma makro!(…)           macro_rules! makro
+        "#]],
     );
 }
 
@@ -119,7 +119,7 @@ fn after_fn_name() {
     // FIXME: This should emit `kw where` only
     check(
         r"fn func() $0",
-        expect![[r##"
+        expect![[r#"
             kw pub(crate)
             kw pub(super)
             kw pub
@@ -143,8 +143,8 @@ fn after_fn_name() {
             kw super
             kw crate
             md module
-            ma makro!(…)           #[macro_export] macro_rules! makro
-        "##]],
+            ma makro!(…)           macro_rules! makro
+        "#]],
     );
 }
 
diff --git a/crates/ide_completion/src/tests/item_list.rs b/crates/ide_completion/src/tests/item_list.rs
index 4c76963054..82824fd393 100644
--- a/crates/ide_completion/src/tests/item_list.rs
+++ b/crates/ide_completion/src/tests/item_list.rs
@@ -12,7 +12,7 @@ fn check(ra_fixture: &str, expect: Expect) {
 fn in_mod_item_list() {
     check(
         r#"mod tests { $0 }"#,
-        expect![[r##"
+        expect![[r#"
             kw pub(crate)
             kw pub(super)
             kw pub
@@ -35,8 +35,8 @@ fn in_mod_item_list() {
             kw self
             kw super
             kw crate
-            ma makro!(…)           #[macro_export] macro_rules! makro
-        "##]],
+            ma makro!(…)           macro_rules! makro
+        "#]],
     )
 }
 
@@ -44,7 +44,7 @@ fn in_mod_item_list() {
 fn in_source_file_item_list() {
     check(
         r#"$0"#,
-        expect![[r##"
+        expect![[r#"
             kw pub(crate)
             kw pub(super)
             kw pub
@@ -68,8 +68,8 @@ fn in_source_file_item_list() {
             kw super
             kw crate
             md module
-            ma makro!(…)           #[macro_export] macro_rules! makro
-        "##]],
+            ma makro!(…)           macro_rules! makro
+        "#]],
     )
 }
 
@@ -106,10 +106,10 @@ fn in_qualified_path() {
     cov_mark::check!(no_keyword_completion_in_non_trivial_path);
     check(
         r#"crate::$0"#,
-        expect![[r##"
+        expect![[r#"
             md module
-            ma makro!(…) #[macro_export] macro_rules! makro
-        "##]],
+            ma makro!(…) macro_rules! makro
+        "#]],
     )
 }
 
@@ -162,7 +162,7 @@ fn after_visibility_unsafe() {
 fn in_impl_assoc_item_list() {
     check(
         r#"impl Struct { $0 }"#,
-        expect![[r##"
+        expect![[r#"
             kw pub(crate)
             kw pub(super)
             kw pub
@@ -174,8 +174,8 @@ fn in_impl_assoc_item_list() {
             kw super
             kw crate
             md module
-            ma makro!(…)  #[macro_export] macro_rules! makro
-        "##]],
+            ma makro!(…)  macro_rules! makro
+        "#]],
     )
 }
 
@@ -199,7 +199,7 @@ fn in_impl_assoc_item_list_after_attr() {
 fn in_trait_assoc_item_list() {
     check(
         r"trait Foo { $0 }",
-        expect![[r##"
+        expect![[r#"
             kw unsafe
             kw fn
             kw const
@@ -208,8 +208,8 @@ fn in_trait_assoc_item_list() {
             kw super
             kw crate
             md module
-            ma makro!(…) #[macro_export] macro_rules! makro
-        "##]],
+            ma makro!(…) macro_rules! makro
+        "#]],
     );
 }
 
@@ -233,7 +233,7 @@ impl Test for () {
     $0
 }
 "#,
-        expect![[r##"
+        expect![[r#"
             kw pub(crate)
             kw pub(super)
             kw pub
@@ -245,7 +245,7 @@ impl Test for () {
             kw super
             kw crate
             md module
-            ma makro!(…)  #[macro_export] macro_rules! makro
-        "##]],
+            ma makro!(…)  macro_rules! makro
+        "#]],
     );
 }
diff --git a/crates/ide_completion/src/tests/pattern.rs b/crates/ide_completion/src/tests/pattern.rs
index fe53257672..0ca20f93b5 100644
--- a/crates/ide_completion/src/tests/pattern.rs
+++ b/crates/ide_completion/src/tests/pattern.rs
@@ -102,7 +102,7 @@ fn foo() {
     if let a$0
 }
 "#,
-        expect![[r##"
+        expect![[r#"
             kw ref
             kw mut
             en Enum
@@ -112,11 +112,11 @@ fn foo() {
             st Tuple
             md module
             st Unit
-            ma makro!(…) #[macro_export] macro_rules! makro
+            ma makro!(…) macro_rules! makro
             bn TupleV    TupleV($1)$0
             ev TupleV
             ct CONST
-        "##]],
+        "#]],
     );
 }
 
@@ -132,7 +132,7 @@ fn foo() {
    let a$0
 }
 "#,
-        expect![[r##"
+        expect![[r#"
             kw ref
             kw mut
             bn Record            Record { field$1 }$0
@@ -142,8 +142,8 @@ fn foo() {
             ev Variant
             en SingleVariantEnum
             st Unit
-            ma makro!(…)         #[macro_export] macro_rules! makro
-        "##]],
+            ma makro!(…)         macro_rules! makro
+        "#]],
     );
 }
 
@@ -154,7 +154,7 @@ fn in_param() {
 fn foo(a$0) {
 }
 "#,
-        expect![[r##"
+        expect![[r#"
             kw ref
             kw mut
             bn Record    Record { field$1 }: Record$0
@@ -162,15 +162,15 @@ fn foo(a$0) {
             bn Tuple     Tuple($1): Tuple$0
             st Tuple
             st Unit
-            ma makro!(…) #[macro_export] macro_rules! makro
-        "##]],
+            ma makro!(…) macro_rules! makro
+        "#]],
     );
     check(
         r#"
 fn foo(a$0: Tuple) {
 }
 "#,
-        expect![[r##"
+        expect![[r#"
             kw ref
             kw mut
             bn Record    Record { field$1 }$0
@@ -178,8 +178,8 @@ fn foo(a$0: Tuple) {
             bn Tuple     Tuple($1)$0
             st Tuple
             st Unit
-            ma makro!(…) #[macro_export] macro_rules! makro
-        "##]],
+            ma makro!(…) macro_rules! makro
+        "#]],
     );
 }
 
diff --git a/crates/ide_completion/src/tests/predicate.rs b/crates/ide_completion/src/tests/predicate.rs
index 163080307d..5e975d715f 100644
--- a/crates/ide_completion/src/tests/predicate.rs
+++ b/crates/ide_completion/src/tests/predicate.rs
@@ -15,7 +15,7 @@ fn predicate_start() {
         r#"
 struct Foo<'lt, T, const C: usize> where $0 {}
 "#,
-        expect![[r##"
+        expect![[r#"
             kw self
             kw super
             kw crate
@@ -26,10 +26,10 @@ struct Foo<'lt, T, const C: usize> where $0 {}
             md module
             st Foo<…>
             st Unit
-            ma makro!(…) #[macro_export] macro_rules! makro
+            ma makro!(…) macro_rules! makro
             un Union
             bt u32
-        "##]],
+        "#]],
     );
 }
 
@@ -39,14 +39,14 @@ fn bound_for_type_pred() {
         r#"
 struct Foo<'lt, T, const C: usize> where T: $0 {}
 "#,
-        expect![[r##"
+        expect![[r#"
             kw self
             kw super
             kw crate
             tt Trait
             md module
-            ma makro!(…) #[macro_export] macro_rules! makro
-        "##]],
+            ma makro!(…) macro_rules! makro
+        "#]],
     );
 }
 
@@ -58,14 +58,14 @@ fn bound_for_lifetime_pred() {
         r#"
 struct Foo<'lt, T, const C: usize> where 'lt: $0 {}
 "#,
-        expect![[r##"
+        expect![[r#"
             kw self
             kw super
             kw crate
             tt Trait
             md module
-            ma makro!(…) #[macro_export] macro_rules! makro
-        "##]],
+            ma makro!(…) macro_rules! makro
+        "#]],
     );
 }
 
@@ -75,14 +75,14 @@ fn bound_for_for_pred() {
         r#"
 struct Foo<'lt, T, const C: usize> where for<'a> T: $0 {}
 "#,
-        expect![[r##"
+        expect![[r#"
             kw self
             kw super
             kw crate
             tt Trait
             md module
-            ma makro!(…) #[macro_export] macro_rules! makro
-        "##]],
+            ma makro!(…) macro_rules! makro
+        "#]],
     );
 }
 
@@ -92,7 +92,7 @@ fn param_list_for_for_pred() {
         r#"
 struct Foo<'lt, T, const C: usize> where for<'a> $0 {}
 "#,
-        expect![[r##"
+        expect![[r#"
             kw self
             kw super
             kw crate
@@ -103,10 +103,10 @@ struct Foo<'lt, T, const C: usize> where for<'a> $0 {}
             md module
             st Foo<…>
             st Unit
-            ma makro!(…) #[macro_export] macro_rules! makro
+            ma makro!(…) macro_rules! makro
             un Union
             bt u32
-        "##]],
+        "#]],
     );
 }
 
@@ -118,7 +118,7 @@ impl Record {
     fn method(self) where $0 {}
 }
 "#,
-        expect![[r##"
+        expect![[r#"
             kw self
             kw super
             kw crate
@@ -129,9 +129,9 @@ impl Record {
             st Tuple
             md module
             st Unit
-            ma makro!(…) #[macro_export] macro_rules! makro
+            ma makro!(…) macro_rules! makro
             un Union
             bt u32
-        "##]],
+        "#]],
     );
 }
diff --git a/crates/ide_completion/src/tests/type_pos.rs b/crates/ide_completion/src/tests/type_pos.rs
index d6c1a787ff..c8260f6e23 100644
--- a/crates/ide_completion/src/tests/type_pos.rs
+++ b/crates/ide_completion/src/tests/type_pos.rs
@@ -16,7 +16,7 @@ struct Foo<'lt, T, const C: usize> {
     f: $0
 }
 "#,
-        expect![[r##"
+        expect![[r#"
             kw self
             kw super
             kw crate
@@ -29,10 +29,10 @@ struct Foo<'lt, T, const C: usize> {
             md module
             st Foo<…>
             st Unit
-            ma makro!(…) #[macro_export] macro_rules! makro
+            ma makro!(…) macro_rules! makro
             un Union
             bt u32
-        "##]],
+        "#]],
     )
 }
 
@@ -42,7 +42,7 @@ fn tuple_struct_field() {
         r#"
 struct Foo<'lt, T, const C: usize>(f$0);
 "#,
-        expect![[r##"
+        expect![[r#"
             kw pub(crate)
             kw pub(super)
             kw pub
@@ -58,10 +58,10 @@ struct Foo<'lt, T, const C: usize>(f$0);
             md module
             st Foo<…>
             st Unit
-            ma makro!(…)  #[macro_export] macro_rules! makro
+            ma makro!(…)  macro_rules! makro
             un Union
             bt u32
-        "##]],
+        "#]],
     )
 }
 
@@ -71,7 +71,7 @@ fn fn_return_type() {
         r#"
 fn x<'lt, T, const C: usize>() -> $0
 "#,
-        expect![[r##"
+        expect![[r#"
             kw self
             kw super
             kw crate
@@ -82,10 +82,10 @@ fn x<'lt, T, const C: usize>() -> $0
             st Tuple
             md module
             st Unit
-            ma makro!(…) #[macro_export] macro_rules! makro
+            ma makro!(…) macro_rules! makro
             un Union
             bt u32
-        "##]],
+        "#]],
     );
 }
 
@@ -98,7 +98,7 @@ fn foo<'lt, T, const C: usize>() {
     let _: $0;
 }
 "#,
-        expect![[r##"
+        expect![[r#"
             kw self
             kw super
             kw crate
@@ -109,10 +109,10 @@ fn foo<'lt, T, const C: usize>() {
             st Tuple
             md module
             st Unit
-            ma makro!(…) #[macro_export] macro_rules! makro
+            ma makro!(…) macro_rules! makro
             un Union
             bt u32
-        "##]],
+        "#]],
     );
     check(
         r#"
@@ -121,16 +121,16 @@ fn foo<'lt, T, const C: usize>() {
     let _: self::$0;
 }
 "#,
-        expect![[r##"
+        expect![[r#"
             tt Trait
             en Enum
             st Record
             st Tuple
             md module
             st Unit
-            ma makro!(…) #[macro_export] macro_rules! makro
+            ma makro!(…) macro_rules! makro
             un Union
-        "##]],
+        "#]],
     );
 }
 
@@ -144,7 +144,7 @@ trait Trait2 {
 
 fn foo<'lt, T: Trait2<$0>, const CONST_PARAM: usize>(_: T) {}
 "#,
-        expect![[r##"
+        expect![[r#"
             kw self
             kw super
             kw crate
@@ -157,12 +157,12 @@ fn foo<'lt, T: Trait2<$0>, const CONST_PARAM: usize>(_: T) {}
             st Tuple
             md module
             st Unit
-            ma makro!(…)          #[macro_export] macro_rules! makro
+            ma makro!(…)          macro_rules! makro
             tt Trait2
             un Union
             ct CONST
             bt u32
-        "##]],
+        "#]],
     );
     check(
         r#"
@@ -172,18 +172,18 @@ trait Trait2 {
 
 fn foo<'lt, T: Trait2<self::$0>, const CONST_PARAM: usize>(_: T) {}
     "#,
-        expect![[r##"
+        expect![[r#"
             tt Trait
             en Enum
             st Record
             st Tuple
             md module
             st Unit
-            ma makro!(…) #[macro_export] macro_rules! makro
+            ma makro!(…) macro_rules! makro
             tt Trait2
             un Union
             ct CONST
-        "##]],
+        "#]],
     );
 }
 
diff --git a/crates/parser/test_data/parser/inline/ok/0012_type_item_where_clause.rast b/crates/parser/test_data/parser/inline/ok/0012_type_item_where_clause.rast
index 31c87d1b30..7210b73895 100644
--- a/crates/parser/test_data/parser/inline/ok/0012_type_item_where_clause.rast
+++ b/crates/parser/test_data/parser/inline/ok/0012_type_item_where_clause.rast
@@ -5,12 +5,6 @@ SOURCE_FILE
     NAME
       IDENT "Foo"
     WHITESPACE " "
-    EQ "="
-    WHITESPACE " "
-    TUPLE_TYPE
-      L_PAREN "("
-      R_PAREN ")"
-    WHITESPACE " "
     WHERE_CLAUSE
       WHERE_KW "where"
       WHITESPACE " "
@@ -29,5 +23,11 @@ SOURCE_FILE
                 PATH_SEGMENT
                   NAME_REF
                     IDENT "Copy"
+    WHITESPACE " "
+    EQ "="
+    WHITESPACE " "
+    TUPLE_TYPE
+      L_PAREN "("
+      R_PAREN ")"
     SEMICOLON ";"
   WHITESPACE "\n"
diff --git a/crates/parser/test_data/parser/inline/ok/0012_type_item_where_clause.rs b/crates/parser/test_data/parser/inline/ok/0012_type_item_where_clause.rs
index 2d30e85218..a602d07f03 100644
--- a/crates/parser/test_data/parser/inline/ok/0012_type_item_where_clause.rs
+++ b/crates/parser/test_data/parser/inline/ok/0012_type_item_where_clause.rs
@@ -1,1 +1,1 @@
-type Foo = () where Foo: Copy;
+type Foo where Foo: Copy = ();
diff --git a/crates/parser/test_data/parser/inline/ok/0199_type_item_where_clause_deprecated.rast b/crates/parser/test_data/parser/inline/ok/0199_type_item_where_clause_deprecated.rast
deleted file mode 100644
index 7210b73895..0000000000
--- a/crates/parser/test_data/parser/inline/ok/0199_type_item_where_clause_deprecated.rast
+++ /dev/null
@@ -1,33 +0,0 @@
-SOURCE_FILE
-  TYPE_ALIAS
-    TYPE_KW "type"
-    WHITESPACE " "
-    NAME
-      IDENT "Foo"
-    WHITESPACE " "
-    WHERE_CLAUSE
-      WHERE_KW "where"
-      WHITESPACE " "
-      WHERE_PRED
-        PATH_TYPE
-          PATH
-            PATH_SEGMENT
-              NAME_REF
-                IDENT "Foo"
-        COLON ":"
-        WHITESPACE " "
-        TYPE_BOUND_LIST
-          TYPE_BOUND
-            PATH_TYPE
-              PATH
-                PATH_SEGMENT
-                  NAME_REF
-                    IDENT "Copy"
-    WHITESPACE " "
-    EQ "="
-    WHITESPACE " "
-    TUPLE_TYPE
-      L_PAREN "("
-      R_PAREN ")"
-    SEMICOLON ";"
-  WHITESPACE "\n"
diff --git a/crates/parser/test_data/parser/inline/ok/0199_type_item_where_clause_deprecated.rs b/crates/parser/test_data/parser/inline/ok/0199_type_item_where_clause_deprecated.rs
deleted file mode 100644
index a602d07f03..0000000000
--- a/crates/parser/test_data/parser/inline/ok/0199_type_item_where_clause_deprecated.rs
+++ /dev/null
@@ -1,1 +0,0 @@
-type Foo where Foo: Copy = ();