1
Fork 0

Introduce new parsing infrastructure and types for parsed attributes

fixup docs in parser
This commit is contained in:
Jana Dönszelmann 2025-02-09 22:49:31 +01:00
parent 115b3b03b0
commit dbd3b7928e
No known key found for this signature in database
30 changed files with 1417 additions and 282 deletions

View file

@ -1,4 +1,6 @@
//! You can find more docs on what groups are on [`AttributeParser`] itself.
//! This module defines traits for attribute parsers, little state machines that recognize and parse
//! attributes out of a longer list of attributes. The main trait is called [`AttributeParser`].
//! You can find more docs about [`AttributeParser`]s on the trait itself.
//! However, for many types of attributes, implementing [`AttributeParser`] is not necessary.
//! It allows for a lot of flexibility you might not want.
//!
@ -10,7 +12,16 @@
//! - [`CombineAttributeParser`]: makes it easy to implement an attribute which should combine the
//! contents of attributes, if an attribute appear multiple times in a list
//!
//! Attributes should be added to [`ATTRIBUTE_GROUP_MAPPING`](crate::context::ATTRIBUTE_GROUP_MAPPING) to be parsed.
//! Attributes should be added to [`ATTRIBUTE_MAPPING`](crate::context::ATTRIBUTE_MAPPING) to be parsed.
use std::marker::PhantomData;
use rustc_attr_data_structures::AttributeKind;
use rustc_span::Span;
use thin_vec::ThinVec;
use crate::context::{AcceptContext, FinalizeContext};
use crate::parser::ArgParser;
pub(crate) mod allow_unstable;
pub(crate) mod cfg;
@ -28,3 +39,122 @@ pub use deprecation::*;
pub use repr::*;
pub use stability::*;
pub use transparency::*;
type AcceptFn<T> = fn(&mut T, &AcceptContext<'_>, &ArgParser<'_>);
type AcceptMapping<T> = &'static [(&'static [rustc_span::Symbol], AcceptFn<T>)];
/// An [`AttributeParser`] is a type which searches for syntactic attributes.
///
/// Parsers are often tiny state machines that gets to see all syntactical attributes on an item.
/// [`Default::default`] creates a fresh instance that sits in some kind of initial state, usually that the
/// attribute it is looking for was not yet seen.
///
/// Then, it defines what paths this group will accept in [`AttributeParser::ATTRIBUTES`].
/// These are listed as pairs, of symbols and function pointers. The function pointer will
/// be called when that attribute is found on an item, which can influence the state of the little
/// state machine.
///
/// Finally, after all attributes on an item have been seen, and possibly been accepted,
/// the [`finalize`](AttributeParser::finalize) functions for all attribute parsers are called. Each can then report
/// whether it has seen the attribute it has been looking for.
///
/// The state machine is automatically reset to parse attributes on the next item.
pub(crate) trait AttributeParser: Default + 'static {
/// The symbols for the attributes that this parser is interested in.
///
/// If an attribute has this symbol, the `accept` function will be called on it.
const ATTRIBUTES: AcceptMapping<Self>;
/// The parser has gotten a chance to accept the attributes on an item,
/// here it can produce an attribute.
fn finalize(self, cx: &FinalizeContext<'_>) -> Option<AttributeKind>;
}
/// Alternative to [`AttributeParser`] that automatically handles state management.
/// A slightly simpler and more restricted way to convert attributes.
/// Assumes that an attribute can only appear a single time on an item,
/// and errors when it sees more.
///
/// [`Single<T> where T: SingleAttributeParser`](Single) implements [`AttributeParser`].
///
/// [`SingleAttributeParser`] can only convert attributes one-to-one, and cannot combine multiple
/// attributes together like is necessary for `#[stable()]` and `#[unstable()]` for example.
pub(crate) trait SingleAttributeParser: 'static {
const PATH: &'static [rustc_span::Symbol];
/// Caled when a duplicate attribute is found.
///
/// `first_span` is the span of the first occurrence of this attribute.
// FIXME(jdonszelmann): default error
fn on_duplicate(cx: &AcceptContext<'_>, first_span: Span);
/// Converts a single syntactical attribute to a single semantic attribute, or [`AttributeKind`]
fn convert(cx: &AcceptContext<'_>, args: &ArgParser<'_>) -> Option<AttributeKind>;
}
pub(crate) struct Single<T: SingleAttributeParser>(PhantomData<T>, Option<(AttributeKind, Span)>);
impl<T: SingleAttributeParser> Default for Single<T> {
fn default() -> Self {
Self(Default::default(), Default::default())
}
}
impl<T: SingleAttributeParser> AttributeParser for Single<T> {
const ATTRIBUTES: AcceptMapping<Self> = &[(T::PATH, |group: &mut Single<T>, cx, args| {
if let Some((_, s)) = group.1 {
T::on_duplicate(cx, s);
return;
}
if let Some(pa) = T::convert(cx, args) {
group.1 = Some((pa, cx.attr_span));
}
})];
fn finalize(self, _cx: &FinalizeContext<'_>) -> Option<AttributeKind> {
Some(self.1?.0)
}
}
type ConvertFn<E> = fn(ThinVec<E>) -> AttributeKind;
/// Alternative to [`AttributeParser`] that automatically handles state management.
/// If multiple attributes appear on an element, combines the values of each into a
/// [`ThinVec`].
/// [`Combine<T> where T: CombineAttributeParser`](Combine) implements [`AttributeParser`].
///
/// [`CombineAttributeParser`] can only convert a single kind of attribute, and cannot combine multiple
/// attributes together like is necessary for `#[stable()]` and `#[unstable()]` for example.
pub(crate) trait CombineAttributeParser: 'static {
const PATH: &'static [rustc_span::Symbol];
type Item;
const CONVERT: ConvertFn<Self::Item>;
/// Converts a single syntactical attribute to a number of elements of the semantic attribute, or [`AttributeKind`]
fn extend<'a>(
cx: &'a AcceptContext<'a>,
args: &'a ArgParser<'a>,
) -> impl IntoIterator<Item = Self::Item> + 'a;
}
pub(crate) struct Combine<T: CombineAttributeParser>(
PhantomData<T>,
ThinVec<<T as CombineAttributeParser>::Item>,
);
impl<T: CombineAttributeParser> Default for Combine<T> {
fn default() -> Self {
Self(Default::default(), Default::default())
}
}
impl<T: CombineAttributeParser> AttributeParser for Combine<T> {
const ATTRIBUTES: AcceptMapping<Self> =
&[(T::PATH, |group: &mut Combine<T>, cx, args| group.1.extend(T::extend(cx, args)))];
fn finalize(self, _cx: &FinalizeContext<'_>) -> Option<AttributeKind> {
if self.1.is_empty() { None } else { Some(T::CONVERT(self.1)) }
}
}

View file

@ -0,0 +1,315 @@
use std::collections::BTreeMap;
use std::ops::Deref;
use std::sync::LazyLock;
use rustc_ast::{self as ast, DelimArgs};
use rustc_attr_data_structures::AttributeKind;
use rustc_errors::{DiagCtxtHandle, Diagnostic};
use rustc_feature::Features;
use rustc_hir::{AttrArgs, AttrItem, AttrPath, Attribute, HashIgnoredAttrId};
use rustc_session::Session;
use rustc_span::symbol::kw;
use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span, Symbol, sym};
use crate::attributes::AttributeParser as _;
use crate::parser::{ArgParser, MetaItemParser};
macro_rules! attribute_groups {
(
pub(crate) static $name: ident = [$($names: ty),* $(,)?];
) => {
pub(crate) static $name: LazyLock<(
BTreeMap<&'static [Symbol], Vec<Box<dyn Fn(&AcceptContext<'_>, &ArgParser<'_>) + Send + Sync>>>,
Vec<Box<dyn Send + Sync + Fn(&FinalizeContext<'_>) -> Option<AttributeKind>>>
)> = LazyLock::new(|| {
let mut accepts = BTreeMap::<_, Vec<Box<dyn Fn(&AcceptContext<'_>, &ArgParser<'_>) + Send + Sync>>>::new();
let mut finalizes = Vec::<Box<dyn Send + Sync + Fn(&FinalizeContext<'_>) -> Option<AttributeKind>>>::new();
$(
{
thread_local! {
static STATE_OBJECT: RefCell<$names> = RefCell::new(<$names>::default());
};
for (k, v) in <$names>::ATTRIBUTES {
accepts.entry(*k).or_default().push(Box::new(|cx, args| {
STATE_OBJECT.with_borrow_mut(|s| {
v(s, cx, args)
})
}));
}
finalizes.push(Box::new(|cx| {
let state = STATE_OBJECT.take();
state.finalize(cx)
}));
}
)*
(accepts, finalizes)
});
};
}
attribute_groups!(
pub(crate) static ATTRIBUTE_MAPPING = [
];
);
/// Context given to every attribute parser when accepting
///
/// Gives [`AttributeParser`]s enough information to create errors, for example.
pub(crate) struct AcceptContext<'a> {
pub(crate) group_cx: &'a FinalizeContext<'a>,
/// The span of the attribute currently being parsed
pub(crate) attr_span: Span,
}
impl<'a> AcceptContext<'a> {
pub(crate) fn emit_err(&self, diag: impl Diagnostic<'a>) -> ErrorGuaranteed {
if self.limit_diagnostics {
self.dcx().create_err(diag).delay_as_bug()
} else {
self.dcx().emit_err(diag)
}
}
}
impl<'a> Deref for AcceptContext<'a> {
type Target = FinalizeContext<'a>;
fn deref(&self) -> &Self::Target {
&self.group_cx
}
}
/// Context given to every attribute parser during finalization.
///
/// Gives [`AttributeParser`](crate::attributes::AttributeParser)s enough information to create errors, for example.
pub(crate) struct FinalizeContext<'a> {
/// The parse context, gives access to the session and the
/// diagnostics context.
pub(crate) cx: &'a AttributeParser<'a>,
/// The span of the syntactical component this attribute was applied to
pub(crate) target_span: Span,
}
impl<'a> Deref for FinalizeContext<'a> {
type Target = AttributeParser<'a>;
fn deref(&self) -> &Self::Target {
&self.cx
}
}
#[derive(PartialEq, Clone, Copy, Debug)]
pub enum OmitDoc {
Lower,
Skip,
}
/// Context created once, for example as part of the ast lowering
/// context, through which all attributes can be lowered.
pub struct AttributeParser<'sess> {
#[expect(dead_code)] // FIXME(jdonszelmann): needed later to verify we parsed all attributes
tools: Vec<Symbol>,
sess: &'sess Session,
features: Option<&'sess Features>,
/// *only* parse attributes with this symbol.
///
/// Used in cases where we want the lowering infrastructure for
/// parse just a single attribute.
parse_only: Option<Symbol>,
/// Can be used to instruct parsers to reduce the number of diagnostics it emits.
/// Useful when using `parse_limited` and you know the attr will be reparsed later.
pub(crate) limit_diagnostics: bool,
}
impl<'sess> AttributeParser<'sess> {
/// This method allows you to parse attributes *before* you have access to features or tools.
/// One example where this is necessary, is to parse `feature` attributes themselves for
/// example.
///
/// Try to use this as little as possible. Attributes *should* be lowered during `rustc_ast_lowering`.
/// Some attributes require access to features to parse, which would crash if you tried to do so
/// through [`parse_limited`](Self::parse_limited).
///
/// To make sure use is limited, supply a `Symbol` you'd like to parse. Only attributes with
/// that symbol are picked out of the list of instructions and parsed. Those are returned.
pub fn parse_limited(
sess: &'sess Session,
attrs: &[ast::Attribute],
sym: Symbol,
target_span: Span,
limit_diagnostics: bool,
) -> Option<Attribute> {
let mut parsed = Self {
sess,
features: None,
tools: Vec::new(),
parse_only: Some(sym),
limit_diagnostics,
}
.parse_attribute_list(attrs, target_span, OmitDoc::Skip);
assert!(parsed.len() <= 1);
parsed.pop()
}
pub fn new(sess: &'sess Session, features: &'sess Features, tools: Vec<Symbol>) -> Self {
Self { sess, features: Some(features), tools, parse_only: None, limit_diagnostics: false }
}
pub(crate) fn sess(&self) -> &'sess Session {
self.sess
}
pub(crate) fn features(&self) -> &'sess Features {
self.features.expect("features not available at this point in the compiler")
}
pub(crate) fn dcx(&self) -> DiagCtxtHandle<'sess> {
self.sess.dcx()
}
/// Parse a list of attributes.
///
/// `target_span` is the span of the thing this list of attributes is applied to,
/// and when `omit_doc` is set, doc attributes are filtered out.
pub fn parse_attribute_list<'a>(
&'a self,
attrs: &'a [ast::Attribute],
target_span: Span,
omit_doc: OmitDoc,
) -> Vec<Attribute> {
let mut attributes = Vec::new();
let group_cx = FinalizeContext { cx: self, target_span };
for attr in attrs {
// if we're only looking for a single attribute,
// skip all the ones we don't care about
if let Some(expected) = self.parse_only {
if attr.name_or_empty() != expected {
continue;
}
}
// sometimes, for example for `#![doc = include_str!("readme.md")]`,
// doc still contains a non-literal. You might say, when we're lowering attributes
// that's expanded right? But no, sometimes, when parsing attributes on macros,
// we already use the lowering logic and these are still there. So, when `omit_doc`
// is set we *also* want to ignore these
if omit_doc == OmitDoc::Skip && attr.name_or_empty() == sym::doc {
continue;
}
match &attr.kind {
ast::AttrKind::DocComment(comment_kind, symbol) => {
if omit_doc == OmitDoc::Skip {
continue;
}
attributes.push(Attribute::Parsed(AttributeKind::DocComment {
style: attr.style,
kind: *comment_kind,
span: attr.span,
comment: *symbol,
}))
}
// // FIXME: make doc attributes go through a proper attribute parser
// ast::AttrKind::Normal(n) if n.name_or_empty() == sym::doc => {
// let p = GenericMetaItemParser::from_attr(&n, self.dcx());
//
// attributes.push(Attribute::Parsed(AttributeKind::DocComment {
// style: attr.style,
// kind: CommentKind::Line,
// span: attr.span,
// comment: p.args().name_value(),
// }))
// }
ast::AttrKind::Normal(n) => {
let parser = MetaItemParser::from_attr(n, self.dcx());
let (path, args) = parser.deconstruct();
let parts = path.segments().map(|i| i.name).collect::<Vec<_>>();
if let Some(accepts) = ATTRIBUTE_MAPPING.0.get(parts.as_slice()) {
for f in accepts {
let cx = AcceptContext { group_cx: &group_cx, attr_span: attr.span };
f(&cx, &args)
}
} else {
// if we're here, we must be compiling a tool attribute... Or someone forgot to
// parse their fancy new attribute. Let's warn them in any case. If you are that
// person, and you really your attribute should remain unparsed, carefully read the
// documentation in this module and if you still think so you can add an exception
// to this assertion.
// FIXME(jdonszelmann): convert other attributes, and check with this that
// we caught em all
// const FIXME_TEMPORARY_ATTR_ALLOWLIST: &[Symbol] = &[sym::cfg];
// assert!(
// self.tools.contains(&parts[0]) || true,
// // || FIXME_TEMPORARY_ATTR_ALLOWLIST.contains(&parts[0]),
// "attribute {path} wasn't parsed and isn't a know tool attribute",
// );
attributes.push(Attribute::Unparsed(Box::new(AttrItem {
path: AttrPath::from_ast(&n.item.path),
args: self.lower_attr_args(&n.item.args),
id: HashIgnoredAttrId { attr_id: attr.id },
style: attr.style,
span: attr.span,
})));
}
}
}
}
let mut parsed_attributes = Vec::new();
for f in &ATTRIBUTE_MAPPING.1 {
if let Some(attr) = f(&group_cx) {
parsed_attributes.push(Attribute::Parsed(attr));
}
}
attributes.extend(parsed_attributes);
attributes
}
fn lower_attr_args(&self, args: &ast::AttrArgs) -> AttrArgs {
match args {
ast::AttrArgs::Empty => AttrArgs::Empty,
ast::AttrArgs::Delimited(args) => AttrArgs::Delimited(DelimArgs {
dspan: args.dspan,
delim: args.delim,
tokens: args.tokens.flattened(),
}),
// This is an inert key-value attribute - it will never be visible to macros
// after it gets lowered to HIR. Therefore, we can extract literals to handle
// nonterminals in `#[doc]` (e.g. `#[doc = $e]`).
ast::AttrArgs::Eq { eq_span, expr } => {
// In valid code the value always ends up as a single literal. Otherwise, a dummy
// literal suffices because the error is handled elsewhere.
let lit = if let ast::ExprKind::Lit(token_lit) = expr.kind
&& let Ok(lit) = ast::MetaItemLit::from_token_lit(token_lit, expr.span)
{
lit
} else {
let guar = self.dcx().has_errors().unwrap();
ast::MetaItemLit {
symbol: kw::Empty,
suffix: None,
kind: ast::LitKind::Err(guar),
span: DUMMY_SP,
}
};
AttrArgs::Eq { eq_span: *eq_span, expr: lit }
}
}
}
}

View file

@ -10,11 +10,49 @@
//! These were then parsed or validated or both in places distributed all over the compiler.
//! This was a mess...
//!
//! Attributes are markers on items. Most are actually attribute-like proc-macros, and are expanded
//! but some remain as the so-called built-in attributes. These are not macros at all, and really
//! are just markers to guide the compilation process. An example is `#[inline(...)]` which changes
//! how code for functions is generated. Built-in attributes aren't macros because there's no rust
//! syntax they could expand to.
//! Attributes are markers on items.
//! Many of them are actually attribute-like proc-macros, and are expanded to some other rust syntax.
//! This could either be a user provided proc macro, or something compiler provided.
//! `derive` is an example of one that the compiler provides.
//! These are built-in, but they have a valid expansion to Rust tokens and are thus called "active".
//! I personally like calling these *active* compiler-provided attributes, built-in *macros*,
//! because they still expand, and this helps to differentiate them from built-in *attributes*.
//! However, I'll be the first to admit that the naming here can be confusing.
//!
//! The alternative to active attributes, are inert attributes.
//! These can occur in user code (proc-macro helper attributes).
//! But what's important is, many built-in attributes are inert like this.
//! There is nothing they expand to during the macro expansion process,
//! sometimes because they literally cannot expand to something that is valid Rust.
//! They are really just markers to guide the compilation process.
//! An example is `#[inline(...)]` which changes how code for functions is generated.
//!
//! ```text
//! Active Inert
//! ┌──────────────────────┬──────────────────────┐
//! │ (mostly in) │ these are parsed │
//! │ rustc_builtin_macros │ here! │
//! │ │ │
//! │ │ │
//! │ #[derive(...)] │ #[stable()] │
//! Built-in │ #[cfg()] │ #[inline()] │
//! │ #[cfg_attr()] │ #[repr()] │
//! │ │ │
//! │ │ │
//! │ │ │
//! ├──────────────────────┼──────────────────────┤
//! │ │ │
//! │ │ │
//! │ │ `b` in │
//! │ │ #[proc_macro_derive( │
//! User created │ #[proc_macro_attr()] │ a, │
//! │ │ attributes(b) │
//! │ │ ] │
//! │ │ │
//! │ │ │
//! │ │ │
//! └──────────────────────┴──────────────────────┘
//! ```
//!
//! In this crate, syntactical attributes (sequences of tokens that look like
//! `#[something(something else)]`) are parsed into more semantic attributes, markers on items.
@ -46,9 +84,12 @@
// tidy-alphabetical-end
mod attributes;
mod context;
pub mod parser;
mod session_diagnostics;
pub use attributes::*;
pub use context::{AttributeParser, OmitDoc};
pub use rustc_attr_data_structures::*;
pub use util::{find_crate_name, is_builtin_attr, parse_version};

View file

@ -0,0 +1,625 @@
//! 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::symbol::{Ident, kw};
use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span, Symbol};
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_or_empty(&self) -> Ident {
self.word().unwrap_or_else(Ident::empty)
}
/// 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)
}
}
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(value: &'a AttrArgs, dcx: DiagCtxtHandle<'a>) -> 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),
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,
}
}
/// Asserts that there are no arguments
pub fn no_args(&self) -> bool {
matches!(self, Self::NoArgs)
}
}
/// 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(attr: &'a NormalAttr, dcx: DiagCtxtHandle<'a>) -> 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.
pub fn path_without_args(&self) -> PathParser<'a> {
self.path.clone()
}
/// Gets just the args parser, without caring about the path.
pub fn args(&self) -> &ArgParser<'a> {
&self.args
}
pub fn deconstruct(&self) -> (PathParser<'a>, &ArgParser<'a>) {
(self.path_without_args(), self.args())
}
/// Asserts that this MetaItem starts with a path. 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>, &ArgParser<'a>) {
self.deconstruct()
}
/// Asserts that this MetaItem starts with a word, or single segment path.
/// Doesn't return the args parser.
///
/// For examples. see [`Self::word`]
pub fn word_without_args(&self) -> Option<Ident> {
Some(self.word()?.0)
}
/// Like [`word`](Self::word), but returns an empty symbol instead of None
pub fn word_or_empty_without_args(&self) -> Ident {
self.word_or_empty().0
}
/// 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(&self) -> Option<(Ident, &ArgParser<'a>)> {
let (path, args) = self.deconstruct();
Some((path.word()?, args))
}
/// Like [`word`](Self::word), but returns an empty symbol instead of None
pub fn word_or_empty(&self) -> (Ident, &ArgParser<'a>) {
let (path, args) = self.deconstruct();
(path.word().unwrap_or(Ident::empty()), args)
}
/// Asserts that this MetaItem starts with some specific word.
///
/// See [`word`](Self::word) for examples of what a word is.
pub fn word_is(&self, sym: Symbol) -> Option<&ArgParser<'a>> {
self.path_without_args().word_is(sym).then(|| self.args())
}
/// Asserts that this MetaItem starts with some specific path.
///
/// See [`word`](Self::path) for examples of what a word is.
pub fn path_is(&self, segments: &[Symbol]) -> Option<&ArgParser<'a>> {
self.path_without_args().segments_is(segments).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) -> 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.has_errors().unwrap();
MetaItemLit { symbol: kw::Empty, suffix: None, kind: LitKind::Err(guar), span: DUMMY_SP }
}
}
struct MetaItemListParserContext<'a> {
// the tokens inside the delimiters, so `#[some::attr(a b c)]` would have `a b c` inside
inside_delimiters: Peekable<TokenStreamIter<'a>>,
dcx: DiagCtxtHandle<'a>,
}
impl<'a> MetaItemListParserContext<'a> {
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: token::OpenDelim(_) | token::CloseDelim(_), .. },
_,
) => 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));
}
// or a path.
let path =
if let Some(TokenTree::Token(Token { kind: token::Interpolated(nt), span, .. }, _)) =
self.inside_delimiters.peek()
{
match &**nt {
// or maybe a full nt meta including the path but we return immediately
token::Nonterminal::NtMeta(item) => {
self.inside_delimiters.next();
return Some(MetaItemOrLitParser::MetaItemParser(MetaItemParser {
path: PathParser::Ast(&item.path),
args: ArgParser::from_attr_args(&item.args, self.dcx),
}));
}
// an already interpolated path from a macro expansion is a path, no need to parse
// one from tokens
token::Nonterminal::NtPath(path) => {
self.inside_delimiters.next();
AttrPath::from_ast(path)
}
_ => {
self.inside_delimiters.next();
// we go into this path if an expr ended up in an attribute that
// expansion did not turn into a literal. Say, `#[repr(align(macro!()))]`
// where the macro didn't expand to a literal. An error is already given
// for this at this point, and then we do continue. This makes this path
// reachable...
let e = self.dcx.span_delayed_bug(
*span,
"expr in place where literal is expected (builtin attr parsing)",
);
return Some(MetaItemOrLitParser::Err(*span, e));
}
}
} else {
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(delim: &'a DelimArgs, dcx: DiagCtxtHandle<'a>) -> MetaItemListParser<'a> {
MetaItemListParser::new_tts(delim.tokens.iter(), delim.dspan.entire(), dcx)
}
fn new_tts(tts: TokenStreamIter<'a>, span: Span, dcx: DiagCtxtHandle<'a>) -> 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<'s>(&'s self) -> impl Iterator<Item = &'s MetaItemOrLitParser<'a>> + 's {
self.sub_parsers.iter()
}
pub fn len(&self) -> usize {
self.sub_parsers.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Asserts that every item in the list is another list starting with a word.
///
/// See [`MetaItemParser::word`] for examples of words.
pub fn all_word_list<'s>(&'s self) -> Option<Vec<(Ident, &'s ArgParser<'a>)>> {
self.mixed().map(|i| i.meta_item()?.word()).collect()
}
/// Asserts that every item in the list is another list starting with a full path.
///
/// See [`MetaItemParser::path`] for examples of paths.
pub fn all_path_list<'s>(&'s self) -> Option<Vec<(PathParser<'a>, &'s ArgParser<'a>)>> {
self.mixed().map(|i| Some(i.meta_item()?.path())).collect()
}
/// 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())
}
}