Repository: rust-analyzer/rust-analyzer
Pull request #11513 (2022-02-21 01:45:15)

feat: Expand the derive attribute into a pseudo expansion
Quoting my comment:

> We generate a very specific expansion here, as we do not actually expand the `#[derive]` attribute
> itself in name res, but we do want to expand it to something for the IDE layer, so that the input
> derive attributes can be downmapped, and resolved as proper paths.
> This is basically a hack, that simplifies the hacks we need in a lot of ide layer places to
> somewhat inconsistently resolve derive attributes.
> 
> As such, we expand `#[derive(Foo, bar::Bar)]` into
> ```
>  #[Foo]
>  #[bar::Bar]
>  ();
> ```
> which allows fallback path resolution in hir::Semantics to properly identify our derives.
> Since we do not expand the attribute in nameres though, we keep the original item.
> 
> The ideal expansion here would be for the `#[derive]` to re-emit the annotated item and somehow
> use the input paths in its output as well.
> But that would bring two problems with it, for one every derive would duplicate the item token tree
> wasting a lot of memory, and it would also require some way to use a path in a way that makes it
> always resolve as a derive without nameres recollecting them.
> So this hacky approach is a lot more friendly for us, though it does require a bit of support in
> [`hir::Semantics`] to make this work.




Patch:
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index f047971a11..9f89bcf9c3 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -1792,6 +1792,10 @@ impl MacroDef {
         }
     }
 
+    pub fn is_builtin_derive(&self) -> bool {
+        matches!(self.id.kind, MacroDefKind::BuiltInAttr(exp, _) if exp.is_derive())
+    }
+
     pub fn is_attr(&self) -> bool {
         matches!(self.kind(), MacroKind::Attr)
     }
@@ -2426,24 +2430,7 @@ impl Impl {
 
     pub fn is_builtin_derive(self, db: &dyn HirDatabase) -> Option<InFile<ast::Attr>> {
         let src = self.source(db)?;
-        let item = src.file_id.is_builtin_derive(db.upcast())?;
-        let hygenic = hir_expand::hygiene::Hygiene::new(db.upcast(), item.file_id);
-
-        // FIXME: handle `cfg_attr`
-        let attr = item
-            .value
-            .attrs()
-            .filter_map(|it| {
-                let path = ModPath::from_src(db.upcast(), it.path()?, &hygenic)?;
-                if path.as_ident()?.to_smol_str() == "derive" {
-                    Some(it)
-                } else {
-                    None
-                }
-            })
-            .last()?;
-
-        Some(item.with_value(attr))
+        src.file_id.is_builtin_derive(db.upcast())
     }
 }
 
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs
index 243ba63b8a..20c360e302 100644
--- a/crates/hir/src/semantics.rs
+++ b/crates/hir/src/semantics.rs
@@ -5,7 +5,6 @@ mod source_to_def;
 use std::{cell::RefCell, fmt, iter};
 
 use base_db::{FileId, FileRange};
-use either::Either;
 use hir_def::{
     body,
     resolver::{self, HasResolver, Resolver, TypeNs},
@@ -19,17 +18,16 @@ use smallvec::{smallvec, SmallVec};
 use syntax::{
     algo::skip_trivia_token,
     ast::{self, HasAttrs as _, HasGenericParams, HasLoopBody},
-    match_ast, AstNode, AstToken, Direction, SyntaxElement, SyntaxNode, SyntaxNodePtr, SyntaxToken,
-    TextSize, T,
+    match_ast, AstNode, Direction, SyntaxNode, SyntaxNodePtr, SyntaxToken, TextSize,
 };
 
 use crate::{
     db::HirDatabase,
     semantics::source_to_def::{ChildContainer, SourceToDefCache, SourceToDefCtx},
     source_analyzer::{resolve_hir_path, SourceAnalyzer},
-    Access, AssocItem, BuiltinAttr, Callable, ConstParam, Crate, Field, Function, HasAttrs as _,
-    HasSource, HirFileId, Impl, InFile, Label, LifetimeParam, Local, MacroDef, Module, ModuleDef,
-    Name, Path, ScopeDef, ToolModule, Trait, Type, TypeAlias, TypeParam, VariantDef,
+    Access, AssocItem, BuiltinAttr, Callable, ConstParam, Crate, Field, Function, HasSource,
+    HirFileId, Impl, InFile, Label, LifetimeParam, Local, MacroDef, Module, ModuleDef, Name, Path,
+    ScopeDef, ToolModule, Trait, Type, TypeAlias, TypeParam, VariantDef,
 };
 
 #[derive(Debug, Clone, PartialEq, Eq)]
@@ -162,6 +160,10 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
         self.imp.is_attr_macro_call(item)
     }
 
+    pub fn is_derive_annotated(&self, item: &ast::Adt) -> bool {
+        self.imp.is_derive_annotated(item)
+    }
+
     pub fn speculative_expand(
         &self,
         actual_macro_call: &ast::MacroCall,
@@ -350,14 +352,6 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
         self.imp.resolve_bind_pat_to_const(pat)
     }
 
-    pub fn resolve_derive_ident(
-        &self,
-        derive: &ast::Attr,
-        ident: &ast::Ident,
-    ) -> Option<PathResolution> {
-        self.imp.resolve_derive_ident(derive, ident)
-    }
-
     pub fn record_literal_missing_fields(&self, literal: &ast::RecordExpr) -> Vec<(Field, Type)> {
         self.imp.record_literal_missing_fields(literal)
     }
@@ -475,11 +469,17 @@ impl<'db> SemanticsImpl<'db> {
         let adt = InFile::new(file_id, &adt);
         let src = InFile::new(file_id, attr.clone());
         self.with_ctx(|ctx| {
-            let (_, res) = ctx.attr_to_derive_macro_call(adt, src)?;
+            let (.., res) = ctx.attr_to_derive_macro_call(adt, src)?;
             Some(res.to_vec())
         })
     }
 
+    fn is_derive_annotated(&self, adt: &ast::Adt) -> bool {
+        let file_id = self.find_file(adt.syntax()).file_id;
+        let adt = InFile::new(file_id, adt);
+        self.with_ctx(|ctx| ctx.has_derives(adt))
+    }
+
     fn is_attr_macro_call(&self, item: &ast::Item) -> bool {
         let file_id = self.find_file(item.syntax()).file_id;
         let src = InFile::new(file_id, item.clone());
@@ -668,7 +668,36 @@ impl<'db> SemanticsImpl<'db> {
                     // FIXME replace map.while_some with take_while once stable
                     token.value.ancestors().map(ast::TokenTree::cast).while_some().last()
                 {
-                    let macro_call = tt.syntax().parent().and_then(ast::MacroCall::cast)?;
+                    let parent = tt.syntax().parent()?;
+                    // check for derive attribute here
+                    let macro_call = match_ast! {
+                        match parent {
+                            ast::MacroCall(mcall) => mcall,
+                            // attribute we failed expansion for earlier, this might be a derive invocation
+                            // so try downmapping the token into the pseudo derive expansion
+                            // see [hir_expand::builtin_attr_macro] for how the pseudo derive expansion works
+                            ast::Meta(meta) => {
+                                let attr = meta.parent_attr()?;
+                                let adt = attr.syntax().parent().and_then(ast::Adt::cast)?;
+                                let call_id = self.with_ctx(|ctx| {
+                                    let (_, call_id, _) = ctx.attr_to_derive_macro_call(
+                                        token.with_value(&adt),
+                                        token.with_value(attr),
+                                    )?;
+                                    Some(call_id)
+                                })?;
+                                let file_id = call_id.as_file();
+                                return process_expansion_for_token(
+                                    &mut stack,
+                                    file_id,
+                                    Some(adt.into()),
+                                    token.as_ref(),
+                                );
+                            },
+                            _ => return None,
+                        }
+                    };
+
                     if tt.left_delimiter_token().map_or(false, |it| it == token.value) {
                         return None;
                     }
@@ -898,72 +927,6 @@ impl<'db> SemanticsImpl<'db> {
         self.analyze(pat.syntax()).resolve_bind_pat_to_const(self.db, pat)
     }
 
-    fn resolve_derive_ident(
-        &self,
-        derive: &ast::Attr,
-        ident: &ast::Ident,
-    ) -> Option<PathResolution> {
-        debug_assert!(ident.syntax().parent().and_then(ast::TokenTree::cast).is_some());
-        debug_assert!(ident.syntax().ancestors().any(|anc| anc == *derive.syntax()));
-        // derive macros are always at depth 2, tokentree -> meta -> attribute
-        let syntax = ident.syntax();
-
-        let tt = derive.token_tree()?;
-        let file = self.find_file(derive.syntax());
-        let adt = derive.syntax().parent().and_then(ast::Adt::cast)?;
-        let adt_def = ToDef::to_def(self, file.with_value(adt.clone()))?;
-        let res = self.with_ctx(|ctx| {
-            let (attr_id, derives) = ctx.attr_to_derive_macro_call(
-                file.with_value(&adt),
-                file.with_value(derive.clone()),
-            )?;
-            let attrs = adt_def.attrs(self.db);
-            let mut derive_paths = attrs.get(attr_id)?.parse_path_comma_token_tree()?;
-
-            let derive_idx = tt
-                .syntax()
-                .children_with_tokens()
-                .filter_map(SyntaxElement::into_token)
-                .take_while(|tok| tok != syntax)
-                .filter(|t| t.kind() == T![,])
-                .count();
-            let path_segment_idx = syntax
-                .siblings_with_tokens(Direction::Prev)
-                .filter_map(SyntaxElement::into_token)
-                .take_while(|tok| matches!(tok.kind(), T![:] | T![ident]))
-                .filter(|tok| tok.kind() == T![ident])
-                .count();
-
-            let mut mod_path = derive_paths.nth(derive_idx)?;
-
-            if path_segment_idx < mod_path.len() {
-                // the path for the given ident is a qualifier, resolve to module if possible
-                while path_segment_idx < mod_path.len() {
-                    mod_path.pop_segment();
-                }
-                Some(Either::Left(mod_path))
-            } else {
-                // otherwise fetch the derive
-                Some(Either::Right(derives[derive_idx]))
-            }
-        })?;
-
-        match res {
-            Either::Left(path) => {
-                let len = path.len();
-                resolve_hir_path(
-                    self.db,
-                    &self.scope(derive.syntax()).resolver,
-                    &Path::from_known_path(path, vec![None; len]),
-                )
-                .filter(|res| matches!(res, PathResolution::Def(ModuleDef::Module(_))))
-            }
-            Either::Right(derive) => derive
-                .map(|call| MacroDef { id: self.db.lookup_intern_macro_call(call).def })
-                .map(PathResolution::Macro),
-        }
-    }
-
     fn record_literal_missing_fields(&self, literal: &ast::RecordExpr) -> Vec<(Field, Type)> {
         self.analyze(literal.syntax())
             .record_literal_missing_fields(self.db, literal)
diff --git a/crates/hir/src/semantics/source_to_def.rs b/crates/hir/src/semantics/source_to_def.rs
index 5061861239..dddb8e33dc 100644
--- a/crates/hir/src/semantics/source_to_def.rs
+++ b/crates/hir/src/semantics/source_to_def.rs
@@ -249,9 +249,14 @@ impl SourceToDefCtx<'_, '_> {
         &mut self,
         item: InFile<&ast::Adt>,
         src: InFile<ast::Attr>,
-    ) -> Option<(AttrId, &[Option<MacroCallId>])> {
+    ) -> Option<(AttrId, MacroCallId, &[Option<MacroCallId>])> {
         let map = self.dyn_map(item)?;
-        map[keys::DERIVE_MACRO_CALL].get(&src.value).map(|(id, ids)| (*id, &**ids))
+        map[keys::DERIVE_MACRO_CALL]
+            .get(&src.value)
+            .map(|&(attr_id, call_id, ref ids)| (attr_id, call_id, &**ids))
+    }
+    pub(super) fn has_derives(&mut self, adt: InFile<&ast::Adt>) -> bool {
+        self.dyn_map(adt).as_ref().map_or(false, |map| !map[keys::DERIVE_MACRO_CALL].is_empty())
     }
 
     fn to_def<Ast: AstNode + 'static, ID: Copy + 'static>(
diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs
index 03e4420985..ef32a5891c 100644
--- a/crates/hir/src/source_analyzer.rs
+++ b/crates/hir/src/source_analyzer.rs
@@ -371,10 +371,10 @@ impl SourceAnalyzer {
                 return builtin.map(PathResolution::BuiltinAttr);
             }
             return match resolve_hir_path_as_macro(db, &self.resolver, &hir_path) {
-                res @ Some(m) if m.is_attr() => res.map(PathResolution::Macro),
+                Some(m) => Some(PathResolution::Macro(m)),
                 // this labels any path that starts with a tool module as the tool itself, this is technically wrong
                 // but there is no benefit in differentiating these two cases for the time being
-                _ => path.first_segment().and_then(|it| it.name_ref()).and_then(|name_ref| {
+                None => path.first_segment().and_then(|it| it.name_ref()).and_then(|name_ref| {
                     match self.resolver.krate() {
                         Some(krate) => ToolModule::by_name(db, krate.into(), &name_ref.text()),
                         None => ToolModule::builtin(&name_ref.text()),
diff --git a/crates/hir_def/src/child_by_source.rs b/crates/hir_def/src/child_by_source.rs
index 5c32a31e44..1e1573d4ae 100644
--- a/crates/hir_def/src/child_by_source.rs
+++ b/crates/hir_def/src/child_by_source.rs
@@ -116,11 +116,11 @@ impl ChildBySource for ItemScope {
         self.derive_macro_invocs().filter(|(id, _)| id.file_id == file_id).for_each(
             |(ast_id, calls)| {
                 let adt = ast_id.to_node(db.upcast());
-                calls.for_each(|(attr_id, calls)| {
+                calls.for_each(|(attr_id, call_id, calls)| {
                     if let Some(Either::Left(attr)) =
                         adt.doc_comments_and_attrs().nth(attr_id.ast_index as usize)
                     {
-                        res[keys::DERIVE_MACRO_CALL].insert(attr, (attr_id, calls.into()));
+                        res[keys::DERIVE_MACRO_CALL].insert(attr, (attr_id, call_id, calls.into()));
                     }
                 });
             },
diff --git a/crates/hir_def/src/dyn_map.rs b/crates/hir_def/src/dyn_map.rs
index 6f269d7b01..166aa04da0 100644
--- a/crates/hir_def/src/dyn_map.rs
+++ b/crates/hir_def/src/dyn_map.rs
@@ -54,6 +54,7 @@ pub trait Policy {
 
     fn insert(map: &mut DynMap, key: Self::K, value: Self::V);
     fn get<'a>(map: &'a DynMap, key: &Self::K) -> Option<&'a Self::V>;
+    fn is_empty(map: &DynMap) -> bool;
 }
 
 impl<K: Hash + Eq + 'static, V: 'static> Policy for (K, V) {
@@ -65,6 +66,9 @@ impl<K: Hash + Eq + 'static, V: 'static> Policy for (K, V) {
     fn get<'a>(map: &'a DynMap, key: &K) -> Option<&'a V> {
         map.map.get::<FxHashMap<K, V>>()?.get(key)
     }
+    fn is_empty(map: &DynMap) -> bool {
+        map.map.get::<FxHashMap<K, V>>().map_or(true, |it| it.is_empty())
+    }
 }
 
 pub struct DynMap {
@@ -90,6 +94,10 @@ impl<P: Policy> KeyMap<Key<P::K, P::V, P>> {
     pub fn get(&self, key: &P::K) -> Option<&P::V> {
         P::get(&self.map, key)
     }
+
+    pub fn is_empty(&self) -> bool {
+        P::is_empty(&self.map)
+    }
 }
 
 impl<P: Policy> Index<Key<P::K, P::V, P>> for DynMap {
diff --git a/crates/hir_def/src/item_scope.rs b/crates/hir_def/src/item_scope.rs
index 258d1e0f6c..fffec96bab 100644
--- a/crates/hir_def/src/item_scope.rs
+++ b/crates/hir_def/src/item_scope.rs
@@ -66,8 +66,10 @@ pub struct ItemScope {
     attr_macros: FxHashMap<AstId<ast::Item>, MacroCallId>,
     /// The derive macro invocations in this scope, keyed by the owner item over the actual derive attributes
     /// paired with the derive macro invocations for the specific attribute.
-    derive_macros:
-        FxHashMap<AstId<ast::Adt>, SmallVec<[(AttrId, SmallVec<[Option<MacroCallId>; 1]>); 1]>>,
+    derive_macros: FxHashMap<
+        AstId<ast::Adt>,
+        SmallVec<[(AttrId, MacroCallId, SmallVec<[Option<MacroCallId>; 1]>); 1]>,
+    >,
 }
 
 pub(crate) static BUILTIN_SCOPE: Lazy<FxHashMap<Name, PerNs>> = Lazy::new(|| {
@@ -210,7 +212,7 @@ impl ItemScope {
         idx: usize,
     ) {
         if let Some(derives) = self.derive_macros.get_mut(&adt) {
-            if let Some((_, invocs)) = derives.iter_mut().find(|&&mut (id, _)| id == attr_id) {
+            if let Some((.., invocs)) = derives.iter_mut().find(|&&mut (id, ..)| id == attr_id) {
                 invocs[idx] = Some(call);
             }
         }
@@ -223,19 +225,23 @@ impl ItemScope {
         &mut self,
         adt: AstId<ast::Adt>,
         attr_id: AttrId,
+        call_id: MacroCallId,
         len: usize,
     ) {
-        self.derive_macros.entry(adt).or_default().push((attr_id, smallvec![None; len]));
+        self.derive_macros.entry(adt).or_default().push((attr_id, call_id, smallvec![None; len]));
     }
 
     pub(crate) fn derive_macro_invocs(
         &self,
     ) -> impl Iterator<
-        Item = (AstId<ast::Adt>, impl Iterator<Item = (AttrId, &[Option<MacroCallId>])>),
+        Item = (
+            AstId<ast::Adt>,
+            impl Iterator<Item = (AttrId, MacroCallId, &[Option<MacroCallId>])>,
+        ),
     > + '_ {
-        self.derive_macros
-            .iter()
-            .map(|(k, v)| (*k, v.iter().map(|(attr_id, invocs)| (*attr_id, &**invocs))))
+        self.derive_macros.iter().map(|(k, v)| {
+            (*k, v.iter().map(|&(attr_id, call_id, ref invocs)| (attr_id, call_id, &**invocs)))
+        })
     }
 
     pub(crate) fn unnamed_trait_vis(&self, tr: TraitId) -> Option<Visibility> {
diff --git a/crates/hir_def/src/keys.rs b/crates/hir_def/src/keys.rs
index 93c92c1b9c..8cd2d77172 100644
--- a/crates/hir_def/src/keys.rs
+++ b/crates/hir_def/src/keys.rs
@@ -34,7 +34,8 @@ pub const CONST_PARAM: Key<ast::ConstParam, ConstParamId> = Key::new();
 
 pub const MACRO: Key<ast::Macro, MacroDefId> = Key::new();
 pub const ATTR_MACRO_CALL: Key<ast::Item, MacroCallId> = Key::new();
-pub const DERIVE_MACRO_CALL: Key<ast::Attr, (AttrId, Box<[Option<MacroCallId>]>)> = Key::new();
+pub const DERIVE_MACRO_CALL: Key<ast::Attr, (AttrId, MacroCallId, Box<[Option<MacroCallId>]>)> =
+    Key::new();
 
 /// XXX: AST Nodes and SyntaxNodes have identity equality semantics: nodes are
 /// equal if they point to exactly the same object.
@@ -60,4 +61,7 @@ impl<AST: AstNode + 'static, ID: 'static> Policy for AstPtrPolicy<AST, ID> {
         let key = AstPtr::new(key);
         map.map.get::<FxHashMap<AstPtr<AST>, ID>>()?.get(&key)
     }
+    fn is_empty(map: &DynMap) -> bool {
+        map.map.get::<FxHashMap<AstPtr<AST>, ID>>().map_or(true, |it| it.is_empty())
+    }
 }
diff --git a/crates/hir_def/src/lib.rs b/crates/hir_def/src/lib.rs
index 7e33e53599..bb65d1dec8 100644
--- a/crates/hir_def/src/lib.rs
+++ b/crates/hir_def/src/lib.rs
@@ -690,9 +690,9 @@ impl AsMacroCall for InFile<&ast::MacroCall> {
         };
 
         macro_call_as_call_id(
+            db,
             &AstIdWithPath::new(ast_id.file_id, ast_id.value, path),
             expands_to,
-            db,
             krate,
             resolver,
             error_sink,
@@ -714,9 +714,9 @@ impl<T: ast::AstNode> AstIdWithPath<T> {
 }
 
 fn macro_call_as_call_id(
+    db: &dyn db::DefDatabase,
     call: &AstIdWithPath<ast::MacroCall>,
     expand_to: ExpandTo,
-    db: &dyn db::DefDatabase,
     krate: CrateId,
     resolver: impl Fn(path::ModPath) -> Option<MacroDefId>,
     error_sink: &mut dyn FnMut(ExpandError),
@@ -739,10 +739,10 @@ fn macro_call_as_call_id(
 }
 
 fn derive_macro_as_call_id(
+    db: &dyn db::DefDatabase,
     item_attr: &AstIdWithPath<ast::Adt>,
     derive_attr: AttrId,
     derive_pos: u32,
-    db: &dyn db::DefDatabase,
     krate: CrateId,
     resolver: impl Fn(path::ModPath) -> Option<MacroDefId>,
 ) -> Result<MacroCallId, UnresolvedMacro> {
@@ -761,11 +761,12 @@ fn derive_macro_as_call_id(
 }
 
 fn attr_macro_as_call_id(
+    db: &dyn db::DefDatabase,
     item_attr: &AstIdWithPath<ast::Item>,
     macro_attr: &Attr,
-    db: &dyn db::DefDatabase,
     krate: CrateId,
     def: MacroDefId,
+    is_derive: bool,
 ) -> MacroCallId {
     let mut arg = match macro_attr.input.as_deref() {
         Some(attr::AttrInput::TokenTree(tt, map)) => (tt.clone(), map.clone()),
@@ -782,6 +783,7 @@ fn attr_macro_as_call_id(
             ast_id: item_attr.ast_id,
             attr_args: Arc::new(arg),
             invoc_attr_index: macro_attr.id.ast_index,
+            is_derive,
         },
     );
     res
diff --git a/crates/hir_def/src/nameres/attr_resolution.rs b/crates/hir_def/src/nameres/attr_resolution.rs
index 4a7211b5c9..4c436250db 100644
--- a/crates/hir_def/src/nameres/attr_resolution.rs
+++ b/crates/hir_def/src/nameres/attr_resolution.rs
@@ -54,7 +54,7 @@ impl DefMap {
             None => return Err(UnresolvedMacro { path: ast_id.path.clone() }),
         };
 
-        Ok(ResolvedAttr::Macro(attr_macro_as_call_id(&ast_id, attr, db, self.krate, def)))
+        Ok(ResolvedAttr::Macro(attr_macro_as_call_id(db, &ast_id, attr, self.krate, def, false)))
     }
 
     pub(crate) fn is_builtin_or_registered_attr(&self, path: &ModPath) -> bool {
diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs
index ec6af65a92..2dd7cc4859 100644
--- a/crates/hir_def/src/nameres/collector.rs
+++ b/crates/hir_def/src/nameres/collector.rs
@@ -1055,9 +1055,9 @@ impl DefCollector<'_> {
             match &directive.kind {
                 MacroDirectiveKind::FnLike { ast_id, expand_to } => {
                     let call_id = macro_call_as_call_id(
+                        self.db,
                         ast_id,
                         *expand_to,
-                        self.db,
                         self.def_map.krate,
                         &resolver,
                         &mut |_err| (),
@@ -1070,10 +1070,10 @@ impl DefCollector<'_> {
                 }
                 MacroDirectiveKind::Derive { ast_id, derive_attr, derive_pos } => {
                     let call_id = derive_macro_as_call_id(
+                        self.db,
                         ast_id,
                         *derive_attr,
                         *derive_pos as u32,
-                        self.db,
                         self.def_map.krate,
                         &resolver,
                     );
@@ -1170,9 +1170,20 @@ impl DefCollector<'_> {
                                     len = idx;
                                 }
 
+                                // We treat the #[derive] macro as an attribute call, but we do not resolve it for nameres collection.
+                                // This is just a trick to be able to resolve the input to derives as proper paths.
+                                // Check the comment in [`builtin_attr_macro`].
+                                let call_id = attr_macro_as_call_id(
+                                    self.db,
+                                    file_ast_id,
+                                    attr,
+                                    self.def_map.krate,
+                                    def,
+                                    true,
+                                );
                                 self.def_map.modules[directive.module_id]
                                     .scope
-                                    .init_derive_attribute(ast_id, attr.id, len + 1);
+                                    .init_derive_attribute(ast_id, attr.id, call_id, len + 1);
                             }
                             None => {
                                 let diag = DefDiagnostic::malformed_derive(
@@ -1192,8 +1203,14 @@ impl DefCollector<'_> {
                     }
 
                     // 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 call_id = attr_macro_as_call_id(
+                        self.db,
+                        file_ast_id,
+                        attr,
+                        self.def_map.krate,
+                        def,
+                        false,
+                    );
                     let loc: MacroCallLoc = self.db.lookup_intern_macro_call(call_id);
 
                     // Skip #[test]/#[bench] expansion, which would merely result in more memory usage
@@ -1310,9 +1327,9 @@ impl DefCollector<'_> {
             match &directive.kind {
                 MacroDirectiveKind::FnLike { ast_id, expand_to } => {
                     let macro_call_as_call_id = macro_call_as_call_id(
+                        self.db,
                         ast_id,
                         *expand_to,
-                        self.db,
                         self.def_map.krate,
                         |path| {
                             let resolved_res = self.def_map.resolve_path_fp_with_macro(
@@ -1959,9 +1976,9 @@ impl ModCollector<'_, '_> {
         // Case 1: try to resolve in legacy scope and expand macro_rules
         let mut error = None;
         match macro_call_as_call_id(
+            self.def_collector.db,
             &ast_id,
             mac.expand_to,
-            self.def_collector.db,
             self.def_collector.def_map.krate,
             |path| {
                 path.as_ident().and_then(|name| {
diff --git a/crates/hir_expand/src/builtin_attr_macro.rs b/crates/hir_expand/src/builtin_attr_macro.rs
index 907ee02e33..6301da1c83 100644
--- a/crates/hir_expand/src/builtin_attr_macro.rs
+++ b/crates/hir_expand/src/builtin_attr_macro.rs
@@ -1,9 +1,11 @@
 //! Builtin attributes.
 
+use itertools::Itertools;
 use syntax::ast;
 
 use crate::{
-    db::AstDatabase, name, AstId, CrateId, ExpandResult, MacroCallId, MacroDefId, MacroDefKind,
+    db::AstDatabase, name, AstId, CrateId, ExpandResult, MacroCallId, MacroCallKind, MacroDefId,
+    MacroDefKind,
 };
 
 macro_rules! register_builtin {
@@ -53,7 +55,7 @@ register_builtin! {
     (bench, Bench) => dummy_attr_expand,
     (cfg_accessible, CfgAccessible) => dummy_attr_expand,
     (cfg_eval, CfgEval) => dummy_attr_expand,
-    (derive, Derive) => dummy_attr_expand,
+    (derive, Derive) => derive_attr_expand,
     (global_allocator, GlobalAllocator) => dummy_attr_expand,
     (test, Test) => dummy_attr_expand,
     (test_case, TestCase) => dummy_attr_expand
@@ -79,3 +81,68 @@ fn dummy_attr_expand(
 ) -> ExpandResult<tt::Subtree> {
     ExpandResult::ok(tt.clone())
 }
+
+/// We generate a very specific expansion here, as we do not actually expand the `#[derive]` attribute
+/// itself in name res, but we do want to expand it to something for the IDE layer, so that the input
+/// derive attributes can be downmapped, and resolved as proper paths.
+/// This is basically a hack, that simplifies the hacks we need in a lot of ide layer places to
+/// somewhat inconsistently resolve derive attributes.
+///
+/// As such, we expand `#[derive(Foo, bar::Bar)]` into
+/// ```
+///  #[Foo]
+///  #[bar::Bar]
+///  ();
+/// ```
+/// which allows fallback path resolution in hir::Semantics to properly identify our derives.
+/// Since we do not expand the attribute in nameres though, we keep the original item.
+///
+/// The ideal expansion here would be for the `#[derive]` to re-emit the annotated item and somehow
+/// use the input paths in its output as well.
+/// But that would bring two problems with it, for one every derive would duplicate the item token tree
+/// wasting a lot of memory, and it would also require some way to use a path in a way that makes it
+/// always resolve as a derive without nameres recollecting them.
+/// So this hacky approach is a lot more friendly for us, though it does require a bit of support in
+/// [`hir::Semantics`] to make this work.
+fn derive_attr_expand(
+    db: &dyn AstDatabase,
+    id: MacroCallId,
+    tt: &tt::Subtree,
+) -> 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()),
+    };
+
+    let mk_leaf = |char| {
+        tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct {
+            char,
+            spacing: tt::Spacing::Alone,
+            id: tt::TokenId::unspecified(),
+        }))
+    };
+
+    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: ',', .. })))
+    {
+        if comma {
+            continue;
+        }
+        token_trees.push(mk_leaf('#'));
+        token_trees.push(mk_leaf('['));
+        token_trees.extend(group.cloned().map(tt::TokenTree::Leaf));
+        token_trees.push(mk_leaf(']'));
+    }
+    token_trees.push(mk_leaf('('));
+    token_trees.push(mk_leaf(')'));
+    token_trees.push(mk_leaf(';'));
+    ExpandResult::ok(tt::Subtree { delimiter: tt.delimiter, token_trees })
+}
diff --git a/crates/hir_expand/src/db.rs b/crates/hir_expand/src/db.rs
index 91c1631e81..d6d33b4cd7 100644
--- a/crates/hir_expand/src/db.rs
+++ b/crates/hir_expand/src/db.rs
@@ -342,6 +342,7 @@ fn censor_for_macro_input(loc: &MacroCallLoc, node: &SyntaxNode) -> FxHashSet<Sy
                     .map(|it| it.syntax().clone())
                     .collect()
             }
+            MacroCallKind::Attr { is_derive: true, .. } => return None,
             MacroCallKind::Attr { invoc_attr_index, .. } => {
                 cov_mark::hit!(attribute_macro_attr_censoring);
                 ast::Item::cast(node.clone())?
diff --git a/crates/hir_expand/src/lib.rs b/crates/hir_expand/src/lib.rs
index 27c3f097ab..cc38faa136 100644
--- a/crates/hir_expand/src/lib.rs
+++ b/crates/hir_expand/src/lib.rs
@@ -166,6 +166,8 @@ pub enum MacroCallKind {
         /// Outer attributes are counted first, then inner attributes. This does not support
         /// out-of-line modules, which may have attributes spread across 2 files!
         invoc_attr_index: u32,
+        /// Whether this attribute is the `#[derive]` attribute.
+        is_derive: bool,
     },
 }
 
@@ -218,9 +220,18 @@ impl HirFileId {
 
                 let arg_tt = loc.kind.arg(db)?;
 
+                let macro_def = db.macro_def(loc.def).ok()?;
+                let (parse, exp_map) = db.parse_macro_expansion(macro_file).value?;
+                let macro_arg = db.macro_arg(macro_file.macro_call_id)?;
+
                 let def = loc.def.ast_id().left().and_then(|id| {
                     let def_tt = match id.to_node(db) {
                         ast::Macro::MacroRules(mac) => mac.token_tree()?,
+                        ast::Macro::MacroDef(_)
+                            if matches!(*macro_def, TokenExpander::BuiltinAttr(_)) =>
+                        {
+                            return None
+                        }
                         ast::Macro::MacroDef(mac) => mac.body()?,
                     };
                     Some(InFile::new(id.file_id, def_tt))
@@ -238,10 +249,6 @@ impl HirFileId {
                     _ => None,
                 });
 
-                let macro_def = db.macro_def(loc.def).ok()?;
-                let (parse, exp_map) = db.parse_macro_expansion(macro_file).value?;
-                let macro_arg = db.macro_arg(macro_file.macro_call_id)?;
-
                 Some(ExpansionInfo {
                     expanded: InFile::new(self, parse.syntax_node()),
                     arg: InFile::new(loc.kind.file_id(), arg_tt),
@@ -256,16 +263,16 @@ impl HirFileId {
     }
 
     /// Indicate it is macro file generated for builtin derive
-    pub fn is_builtin_derive(&self, db: &dyn db::AstDatabase) -> Option<InFile<ast::Item>> {
+    pub fn is_builtin_derive(&self, db: &dyn db::AstDatabase) -> Option<InFile<ast::Attr>> {
         match self.0 {
             HirFileIdRepr::FileId(_) => None,
             HirFileIdRepr::MacroFile(macro_file) => {
                 let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id);
-                let item = match loc.def.kind {
+                let attr = match loc.def.kind {
                     MacroDefKind::BuiltInDerive(..) => loc.kind.to_node(db),
                     _ => return None,
                 };
-                Some(item.with_value(ast::Item::cast(item.value.clone())?))
+                Some(attr.with_value(ast::Attr::cast(attr.value.clone())?))
             }
         }
     }
@@ -291,7 +298,7 @@ impl HirFileId {
         }
     }
 
-    /// Return whether this file is an include macro
+    /// Return whether this file is an attr macro
     pub fn is_attr_macro(&self, db: &dyn db::AstDatabase) -> bool {
         match self.0 {
             HirFileIdRepr::MacroFile(macro_file) => {
@@ -302,6 +309,17 @@ impl HirFileId {
         }
     }
 
+    /// Return whether this file is the pseudo expansion of the derive attribute.
+    pub fn is_derive_attr_macro(&self, db: &dyn db::AstDatabase) -> bool {
+        match self.0 {
+            HirFileIdRepr::MacroFile(macro_file) => {
+                let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id);
+                matches!(loc.kind, MacroCallKind::Attr { is_derive: true, .. })
+            }
+            _ => false,
+        }
+    }
+
     pub fn is_macro(self) -> bool {
         matches!(self.0, HirFileIdRepr::MacroFile(_))
     }
@@ -366,8 +384,29 @@ impl MacroCallKind {
             MacroCallKind::FnLike { ast_id, .. } => {
                 ast_id.with_value(ast_id.to_node(db).syntax().clone())
             }
-            MacroCallKind::Derive { ast_id, .. } => {
-                ast_id.with_value(ast_id.to_node(db).syntax().clone())
+            MacroCallKind::Derive { ast_id, derive_attr_index, .. } => {
+                // FIXME: handle `cfg_attr`
+                ast_id.with_value(ast_id.to_node(db)).map(|it| {
+                    it.doc_comments_and_attrs()
+                        .nth(*derive_attr_index as usize)
+                        .and_then(|it| match it {
+                            Either::Left(attr) => Some(attr.syntax().clone()),
+                            Either::Right(_) => None,
+                        })
+                        .unwrap_or_else(|| it.syntax().clone())
+                })
+            }
+            MacroCallKind::Attr { ast_id, is_derive: true, invoc_attr_index, .. } => {
+                // FIXME: handle `cfg_attr`
+                ast_id.with_value(ast_id.to_node(db)).map(|it| {
+                    it.doc_comments_and_attrs()
+                        .nth(*invoc_attr_index as usize)
+                        .and_then(|it| match it {
+                            Either::Left(attr) => Some(attr.syntax().clone()),
+                            Either::Right(_) => None,
+                        })
+                        .unwrap_or_else(|| it.syntax().clone())
+                })
             }
             MacroCallKind::Attr { ast_id, .. } => {
                 ast_id.with_value(ast_id.to_node(db).syntax().clone())
@@ -431,6 +470,7 @@ impl MacroCallKind {
         match self {
             MacroCallKind::FnLike { expand_to, .. } => *expand_to,
             MacroCallKind::Derive { .. } => ExpandTo::Items,
+            MacroCallKind::Attr { is_derive: true, .. } => ExpandTo::Statements,
             MacroCallKind::Attr { .. } => ExpandTo::Items, // is this always correct?
         }
     }
@@ -497,7 +537,7 @@ impl ExpansionInfo {
 
             let token_range = token.value.text_range();
             match &loc.kind {
-                MacroCallKind::Attr { attr_args, invoc_attr_index, .. } => {
+                MacroCallKind::Attr { attr_args, invoc_attr_index, is_derive, .. } => {
                     let attr = item
                         .doc_comments_and_attrs()
                         .nth(*invoc_attr_index as usize)
@@ -511,9 +551,13 @@ impl ExpansionInfo {
                             let relative_range =
                                 token.value.text_range().checked_sub(attr_input_start)?;
                             // shift by the item's tree's max id
-                            let token_id = self
-                                .macro_arg_shift
-                                .shift(attr_args.1.token_by_range(relative_range)?);
+                            let token_id = attr_args.1.token_by_range(relative_range)?;
+                            let token_id = if *is_derive {
+                                // we do not shift for `#[derive]`, as we only need to downmap the derive attribute tokens
+                                token_id
+                            } else {
+                                self.macro_arg_shift.shift(token_id)
+                            };
                             Some(token_id)
                         }
                         _ => None,
@@ -561,6 +605,9 @@ impl ExpansionInfo {
 
         // Attributes are a bit special for us, they have two inputs, the input tokentree and the annotated item.
         let (token_map, tt) = match &loc.kind {
+            MacroCallKind::Attr { attr_args, is_derive: true, .. } => {
+                (&attr_args.1, self.attr_input_or_mac_def.clone()?.syntax().cloned())
+            }
             MacroCallKind::Attr { attr_args, .. } => {
                 // try unshifting the the token id, if unshifting fails, the token resides in the non-item attribute input
                 // note that the `TokenExpander::map_id_up` earlier only unshifts for declarative macros, so we don't double unshift with this
@@ -716,6 +763,13 @@ impl<'a> InFile<&'a SyntaxNode> {
     }
 }
 
+impl InFile<SyntaxToken> {
+    pub fn upmap(self, db: &dyn db::AstDatabase) -> Option<InFile<SyntaxToken>> {
+        let expansion = self.file_id.expansion_info(db)?;
+        expansion.map_token_up(db, self.as_ref()).map(|(it, _)| it)
+    }
+}
+
 fn ascend_node_border_tokens(
     db: &dyn db::AstDatabase,
     InFile { file_id, value: node }: InFile<&SyntaxNode>,
diff --git a/crates/ide/src/expand_macro.rs b/crates/ide/src/expand_macro.rs
index 32dbd9070b..f732674725 100644
--- a/crates/ide/src/expand_macro.rs
+++ b/crates/ide/src/expand_macro.rs
@@ -3,8 +3,7 @@ use ide_db::{
     helpers::{insert_whitespace_into_node::insert_ws_into, pick_best_token},
     RootDatabase,
 };
-use itertools::Itertools;
-use syntax::{ast, ted, AstNode, SyntaxKind, SyntaxNode};
+use syntax::{ast, ted, AstNode, NodeOrToken, SyntaxKind, SyntaxNode, T};
 
 use crate::FilePosition;
 
@@ -41,20 +40,28 @@ pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<
     // struct Bar;
     // ```
 
-    let derive = sema.descend_into_macros(tok.clone()).iter().find_map(|descended| {
-        let attr = descended.ancestors().find_map(ast::Attr::cast)?;
-        let (path, tt) = attr.as_simple_call()?;
-        if path == "derive" {
-            let mut tt = tt.syntax().children_with_tokens().skip(1).join("");
-            tt.pop();
-            let expansions = sema.expand_derive_macro(&attr)?;
-            Some(ExpandedMacro {
-                name: tt,
-                expansion: expansions.into_iter().map(insert_ws_into).join(""),
-            })
-        } else {
-            None
+    let derive = sema.descend_into_macros(tok.clone()).into_iter().find_map(|descended| {
+        let hir_file = sema.hir_file_for(&descended.parent()?);
+        if !hir_file.is_derive_attr_macro(db) {
+            return None;
         }
+
+        let name = descended.ancestors().filter_map(ast::Path::cast).last()?.to_string();
+        // up map out of the #[derive] expansion
+        let token = hir::InFile::new(hir_file, descended).upmap(db)?.value;
+        let attr = token.ancestors().find_map(ast::Attr::cast)?;
+        let expansions = sema.expand_derive_macro(&attr)?;
+        let idx = attr
+            .token_tree()?
+            .token_trees_and_tokens()
+            .filter_map(NodeOrToken::into_token)
+            .take_while(|it| it == &token)
+            .filter(|it| it.kind() == T![,])
+            .count();
+        Some(ExpandedMacro {
+            name,
+            expansion: expansions.get(idx).cloned().map(insert_ws_into)?.to_string(),
+        })
     });
 
     if derive.is_some() {
@@ -372,11 +379,9 @@ struct Foo {}
 struct Foo {}
 "#,
             expect![[r#"
-                Copy, Clone
+                Copy
                 impl < >core::marker::Copy for Foo< >{}
 
-                impl < >core::clone::Clone for Foo< >{}
-
             "#]],
         );
     }
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs
index 91c311fe94..38edda9c1c 100644
--- a/crates/ide/src/references.rs
+++ b/crates/ide/src/references.rs
@@ -1564,7 +1564,6 @@ fn func$0() {}
         );
     }
 
-    // FIXME
     #[test]
     fn derive() {
         check(
@@ -1575,7 +1574,11 @@ fn func$0() {}
 #[derive(proc_macros::DeriveIdentity$0)]
 struct Foo;
 "#,
-            expect![[r#""#]],
+            expect![[r#"
+                derive_identity Derive FileId(2) 1..107 45..60
+
+                FileId(0) 23..37
+            "#]],
         )
     }
 }
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs
index f09f291e96..7d92c5051b 100644
--- a/crates/ide/src/syntax_highlighting.rs
+++ b/crates/ide/src/syntax_highlighting.rs
@@ -237,6 +237,20 @@ fn traverse(
                     continue;
                 }
                 Some(item) if sema.is_attr_macro_call(&item) => current_attr_call = Some(item),
+                Some(item) if current_attr_call.is_none() => {
+                    let adt = match item {
+                        ast::Item::Enum(it) => Some(ast::Adt::Enum(it)),
+                        ast::Item::Struct(it) => Some(ast::Adt::Struct(it)),
+                        ast::Item::Union(it) => Some(ast::Adt::Union(it)),
+                        _ => None,
+                    };
+                    match adt {
+                        Some(adt) if sema.is_derive_annotated(&adt) => {
+                            current_attr_call = Some(adt.into());
+                        }
+                        _ => (),
+                    }
+                }
                 None if ast::Attr::can_cast(node.kind()) => inside_attribute = true,
                 _ => (),
             },
@@ -361,7 +375,7 @@ fn traverse(
                 syntactic_name_ref_highlighting,
                 node,
             ),
-            NodeOrToken::Token(token) => highlight::token(sema, krate, token).zip(Some(None)),
+            NodeOrToken::Token(token) => highlight::token(sema, token).zip(Some(None)),
         };
         if let Some((mut highlight, binding_hash)) = element {
             if inside_attribute {
diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs
index c869db3b8b..85c0c1b286 100644
--- a/crates/ide/src/syntax_highlighting/highlight.rs
+++ b/crates/ide/src/syntax_highlighting/highlight.rs
@@ -18,11 +18,7 @@ use crate::{
     Highlight, HlMod, HlTag,
 };
 
-pub(super) fn token(
-    sema: &Semantics<RootDatabase>,
-    krate: Option<hir::Crate>,
-    token: SyntaxToken,
-) -> Option<Highlight> {
+pub(super) fn token(sema: &Semantics<RootDatabase>, token: SyntaxToken) -> Option<Highlight> {
     if let Some(comment) = ast::Comment::cast(token.clone()) {
         let h = HlTag::Comment;
         return Some(match comment.kind().doc {
@@ -39,17 +35,10 @@ pub(super) fn token(
         INT_NUMBER | FLOAT_NUMBER => HlTag::NumericLiteral.into(),
         BYTE => HlTag::ByteLiteral.into(),
         CHAR => HlTag::CharLiteral.into(),
-        IDENT => {
-            let tt = ast::TokenTree::cast(token.parent()?)?;
-            let ident = ast::Ident::cast(token)?;
+        IDENT if token.parent().and_then(ast::TokenTree::cast).is_some() => {
             // from this point on we are inside a token tree, this only happens for identifiers
             // that were not mapped down into macro invocations
-            (|| {
-                let attr = tt.parent_meta()?.parent_attr()?;
-                let res = sema.resolve_derive_ident(&attr, &ident)?;
-                Some(highlight_def(sema, krate, Definition::from(res)))
-            })()
-            .unwrap_or_else(|| HlTag::None.into())
+            HlTag::None.into()
         }
         p if p.is_punct() => punctuation(sema, token, p),
         k if k.is_keyword() => keyword(sema, token, k)?,
diff --git a/crates/ide_assists/src/handlers/auto_import.rs b/crates/ide_assists/src/handlers/auto_import.rs
index cac736ff85..9c0233b028 100644
--- a/crates/ide_assists/src/handlers/auto_import.rs
+++ b/crates/ide_assists/src/handlers/auto_import.rs
@@ -3,7 +3,7 @@ use ide_db::helpers::{
     insert_use::{insert_use, ImportScope},
     mod_path_to_ast,
 };
-use syntax::{ast, AstNode, AstToken, NodeOrToken, SyntaxElement};
+use syntax::{ast, AstNode, NodeOrToken, SyntaxElement};
 
 use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel};
 
@@ -139,9 +139,7 @@ pub(super) fn find_importable_node(ctx: &AssistContext) -> Option<(ImportAssets,
     {
         ImportAssets::for_ident_pat(&ctx.sema, &pat).zip(Some(pat.syntax().clone().into()))
     } else {
-        // FIXME: Descend?
-        let ident = ctx.find_token_at_offset()?;
-        ImportAssets::for_derive_ident(&ctx.sema, &ident).zip(Some(ident.syntax().clone().into()))
+        None
     }
 }
 
diff --git a/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs b/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs
index b3723710a8..8ac05bf5ff 100644
--- a/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs
+++ b/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs
@@ -1,13 +1,13 @@
-use hir::ModuleDef;
-use ide_db::helpers::insert_whitespace_into_node::insert_ws_into;
-use ide_db::helpers::{
-    get_path_at_cursor_in_tt, import_assets::NameToImport, mod_path_to_ast,
-    parse_tt_as_comma_sep_paths,
+use hir::{InFile, ModuleDef};
+use ide_db::{
+    helpers::{
+        import_assets::NameToImport, insert_whitespace_into_node::insert_ws_into, mod_path_to_ast,
+    },
+    items_locator,
 };
-use ide_db::items_locator;
 use itertools::Itertools;
 use syntax::{
-    ast::{self, AstNode, AstToken, HasName},
+    ast::{self, AstNode, HasName},
     SyntaxKind::WHITESPACE,
 };
 
@@ -25,6 +25,7 @@ use crate::{
 // Converts a `derive` impl into a manual one.
 //
 // ```
+// # //- minicore: derive
 // # trait Debug { fn fmt(&self, f: &mut Formatter) -> Result<()>; }
 // #[derive(Deb$0ug, Display)]
 // struct S;
@@ -45,20 +46,30 @@ pub(crate) fn replace_derive_with_manual_impl(
     acc: &mut Assists,
     ctx: &AssistContext,
 ) -> Option<()> {
-    let attr = ctx.find_node_at_offset::<ast::Attr>()?;
-    let (name, args) = attr.as_simple_call()?;
-    if name != "derive" {
+    let attr = ctx.find_node_at_offset_with_descend::<ast::Attr>()?;
+    let path = attr.path()?;
+    let hir_file = ctx.sema.hir_file_for(attr.syntax());
+    if !hir_file.is_derive_attr_macro(ctx.db()) {
         return None;
     }
 
-    if !args.syntax().text_range().contains(ctx.offset()) {
-        cov_mark::hit!(outside_of_attr_args);
+    let InFile { file_id, value } = hir_file.call_node(ctx.db())?;
+    if file_id.is_macro() {
+        // FIXME: make this work in macro files
         return None;
     }
+    // collect the derive paths from the #[derive] expansion
+    let current_derives = ctx
+        .sema
+        .parse_or_expand(hir_file)?
+        .descendants()
+        .filter_map(ast::Attr::cast)
+        .filter_map(|attr| attr.path())
+        .collect::<Vec<_>>();
 
-    let ident = args.syntax().token_at_offset(ctx.offset()).find_map(ast::Ident::cast)?;
-    let trait_path = get_path_at_cursor_in_tt(&ident)?;
-    let adt = attr.syntax().parent().and_then(ast::Adt::cast)?;
+    let adt = value.parent().and_then(ast::Adt::cast)?;
+    let attr = ast::Attr::cast(value)?;
+    let args = attr.token_tree()?;
 
     let current_module = ctx.sema.scope(adt.syntax()).module()?;
     let current_crate = current_module.krate();
@@ -66,7 +77,7 @@ pub(crate) fn replace_derive_with_manual_impl(
     let found_traits = items_locator::items_with_name(
         &ctx.sema,
         current_crate,
-        NameToImport::exact_case_sensitive(trait_path.segments().last()?.to_string()),
+        NameToImport::exact_case_sensitive(path.segments().last()?.to_string()),
         items_locator::AssocItemSearch::Exclude,
         Some(items_locator::DEFAULT_QUERY_SEARCH_LIMIT.inner()),
     )
@@ -83,8 +94,6 @@ pub(crate) fn replace_derive_with_manual_impl(
     });
 
     let mut no_traits_found = true;
-    let current_derives = parse_tt_as_comma_sep_paths(args.clone())?;
-    let current_derives = current_derives.as_slice();
     for (replace_trait_path, trait_) in found_traits.inspect(|_| no_traits_found = false) {
         add_assist(
             acc,
@@ -92,14 +101,14 @@ pub(crate) fn replace_derive_with_manual_impl(
             &attr,
             &current_derives,
             &args,
-            &trait_path,
+            &path,
             &replace_trait_path,
             Some(trait_),
             &adt,
         )?;
     }
     if no_traits_found {
-        add_assist(acc, ctx, &attr, &current_derives, &args, &trait_path, &trait_path, None, &adt)?;
+        add_assist(acc, ctx, &attr, &current_derives, &args, &path, &path, None, &adt)?;
     }
     Some(())
 }
@@ -128,7 +137,7 @@ fn add_assist(
             let impl_def_with_items =
                 impl_def_from_trait(&ctx.sema, adt, &annotated_name, trait_, replace_trait_path);
             update_attribute(builder, old_derives, old_tree, old_trait_path, attr);
-            let trait_path = format!("{}", replace_trait_path);
+            let trait_path = replace_trait_path.to_string();
             match (ctx.config.snippet_cap, impl_def_with_items) {
                 (None, _) => {
                     builder.insert(insert_pos, generate_trait_impl_text(adt, &trait_path, ""))
@@ -258,7 +267,7 @@ mod tests {
         check_assist(
             replace_derive_with_manual_impl,
             r#"
-//- minicore: fmt
+//- minicore: fmt, derive
 #[derive(Debu$0g)]
 struct Foo {
     bar: String,
@@ -282,7 +291,7 @@ impl core::fmt::Debug for Foo {
         check_assist(
             replace_derive_with_manual_impl,
             r#"
-//- minicore: fmt
+//- minicore: fmt, derive
 #[derive(Debu$0g)]
 struct Foo(String, usize);
 "#,
@@ -301,7 +310,7 @@ impl core::fmt::Debug for Foo {
         check_assist(
             replace_derive_with_manual_impl,
             r#"
-//- minicore: fmt
+//- minicore: fmt, derive
 #[derive(Debu$0g)]
 struct Foo;
 "#,
@@ -321,7 +330,7 @@ impl core::fmt::Debug for Foo {
         check_assist(
             replace_derive_with_manual_impl,
             r#"
-//- minicore: fmt
+//- minicore: fmt, derive
 #[derive(Debu$0g)]
 enum Foo {
     Bar,
@@ -351,7 +360,7 @@ impl core::fmt::Debug for Foo {
         check_assist(
             replace_derive_with_manual_impl,
             r#"
-//- minicore: fmt
+//- minicore: fmt, derive
 #[derive(Debu$0g)]
 enum Foo {
     Bar(usize, usize),
@@ -380,7 +389,7 @@ impl core::fmt::Debug for Foo {
         check_assist(
             replace_derive_with_manual_impl,
             r#"
-//- minicore: fmt
+//- minicore: fmt, derive
 #[derive(Debu$0g)]
 enum Foo {
     Bar {
@@ -415,7 +424,7 @@ impl core::fmt::Debug for Foo {
         check_assist(
             replace_derive_with_manual_impl,
             r#"
-//- minicore: default
+//- minicore: default, derive
 #[derive(Defau$0lt)]
 struct Foo {
     foo: usize,
@@ -439,7 +448,7 @@ impl Default for Foo {
         check_assist(
             replace_derive_with_manual_impl,
             r#"
-//- minicore: default
+//- minicore: default, derive
 #[derive(Defau$0lt)]
 struct Foo(usize);
 "#,
@@ -459,7 +468,7 @@ impl Default for Foo {
         check_assist(
             replace_derive_with_manual_impl,
             r#"
-//- minicore: default
+//- minicore: default, derive
 #[derive(Defau$0lt)]
 struct Foo;
 "#,
@@ -480,7 +489,7 @@ impl Default for Foo {
         check_assist(
             replace_derive_with_manual_impl,
             r#"
-//- minicore: hash
+//- minicore: hash, derive
 #[derive(Has$0h)]
 struct Foo {
     bin: usize,
@@ -508,7 +517,7 @@ impl core::hash::Hash for Foo {
         check_assist(
             replace_derive_with_manual_impl,
             r#"
-//- minicore: hash
+//- minicore: hash, derive
 #[derive(Has$0h)]
 struct Foo(usize, usize);
 "#,
@@ -530,7 +539,7 @@ impl core::hash::Hash for Foo {
         check_assist(
             replace_derive_with_manual_impl,
             r#"
-//- minicore: hash
+//- minicore: hash, derive
 #[derive(Has$0h)]
 enum Foo {
     Bar,
@@ -557,7 +566,7 @@ impl core::hash::Hash for Foo {
         check_assist(
             replace_derive_with_manual_impl,
             r#"
-//- minicore: clone
+//- minicore: clone, derive
 #[derive(Clo$0ne)]
 struct Foo {
     bin: usize,
@@ -584,7 +593,7 @@ impl Clone for Foo {
         check_assist(
             replace_derive_with_manual_impl,
             r#"
-//- minicore: clone
+//- minicore: clone, derive
 #[derive(Clo$0ne)]
 struct Foo(usize, usize);
 "#,
@@ -605,7 +614,7 @@ impl Clone for Foo {
         check_assist(
             replace_derive_with_manual_impl,
             r#"
-//- minicore: clone
+//- minicore: clone, derive
 #[derive(Clo$0ne)]
 struct Foo;
 "#,
@@ -626,7 +635,7 @@ impl Clone for Foo {
         check_assist(
             replace_derive_with_manual_impl,
             r#"
-//- minicore: clone
+//- minicore: clone, derive
 #[derive(Clo$0ne)]
 enum Foo {
     Bar,
@@ -656,7 +665,7 @@ impl Clone for Foo {
         check_assist(
             replace_derive_with_manual_impl,
             r#"
-//- minicore: clone
+//- minicore: clone, derive
 #[derive(Clo$0ne)]
 enum Foo {
     Bar(String),
@@ -686,7 +695,7 @@ impl Clone for Foo {
         check_assist(
             replace_derive_with_manual_impl,
             r#"
-//- minicore: clone
+//- minicore: clone, derive
 #[derive(Clo$0ne)]
 enum Foo {
     Bar {
@@ -720,7 +729,7 @@ impl Clone for Foo {
         check_assist(
             replace_derive_with_manual_impl,
             r#"
-//- minicore: ord
+//- minicore: ord, derive
 #[derive(Partial$0Ord)]
 struct Foo {
     bin: usize,
@@ -745,7 +754,7 @@ impl PartialOrd for Foo {
         check_assist(
             replace_derive_with_manual_impl,
             r#"
-//- minicore: ord
+//- minicore: ord, derive
 #[derive(Partial$0Ord)]
 struct Foo {
     bin: usize,
@@ -782,7 +791,7 @@ impl PartialOrd for Foo {
         check_assist(
             replace_derive_with_manual_impl,
             r#"
-//- minicore: ord
+//- minicore: ord, derive
 #[derive(Partial$0Ord)]
 struct Foo(usize, usize, usize);
 "#,
@@ -811,7 +820,7 @@ impl PartialOrd for Foo {
         check_assist(
             replace_derive_with_manual_impl,
             r#"
-//- minicore: eq
+//- minicore: eq, derive
 #[derive(Partial$0Eq)]
 struct Foo {
     bin: usize,
@@ -838,7 +847,7 @@ impl PartialEq for Foo {
         check_assist(
             replace_derive_with_manual_impl,
             r#"
-//- minicore: eq
+//- minicore: eq, derive
 #[derive(Partial$0Eq)]
 struct Foo(usize, usize);
 "#,
@@ -859,7 +868,7 @@ impl PartialEq for Foo {
         check_assist(
             replace_derive_with_manual_impl,
             r#"
-//- minicore: eq
+//- minicore: eq, derive
 #[derive(Partial$0Eq)]
 struct Foo;
 "#,
@@ -880,7 +889,7 @@ impl PartialEq for Foo {
         check_assist(
             replace_derive_with_manual_impl,
             r#"
-//- minicore: eq
+//- minicore: eq, derive
 #[derive(Partial$0Eq)]
 enum Foo {
     Bar,
@@ -907,7 +916,7 @@ impl PartialEq for Foo {
         check_assist(
             replace_derive_with_manual_impl,
             r#"
-//- minicore: eq
+//- minicore: eq, derive
 #[derive(Partial$0Eq)]
 enum Foo {
     Bar(String),
@@ -937,7 +946,7 @@ impl PartialEq for Foo {
         check_assist(
             replace_derive_with_manual_impl,
             r#"
-//- minicore: eq
+//- minicore: eq, derive
 #[derive(Partial$0Eq)]
 enum Foo {
     Bar {
@@ -981,6 +990,7 @@ impl PartialEq for Foo {
         check_assist(
             replace_derive_with_manual_impl,
             r#"
+//- minicore: derive
 mod foo {
     pub trait Bar {
         type Qux;
@@ -1026,10 +1036,11 @@ impl foo::Bar for Foo {
         )
     }
     #[test]
-    fn add_custom_impl_for_unique_input() {
+    fn add_custom_impl_for_unique_input_unknown() {
         check_assist(
             replace_derive_with_manual_impl,
             r#"
+//- minicore: derive
 #[derive(Debu$0g)]
 struct Foo {
     bar: String,
@@ -1052,6 +1063,7 @@ impl Debug for Foo {
         check_assist(
             replace_derive_with_manual_impl,
             r#"
+//- minicore: derive
 #[derive(Debug$0)]
 pub struct Foo {
     bar: String,
@@ -1074,6 +1086,7 @@ impl Debug for Foo {
         check_assist(
             replace_derive_with_manual_impl,
             r#"
+//- minicore: derive
 #[derive(Display, Debug$0, Serialize)]
 struct Foo {}
             "#,
@@ -1093,7 +1106,7 @@ impl Debug for Foo {
         check_assist(
             replace_derive_with_manual_impl,
             r#"
-//- minicore: default
+//- minicore: default, derive
 #[derive(Defau$0lt)]
 struct Foo<T, U> {
     foo: T,
@@ -1120,7 +1133,7 @@ impl<T, U> Default for Foo<T, U> {
         check_assist(
             replace_derive_with_manual_impl,
             r#"
-//- minicore: clone
+//- minicore: clone, derive
 #[derive(Clo$0ne)]
 struct Foo<T: Clone>(T, usize);
 "#,
@@ -1141,6 +1154,7 @@ impl<T: Clone> Clone for Foo<T> {
         check_assist_not_applicable(
             replace_derive_with_manual_impl,
             r#"
+//- minicore: derive
 #[derive($0)]
 struct Foo {}
             "#,
@@ -1152,6 +1166,7 @@ struct Foo {}
         check_assist_not_applicable(
             replace_derive_with_manual_impl,
             r#"
+//- minicore: derive, fmt
 #[derive$0(Debug)]
 struct Foo {}
             "#,
@@ -1160,6 +1175,7 @@ struct Foo {}
         check_assist_not_applicable(
             replace_derive_with_manual_impl,
             r#"
+//- minicore: derive, fmt
 #[derive(Debug)$0]
 struct Foo {}
             "#,
@@ -1171,6 +1187,7 @@ struct Foo {}
         check_assist_not_applicable(
             replace_derive_with_manual_impl,
             r#"
+//- minicore: derive
 #[allow(non_camel_$0case_types)]
 struct Foo {}
             "#,
@@ -1179,10 +1196,10 @@ struct Foo {}
 
     #[test]
     fn works_at_start_of_file() {
-        cov_mark::check!(outside_of_attr_args);
         check_assist_not_applicable(
             replace_derive_with_manual_impl,
             r#"
+//- minicore: derive, fmt
 $0#[derive(Debug)]
 struct S;
             "#,
@@ -1194,7 +1211,7 @@ struct S;
         check_assist(
             replace_derive_with_manual_impl,
             r#"
-//- minicore: clone
+//- minicore: clone, derive
 #[derive(std::fmt::Debug, Clo$0ne)]
 pub struct Foo;
 "#,
@@ -1216,7 +1233,7 @@ impl Clone for Foo {
         check_assist(
             replace_derive_with_manual_impl,
             r#"
-//- minicore: fmt
+//- minicore: fmt, derive
 #[derive(core::fmt::Deb$0ug, Clone)]
 pub struct Foo;
 "#,
diff --git a/crates/ide_completion/src/completions/trait_impl.rs b/crates/ide_completion/src/completions/trait_impl.rs
index 4b1de8058d..0d59f77a55 100644
--- a/crates/ide_completion/src/completions/trait_impl.rs
+++ b/crates/ide_completion/src/completions/trait_impl.rs
@@ -58,15 +58,15 @@ pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext
                     (
                         hir::AssocItem::Function(fn_item),
                         ImplCompletionKind::All | ImplCompletionKind::Fn,
-                    ) => add_function_impl(&trigger, acc, ctx, fn_item, hir_impl),
+                    ) => add_function_impl(acc, ctx, &trigger, fn_item, hir_impl),
                     (
                         hir::AssocItem::TypeAlias(type_item),
                         ImplCompletionKind::All | ImplCompletionKind::TypeAlias,
-                    ) => add_type_alias_impl(&trigger, acc, ctx, type_item),
+                    ) => add_type_alias_impl(acc, ctx, &trigger, type_item),
                     (
                         hir::AssocItem::Const(const_item),
                         ImplCompletionKind::All | ImplCompletionKind::Const,
-                    ) => add_const_impl(&trigger, acc, ctx, const_item, hir_impl),
+                    ) => add_const_impl(acc, ctx, &trigger, const_item, hir_impl),
                     _ => {}
                 }
             });
@@ -126,9 +126,9 @@ fn completion_match(mut token: SyntaxToken) -> Option<(ImplCompletionKind, Synta
 }
 
 fn add_function_impl(
-    fn_def_node: &SyntaxNode,
     acc: &mut Completions,
     ctx: &CompletionContext,
+    fn_def_node: &SyntaxNode,
     func: hir::Function,
     impl_def: hir::Impl,
 ) {
@@ -199,9 +199,9 @@ fn get_transformed_assoc_item(
 }
 
 fn add_type_alias_impl(
-    type_def_node: &SyntaxNode,
     acc: &mut Completions,
     ctx: &CompletionContext,
+    type_def_node: &SyntaxNode,
     type_alias: hir::TypeAlias,
 ) {
     let alias_name = type_alias.name(ctx.db).to_smol_str();
@@ -217,9 +217,9 @@ fn add_type_alias_impl(
 }
 
 fn add_const_impl(
-    const_def_node: &SyntaxNode,
     acc: &mut Completions,
     ctx: &CompletionContext,
+    const_def_node: &SyntaxNode,
     const_: hir::Const,
     impl_def: hir::Impl,
 ) {
diff --git a/crates/ide_db/src/defs.rs b/crates/ide_db/src/defs.rs
index 5a4cfe6e94..08104efcdc 100644
--- a/crates/ide_db/src/defs.rs
+++ b/crates/ide_db/src/defs.rs
@@ -14,7 +14,7 @@ use hir::{
 use stdx::impl_from;
 use syntax::{
     ast::{self, AstNode},
-    match_ast, AstToken, SyntaxKind, SyntaxNode, SyntaxToken,
+    match_ast, SyntaxKind, SyntaxNode, SyntaxToken,
 };
 
 use crate::RootDatabase;
@@ -142,16 +142,6 @@ impl IdentClass {
         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)
     }
 
@@ -461,14 +451,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.rs b/crates/ide_db/src/helpers.rs
index c355016c5d..cbe4adf1b9 100644
--- a/crates/ide_db/src/helpers.rs
+++ b/crates/ide_db/src/helpers.rs
@@ -9,15 +9,14 @@ pub mod node_ext;
 pub mod rust_doc;
 pub mod format_string;
 
-use std::{collections::VecDeque, iter};
+use std::collections::VecDeque;
 
 use base_db::FileId;
-use hir::{ItemInNs, MacroDef, ModuleDef, Name, PathResolution, Semantics};
+use hir::{ItemInNs, MacroDef, ModuleDef, Name, Semantics};
 use itertools::Itertools;
 use syntax::{
     ast::{self, make, HasLoopBody},
-    AstNode, AstToken, Direction, SyntaxElement, SyntaxKind, SyntaxToken, TokenAtOffset, WalkEvent,
-    T,
+    AstNode, AstToken, SyntaxKind, SyntaxToken, TokenAtOffset, WalkEvent, T,
 };
 
 use crate::{defs::Definition, RootDatabase};
@@ -32,49 +31,6 @@ pub fn item_name(db: &RootDatabase, item: ItemInNs) -> Option<Name> {
     }
 }
 
-/// Parses and returns the derive path at the cursor position in the given attribute, if it is a derive.
-/// This special case is required because the derive macro is a compiler builtin that discards the input derives.
-///
-/// The returned path is synthesized from TokenTree tokens and as such cannot be used with the [`Semantics`].
-pub fn get_path_in_derive_attr(
-    sema: &hir::Semantics<RootDatabase>,
-    attr: &ast::Attr,
-    cursor: &ast::Ident,
-) -> Option<ast::Path> {
-    let path = attr.path()?;
-    let tt = attr.token_tree()?;
-    if !tt.syntax().text_range().contains_range(cursor.syntax().text_range()) {
-        return None;
-    }
-    let scope = sema.scope(attr.syntax());
-    let resolved_attr = sema.resolve_path(&path)?;
-    let derive = FamousDefs(sema, scope.krate()).core_macros_builtin_derive()?;
-    if PathResolution::Macro(derive) != resolved_attr {
-        return None;
-    }
-    get_path_at_cursor_in_tt(cursor)
-}
-
-/// Parses the path the identifier is part of inside a token tree.
-pub fn get_path_at_cursor_in_tt(cursor: &ast::Ident) -> Option<ast::Path> {
-    let cursor = cursor.syntax();
-    let first = cursor
-        .siblings_with_tokens(Direction::Prev)
-        .filter_map(SyntaxElement::into_token)
-        .take_while(|tok| tok.kind() != T!['('] && tok.kind() != T![,])
-        .last()?;
-    let path_tokens = first
-        .siblings_with_tokens(Direction::Next)
-        .filter_map(SyntaxElement::into_token)
-        .take_while(|tok| tok != cursor);
-
-    syntax::hacks::parse_expr_from_str(&path_tokens.chain(iter::once(cursor.clone())).join(""))
-        .and_then(|expr| match expr {
-            ast::Expr::PathExpr(it) => it.path(),
-            _ => None,
-        })
-}
-
 /// Picks the token with the highest rank returned by the passed in function.
 pub fn pick_best_token(
     tokens: TokenAtOffset<SyntaxToken>,
diff --git a/crates/ide_db/src/helpers/import_assets.rs b/crates/ide_db/src/helpers/import_assets.rs
index c037c3e0f8..319a217352 100644
--- a/crates/ide_db/src/helpers/import_assets.rs
+++ b/crates/ide_db/src/helpers/import_assets.rs
@@ -8,11 +8,10 @@ use rustc_hash::FxHashSet;
 use syntax::{
     ast::{self, HasName},
     utils::path_to_string_stripping_turbo_fish,
-    AstNode, AstToken, SyntaxNode,
+    AstNode, SyntaxNode,
 };
 
 use crate::{
-    helpers::get_path_in_derive_attr,
     items_locator::{self, AssocItemSearch, DEFAULT_QUERY_SEARCH_LIMIT},
     RootDatabase,
 };
@@ -139,23 +138,6 @@ impl ImportAssets {
         })
     }
 
-    pub fn for_derive_ident(sema: &Semantics<RootDatabase>, ident: &ast::Ident) -> Option<Self> {
-        let attr = ident.syntax().ancestors().find_map(ast::Attr::cast)?;
-        let path = get_path_in_derive_attr(sema, &attr, ident)?;
-
-        if let Some(_) = path.qualifier() {
-            return None;
-        }
-
-        let name = NameToImport::exact_case_sensitive(path.segment()?.name_ref()?.to_string());
-        let candidate_node = attr.syntax().clone();
-        Some(Self {
-            import_candidate: ImportCandidate::Path(PathImportCandidate { qualifier: None, name }),
-            module_with_candidate: sema.scope(&candidate_node).module()?,
-            candidate_node,
-        })
-    }
-
     pub fn for_fuzzy_path(
         module_with_candidate: Module,
         qualifier: Option<ast::Path>,
diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs
index 5ff6519c9c..1b916e91bf 100644
--- a/crates/syntax/src/ast/node_ext.rs
+++ b/crates/syntax/src/ast/node_ext.rs
@@ -705,6 +705,15 @@ impl ast::RangePat {
 }
 
 impl ast::TokenTree {
+    pub fn token_trees_and_tokens(
+        &self,
+    ) -> impl Iterator<Item = NodeOrToken<ast::TokenTree, SyntaxToken>> {
+        self.syntax().children_with_tokens().filter_map(|not| match not {
+            NodeOrToken::Node(node) => ast::TokenTree::cast(node).map(NodeOrToken::Node),
+            NodeOrToken::Token(t) => Some(NodeOrToken::Token(t)),
+        })
+    }
+
     pub fn left_delimiter_token(&self) -> Option<SyntaxToken> {
         self.syntax()
             .first_child_or_token()?
diff --git a/docs/dev/architecture.md b/docs/dev/architecture.md
index 2f2c4351c7..99ddc188d8 100644
--- a/docs/dev/architecture.md
+++ b/docs/dev/architecture.md
@@ -111,7 +111,7 @@ env UPDATE_EXPECT=1 cargo qt
 
 After adding a new inline test you need to run `cargo test -p xtask` and also update the test data as described above.
 
-Note  [`api_walkthrough`](https://github.com/rust-analyzer/rust-analyzer/blob/2fb6af89eb794f775de60b82afe56b6f986c2a40/crates/ra_syntax/src/lib.rs#L190-L348)
+Note [`api_walkthrough`](https://github.com/rust-analyzer/rust-analyzer/blob/2fb6af89eb794f775de60b82afe56b6f986c2a40/crates/ra_syntax/src/lib.rs#L190-L348)
 in particular: it shows off various methods of working with syntax tree.
 
 See [#93](https://github.com/rust-analyzer/rust-analyzer/pull/93) for an example PR which fixes a bug in the grammar.


Test patch:
diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs
index c5c531c30b..a232ebd4fb 100644
--- a/crates/ide/src/hover/tests.rs
+++ b/crates/ide/src/hover/tests.rs
@@ -15,6 +15,7 @@ fn check_hover_no_result(ra_fixture: &str) {
     assert!(hover.is_none(), "hover not expected but found: {:?}", hover.unwrap());
 }
 
+#[track_caller]
 fn check(ra_fixture: &str, expect: Expect) {
     let (analysis, position) = fixture::position(ra_fixture);
     let hover = analysis
diff --git a/crates/ide_assists/src/tests/generated.rs b/crates/ide_assists/src/tests/generated.rs
index 0ad4b3bc34..485b807d05 100644
--- a/crates/ide_assists/src/tests/generated.rs
+++ b/crates/ide_assists/src/tests/generated.rs
@@ -1766,6 +1766,7 @@ fn doctest_replace_derive_with_manual_impl() {
     check_doc_test(
         "replace_derive_with_manual_impl",
         r#####"
+//- minicore: derive
 trait Debug { fn fmt(&self, f: &mut Formatter) -> Result<()>; }
 #[derive(Deb$0ug, Display)]
 struct S;
diff --git a/crates/ide_completion/src/tests/attribute.rs b/crates/ide_completion/src/tests/attribute.rs
index 2e643453af..ae7ba7e055 100644
--- a/crates/ide_completion/src/tests/attribute.rs
+++ b/crates/ide_completion/src/tests/attribute.rs
@@ -735,6 +735,26 @@ mod derive {
         )
     }
 
+    #[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#""#]],
+        );
+    }
+
     #[test]
     fn derive_flyimport() {
         check_derive(