diff options
| author | Michael Goulet <michael@errs.io> | 2023-11-19 19:14:34 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-11-19 19:14:34 -0800 |
| commit | e6a3ca0c657a6c37e309fd8c9f0cc41d13478e20 (patch) | |
| tree | 53e315e7ca754f21b822604e74cab8e3c2993816 /compiler/rustc_parse/src | |
| parent | 6d33e900d840445b5fccfad3c0c46ec347292d0b (diff) | |
| parent | a16722d2210caa5eea941ffa20837859e249fd05 (diff) | |
| download | rust-e6a3ca0c657a6c37e309fd8c9f0cc41d13478e20.tar.gz rust-e6a3ca0c657a6c37e309fd8c9f0cc41d13478e20.zip | |
Rollup merge of #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 #106020.
r? `@oli-obk`
Diffstat (limited to 'compiler/rustc_parse/src')
| -rw-r--r-- | compiler/rustc_parse/src/parser/diagnostics.rs | 96 | ||||
| -rw-r--r-- | compiler/rustc_parse/src/parser/stmt.rs | 14 |
2 files changed, 110 insertions, 0 deletions
diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs index 262e7b899ad..57bc865a0ee 100644 --- a/compiler/rustc_parse/src/parser/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/diagnostics.rs @@ -21,6 +21,7 @@ use crate::errors::{ use crate::fluent_generated as fluent; use crate::parser; +use crate::parser::attr::InnerAttrPolicy; use rustc_ast as ast; use rustc_ast::ptr::P; use rustc_ast::token::{self, Delimiter, Lit, LitKind, TokenKind}; @@ -723,6 +724,101 @@ impl<'a> Parser<'a> { Err(err) } + pub(super) fn attr_on_non_tail_expr(&self, expr: &Expr) { + // Missing semicolon typo error. + let span = self.prev_token.span.shrink_to_hi(); + let mut err = self.sess.create_err(ExpectedSemi { + span, + token: self.token.clone(), + unexpected_token_label: Some(self.token.span), + sugg: ExpectedSemiSugg::AddSemi(span), + }); + let attr_span = match &expr.attrs[..] { + [] => unreachable!(), + [only] => only.span, + [first, rest @ ..] => { + for attr in rest { + err.span_label(attr.span, ""); + } + first.span + } + }; + err.span_label( + attr_span, + format!( + "only `;` terminated statements or tail expressions are allowed after {}", + if expr.attrs.len() == 1 { "this attribute" } else { "these attributes" }, + ), + ); + if self.token == token::Pound + && self.look_ahead(1, |t| t.kind == token::OpenDelim(Delimiter::Bracket)) + { + // We have + // #[attr] + // expr + // #[not_attr] + // other_expr + err.span_label(span, "expected `;` here"); + err.multipart_suggestion( + "alternatively, consider surrounding the expression with a block", + vec![ + (expr.span.shrink_to_lo(), "{ ".to_string()), + (expr.span.shrink_to_hi(), " }".to_string()), + ], + Applicability::MachineApplicable, + ); + let mut snapshot = self.create_snapshot_for_diagnostic(); + if let [attr] = &expr.attrs[..] + && let ast::AttrKind::Normal(attr_kind) = &attr.kind + && let [segment] = &attr_kind.item.path.segments[..] + && segment.ident.name == sym::cfg + && let Ok(next_attr) = snapshot.parse_attribute(InnerAttrPolicy::Forbidden(None)) + && let ast::AttrKind::Normal(next_attr_kind) = next_attr.kind + && let [next_segment] = &next_attr_kind.item.path.segments[..] + && segment.ident.name == sym::cfg + && let Ok(next_expr) = snapshot.parse_expr() + { + // We have for sure + // #[cfg(..)] + // expr + // #[cfg(..)] + // other_expr + // So we suggest using `if cfg!(..) { expr } else if cfg!(..) { other_expr }`. + let margin = self.sess.source_map().span_to_margin(next_expr.span).unwrap_or(0); + let sugg = vec![ + (attr.span.with_hi(segment.span().hi()), "if cfg!".to_string()), + ( + attr_kind.item.args.span().unwrap().shrink_to_hi().with_hi(attr.span.hi()), + " {".to_string(), + ), + (expr.span.shrink_to_lo(), " ".to_string()), + ( + next_attr.span.with_hi(next_segment.span().hi()), + "} else if cfg!".to_string(), + ), + ( + next_attr_kind + .item + .args + .span() + .unwrap() + .shrink_to_hi() + .with_hi(next_attr.span.hi()), + " {".to_string(), + ), + (next_expr.span.shrink_to_lo(), " ".to_string()), + (next_expr.span.shrink_to_hi(), format!("\n{}}}", " ".repeat(margin))), + ]; + err.multipart_suggestion( + "it seems like you are trying to provide different expressions depending on \ + `cfg`, consider using `if cfg!(..)`", + sugg, + Applicability::MachineApplicable, + ); + } + } + err.emit(); + } fn check_too_many_raw_str_terminators(&mut self, err: &mut Diagnostic) -> bool { let sm = self.sess.source_map(); match (&self.prev_token.kind, &self.token.kind) { diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs index d1c5ca4b93c..5316dde6096 100644 --- a/compiler/rustc_parse/src/parser/stmt.rs +++ b/compiler/rustc_parse/src/parser/stmt.rs @@ -619,6 +619,20 @@ impl<'a> Parser<'a> { match &mut stmt.kind { // Expression without semicolon. StmtKind::Expr(expr) + if classify::expr_requires_semi_to_be_stmt(expr) + && !expr.attrs.is_empty() + && ![token::Eof, token::Semi, token::CloseDelim(Delimiter::Brace)] + .contains(&self.token.kind) => + { + // The user has written `#[attr] expr` which is unsupported. (#106020) + self.attr_on_non_tail_expr(&expr); + // We already emitted an error, so don't emit another type error + let sp = expr.span.to(self.prev_token.span); + *expr = self.mk_expr_err(sp); + } + + // Expression without semicolon. + StmtKind::Expr(expr) if self.token != token::Eof && classify::expr_requires_semi_to_be_stmt(expr) => { // Just check for errors and recover; do not eat semicolon yet. |
