Skip to content

Commit e3aeb08

Browse files
committed
make cfg_select a builtin macro
1 parent 6268d0a commit e3aeb08

File tree

14 files changed

+248
-23
lines changed

14 files changed

+248
-23
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3481,6 +3481,7 @@ dependencies = [
34813481
name = "rustc_builtin_macros"
34823482
version = "0.0.0"
34833483
dependencies = [
3484+
"either",
34843485
"rustc_ast",
34853486
"rustc_ast_pretty",
34863487
"rustc_attr_data_structures",

compiler/rustc_builtin_macros/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ doctest = false
88

99
[dependencies]
1010
# tidy-alphabetical-start
11+
either = "1.5.0"
1112
rustc_ast = { path = "../rustc_ast" }
1213
rustc_ast_pretty = { path = "../rustc_ast_pretty" }
1314
rustc_attr_data_structures = { path = "../rustc_attr_data_structures" }

compiler/rustc_builtin_macros/messages.ftl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,12 @@ builtin_macros_cfg_accessible_literal_path = `cfg_accessible` path cannot be a l
8181
builtin_macros_cfg_accessible_multiple_paths = multiple `cfg_accessible` paths are specified
8282
builtin_macros_cfg_accessible_unspecified_path = `cfg_accessible` path is not specified
8383
84+
builtin_macros_cfg_select_no_matches = none of the rules in this `cfg_select` evaluated to true
85+
86+
builtin_macros_cfg_select_unreachable = unreachable rule
87+
.label = always matches
88+
.label2 = this rules is never reached
89+
8490
builtin_macros_coerce_pointee_requires_maybe_sized = `derive(CoercePointee)` requires `{$name}` to be marked `?Sized`
8591
8692
builtin_macros_coerce_pointee_requires_one_field = `CoercePointee` can only be derived on `struct`s with at least one field
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
use either::Either;
2+
use rustc_ast::tokenstream::{TokenStream, TokenTree};
3+
use rustc_ast::{MetaItemInner, token};
4+
use rustc_attr_parsing as attr;
5+
use rustc_errors::PResult;
6+
use rustc_expand::base::{ExtCtxt, MacroExpanderResult, *};
7+
use rustc_parse::exp;
8+
use rustc_parse::parser::Parser;
9+
use rustc_span::{Span, kw};
10+
11+
use crate::errors::{CfgSelectNoMatches, CfgSelectUnreachable};
12+
13+
#[derive(Default)]
14+
struct SelectBranches {
15+
reachable: Vec<(MetaItemInner, TokenStream)>,
16+
wildcard: Option<(Span, TokenStream)>,
17+
// Either the span of the `_`, or the rule of the arm.
18+
unreachable: Vec<(Either<Span, MetaItemInner>, TokenStream)>,
19+
}
20+
21+
impl SelectBranches {
22+
/// Selects the first arm whose rule evaluates to true.
23+
fn select_arm(self, cx: &ExtCtxt<'_>) -> Option<TokenStream> {
24+
for (cfg, tt) in self.reachable {
25+
if attr::cfg_matches(
26+
&cfg,
27+
&cx.sess,
28+
cx.current_expansion.lint_node_id,
29+
Some(cx.ecfg.features),
30+
) {
31+
return Some(tt);
32+
}
33+
}
34+
35+
self.wildcard.map(|(_, tt)| tt)
36+
}
37+
}
38+
39+
/// Parses a `TokenTree` that must be of the form `{ /* ... */ }`, and returns a `TokenStream` where
40+
/// the the surrounding braces are stripped.
41+
fn parse_token_tree<'a>(p: &mut Parser<'a>) -> PResult<'a, TokenStream> {
42+
// generate an error if the `=>` is not followed by `{`
43+
if p.token != token::OpenBrace {
44+
p.expect(exp!(OpenBrace))?;
45+
}
46+
47+
// Strip the outer '{' and '}'
48+
match p.parse_token_tree() {
49+
TokenTree::Token(..) => unreachable!("because of the expect above"),
50+
TokenTree::Delimited(.., tts) => Ok(tts),
51+
}
52+
}
53+
54+
fn parse_args<'a>(p: &mut Parser<'a>) -> PResult<'a, SelectBranches> {
55+
let mut branches = SelectBranches::default();
56+
57+
while p.token != token::Eof {
58+
if p.token.is_keyword(kw::Underscore) {
59+
let span = p.token.span;
60+
p.bump();
61+
p.expect(exp!(FatArrow))?;
62+
63+
let tts = parse_token_tree(p)?;
64+
65+
match branches.wildcard {
66+
None => branches.wildcard = Some((span, tts)),
67+
Some(_) => branches.unreachable.push((Either::Left(span), tts)),
68+
}
69+
} else {
70+
let meta_item = p.parse_meta_item_inner()?;
71+
p.expect(exp!(FatArrow))?;
72+
73+
let tts = parse_token_tree(p)?;
74+
75+
match branches.wildcard {
76+
None => branches.reachable.push((meta_item, tts)),
77+
Some(_) => branches.unreachable.push((Either::Right(meta_item), tts)),
78+
}
79+
}
80+
}
81+
82+
Ok(branches)
83+
}
84+
85+
pub(super) fn expand_cfg_select<'cx>(
86+
ecx: &'cx mut ExtCtxt<'_>,
87+
sp: Span,
88+
tts: TokenStream,
89+
) -> MacroExpanderResult<'cx> {
90+
ExpandResult::Ready(match parse_args(&mut ecx.new_parser_from_tts(tts)) {
91+
Ok(branches) => {
92+
if let Some((wildcard_span, _)) = branches.wildcard {
93+
// Warn for every unreachable rule.
94+
for (rule, _) in &branches.unreachable {
95+
let span = match rule {
96+
Either::Left(wildcard_span) => *wildcard_span,
97+
Either::Right(rule) => rule.span(),
98+
};
99+
let err = CfgSelectUnreachable { span, wildcard_span };
100+
ecx.dcx().emit_warn(err);
101+
}
102+
}
103+
104+
if let Some(tts) = branches.select_arm(ecx) {
105+
rustc_expand::expand_token_stream(ecx, sp, tts)
106+
} else {
107+
// Emit a compiler error when none of the rules matched.
108+
let guar = ecx.dcx().emit_err(CfgSelectNoMatches { span: sp });
109+
DummyResult::any(sp, guar)
110+
}
111+
}
112+
Err(err) => {
113+
let guar = err.emit();
114+
DummyResult::any(sp, guar)
115+
}
116+
})
117+
}

compiler/rustc_builtin_macros/src/errors.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -954,3 +954,21 @@ pub(crate) struct AsmExpectedOther {
954954
pub(crate) span: Span,
955955
pub(crate) is_inline_asm: bool,
956956
}
957+
958+
#[derive(Diagnostic)]
959+
#[diag(builtin_macros_cfg_select_no_matches)]
960+
pub(crate) struct CfgSelectNoMatches {
961+
#[primary_span]
962+
pub span: Span,
963+
}
964+
965+
#[derive(Diagnostic)]
966+
#[diag(builtin_macros_cfg_select_unreachable)]
967+
pub(crate) struct CfgSelectUnreachable {
968+
#[primary_span]
969+
#[label(builtin_macros_label2)]
970+
pub span: Span,
971+
972+
#[label]
973+
pub wildcard_span: Span,
974+
}

compiler/rustc_builtin_macros/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ mod autodiff;
3333
mod cfg;
3434
mod cfg_accessible;
3535
mod cfg_eval;
36+
mod cfg_select;
3637
mod compile_error;
3738
mod concat;
3839
mod concat_bytes;
@@ -79,6 +80,7 @@ pub fn register_builtin_macros(resolver: &mut dyn ResolverExpand) {
7980
asm: asm::expand_asm,
8081
assert: assert::expand_assert,
8182
cfg: cfg::expand_cfg,
83+
cfg_select: cfg_select::expand_cfg_select,
8284
column: source_util::expand_column,
8385
compile_error: compile_error::expand_compile_error,
8486
concat: concat::expand_concat,

compiler/rustc_expand/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ mod placeholders;
2222
mod proc_macro_server;
2323
mod stats;
2424

25-
pub use mbe::macro_rules::compile_declarative_macro;
25+
pub use mbe::macro_rules::{compile_declarative_macro, expand_token_stream};
2626
pub mod base;
2727
pub mod config;
2828
pub mod expand;

compiler/rustc_expand/src/mbe/macro_rules.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,45 @@ impl<'matcher> Tracker<'matcher> for NoopTracker {
183183
}
184184
}
185185

186+
pub fn expand_token_stream<'cx>(
187+
cx: &'cx mut ExtCtxt<'_>,
188+
sp: Span,
189+
tts: TokenStream,
190+
) -> Box<dyn MacResult + 'cx> {
191+
let psess = &cx.sess.psess;
192+
// Macros defined in the current crate have a real node id,
193+
// whereas macros from an external crate have a dummy id.
194+
let node_id = cx.current_expansion.lint_node_id;
195+
let is_local = node_id != DUMMY_NODE_ID;
196+
197+
if cx.trace_macros() {
198+
let msg = format!("to `{}`", pprust::tts_to_string(&tts));
199+
trace_macros_note(&mut cx.expansions, sp, msg);
200+
}
201+
202+
let p = Parser::new(psess, tts, None);
203+
204+
if is_local {
205+
cx.resolver.record_macro_rule_usage(node_id, 0);
206+
}
207+
208+
// Let the context choose how to interpret the result.
209+
// Weird, but useful for X-macros.
210+
Box::new(ParserAnyMacro {
211+
parser: p,
212+
213+
// Pass along the original expansion site and the name of the macro
214+
// so we can print a useful error message if the parse of the expanded
215+
// macro leaves unparsed tokens.
216+
site_span: sp,
217+
macro_ident: Ident::with_dummy_span(sym::cfg_select),
218+
lint_node_id: cx.current_expansion.lint_node_id,
219+
is_trailing_mac: cx.current_expansion.is_trailing_mac,
220+
arm_span: Span::default(),
221+
is_local,
222+
})
223+
}
224+
186225
/// Expands the rules based macro defined by `lhses` and `rhses` for a given
187226
/// input `arg`.
188227
#[instrument(skip(cx, transparency, arg, lhses, rhses))]

compiler/rustc_span/src/symbol.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,7 @@ symbols! {
624624
cfg_relocation_model,
625625
cfg_sanitize,
626626
cfg_sanitizer_cfi,
627+
cfg_select,
627628
cfg_target_abi,
628629
cfg_target_compact,
629630
cfg_target_feature,

library/core/src/macros/mod.rs

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -230,32 +230,17 @@ pub macro assert_matches {
230230
/// ```
231231
/// #![feature(cfg_select)]
232232
///
233-
/// let _some_string = cfg_select! {{
233+
/// let _some_string = cfg_select! {
234234
/// unix => { "With great power comes great electricity bills" }
235235
/// _ => { "Behind every successful diet is an unwatched pizza" }
236-
/// }};
236+
/// };
237237
/// ```
238238
#[unstable(feature = "cfg_select", issue = "115585")]
239239
#[rustc_diagnostic_item = "cfg_select"]
240240
#[rustc_macro_transparency = "semitransparent"]
241-
pub macro cfg_select {
242-
({ $($tt:tt)* }) => {{
243-
$crate::cfg_select! { $($tt)* }
244-
}},
245-
(_ => { $($output:tt)* }) => {
246-
$($output)*
247-
},
248-
(
249-
$cfg:meta => $output:tt
250-
$($( $rest:tt )+)?
251-
) => {
252-
#[cfg($cfg)]
253-
$crate::cfg_select! { _ => $output }
254-
$(
255-
#[cfg(not($cfg))]
256-
$crate::cfg_select! { $($rest)+ }
257-
)?
258-
},
241+
#[rustc_builtin_macro]
242+
pub macro cfg_select($($tt:tt)*) {
243+
/* compiler built-in */
259244
}
260245

261246
/// Asserts that a boolean expression is `true` at runtime.

library/coretests/tests/macros.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,12 @@ fn _accepts_expressions() -> i32 {
183183
}
184184
}
185185

186+
fn _accepts_only_wildcard() -> i32 {
187+
cfg_select! {
188+
_ => { 1 }
189+
}
190+
}
191+
186192
// The current implementation expands to a macro call, which allows the use of expression
187193
// statements.
188194
fn _allows_stmt_expr_attributes() {
@@ -195,12 +201,12 @@ fn _allows_stmt_expr_attributes() {
195201
}
196202

197203
fn _expression() {
198-
let _ = cfg_select!({
204+
let _ = cfg_select!(
199205
windows => {
200206
" XP"
201207
}
202208
_ => {
203209
""
204210
}
205-
});
211+
);
206212
}

tests/auxiliary/minicore.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,10 @@ pub macro naked_asm("assembly template", $(operands,)* $(options($(option),*))?)
124124
pub macro global_asm("assembly template", $(operands,)* $(options($(option),*))?) {
125125
/* compiler built-in */
126126
}
127+
#[rustc_builtin_macro]
128+
pub macro cfg_select($($tt:tt)*) {
129+
/* compiler built-in */
130+
}
127131

128132
#[rustc_builtin_macro]
129133
#[macro_export]

tests/ui/macros/cfg_select.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#![feature(cfg_select)]
2+
#![crate_type = "lib"]
3+
4+
cfg_select!{
5+
_ => {}
6+
true => {}
7+
//~^ WARN unreachable rule
8+
}
9+
10+
cfg_select!{
11+
//~^ ERROR none of the rules in this `cfg_select` evaluated to true
12+
false => {}
13+
}
14+
15+
fn foo() -> i32 {
16+
cfg_select!{
17+
true => 1
18+
//~^ ERROR: expected `{`, found `1`
19+
}
20+
}

tests/ui/macros/cfg_select.stderr

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
warning: unreachable rule
2+
--> $DIR/cfg_select.rs:6:5
3+
|
4+
LL | _ => {}
5+
| - always matches
6+
LL | true => {}
7+
| ^^^^ this rules is never reached
8+
9+
error: none of the rules in this `cfg_select` evaluated to true
10+
--> $DIR/cfg_select.rs:10:1
11+
|
12+
LL | / cfg_select!{
13+
LL | |
14+
LL | | false => {}
15+
LL | | }
16+
| |_^
17+
18+
error: expected `{`, found `1`
19+
--> $DIR/cfg_select.rs:17:17
20+
|
21+
LL | true => 1
22+
| ^ expected `{`
23+
24+
error: aborting due to 2 previous errors; 1 warning emitted
25+

0 commit comments

Comments
 (0)