//! 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;
}
}
}
+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];
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))
}
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)
}
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)
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,
};
}
}
+ 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),
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
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,
}),
),
}
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));
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));
}
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(
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.
///
/// 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))
}
}
}
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];
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(
}
}
+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,
}
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(),
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> {
}
#[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,
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 {
}
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(