1 //! A higher level attributes based on TokenTree, with also some shortcuts.
3 use std::{ops, sync::Arc};
6 use cfg::{CfgExpr, CfgOptions};
8 use hir_expand::{hygiene::Hygiene, name::AsName, AstId, InFile};
9 use itertools::Itertools;
10 use la_arena::ArenaMap;
11 use mbe::ast_to_token_tree;
12 use smallvec::{smallvec, SmallVec};
14 ast::{self, AstNode, AttrsOwner},
15 match_ast, AstToken, SmolStr, SyntaxNode,
21 item_tree::{ItemTreeId, ItemTreeNode},
22 nameres::ModuleSource,
23 path::{ModPath, PathKind},
24 src::{HasChildSource, HasSource},
25 AdtId, AttrDefId, EnumId, GenericParamId, HasModule, LocalEnumVariantId, LocalFieldId, Lookup,
29 /// Holds documentation
30 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
31 pub struct Documentation(String);
34 pub fn as_str(&self) -> &str {
39 impl From<Documentation> for String {
40 fn from(Documentation(string): Documentation) -> Self {
45 /// Syntactical attributes, without filtering of `cfg_attr`s.
46 #[derive(Default, Debug, Clone, PartialEq, Eq)]
47 pub(crate) struct RawAttrs {
48 entries: Option<Arc<[Attr]>>,
51 #[derive(Default, Debug, Clone, PartialEq, Eq)]
52 pub struct Attrs(RawAttrs);
54 #[derive(Debug, Clone, PartialEq, Eq)]
55 pub struct AttrsWithOwner {
60 impl ops::Deref for RawAttrs {
63 fn deref(&self) -> &[Attr] {
71 impl ops::Deref for Attrs {
74 fn deref(&self) -> &[Attr] {
75 match &self.0.entries {
82 impl ops::Deref for AttrsWithOwner {
85 fn deref(&self) -> &Attrs {
91 pub(crate) const EMPTY: Self = Self { entries: None };
93 pub(crate) fn new(owner: &dyn ast::AttrsOwner, hygiene: &Hygiene) -> Self {
94 let entries = collect_attrs(owner)
96 .flat_map(|(i, attr)| match attr {
97 Either::Left(attr) => Attr::from_src(attr, hygiene, i as u32),
98 Either::Right(comment) => comment.doc_comment().map(|doc| Attr {
100 input: Some(AttrInput::Literal(SmolStr::new(doc))),
101 path: ModPath::from(hir_expand::name!(doc)),
104 .collect::<Arc<_>>();
106 Self { entries: if entries.is_empty() { None } else { Some(entries) } }
109 fn from_attrs_owner(db: &dyn DefDatabase, owner: InFile<&dyn ast::AttrsOwner>) -> Self {
110 let hygiene = Hygiene::new(db.upcast(), owner.file_id);
111 Self::new(owner.value, &hygiene)
114 pub(crate) fn merge(&self, other: Self) -> Self {
115 match (&self.entries, &other.entries) {
116 (None, None) => Self::EMPTY,
117 (Some(entries), None) | (None, Some(entries)) => {
118 Self { entries: Some(entries.clone()) }
120 (Some(a), Some(b)) => {
121 Self { entries: Some(a.iter().chain(b.iter()).cloned().collect()) }
126 /// Processes `cfg_attr`s, returning the resulting semantic `Attrs`.
127 pub(crate) fn filter(self, db: &dyn DefDatabase, krate: CrateId) -> Attrs {
128 let has_cfg_attrs = self.iter().any(|attr| {
129 attr.path.as_ident().map_or(false, |name| *name == hir_expand::name![cfg_attr])
135 let crate_graph = db.crate_graph();
138 .flat_map(|attr| -> SmallVec<[_; 1]> {
140 attr.path.as_ident().map_or(false, |name| *name == hir_expand::name![cfg_attr]);
142 return smallvec![attr.clone()];
145 let subtree = match &attr.input {
146 Some(AttrInput::TokenTree(it)) => it,
147 _ => return smallvec![attr.clone()],
150 // Input subtree is: `(cfg, $(attr),+)`
151 // Split it up into a `cfg` subtree and the `attr` subtrees.
152 // FIXME: There should be a common API for this.
153 let mut parts = subtree.token_trees.split(
154 |tt| matches!(tt, tt::TokenTree::Leaf(tt::Leaf::Punct(p)) if p.char == ','),
156 let cfg = parts.next().unwrap();
157 let cfg = Subtree { delimiter: subtree.delimiter, token_trees: cfg.to_vec() };
158 let cfg = CfgExpr::parse(&cfg);
159 let index = attr.index;
160 let attrs = parts.filter(|a| !a.is_empty()).filter_map(|attr| {
161 let tree = Subtree { delimiter: None, token_trees: attr.to_vec() };
162 let attr = ast::Attr::parse(&format!("#[{}]", tree)).ok()?;
164 let hygiene = Hygiene::new_unhygienic();
165 Attr::from_src(attr, &hygiene, index)
168 let cfg_options = &crate_graph[krate].cfg_options;
169 if cfg_options.check(&cfg) == Some(false) {
172 cov_mark::hit!(cfg_attr_active);
179 Attrs(RawAttrs { entries: Some(new_attrs) })
184 pub const EMPTY: Self = Self(RawAttrs::EMPTY);
186 pub(crate) fn variants_attrs_query(
187 db: &dyn DefDatabase,
189 ) -> Arc<ArenaMap<LocalEnumVariantId, Attrs>> {
190 let krate = e.lookup(db).container.krate;
191 let src = e.child_source(db);
192 let mut res = ArenaMap::default();
194 for (id, var) in src.value.iter() {
195 let attrs = RawAttrs::from_attrs_owner(db, src.with_value(var as &dyn ast::AttrsOwner))
198 res.insert(id, attrs)
204 pub(crate) fn fields_attrs_query(
205 db: &dyn DefDatabase,
207 ) -> Arc<ArenaMap<LocalFieldId, Attrs>> {
208 let krate = v.module(db).krate;
209 let src = v.child_source(db);
210 let mut res = ArenaMap::default();
212 for (id, fld) in src.value.iter() {
213 let attrs = match fld {
214 Either::Left(_tuple) => Attrs::default(),
215 Either::Right(record) => {
216 RawAttrs::from_attrs_owner(db, src.with_value(record)).filter(db, krate)
220 res.insert(id, attrs);
226 pub fn by_key(&self, key: &'static str) -> AttrQuery<'_> {
227 AttrQuery { attrs: self, key }
230 pub fn cfg(&self) -> Option<CfgExpr> {
231 let mut cfgs = self.by_key("cfg").tt_values().map(CfgExpr::parse).collect::<Vec<_>>();
234 1 => Some(cfgs.pop().unwrap()),
235 _ => Some(CfgExpr::All(cfgs)),
238 pub(crate) fn is_cfg_enabled(&self, cfg_options: &CfgOptions) -> bool {
241 Some(cfg) => cfg_options.check(&cfg) != Some(false),
245 pub fn docs(&self) -> Option<Documentation> {
246 let docs = self.by_key("doc").attrs().flat_map(|attr| match attr.input.as_ref()? {
247 AttrInput::Literal(s) => Some(s),
248 AttrInput::TokenTree(_) => None,
252 .flat_map(|s| s.lines())
253 .filter(|line| !line.chars().all(|c| c.is_whitespace()))
254 .map(|line| line.chars().take_while(|c| c.is_whitespace()).count())
257 let mut buf = String::new();
259 // str::lines doesn't yield anything for the empty string
261 buf.extend(Itertools::intersperse(
262 doc.lines().map(|line| {
265 .map_or(line, |(offset, _)| &line[offset..])
277 Some(Documentation(buf))
282 impl AttrsWithOwner {
283 pub(crate) fn attrs_query(db: &dyn DefDatabase, def: AttrDefId) -> Self {
284 // FIXME: this should use `Trace` to avoid duplication in `source_map` below
285 let raw_attrs = match def {
286 AttrDefId::ModuleId(module) => {
287 let def_map = module.def_map(db);
288 let mod_data = &def_map[module.local_id];
289 match mod_data.declaration_source(db) {
291 let raw_attrs = RawAttrs::from_attrs_owner(
293 it.as_ref().map(|it| it as &dyn ast::AttrsOwner),
295 match mod_data.definition_source(db) {
296 InFile { file_id, value: ModuleSource::SourceFile(file) } => raw_attrs
297 .merge(RawAttrs::from_attrs_owner(db, InFile::new(file_id, &file))),
301 None => RawAttrs::from_attrs_owner(
303 mod_data.definition_source(db).as_ref().map(|src| match src {
304 ModuleSource::SourceFile(file) => file as &dyn ast::AttrsOwner,
305 ModuleSource::Module(module) => module as &dyn ast::AttrsOwner,
306 ModuleSource::BlockExpr(block) => block as &dyn ast::AttrsOwner,
311 AttrDefId::FieldId(it) => {
312 return Self { attrs: db.fields_attrs(it.parent)[it.local_id].clone(), owner: def };
314 AttrDefId::EnumVariantId(it) => {
316 attrs: db.variants_attrs(it.parent)[it.local_id].clone(),
320 AttrDefId::AdtId(it) => match it {
321 AdtId::StructId(it) => attrs_from_item_tree(it.lookup(db).id, db),
322 AdtId::EnumId(it) => attrs_from_item_tree(it.lookup(db).id, db),
323 AdtId::UnionId(it) => attrs_from_item_tree(it.lookup(db).id, db),
325 AttrDefId::TraitId(it) => attrs_from_item_tree(it.lookup(db).id, db),
326 AttrDefId::MacroDefId(it) => it
329 .map_or_else(Default::default, |ast_id| attrs_from_ast(ast_id, db)),
330 AttrDefId::ImplId(it) => attrs_from_item_tree(it.lookup(db).id, db),
331 AttrDefId::ConstId(it) => attrs_from_item_tree(it.lookup(db).id, db),
332 AttrDefId::StaticId(it) => attrs_from_item_tree(it.lookup(db).id, db),
333 AttrDefId::FunctionId(it) => attrs_from_item_tree(it.lookup(db).id, db),
334 AttrDefId::TypeAliasId(it) => attrs_from_item_tree(it.lookup(db).id, db),
335 AttrDefId::GenericParamId(it) => match it {
336 GenericParamId::TypeParamId(it) => {
337 let src = it.parent.child_source(db);
338 RawAttrs::from_attrs_owner(
341 src.value[it.local_id].as_ref().either(|it| it as _, |it| it as _),
345 GenericParamId::LifetimeParamId(it) => {
346 let src = it.parent.child_source(db);
347 RawAttrs::from_attrs_owner(db, src.with_value(&src.value[it.local_id]))
349 GenericParamId::ConstParamId(it) => {
350 let src = it.parent.child_source(db);
351 RawAttrs::from_attrs_owner(db, src.with_value(&src.value[it.local_id]))
356 let attrs = raw_attrs.filter(db, def.krate(db));
357 Self { attrs, owner: def }
360 pub fn source_map(&self, db: &dyn DefDatabase) -> AttrSourceMap {
361 let owner = match self.owner {
362 AttrDefId::ModuleId(module) => {
363 // Modules can have 2 attribute owners (the `mod x;` item, and the module file itself).
365 let def_map = module.def_map(db);
366 let mod_data = &def_map[module.local_id];
367 let attrs = match mod_data.declaration_source(db) {
369 let mut attrs: Vec<_> = collect_attrs(&it.value as &dyn ast::AttrsOwner)
370 .map(|attr| InFile::new(it.file_id, attr))
372 if let InFile { file_id, value: ModuleSource::SourceFile(file) } =
373 mod_data.definition_source(db)
376 collect_attrs(&file as &dyn ast::AttrsOwner)
377 .map(|attr| InFile::new(file_id, attr)),
383 let InFile { file_id, value } = mod_data.definition_source(db);
385 ModuleSource::SourceFile(file) => {
386 collect_attrs(file as &dyn ast::AttrsOwner)
388 ModuleSource::Module(module) => {
389 collect_attrs(module as &dyn ast::AttrsOwner)
391 ModuleSource::BlockExpr(block) => {
392 collect_attrs(block as &dyn ast::AttrsOwner)
395 .map(|attr| InFile::new(file_id, attr))
399 return AttrSourceMap { attrs };
401 AttrDefId::FieldId(id) => {
402 id.parent.child_source(db).map(|source| match &source[id.local_id] {
403 Either::Left(field) => ast::AttrsOwnerNode::new(field.clone()),
404 Either::Right(field) => ast::AttrsOwnerNode::new(field.clone()),
407 AttrDefId::AdtId(adt) => match adt {
408 AdtId::StructId(id) => id.lookup(db).source(db).map(ast::AttrsOwnerNode::new),
409 AdtId::UnionId(id) => id.lookup(db).source(db).map(ast::AttrsOwnerNode::new),
410 AdtId::EnumId(id) => id.lookup(db).source(db).map(ast::AttrsOwnerNode::new),
412 AttrDefId::FunctionId(id) => id.lookup(db).source(db).map(ast::AttrsOwnerNode::new),
413 AttrDefId::EnumVariantId(id) => id
416 .map(|source| ast::AttrsOwnerNode::new(source[id.local_id].clone())),
417 AttrDefId::StaticId(id) => id.lookup(db).source(db).map(ast::AttrsOwnerNode::new),
418 AttrDefId::ConstId(id) => id.lookup(db).source(db).map(ast::AttrsOwnerNode::new),
419 AttrDefId::TraitId(id) => id.lookup(db).source(db).map(ast::AttrsOwnerNode::new),
420 AttrDefId::TypeAliasId(id) => id.lookup(db).source(db).map(ast::AttrsOwnerNode::new),
421 AttrDefId::MacroDefId(id) => match id.ast_id() {
422 Either::Left(it) => {
423 it.with_value(ast::AttrsOwnerNode::new(it.to_node(db.upcast())))
425 Either::Right(it) => {
426 it.with_value(ast::AttrsOwnerNode::new(it.to_node(db.upcast())))
429 AttrDefId::ImplId(id) => id.lookup(db).source(db).map(ast::AttrsOwnerNode::new),
430 AttrDefId::GenericParamId(id) => match id {
431 GenericParamId::TypeParamId(id) => {
432 id.parent.child_source(db).map(|source| match &source[id.local_id] {
433 Either::Left(id) => ast::AttrsOwnerNode::new(id.clone()),
434 Either::Right(id) => ast::AttrsOwnerNode::new(id.clone()),
437 GenericParamId::LifetimeParamId(id) => id
440 .map(|source| ast::AttrsOwnerNode::new(source[id.local_id].clone())),
441 GenericParamId::ConstParamId(id) => id
444 .map(|source| ast::AttrsOwnerNode::new(source[id.local_id].clone())),
449 attrs: collect_attrs(&owner.value)
450 .map(|attr| InFile::new(owner.file_id, attr))
458 ) -> Option<(impl Iterator<Item = ast::Attr>, impl Iterator<Item = ast::Comment>)> {
459 let (attrs, docs) = match_ast! {
461 ast::SourceFile(it) => (it.attrs(), ast::CommentIter::from_syntax_node(it.syntax())),
462 ast::ExternBlock(it) => {
463 let extern_item_list = it.extern_item_list()?;
464 (extern_item_list.attrs(), ast::CommentIter::from_syntax_node(extern_item_list.syntax()))
467 let body = it.body()?;
468 (body.attrs(), ast::CommentIter::from_syntax_node(body.syntax()))
471 let assoc_item_list = it.assoc_item_list()?;
472 (assoc_item_list.attrs(), ast::CommentIter::from_syntax_node(assoc_item_list.syntax()))
475 let item_list = it.item_list()?;
476 (item_list.attrs(), ast::CommentIter::from_syntax_node(item_list.syntax()))
478 // FIXME: BlockExpr's only accept inner attributes in specific cases
479 // Excerpt from the reference:
480 // Block expressions accept outer and inner attributes, but only when they are the outer
481 // expression of an expression statement or the final expression of another block expression.
482 ast::BlockExpr(_it) => return None,
486 let attrs = attrs.filter(|attr| attr.excl_token().is_some());
487 let docs = docs.filter(|doc| doc.is_inner());
491 pub struct AttrSourceMap {
492 attrs: Vec<InFile<Either<ast::Attr, ast::Comment>>>,
496 /// Maps the lowered `Attr` back to its original syntax node.
498 /// `attr` must come from the `owner` used for AttrSourceMap
500 /// Note that the returned syntax node might be a `#[cfg_attr]`, or a doc comment, instead of
501 /// the attribute represented by `Attr`.
502 pub fn source_of(&self, attr: &Attr) -> InFile<&Either<ast::Attr, ast::Comment>> {
504 .get(attr.index as usize)
505 .unwrap_or_else(|| panic!("cannot find `Attr` at index {}", attr.index))
510 #[derive(Debug, Clone, PartialEq, Eq)]
513 pub(crate) path: ModPath,
514 pub(crate) input: Option<AttrInput>,
517 #[derive(Debug, Clone, PartialEq, Eq)]
519 /// `#[attr = "string"]`
521 /// `#[attr(subtree)]`
526 fn from_src(ast: ast::Attr, hygiene: &Hygiene, index: u32) -> Option<Attr> {
527 let path = ModPath::from_src(ast.path()?, hygiene)?;
528 let input = if let Some(ast::Expr::Literal(lit)) = ast.expr() {
529 let value = match lit.kind() {
530 ast::LiteralKind::String(string) => string.value()?.into(),
531 _ => lit.syntax().first_token()?.text().trim_matches('"').into(),
533 Some(AttrInput::Literal(value))
534 } else if let Some(tt) = ast.token_tree() {
535 Some(AttrInput::TokenTree(ast_to_token_tree(&tt)?.0))
539 Some(Attr { index, path, input })
542 /// Parses this attribute as a `#[derive]`, returns an iterator that yields all contained paths
543 /// to derive macros.
545 /// Returns `None` when the attribute is not a well-formed `#[derive]` attribute.
546 pub(crate) fn parse_derive(&self) -> Option<impl Iterator<Item = ModPath>> {
547 if self.path.as_ident() != Some(&hir_expand::name![derive]) {
552 Some(AttrInput::TokenTree(args)) => {
557 .group_by(move |tt| {
559 tt::TokenTree::Leaf(tt::Leaf::Punct(p)) if p.char == ',' => {
568 let segments = tts.filter_map(|tt| match tt {
569 tt::TokenTree::Leaf(tt::Leaf::Ident(id)) => Some(id.as_name()),
572 ModPath::from_segments(PathKind::Plain, segments)
574 .collect::<Vec<_>>();
576 Some(paths.into_iter())
582 pub fn string_value(&self) -> Option<&SmolStr> {
583 match self.input.as_ref()? {
584 AttrInput::Literal(it) => Some(it),
590 #[derive(Debug, Clone, Copy)]
591 pub struct AttrQuery<'a> {
596 impl<'a> AttrQuery<'a> {
597 pub fn tt_values(self) -> impl Iterator<Item = &'a Subtree> {
598 self.attrs().filter_map(|attr| match attr.input.as_ref()? {
599 AttrInput::TokenTree(it) => Some(it),
604 pub fn string_value(self) -> Option<&'a SmolStr> {
605 self.attrs().find_map(|attr| match attr.input.as_ref()? {
606 AttrInput::Literal(it) => Some(it),
611 pub fn exists(self) -> bool {
612 self.attrs().next().is_some()
615 pub fn attrs(self) -> impl Iterator<Item = &'a Attr> + Clone {
619 .filter(move |attr| attr.path.as_ident().map_or(false, |s| s.to_string() == key))
623 fn attrs_from_ast<N>(src: AstId<N>, db: &dyn DefDatabase) -> RawAttrs
627 let src = InFile::new(src.file_id, src.to_node(db.upcast()));
628 RawAttrs::from_attrs_owner(db, src.as_ref().map(|it| it as &dyn ast::AttrsOwner))
631 fn attrs_from_item_tree<N: ItemTreeNode>(id: ItemTreeId<N>, db: &dyn DefDatabase) -> RawAttrs {
632 let tree = id.item_tree(db);
633 let mod_item = N::id_to_mod_item(id.value);
634 tree.raw_attrs(mod_item.into()).clone()
638 owner: &dyn ast::AttrsOwner,
639 ) -> impl Iterator<Item = Either<ast::Attr, ast::Comment>> {
640 let (inner_attrs, inner_docs) = inner_attributes(owner.syntax())
641 .map_or((None, None), |(attrs, docs)| (Some(attrs), Some(docs)));
643 let outer_attrs = owner.attrs().filter(|attr| attr.excl_token().is_none());
644 let attrs = outer_attrs
645 .chain(inner_attrs.into_iter().flatten())
646 .map(|attr| (attr.syntax().text_range().start(), Either::Left(attr)));
649 ast::CommentIter::from_syntax_node(owner.syntax()).filter(ast::Comment::is_outer);
650 let docs = outer_docs
651 .chain(inner_docs.into_iter().flatten())
652 .map(|docs_text| (docs_text.syntax().text_range().start(), Either::Right(docs_text)));
653 // sort here by syntax node offset because the source can have doc attributes and doc strings be interleaved
654 let attrs: Vec<_> = docs.chain(attrs).sorted_by_key(|&(offset, _)| offset).collect();
656 attrs.into_iter().map(|(_, attr)| attr)