blob: f11190b281053ca2562f31df3d6ec12f9268ec55 [file] [log] [blame] [edit]
use rustc_ast::tokenstream::TokenStream;
use rustc_ast::{Expr, ast};
use rustc_attr_parsing as attr;
use rustc_attr_parsing::{
CfgSelectBranches, CfgSelectPredicate, EvalConfigResult, parse_cfg_select,
};
use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacResult, MacroExpanderResult};
use rustc_span::{Ident, Span, sym};
use smallvec::SmallVec;
use crate::errors::{CfgSelectNoMatches, CfgSelectUnreachable};
/// This intermediate structure is used to emit parse errors for the branches that are not chosen.
/// The `MacResult` instance below parses all branches, emitting any errors it encounters, but only
/// keeps the parse result for the selected branch.
struct CfgSelectResult<'cx, 'sess> {
ecx: &'cx mut ExtCtxt<'sess>,
site_span: Span,
selected_tts: TokenStream,
selected_span: Span,
other_branches: CfgSelectBranches,
}
fn tts_to_mac_result<'cx, 'sess>(
ecx: &'cx mut ExtCtxt<'sess>,
site_span: Span,
tts: TokenStream,
span: Span,
) -> Box<dyn MacResult + 'cx> {
match ExpandResult::from_tts(ecx, tts, site_span, span, Ident::with_dummy_span(sym::cfg_select))
{
ExpandResult::Ready(x) => x,
_ => unreachable!("from_tts always returns Ready"),
}
}
macro_rules! forward_to_parser_any_macro {
($method_name:ident, $ret_ty:ty) => {
fn $method_name(self: Box<Self>) -> Option<$ret_ty> {
let CfgSelectResult { ecx, site_span, selected_tts, selected_span, .. } = *self;
for (tts, span) in self.other_branches.into_iter_tts() {
let _ = tts_to_mac_result(ecx, site_span, tts, span).$method_name();
}
tts_to_mac_result(ecx, site_span, selected_tts, selected_span).$method_name()
}
};
}
impl<'cx, 'sess> MacResult for CfgSelectResult<'cx, 'sess> {
forward_to_parser_any_macro!(make_expr, Box<Expr>);
forward_to_parser_any_macro!(make_stmts, SmallVec<[ast::Stmt; 1]>);
forward_to_parser_any_macro!(make_items, SmallVec<[Box<ast::Item>; 1]>);
forward_to_parser_any_macro!(make_impl_items, SmallVec<[Box<ast::AssocItem>; 1]>);
forward_to_parser_any_macro!(make_trait_impl_items, SmallVec<[Box<ast::AssocItem>; 1]>);
forward_to_parser_any_macro!(make_trait_items, SmallVec<[Box<ast::AssocItem>; 1]>);
forward_to_parser_any_macro!(make_foreign_items, SmallVec<[Box<ast::ForeignItem>; 1]>);
forward_to_parser_any_macro!(make_ty, Box<ast::Ty>);
forward_to_parser_any_macro!(make_pat, Box<ast::Pat>);
}
pub(super) fn expand_cfg_select<'cx>(
ecx: &'cx mut ExtCtxt<'_>,
sp: Span,
tts: TokenStream,
) -> MacroExpanderResult<'cx> {
ExpandResult::Ready(
match parse_cfg_select(
&mut ecx.new_parser_from_tts(tts),
ecx.sess,
Some(ecx.ecfg.features),
ecx.current_expansion.lint_node_id,
) {
Ok(mut branches) => {
if let Some((underscore, _, _)) = branches.wildcard {
// Warn for every unreachable predicate. We store the fully parsed branch for rustfmt.
for (predicate, _, _) in &branches.unreachable {
let span = match predicate {
CfgSelectPredicate::Wildcard(underscore) => underscore.span,
CfgSelectPredicate::Cfg(cfg) => cfg.span(),
};
let err = CfgSelectUnreachable { span, wildcard_span: underscore.span };
ecx.dcx().emit_warn(err);
}
}
if let Some((selected_tts, selected_span)) = branches.pop_first_match(|cfg| {
matches!(attr::eval_config_entry(&ecx.sess, cfg), EvalConfigResult::True)
}) {
let mac = CfgSelectResult {
ecx,
selected_tts,
selected_span,
other_branches: branches,
site_span: sp,
};
return ExpandResult::Ready(Box::new(mac));
} else {
// Emit a compiler error when none of the predicates matched.
let guar = ecx.dcx().emit_err(CfgSelectNoMatches { span: sp });
DummyResult::any(sp, guar)
}
}
Err(guar) => DummyResult::any(sp, guar),
},
)
}