| use rustc_ast::attr::AttrIdGenerator; | |
| use rustc_ast::tokenstream::TokenStream; | |
| use rustc_ast::{AttrKind, Expr, SyntheticAttr, ast}; | |
| use rustc_attr_ir::CfgEntry; | |
| use rustc_attr_parsing as attr; | |
| use rustc_attr_parsing::{CfgSelectBranches, parse_cfg_select}; | |
| use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacResult, MacroExpanderResult}; | |
| use rustc_expand::expand::DeclaredIdents; | |
| use rustc_span::{Ident, Span, sym}; | |
| use smallvec::SmallVec; | |
| use crate::diagnostics::CfgSelectNoMatches; | |
| /// 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, | |
| cfg_entry: CfgEntry, | |
| } | |
| 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, $other:expr, $selected:expr) => { | |
| fn $method_name(self: Box<Self>) -> Option<$ret_ty> { | |
| let CfgSelectResult { ecx, site_span, selected_tts, selected_span, cfg_entry, .. } = | |
| *self; | |
| for (cfg_entry, tts, span) in self.other_branches.into_iter_tts() { | |
| let result = tts_to_mac_result(ecx, site_span, tts, span).$method_name(); | |
| $other(&mut *ecx, cfg_entry, span, result); | |
| } | |
| tts_to_mac_result(ecx, site_span, selected_tts, selected_span) | |
| .$method_name() | |
| .map(|elements| $selected(&mut *ecx, cfg_entry, elements)) | |
| } | |
| }; | |
| ($method_name:ident, $ret_ty:ty) => { | |
| forward_to_parser_any_macro!($method_name, $ret_ty, |_, _, _, _| {}, |_, _, elements| { | |
| elements | |
| }); | |
| }; | |
| } | |
| /// Construct a `#[<cfg_trace>]` attribute from a `CfgEntry`. This allows us to keep track of items | |
| /// that were behind a `cfg_select!`, which is relevant for some diagnostics. | |
| fn mk_attr(g: &AttrIdGenerator, cfg_entry: CfgEntry) -> ast::Attribute { | |
| let cfg_span = cfg_entry.span(); | |
| ast::Attribute { | |
| kind: AttrKind::Synthetic(Box::new(SyntheticAttr::CfgAttrTrace(cfg_entry))), | |
| id: g.mk_attr_id(), | |
| style: ast::AttrStyle::Outer, | |
| span: cfg_span, | |
| } | |
| } | |
| 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]>, | |
| |ecx: &mut ExtCtxt<'_>, | |
| cfg_entry: CfgEntry, | |
| _span: Span, | |
| items: Option<SmallVec<[Box<ast::Item>; 1]>>| if let Some(items) = items { | |
| // Register item names that were not selected for error reporting. We do this | |
| // for `#[cfg]` too. | |
| for item in items { | |
| for name in item.declared_idents() { | |
| ecx.resolver.append_stripped_cfg_item( | |
| ecx.current_expansion.lint_node_id, | |
| name, | |
| cfg_entry.clone(), | |
| cfg_entry.span(), | |
| ); | |
| } | |
| } | |
| }, | |
| |ecx: &mut ExtCtxt<'_>, cfg_entry: CfgEntry, items: SmallVec<[Box<ast::Item>; 1]>| { | |
| items | |
| .into_iter() | |
| .map(|mut item| { | |
| item.attrs.push(mk_attr(&ecx.sess.psess.attr_id_generator, cfg_entry.clone())); | |
| item | |
| }) | |
| .collect() | |
| } | |
| ); | |
| 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((cfg_entry, selected_tts, selected_span)) = | |
| branches.pop_first_match(|cfg| attr::eval_config_entry(ecx.sess, cfg)) | |
| { | |
| let mac = CfgSelectResult { | |
| ecx, | |
| selected_tts, | |
| selected_span, | |
| other_branches: branches, | |
| site_span: sp, | |
| cfg_entry, | |
| }; | |
| 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), | |
| }, | |
| ) | |
| } |