-
-
Notifications
You must be signed in to change notification settings - Fork 14.5k
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
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,65 @@ | ||
| 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"), | ||
| } | ||
| } | ||
|
Comment on lines
24
to
35
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. This works around
Contributor
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'm trying to understand how much prettier that'd become. I think it'd be fine to export it from rustc_expand, but I wonder if it changes much. Would you not need the macro calls below anymore? I think you still do right? Because then I don't think this is so bad.
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. You'd still need the macros. The
Contributor
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. okay, that sounds reasonable! |
||
|
|
||
| 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]>); | ||
|
|
||
| branches.wildcard.map(|(_, tt, span)| (tt, span)) | ||
| forward_to_parser_any_macro!(make_ty, Box<ast::Ty>); | ||
| forward_to_parser_any_macro!(make_pat, Box<ast::Pat>); | ||
| } | ||
|
Comment on lines
51
to
63
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 +74,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 +87,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 | ||
|
|
Uh oh!
There was an error while loading. Please reload this page.