Skip to content

Commit 1dc0034

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

File tree

14 files changed

+266
-50
lines changed

14 files changed

+266
-50
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: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
use either::Either;
2+
use rustc_ast::token::Token;
3+
use rustc_ast::tokenstream::{TokenStream, TokenTree};
4+
use rustc_ast::{MetaItemInner, token};
5+
use rustc_attr_parsing as attr;
6+
use rustc_errors::PResult;
7+
use rustc_expand::base::{ExtCtxt, MacroExpanderResult, *};
8+
use rustc_parse::exp;
9+
use rustc_parse::parser::Parser;
10+
use rustc_span::{Ident, Span, kw, sym};
11+
12+
use crate::errors::{CfgSelectNoMatches, CfgSelectUnreachable};
13+
14+
#[derive(Default)]
15+
struct SelectBranches {
16+
reachable: Vec<(MetaItemInner, TokenStream, Span)>,
17+
wildcard: Option<(Token, TokenStream, Span)>,
18+
unreachable: Vec<(Either<Token, MetaItemInner>, TokenStream, Span)>,
19+
}
20+
21+
impl SelectBranches {
22+
/// Selects the first arm whose rule evaluates to true.
23+
fn select_arm(self, cx: &ExtCtxt<'_>) -> Option<(TokenStream, Span)> {
24+
for (cfg, tt, arm_span) 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, arm_span));
32+
}
33+
}
34+
35+
self.wildcard.map(|(_, tt, span)| (tt, span))
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 underscore = p.token;
60+
p.bump();
61+
p.expect(exp!(FatArrow))?;
62+
63+
let tts = parse_token_tree(p)?;
64+
let span = underscore.span.to(p.token.span);
65+
66+
match branches.wildcard {
67+
None => branches.wildcard = Some((underscore, tts, span)),
68+
Some(_) => branches.unreachable.push((Either::Left(underscore), tts, span)),
69+
}
70+
} else {
71+
let meta_item = p.parse_meta_item_inner()?;
72+
p.expect(exp!(FatArrow))?;
73+
74+
let tts = parse_token_tree(p)?;
75+
let span = meta_item.span().to(p.token.span);
76+
77+
match branches.wildcard {
78+
None => branches.reachable.push((meta_item, tts, span)),
79+
Some(_) => branches.unreachable.push((Either::Right(meta_item), tts, span)),
80+
}
81+
}
82+
}
83+
84+
Ok(branches)
85+
}
86+
87+
pub(super) fn expand_cfg_select<'cx>(
88+
ecx: &'cx mut ExtCtxt<'_>,
89+
sp: Span,
90+
tts: TokenStream,
91+
) -> MacroExpanderResult<'cx> {
92+
ExpandResult::Ready(match parse_args(&mut ecx.new_parser_from_tts(tts)) {
93+
Ok(branches) => {
94+
if let Some((underscore, _, _)) = branches.wildcard {
95+
// Warn for every unreachable rule.
96+
for (rule, _, _) in &branches.unreachable {
97+
let span = match rule {
98+
Either::Left(underscore) => underscore.span,
99+
Either::Right(rule) => rule.span(),
100+
};
101+
let err = CfgSelectUnreachable { span, wildcard_span: underscore.span };
102+
ecx.dcx().emit_warn(err);
103+
}
104+
}
105+
106+
if let Some((tts, arm_span)) = branches.select_arm(ecx) {
107+
rustc_expand::expand_token_stream(
108+
ecx,
109+
sp,
110+
arm_span,
111+
ecx.current_expansion.lint_node_id,
112+
Ident::with_dummy_span(sym::cfg_select),
113+
tts,
114+
)
115+
} else {
116+
// Emit a compiler error when none of the rules matched.
117+
let guar = ecx.dcx().emit_err(CfgSelectNoMatches { span: sp });
118+
DummyResult::any(sp, guar)
119+
}
120+
}
121+
Err(err) => {
122+
let guar = err.emit();
123+
DummyResult::any(sp, guar)
124+
}
125+
})
126+
}

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: 41 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,46 @@ impl<'matcher> Tracker<'matcher> for NoopTracker {
183183
}
184184
}
185185

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

205242
if cx.trace_macros() {
206243
let msg = format!("expanding `{}! {{ {} }}`", name, pprust::tts_to_string(&arg));
@@ -228,32 +265,9 @@ fn expand_macro<'cx>(
228265
}
229266
};
230267

231-
if cx.trace_macros() {
232-
let msg = format!("to `{}`", pprust::tts_to_string(&tts));
233-
trace_macros_note(&mut cx.expansions, sp, msg);
234-
}
235-
236-
let p = Parser::new(psess, tts, None);
237-
238-
if is_local {
239-
cx.resolver.record_macro_rule_usage(node_id, i);
240-
}
241-
242268
// Let the context choose how to interpret the result.
243269
// Weird, but useful for X-macros.
244-
Box::new(ParserAnyMacro {
245-
parser: p,
246-
247-
// Pass along the original expansion site and the name of the macro
248-
// so we can print a useful error message if the parse of the expanded
249-
// macro leaves unparsed tokens.
250-
site_span: sp,
251-
macro_ident: name,
252-
lint_node_id: cx.current_expansion.lint_node_id,
253-
is_trailing_mac: cx.current_expansion.is_trailing_mac,
254-
arm_span,
255-
is_local,
256-
})
270+
expand_token_stream(cx, sp, arm_span, node_id, name, tts)
257271
}
258272
Err(CanRetry::No(guar)) => {
259273
debug!("Will not retry matching as an error was emitted already");

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]

0 commit comments

Comments
 (0)