| //! Functions dealing with attributes and meta items. |
| |
| use std::fmt::Debug; |
| use std::sync::atomic::{AtomicU32, Ordering}; |
| |
| use rustc_index::bit_set::GrowableBitSet; |
| use rustc_span::{Ident, Span, Symbol, sym}; |
| use smallvec::{SmallVec, smallvec}; |
| use thin_vec::{ThinVec, thin_vec}; |
| |
| use crate::ast::{ |
| AttrArgs, AttrId, AttrItem, AttrKind, AttrStyle, AttrVec, Attribute, DUMMY_NODE_ID, DelimArgs, |
| Expr, ExprKind, LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit, NormalAttr, Path, |
| PathSegment, Safety, |
| }; |
| use crate::token::{self, CommentKind, Delimiter, InvisibleOrigin, MetaVarKind, Token}; |
| use crate::tokenstream::{ |
| DelimSpan, LazyAttrTokenStream, Spacing, TokenStream, TokenStreamIter, TokenTree, |
| }; |
| use crate::util::comments; |
| use crate::util::literal::escape_string_symbol; |
| |
| pub struct MarkedAttrs(GrowableBitSet<AttrId>); |
| |
| impl MarkedAttrs { |
| pub fn new() -> Self { |
| // We have no idea how many attributes there will be, so just |
| // initiate the vectors with 0 bits. We'll grow them as necessary. |
| MarkedAttrs(GrowableBitSet::new_empty()) |
| } |
| |
| pub fn mark(&mut self, attr: &Attribute) { |
| self.0.insert(attr.id); |
| } |
| |
| pub fn is_marked(&self, attr: &Attribute) -> bool { |
| self.0.contains(attr.id) |
| } |
| } |
| |
| pub struct AttrIdGenerator(AtomicU32); |
| |
| impl AttrIdGenerator { |
| pub fn new() -> Self { |
| AttrIdGenerator(AtomicU32::new(0)) |
| } |
| |
| pub fn mk_attr_id(&self) -> AttrId { |
| let id = self.0.fetch_add(1, Ordering::Relaxed); |
| assert!(id != u32::MAX); |
| AttrId::from_u32(id) |
| } |
| } |
| |
| impl Attribute { |
| pub fn get_normal_item(&self) -> &AttrItem { |
| match &self.kind { |
| AttrKind::Normal(normal) => &normal.item, |
| AttrKind::DocComment(..) => panic!("unexpected doc comment"), |
| } |
| } |
| |
| pub fn unwrap_normal_item(self) -> AttrItem { |
| match self.kind { |
| AttrKind::Normal(normal) => normal.item, |
| AttrKind::DocComment(..) => panic!("unexpected doc comment"), |
| } |
| } |
| } |
| |
| impl AttributeExt for Attribute { |
| fn id(&self) -> AttrId { |
| self.id |
| } |
| |
| fn value_span(&self) -> Option<Span> { |
| match &self.kind { |
| AttrKind::Normal(normal) => match &normal.item.args { |
| AttrArgs::Eq { expr, .. } => Some(expr.span), |
| _ => None, |
| }, |
| AttrKind::DocComment(..) => None, |
| } |
| } |
| |
| /// Returns `true` if it is a sugared doc comment (`///` or `//!` for example). |
| /// So `#[doc = "doc"]` (which is a doc comment) and `#[doc(...)]` (which is not |
| /// a doc comment) will return `false`. |
| fn is_doc_comment(&self) -> bool { |
| match self.kind { |
| AttrKind::Normal(..) => false, |
| AttrKind::DocComment(..) => true, |
| } |
| } |
| |
| /// For a single-segment attribute, returns its name; otherwise, returns `None`. |
| fn ident(&self) -> Option<Ident> { |
| match &self.kind { |
| AttrKind::Normal(normal) => { |
| if let [ident] = &*normal.item.path.segments { |
| Some(ident.ident) |
| } else { |
| None |
| } |
| } |
| AttrKind::DocComment(..) => None, |
| } |
| } |
| |
| fn ident_path(&self) -> Option<SmallVec<[Ident; 1]>> { |
| match &self.kind { |
| AttrKind::Normal(p) => Some(p.item.path.segments.iter().map(|i| i.ident).collect()), |
| AttrKind::DocComment(_, _) => None, |
| } |
| } |
| |
| fn path_matches(&self, name: &[Symbol]) -> bool { |
| match &self.kind { |
| AttrKind::Normal(normal) => { |
| normal.item.path.segments.len() == name.len() |
| && normal |
| .item |
| .path |
| .segments |
| .iter() |
| .zip(name) |
| .all(|(s, n)| s.args.is_none() && s.ident.name == *n) |
| } |
| AttrKind::DocComment(..) => false, |
| } |
| } |
| |
| fn span(&self) -> Span { |
| self.span |
| } |
| |
| fn is_word(&self) -> bool { |
| if let AttrKind::Normal(normal) = &self.kind { |
| matches!(normal.item.args, AttrArgs::Empty) |
| } else { |
| false |
| } |
| } |
| |
| /// Returns a list of meta items if the attribute is delimited with parenthesis: |
| /// |
| /// ```text |
| /// #[attr(a, b = "c")] // Returns `Some()`. |
| /// #[attr = ""] // Returns `None`. |
| /// #[attr] // Returns `None`. |
| /// ``` |
| fn meta_item_list(&self) -> Option<ThinVec<MetaItemInner>> { |
| match &self.kind { |
| AttrKind::Normal(normal) => normal.item.meta_item_list(), |
| AttrKind::DocComment(..) => None, |
| } |
| } |
| |
| /// Returns the string value in: |
| /// |
| /// ```text |
| /// #[attribute = "value"] |
| /// ^^^^^^^ |
| /// ``` |
| /// |
| /// It returns `None` in any other cases, including doc comments if they |
| /// are not under the form `#[doc = "..."]`. |
| /// |
| /// It also returns `None` for: |
| /// |
| /// ```text |
| /// #[attr("value")] |
| /// ``` |
| fn value_str(&self) -> Option<Symbol> { |
| match &self.kind { |
| AttrKind::Normal(normal) => normal.item.value_str(), |
| AttrKind::DocComment(..) => None, |
| } |
| } |
| |
| /// Returns the documentation and its kind if this is a doc comment or a sugared doc comment. |
| /// * `///doc` returns `Some(("doc", CommentKind::Line))`. |
| /// * `/** doc */` returns `Some(("doc", CommentKind::Block))`. |
| /// * `#[doc = "doc"]` returns `Some(("doc", CommentKind::Line))`. |
| /// * `#[doc(...)]` returns `None`. |
| fn doc_str_and_comment_kind(&self) -> Option<(Symbol, CommentKind)> { |
| match &self.kind { |
| AttrKind::DocComment(kind, data) => Some((*data, *kind)), |
| AttrKind::Normal(normal) if normal.item.path == sym::doc => { |
| normal.item.value_str().map(|s| (s, CommentKind::Line)) |
| } |
| _ => None, |
| } |
| } |
| |
| /// Returns the documentation if this is a doc comment or a sugared doc comment. |
| /// * `///doc` returns `Some("doc")`. |
| /// * `#[doc = "doc"]` returns `Some("doc")`. |
| /// * `#[doc(...)]` returns `None`. |
| fn doc_str(&self) -> Option<Symbol> { |
| match &self.kind { |
| AttrKind::DocComment(.., data) => Some(*data), |
| AttrKind::Normal(normal) if normal.item.path == sym::doc => normal.item.value_str(), |
| _ => None, |
| } |
| } |
| |
| fn doc_resolution_scope(&self) -> Option<AttrStyle> { |
| match &self.kind { |
| AttrKind::DocComment(..) => Some(self.style), |
| AttrKind::Normal(normal) |
| if normal.item.path == sym::doc && normal.item.value_str().is_some() => |
| { |
| Some(self.style) |
| } |
| _ => None, |
| } |
| } |
| |
| fn is_automatically_derived_attr(&self) -> bool { |
| self.has_name(sym::automatically_derived) |
| } |
| } |
| |
| impl Attribute { |
| pub fn style(&self) -> AttrStyle { |
| self.style |
| } |
| |
| pub fn may_have_doc_links(&self) -> bool { |
| self.doc_str().is_some_and(|s| comments::may_have_doc_links(s.as_str())) |
| } |
| |
| /// Extracts the MetaItem from inside this Attribute. |
| pub fn meta(&self) -> Option<MetaItem> { |
| match &self.kind { |
| AttrKind::Normal(normal) => normal.item.meta(self.span), |
| AttrKind::DocComment(..) => None, |
| } |
| } |
| |
| pub fn meta_kind(&self) -> Option<MetaItemKind> { |
| match &self.kind { |
| AttrKind::Normal(normal) => normal.item.meta_kind(), |
| AttrKind::DocComment(..) => None, |
| } |
| } |
| |
| pub fn token_trees(&self) -> Vec<TokenTree> { |
| match self.kind { |
| AttrKind::Normal(ref normal) => normal |
| .tokens |
| .as_ref() |
| .unwrap_or_else(|| panic!("attribute is missing tokens: {self:?}")) |
| .to_attr_token_stream() |
| .to_token_trees(), |
| AttrKind::DocComment(comment_kind, data) => vec![TokenTree::token_alone( |
| token::DocComment(comment_kind, self.style, data), |
| self.span, |
| )], |
| } |
| } |
| } |
| |
| impl AttrItem { |
| pub fn span(&self) -> Span { |
| self.args.span().map_or(self.path.span, |args_span| self.path.span.to(args_span)) |
| } |
| |
| pub fn meta_item_list(&self) -> Option<ThinVec<MetaItemInner>> { |
| match &self.args { |
| AttrArgs::Delimited(args) if args.delim == Delimiter::Parenthesis => { |
| MetaItemKind::list_from_tokens(args.tokens.clone()) |
| } |
| AttrArgs::Delimited(_) | AttrArgs::Eq { .. } | AttrArgs::Empty => None, |
| } |
| } |
| |
| /// Returns the string value in: |
| /// |
| /// ```text |
| /// #[attribute = "value"] |
| /// ^^^^^^^ |
| /// ``` |
| /// |
| /// It returns `None` in any other cases like: |
| /// |
| /// ```text |
| /// #[attr("value")] |
| /// ``` |
| fn value_str(&self) -> Option<Symbol> { |
| match &self.args { |
| AttrArgs::Eq { expr, .. } => match expr.kind { |
| ExprKind::Lit(token_lit) => { |
| LitKind::from_token_lit(token_lit).ok().and_then(|lit| lit.str()) |
| } |
| _ => None, |
| }, |
| AttrArgs::Delimited(_) | AttrArgs::Empty => None, |
| } |
| } |
| |
| pub fn meta(&self, span: Span) -> Option<MetaItem> { |
| Some(MetaItem { |
| unsafety: Safety::Default, |
| path: self.path.clone(), |
| kind: self.meta_kind()?, |
| span, |
| }) |
| } |
| |
| pub fn meta_kind(&self) -> Option<MetaItemKind> { |
| MetaItemKind::from_attr_args(&self.args) |
| } |
| } |
| |
| impl MetaItem { |
| /// For a single-segment meta item, returns its name; otherwise, returns `None`. |
| pub fn ident(&self) -> Option<Ident> { |
| if let [PathSegment { ident, .. }] = self.path.segments[..] { Some(ident) } else { None } |
| } |
| |
| pub fn name(&self) -> Option<Symbol> { |
| self.ident().map(|ident| ident.name) |
| } |
| |
| pub fn has_name(&self, name: Symbol) -> bool { |
| self.path == name |
| } |
| |
| pub fn is_word(&self) -> bool { |
| matches!(self.kind, MetaItemKind::Word) |
| } |
| |
| pub fn meta_item_list(&self) -> Option<&[MetaItemInner]> { |
| match &self.kind { |
| MetaItemKind::List(l) => Some(&**l), |
| _ => None, |
| } |
| } |
| |
| /// ```text |
| /// Example: |
| /// #[attribute(name = "value")] |
| /// ^^^^^^^^^^^^^^ |
| /// ``` |
| pub fn name_value_literal(&self) -> Option<&MetaItemLit> { |
| match &self.kind { |
| MetaItemKind::NameValue(v) => Some(v), |
| _ => None, |
| } |
| } |
| |
| /// This is used in case you want the value span instead of the whole attribute. Example: |
| /// |
| /// ```text |
| /// #[doc(alias = "foo")] |
| /// ``` |
| /// |
| /// In here, it'll return a span for `"foo"`. |
| pub fn name_value_literal_span(&self) -> Option<Span> { |
| Some(self.name_value_literal()?.span) |
| } |
| |
| /// Returns the string value in: |
| /// |
| /// ```text |
| /// #[attribute = "value"] |
| /// ^^^^^^^ |
| /// ``` |
| /// |
| /// It returns `None` in any other cases like: |
| /// |
| /// ```text |
| /// #[attr("value")] |
| /// ``` |
| pub fn value_str(&self) -> Option<Symbol> { |
| match &self.kind { |
| MetaItemKind::NameValue(v) => v.kind.str(), |
| _ => None, |
| } |
| } |
| |
| fn from_tokens(iter: &mut TokenStreamIter<'_>) -> Option<MetaItem> { |
| // FIXME: Share code with `parse_path`. |
| let tt = iter.next().map(|tt| TokenTree::uninterpolate(tt)); |
| let path = match tt.as_deref() { |
| Some(&TokenTree::Token( |
| Token { kind: ref kind @ (token::Ident(..) | token::PathSep), span }, |
| _, |
| )) => 'arm: { |
| let mut segments = if let &token::Ident(name, _) = kind { |
| if let Some(TokenTree::Token(Token { kind: token::PathSep, .. }, _)) = |
| iter.peek() |
| { |
| iter.next(); |
| thin_vec![PathSegment::from_ident(Ident::new(name, span))] |
| } else { |
| break 'arm Path::from_ident(Ident::new(name, span)); |
| } |
| } else { |
| thin_vec![PathSegment::path_root(span)] |
| }; |
| loop { |
| if let Some(&TokenTree::Token(Token { kind: token::Ident(name, _), span }, _)) = |
| iter.next().map(|tt| TokenTree::uninterpolate(tt)).as_deref() |
| { |
| segments.push(PathSegment::from_ident(Ident::new(name, span))); |
| } else { |
| return None; |
| } |
| if let Some(TokenTree::Token(Token { kind: token::PathSep, .. }, _)) = |
| iter.peek() |
| { |
| iter.next(); |
| } else { |
| break; |
| } |
| } |
| let span = span.with_hi(segments.last().unwrap().ident.span.hi()); |
| Path { span, segments, tokens: None } |
| } |
| Some(TokenTree::Delimited( |
| _span, |
| _spacing, |
| Delimiter::Invisible(InvisibleOrigin::MetaVar( |
| MetaVarKind::Meta { .. } | MetaVarKind::Path, |
| )), |
| _stream, |
| )) => { |
| // This path is currently unreachable in the test suite. |
| unreachable!() |
| } |
| Some(TokenTree::Token(Token { kind, .. }, _)) if kind.is_delim() => { |
| panic!("Should be `AttrTokenTree::Delimited`, not delim tokens: {:?}", tt); |
| } |
| _ => return None, |
| }; |
| let list_closing_paren_pos = iter.peek().map(|tt| tt.span().hi()); |
| let kind = MetaItemKind::from_tokens(iter)?; |
| let hi = match &kind { |
| MetaItemKind::NameValue(lit) => lit.span.hi(), |
| MetaItemKind::List(..) => list_closing_paren_pos.unwrap_or(path.span.hi()), |
| _ => path.span.hi(), |
| }; |
| let span = path.span.with_hi(hi); |
| // FIXME: This parses `unsafe()` not as unsafe attribute syntax in `MetaItem`, |
| // but as a parenthesized list. This (and likely `MetaItem`) should be changed in |
| // such a way that builtin macros don't accept extraneous `unsafe()`. |
| Some(MetaItem { unsafety: Safety::Default, path, kind, span }) |
| } |
| } |
| |
| impl MetaItemKind { |
| // public because it can be called in the hir |
| pub fn list_from_tokens(tokens: TokenStream) -> Option<ThinVec<MetaItemInner>> { |
| let mut iter = tokens.iter(); |
| let mut result = ThinVec::new(); |
| while iter.peek().is_some() { |
| let item = MetaItemInner::from_tokens(&mut iter)?; |
| result.push(item); |
| match iter.next() { |
| None | Some(TokenTree::Token(Token { kind: token::Comma, .. }, _)) => {} |
| _ => return None, |
| } |
| } |
| Some(result) |
| } |
| |
| fn name_value_from_tokens(iter: &mut TokenStreamIter<'_>) -> Option<MetaItemKind> { |
| match iter.next() { |
| Some(TokenTree::Delimited(.., Delimiter::Invisible(_), inner_tokens)) => { |
| MetaItemKind::name_value_from_tokens(&mut inner_tokens.iter()) |
| } |
| Some(TokenTree::Token(token, _)) => { |
| MetaItemLit::from_token(token).map(MetaItemKind::NameValue) |
| } |
| _ => None, |
| } |
| } |
| |
| fn from_tokens(iter: &mut TokenStreamIter<'_>) -> Option<MetaItemKind> { |
| match iter.peek() { |
| Some(TokenTree::Delimited(.., Delimiter::Parenthesis, inner_tokens)) => { |
| let inner_tokens = inner_tokens.clone(); |
| iter.next(); |
| MetaItemKind::list_from_tokens(inner_tokens).map(MetaItemKind::List) |
| } |
| Some(TokenTree::Delimited(..)) => None, |
| Some(TokenTree::Token(Token { kind: token::Eq, .. }, _)) => { |
| iter.next(); |
| MetaItemKind::name_value_from_tokens(iter) |
| } |
| _ => Some(MetaItemKind::Word), |
| } |
| } |
| |
| fn from_attr_args(args: &AttrArgs) -> Option<MetaItemKind> { |
| match args { |
| AttrArgs::Empty => Some(MetaItemKind::Word), |
| AttrArgs::Delimited(DelimArgs { dspan: _, delim: Delimiter::Parenthesis, tokens }) => { |
| MetaItemKind::list_from_tokens(tokens.clone()).map(MetaItemKind::List) |
| } |
| AttrArgs::Delimited(..) => None, |
| AttrArgs::Eq { expr, .. } => match expr.kind { |
| ExprKind::Lit(token_lit) => { |
| // Turn failures to `None`, we'll get parse errors elsewhere. |
| MetaItemLit::from_token_lit(token_lit, expr.span) |
| .ok() |
| .map(|lit| MetaItemKind::NameValue(lit)) |
| } |
| _ => None, |
| }, |
| } |
| } |
| } |
| |
| impl MetaItemInner { |
| pub fn span(&self) -> Span { |
| match self { |
| MetaItemInner::MetaItem(item) => item.span, |
| MetaItemInner::Lit(lit) => lit.span, |
| } |
| } |
| |
| /// For a single-segment meta item, returns its identifier; otherwise, returns `None`. |
| pub fn ident(&self) -> Option<Ident> { |
| self.meta_item().and_then(|meta_item| meta_item.ident()) |
| } |
| |
| /// For a single-segment meta item, returns its name; otherwise, returns `None`. |
| pub fn name(&self) -> Option<Symbol> { |
| self.ident().map(|ident| ident.name) |
| } |
| |
| /// Returns `true` if this list item is a MetaItem with a name of `name`. |
| pub fn has_name(&self, name: Symbol) -> bool { |
| self.meta_item().is_some_and(|meta_item| meta_item.has_name(name)) |
| } |
| |
| /// Returns `true` if `self` is a `MetaItem` and the meta item is a word. |
| pub fn is_word(&self) -> bool { |
| self.meta_item().is_some_and(|meta_item| meta_item.is_word()) |
| } |
| |
| /// Gets a list of inner meta items from a list `MetaItem` type. |
| pub fn meta_item_list(&self) -> Option<&[MetaItemInner]> { |
| self.meta_item().and_then(|meta_item| meta_item.meta_item_list()) |
| } |
| |
| /// If it's a singleton list of the form `foo(lit)`, returns the `foo` and |
| /// the `lit`. |
| pub fn singleton_lit_list(&self) -> Option<(Symbol, &MetaItemLit)> { |
| self.meta_item().and_then(|meta_item| { |
| meta_item.meta_item_list().and_then(|meta_item_list| { |
| if meta_item_list.len() == 1 |
| && let Some(ident) = meta_item.ident() |
| && let Some(lit) = meta_item_list[0].lit() |
| { |
| return Some((ident.name, lit)); |
| } |
| None |
| }) |
| }) |
| } |
| |
| /// See [`MetaItem::name_value_literal_span`]. |
| pub fn name_value_literal_span(&self) -> Option<Span> { |
| self.meta_item()?.name_value_literal_span() |
| } |
| |
| /// Gets the string value if `self` is a `MetaItem` and the `MetaItem` is a |
| /// `MetaItemKind::NameValue` variant containing a string, otherwise `None`. |
| pub fn value_str(&self) -> Option<Symbol> { |
| self.meta_item().and_then(|meta_item| meta_item.value_str()) |
| } |
| |
| /// Returns the `MetaItemLit` if `self` is a `MetaItemInner::Literal`s. |
| pub fn lit(&self) -> Option<&MetaItemLit> { |
| match self { |
| MetaItemInner::Lit(lit) => Some(lit), |
| _ => None, |
| } |
| } |
| |
| /// Returns the bool if `self` is a boolean `MetaItemInner::Literal`. |
| pub fn boolean_literal(&self) -> Option<bool> { |
| match self { |
| MetaItemInner::Lit(MetaItemLit { kind: LitKind::Bool(b), .. }) => Some(*b), |
| _ => None, |
| } |
| } |
| |
| /// Returns the `MetaItem` if `self` is a `MetaItemInner::MetaItem` or if it's |
| /// `MetaItemInner::Lit(MetaItemLit { kind: LitKind::Bool(_), .. })`. |
| pub fn meta_item_or_bool(&self) -> Option<&MetaItemInner> { |
| match self { |
| MetaItemInner::MetaItem(_item) => Some(self), |
| MetaItemInner::Lit(MetaItemLit { kind: LitKind::Bool(_), .. }) => Some(self), |
| _ => None, |
| } |
| } |
| |
| /// Returns the `MetaItem` if `self` is a `MetaItemInner::MetaItem`. |
| pub fn meta_item(&self) -> Option<&MetaItem> { |
| match self { |
| MetaItemInner::MetaItem(item) => Some(item), |
| _ => None, |
| } |
| } |
| |
| /// Returns `true` if the variant is `MetaItem`. |
| pub fn is_meta_item(&self) -> bool { |
| self.meta_item().is_some() |
| } |
| |
| fn from_tokens(iter: &mut TokenStreamIter<'_>) -> Option<MetaItemInner> { |
| match iter.peek() { |
| Some(TokenTree::Token(token, _)) if let Some(lit) = MetaItemLit::from_token(token) => { |
| iter.next(); |
| return Some(MetaItemInner::Lit(lit)); |
| } |
| Some(TokenTree::Delimited(.., Delimiter::Invisible(_), inner_tokens)) => { |
| iter.next(); |
| return MetaItemInner::from_tokens(&mut inner_tokens.iter()); |
| } |
| _ => {} |
| } |
| MetaItem::from_tokens(iter).map(MetaItemInner::MetaItem) |
| } |
| } |
| |
| pub fn mk_doc_comment( |
| g: &AttrIdGenerator, |
| comment_kind: CommentKind, |
| style: AttrStyle, |
| data: Symbol, |
| span: Span, |
| ) -> Attribute { |
| Attribute { kind: AttrKind::DocComment(comment_kind, data), id: g.mk_attr_id(), style, span } |
| } |
| |
| fn mk_attr( |
| g: &AttrIdGenerator, |
| style: AttrStyle, |
| unsafety: Safety, |
| path: Path, |
| args: AttrArgs, |
| span: Span, |
| ) -> Attribute { |
| mk_attr_from_item(g, AttrItem { unsafety, path, args, tokens: None }, None, style, span) |
| } |
| |
| pub fn mk_attr_from_item( |
| g: &AttrIdGenerator, |
| item: AttrItem, |
| tokens: Option<LazyAttrTokenStream>, |
| style: AttrStyle, |
| span: Span, |
| ) -> Attribute { |
| Attribute { |
| kind: AttrKind::Normal(Box::new(NormalAttr { item, tokens })), |
| id: g.mk_attr_id(), |
| style, |
| span, |
| } |
| } |
| |
| pub fn mk_attr_word( |
| g: &AttrIdGenerator, |
| style: AttrStyle, |
| unsafety: Safety, |
| name: Symbol, |
| span: Span, |
| ) -> Attribute { |
| let path = Path::from_ident(Ident::new(name, span)); |
| let args = AttrArgs::Empty; |
| mk_attr(g, style, unsafety, path, args, span) |
| } |
| |
| pub fn mk_attr_nested_word( |
| g: &AttrIdGenerator, |
| style: AttrStyle, |
| unsafety: Safety, |
| outer: Symbol, |
| inner: Symbol, |
| span: Span, |
| ) -> Attribute { |
| let inner_tokens = TokenStream::new(vec![TokenTree::Token( |
| Token::from_ast_ident(Ident::new(inner, span)), |
| Spacing::Alone, |
| )]); |
| let outer_ident = Ident::new(outer, span); |
| let path = Path::from_ident(outer_ident); |
| let attr_args = AttrArgs::Delimited(DelimArgs { |
| dspan: DelimSpan::from_single(span), |
| delim: Delimiter::Parenthesis, |
| tokens: inner_tokens, |
| }); |
| mk_attr(g, style, unsafety, path, attr_args, span) |
| } |
| |
| pub fn mk_attr_name_value_str( |
| g: &AttrIdGenerator, |
| style: AttrStyle, |
| unsafety: Safety, |
| name: Symbol, |
| val: Symbol, |
| span: Span, |
| ) -> Attribute { |
| let lit = token::Lit::new(token::Str, escape_string_symbol(val), None); |
| let expr = Box::new(Expr { |
| id: DUMMY_NODE_ID, |
| kind: ExprKind::Lit(lit), |
| span, |
| attrs: AttrVec::new(), |
| tokens: None, |
| }); |
| let path = Path::from_ident(Ident::new(name, span)); |
| let args = AttrArgs::Eq { eq_span: span, expr }; |
| mk_attr(g, style, unsafety, path, args, span) |
| } |
| |
| pub fn filter_by_name<A: AttributeExt>(attrs: &[A], name: Symbol) -> impl Iterator<Item = &A> { |
| attrs.iter().filter(move |attr| attr.has_name(name)) |
| } |
| |
| pub fn find_by_name<A: AttributeExt>(attrs: &[A], name: Symbol) -> Option<&A> { |
| filter_by_name(attrs, name).next() |
| } |
| |
| pub fn first_attr_value_str_by_name(attrs: &[impl AttributeExt], name: Symbol) -> Option<Symbol> { |
| find_by_name(attrs, name).and_then(|attr| attr.value_str()) |
| } |
| |
| pub fn contains_name(attrs: &[impl AttributeExt], name: Symbol) -> bool { |
| find_by_name(attrs, name).is_some() |
| } |
| |
| pub fn list_contains_name(items: &[MetaItemInner], name: Symbol) -> bool { |
| items.iter().any(|item| item.has_name(name)) |
| } |
| |
| impl MetaItemLit { |
| pub fn value_str(&self) -> Option<Symbol> { |
| LitKind::from_token_lit(self.as_token_lit()).ok().and_then(|lit| lit.str()) |
| } |
| } |
| |
| pub trait AttributeExt: Debug { |
| fn id(&self) -> AttrId; |
| |
| /// For a single-segment attribute (i.e., `#[attr]` and not `#[path::atrr]`), |
| /// return the name of the attribute; otherwise, returns `None`. |
| fn name(&self) -> Option<Symbol> { |
| self.ident().map(|ident| ident.name) |
| } |
| |
| /// Get the meta item list, `#[attr(meta item list)]` |
| fn meta_item_list(&self) -> Option<ThinVec<MetaItemInner>>; |
| |
| /// Gets the value literal, as string, when using `#[attr = value]` |
| fn value_str(&self) -> Option<Symbol>; |
| |
| /// Gets the span of the value literal, as string, when using `#[attr = value]` |
| fn value_span(&self) -> Option<Span>; |
| |
| /// For a single-segment attribute, returns its ident; otherwise, returns `None`. |
| fn ident(&self) -> Option<Ident>; |
| |
| /// Checks whether the path of this attribute matches the name. |
| /// |
| /// Matches one segment of the path to each element in `name` |
| fn path_matches(&self, name: &[Symbol]) -> bool; |
| |
| /// Returns `true` if it is a sugared doc comment (`///` or `//!` for example). |
| /// So `#[doc = "doc"]` (which is a doc comment) and `#[doc(...)]` (which is not |
| /// a doc comment) will return `false`. |
| fn is_doc_comment(&self) -> bool; |
| |
| #[inline] |
| fn has_name(&self, name: Symbol) -> bool { |
| self.ident().map(|x| x.name == name).unwrap_or(false) |
| } |
| |
| #[inline] |
| fn has_any_name(&self, names: &[Symbol]) -> bool { |
| names.iter().any(|&name| self.has_name(name)) |
| } |
| |
| /// get the span of the entire attribute |
| fn span(&self) -> Span; |
| |
| fn is_word(&self) -> bool; |
| |
| fn path(&self) -> SmallVec<[Symbol; 1]> { |
| self.ident_path() |
| .map(|i| i.into_iter().map(|i| i.name).collect()) |
| .unwrap_or(smallvec![sym::doc]) |
| } |
| |
| /// Returns None for doc comments |
| fn ident_path(&self) -> Option<SmallVec<[Ident; 1]>>; |
| |
| /// Returns the documentation if this is a doc comment or a sugared doc comment. |
| /// * `///doc` returns `Some("doc")`. |
| /// * `#[doc = "doc"]` returns `Some("doc")`. |
| /// * `#[doc(...)]` returns `None`. |
| fn doc_str(&self) -> Option<Symbol>; |
| |
| fn is_proc_macro_attr(&self) -> bool { |
| [sym::proc_macro, sym::proc_macro_attribute, sym::proc_macro_derive] |
| .iter() |
| .any(|kind| self.has_name(*kind)) |
| } |
| fn is_automatically_derived_attr(&self) -> bool; |
| |
| /// Returns the documentation and its kind if this is a doc comment or a sugared doc comment. |
| /// * `///doc` returns `Some(("doc", CommentKind::Line))`. |
| /// * `/** doc */` returns `Some(("doc", CommentKind::Block))`. |
| /// * `#[doc = "doc"]` returns `Some(("doc", CommentKind::Line))`. |
| /// * `#[doc(...)]` returns `None`. |
| fn doc_str_and_comment_kind(&self) -> Option<(Symbol, CommentKind)>; |
| |
| /// Returns outer or inner if this is a doc attribute or a sugared doc |
| /// comment, otherwise None. |
| /// |
| /// This is used in the case of doc comments on modules, to decide whether |
| /// to resolve intra-doc links against the symbols in scope within the |
| /// commented module (for inner doc) vs within its parent module (for outer |
| /// doc). |
| fn doc_resolution_scope(&self) -> Option<AttrStyle>; |
| } |
| |
| // FIXME(fn_delegation): use function delegation instead of manually forwarding |
| |
| impl Attribute { |
| pub fn id(&self) -> AttrId { |
| AttributeExt::id(self) |
| } |
| |
| pub fn name(&self) -> Option<Symbol> { |
| AttributeExt::name(self) |
| } |
| |
| pub fn meta_item_list(&self) -> Option<ThinVec<MetaItemInner>> { |
| AttributeExt::meta_item_list(self) |
| } |
| |
| pub fn value_str(&self) -> Option<Symbol> { |
| AttributeExt::value_str(self) |
| } |
| |
| pub fn value_span(&self) -> Option<Span> { |
| AttributeExt::value_span(self) |
| } |
| |
| pub fn ident(&self) -> Option<Ident> { |
| AttributeExt::ident(self) |
| } |
| |
| pub fn path_matches(&self, name: &[Symbol]) -> bool { |
| AttributeExt::path_matches(self, name) |
| } |
| |
| pub fn is_doc_comment(&self) -> bool { |
| AttributeExt::is_doc_comment(self) |
| } |
| |
| #[inline] |
| pub fn has_name(&self, name: Symbol) -> bool { |
| AttributeExt::has_name(self, name) |
| } |
| |
| #[inline] |
| pub fn has_any_name(&self, names: &[Symbol]) -> bool { |
| AttributeExt::has_any_name(self, names) |
| } |
| |
| pub fn span(&self) -> Span { |
| AttributeExt::span(self) |
| } |
| |
| pub fn is_word(&self) -> bool { |
| AttributeExt::is_word(self) |
| } |
| |
| pub fn path(&self) -> SmallVec<[Symbol; 1]> { |
| AttributeExt::path(self) |
| } |
| |
| pub fn ident_path(&self) -> Option<SmallVec<[Ident; 1]>> { |
| AttributeExt::ident_path(self) |
| } |
| |
| pub fn doc_str(&self) -> Option<Symbol> { |
| AttributeExt::doc_str(self) |
| } |
| |
| pub fn is_proc_macro_attr(&self) -> bool { |
| AttributeExt::is_proc_macro_attr(self) |
| } |
| |
| pub fn doc_str_and_comment_kind(&self) -> Option<(Symbol, CommentKind)> { |
| AttributeExt::doc_str_and_comment_kind(self) |
| } |
| } |