Skip to content

Commit f9d6aee

Browse files
committed
cfg_select!: emit parse errors in unused branches
1 parent d5525a7 commit f9d6aee

File tree

3 files changed

+115
-17
lines changed

3 files changed

+115
-17
lines changed

compiler/rustc_attr_parsing/src/attributes/cfg_select.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,32 @@ pub struct CfgSelectBranches {
2828
pub unreachable: Vec<(CfgSelectPredicate, TokenStream, Span)>,
2929
}
3030

31+
impl CfgSelectBranches {
32+
/// Removes the top-most branch for which `predicate` returns `true`,
33+
/// or the wildcard if none of the reachable branches satisfied the predicate.
34+
pub fn pop_first_match<F>(&mut self, predicate: F) -> Option<TokenStream>
35+
where
36+
F: Fn(&CfgEntry) -> bool,
37+
{
38+
for (index, (cfg, _, _)) in self.reachable.iter().enumerate() {
39+
if predicate(cfg) {
40+
return Some(self.reachable.remove(index).1);
41+
}
42+
}
43+
44+
self.wildcard.take().map(|(_, tts, _)| tts)
45+
}
46+
47+
/// Consume this value and iterate over all the `TokenStream`s that it stores.
48+
pub fn into_iter_tts(self) -> impl Iterator<Item = TokenStream> {
49+
let it1 = self.reachable.into_iter().map(|(_, tts, _)| tts);
50+
let it2 = self.wildcard.into_iter().map(|(_, tts, _)| tts);
51+
let it3 = self.unreachable.into_iter().map(|(_, tts, _)| tts);
52+
53+
it1.chain(it2).chain(it3)
54+
}
55+
}
56+
3157
pub fn parse_cfg_select(
3258
p: &mut Parser<'_>,
3359
sess: &Session,

compiler/rustc_builtin_macros/src/cfg_select.rs

Lines changed: 73 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,81 @@
11
use rustc_ast::tokenstream::TokenStream;
2+
use rustc_ast::{Expr, ast, token};
3+
use rustc_ast_pretty::pprust;
24
use rustc_attr_parsing as attr;
35
use rustc_attr_parsing::{
46
CfgSelectBranches, CfgSelectPredicate, EvalConfigResult, parse_cfg_select,
57
};
6-
use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacroExpanderResult};
7-
use rustc_span::{Ident, Span, sym};
8+
use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacResult, MacroExpanderResult};
9+
use rustc_parse::parser::ForceCollect;
10+
use rustc_span::Span;
11+
use smallvec::SmallVec;
812

13+
use crate::errors;
914
use crate::errors::{CfgSelectNoMatches, CfgSelectUnreachable};
1015

11-
/// Selects the first arm whose predicate evaluates to true.
12-
fn select_arm(ecx: &ExtCtxt<'_>, branches: CfgSelectBranches) -> Option<(TokenStream, Span)> {
13-
for (cfg, tt, arm_span) in branches.reachable {
14-
if let EvalConfigResult::True = attr::eval_config_entry(&ecx.sess, &cfg) {
15-
return Some((tt, arm_span));
16+
/// This intermediate structure is used to emit parse errors for the branches that are not chosen.
17+
/// The `MacResult` instance below parses all branches, emitting any errors it encounters, but only
18+
/// keeps the parse result for the selected branch.
19+
struct CfgSelectResult<'cx, 'sess> {
20+
ecx: &'cx mut ExtCtxt<'sess>,
21+
selected_tts: TokenStream,
22+
other_branches: CfgSelectBranches,
23+
}
24+
25+
impl<'cx, 'sess> MacResult for CfgSelectResult<'cx, 'sess> {
26+
fn make_expr(self: Box<Self>) -> Option<Box<Expr>> {
27+
for tts in self.other_branches.into_iter_tts() {
28+
let mut p = self.ecx.new_parser_from_tts(tts);
29+
if let Err(diag) = p.parse_expr() {
30+
diag.emit();
31+
return None;
32+
}
33+
}
34+
35+
let mut p = self.ecx.new_parser_from_tts(self.selected_tts);
36+
p.parse_expr().ok()
37+
}
38+
39+
fn make_items(self: Box<Self>) -> Option<SmallVec<[Box<ast::Item>; 1]>> {
40+
for tts in self.other_branches.into_iter_tts() {
41+
let _ = make_items(self.ecx, tts, false);
1642
}
43+
44+
make_items(self.ecx, self.selected_tts, true)
1745
}
46+
}
1847

19-
branches.wildcard.map(|(_, tt, span)| (tt, span))
48+
fn make_items<'cx>(
49+
ecx: &'cx mut ExtCtxt<'_>,
50+
tts: TokenStream,
51+
keep: bool,
52+
) -> Option<SmallVec<[Box<ast::Item>; 1]>> {
53+
let mut p = ecx.new_parser_from_tts(tts);
54+
let mut ret = SmallVec::new();
55+
loop {
56+
match p.parse_item(ForceCollect::No) {
57+
Err(err) => {
58+
err.emit();
59+
break;
60+
}
61+
Ok(Some(item)) => {
62+
if keep {
63+
ret.push(item)
64+
}
65+
}
66+
Ok(None) => {
67+
if p.token != token::Eof {
68+
p.dcx().emit_err(errors::ExpectedItem {
69+
span: p.token.span,
70+
token: &pprust::token_to_string(&p.token),
71+
});
72+
}
73+
74+
break;
75+
}
76+
}
77+
}
78+
Some(ret)
2079
}
2180

2281
pub(super) fn expand_cfg_select<'cx>(
@@ -31,7 +90,7 @@ pub(super) fn expand_cfg_select<'cx>(
3190
Some(ecx.ecfg.features),
3291
ecx.current_expansion.lint_node_id,
3392
) {
34-
Ok(branches) => {
93+
Ok(mut branches) => {
3594
if let Some((underscore, _, _)) = branches.wildcard {
3695
// Warn for every unreachable predicate. We store the fully parsed branch for rustfmt.
3796
for (predicate, _, _) in &branches.unreachable {
@@ -44,14 +103,11 @@ pub(super) fn expand_cfg_select<'cx>(
44103
}
45104
}
46105

47-
if let Some((tts, arm_span)) = select_arm(ecx, branches) {
48-
return ExpandResult::from_tts(
49-
ecx,
50-
tts,
51-
sp,
52-
arm_span,
53-
Ident::with_dummy_span(sym::cfg_select),
54-
);
106+
if let Some(selected_tts) = branches.pop_first_match(|cfg| {
107+
matches!(attr::eval_config_entry(&ecx.sess, cfg), EvalConfigResult::True)
108+
}) {
109+
let mac = CfgSelectResult { ecx, selected_tts, other_branches: branches };
110+
return ExpandResult::Ready(Box::new(mac));
55111
} else {
56112
// Emit a compiler error when none of the predicates matched.
57113
let guar = ecx.dcx().emit_err(CfgSelectNoMatches { span: sp });
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#![feature(cfg_select)]
2+
#![crate_type = "lib"]
3+
4+
// Check that parse errors in arms that are not selected are still reported.
5+
6+
fn print() {
7+
println!(cfg_select! {
8+
false => { 1 ++ 2 }
9+
_ => { "not unix" }
10+
});
11+
}
12+
13+
cfg_select! {
14+
false => { fn foo() { 1 +++ 2 } }
15+
_ => {}
16+
}

0 commit comments

Comments
 (0)