Skip to content

Commit ee730ff

Browse files
authored
Unrolled build for rust-lang#117988
Rollup merge of rust-lang#117988 - estebank:issue-106020, r=cjgillot Handle attempts to have multiple `cfg`d 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. r? `@oli-obk`
2 parents 79e961f + a16722d commit ee730ff

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};
@@ -723,6 +724,101 @@ impl<'a> Parser<'a> {
723724
Err(err)
724725
}
725726

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