Skip to content

Commit 3689b80

Browse files
committed
make cfg_select a builtin macro
1 parent 1b0bc59 commit 3689b80

File tree

14 files changed

+299
-52
lines changed

14 files changed

+299
-52
lines changed

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: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
use rustc_ast::tokenstream::TokenStream;
2+
use rustc_attr_parsing as attr;
3+
use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacroExpanderResult};
4+
use rustc_parse::parser::cfg_select::{CfgSelectBranches, CfgSelectRule, parse_cfg_select};
5+
use rustc_span::{Ident, Span, sym};
6+
7+
use crate::errors::{CfgSelectNoMatches, CfgSelectUnreachable};
8+
9+
/// Selects the first arm whose rule evaluates to true.
10+
fn select_arm(ecx: &ExtCtxt<'_>, branches: CfgSelectBranches) -> Option<(TokenStream, Span)> {
11+
for (cfg, tt, arm_span) in branches.reachable {
12+
if attr::cfg_matches(
13+
&cfg,
14+
&ecx.sess,
15+
ecx.current_expansion.lint_node_id,
16+
Some(ecx.ecfg.features),
17+
) {
18+
return Some((tt, arm_span));
19+
}
20+
}
21+
22+
branches.wildcard.map(|(_, tt, span)| (tt, span))
23+
}
24+
25+
pub(super) fn expand_cfg_select<'cx>(
26+
ecx: &'cx mut ExtCtxt<'_>,
27+
sp: Span,
28+
tts: TokenStream,
29+
) -> MacroExpanderResult<'cx> {
30+
ExpandResult::Ready(match parse_cfg_select(&mut ecx.new_parser_from_tts(tts)) {
31+
Ok(branches) => {
32+
if let Some((underscore, _, _)) = branches.wildcard {
33+
// Warn for every unreachable rule. We store the fully parsed branch for rustfmt.
34+
for (rule, _, _) in &branches.unreachable {
35+
let span = match rule {
36+
CfgSelectRule::Wildcard(underscore) => underscore.span,
37+
CfgSelectRule::Cfg(cfg) => cfg.span(),
38+
};
39+
let err = CfgSelectUnreachable { span, wildcard_span: underscore.span };
40+
ecx.dcx().emit_warn(err);
41+
}
42+
}
43+
44+
if let Some((tts, arm_span)) = select_arm(ecx, branches) {
45+
return ExpandResult::from_tts(
46+
ecx,
47+
tts,
48+
sp,
49+
arm_span,
50+
Ident::with_dummy_span(sym::cfg_select),
51+
);
52+
} else {
53+
// Emit a compiler error when none of the rules matched.
54+
let guar = ecx.dcx().emit_err(CfgSelectNoMatches { span: sp });
55+
DummyResult::any(sp, guar)
56+
}
57+
}
58+
Err(err) => {
59+
let guar = err.emit();
60+
DummyResult::any(sp, guar)
61+
}
62+
})
63+
}

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/base.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ use thin_vec::ThinVec;
3434
use crate::base::ast::MetaItemInner;
3535
use crate::errors;
3636
use crate::expand::{self, AstFragment, Invocation};
37+
use crate::mbe::macro_rules::ParserAnyMacro;
3738
use crate::module::DirOwnership;
3839
use crate::stats::MacroStat;
3940

@@ -262,6 +263,25 @@ impl<T, U> ExpandResult<T, U> {
262263
}
263264
}
264265

266+
impl<'cx> MacroExpanderResult<'cx> {
267+
/// Creates a [`MacroExpanderResult::Ready`] from a [`TokenStream`].
268+
///
269+
/// The `TokenStream` is forwarded without any expansion.
270+
pub fn from_tts(
271+
cx: &'cx mut ExtCtxt<'_>,
272+
tts: TokenStream,
273+
site_span: Span,
274+
arm_span: Span,
275+
macro_ident: Ident,
276+
) -> Self {
277+
// Emit the SEMICOLON_IN_EXPRESSIONS_FROM_MACROS deprecation lint.
278+
let is_local = true;
279+
280+
let parser = ParserAnyMacro::from_tts(cx, tts, site_span, arm_span, is_local, macro_ident);
281+
ExpandResult::Ready(Box::new(parser))
282+
}
283+
}
284+
265285
pub trait MultiItemModifier {
266286
/// `meta_item` is the attribute, and `item` is the item being modified.
267287
fn expand(

compiler/rustc_expand/src/mbe/macro_rules.rs

Lines changed: 38 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,30 @@ impl<'a> ParserAnyMacro<'a> {
9696
ensure_complete_parse(parser, &path, kind.name(), site_span);
9797
fragment
9898
}
99+
100+
#[instrument(skip(cx, tts))]
101+
pub(crate) fn from_tts<'cx>(
102+
cx: &'cx mut ExtCtxt<'a>,
103+
tts: TokenStream,
104+
site_span: Span,
105+
arm_span: Span,
106+
is_local: bool,
107+
macro_ident: Ident,
108+
) -> Self {
109+
Self {
110+
parser: Parser::new(&cx.sess.psess, tts, None),
111+
112+
// Pass along the original expansion site and the name of the macro
113+
// so we can print a useful error message if the parse of the expanded
114+
// macro leaves unparsed tokens.
115+
site_span,
116+
macro_ident,
117+
lint_node_id: cx.current_expansion.lint_node_id,
118+
is_trailing_mac: cx.current_expansion.is_trailing_mac,
119+
arm_span,
120+
is_local,
121+
}
122+
}
99123
}
100124

101125
pub(super) struct MacroRule {
@@ -207,9 +231,6 @@ fn expand_macro<'cx>(
207231
rules: &[MacroRule],
208232
) -> Box<dyn MacResult + 'cx> {
209233
let psess = &cx.sess.psess;
210-
// Macros defined in the current crate have a real node id,
211-
// whereas macros from an external crate have a dummy id.
212-
let is_local = node_id != DUMMY_NODE_ID;
213234

214235
if cx.trace_macros() {
215236
let msg = format!("expanding `{}! {{ {} }}`", name, pprust::tts_to_string(&arg));
@@ -220,7 +241,7 @@ fn expand_macro<'cx>(
220241
let try_success_result = try_match_macro(psess, name, &arg, rules, &mut NoopTracker);
221242

222243
match try_success_result {
223-
Ok((i, rule, named_matches)) => {
244+
Ok((rule_index, rule, named_matches)) => {
224245
let mbe::TokenTree::Delimited(rhs_span, _, ref rhs) = rule.rhs else {
225246
cx.dcx().span_bug(sp, "malformed macro rhs");
226247
};
@@ -241,27 +262,13 @@ fn expand_macro<'cx>(
241262
trace_macros_note(&mut cx.expansions, sp, msg);
242263
}
243264

244-
let p = Parser::new(psess, tts, None);
245-
265+
let is_local = is_defined_in_current_crate(node_id);
246266
if is_local {
247-
cx.resolver.record_macro_rule_usage(node_id, i);
267+
cx.resolver.record_macro_rule_usage(node_id, rule_index);
248268
}
249269

250-
// Let the context choose how to interpret the result.
251-
// Weird, but useful for X-macros.
252-
Box::new(ParserAnyMacro {
253-
parser: p,
254-
255-
// Pass along the original expansion site and the name of the macro
256-
// so we can print a useful error message if the parse of the expanded
257-
// macro leaves unparsed tokens.
258-
site_span: sp,
259-
macro_ident: name,
260-
lint_node_id: cx.current_expansion.lint_node_id,
261-
is_trailing_mac: cx.current_expansion.is_trailing_mac,
262-
arm_span,
263-
is_local,
264-
})
270+
// Let the context choose how to interpret the result. Weird, but useful for X-macros.
271+
Box::new(ParserAnyMacro::from_tts(cx, tts, sp, arm_span, is_local, name))
265272
}
266273
Err(CanRetry::No(guar)) => {
267274
debug!("Will not retry matching as an error was emitted already");
@@ -373,9 +380,9 @@ pub fn compile_declarative_macro(
373380
node_id: NodeId,
374381
edition: Edition,
375382
) -> (SyntaxExtension, usize) {
376-
let is_local = node_id != DUMMY_NODE_ID;
377383
let mk_syn_ext = |expander| {
378384
let kind = SyntaxExtensionKind::LegacyBang(expander);
385+
let is_local = is_defined_in_current_crate(node_id);
379386
SyntaxExtension::new(sess, kind, span, Vec::new(), edition, ident.name, attrs, is_local)
380387
};
381388
let dummy_syn_ext = |guar| (mk_syn_ext(Arc::new(DummyExpander(guar))), 0);
@@ -439,7 +446,7 @@ pub fn compile_declarative_macro(
439446
}
440447

441448
// Return the number of rules for unused rule linting, if this is a local macro.
442-
let nrules = if is_local { rules.len() } else { 0 };
449+
let nrules = if is_defined_in_current_crate(node_id) { rules.len() } else { 0 };
443450

444451
let expander =
445452
Arc::new(MacroRulesMacroExpander { name: ident, span, node_id, transparency, rules });
@@ -1034,9 +1041,7 @@ fn check_matcher_core<'tt>(
10341041
// definition of this macro_rules, not while (re)parsing
10351042
// the macro when compiling another crate that is using the
10361043
// macro. (See #86567.)
1037-
// Macros defined in the current crate have a real node id,
1038-
// whereas macros from an external crate have a dummy id.
1039-
if node_id != DUMMY_NODE_ID
1044+
if is_defined_in_current_crate(node_id)
10401045
&& matches!(kind, NonterminalKind::Pat(PatParam { inferred: true }))
10411046
&& matches!(
10421047
next_token,
@@ -1296,6 +1301,12 @@ fn quoted_tt_to_string(tt: &mbe::TokenTree) -> String {
12961301
}
12971302
}
12981303

1304+
fn is_defined_in_current_crate(node_id: NodeId) -> bool {
1305+
// Macros defined in the current crate have a real node id,
1306+
// whereas macros from an external crate have a dummy id.
1307+
node_id != DUMMY_NODE_ID
1308+
}
1309+
12991310
pub(super) fn parser_from_cx(
13001311
psess: &ParseSess,
13011312
mut tts: TokenStream,
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
use rustc_ast::token::Token;
2+
use rustc_ast::tokenstream::{TokenStream, TokenTree};
3+
use rustc_ast::{MetaItemInner, token};
4+
use rustc_errors::PResult;
5+
use rustc_span::Span;
6+
7+
use crate::exp;
8+
use crate::parser::Parser;
9+
10+
pub enum CfgSelectRule {
11+
Cfg(MetaItemInner),
12+
Wildcard(Token),
13+
}
14+
15+
#[derive(Default)]
16+
pub struct CfgSelectBranches {
17+
/// All the conditional branches.
18+
pub reachable: Vec<(MetaItemInner, TokenStream, Span)>,
19+
/// The first wildcard `_ => { ... }` branch.
20+
pub wildcard: Option<(Token, TokenStream, Span)>,
21+
/// All branches after the first wildcard, including further wildcards.
22+
/// These branches are kept for formatting.
23+
pub unreachable: Vec<(CfgSelectRule, TokenStream, Span)>,
24+
}
25+
26+
/// Parses a `TokenTree` that must be of the form `{ /* ... */ }`, and returns a `TokenStream` where
27+
/// the surrounding braces are stripped.
28+
fn parse_token_tree<'a>(p: &mut Parser<'a>) -> PResult<'a, TokenStream> {
29+
// Generate an error if the `=>` is not followed by `{`.
30+
if p.token != token::OpenBrace {
31+
p.expect(exp!(OpenBrace))?;
32+
}
33+
34+
// Strip the outer '{' and '}'.
35+
match p.parse_token_tree() {
36+
TokenTree::Token(..) => unreachable!("because of the expect above"),
37+
TokenTree::Delimited(.., tts) => Ok(tts),
38+
}
39+
}
40+
41+
pub fn parse_cfg_select<'a>(p: &mut Parser<'a>) -> PResult<'a, CfgSelectBranches> {
42+
let mut branches = CfgSelectBranches::default();
43+
44+
while p.token != token::Eof {
45+
if p.eat_keyword(exp!(Underscore)) {
46+
let underscore = p.prev_token;
47+
p.expect(exp!(FatArrow))?;
48+
49+
let tts = parse_token_tree(p)?;
50+
let span = underscore.span.to(p.token.span);
51+
52+
match branches.wildcard {
53+
None => branches.wildcard = Some((underscore, tts, span)),
54+
Some(_) => {
55+
branches.unreachable.push((CfgSelectRule::Wildcard(underscore), tts, span))
56+
}
57+
}
58+
} else {
59+
let meta_item = p.parse_meta_item_inner()?;
60+
p.expect(exp!(FatArrow))?;
61+
62+
let tts = parse_token_tree(p)?;
63+
let span = meta_item.span().to(p.token.span);
64+
65+
match branches.wildcard {
66+
None => branches.reachable.push((meta_item, tts, span)),
67+
Some(_) => branches.unreachable.push((CfgSelectRule::Cfg(meta_item), tts, span)),
68+
}
69+
}
70+
}
71+
72+
Ok(branches)
73+
}

compiler/rustc_parse/src/parser/mod.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
pub mod asm;
21
pub mod attr;
32
mod attr_wrapper;
43
mod diagnostics;
@@ -12,6 +11,11 @@ mod stmt;
1211
pub mod token_type;
1312
mod ty;
1413

14+
// Parsers for non-functionlike builtin macros are defined in rustc_parse so they can be used by
15+
// both rustc_builtin_macros and rustfmt.
16+
pub mod asm;
17+
pub mod cfg_select;
18+
1519
use std::assert_matches::debug_assert_matches;
1620
use std::{fmt, mem, slice};
1721

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,

0 commit comments

Comments
 (0)