Rollup merge of #145233 - joshtriplett:cfg-select-expr, r=jieyouxu

cfg_select: Support unbraced expressions

Tracking issue for `cfg_select`: rust-lang/rust#115585

When operating on expressions, `cfg_select!` can now handle expressions
without braces. (It still requires braces for other things, such as
items.)

Expand the test coverage and documentation accordingly.

---

I'm not sure whether deciding to extend `cfg_select!` in this way is T-lang or T-libs-api. I've labeled for both, with the request that both teams don't block on each other. :)
This commit is contained in:
Jakub Beránek
2025-08-14 21:48:42 +02:00
committed by GitHub
4 changed files with 60 additions and 25 deletions

View File

@@ -1,11 +1,12 @@
use rustc_ast::token::Token; use rustc_ast::token::Token;
use rustc_ast::tokenstream::{TokenStream, TokenTree}; use rustc_ast::tokenstream::{TokenStream, TokenTree};
use rustc_ast::util::classify;
use rustc_ast::{MetaItemInner, token}; use rustc_ast::{MetaItemInner, token};
use rustc_errors::PResult; use rustc_errors::PResult;
use rustc_span::Span; use rustc_span::Span;
use crate::exp; use crate::exp;
use crate::parser::Parser; use crate::parser::{AttrWrapper, ForceCollect, Parser, Restrictions, Trailing, UsePreAttrPos};
pub enum CfgSelectPredicate { pub enum CfgSelectPredicate {
Cfg(MetaItemInner), Cfg(MetaItemInner),
@@ -23,20 +24,27 @@ pub struct CfgSelectBranches {
pub unreachable: Vec<(CfgSelectPredicate, TokenStream, Span)>, pub unreachable: Vec<(CfgSelectPredicate, TokenStream, Span)>,
} }
/// Parses a `TokenTree` that must be of the form `{ /* ... */ }`, and returns a `TokenStream` where /// Parses a `TokenTree` consisting either of `{ /* ... */ }` (and strip the braces) or an
/// the surrounding braces are stripped. /// expression followed by a comma (and strip the comma).
fn parse_token_tree<'a>(p: &mut Parser<'a>) -> PResult<'a, TokenStream> { fn parse_token_tree<'a>(p: &mut Parser<'a>) -> PResult<'a, TokenStream> {
// Generate an error if the `=>` is not followed by `{`. if p.token == token::OpenBrace {
if p.token != token::OpenBrace {
p.expect(exp!(OpenBrace))?;
}
// Strip the outer '{' and '}'. // Strip the outer '{' and '}'.
match p.parse_token_tree() { match p.parse_token_tree() {
TokenTree::Token(..) => unreachable!("because of the expect above"), TokenTree::Token(..) => unreachable!("because of the expect above"),
TokenTree::Delimited(.., tts) => Ok(tts), TokenTree::Delimited(.., tts) => return Ok(tts),
} }
} }
let expr = p.collect_tokens(None, AttrWrapper::empty(), ForceCollect::Yes, |p, _| {
p.parse_expr_res(Restrictions::STMT_EXPR, AttrWrapper::empty())
.map(|(expr, _)| (expr, Trailing::No, UsePreAttrPos::No))
})?;
if !classify::expr_is_complete(&expr) && p.token != token::CloseBrace && p.token != token::Eof {
p.expect(exp!(Comma))?;
} else {
let _ = p.eat(exp!(Comma));
}
Ok(TokenStream::from_ast(&expr))
}
pub fn parse_cfg_select<'a>(p: &mut Parser<'a>) -> PResult<'a, CfgSelectBranches> { pub fn parse_cfg_select<'a>(p: &mut Parser<'a>) -> PResult<'a, CfgSelectBranches> {
let mut branches = CfgSelectBranches::default(); let mut branches = CfgSelectBranches::default();

View File

@@ -223,13 +223,14 @@ pub macro assert_matches {
/// } /// }
/// ``` /// ```
/// ///
/// The `cfg_select!` macro can also be used in expression position: /// The `cfg_select!` macro can also be used in expression position, with or without braces on the
/// right-hand side:
/// ///
/// ``` /// ```
/// #![feature(cfg_select)] /// #![feature(cfg_select)]
/// ///
/// let _some_string = cfg_select! { /// let _some_string = cfg_select! {
/// unix => { "With great power comes great electricity bills" } /// unix => "With great power comes great electricity bills",
/// _ => { "Behind every successful diet is an unwatched pizza" } /// _ => { "Behind every successful diet is an unwatched pizza" }
/// }; /// };
/// ``` /// ```

View File

@@ -8,10 +8,42 @@ fn print() {
}); });
} }
fn arm_rhs_must_be_in_braces() -> i32 { fn print_2() {
println!(cfg_select! {
unix => "unix",
_ => "not unix",
});
}
fn arm_rhs_expr_1() -> i32 {
cfg_select! { cfg_select! {
true => 1 true => 1
//~^ ERROR: expected `{`, found `1` }
}
fn arm_rhs_expr_2() -> i32 {
cfg_select! {
true => 1,
false => 2
}
}
fn arm_rhs_expr_3() -> i32 {
cfg_select! {
true => 1,
false => 2,
true => { 42 }
false => -1 as i32,
true => 2 + 2,
false => "",
true => if true { 42 } else { 84 }
false => if true { 42 } else { 84 },
true => return 42,
false => loop {}
true => (1, 2),
false => (1, 2,),
true => todo!(),
false => println!("hello"),
} }
} }

View File

@@ -1,11 +1,5 @@
error: expected `{`, found `1`
--> $DIR/cfg_select.rs:13:17
|
LL | true => 1
| ^ expected `{`
warning: unreachable predicate warning: unreachable predicate
--> $DIR/cfg_select.rs:20:5 --> $DIR/cfg_select.rs:52:5
| |
LL | _ => {} LL | _ => {}
| - always matches | - always matches
@@ -13,7 +7,7 @@ LL | true => {}
| ^^^^ this predicate is never reached | ^^^^ this predicate is never reached
error: none of the predicates in this `cfg_select` evaluated to true error: none of the predicates in this `cfg_select` evaluated to true
--> $DIR/cfg_select.rs:24:1 --> $DIR/cfg_select.rs:56:1
| |
LL | / cfg_select! { LL | / cfg_select! {
LL | | LL | |
@@ -22,10 +16,10 @@ LL | | }
| |_^ | |_^
error: none of the predicates in this `cfg_select` evaluated to true error: none of the predicates in this `cfg_select` evaluated to true
--> $DIR/cfg_select.rs:29:1 --> $DIR/cfg_select.rs:61:1
| |
LL | cfg_select! {} LL | cfg_select! {}
| ^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^
error: aborting due to 3 previous errors; 1 warning emitted error: aborting due to 2 previous errors; 1 warning emitted