Skip to content

Commit a16722d

Browse files
committed
Handle attempts to have multiple cfgd tail expressions
When encountering code that seems like it might be trying to have multiple tail expressions depending on `cfg` information, suggest alternatives that will success to parse. ```rust fn foo() -> String { #[cfg(feature = "validation")] [1, 2, 3].iter().map(|c| c.to_string()).collect::<String>() #[cfg(not(feature = "validation"))] String::new() } ``` ``` error: expected `;`, found `#` --> $DIR/multiple-tail-expr-behind-cfg.rs:5:64 | LL | #[cfg(feature = "validation")] | ------------------------------ only `;` terminated statements or tail expressions are allowed after this attribute LL | [1, 2, 3].iter().map(|c| c.to_string()).collect::<String>() | ^ expected `;` here LL | #[cfg(not(feature = "validation"))] | - unexpected token | help: add `;` here | LL | [1, 2, 3].iter().map(|c| c.to_string()).collect::<String>(); | + help: alternatively, consider surrounding the expression with a block | LL | { [1, 2, 3].iter().map(|c| c.to_string()).collect::<String>() } | + + help: it seems like you are trying to provide different expressions depending on `cfg`, consider using `if cfg!(..)` | LL ~ if cfg!(feature = "validation") { LL ~ [1, 2, 3].iter().map(|c| c.to_string()).collect::<String>() LL ~ } else if cfg!(not(feature = "validation")) { LL ~ String::new() LL + } | ``` Fix #106020.
1 parent 1be1e84 commit a16722d

File tree

4 files changed

+183
-0
lines changed

4 files changed

+183
-0
lines changed

compiler/rustc_parse/src/parser/diagnostics.rs

+96
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use crate::errors::{
2121

2222
use crate::fluent_generated as fluent;
2323
use crate::parser;
24+
use crate::parser::attr::InnerAttrPolicy;
2425
use rustc_ast as ast;
2526
use rustc_ast::ptr::P;
2627
use rustc_ast::token::{self, Delimiter, Lit, LitKind, TokenKind};
@@ -722,6 +723,101 @@ impl<'a> Parser<'a> {
722723
Err(err)
723724
}
724725

726+
pub(super) fn attr_on_non_tail_expr(&self, expr: &Expr) {
727+
// Missing semicolon typo error.
728+
let span = self.prev_token.span.shrink_to_hi();
729+
let mut err = self.sess.create_err(ExpectedSemi {
730+
span,
731+
token: self.token.clone(),
732+
unexpected_token_label: Some(self.token.span),
733+
sugg: ExpectedSemiSugg::AddSemi(span),
734+
});
735+
let attr_span = match &expr.attrs[..] {
736+
[] => unreachable!(),
737+
[only] => only.span,
738+
[first, rest @ ..] => {
739+
for attr in rest {
740+
err.span_label(attr.span, "");
741+
}
742+
first.span
743+
}
744+
};
745+
err.span_label(
746+
attr_span,
747+
format!(
748+
"only `;` terminated statements or tail expressions are allowed after {}",
749+
if expr.attrs.len() == 1 { "this attribute" } else { "these attributes" },
750+
),
751+
);
752+
if self.token == token::Pound
753+
&& self.look_ahead(1, |t| t.kind == token::OpenDelim(Delimiter::Bracket))
754+
{
755+
// We have
756+
// #[attr]
757+
// expr
758+
// #[not_attr]
759+
// other_expr
760+
err.span_label(span, "expected `;` here");
761+
err.multipart_suggestion(
762+
"alternatively, consider surrounding the expression with a block",
763+
vec![
764+
(expr.span.shrink_to_lo(), "{ ".to_string()),
765+
(expr.span.shrink_to_hi(), " }".to_string()),
766+
],
767+
Applicability::MachineApplicable,
768+
);
769+
let mut snapshot = self.create_snapshot_for_diagnostic();
770+
if let [attr] = &expr.attrs[..]
771+
&& let ast::AttrKind::Normal(attr_kind) = &attr.kind
772+
&& let [segment] = &attr_kind.item.path.segments[..]
773+
&& segment.ident.name == sym::cfg
774+
&& let Ok(next_attr) = snapshot.parse_attribute(InnerAttrPolicy::Forbidden(None))
775+
&& let ast::AttrKind::Normal(next_attr_kind) = next_attr.kind
776+
&& let [next_segment] = &next_attr_kind.item.path.segments[..]
777+
&& segment.ident.name == sym::cfg
778+
&& let Ok(next_expr) = snapshot.parse_expr()
779+
{
780+
// We have for sure
781+
// #[cfg(..)]
782+
// expr
783+
// #[cfg(..)]
784+
// other_expr
785+
// So we suggest using `if cfg!(..) { expr } else if cfg!(..) { other_expr }`.
786+
let margin = self.sess.source_map().span_to_margin(next_expr.span).unwrap_or(0);
787+
let sugg = vec![
788+
(attr.span.with_hi(segment.span().hi()), "if cfg!".to_string()),
789+
(
790+
attr_kind.item.args.span().unwrap().shrink_to_hi().with_hi(attr.span.hi()),
791+
" {".to_string(),
792+
),
793+
(expr.span.shrink_to_lo(), " ".to_string()),
794+
(
795+
next_attr.span.with_hi(next_segment.span().hi()),
796+
"} else if cfg!".to_string(),
797+
),
798+
(
799+
next_attr_kind
800+
.item
801+
.args
802+
.span()
803+
.unwrap()
804+
.shrink_to_hi()
805+
.with_hi(next_attr.span.hi()),
806+
" {".to_string(),
807+
),
808+
(next_expr.span.shrink_to_lo(), " ".to_string()),
809+
(next_expr.span.shrink_to_hi(), format!("\n{}}}", " ".repeat(margin))),
810+
];
811+
err.multipart_suggestion(
812+
"it seems like you are trying to provide different expressions depending on \
813+
`cfg`, consider using `if cfg!(..)`",
814+
sugg,
815+
Applicability::MachineApplicable,
816+
);
817+
}
818+
}
819+
err.emit();
820+
}
725821
fn check_too_many_raw_str_terminators(&mut self, err: &mut Diagnostic) -> bool {
726822
let sm = self.sess.source_map();
727823
match (&self.prev_token.kind, &self.token.kind) {

compiler/rustc_parse/src/parser/stmt.rs

+14
Original file line numberDiff line numberDiff line change
@@ -617,6 +617,20 @@ impl<'a> Parser<'a> {
617617
let mut add_semi_to_stmt = false;
618618

619619
match &mut stmt.kind {
620+
// Expression without semicolon.
621+
StmtKind::Expr(expr)
622+
if classify::expr_requires_semi_to_be_stmt(expr)
623+
&& !expr.attrs.is_empty()
624+
&& ![token::Eof, token::Semi, token::CloseDelim(Delimiter::Brace)]
625+
.contains(&self.token.kind) =>
626+
{
627+
// The user has written `#[attr] expr` which is unsupported. (#106020)
628+
self.attr_on_non_tail_expr(&expr);
629+
// We already emitted an error, so don't emit another type error
630+
let sp = expr.span.to(self.prev_token.span);
631+
*expr = self.mk_expr_err(sp);
632+
}
633+
620634
// Expression without semicolon.
621635
StmtKind::Expr(expr)
622636
if self.token != token::Eof && classify::expr_requires_semi_to_be_stmt(expr) =>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#![feature(stmt_expr_attributes)]
2+
3+
fn foo() -> String {
4+
#[cfg(feature = "validation")]
5+
[1, 2, 3].iter().map(|c| c.to_string()).collect::<String>() //~ ERROR expected `;`, found `#`
6+
#[cfg(not(feature = "validation"))]
7+
String::new()
8+
}
9+
10+
fn bar() -> String {
11+
#[attr]
12+
[1, 2, 3].iter().map(|c| c.to_string()).collect::<String>() //~ ERROR expected `;`, found `#`
13+
#[attr] //~ ERROR cannot find attribute `attr` in this scope
14+
String::new()
15+
}
16+
17+
fn main() {
18+
println!("{}", foo());
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
error: expected `;`, found `#`
2+
--> $DIR/multiple-tail-expr-behind-cfg.rs:5:64
3+
|
4+
LL | #[cfg(feature = "validation")]
5+
| ------------------------------ only `;` terminated statements or tail expressions are allowed after this attribute
6+
LL | [1, 2, 3].iter().map(|c| c.to_string()).collect::<String>()
7+
| ^ expected `;` here
8+
LL | #[cfg(not(feature = "validation"))]
9+
| - unexpected token
10+
|
11+
help: add `;` here
12+
|
13+
LL | [1, 2, 3].iter().map(|c| c.to_string()).collect::<String>();
14+
| +
15+
help: alternatively, consider surrounding the expression with a block
16+
|
17+
LL | { [1, 2, 3].iter().map(|c| c.to_string()).collect::<String>() }
18+
| + +
19+
help: it seems like you are trying to provide different expressions depending on `cfg`, consider using `if cfg!(..)`
20+
|
21+
LL ~ if cfg!(feature = "validation") {
22+
LL ~ [1, 2, 3].iter().map(|c| c.to_string()).collect::<String>()
23+
LL ~ } else if cfg!(not(feature = "validation")) {
24+
LL ~ String::new()
25+
LL + }
26+
|
27+
28+
error: expected `;`, found `#`
29+
--> $DIR/multiple-tail-expr-behind-cfg.rs:12:64
30+
|
31+
LL | #[attr]
32+
| ------- only `;` terminated statements or tail expressions are allowed after this attribute
33+
LL | [1, 2, 3].iter().map(|c| c.to_string()).collect::<String>()
34+
| ^ expected `;` here
35+
LL | #[attr]
36+
| - unexpected token
37+
|
38+
help: add `;` here
39+
|
40+
LL | [1, 2, 3].iter().map(|c| c.to_string()).collect::<String>();
41+
| +
42+
help: alternatively, consider surrounding the expression with a block
43+
|
44+
LL | { [1, 2, 3].iter().map(|c| c.to_string()).collect::<String>() }
45+
| + +
46+
47+
error: cannot find attribute `attr` in this scope
48+
--> $DIR/multiple-tail-expr-behind-cfg.rs:13:7
49+
|
50+
LL | #[attr]
51+
| ^^^^
52+
53+
error: aborting due to 3 previous errors
54+

0 commit comments

Comments
 (0)