Skip to content

Commit 12d05e7

Browse files
committed
cfg_select!: emit parse errors in unused branches
1 parent 68f11a1 commit 12d05e7

File tree

6 files changed

+217
-28
lines changed

6 files changed

+217
-28
lines changed

compiler/rustc_attr_parsing/src/attributes/cfg_select.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,33 @@ 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, Span)>
35+
where
36+
F: Fn(&CfgEntry) -> bool,
37+
{
38+
for (index, (cfg, _, _)) in self.reachable.iter().enumerate() {
39+
if predicate(cfg) {
40+
let matched = self.reachable.remove(index);
41+
return Some((matched.1, matched.2));
42+
}
43+
}
44+
45+
self.wildcard.take().map(|(_, tts, span)| (tts, span))
46+
}
47+
48+
/// Consume this value and iterate over all the `TokenStream`s that it stores.
49+
pub fn into_iter_tts(self) -> impl Iterator<Item = (TokenStream, Span)> {
50+
let it1 = self.reachable.into_iter().map(|(_, tts, span)| (tts, span));
51+
let it2 = self.wildcard.into_iter().map(|(_, tts, span)| (tts, span));
52+
let it3 = self.unreachable.into_iter().map(|(_, tts, span)| (tts, span));
53+
54+
it1.chain(it2).chain(it3)
55+
}
56+
}
57+
3158
pub fn parse_cfg_select(
3259
p: &mut Parser<'_>,
3360
sess: &Session,

compiler/rustc_builtin_macros/src/cfg_select.rs

Lines changed: 61 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,64 @@
11
use rustc_ast::tokenstream::TokenStream;
2+
use rustc_ast::{Expr, ast};
23
use rustc_attr_parsing as attr;
34
use rustc_attr_parsing::{
45
CfgSelectBranches, CfgSelectPredicate, EvalConfigResult, parse_cfg_select,
56
};
6-
use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacroExpanderResult};
7+
use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacResult, MacroExpanderResult};
78
use rustc_span::{Ident, Span, sym};
9+
use smallvec::SmallVec;
810

911
use crate::errors::{CfgSelectNoMatches, CfgSelectUnreachable};
1012

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-
}
13+
/// This intermediate structure is used to emit parse errors for the branches that are not chosen.
14+
/// The `MacResult` instance below parses all branches, emitting any errors it encounters, but only
15+
/// keeps the parse result for the selected branch.
16+
struct CfgSelectResult<'cx, 'sess> {
17+
ecx: &'cx mut ExtCtxt<'sess>,
18+
site_span: Span,
19+
selected_tts: TokenStream,
20+
selected_span: Span,
21+
other_branches: CfgSelectBranches,
22+
}
23+
24+
fn tts_to_mac_result<'cx, 'sess>(
25+
ecx: &'cx mut ExtCtxt<'sess>,
26+
site_span: Span,
27+
tts: TokenStream,
28+
span: Span,
29+
) -> Box<dyn MacResult + 'cx> {
30+
match ExpandResult::from_tts(ecx, tts, site_span, span, Ident::with_dummy_span(sym::cfg_select))
31+
{
32+
ExpandResult::Ready(x) => x,
33+
_ => unreachable!("from_tts always returns Ready"),
1734
}
35+
}
36+
37+
macro_rules! forward_to_parser_any_macro {
38+
($method_name:ident, $ret_ty:ty) => {
39+
fn $method_name(self: Box<Self>) -> Option<$ret_ty> {
40+
let CfgSelectResult { ecx, site_span, selected_tts, selected_span, .. } = *self;
41+
42+
for (tts, span) in self.other_branches.into_iter_tts() {
43+
let _ = tts_to_mac_result(ecx, site_span, tts, span).$method_name();
44+
}
45+
46+
tts_to_mac_result(ecx, site_span, selected_tts, selected_span).$method_name()
47+
}
48+
};
49+
}
50+
51+
impl<'cx, 'sess> MacResult for CfgSelectResult<'cx, 'sess> {
52+
forward_to_parser_any_macro!(make_expr, Box<Expr>);
53+
forward_to_parser_any_macro!(make_stmts, SmallVec<[ast::Stmt; 1]>);
54+
forward_to_parser_any_macro!(make_items, SmallVec<[Box<ast::Item>; 1]>);
55+
56+
forward_to_parser_any_macro!(make_impl_items, SmallVec<[Box<ast::AssocItem>; 1]>);
57+
forward_to_parser_any_macro!(make_trait_impl_items, SmallVec<[Box<ast::AssocItem>; 1]>);
58+
forward_to_parser_any_macro!(make_trait_items, SmallVec<[Box<ast::AssocItem>; 1]>);
59+
forward_to_parser_any_macro!(make_foreign_items, SmallVec<[Box<ast::ForeignItem>; 1]>);
1860

19-
branches.wildcard.map(|(_, tt, span)| (tt, span))
61+
forward_to_parser_any_macro!(make_ty, Box<ast::Ty>);
2062
}
2163

2264
pub(super) fn expand_cfg_select<'cx>(
@@ -31,7 +73,7 @@ pub(super) fn expand_cfg_select<'cx>(
3173
Some(ecx.ecfg.features),
3274
ecx.current_expansion.lint_node_id,
3375
) {
34-
Ok(branches) => {
76+
Ok(mut branches) => {
3577
if let Some((underscore, _, _)) = branches.wildcard {
3678
// Warn for every unreachable predicate. We store the fully parsed branch for rustfmt.
3779
for (predicate, _, _) in &branches.unreachable {
@@ -44,14 +86,17 @@ pub(super) fn expand_cfg_select<'cx>(
4486
}
4587
}
4688

47-
if let Some((tts, arm_span)) = select_arm(ecx, branches) {
48-
return ExpandResult::from_tts(
89+
if let Some((selected_tts, selected_span)) = branches.pop_first_match(|cfg| {
90+
matches!(attr::eval_config_entry(&ecx.sess, cfg), EvalConfigResult::True)
91+
}) {
92+
let mac = CfgSelectResult {
4993
ecx,
50-
tts,
51-
sp,
52-
arm_span,
53-
Ident::with_dummy_span(sym::cfg_select),
54-
);
94+
selected_tts,
95+
selected_span,
96+
other_branches: branches,
97+
site_span: sp,
98+
};
99+
return ExpandResult::Ready(Box::new(mac));
55100
} else {
56101
// Emit a compiler error when none of the predicates matched.
57102
let guar = ecx.dcx().emit_err(CfgSelectNoMatches { span: sp });

tests/ui/macros/cfg_select.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,79 @@ fn arm_rhs_expr_3() -> i32 {
4747
}
4848
}
4949

50+
fn expand_to_statements() -> i32 {
51+
cfg_select! {
52+
true => {
53+
let a = 1;
54+
a + 1
55+
}
56+
false => {
57+
let b = 2;
58+
b + 1
59+
}
60+
}
61+
}
62+
63+
type ExpandToType = cfg_select! {
64+
unix => u32,
65+
_ => i32,
66+
};
67+
68+
cfg_select! {
69+
true => {
70+
fn foo() {}
71+
}
72+
_ => {
73+
fn bar() {}
74+
}
75+
}
76+
77+
struct S;
78+
79+
impl S {
80+
cfg_select! {
81+
true => {
82+
fn foo() {}
83+
}
84+
_ => {
85+
fn bar() {}
86+
}
87+
}
88+
}
89+
90+
trait T {
91+
cfg_select! {
92+
true => {
93+
fn a();
94+
}
95+
_ => {
96+
fn b();
97+
}
98+
}
99+
}
100+
101+
impl T for S {
102+
cfg_select! {
103+
true => {
104+
fn a() {}
105+
}
106+
_ => {
107+
fn b() {}
108+
}
109+
}
110+
}
111+
112+
extern "C" {
113+
cfg_select! {
114+
true => {
115+
fn puts(s: *const i8) -> i32;
116+
}
117+
_ => {
118+
fn printf(fmt: *const i8, ...) -> i32;
119+
}
120+
}
121+
}
122+
50123
cfg_select! {
51124
_ => {}
52125
true => {}

tests/ui/macros/cfg_select.stderr

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
warning: unreachable predicate
2-
--> $DIR/cfg_select.rs:52:5
2+
--> $DIR/cfg_select.rs:125:5
33
|
44
LL | _ => {}
55
| - always matches
66
LL | true => {}
77
| ^^^^ this predicate is never reached
88

99
error: none of the predicates in this `cfg_select` evaluated to true
10-
--> $DIR/cfg_select.rs:56:1
10+
--> $DIR/cfg_select.rs:129:1
1111
|
1212
LL | / cfg_select! {
1313
LL | |
@@ -16,55 +16,55 @@ LL | | }
1616
| |_^
1717

1818
error: none of the predicates in this `cfg_select` evaluated to true
19-
--> $DIR/cfg_select.rs:61:1
19+
--> $DIR/cfg_select.rs:134:1
2020
|
2121
LL | cfg_select! {}
2222
| ^^^^^^^^^^^^^^
2323

2424
error: expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found `=>`
25-
--> $DIR/cfg_select.rs:65:5
25+
--> $DIR/cfg_select.rs:138:5
2626
|
2727
LL | => {}
2828
| ^^
2929

3030
error: expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found expression
31-
--> $DIR/cfg_select.rs:70:5
31+
--> $DIR/cfg_select.rs:143:5
3232
|
3333
LL | () => {}
3434
| ^^ expressions are not allowed here
3535

3636
error[E0539]: malformed `cfg_select` macro input
37-
--> $DIR/cfg_select.rs:75:5
37+
--> $DIR/cfg_select.rs:148:5
3838
|
3939
LL | "str" => {}
4040
| ^^^^^ expected a valid identifier here
4141

4242
error[E0539]: malformed `cfg_select` macro input
43-
--> $DIR/cfg_select.rs:80:5
43+
--> $DIR/cfg_select.rs:153:5
4444
|
4545
LL | a::b => {}
4646
| ^^^^ expected a valid identifier here
4747

4848
error[E0537]: invalid predicate `a`
49-
--> $DIR/cfg_select.rs:85:5
49+
--> $DIR/cfg_select.rs:158:5
5050
|
5151
LL | a() => {}
5252
| ^^^
5353

5454
error: expected one of `(`, `::`, `=>`, or `=`, found `+`
55-
--> $DIR/cfg_select.rs:90:7
55+
--> $DIR/cfg_select.rs:163:7
5656
|
5757
LL | a + 1 => {}
5858
| ^ expected one of `(`, `::`, `=>`, or `=`
5959

6060
error: expected one of `(`, `::`, `=>`, or `=`, found `!`
61-
--> $DIR/cfg_select.rs:96:8
61+
--> $DIR/cfg_select.rs:169:8
6262
|
6363
LL | cfg!() => {}
6464
| ^ expected one of `(`, `::`, `=>`, or `=`
6565

6666
warning: unexpected `cfg` condition name: `a`
67-
--> $DIR/cfg_select.rs:90:5
67+
--> $DIR/cfg_select.rs:163:5
6868
|
6969
LL | a + 1 => {}
7070
| ^ help: found config with similar value: `target_feature = "a"`
@@ -75,7 +75,7 @@ LL | a + 1 => {}
7575
= note: `#[warn(unexpected_cfgs)]` on by default
7676

7777
warning: unexpected `cfg` condition name: `cfg`
78-
--> $DIR/cfg_select.rs:96:5
78+
--> $DIR/cfg_select.rs:169:5
7979
|
8080
LL | cfg!() => {}
8181
| ^^^
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
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+
//~^ ERROR Rust has no postfix increment operator
10+
_ => { "not unix" }
11+
});
12+
}
13+
14+
cfg_select! {
15+
false => { fn foo() { 1 +++ 2 } }
16+
//~^ ERROR Rust has no postfix increment operator
17+
_ => {}
18+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
error: Rust has no postfix increment operator
2+
--> $DIR/cfg_select_parse_error.rs:8:22
3+
|
4+
LL | false => { 1 ++ 2 }
5+
| ^^ not a valid postfix operator
6+
|
7+
help: use `+= 1` instead
8+
|
9+
LL - false => { 1 ++ 2 }
10+
LL + false => { { let tmp = 1 ; 1 += 1; tmp } 2 }
11+
|
12+
13+
error: Rust has no postfix increment operator
14+
--> $DIR/cfg_select_parse_error.rs:15:29
15+
|
16+
LL | false => { fn foo() { 1 +++ 2 } }
17+
| ^^ not a valid postfix operator
18+
|
19+
help: use `+= 1` instead
20+
|
21+
LL - false => { fn foo() { 1 +++ 2 } }
22+
LL + false => { fn foo() { { let tmp = 1 ; 1 += 1; tmp }+ 2 } }
23+
|
24+
25+
error: aborting due to 2 previous errors
26+

0 commit comments

Comments
 (0)