Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

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 rust-lang#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

Lines changed: 96 additions & 0 deletions
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

Lines changed: 14 additions & 0 deletions
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) =>
Lines changed: 19 additions & 0 deletions
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+
}
Lines changed: 54 additions & 0 deletions
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)