]> git.lizzy.rs Git - rust.git/blobdiff - crates/hir_def/src/attr.rs
parameters.split_last()
[rust.git] / crates / hir_def / src / attr.rs
index ef86572fe5ac5ce2fab18b1703190bab90a7c9a6..0a8fb2e47a8fee80bf74d842dc5ce974051dc94d 100644 (file)
@@ -1,23 +1,17 @@
 //! A higher level attributes based on TokenTree, with also some shortcuts.
 
-use std::{
-    convert::{TryFrom, TryInto},
-    fmt,
-    hash::Hash,
-    ops,
-    sync::Arc,
-};
+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};
+use mbe::{syntax_node_to_token_tree, DelimiterKind, Punct};
 use smallvec::{smallvec, SmallVec};
 use syntax::{
-    ast::{self, AstNode, AttrsOwner},
+    ast::{self, AstNode, HasAttrs, IsString},
     match_ast, AstPtr, AstToken, SmolStr, SyntaxNode, TextRange, TextSize,
 };
 use tt::Subtree;
@@ -78,6 +72,11 @@ fn deref(&self) -> &[Attr] {
         }
     }
 }
+impl Attrs {
+    pub fn get(&self, id: AttrId) -> Option<&Attr> {
+        (**self).iter().find(|attr| attr.id == id)
+    }
+}
 
 impl ops::Deref for Attrs {
     type Target = [Attr];
@@ -101,13 +100,9 @@ fn deref(&self) -> &Attrs {
 impl RawAttrs {
     pub(crate) const EMPTY: Self = Self { entries: None };
 
-    pub(crate) fn new(
-        db: &dyn DefDatabase,
-        owner: &dyn ast::AttrsOwner,
-        hygiene: &Hygiene,
-    ) -> Self {
+    pub(crate) fn new(db: &dyn DefDatabase, owner: &dyn ast::HasAttrs, hygiene: &Hygiene) -> Self {
         let entries = collect_attrs(owner)
-            .flat_map(|(id, attr)| match attr {
+            .filter_map(|(id, attr)| match attr {
                 Either::Left(attr) => {
                     attr.meta().and_then(|meta| Attr::from_src(db, meta, hygiene, id))
                 }
@@ -122,7 +117,7 @@ pub(crate) fn new(
         Self { entries: if entries.is_empty() { None } else { Some(entries) } }
     }
 
-    fn from_attrs_owner(db: &dyn DefDatabase, owner: InFile<&dyn ast::AttrsOwner>) -> Self {
+    fn from_attrs_owner(db: &dyn DefDatabase, owner: InFile<&dyn ast::HasAttrs>) -> Self {
         let hygiene = Hygiene::new(db.upcast(), owner.file_id);
         Self::new(db, owner.value, &hygiene)
     }
@@ -208,7 +203,7 @@ pub(crate) fn variants_attrs_query(
         let mut res = ArenaMap::default();
 
         for (id, var) in src.value.iter() {
-            let attrs = RawAttrs::from_attrs_owner(db, src.with_value(var as &dyn ast::AttrsOwner))
+            let attrs = RawAttrs::from_attrs_owner(db, src.with_value(var as &dyn ast::HasAttrs))
                 .filter(db, krate);
 
             res.insert(id, attrs)
@@ -226,7 +221,7 @@ pub(crate) fn fields_attrs_query(
         let mut res = ArenaMap::default();
 
         for (id, fld) in src.value.iter() {
-            let owner: &dyn AttrsOwner = match fld {
+            let owner: &dyn HasAttrs = match fld {
                 Either::Left(tuple) => tuple,
                 Either::Right(record) => record,
             };
@@ -257,6 +252,10 @@ pub(crate) fn is_cfg_enabled(&self, cfg_options: &CfgOptions) -> bool {
         }
     }
 
+    pub fn lang(&self) -> Option<&SmolStr> {
+        self.by_key("lang").string_value()
+    }
+
     pub fn docs(&self) -> Option<Documentation> {
         let docs = self.by_key("doc").attrs().flat_map(|attr| match attr.input.as_deref()? {
             AttrInput::Literal(s) => Some(s),
@@ -312,7 +311,7 @@ pub(crate) fn attrs_query(db: &dyn DefDatabase, def: AttrDefId) -> Self {
                     Some(it) => {
                         let raw_attrs = RawAttrs::from_attrs_owner(
                             db,
-                            it.as_ref().map(|it| it as &dyn ast::AttrsOwner),
+                            it.as_ref().map(|it| it as &dyn ast::HasAttrs),
                         );
                         match mod_data.definition_source(db) {
                             InFile { file_id, value: ModuleSource::SourceFile(file) } => raw_attrs
@@ -323,9 +322,9 @@ pub(crate) fn attrs_query(db: &dyn DefDatabase, def: AttrDefId) -> Self {
                     None => RawAttrs::from_attrs_owner(
                         db,
                         mod_data.definition_source(db).as_ref().map(|src| match src {
-                            ModuleSource::SourceFile(file) => file as &dyn ast::AttrsOwner,
-                            ModuleSource::Module(module) => module as &dyn ast::AttrsOwner,
-                            ModuleSource::BlockExpr(block) => block as &dyn ast::AttrsOwner,
+                            ModuleSource::SourceFile(file) => file as &dyn ast::HasAttrs,
+                            ModuleSource::Module(module) => module as &dyn ast::HasAttrs,
+                            ModuleSource::BlockExpr(block) => block as &dyn ast::HasAttrs,
                         }),
                     ),
                 }
@@ -372,6 +371,7 @@ pub(crate) fn attrs_query(db: &dyn DefDatabase, def: AttrDefId) -> Self {
                     RawAttrs::from_attrs_owner(db, src.with_value(&src.value[it.local_id]))
                 }
             },
+            AttrDefId::ExternBlockId(it) => attrs_from_item_tree(it.lookup(db).id, db),
         };
 
         let attrs = raw_attrs.filter(db, def.krate(db));
@@ -391,16 +391,18 @@ pub fn source_map(&self, db: &dyn DefDatabase) -> AttrSourceMap {
                         if let InFile { file_id, value: ModuleSource::SourceFile(file) } =
                             mod_data.definition_source(db)
                         {
-                            map.merge(AttrSourceMap::new(InFile::new(file_id, &file)));
+                            map.append_module_inline_attrs(AttrSourceMap::new(InFile::new(
+                                file_id, &file,
+                            )));
                         }
                         return map;
                     }
                     None => {
                         let InFile { file_id, value } = mod_data.definition_source(db);
                         let attrs_owner = match &value {
-                            ModuleSource::SourceFile(file) => file as &dyn ast::AttrsOwner,
-                            ModuleSource::Module(module) => module as &dyn ast::AttrsOwner,
-                            ModuleSource::BlockExpr(block) => block as &dyn ast::AttrsOwner,
+                            ModuleSource::SourceFile(file) => file as &dyn ast::HasAttrs,
+                            ModuleSource::Module(module) => module as &dyn ast::HasAttrs,
+                            ModuleSource::BlockExpr(block) => block as &dyn ast::HasAttrs,
                         };
                         return AttrSourceMap::new(InFile::new(file_id, attrs_owner));
                     }
@@ -411,51 +413,52 @@ pub fn source_map(&self, db: &dyn DefDatabase) -> AttrSourceMap {
                 let file_id = id.parent.file_id(db);
                 let root = db.parse_or_expand(file_id).unwrap();
                 let owner = match &map[id.local_id] {
-                    Either::Left(it) => ast::DynAttrsOwner::new(it.to_node(&root)),
-                    Either::Right(it) => ast::DynAttrsOwner::new(it.to_node(&root)),
+                    Either::Left(it) => ast::AnyHasAttrs::new(it.to_node(&root)),
+                    Either::Right(it) => ast::AnyHasAttrs::new(it.to_node(&root)),
                 };
                 InFile::new(file_id, owner)
             }
             AttrDefId::AdtId(adt) => match adt {
-                AdtId::StructId(id) => id.lookup(db).source(db).map(ast::DynAttrsOwner::new),
-                AdtId::UnionId(id) => id.lookup(db).source(db).map(ast::DynAttrsOwner::new),
-                AdtId::EnumId(id) => id.lookup(db).source(db).map(ast::DynAttrsOwner::new),
+                AdtId::StructId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new),
+                AdtId::UnionId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new),
+                AdtId::EnumId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new),
             },
-            AttrDefId::FunctionId(id) => id.lookup(db).source(db).map(ast::DynAttrsOwner::new),
+            AttrDefId::FunctionId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new),
             AttrDefId::EnumVariantId(id) => {
                 let map = db.variants_attrs_source_map(id.parent);
                 let file_id = id.parent.lookup(db).id.file_id();
                 let root = db.parse_or_expand(file_id).unwrap();
-                InFile::new(file_id, ast::DynAttrsOwner::new(map[id.local_id].to_node(&root)))
+                InFile::new(file_id, ast::AnyHasAttrs::new(map[id.local_id].to_node(&root)))
             }
-            AttrDefId::StaticId(id) => id.lookup(db).source(db).map(ast::DynAttrsOwner::new),
-            AttrDefId::ConstId(id) => id.lookup(db).source(db).map(ast::DynAttrsOwner::new),
-            AttrDefId::TraitId(id) => id.lookup(db).source(db).map(ast::DynAttrsOwner::new),
-            AttrDefId::TypeAliasId(id) => id.lookup(db).source(db).map(ast::DynAttrsOwner::new),
+            AttrDefId::StaticId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new),
+            AttrDefId::ConstId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new),
+            AttrDefId::TraitId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new),
+            AttrDefId::TypeAliasId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new),
             AttrDefId::MacroDefId(id) => id.ast_id().either(
-                |it| it.with_value(ast::DynAttrsOwner::new(it.to_node(db.upcast()))),
-                |it| it.with_value(ast::DynAttrsOwner::new(it.to_node(db.upcast()))),
+                |it| it.with_value(ast::AnyHasAttrs::new(it.to_node(db.upcast()))),
+                |it| it.with_value(ast::AnyHasAttrs::new(it.to_node(db.upcast()))),
             ),
-            AttrDefId::ImplId(id) => id.lookup(db).source(db).map(ast::DynAttrsOwner::new),
+            AttrDefId::ImplId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new),
             AttrDefId::GenericParamId(id) => match id {
                 GenericParamId::TypeParamId(id) => {
                     id.parent.child_source(db).map(|source| match &source[id.local_id] {
-                        Either::Left(id) => ast::DynAttrsOwner::new(id.clone()),
-                        Either::Right(id) => ast::DynAttrsOwner::new(id.clone()),
+                        Either::Left(id) => ast::AnyHasAttrs::new(id.clone()),
+                        Either::Right(id) => ast::AnyHasAttrs::new(id.clone()),
                     })
                 }
                 GenericParamId::LifetimeParamId(id) => id
                     .parent
                     .child_source(db)
-                    .map(|source| ast::DynAttrsOwner::new(source[id.local_id].clone())),
+                    .map(|source| ast::AnyHasAttrs::new(source[id.local_id].clone())),
                 GenericParamId::ConstParamId(id) => id
                     .parent
                     .child_source(db)
-                    .map(|source| ast::DynAttrsOwner::new(source[id.local_id].clone())),
+                    .map(|source| ast::AnyHasAttrs::new(source[id.local_id].clone())),
             },
+            AttrDefId::ExternBlockId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new),
         };
 
-        AttrSourceMap::new(owner.as_ref().map(|node| node as &dyn AttrsOwner))
+        AttrSourceMap::new(owner.as_ref().map(|node| node as &dyn HasAttrs))
     }
 
     pub fn docs_with_rangemap(
@@ -514,62 +517,67 @@ pub fn docs_with_rangemap(
 
 fn inner_attributes(
     syntax: &SyntaxNode,
-) -> Option<(impl Iterator<Item = ast::Attr>, impl Iterator<Item = ast::Comment>)> {
-    let (attrs, docs) = match_ast! {
+) -> Option<impl Iterator<Item = Either<ast::Attr, ast::Comment>>> {
+    let node = match_ast! {
         match syntax {
-            ast::SourceFile(it) => (it.attrs(), ast::CommentIter::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()))
-            },
-            ast::Fn(it) => {
-                let body = it.body()?;
-                (body.attrs(), ast::CommentIter::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()))
-            },
-            ast::Module(it) => {
-                let item_list = it.item_list()?;
-                (item_list.attrs(), ast::CommentIter::from_syntax_node(item_list.syntax()))
+            ast::SourceFile(_) => syntax.clone(),
+            ast::ExternBlock(it) => it.extern_item_list()?.syntax().clone(),
+            ast::Fn(it) => it.body()?.stmt_list()?.syntax().clone(),
+            ast::Impl(it) => it.assoc_item_list()?.syntax().clone(),
+            ast::Module(it) => it.item_list()?.syntax().clone(),
+            ast::BlockExpr(it) => {
+                use syntax::SyntaxKind::{BLOCK_EXPR , EXPR_STMT};
+                // Block expressions accept outer and inner attributes, but only when they are the outer
+                // expression of an expression statement or the final expression of another block expression.
+                let may_carry_attributes = matches!(
+                    it.syntax().parent().map(|it| it.kind()),
+                     Some(BLOCK_EXPR | EXPR_STMT)
+                );
+                if !may_carry_attributes {
+                    return None
+                }
+                syntax.clone()
             },
-            // FIXME: BlockExpr's only accept inner attributes in specific cases
-            // Excerpt from the reference:
-            // Block expressions accept outer and inner attributes, but only when they are the outer
-            // expression of an expression statement or the final expression of another block expression.
-            ast::BlockExpr(_it) => return None,
             _ => return None,
         }
     };
-    let attrs = attrs.filter(|attr| attr.kind().is_inner());
-    let docs = docs.filter(|doc| doc.is_inner());
-    Some((attrs, docs))
+
+    let attrs = ast::AttrDocCommentIter::from_syntax_node(&node).filter(|el| match el {
+        Either::Left(attr) => attr.kind().is_inner(),
+        Either::Right(comment) => comment.is_inner(),
+    });
+    Some(attrs)
 }
 
 #[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,
+    /// If this map is for a module, this will be the [`HirFileId`] of the module's definition site,
+    /// while `file_id` will be the one of the module declaration site.
+    /// The usize is the index into `source` from which point on the entries reside in the def site
+    /// file.
+    mod_def_site_file_id: Option<(HirFileId, usize)>,
 }
 
 impl AttrSourceMap {
-    fn new(owner: InFile<&dyn ast::AttrsOwner>) -> 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)),
-            }
+    fn new(owner: InFile<&dyn ast::HasAttrs>) -> Self {
+        Self {
+            source: collect_attrs(owner.value).map(|(_, it)| it).collect(),
+            file_id: owner.file_id,
+            mod_def_site_file_id: None,
         }
-
-        Self { attrs, doc_comments }
     }
 
-    fn merge(&mut self, other: Self) {
-        self.attrs.extend(other.attrs);
-        self.doc_comments.extend(other.doc_comments);
+    /// Append a second source map to this one, this is required for modules, whose outline and inline
+    /// attributes can reside in different files
+    fn append_module_inline_attrs(&mut self, other: Self) {
+        assert!(self.mod_def_site_file_id.is_none() && other.mod_def_site_file_id.is_none());
+        let len = self.source.len();
+        self.source.extend(other.source);
+        if other.file_id != self.file_id {
+            self.mod_def_site_file_id = Some((other.file_id, len));
+        }
     }
 
     /// Maps the lowered `Attr` back to its original syntax node.
@@ -578,24 +586,21 @@ fn merge(&mut self, other: Self) {
     ///
     /// 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>> {
+        let ast_idx = id.ast_index as usize;
+        let file_id = match self.mod_def_site_file_id {
+            Some((file_id, def_site_cut)) if def_site_cut <= ast_idx => file_id,
+            _ => self.file_id,
+        };
+
+        self.source
+            .get(ast_idx)
+            .map(|it| InFile::new(file_id, it))
+            .unwrap_or_else(|| panic!("cannot find attr at index {:?}", id))
     }
 }
 
@@ -610,6 +615,7 @@ pub struct DocsRangeMap {
 }
 
 impl DocsRangeMap {
+    /// Maps a [`TextRange`] relative to the documentation string back to its AST range
     pub fn map(&self, range: TextRange) -> Option<InFile<TextRange>> {
         let found = self.mapping.binary_search_by(|(probe, ..)| probe.ordering(range)).ok()?;
         let (line_docs_range, idx, original_line_src_range) = self.mapping[found];
@@ -621,8 +627,15 @@ pub fn map(&self, range: TextRange) -> Option<InFile<TextRange>> {
 
         let InFile { file_id, value: source } = self.source_map.source_of_id(idx);
         match source {
-            Either::Left(_) => None, // FIXME, figure out a nice way to handle doc attributes here
-            // as well as for whats done in syntax highlight doc injection
+            Either::Left(attr) => {
+                let string = get_doc_string_in_attr(&attr)?;
+                let text_range = string.open_quote_text_range()?;
+                let range = TextRange::at(
+                    text_range.end() + original_line_src_range.start() + relative_range.start(),
+                    string.syntax().text_range().len().min(range.len()),
+                );
+                Some(InFile { file_id, value: range })
+            }
             Either::Right(comment) => {
                 let text_range = comment.syntax().text_range();
                 let range = TextRange::at(
@@ -638,9 +651,24 @@ pub fn map(&self, range: TextRange) -> Option<InFile<TextRange>> {
     }
 }
 
+fn get_doc_string_in_attr(it: &ast::Attr) -> Option<ast::String> {
+    match it.expr() {
+        // #[doc = lit]
+        Some(ast::Expr::Literal(lit)) => match lit.kind() {
+            ast::LiteralKind::String(it) => Some(it),
+            _ => None,
+        },
+        // #[cfg_attr(..., doc = "", ...)]
+        None => {
+            // FIXME: See highlight injection for what to do here
+            None
+        }
+        _ => None,
+    }
+}
+
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-pub(crate) struct AttrId {
-    is_doc_comment: bool,
+pub struct AttrId {
     pub(crate) ast_index: u32,
 }
 
@@ -675,7 +703,7 @@ fn from_src(
         hygiene: &Hygiene,
         id: AttrId,
     ) -> Option<Attr> {
-        let path = Interned::new(ModPath::from_src(db, ast.path()?, hygiene)?);
+        let path = Interned::new(ModPath::from_src(db.upcast(), ast.path()?, hygiene)?);
         let input = if let Some(ast::Expr::Literal(lit)) = ast.expr() {
             let value = match lit.kind() {
                 ast::LiteralKind::String(string) => string.value()?.into(),
@@ -697,51 +725,44 @@ fn from_tt(
         hygiene: &Hygiene,
         id: AttrId,
     ) -> Option<Attr> {
-        let (parse, _) =
-            mbe::token_tree_to_syntax_node(tt, mbe::ParserEntryPoint::MetaItem).ok()?;
+        let (parse, _) = mbe::token_tree_to_syntax_node(tt, mbe::TopEntryPoint::MetaItem);
         let ast = ast::Meta::cast(parse.syntax_node())?;
 
         Self::from_src(db, ast, hygiene, id)
     }
 
-    /// Parses this attribute as a `#[derive]`, returns an iterator that yields all contained paths
-    /// to derive macros.
-    ///
-    /// Returns `None` when the attribute is not a well-formed `#[derive]` attribute.
-    pub(crate) fn parse_derive(&self) -> Option<impl Iterator<Item = ModPath>> {
-        if self.path.as_ident() != Some(&hir_expand::name![derive]) {
+    /// Parses this attribute as a token tree consisting of comma separated paths.
+    pub fn parse_path_comma_token_tree(&self) -> Option<impl Iterator<Item = ModPath> + '_> {
+        let args = match self.input.as_deref() {
+            Some(AttrInput::TokenTree(args, _)) => args,
+            _ => return None,
+        };
+
+        if args.delimiter_kind() != Some(DelimiterKind::Parenthesis) {
             return None;
         }
+        let paths = args
+            .token_trees
+            .iter()
+            .group_by(|tt| {
+                matches!(tt, tt::TokenTree::Leaf(tt::Leaf::Punct(Punct { char: ',', .. })))
+            })
+            .into_iter()
+            .filter(|(comma, _)| !*comma)
+            .map(|(_, tts)| {
+                let segments = tts.filter_map(|tt| match tt {
+                    tt::TokenTree::Leaf(tt::Leaf::Ident(id)) => Some(id.as_name()),
+                    _ => None,
+                });
+                ModPath::from_segments(PathKind::Plain, segments)
+            })
+            .collect::<Vec<_>>();
 
-        match self.input.as_deref() {
-            Some(AttrInput::TokenTree(args, _)) => {
-                let mut counter = 0;
-                let paths = args
-                    .token_trees
-                    .iter()
-                    .group_by(move |tt| {
-                        match tt {
-                            tt::TokenTree::Leaf(tt::Leaf::Punct(p)) if p.char == ',' => {
-                                counter += 1;
-                            }
-                            _ => {}
-                        }
-                        counter
-                    })
-                    .into_iter()
-                    .map(|(_, tts)| {
-                        let segments = tts.filter_map(|tt| match tt {
-                            tt::TokenTree::Leaf(tt::Leaf::Ident(id)) => Some(id.as_name()),
-                            _ => None,
-                        });
-                        ModPath::from_segments(PathKind::Plain, segments)
-                    })
-                    .collect::<Vec<_>>();
+        Some(paths.into_iter())
+    }
 
-                Some(paths.into_iter())
-            }
-            _ => None,
-        }
+    pub fn path(&self) -> &ModPath {
+        &self.path
     }
 
     pub fn string_value(&self) -> Option<&SmolStr> {
@@ -753,20 +774,20 @@ pub fn string_value(&self) -> Option<&SmolStr> {
 }
 
 #[derive(Debug, Clone, Copy)]
-pub struct AttrQuery<'a> {
-    attrs: &'a Attrs,
+pub struct AttrQuery<'attr> {
+    attrs: &'attr Attrs,
     key: &'static str,
 }
 
-impl<'a> AttrQuery<'a> {
-    pub fn tt_values(self) -> impl Iterator<Item = &'a Subtree> {
+impl<'attr> AttrQuery<'attr> {
+    pub fn tt_values(self) -> impl Iterator<Item = &'attr Subtree> {
         self.attrs().filter_map(|attr| match attr.input.as_deref()? {
             AttrInput::TokenTree(it, _) => Some(it),
             _ => None,
         })
     }
 
-    pub fn string_value(self) -> Option<&'a SmolStr> {
+    pub fn string_value(self) -> Option<&'attr SmolStr> {
         self.attrs().find_map(|attr| match attr.input.as_deref()? {
             AttrInput::Literal(it) => Some(it),
             _ => None,
@@ -777,20 +798,20 @@ pub fn exists(self) -> bool {
         self.attrs().next().is_some()
     }
 
-    pub fn attrs(self) -> impl Iterator<Item = &'a Attr> + Clone {
+    pub fn attrs(self) -> impl Iterator<Item = &'attr Attr> + Clone {
         let key = self.key;
         self.attrs
             .iter()
-            .filter(move |attr| attr.path.as_ident().map_or(false, |s| s.to_string() == key))
+            .filter(move |attr| attr.path.as_ident().map_or(false, |s| s.to_smol_str() == key))
     }
 }
 
 fn attrs_from_ast<N>(src: AstId<N>, db: &dyn DefDatabase) -> RawAttrs
 where
-    N: ast::AttrsOwner,
+    N: ast::HasAttrs,
 {
     let src = InFile::new(src.file_id, src.to_node(db.upcast()));
-    RawAttrs::from_attrs_owner(db, src.as_ref().map(|it| it as &dyn ast::AttrsOwner))
+    RawAttrs::from_attrs_owner(db, src.as_ref().map(|it| it as &dyn ast::HasAttrs))
 }
 
 fn attrs_from_item_tree<N: ItemTreeNode>(id: ItemTreeId<N>, db: &dyn DefDatabase) -> RawAttrs {
@@ -800,33 +821,18 @@ fn attrs_from_item_tree<N: ItemTreeNode>(id: ItemTreeId<N>, db: &dyn DefDatabase
 }
 
 fn collect_attrs(
-    owner: &dyn ast::AttrsOwner,
+    owner: &dyn ast::HasAttrs,
 ) -> impl Iterator<Item = (AttrId, Either<ast::Attr, ast::Comment>)> {
-    let (inner_attrs, inner_docs) = inner_attributes(owner.syntax())
-        .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 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),
-            )
+    let inner_attrs = inner_attributes(owner.syntax()).into_iter().flatten();
+    let outer_attrs =
+        ast::AttrDocCommentIter::from_syntax_node(owner.syntax()).filter(|el| match el {
+            Either::Left(attr) => attr.kind().is_outer(),
+            Either::Right(comment) => comment.is_outer(),
         });
-    // 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))
+    outer_attrs
+        .chain(inner_attrs)
+        .enumerate()
+        .map(|(id, attr)| (AttrId { ast_index: id as u32 }, attr))
 }
 
 pub(crate) fn variants_attrs_source_map(