-
-
Notifications
You must be signed in to change notification settings - Fork 14.2k
cfg_select!: parse unused branches
#149925
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,22 +1,62 @@ | ||
| 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, MacroExpanderResult}; | ||
| use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacResult, MacroExpanderResult}; | ||
| use rustc_span::{Ident, Span, sym}; | ||
| use smallvec::SmallVec; | ||
|
|
||
| use crate::errors::{CfgSelectNoMatches, CfgSelectUnreachable}; | ||
|
|
||
| /// Selects the first arm whose predicate evaluates to true. | ||
| fn select_arm(ecx: &ExtCtxt<'_>, branches: CfgSelectBranches) -> Option<(TokenStream, Span)> { | ||
| for (cfg, tt, arm_span) in branches.reachable { | ||
| if let EvalConfigResult::True = attr::eval_config_entry(&ecx.sess, &cfg) { | ||
| return Some((tt, arm_span)); | ||
| } | ||
| /// 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]>); | ||
|
|
||
| branches.wildcard.map(|(_, tt, span)| (tt, span)) | ||
| 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]>); | ||
| } | ||
|
Comment on lines
+51
to
60
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've started a discussion at #t-lang > where can `cfg_select!` go to decide what we want to support. expr/stmt/item is the bare minimum I think, but we already had some use of (i believe) impl items in the stdlib test suite, so including the other item flavors seems natural. We can see about e.g. patterns or where-clauses etc. |
||
|
|
||
| pub(super) fn expand_cfg_select<'cx>( | ||
|
|
@@ -31,7 +71,7 @@ pub(super) fn expand_cfg_select<'cx>( | |
| Some(ecx.ecfg.features), | ||
| ecx.current_expansion.lint_node_id, | ||
| ) { | ||
| Ok(branches) => { | ||
| 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 { | ||
|
|
@@ -44,14 +84,17 @@ pub(super) fn expand_cfg_select<'cx>( | |
| } | ||
| } | ||
|
|
||
| if let Some((tts, arm_span)) = select_arm(ecx, branches) { | ||
| return ExpandResult::from_tts( | ||
| 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, | ||
| tts, | ||
| sp, | ||
| arm_span, | ||
| Ident::with_dummy_span(sym::cfg_select), | ||
| ); | ||
| 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 }); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| #![feature(cfg_select)] | ||
| #![crate_type = "lib"] | ||
|
|
||
| // Check that parse errors in arms that are not selected are still reported. | ||
|
|
||
| fn print() { | ||
| println!(cfg_select! { | ||
| false => { 1 ++ 2 } | ||
| //~^ ERROR Rust has no postfix increment operator | ||
| _ => { "not unix" } | ||
| }); | ||
| } | ||
|
|
||
| cfg_select! { | ||
| false => { fn foo() { 1 +++ 2 } } | ||
| //~^ ERROR Rust has no postfix increment operator | ||
| _ => {} | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| error: Rust has no postfix increment operator | ||
| --> $DIR/cfg_select_parse_error.rs:8:22 | ||
| | | ||
| LL | false => { 1 ++ 2 } | ||
| | ^^ not a valid postfix operator | ||
| | | ||
| help: use `+= 1` instead | ||
| | | ||
| LL - false => { 1 ++ 2 } | ||
| LL + false => { { let tmp = 1 ; 1 += 1; tmp } 2 } | ||
| | | ||
|
|
||
| error: Rust has no postfix increment operator | ||
| --> $DIR/cfg_select_parse_error.rs:15:29 | ||
| | | ||
| LL | false => { fn foo() { 1 +++ 2 } } | ||
| | ^^ not a valid postfix operator | ||
| | | ||
| help: use `+= 1` instead | ||
| | | ||
| LL - false => { fn foo() { 1 +++ 2 } } | ||
| LL + false => { fn foo() { { let tmp = 1 ; 1 += 1; tmp }+ 2 } } | ||
| | | ||
|
|
||
| error: aborting due to 2 previous errors | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
might be nicer to write as an iterator? and since it consumes maybe it can take self by value. Then it'd become something like
self.reachable.into_iter().find(predicate).unwrap_or(self.wildcard)with maybe something added to just get the tts and span heheThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That won't work, at least with the current setup. This function isn't just about finding the match, but also specifically about removing it from the
CfgSelectBranchesso that it does not get parsed twice (which would duplicate errors).