blob: 7127445a18984fd2e72a9aa4fdc8a414ab424d69 [file]
//! See [`cfg_select!` reference](
//! https://doc.rust-lang.org/nightly/reference/conditional-compilation.html#the-cfg_select-macro
//! ) for grammar.
use std::panic::{AssertUnwindSafe, catch_unwind};
use rustc_ast::ast;
use rustc_ast::token;
use rustc_ast::token::{Token, TokenKind};
use rustc_ast::tokenstream::TokenStream;
use rustc_parse::exp;
use rustc_parse::parser::{AllowConstBlockItems, ForceCollect};
use rustc_span::Span;
use tracing::debug;
use crate::parse::macros::build_stream_parser;
use crate::parse::session::ParseSess;
use crate::spanned::Spanned;
pub(crate) fn parse_items_from_cfg_select<'a>(
psess: &'a ParseSess,
mac: &'a ast::MacCall,
) -> Result<Vec<ast::Item>, &'static str> {
match catch_unwind(AssertUnwindSafe(|| {
parse_items_from_cfg_select_inner(psess, mac)
})) {
Ok(Ok(items)) => Ok(items),
Ok(err @ Err(_)) => err,
Err(..) => Err("failed to parse cfg_select!"),
}
}
fn parse_items_from_cfg_select_inner<'a>(
psess: &'a ParseSess,
mac: &'a ast::MacCall,
) -> Result<Vec<ast::Item>, &'static str> {
let ts = mac.args.tokens.clone();
let mut parser = build_stream_parser(psess.inner(), ts);
if parser.token == TokenKind::OpenBrace {
return Err("Expression position cfg_select! not yet supported");
}
let mut items = vec![];
while parser.token.kind != TokenKind::Eof {
if !parser.eat_keyword(exp!(Underscore)) {
parser.parse_attr_item(ForceCollect::No).map_err(|e| {
e.cancel();
"Failed to parse attr item"
})?;
}
if !parser.eat(exp!(FatArrow)) {
return Err("Expected a fat arrow");
}
if !parser.eat(exp!(OpenBrace)) {
return Err("Expected an opening brace");
}
while parser.token != TokenKind::CloseBrace && parser.token.kind != TokenKind::Eof {
let item = match parser
.parse_item(ForceCollect::No, AllowConstBlockItems::DoesNotMatter)
{
Ok(Some(item_ptr)) => *item_ptr,
Ok(None) => continue,
Err(err) => {
err.cancel();
parser.psess.dcx().reset_err_count();
return Err(
"Expected item inside cfg_select block, but failed to parse it as an item",
);
}
};
if let ast::ItemKind::Mod(..) = item.kind {
items.push(item);
}
}
if !parser.eat(exp!(CloseBrace)) {
return Err("Expected a closing brace");
}
if parser.eat(exp!(Eof)) {
break;
}
}
Ok(items)
}
/// LHS predicate of a `cfg_select!` arm.
pub(crate) enum CfgSelectFormatPredicate {
/// Example: the `unix` in `unix => {}`. Notably, outer or inner attributes are not permitted.
Cfg(ast::MetaItemInner),
/// `_` in `_ => {}`.
Wildcard(Span),
}
impl Spanned for CfgSelectFormatPredicate {
fn span(&self) -> rustc_span::Span {
match self {
Self::Cfg(meta_item_inner) => meta_item_inner.span(),
Self::Wildcard(span) => *span,
}
}
}
/// Each `$predicate => $production` arm in `cfg_select!`.
pub(crate) struct CfgSelectArm {
/// The `$predicate` part.
pub(crate) predicate: CfgSelectFormatPredicate,
/// Span of `=>`.
pub(crate) arrow: Token,
/// The RHS `$production` expression.
pub(crate) expr: Box<ast::Expr>,
/// `cfg_select!` arms `$production`s can be optionally `,` terminated, like `match` arms.
/// The `,` is not needed when `$production` is itself braced `{}`.
pub(crate) trailing_comma: Option<Span>,
}
impl PartialEq for &CfgSelectArm {
fn eq(&self, other: &Self) -> bool {
// consider the arms equal if they have the same span
self.span() == other.span()
}
}
impl Spanned for CfgSelectArm {
fn span(&self) -> Span {
self.predicate
.span()
.with_hi(if let Some(comma) = self.trailing_comma {
comma.hi()
} else {
self.expr.span.hi()
})
}
}
impl std::fmt::Debug for CfgSelectArm {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.predicate {
CfgSelectFormatPredicate::Cfg(cfg_entry) => cfg_entry.fmt(f)?,
CfgSelectFormatPredicate::Wildcard(t) => t.fmt(f)?,
};
write!(f, "=> {:?}", self.expr)
}
}
// FIXME(ytmimi) would be nice if rustfmt didn't need to implement parsing logic on its own
// and could instead just call rustc_attr_parsing::parse_cfg_select, but this is fine for now.
pub(crate) fn parse_cfg_select_arms(
psess: &ParseSess,
ts: TokenStream,
) -> Option<Vec<CfgSelectArm>> {
let mut cfg_select_predicates = vec![];
let mut parser = build_stream_parser(psess.inner(), ts);
while parser.token != token::Eof {
let predicate = if parser.eat_keyword(exp!(Underscore)) {
CfgSelectFormatPredicate::Wildcard(parser.prev_token.span)
} else {
let Ok(meta_item) = parser.parse_meta_item_inner().map_err(|e| e.cancel()) else {
debug!("Failed to parse cfg entry in cfg_select! predicate");
return None;
};
CfgSelectFormatPredicate::Cfg(meta_item)
};
if let Err(e) = parser.expect(exp!(FatArrow)) {
e.cancel();
debug!("Expected to find a `=>` after cfg_selec! predicate.");
return None;
};
let arrow = parser.prev_token;
let Ok(expr) = parser.parse_expr().map_err(|e| e.cancel()) else {
debug!("Couldn't parse cfg_select! arm body after `=>`.");
return None;
};
let trailing_comma = if parser.eat(exp!(Comma)) {
Some(parser.prev_token.span)
} else {
None
};
let arm = CfgSelectArm {
predicate,
arrow,
expr,
trailing_comma,
};
cfg_select_predicates.push(arm);
}
Some(cfg_select_predicates)
}