| //! This is in essence an (improved) duplicate of `rustc_ast/attr/mod.rs`. |
| //! That module is intended to be deleted in its entirety. |
| //! |
| //! FIXME(jdonszelmann): delete `rustc_ast/attr/mod.rs` |
| |
| use std::fmt::{Debug, Display}; |
| use std::iter::Peekable; |
| |
| use rustc_ast::token::{self, Delimiter, Token}; |
| use rustc_ast::tokenstream::{TokenStreamIter, TokenTree}; |
| use rustc_ast::{AttrArgs, DelimArgs, Expr, ExprKind, LitKind, MetaItemLit, NormalAttr, Path}; |
| use rustc_ast_pretty::pprust; |
| use rustc_errors::DiagCtxtHandle; |
| use rustc_hir::{self as hir, AttrPath}; |
| use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol, kw, sym}; |
| |
| pub struct SegmentIterator<'a> { |
| offset: usize, |
| path: &'a PathParser<'a>, |
| } |
| |
| impl<'a> Iterator for SegmentIterator<'a> { |
| type Item = &'a Ident; |
| |
| fn next(&mut self) -> Option<Self::Item> { |
| if self.offset >= self.path.len() { |
| return None; |
| } |
| |
| let res = match self.path { |
| PathParser::Ast(ast_path) => &ast_path.segments[self.offset].ident, |
| PathParser::Attr(attr_path) => &attr_path.segments[self.offset], |
| }; |
| |
| self.offset += 1; |
| Some(res) |
| } |
| } |
| |
| #[derive(Clone, Debug)] |
| pub enum PathParser<'a> { |
| Ast(&'a Path), |
| Attr(AttrPath), |
| } |
| |
| impl<'a> PathParser<'a> { |
| pub fn get_attribute_path(&self) -> hir::AttrPath { |
| AttrPath { |
| segments: self.segments().copied().collect::<Vec<_>>().into_boxed_slice(), |
| span: self.span(), |
| } |
| } |
| |
| pub fn segments(&'a self) -> impl Iterator<Item = &'a Ident> { |
| SegmentIterator { offset: 0, path: self } |
| } |
| |
| pub fn span(&self) -> Span { |
| match self { |
| PathParser::Ast(path) => path.span, |
| PathParser::Attr(attr_path) => attr_path.span, |
| } |
| } |
| |
| pub fn len(&self) -> usize { |
| match self { |
| PathParser::Ast(path) => path.segments.len(), |
| PathParser::Attr(attr_path) => attr_path.segments.len(), |
| } |
| } |
| |
| pub fn segments_is(&self, segments: &[Symbol]) -> bool { |
| self.len() == segments.len() && self.segments().zip(segments).all(|(a, b)| a.name == *b) |
| } |
| |
| pub fn word(&self) -> Option<Ident> { |
| (self.len() == 1).then(|| **self.segments().next().as_ref().unwrap()) |
| } |
| |
| pub fn word_sym(&self) -> Option<Symbol> { |
| self.word().map(|ident| ident.name) |
| } |
| |
| /// Asserts that this MetaItem is some specific word. |
| /// |
| /// See [`word`](Self::word) for examples of what a word is. |
| pub fn word_is(&self, sym: Symbol) -> bool { |
| self.word().map(|i| i.name == sym).unwrap_or(false) |
| } |
| |
| /// Checks whether the first segments match the givens. |
| /// |
| /// Unlike [`segments_is`](Self::segments_is), |
| /// `self` may contain more segments than the number matched against. |
| pub fn starts_with(&self, segments: &[Symbol]) -> bool { |
| segments.len() < self.len() && self.segments().zip(segments).all(|(a, b)| a.name == *b) |
| } |
| } |
| |
| impl Display for PathParser<'_> { |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| match self { |
| PathParser::Ast(path) => write!(f, "{}", pprust::path_to_string(path)), |
| PathParser::Attr(attr_path) => write!(f, "{attr_path}"), |
| } |
| } |
| } |
| |
| #[derive(Clone, Debug)] |
| #[must_use] |
| pub enum ArgParser<'a> { |
| NoArgs, |
| List(MetaItemListParser<'a>), |
| NameValue(NameValueParser), |
| } |
| |
| impl<'a> ArgParser<'a> { |
| pub fn span(&self) -> Option<Span> { |
| match self { |
| Self::NoArgs => None, |
| Self::List(l) => Some(l.span), |
| Self::NameValue(n) => Some(n.value_span.with_lo(n.eq_span.lo())), |
| } |
| } |
| |
| pub fn from_attr_args<'sess>(value: &'a AttrArgs, dcx: DiagCtxtHandle<'sess>) -> Self { |
| match value { |
| AttrArgs::Empty => Self::NoArgs, |
| AttrArgs::Delimited(args) if args.delim == Delimiter::Parenthesis => { |
| Self::List(MetaItemListParser::new(args, dcx)) |
| } |
| AttrArgs::Delimited(args) => { |
| Self::List(MetaItemListParser { sub_parsers: vec![], span: args.dspan.entire() }) |
| } |
| AttrArgs::Eq { eq_span, expr } => Self::NameValue(NameValueParser { |
| eq_span: *eq_span, |
| value: expr_to_lit(dcx, &expr, *eq_span), |
| value_span: expr.span, |
| }), |
| } |
| } |
| |
| /// Asserts that this MetaItem is a list |
| /// |
| /// Some examples: |
| /// |
| /// - `#[allow(clippy::complexity)]`: `(clippy::complexity)` is a list |
| /// - `#[rustfmt::skip::macros(target_macro_name)]`: `(target_macro_name)` is a list |
| pub fn list(&self) -> Option<&MetaItemListParser<'a>> { |
| match self { |
| Self::List(l) => Some(l), |
| Self::NameValue(_) | Self::NoArgs => None, |
| } |
| } |
| |
| /// Asserts that this MetaItem is a name-value pair. |
| /// |
| /// Some examples: |
| /// |
| /// - `#[clippy::cyclomatic_complexity = "100"]`: `clippy::cyclomatic_complexity = "100"` is a name value pair, |
| /// where the name is a path (`clippy::cyclomatic_complexity`). You already checked the path |
| /// to get an `ArgParser`, so this method will effectively only assert that the `= "100"` is |
| /// there |
| /// - `#[doc = "hello"]`: `doc = "hello` is also a name value pair |
| pub fn name_value(&self) -> Option<&NameValueParser> { |
| match self { |
| Self::NameValue(n) => Some(n), |
| Self::List(_) | Self::NoArgs => None, |
| } |
| } |
| |
| /// Assert that there were no args. |
| /// If there were, get a span to the arguments |
| /// (to pass to [`AcceptContext::expected_no_args`](crate::context::AcceptContext::expected_no_args)). |
| pub fn no_args(&self) -> Result<(), Span> { |
| match self { |
| Self::NoArgs => Ok(()), |
| Self::List(args) => Err(args.span), |
| Self::NameValue(args) => Err(args.eq_span.to(args.value_span)), |
| } |
| } |
| } |
| |
| /// Inside lists, values could be either literals, or more deeply nested meta items. |
| /// This enum represents that. |
| /// |
| /// Choose which one you want using the provided methods. |
| #[derive(Debug, Clone)] |
| pub enum MetaItemOrLitParser<'a> { |
| MetaItemParser(MetaItemParser<'a>), |
| Lit(MetaItemLit), |
| Err(Span, ErrorGuaranteed), |
| } |
| |
| impl<'a> MetaItemOrLitParser<'a> { |
| pub fn span(&self) -> Span { |
| match self { |
| MetaItemOrLitParser::MetaItemParser(generic_meta_item_parser) => { |
| generic_meta_item_parser.span() |
| } |
| MetaItemOrLitParser::Lit(meta_item_lit) => meta_item_lit.span, |
| MetaItemOrLitParser::Err(span, _) => *span, |
| } |
| } |
| |
| pub fn lit(&self) -> Option<&MetaItemLit> { |
| match self { |
| MetaItemOrLitParser::Lit(meta_item_lit) => Some(meta_item_lit), |
| _ => None, |
| } |
| } |
| |
| pub fn meta_item(&self) -> Option<&MetaItemParser<'a>> { |
| match self { |
| MetaItemOrLitParser::MetaItemParser(parser) => Some(parser), |
| _ => None, |
| } |
| } |
| } |
| |
| /// Utility that deconstructs a MetaItem into usable parts. |
| /// |
| /// MetaItems are syntactically extremely flexible, but specific attributes want to parse |
| /// them in custom, more restricted ways. This can be done using this struct. |
| /// |
| /// MetaItems consist of some path, and some args. The args could be empty. In other words: |
| /// |
| /// - `name` -> args are empty |
| /// - `name(...)` -> args are a [`list`](ArgParser::list), which is the bit between the parentheses |
| /// - `name = value`-> arg is [`name_value`](ArgParser::name_value), where the argument is the |
| /// `= value` part |
| /// |
| /// The syntax of MetaItems can be found at <https://doc.rust-lang.org/reference/attributes.html> |
| #[derive(Clone)] |
| pub struct MetaItemParser<'a> { |
| path: PathParser<'a>, |
| args: ArgParser<'a>, |
| } |
| |
| impl<'a> Debug for MetaItemParser<'a> { |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| f.debug_struct("MetaItemParser") |
| .field("path", &self.path) |
| .field("args", &self.args) |
| .finish() |
| } |
| } |
| |
| impl<'a> MetaItemParser<'a> { |
| /// Create a new parser from a [`NormalAttr`], which is stored inside of any |
| /// [`ast::Attribute`](rustc_ast::Attribute) |
| pub fn from_attr<'sess>(attr: &'a NormalAttr, dcx: DiagCtxtHandle<'sess>) -> Self { |
| Self { |
| path: PathParser::Ast(&attr.item.path), |
| args: ArgParser::from_attr_args(&attr.item.args, dcx), |
| } |
| } |
| } |
| |
| impl<'a> MetaItemParser<'a> { |
| pub fn span(&self) -> Span { |
| if let Some(other) = self.args.span() { |
| self.path.span().with_hi(other.hi()) |
| } else { |
| self.path.span() |
| } |
| } |
| |
| /// Gets just the path, without the args. Some examples: |
| /// |
| /// - `#[rustfmt::skip]`: `rustfmt::skip` is a path |
| /// - `#[allow(clippy::complexity)]`: `clippy::complexity` is a path |
| /// - `#[inline]`: `inline` is a single segment path |
| pub fn path(&self) -> &PathParser<'a> { |
| &self.path |
| } |
| |
| /// Gets just the args parser, without caring about the path. |
| pub fn args(&self) -> &ArgParser<'a> { |
| &self.args |
| } |
| |
| /// Asserts that this MetaItem starts with a word, or single segment path. |
| /// |
| /// Some examples: |
| /// - `#[inline]`: `inline` is a word |
| /// - `#[rustfmt::skip]`: `rustfmt::skip` is a path, |
| /// and not a word and should instead be parsed using [`path`](Self::path) |
| pub fn word_is(&self, sym: Symbol) -> Option<&ArgParser<'a>> { |
| self.path().word_is(sym).then(|| self.args()) |
| } |
| } |
| |
| #[derive(Clone)] |
| pub struct NameValueParser { |
| pub eq_span: Span, |
| value: MetaItemLit, |
| pub value_span: Span, |
| } |
| |
| impl Debug for NameValueParser { |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| f.debug_struct("NameValueParser") |
| .field("eq_span", &self.eq_span) |
| .field("value", &self.value) |
| .field("value_span", &self.value_span) |
| .finish() |
| } |
| } |
| |
| impl NameValueParser { |
| pub fn value_as_lit(&self) -> &MetaItemLit { |
| &self.value |
| } |
| |
| pub fn value_as_str(&self) -> Option<Symbol> { |
| self.value_as_lit().kind.str() |
| } |
| } |
| |
| fn expr_to_lit(dcx: DiagCtxtHandle<'_>, expr: &Expr, span: Span) -> MetaItemLit { |
| // In valid code the value always ends up as a single literal. Otherwise, a dummy |
| // literal suffices because the error is handled elsewhere. |
| if let ExprKind::Lit(token_lit) = expr.kind |
| && let Ok(lit) = MetaItemLit::from_token_lit(token_lit, expr.span) |
| { |
| lit |
| } else { |
| let guar = dcx.span_delayed_bug( |
| span, |
| "expr in place where literal is expected (builtin attr parsing)", |
| ); |
| MetaItemLit { symbol: sym::dummy, suffix: None, kind: LitKind::Err(guar), span } |
| } |
| } |
| |
| struct MetaItemListParserContext<'a, 'sess> { |
| // the tokens inside the delimiters, so `#[some::attr(a b c)]` would have `a b c` inside |
| inside_delimiters: Peekable<TokenStreamIter<'a>>, |
| dcx: DiagCtxtHandle<'sess>, |
| } |
| |
| impl<'a, 'sess> MetaItemListParserContext<'a, 'sess> { |
| fn done(&mut self) -> bool { |
| self.inside_delimiters.peek().is_none() |
| } |
| |
| fn next_path(&mut self) -> Option<AttrPath> { |
| // FIXME: Share code with `parse_path`. |
| let tt = self.inside_delimiters.next().map(|tt| TokenTree::uninterpolate(tt)); |
| |
| match tt.as_deref()? { |
| &TokenTree::Token( |
| Token { kind: ref kind @ (token::Ident(..) | token::PathSep), span }, |
| _, |
| ) => { |
| // here we have either an ident or pathsep `::`. |
| |
| let mut segments = if let &token::Ident(name, _) = kind { |
| // when we lookahead another pathsep, more path's coming |
| if let Some(TokenTree::Token(Token { kind: token::PathSep, .. }, _)) = |
| self.inside_delimiters.peek() |
| { |
| self.inside_delimiters.next(); |
| vec![Ident::new(name, span)] |
| } else { |
| // else we have a single identifier path, that's all |
| return Some(AttrPath { |
| segments: vec![Ident::new(name, span)].into_boxed_slice(), |
| span, |
| }); |
| } |
| } else { |
| // if `::` is all we get, we just got a path root |
| vec![Ident::new(kw::PathRoot, span)] |
| }; |
| |
| // one segment accepted. accept n more |
| loop { |
| // another ident? |
| if let Some(&TokenTree::Token(Token { kind: token::Ident(name, _), span }, _)) = |
| self.inside_delimiters |
| .next() |
| .map(|tt| TokenTree::uninterpolate(tt)) |
| .as_deref() |
| { |
| segments.push(Ident::new(name, span)); |
| } else { |
| return None; |
| } |
| // stop unless we see another `::` |
| if let Some(TokenTree::Token(Token { kind: token::PathSep, .. }, _)) = |
| self.inside_delimiters.peek() |
| { |
| self.inside_delimiters.next(); |
| } else { |
| break; |
| } |
| } |
| let span = span.with_hi(segments.last().unwrap().span.hi()); |
| Some(AttrPath { segments: segments.into_boxed_slice(), span }) |
| } |
| TokenTree::Token(Token { kind, .. }, _) if kind.is_delim() => None, |
| _ => { |
| // malformed attributes can get here. We can't crash, but somewhere else should've |
| // already warned for this. |
| None |
| } |
| } |
| } |
| |
| fn value(&mut self) -> Option<MetaItemLit> { |
| match self.inside_delimiters.next() { |
| Some(TokenTree::Delimited(.., Delimiter::Invisible(_), inner_tokens)) => { |
| MetaItemListParserContext { |
| inside_delimiters: inner_tokens.iter().peekable(), |
| dcx: self.dcx, |
| } |
| .value() |
| } |
| Some(TokenTree::Token(token, _)) => MetaItemLit::from_token(token), |
| _ => None, |
| } |
| } |
| |
| /// parses one element on the inside of a list attribute like `#[my_attr( <insides> )]` |
| /// |
| /// parses a path followed be either: |
| /// 1. nothing (a word attr) |
| /// 2. a parenthesized list |
| /// 3. an equals sign and a literal (name-value) |
| /// |
| /// Can also parse *just* a literal. This is for cases like as `#[my_attr("literal")]` |
| /// where no path is given before the literal |
| /// |
| /// Some exceptions too for interpolated attributes which are already pre-processed |
| fn next(&mut self) -> Option<MetaItemOrLitParser<'a>> { |
| // a list element is either a literal |
| if let Some(TokenTree::Token(token, _)) = self.inside_delimiters.peek() |
| && let Some(lit) = MetaItemLit::from_token(token) |
| { |
| self.inside_delimiters.next(); |
| return Some(MetaItemOrLitParser::Lit(lit)); |
| } else if let Some(TokenTree::Delimited(.., Delimiter::Invisible(_), inner_tokens)) = |
| self.inside_delimiters.peek() |
| { |
| self.inside_delimiters.next(); |
| return MetaItemListParserContext { |
| inside_delimiters: inner_tokens.iter().peekable(), |
| dcx: self.dcx, |
| } |
| .next(); |
| } |
| |
| // or a path. |
| let path = self.next_path()?; |
| |
| // Paths can be followed by: |
| // - `(more meta items)` (another list) |
| // - `= lit` (a name-value) |
| // - nothing |
| Some(MetaItemOrLitParser::MetaItemParser(match self.inside_delimiters.peek() { |
| Some(TokenTree::Delimited(dspan, _, Delimiter::Parenthesis, inner_tokens)) => { |
| self.inside_delimiters.next(); |
| |
| MetaItemParser { |
| path: PathParser::Attr(path), |
| args: ArgParser::List(MetaItemListParser::new_tts( |
| inner_tokens.iter(), |
| dspan.entire(), |
| self.dcx, |
| )), |
| } |
| } |
| Some(TokenTree::Delimited(_, ..)) => { |
| self.inside_delimiters.next(); |
| // self.dcx.span_delayed_bug(span.entire(), "wrong delimiters"); |
| return None; |
| } |
| Some(TokenTree::Token(Token { kind: token::Eq, span }, _)) => { |
| self.inside_delimiters.next(); |
| let value = self.value()?; |
| MetaItemParser { |
| path: PathParser::Attr(path), |
| args: ArgParser::NameValue(NameValueParser { |
| eq_span: *span, |
| value_span: value.span, |
| value, |
| }), |
| } |
| } |
| _ => MetaItemParser { path: PathParser::Attr(path), args: ArgParser::NoArgs }, |
| })) |
| } |
| |
| fn parse(mut self, span: Span) -> MetaItemListParser<'a> { |
| let mut sub_parsers = Vec::new(); |
| |
| while !self.done() { |
| let Some(n) = self.next() else { |
| continue; |
| }; |
| sub_parsers.push(n); |
| |
| match self.inside_delimiters.peek() { |
| None | Some(TokenTree::Token(Token { kind: token::Comma, .. }, _)) => { |
| self.inside_delimiters.next(); |
| } |
| Some(_) => {} |
| } |
| } |
| |
| MetaItemListParser { sub_parsers, span } |
| } |
| } |
| |
| #[derive(Debug, Clone)] |
| pub struct MetaItemListParser<'a> { |
| sub_parsers: Vec<MetaItemOrLitParser<'a>>, |
| pub span: Span, |
| } |
| |
| impl<'a> MetaItemListParser<'a> { |
| fn new<'sess>(delim: &'a DelimArgs, dcx: DiagCtxtHandle<'sess>) -> Self { |
| MetaItemListParser::new_tts(delim.tokens.iter(), delim.dspan.entire(), dcx) |
| } |
| |
| fn new_tts<'sess>(tts: TokenStreamIter<'a>, span: Span, dcx: DiagCtxtHandle<'sess>) -> Self { |
| MetaItemListParserContext { inside_delimiters: tts.peekable(), dcx }.parse(span) |
| } |
| |
| /// Lets you pick and choose as what you want to parse each element in the list |
| pub fn mixed(&self) -> impl Iterator<Item = &MetaItemOrLitParser<'a>> { |
| self.sub_parsers.iter() |
| } |
| |
| pub fn len(&self) -> usize { |
| self.sub_parsers.len() |
| } |
| |
| pub fn is_empty(&self) -> bool { |
| self.len() == 0 |
| } |
| |
| /// Returns Some if the list contains only a single element. |
| /// |
| /// Inside the Some is the parser to parse this single element. |
| pub fn single(&self) -> Option<&MetaItemOrLitParser<'a>> { |
| let mut iter = self.mixed(); |
| iter.next().filter(|_| iter.next().is_none()) |
| } |
| } |