Repository: rust-analyzer/rust-analyzer
Pull request #11225 (2022-01-07 13:21:19)

internal: Cleanup doc and attribute handling
(very vague PR title but as I tried to fix the mentioned issue I ran into more and more subtle things that were interwoven)
Fixes https://github.com/rust-analyzer/rust-analyzer/issues/11215


Patch:
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index ee423573d5..c6d401a6d6 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -649,8 +649,12 @@ impl Module {
                             let node = ast_id.to_node(db.upcast());
                             ast_id.with_value(SyntaxNodePtr::from(AstPtr::new(&node)))
                         }
-                        MacroCallKind::Derive { ast_id, .. }
-                        | MacroCallKind::Attr { ast_id, .. } => {
+                        MacroCallKind::Derive { ast_id, .. } => {
+                            // FIXME: point to the attribute instead, this creates very large diagnostics
+                            let node = ast_id.to_node(db.upcast());
+                            ast_id.with_value(SyntaxNodePtr::from(AstPtr::new(&node)))
+                        }
+                        MacroCallKind::Attr { ast_id, .. } => {
                             // FIXME: point to the attribute instead, this creates very large diagnostics
                             let node = ast_id.to_node(db.upcast());
                             ast_id.with_value(SyntaxNodePtr::from(AstPtr::new(&node)))
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs
index b685d26081..444fb4ea54 100644
--- a/crates/hir/src/semantics.rs
+++ b/crates/hir/src/semantics.rs
@@ -18,7 +18,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
 use smallvec::{smallvec, SmallVec};
 use syntax::{
     algo::skip_trivia_token,
-    ast::{self, HasAttrs, HasGenericParams, HasLoopBody},
+    ast::{self, HasAttrs as _, HasGenericParams, HasLoopBody},
     match_ast, AstNode, AstToken, Direction, SyntaxElement, SyntaxNode, SyntaxNodePtr, SyntaxToken,
     TextSize, T,
 };
@@ -27,9 +27,9 @@ 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, 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, HasAttrs as _,
+    HasSource, HirFileId, Impl, InFile, Label, LifetimeParam, Local, MacroDef, Module, ModuleDef,
+    Name, Path, ScopeDef, ToolModule, Trait, Type, TypeAlias, TypeParam, VariantDef,
 };
 
 #[derive(Debug, Clone, PartialEq, Eq)]
@@ -486,7 +486,7 @@ 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())
         })
     }
@@ -917,15 +917,14 @@ impl<'db> SemanticsImpl<'db> {
         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_def = ctx.attr_to_def(file.with_value(derive.clone()))?;
-            let derives = ctx.attr_to_derive_macro_call(
+            let (attr_id, derives) = ctx.attr_to_derive_macro_call(
                 file.with_value(&adt),
                 file.with_value(derive.clone()),
             )?;
-
-            let mut derive_paths = attr_def.parse_path_comma_token_tree()?;
+            let attrs = adt_def.attrs(self.db);
+            let mut derive_paths = attrs[attr_id].parse_path_comma_token_tree()?;
 
             let derive_idx = tt
                 .syntax()
@@ -1225,7 +1224,6 @@ to_def_impls![
     (crate::Local, ast::SelfParam, self_param_to_def),
     (crate::Label, ast::Label, label_to_def),
     (crate::Adt, ast::Adt, adt_to_def),
-    (crate::Attr, ast::Attr, attr_to_def),
 ];
 
 fn find_root(node: &SyntaxNode) -> SyntaxNode {
diff --git a/crates/hir/src/semantics/source_to_def.rs b/crates/hir/src/semantics/source_to_def.rs
index fbce53eb16..f9c6564d01 100644
--- a/crates/hir/src/semantics/source_to_def.rs
+++ b/crates/hir/src/semantics/source_to_def.rs
@@ -87,6 +87,7 @@
 
 use base_db::FileId;
 use hir_def::{
+    attr::AttrId,
     child_by_source::ChildBySource,
     dyn_map::DynMap,
     expr::{LabelId, PatId},
@@ -210,19 +211,6 @@ impl SourceToDefCtx<'_, '_> {
             ast::Adt::Union(it) => self.union_to_def(InFile::new(file_id, it)).map(AdtId::UnionId),
         }
     }
-    pub(super) fn attr_to_def(
-        &mut self,
-        InFile { file_id, value }: InFile<ast::Attr>,
-    ) -> Option<crate::Attr> {
-        // FIXME: Use dynmap?
-        let adt = value.syntax().parent().and_then(ast::Adt::cast)?;
-        let attr_pos = ast::HasAttrs::attrs(&adt).position(|it| it == value)?;
-        let attrs = {
-            let def = self.adt_to_def(InFile::new(file_id, adt))?;
-            self.db.attrs(def.into())
-        };
-        attrs.get(attr_pos).cloned()
-    }
     pub(super) fn bind_pat_to_def(
         &mut self,
         src: InFile<ast::IdentPat>,
@@ -254,16 +242,16 @@ impl SourceToDefCtx<'_, '_> {
 
     pub(super) fn item_to_macro_call(&mut self, src: InFile<ast::Item>) -> Option<MacroCallId> {
         let map = self.dyn_map(src.as_ref())?;
-        map[keys::ATTR_MACRO].get(&src).copied()
+        map[keys::ATTR_MACRO_CALL].get(&src).copied()
     }
 
     pub(super) fn attr_to_derive_macro_call(
         &mut self,
         item: InFile<&ast::Adt>,
         src: InFile<ast::Attr>,
-    ) -> Option<&[Option<MacroCallId>]> {
+    ) -> Option<(AttrId, &[Option<MacroCallId>])> {
         let map = self.dyn_map(item)?;
-        map[keys::DERIVE_MACRO].get(&src).map(AsRef::as_ref)
+        map[keys::DERIVE_MACRO_CALL].get(&src).map(|(id, ids)| (*id, &**ids))
     }
 
     fn to_def<Ast: AstNode + 'static, ID: Copy + 'static>(
@@ -328,7 +316,8 @@ impl SourceToDefCtx<'_, '_> {
     }
 
     pub(super) fn macro_to_def(&mut self, src: InFile<ast::Macro>) -> Option<MacroDefId> {
-        let makro = self.dyn_map(src.as_ref()).and_then(|it| it[keys::MACRO].get(&src).copied());
+        let makro =
+            self.dyn_map(src.as_ref()).and_then(|it| it[keys::MACRO_CALL].get(&src).copied());
         if let res @ Some(_) = makro {
             return res;
         }
diff --git a/crates/hir_def/src/attr.rs b/crates/hir_def/src/attr.rs
index b0be892c7d..30642f6cc6 100644
--- a/crates/hir_def/src/attr.rs
+++ b/crates/hir_def/src/attr.rs
@@ -5,7 +5,7 @@ use std::{fmt, hash::Hash, ops, sync::Arc};
 use base_db::CrateId;
 use cfg::{CfgExpr, CfgOptions};
 use either::Either;
-use hir_expand::{hygiene::Hygiene, name::AsName, AstId, InFile};
+use hir_expand::{hygiene::Hygiene, name::AsName, AstId, HirFileId, InFile};
 use itertools::Itertools;
 use la_arena::ArenaMap;
 use mbe::{syntax_node_to_token_tree, DelimiterKind, Punct};
@@ -84,6 +84,14 @@ impl ops::Deref for Attrs {
     }
 }
 
+impl ops::Index<AttrId> for Attrs {
+    type Output = Attr;
+
+    fn index(&self, AttrId { ast_index, .. }: AttrId) -> &Self::Output {
+        &(**self)[ast_index as usize]
+    }
+}
+
 impl ops::Deref for AttrsWithOwner {
     type Target = Attrs;
 
@@ -509,23 +517,23 @@ fn inner_attributes(
 ) -> Option<(impl Iterator<Item = ast::Attr>, impl Iterator<Item = ast::Comment>)> {
     let (attrs, docs) = match_ast! {
         match syntax {
-            ast::SourceFile(it) => (it.attrs(), ast::CommentIter::from_syntax_node(it.syntax())),
+            ast::SourceFile(it) => (it.attrs(), ast::DocCommentIter::from_syntax_node(it.syntax())),
             ast::ExternBlock(it) => {
                 let extern_item_list = it.extern_item_list()?;
-                (extern_item_list.attrs(), ast::CommentIter::from_syntax_node(extern_item_list.syntax()))
+                (extern_item_list.attrs(), ast::DocCommentIter::from_syntax_node(extern_item_list.syntax()))
             },
             ast::Fn(it) => {
                 let body = it.body()?;
                 let stmt_list = body.stmt_list()?;
-                (stmt_list.attrs(), ast::CommentIter::from_syntax_node(body.syntax()))
+                (stmt_list.attrs(), ast::DocCommentIter::from_syntax_node(body.syntax()))
             },
             ast::Impl(it) => {
                 let assoc_item_list = it.assoc_item_list()?;
-                (assoc_item_list.attrs(), ast::CommentIter::from_syntax_node(assoc_item_list.syntax()))
+                (assoc_item_list.attrs(), ast::DocCommentIter::from_syntax_node(assoc_item_list.syntax()))
             },
             ast::Module(it) => {
                 let item_list = it.item_list()?;
-                (item_list.attrs(), ast::CommentIter::from_syntax_node(item_list.syntax()))
+                (item_list.attrs(), ast::DocCommentIter::from_syntax_node(item_list.syntax()))
             },
             // FIXME: BlockExpr's only accept inner attributes in specific cases
             // Excerpt from the reference:
@@ -542,27 +550,20 @@ fn inner_attributes(
 
 #[derive(Debug)]
 pub struct AttrSourceMap {
-    attrs: Vec<InFile<ast::Attr>>,
-    doc_comments: Vec<InFile<ast::Comment>>,
+    source: Vec<Either<ast::Attr, ast::Comment>>,
+    file_id: HirFileId,
 }
 
 impl AttrSourceMap {
     fn new(owner: InFile<&dyn ast::HasAttrs>) -> Self {
-        let mut attrs = Vec::new();
-        let mut doc_comments = Vec::new();
-        for (_, attr) in collect_attrs(owner.value) {
-            match attr {
-                Either::Left(attr) => attrs.push(owner.with_value(attr)),
-                Either::Right(comment) => doc_comments.push(owner.with_value(comment)),
-            }
+        Self {
+            source: collect_attrs(owner.value).map(|(_, it)| it).collect(),
+            file_id: owner.file_id,
         }
-
-        Self { attrs, doc_comments }
     }
 
     fn merge(&mut self, other: Self) {
-        self.attrs.extend(other.attrs);
-        self.doc_comments.extend(other.doc_comments);
+        self.source.extend(other.source);
     }
 
     /// Maps the lowered `Attr` back to its original syntax node.
@@ -571,24 +572,15 @@ impl AttrSourceMap {
     ///
     /// Note that the returned syntax node might be a `#[cfg_attr]`, or a doc comment, instead of
     /// the attribute represented by `Attr`.
-    pub fn source_of(&self, attr: &Attr) -> InFile<Either<ast::Attr, ast::Comment>> {
+    pub fn source_of(&self, attr: &Attr) -> InFile<&Either<ast::Attr, ast::Comment>> {
         self.source_of_id(attr.id)
     }
 
-    fn source_of_id(&self, id: AttrId) -> InFile<Either<ast::Attr, ast::Comment>> {
-        if id.is_doc_comment {
-            self.doc_comments
-                .get(id.ast_index as usize)
-                .unwrap_or_else(|| panic!("cannot find doc comment at index {:?}", id))
-                .clone()
-                .map(Either::Right)
-        } else {
-            self.attrs
-                .get(id.ast_index as usize)
-                .unwrap_or_else(|| panic!("cannot find `Attr` at index {:?}", id))
-                .clone()
-                .map(Either::Left)
-        }
+    fn source_of_id(&self, id: AttrId) -> InFile<&Either<ast::Attr, ast::Comment>> {
+        self.source
+            .get(id.ast_index as usize)
+            .map(|it| InFile::new(self.file_id, it))
+            .unwrap_or_else(|| panic!("cannot find attr at index {:?}", id))
     }
 }
 
@@ -656,8 +648,7 @@ fn get_doc_string_in_attr(it: &ast::Attr) -> Option<ast::String> {
 }
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-pub(crate) struct AttrId {
-    is_doc_comment: bool,
+pub struct AttrId {
     pub(crate) ast_index: u32,
 }
 
@@ -816,27 +807,20 @@ fn collect_attrs(
         .map_or((None, None), |(attrs, docs)| (Some(attrs), Some(docs)));
 
     let outer_attrs = owner.attrs().filter(|attr| attr.kind().is_outer());
-    let attrs =
-        outer_attrs.chain(inner_attrs.into_iter().flatten()).enumerate().map(|(idx, attr)| {
-            (
-                AttrId { ast_index: idx as u32, is_doc_comment: false },
-                attr.syntax().text_range().start(),
-                Either::Left(attr),
-            )
-        });
+    let attrs = outer_attrs
+        .chain(inner_attrs.into_iter().flatten())
+        .map(|attr| (attr.syntax().text_range().start(), Either::Left(attr)));
 
     let outer_docs =
-        ast::CommentIter::from_syntax_node(owner.syntax()).filter(ast::Comment::is_outer);
-    let docs =
-        outer_docs.chain(inner_docs.into_iter().flatten()).enumerate().map(|(idx, docs_text)| {
-            (
-                AttrId { ast_index: idx as u32, is_doc_comment: true },
-                docs_text.syntax().text_range().start(),
-                Either::Right(docs_text),
-            )
-        });
+        ast::DocCommentIter::from_syntax_node(owner.syntax()).filter(ast::Comment::is_outer);
+    let docs = outer_docs
+        .chain(inner_docs.into_iter().flatten())
+        .map(|docs_text| (docs_text.syntax().text_range().start(), Either::Right(docs_text)));
     // sort here by syntax node offset because the source can have doc attributes and doc strings be interleaved
-    docs.chain(attrs).sorted_by_key(|&(_, offset, _)| offset).map(|(id, _, attr)| (id, attr))
+    docs.chain(attrs)
+        .sorted_by_key(|&(offset, _)| offset)
+        .enumerate()
+        .map(|(id, (_, attr))| (AttrId { ast_index: id as u32 }, attr))
 }
 
 pub(crate) fn variants_attrs_source_map(
diff --git a/crates/hir_def/src/child_by_source.rs b/crates/hir_def/src/child_by_source.rs
index 7a9e414ece..d314b7fc01 100644
--- a/crates/hir_def/src/child_by_source.rs
+++ b/crates/hir_def/src/child_by_source.rs
@@ -6,7 +6,7 @@
 
 use either::Either;
 use hir_expand::HirFileId;
-use syntax::ast::HasAttrs;
+use syntax::ast::HasDocComments;
 
 use crate::{
     db::DefDatabase,
@@ -110,7 +110,7 @@ impl ChildBySource for ItemScope {
                     // FIXME: Do we need to add proc-macros into a PROCMACRO dynmap here?
                     Either::Right(_fn) => return,
                 };
-                res[keys::MACRO].insert(src, makro);
+                res[keys::MACRO_CALL].insert(src, makro);
             }
         });
         self.unnamed_consts().for_each(|konst| {
@@ -120,13 +120,16 @@ impl ChildBySource for ItemScope {
         self.impls().for_each(|imp| add_impl(db, file_id, res, imp));
         self.attr_macro_invocs().for_each(|(ast_id, call_id)| {
             let item = ast_id.with_value(ast_id.to_node(db.upcast()));
-            res[keys::ATTR_MACRO].insert(item, call_id);
+            res[keys::ATTR_MACRO_CALL].insert(item, call_id);
         });
         self.derive_macro_invocs().for_each(|(ast_id, calls)| {
-            let item = ast_id.to_node(db.upcast());
+            let adt = ast_id.to_node(db.upcast());
             for (attr_id, calls) in calls {
-                if let Some(attr) = item.attrs().nth(attr_id.ast_index as usize) {
-                    res[keys::DERIVE_MACRO].insert(ast_id.with_value(attr), calls.into());
+                if let Some(Either::Right(attr)) =
+                    adt.doc_comments_and_attrs().nth(attr_id.ast_index as usize)
+                {
+                    res[keys::DERIVE_MACRO_CALL]
+                        .insert(ast_id.with_value(attr), (attr_id, calls.into()));
                 }
             }
         });
diff --git a/crates/hir_def/src/item_scope.rs b/crates/hir_def/src/item_scope.rs
index 25369fdb26..258d1e0f6c 100644
--- a/crates/hir_def/src/item_scope.rs
+++ b/crates/hir_def/src/item_scope.rs
@@ -67,7 +67,7 @@ pub struct ItemScope {
     /// 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::Item>, SmallVec<[(AttrId, SmallVec<[Option<MacroCallId>; 1]>); 1]>>,
+        FxHashMap<AstId<ast::Adt>, SmallVec<[(AttrId, SmallVec<[Option<MacroCallId>; 1]>); 1]>>,
 }
 
 pub(crate) static BUILTIN_SCOPE: Lazy<FxHashMap<Name, PerNs>> = Lazy::new(|| {
@@ -204,12 +204,12 @@ impl ItemScope {
 
     pub(crate) fn set_derive_macro_invoc(
         &mut self,
-        item: AstId<ast::Item>,
+        adt: AstId<ast::Adt>,
         call: MacroCallId,
         attr_id: AttrId,
         idx: usize,
     ) {
-        if let Some(derives) = self.derive_macros.get_mut(&item) {
+        if let Some(derives) = self.derive_macros.get_mut(&adt) {
             if let Some((_, invocs)) = derives.iter_mut().find(|&&mut (id, _)| id == attr_id) {
                 invocs[idx] = Some(call);
             }
@@ -221,17 +221,17 @@ impl ItemScope {
     /// independent of their indices.
     pub(crate) fn init_derive_attribute(
         &mut self,
-        item: AstId<ast::Item>,
+        adt: AstId<ast::Adt>,
         attr_id: AttrId,
         len: usize,
     ) {
-        self.derive_macros.entry(item).or_default().push((attr_id, smallvec![None; len]));
+        self.derive_macros.entry(adt).or_default().push((attr_id, smallvec![None; len]));
     }
 
     pub(crate) fn derive_macro_invocs(
         &self,
     ) -> impl Iterator<
-        Item = (AstId<ast::Item>, impl Iterator<Item = (AttrId, &[Option<MacroCallId>])>),
+        Item = (AstId<ast::Adt>, impl Iterator<Item = (AttrId, &[Option<MacroCallId>])>),
     > + '_ {
         self.derive_macros
             .iter()
diff --git a/crates/hir_def/src/keys.rs b/crates/hir_def/src/keys.rs
index 6a7dc13ff1..3a9cf6eb81 100644
--- a/crates/hir_def/src/keys.rs
+++ b/crates/hir_def/src/keys.rs
@@ -7,6 +7,7 @@ use rustc_hash::FxHashMap;
 use syntax::{ast, AstNode, AstPtr};
 
 use crate::{
+    attr::AttrId,
     dyn_map::{DynMap, Policy},
     ConstId, ConstParamId, EnumId, EnumVariantId, FieldId, FunctionId, ImplId, LifetimeParamId,
     StaticId, StructId, TraitId, TypeAliasId, TypeParamId, UnionId,
@@ -31,9 +32,9 @@ pub const TYPE_PARAM: Key<ast::TypeParam, TypeParamId> = Key::new();
 pub const LIFETIME_PARAM: Key<ast::LifetimeParam, LifetimeParamId> = Key::new();
 pub const CONST_PARAM: Key<ast::ConstParam, ConstParamId> = Key::new();
 
-pub const MACRO: Key<ast::Macro, MacroDefId> = Key::new();
-pub const ATTR_MACRO: Key<ast::Item, MacroCallId> = Key::new();
-pub const DERIVE_MACRO: Key<ast::Attr, Box<[Option<MacroCallId>]>> = Key::new();
+pub const MACRO_CALL: 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();
 
 /// XXX: AST Nodes and SyntaxNodes have identity equality semantics: nodes are
 /// equal if they point to exactly the same object.
diff --git a/crates/hir_def/src/lib.rs b/crates/hir_def/src/lib.rs
index 730523252c..c38b498b9c 100644
--- a/crates/hir_def/src/lib.rs
+++ b/crates/hir_def/src/lib.rs
@@ -751,7 +751,7 @@ fn macro_call_as_call_id(
 }
 
 fn derive_macro_as_call_id(
-    item_attr: &AstIdWithPath<ast::Item>,
+    item_attr: &AstIdWithPath<ast::Adt>,
     derive_attr: AttrId,
     db: &dyn db::DefDatabase,
     krate: CrateId,
diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs
index e8246d4fb9..dc75dc4d91 100644
--- a/crates/hir_def/src/nameres/collector.rs
+++ b/crates/hir_def/src/nameres/collector.rs
@@ -8,6 +8,7 @@ use std::iter;
 use base_db::{CrateId, Edition, FileId, ProcMacroId};
 use cfg::{CfgExpr, CfgOptions};
 use hir_expand::{
+    ast_id_map::FileAstId,
     builtin_attr_macro::find_builtin_attr,
     builtin_derive_macro::find_builtin_derive,
     builtin_fn_macro::find_builtin_macro,
@@ -30,8 +31,8 @@ use crate::{
     intern::Interned,
     item_scope::{ImportType, PerNsGlobImports},
     item_tree::{
-        self, Fields, FileItemTreeId, ImportKind, ItemTree, ItemTreeId, MacroCall, MacroDef,
-        MacroRules, Mod, ModItem, ModKind, TreeId,
+        self, Fields, FileItemTreeId, ImportKind, ItemTree, ItemTreeId, ItemTreeNode, MacroCall,
+        MacroDef, MacroRules, Mod, ModItem, ModKind, TreeId,
     },
     macro_call_as_call_id,
     nameres::{
@@ -217,7 +218,7 @@ struct MacroDirective {
 #[derive(Clone, Debug, Eq, PartialEq)]
 enum MacroDirectiveKind {
     FnLike { ast_id: AstIdWithPath<ast::MacroCall>, expand_to: ExpandTo },
-    Derive { ast_id: AstIdWithPath<ast::Item>, derive_attr: AttrId, derive_pos: usize },
+    Derive { ast_id: AstIdWithPath<ast::Adt>, derive_attr: AttrId, derive_pos: usize },
     Attr { ast_id: AstIdWithPath<ast::Item>, attr: Attr, mod_item: ModItem, tree: TreeId },
 }
 
@@ -1129,8 +1130,11 @@ impl DefCollector<'_> {
                     ) {
                         // Resolved to `#[derive]`
 
-                        match mod_item {
-                            ModItem::Struct(_) | ModItem::Union(_) | ModItem::Enum(_) => (),
+                        let item_tree = tree.item_tree(self.db);
+                        let ast_adt_id: FileAstId<ast::Adt> = match *mod_item {
+                            ModItem::Struct(strukt) => item_tree[strukt].ast_id().upcast(),
+                            ModItem::Union(union) => item_tree[union].ast_id().upcast(),
+                            ModItem::Enum(enum_) => item_tree[enum_].ast_id().upcast(),
                             _ => {
                                 let diag = DefDiagnostic::invalid_derive_target(
                                     directive.module_id,
@@ -1140,7 +1144,8 @@ impl DefCollector<'_> {
                                 self.def_map.diagnostics.push(diag);
                                 return recollect_without(self);
                             }
-                        }
+                        };
+                        let ast_id = ast_id.with_value(ast_adt_id);
 
                         match attr.parse_path_comma_token_tree() {
                             Some(derive_macros) => {
@@ -1274,7 +1279,7 @@ impl DefCollector<'_> {
                 if let Some(def) = def_map.exported_proc_macros.get(&loc.def) {
                     if let ProcMacroKind::CustomDerive { helpers } = &def.kind {
                         self.derive_helpers_in_scope
-                            .entry(*ast_id)
+                            .entry(ast_id.map(|it| it.upcast()))
                             .or_default()
                             .extend(helpers.iter().cloned());
                     }
diff --git a/crates/hir_def/src/nameres/diagnostics.rs b/crates/hir_def/src/nameres/diagnostics.rs
index 51e3f18c11..60b8601bdd 100644
--- a/crates/hir_def/src/nameres/diagnostics.rs
+++ b/crates/hir_def/src/nameres/diagnostics.rs
@@ -33,7 +33,7 @@ pub enum DefDiagnosticKind {
 
     InvalidDeriveTarget { ast: AstId<ast::Item>, id: u32 },
 
-    MalformedDerive { ast: AstId<ast::Item>, id: u32 },
+    MalformedDerive { ast: AstId<ast::Adt>, id: u32 },
 }
 
 #[derive(Debug, PartialEq, Eq)]
@@ -121,7 +121,7 @@ impl DefDiagnostic {
 
     pub(super) fn malformed_derive(
         container: LocalModuleId,
-        ast: AstId<ast::Item>,
+        ast: AstId<ast::Adt>,
         id: AttrId,
     ) -> Self {
         Self {
diff --git a/crates/hir_expand/src/lib.rs b/crates/hir_expand/src/lib.rs
index fe9f7ef274..3e2e1ba630 100644
--- a/crates/hir_expand/src/lib.rs
+++ b/crates/hir_expand/src/lib.rs
@@ -121,7 +121,7 @@ pub enum MacroCallKind {
         expand_to: ExpandTo,
     },
     Derive {
-        ast_id: AstId<ast::Item>,
+        ast_id: AstId<ast::Adt>,
         derive_name: Box<str>,
         /// Syntactical index of the invoking `#[derive]` attribute.
         ///
@@ -328,11 +328,10 @@ impl MacroDefId {
 impl MacroCallKind {
     /// Returns the file containing the macro invocation.
     fn file_id(&self) -> HirFileId {
-        match self {
-            MacroCallKind::FnLike { ast_id, .. } => ast_id.file_id,
-            MacroCallKind::Derive { ast_id, .. } | MacroCallKind::Attr { ast_id, .. } => {
-                ast_id.file_id
-            }
+        match *self {
+            MacroCallKind::FnLike { ast_id: InFile { file_id, .. }, .. }
+            | MacroCallKind::Derive { ast_id: InFile { file_id, .. }, .. }
+            | MacroCallKind::Attr { ast_id: InFile { file_id, .. }, .. } => file_id,
         }
     }
 
@@ -341,7 +340,10 @@ impl MacroCallKind {
             MacroCallKind::FnLike { ast_id, .. } => {
                 ast_id.with_value(ast_id.to_node(db).syntax().clone())
             }
-            MacroCallKind::Derive { ast_id, .. } | MacroCallKind::Attr { ast_id, .. } => {
+            MacroCallKind::Derive { ast_id, .. } => {
+                ast_id.with_value(ast_id.to_node(db).syntax().clone())
+            }
+            MacroCallKind::Attr { ast_id, .. } => {
                 ast_id.with_value(ast_id.to_node(db).syntax().clone())
             }
         }
@@ -352,9 +354,8 @@ impl MacroCallKind {
             MacroCallKind::FnLike { ast_id, .. } => {
                 Some(ast_id.to_node(db).token_tree()?.syntax().clone())
             }
-            MacroCallKind::Derive { ast_id, .. } | MacroCallKind::Attr { ast_id, .. } => {
-                Some(ast_id.to_node(db).syntax().clone())
-            }
+            MacroCallKind::Derive { ast_id, .. } => Some(ast_id.to_node(db).syntax().clone()),
+            MacroCallKind::Attr { ast_id, .. } => Some(ast_id.to_node(db).syntax().clone()),
         }
     }
 
diff --git a/crates/ide/src/syntax_highlighting/html.rs b/crates/ide/src/syntax_highlighting/html.rs
index 5af73e6976..46c55da1f9 100644
--- a/crates/ide/src/syntax_highlighting/html.rs
+++ b/crates/ide/src/syntax_highlighting/html.rs
@@ -80,6 +80,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .numeric_literal    { color: #BFEBBF; }
 .bool_literal       { color: #BFE6EB; }
 .macro              { color: #94BFF3; }
+.derive             { color: #94BFF3; font-style: italic; }
 .module             { color: #AFD8AF; }
 .value_param        { color: #DCDCCC; }
 .variable           { color: #DCDCCC; }
diff --git a/crates/profile/Cargo.toml b/crates/profile/Cargo.toml
index ac0a06ca5a..51eeea30d2 100644
--- a/crates/profile/Cargo.toml
+++ b/crates/profile/Cargo.toml
@@ -21,7 +21,7 @@ jemalloc-ctl = { version = "0.4.1", package = "tikv-jemalloc-ctl", optional = tr
 perf-event = "0.4"
 
 [target.'cfg(windows)'.dependencies]
-winapi = { version = "0.3.8", features = ["psapi"] }
+winapi = { version = "0.3.8", features = ["processthreadsapi", "psapi"] }
 
 [features]
 cpu_profiler = []
diff --git a/crates/stdx/Cargo.toml b/crates/stdx/Cargo.toml
index bdde9fc8c9..bfce91b1d3 100644
--- a/crates/stdx/Cargo.toml
+++ b/crates/stdx/Cargo.toml
@@ -17,7 +17,7 @@ always-assert = { version = "0.1.2", features = ["log"] }
 
 [target.'cfg(windows)'.dependencies]
 miow = "0.4.0"
-winapi = "0.3.9"
+winapi = { version = "0.3.9", features = ["winerror"] }
 
 [features]
 # Uncomment to enable for the whole crate graph
diff --git a/crates/syntax/src/ast.rs b/crates/syntax/src/ast.rs
index 8732593af9..421120602e 100644
--- a/crates/syntax/src/ast.rs
+++ b/crates/syntax/src/ast.rs
@@ -30,7 +30,7 @@ pub use self::{
         QuoteOffsets, Radix,
     },
     traits::{
-        CommentIter, HasArgList, HasAttrs, HasDocComments, HasGenericParams, HasLoopBody,
+        DocCommentIter, HasArgList, HasAttrs, HasDocComments, HasGenericParams, HasLoopBody,
         HasModuleItem, HasName, HasTypeBounds, HasVisibility,
     },
 };
diff --git a/crates/syntax/src/ast/generated/nodes.rs b/crates/syntax/src/ast/generated/nodes.rs
index 18a6e1410d..8e55afb949 100644
--- a/crates/syntax/src/ast/generated/nodes.rs
+++ b/crates/syntax/src/ast/generated/nodes.rs
@@ -216,6 +216,7 @@ pub struct SourceFile {
 }
 impl ast::HasAttrs for SourceFile {}
 impl ast::HasModuleItem for SourceFile {}
+impl ast::HasDocComments for SourceFile {}
 impl SourceFile {
     pub fn shebang_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![shebang]) }
 }
@@ -227,6 +228,7 @@ pub struct Const {
 impl ast::HasAttrs for Const {}
 impl ast::HasName for Const {}
 impl ast::HasVisibility for Const {}
+impl ast::HasDocComments for Const {}
 impl Const {
     pub fn default_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![default]) }
     pub fn const_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![const]) }
@@ -246,6 +248,7 @@ impl ast::HasAttrs for Enum {}
 impl ast::HasName for Enum {}
 impl ast::HasVisibility for Enum {}
 impl ast::HasGenericParams for Enum {}
+impl ast::HasDocComments for Enum {}
 impl Enum {
     pub fn enum_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![enum]) }
     pub fn variant_list(&self) -> Option<VariantList> { support::child(&self.syntax) }
@@ -283,6 +286,7 @@ impl ast::HasAttrs for Fn {}
 impl ast::HasName for Fn {}
 impl ast::HasVisibility for Fn {}
 impl ast::HasGenericParams for Fn {}
+impl ast::HasDocComments for Fn {}
 impl Fn {
     pub fn default_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![default]) }
     pub fn const_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![const]) }
@@ -303,6 +307,7 @@ pub struct Impl {
 impl ast::HasAttrs for Impl {}
 impl ast::HasVisibility for Impl {}
 impl ast::HasGenericParams for Impl {}
+impl ast::HasDocComments for Impl {}
 impl Impl {
     pub fn default_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![default]) }
     pub fn unsafe_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![unsafe]) }
@@ -320,6 +325,7 @@ pub struct MacroRules {
 impl ast::HasAttrs for MacroRules {}
 impl ast::HasName for MacroRules {}
 impl ast::HasVisibility for MacroRules {}
+impl ast::HasDocComments for MacroRules {}
 impl MacroRules {
     pub fn macro_rules_token(&self) -> Option<SyntaxToken> {
         support::token(&self.syntax, T![macro_rules])
@@ -335,6 +341,7 @@ pub struct MacroDef {
 impl ast::HasAttrs for MacroDef {}
 impl ast::HasName for MacroDef {}
 impl ast::HasVisibility for MacroDef {}
+impl ast::HasDocComments for MacroDef {}
 impl MacroDef {
     pub fn macro_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![macro]) }
     pub fn args(&self) -> Option<TokenTree> { support::child(&self.syntax) }
@@ -348,6 +355,7 @@ pub struct Module {
 impl ast::HasAttrs for Module {}
 impl ast::HasName for Module {}
 impl ast::HasVisibility for Module {}
+impl ast::HasDocComments for Module {}
 impl Module {
     pub fn mod_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![mod]) }
     pub fn item_list(&self) -> Option<ItemList> { support::child(&self.syntax) }
@@ -361,6 +369,7 @@ pub struct Static {
 impl ast::HasAttrs for Static {}
 impl ast::HasName for Static {}
 impl ast::HasVisibility for Static {}
+impl ast::HasDocComments for Static {}
 impl Static {
     pub fn static_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![static]) }
     pub fn mut_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![mut]) }
@@ -379,6 +388,7 @@ impl ast::HasAttrs for Struct {}
 impl ast::HasName for Struct {}
 impl ast::HasVisibility for Struct {}
 impl ast::HasGenericParams for Struct {}
+impl ast::HasDocComments for Struct {}
 impl Struct {
     pub fn struct_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![struct]) }
     pub fn semicolon_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![;]) }
@@ -394,6 +404,7 @@ impl ast::HasName for Trait {}
 impl ast::HasVisibility for Trait {}
 impl ast::HasGenericParams for Trait {}
 impl ast::HasTypeBounds for Trait {}
+impl ast::HasDocComments for Trait {}
 impl Trait {
     pub fn unsafe_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![unsafe]) }
     pub fn auto_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![auto]) }
@@ -410,6 +421,7 @@ impl ast::HasName for TypeAlias {}
 impl ast::HasVisibility for TypeAlias {}
 impl ast::HasGenericParams for TypeAlias {}
 impl ast::HasTypeBounds for TypeAlias {}
+impl ast::HasDocComments for TypeAlias {}
 impl TypeAlias {
     pub fn default_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![default]) }
     pub fn type_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![type]) }
@@ -426,6 +438,7 @@ impl ast::HasAttrs for Union {}
 impl ast::HasName for Union {}
 impl ast::HasVisibility for Union {}
 impl ast::HasGenericParams for Union {}
+impl ast::HasDocComments for Union {}
 impl Union {
     pub fn union_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![union]) }
     pub fn record_field_list(&self) -> Option<RecordFieldList> { support::child(&self.syntax) }
@@ -437,6 +450,7 @@ pub struct Use {
 }
 impl ast::HasAttrs for Use {}
 impl ast::HasVisibility for Use {}
+impl ast::HasDocComments for Use {}
 impl Use {
     pub fn use_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![use]) }
     pub fn use_tree(&self) -> Option<UseTree> { support::child(&self.syntax) }
@@ -582,6 +596,7 @@ pub struct RecordField {
 impl ast::HasAttrs for RecordField {}
 impl ast::HasName for RecordField {}
 impl ast::HasVisibility for RecordField {}
+impl ast::HasDocComments for RecordField {}
 impl RecordField {
     pub fn colon_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![:]) }
     pub fn ty(&self) -> Option<Type> { support::child(&self.syntax) }
@@ -593,6 +608,7 @@ pub struct TupleField {
 }
 impl ast::HasAttrs for TupleField {}
 impl ast::HasVisibility for TupleField {}
+impl ast::HasDocComments for TupleField {}
 impl TupleField {
     pub fn ty(&self) -> Option<Type> { support::child(&self.syntax) }
 }
@@ -614,6 +630,7 @@ pub struct Variant {
 impl ast::HasAttrs for Variant {}
 impl ast::HasName for Variant {}
 impl ast::HasVisibility for Variant {}
+impl ast::HasDocComments for Variant {}
 impl Variant {
     pub fn field_list(&self) -> Option<FieldList> { support::child(&self.syntax) }
     pub fn eq_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![=]) }
@@ -1567,6 +1584,7 @@ pub enum Adt {
     Union(Union),
 }
 impl ast::HasAttrs for Adt {}
+impl ast::HasDocComments for Adt {}
 impl ast::HasGenericParams for Adt {}
 impl ast::HasName for Adt {}
 impl ast::HasVisibility for Adt {}
@@ -1609,6 +1627,12 @@ pub struct AnyHasAttrs {
 }
 impl ast::HasAttrs for AnyHasAttrs {}
 
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct AnyHasDocComments {
+    pub(crate) syntax: SyntaxNode,
+}
+impl ast::HasDocComments for AnyHasDocComments {}
+
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct AnyHasGenericParams {
     pub(crate) syntax: SyntaxNode,
@@ -3869,6 +3893,27 @@ impl AstNode for AnyHasAttrs {
     }
     fn syntax(&self) -> &SyntaxNode { &self.syntax }
 }
+impl AnyHasDocComments {
+    #[inline]
+    pub fn new<T: ast::HasDocComments>(node: T) -> AnyHasDocComments {
+        AnyHasDocComments { syntax: node.syntax().clone() }
+    }
+}
+impl AstNode for AnyHasDocComments {
+    fn can_cast(kind: SyntaxKind) -> bool {
+        match kind {
+            SOURCE_FILE | CONST | ENUM | FN | IMPL | MACRO_RULES | MACRO_DEF | MODULE | STATIC
+            | STRUCT | TRAIT | TYPE_ALIAS | UNION | USE | RECORD_FIELD | TUPLE_FIELD | VARIANT => {
+                true
+            }
+            _ => false,
+        }
+    }
+    fn cast(syntax: SyntaxNode) -> Option<Self> {
+        Self::can_cast(syntax.kind()).then(|| AnyHasDocComments { syntax })
+    }
+    fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
 impl AnyHasGenericParams {
     #[inline]
     pub fn new<T: ast::HasGenericParams>(node: T) -> AnyHasGenericParams {
diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs
index 7dd9ac1e9e..705aa5edac 100644
--- a/crates/syntax/src/ast/node_ext.rs
+++ b/crates/syntax/src/ast/node_ext.rs
@@ -771,21 +771,14 @@ impl ast::HasLoopBody for ast::ForExpr {
     }
 }
 
-impl ast::HasDocComments for ast::SourceFile {}
-impl ast::HasDocComments for ast::Fn {}
-impl ast::HasDocComments for ast::Struct {}
-impl ast::HasDocComments for ast::Union {}
-impl ast::HasDocComments for ast::RecordField {}
-impl ast::HasDocComments for ast::TupleField {}
-impl ast::HasDocComments for ast::Enum {}
-impl ast::HasDocComments for ast::Variant {}
-impl ast::HasDocComments for ast::Trait {}
-impl ast::HasDocComments for ast::Module {}
-impl ast::HasDocComments for ast::Static {}
-impl ast::HasDocComments for ast::Const {}
-impl ast::HasDocComments for ast::TypeAlias {}
-impl ast::HasDocComments for ast::Impl {}
-impl ast::HasDocComments for ast::MacroRules {}
-impl ast::HasDocComments for ast::MacroDef {}
-impl ast::HasDocComments for ast::Macro {}
-impl ast::HasDocComments for ast::Use {}
+impl ast::HasAttrs for ast::AnyHasDocComments {}
+
+impl From<ast::Adt> for ast::Item {
+    fn from(it: ast::Adt) -> Self {
+        match it {
+            ast::Adt::Enum(it) => ast::Item::Enum(it),
+            ast::Adt::Struct(it) => ast::Item::Struct(it),
+            ast::Adt::Union(it) => ast::Item::Union(it),
+        }
+    }
+}
diff --git a/crates/syntax/src/ast/token_ext.rs b/crates/syntax/src/ast/token_ext.rs
index c32ab686cc..cc7e5150b3 100644
--- a/crates/syntax/src/ast/token_ext.rs
+++ b/crates/syntax/src/ast/token_ext.rs
@@ -14,6 +14,10 @@ impl ast::Comment {
         CommentKind::from_text(self.text())
     }
 
+    pub fn is_doc(&self) -> bool {
+        self.kind().doc.is_some()
+    }
+
     pub fn is_inner(&self) -> bool {
         self.kind().doc == Some(CommentPlacement::Inner)
     }
diff --git a/crates/syntax/src/ast/traits.rs b/crates/syntax/src/ast/traits.rs
index eae3a7e2fe..98b1087e64 100644
--- a/crates/syntax/src/ast/traits.rs
+++ b/crates/syntax/src/ast/traits.rs
@@ -1,10 +1,12 @@
 //! Various traits that are implemented by ast nodes.
 //!
 //! The implementations are usually trivial, and live in generated.rs
+use itertools::Either;
+
 use crate::{
     ast::{self, support, AstChildren, AstNode, AstToken},
     syntax_node::SyntaxElementChildren,
-    SyntaxToken, T,
+    SyntaxElement, SyntaxToken, T,
 };
 
 pub trait HasName: AstNode {
@@ -71,14 +73,17 @@ pub trait HasAttrs: AstNode {
 }
 
 pub trait HasDocComments: HasAttrs {
-    fn doc_comments(&self) -> CommentIter {
-        CommentIter { iter: self.syntax().children_with_tokens() }
+    fn doc_comments(&self) -> DocCommentIter {
+        DocCommentIter { iter: self.syntax().children_with_tokens() }
+    }
+    fn doc_comments_and_attrs(&self) -> AttrCommentIter {
+        AttrCommentIter { iter: self.syntax().children_with_tokens() }
     }
 }
 
-impl CommentIter {
-    pub fn from_syntax_node(syntax_node: &ast::SyntaxNode) -> CommentIter {
-        CommentIter { iter: syntax_node.children_with_tokens() }
+impl DocCommentIter {
+    pub fn from_syntax_node(syntax_node: &ast::SyntaxNode) -> DocCommentIter {
+        DocCommentIter { iter: syntax_node.children_with_tokens() }
     }
 
     #[cfg(test)]
@@ -95,13 +100,31 @@ impl CommentIter {
     }
 }
 
-pub struct CommentIter {
+pub struct DocCommentIter {
     iter: SyntaxElementChildren,
 }
 
-impl Iterator for CommentIter {
+impl Iterator for DocCommentIter {
     type Item = ast::Comment;
     fn next(&mut self) -> Option<ast::Comment> {
-        self.iter.by_ref().find_map(|el| el.into_token().and_then(ast::Comment::cast))
+        self.iter.by_ref().find_map(|el| {
+            el.into_token().and_then(ast::Comment::cast).filter(ast::Comment::is_doc)
+        })
+    }
+}
+
+pub struct AttrCommentIter {
+    iter: SyntaxElementChildren,
+}
+
+impl Iterator for AttrCommentIter {
+    type Item = Either<ast::Comment, ast::Attr>;
+    fn next(&mut self) -> Option<Self::Item> {
+        self.iter.by_ref().find_map(|el| match el {
+            SyntaxElement::Node(node) => ast::Attr::cast(node).map(Either::Right),
+            SyntaxElement::Token(tok) => {
+                ast::Comment::cast(tok).filter(ast::Comment::is_doc).map(Either::Left)
+            }
+        })
     }
 }


Test patch:
diff --git a/crates/hir_def/src/item_tree/tests.rs b/crates/hir_def/src/item_tree/tests.rs
index 63d177eaab..cb3fd9b94a 100644
--- a/crates/hir_def/src/item_tree/tests.rs
+++ b/crates/hir_def/src/item_tree/tests.rs
@@ -30,9 +30,9 @@ use crate::{A, B};
 use a::{c, d::{e}};
         "#,
         expect![[r##"
-            #![doc = " file comment"]  // AttrId { is_doc_comment: true, ast_index: 0 }
-            #![no_std]  // AttrId { is_doc_comment: false, ast_index: 0 }
-            #![doc = " another file comment"]  // AttrId { is_doc_comment: true, ast_index: 1 }
+            #![doc = " file comment"]  // AttrId { ast_index: 0 }
+            #![no_std]  // AttrId { ast_index: 1 }
+            #![doc = " another file comment"]  // AttrId { ast_index: 2 }
 
             pub(self) extern crate self as renamed;
 
@@ -42,7 +42,7 @@ use a::{c, d::{e}};
 
             pub(self) use globs::*;
 
-            #[doc = " docs on import"]  // AttrId { is_doc_comment: true, ast_index: 0 }
+            #[doc = " docs on import"]  // AttrId { ast_index: 0 }
             pub(self) use crate::{A, B};
 
             pub(self) use a::{c, d::{e}};
@@ -67,15 +67,15 @@ extern "C" {
 }
         "#,
         expect![[r##"
-            #[on_extern_block]  // AttrId { is_doc_comment: false, ast_index: 0 }
+            #[on_extern_block]  // AttrId { ast_index: 0 }
             extern "C" {
-                #[on_extern_type]  // AttrId { is_doc_comment: false, ast_index: 0 }
+                #[on_extern_type]  // AttrId { ast_index: 0 }
                 pub(self) type ExType;
 
-                #[on_extern_static]  // AttrId { is_doc_comment: false, ast_index: 0 }
+                #[on_extern_static]  // AttrId { ast_index: 0 }
                 pub(self) static EX_STATIC: u8 = _;
 
-                #[on_extern_fn]  // AttrId { is_doc_comment: false, ast_index: 0 }
+                #[on_extern_fn]  // AttrId { ast_index: 0 }
                 // flags = 0x20
                 pub(self) fn ex_fn() -> ();
             }
@@ -116,14 +116,14 @@ enum E {
         expect![[r##"
             pub(self) struct Unit;
 
-            #[derive(Debug)]  // AttrId { is_doc_comment: false, ast_index: 0 }
+            #[derive(Debug)]  // AttrId { ast_index: 0 }
             pub(self) struct Struct {
-                #[doc = " fld docs"]  // AttrId { is_doc_comment: true, ast_index: 0 }
+                #[doc = " fld docs"]  // AttrId { ast_index: 0 }
                 pub(self) fld: (),
             }
 
             pub(self) struct Tuple(
-                #[attr]  // AttrId { is_doc_comment: false, ast_index: 0 }
+                #[attr]  // AttrId { ast_index: 0 }
                 pub(self) 0: u8,
             );
 
@@ -133,14 +133,14 @@ enum E {
             }
 
             pub(self) enum E {
-                #[doc = " comment on Unit"]  // AttrId { is_doc_comment: true, ast_index: 0 }
+                #[doc = " comment on Unit"]  // AttrId { ast_index: 0 }
                 Unit,
-                #[doc = " comment on Tuple"]  // AttrId { is_doc_comment: true, ast_index: 0 }
+                #[doc = " comment on Tuple"]  // AttrId { ast_index: 0 }
                 Tuple(
                     pub(self) 0: u8,
                 ),
                 Struct {
-                    #[doc = " comment on a: u8"]  // AttrId { is_doc_comment: true, ast_index: 0 }
+                    #[doc = " comment on a: u8"]  // AttrId { ast_index: 0 }
                     pub(self) a: u8,
                 },
             }
@@ -171,11 +171,11 @@ trait Tr: SuperTrait + 'lifetime {
 
             pub(self) const _: Anon = _;
 
-            #[attr]  // AttrId { is_doc_comment: false, ast_index: 0 }
-            #[inner_attr_in_fn]  // AttrId { is_doc_comment: false, ast_index: 1 }
+            #[attr]  // AttrId { ast_index: 0 }
+            #[inner_attr_in_fn]  // AttrId { ast_index: 1 }
             // flags = 0x2
             pub(self) fn f(
-                #[attr]  // AttrId { is_doc_comment: false, ast_index: 0 }
+                #[attr]  // AttrId { ast_index: 0 }
                 arg: u8,
                 _: (),
             ) -> ();
@@ -212,8 +212,8 @@ mod inline {
 mod outline;
         "#,
         expect![[r##"
-            #[doc = " outer"]  // AttrId { is_doc_comment: true, ast_index: 0 }
-            #[doc = " inner"]  // AttrId { is_doc_comment: true, ast_index: 1 }
+            #[doc = " outer"]  // AttrId { ast_index: 0 }
+            #[doc = " inner"]  // AttrId { ast_index: 1 }
             pub(self) mod inline {
                 pub(self) use super::*;
 
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html b/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html
index bf3f1616ac..645e6c589d 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html
@@ -28,6 +28,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .numeric_literal    { color: #BFEBBF; }
 .bool_literal       { color: #BFE6EB; }
 .macro              { color: #94BFF3; }
+.derive             { color: #94BFF3; font-style: italic; }
 .module             { color: #AFD8AF; }
 .value_param        { color: #DCDCCC; }
 .variable           { color: #DCDCCC; }
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_crate_root.html b/crates/ide/src/syntax_highlighting/test_data/highlight_crate_root.html
index 8b4a76453b..37c8b56682 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_crate_root.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_crate_root.html
@@ -28,6 +28,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .numeric_literal    { color: #BFEBBF; }
 .bool_literal       { color: #BFE6EB; }
 .macro              { color: #94BFF3; }
+.derive             { color: #94BFF3; font-style: italic; }
 .module             { color: #AFD8AF; }
 .value_param        { color: #DCDCCC; }
 .variable           { color: #DCDCCC; }
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_default_library.html b/crates/ide/src/syntax_highlighting/test_data/highlight_default_library.html
index ea68f5f14a..2f96a1be85 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_default_library.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_default_library.html
@@ -28,6 +28,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .numeric_literal    { color: #BFEBBF; }
 .bool_literal       { color: #BFE6EB; }
 .macro              { color: #94BFF3; }
+.derive             { color: #94BFF3; font-style: italic; }
 .module             { color: #AFD8AF; }
 .value_param        { color: #DCDCCC; }
 .variable           { color: #DCDCCC; }
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
index 50013c06a7..6e87147e8b 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
@@ -28,6 +28,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .numeric_literal    { color: #BFEBBF; }
 .bool_literal       { color: #BFE6EB; }
 .macro              { color: #94BFF3; }
+.derive             { color: #94BFF3; font-style: italic; }
 .module             { color: #AFD8AF; }
 .value_param        { color: #DCDCCC; }
 .variable           { color: #DCDCCC; }
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html b/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html
index ca869c78be..3208770d7f 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html
@@ -28,6 +28,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .numeric_literal    { color: #BFEBBF; }
 .bool_literal       { color: #BFE6EB; }
 .macro              { color: #94BFF3; }
+.derive             { color: #94BFF3; font-style: italic; }
 .module             { color: #AFD8AF; }
 .value_param        { color: #DCDCCC; }
 .variable           { color: #DCDCCC; }
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html b/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html
index f4134fee4a..1713306dae 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html
@@ -28,6 +28,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .numeric_literal    { color: #BFEBBF; }
 .bool_literal       { color: #BFE6EB; }
 .macro              { color: #94BFF3; }
+.derive             { color: #94BFF3; font-style: italic; }
 .module             { color: #AFD8AF; }
 .value_param        { color: #DCDCCC; }
 .variable           { color: #DCDCCC; }
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html b/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
index 41cb3903d6..07c7881a1f 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
@@ -28,6 +28,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .numeric_literal    { color: #BFEBBF; }
 .bool_literal       { color: #BFE6EB; }
 .macro              { color: #94BFF3; }
+.derive             { color: #94BFF3; font-style: italic; }
 .module             { color: #AFD8AF; }
 .value_param        { color: #DCDCCC; }
 .variable           { color: #DCDCCC; }
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html b/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html
index 04494283d5..25bfcb6482 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html
@@ -28,6 +28,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .numeric_literal    { color: #BFEBBF; }
 .bool_literal       { color: #BFE6EB; }
 .macro              { color: #94BFF3; }
+.derive             { color: #94BFF3; font-style: italic; }
 .module             { color: #AFD8AF; }
 .value_param        { color: #DCDCCC; }
 .variable           { color: #DCDCCC; }
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlighting.html b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
index 3ec6b6a77d..793f554c63 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlighting.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
@@ -28,6 +28,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .numeric_literal    { color: #BFEBBF; }
 .bool_literal       { color: #BFE6EB; }
 .macro              { color: #94BFF3; }
+.derive             { color: #94BFF3; font-style: italic; }
 .module             { color: #AFD8AF; }
 .value_param        { color: #DCDCCC; }
 .variable           { color: #DCDCCC; }
@@ -89,7 +90,17 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 <span class="brace">}</span>
 
 <span class="keyword">use</span> <span class="self_keyword crate_root">self</span><span class="operator">::</span><span class="struct">FooCopy</span><span class="operator">::</span><span class="brace">{</span><span class="self_keyword">self</span> <span class="keyword">as</span> <span class="struct declaration">BarCopy</span><span class="brace">}</span><span class="semicolon">;</span>
+
+<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="attribute attribute default_library library">derive</span><span class="parenthesis attribute">(</span><span class="derive attribute default_library library">Copy</span><span class="parenthesis attribute">)</span><span class="attribute_bracket attribute">]</span>
+<span class="comment documentation">/// This is a doc comment</span>
+<span class="comment">// This is a normal comment</span>
+<span class="comment documentation">/// This is a doc comment</span>
+<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="attribute attribute default_library library">derive</span><span class="parenthesis attribute">(</span><span class="derive attribute default_library library">Copy</span><span class="parenthesis attribute">)</span><span class="attribute_bracket attribute">]</span>
+<span class="comment">// This is another normal comment</span>
+<span class="comment documentation">/// This is another doc comment</span>
+<span class="comment">// This is another normal comment</span>
 <span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="attribute attribute default_library library">derive</span><span class="parenthesis attribute">(</span><span class="derive attribute default_library library">Copy</span><span class="parenthesis attribute">)</span><span class="attribute_bracket attribute">]</span>
+<span class="comment">// The reason for these being here is to test AttrIds</span>
 <span class="keyword">struct</span> <span class="struct declaration">FooCopy</span> <span class="brace">{</span>
     <span class="field declaration">x</span><span class="colon">:</span> <span class="builtin_type">u32</span><span class="comma">,</span>
 <span class="brace">}</span>
diff --git a/crates/ide/src/syntax_highlighting/test_data/injection.html b/crates/ide/src/syntax_highlighting/test_data/injection.html
index 28c2bed768..2e9ad144f1 100644
--- a/crates/ide/src/syntax_highlighting/test_data/injection.html
+++ b/crates/ide/src/syntax_highlighting/test_data/injection.html
@@ -28,6 +28,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .numeric_literal    { color: #BFEBBF; }
 .bool_literal       { color: #BFE6EB; }
 .macro              { color: #94BFF3; }
+.derive             { color: #94BFF3; font-style: italic; }
 .module             { color: #AFD8AF; }
 .value_param        { color: #DCDCCC; }
 .variable           { color: #DCDCCC; }
diff --git a/crates/ide/src/syntax_highlighting/test_data/rainbow_highlighting.html b/crates/ide/src/syntax_highlighting/test_data/rainbow_highlighting.html
index 30c4bf40f4..e0287f1185 100644
--- a/crates/ide/src/syntax_highlighting/test_data/rainbow_highlighting.html
+++ b/crates/ide/src/syntax_highlighting/test_data/rainbow_highlighting.html
@@ -28,6 +28,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .numeric_literal    { color: #BFEBBF; }
 .bool_literal       { color: #BFE6EB; }
 .macro              { color: #94BFF3; }
+.derive             { color: #94BFF3; font-style: italic; }
 .module             { color: #AFD8AF; }
 .value_param        { color: #DCDCCC; }
 .variable           { color: #DCDCCC; }
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs
index 4e62edc7c1..c9c23318f5 100644
--- a/crates/ide/src/syntax_highlighting/tests.rs
+++ b/crates/ide/src/syntax_highlighting/tests.rs
@@ -62,7 +62,17 @@ impl Foo {
 }
 
 use self::FooCopy::{self as BarCopy};
+
+#[derive(Copy)]
+/// This is a doc comment
+// This is a normal comment
+/// This is a doc comment
+#[derive(Copy)]
+// This is another normal comment
+/// This is another doc comment
+// This is another normal comment
 #[derive(Copy)]
+// The reason for these being here is to test AttrIds
 struct FooCopy {
     x: u32,
 }
diff --git a/crates/syntax/src/tests/sourcegen_ast.rs b/crates/syntax/src/tests/sourcegen_ast.rs
index c66edadc3c..ac928402f3 100644
--- a/crates/syntax/src/tests/sourcegen_ast.rs
+++ b/crates/syntax/src/tests/sourcegen_ast.rs
@@ -777,6 +777,33 @@ fn extract_struct_traits(ast: &mut AstSrc) {
             extract_struct_trait(node, name, methods);
         }
     }
+
+    let nodes_with_doc_comments = [
+        "SourceFile",
+        "Fn",
+        "Struct",
+        "Union",
+        "RecordField",
+        "TupleField",
+        "Enum",
+        "Variant",
+        "Trait",
+        "Module",
+        "Static",
+        "Const",
+        "TypeAlias",
+        "Impl",
+        "MacroRules",
+        "MacroDef",
+        "Macro",
+        "Use",
+    ];
+
+    for node in &mut ast.nodes {
+        if nodes_with_doc_comments.contains(&&*node.name) {
+            node.traits.push("HasDocComments".into());
+        }
+    }
 }
 
 fn extract_struct_trait(node: &mut AstNodeSrc, trait_name: &str, methods: &[&str]) {