Skip to content

Detect blocks that could be struct expr bodies #75470

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 8, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions compiler/rustc_expand/src/expand.rs
Original file line number Diff line number Diff line change
@@ -20,7 +20,7 @@ use rustc_data_structures::map_in_place::MapInPlace;
use rustc_data_structures::stack::ensure_sufficient_stack;
use rustc_errors::{struct_span_err, Applicability, PResult};
use rustc_feature::Features;
use rustc_parse::parser::Parser;
use rustc_parse::parser::{AttemptLocalParseRecovery, Parser};
use rustc_parse::validate_attr;
use rustc_session::lint::builtin::UNUSED_DOC_COMMENTS;
use rustc_session::lint::BuiltinLintDiagnostics;
@@ -921,7 +921,7 @@ pub fn parse_ast_fragment<'a>(
let mut stmts = SmallVec::new();
// Won't make progress on a `}`.
while this.token != token::Eof && this.token != token::CloseDelim(token::Brace) {
if let Some(stmt) = this.parse_full_stmt()? {
if let Some(stmt) = this.parse_full_stmt(AttemptLocalParseRecovery::Yes)? {
stmts.push(stmt);
}
}
87 changes: 85 additions & 2 deletions compiler/rustc_parse/src/parser/diagnostics.rs
Original file line number Diff line number Diff line change
@@ -5,8 +5,9 @@ use rustc_ast::ptr::P;
use rustc_ast::token::{self, Lit, LitKind, TokenKind};
use rustc_ast::util::parser::AssocOp;
use rustc_ast::{
self as ast, AngleBracketedArgs, AttrVec, BinOpKind, BindingMode, BlockCheckMode, Expr,
ExprKind, Item, ItemKind, Mutability, Param, Pat, PatKind, PathSegment, QSelf, Ty, TyKind,
self as ast, AngleBracketedArgs, AttrVec, BinOpKind, BindingMode, Block, BlockCheckMode, Expr,
ExprKind, Item, ItemKind, Mutability, Param, Pat, PatKind, Path, PathSegment, QSelf, Ty,
TyKind,
};
use rustc_ast_pretty::pprust;
use rustc_data_structures::fx::FxHashSet;
@@ -119,6 +120,28 @@ crate enum ConsumeClosingDelim {
No,
}

#[derive(Clone, Copy)]
pub enum AttemptLocalParseRecovery {
Yes,
No,
}

impl AttemptLocalParseRecovery {
pub fn yes(&self) -> bool {
match self {
AttemptLocalParseRecovery::Yes => true,
AttemptLocalParseRecovery::No => false,
}
}

pub fn no(&self) -> bool {
match self {
AttemptLocalParseRecovery::Yes => false,
AttemptLocalParseRecovery::No => true,
}
}
}

impl<'a> Parser<'a> {
pub(super) fn span_fatal_err<S: Into<MultiSpan>>(
&self,
@@ -321,6 +344,66 @@ impl<'a> Parser<'a> {
}
}

pub fn maybe_suggest_struct_literal(
&mut self,
lo: Span,
s: BlockCheckMode,
) -> Option<PResult<'a, P<Block>>> {
if self.token.is_ident() && self.look_ahead(1, |t| t == &token::Colon) {
// We might be having a struct literal where people forgot to include the path:
// fn foo() -> Foo {
// field: value,
// }
let mut snapshot = self.clone();
let path =
Path { segments: vec![], span: self.prev_token.span.shrink_to_lo(), tokens: None };
let struct_expr = snapshot.parse_struct_expr(path, AttrVec::new(), false);
let block_tail = self.parse_block_tail(lo, s, AttemptLocalParseRecovery::No);
return Some(match (struct_expr, block_tail) {
(Ok(expr), Err(mut err)) => {
// We have encountered the following:
// fn foo() -> Foo {
// field: value,
// }
// Suggest:
// fn foo() -> Foo { Path {
// field: value,
// } }
err.delay_as_bug();
self.struct_span_err(expr.span, "struct literal body without path")
.multipart_suggestion(
"you might have forgotten to add the struct literal inside the block",
vec![
(expr.span.shrink_to_lo(), "{ SomeStruct ".to_string()),
(expr.span.shrink_to_hi(), " }".to_string()),
],
Applicability::MaybeIncorrect,
)
.emit();
*self = snapshot;
Ok(self.mk_block(
vec![self.mk_stmt_err(expr.span)],
s,
lo.to(self.prev_token.span),
))
}
(Err(mut err), Ok(tail)) => {
// We have a block tail that contains a somehow valid type ascription expr.
err.cancel();
Ok(tail)
}
(Err(mut snapshot_err), Err(err)) => {
// We don't know what went wrong, emit the normal error.
snapshot_err.cancel();
self.consume_block(token::Brace, ConsumeClosingDelim::Yes);
Err(err)
}
(Ok(_), Ok(tail)) => Ok(tail),
});
}
None
}

pub fn maybe_annotate_with_ascription(
&mut self,
err: &mut DiagnosticBuilder<'_>,
16 changes: 12 additions & 4 deletions compiler/rustc_parse/src/parser/expr.rs
Original file line number Diff line number Diff line change
@@ -2015,9 +2015,12 @@ impl<'a> Parser<'a> {
) -> Option<PResult<'a, P<Expr>>> {
let struct_allowed = !self.restrictions.contains(Restrictions::NO_STRUCT_LITERAL);
if struct_allowed || self.is_certainly_not_a_block() {
// This is a struct literal, but we don't can't accept them here.
let expr = self.parse_struct_expr(path.clone(), attrs.clone());
if let Err(err) = self.expect(&token::OpenDelim(token::Brace)) {
return Some(Err(err));
}
let expr = self.parse_struct_expr(path.clone(), attrs.clone(), true);
if let (Ok(expr), false) = (&expr, struct_allowed) {
// This is a struct literal, but we don't can't accept them here.
self.error_struct_lit_not_allowed_here(path.span, expr.span);
}
return Some(expr);
@@ -2035,12 +2038,13 @@ impl<'a> Parser<'a> {
.emit();
}

/// Precondition: already parsed the '{'.
pub(super) fn parse_struct_expr(
&mut self,
pth: ast::Path,
mut attrs: AttrVec,
recover: bool,
) -> PResult<'a, P<Expr>> {
self.bump();
let mut fields = Vec::new();
let mut base = None;
let mut recover_async = false;
@@ -2059,10 +2063,11 @@ impl<'a> Parser<'a> {
let exp_span = self.prev_token.span;
match self.parse_expr() {
Ok(e) => base = Some(e),
Err(mut e) => {
Err(mut e) if recover => {
e.emit();
self.recover_stmt();
}
Err(e) => return Err(e),
}
self.recover_struct_comma_after_dotdot(exp_span);
break;
@@ -2114,6 +2119,9 @@ impl<'a> Parser<'a> {
);
}
}
if !recover {
return Err(e);
}
e.emit();
self.recover_stmt_(SemiColonMode::Comma, BlockMode::Ignore);
self.eat(&token::Comma);
1 change: 1 addition & 0 deletions compiler/rustc_parse/src/parser/mod.rs
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@ mod stmt;
mod ty;

use crate::lexer::UnmatchedBrace;
pub use diagnostics::AttemptLocalParseRecovery;
use diagnostics::Error;
pub use path::PathStyle;

36 changes: 27 additions & 9 deletions compiler/rustc_parse/src/parser/stmt.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::attr::DEFAULT_INNER_ATTR_FORBIDDEN;
use super::diagnostics::Error;
use super::diagnostics::{AttemptLocalParseRecovery, Error};
use super::expr::LhsExpr;
use super::pat::GateOr;
use super::path::PathStyle;
@@ -79,8 +79,8 @@ impl<'a> Parser<'a> {
return self.parse_stmt_mac(lo, attrs.into(), path);
}

let expr = if self.check(&token::OpenDelim(token::Brace)) {
self.parse_struct_expr(path, AttrVec::new())?
let expr = if self.eat(&token::OpenDelim(token::Brace)) {
self.parse_struct_expr(path, AttrVec::new(), true)?
} else {
let hi = self.prev_token.span;
self.mk_expr(lo.to(hi), ExprKind::Path(None, path), AttrVec::new())
@@ -321,25 +321,37 @@ impl<'a> Parser<'a> {
return self.error_block_no_opening_brace();
}

Ok((self.parse_inner_attributes()?, self.parse_block_tail(lo, blk_mode)?))
let attrs = self.parse_inner_attributes()?;
let tail = if let Some(tail) = self.maybe_suggest_struct_literal(lo, blk_mode) {
tail?
} else {
self.parse_block_tail(lo, blk_mode, AttemptLocalParseRecovery::Yes)?
};
Ok((attrs, tail))
}

/// Parses the rest of a block expression or function body.
/// Precondition: already parsed the '{'.
fn parse_block_tail(&mut self, lo: Span, s: BlockCheckMode) -> PResult<'a, P<Block>> {
crate fn parse_block_tail(
&mut self,
lo: Span,
s: BlockCheckMode,
recover: AttemptLocalParseRecovery,
) -> PResult<'a, P<Block>> {
let mut stmts = vec![];
while !self.eat(&token::CloseDelim(token::Brace)) {
if self.token == token::Eof {
break;
}
let stmt = match self.parse_full_stmt() {
Err(mut err) => {
let stmt = match self.parse_full_stmt(recover) {
Err(mut err) if recover.yes() => {
self.maybe_annotate_with_ascription(&mut err, false);
err.emit();
self.recover_stmt_(SemiColonMode::Ignore, BlockMode::Ignore);
Some(self.mk_stmt_err(self.token.span))
}
Ok(stmt) => stmt,
Err(err) => return Err(err),
};
if let Some(stmt) = stmt {
stmts.push(stmt);
@@ -352,7 +364,10 @@ impl<'a> Parser<'a> {
}

/// Parses a statement, including the trailing semicolon.
pub fn parse_full_stmt(&mut self) -> PResult<'a, Option<Stmt>> {
pub fn parse_full_stmt(
&mut self,
recover: AttemptLocalParseRecovery,
) -> PResult<'a, Option<Stmt>> {
// Skip looking for a trailing semicolon when we have an interpolated statement.
maybe_whole!(self, NtStmt, |x| Some(x));

@@ -391,6 +406,9 @@ impl<'a> Parser<'a> {
if let Err(mut e) =
self.check_mistyped_turbofish_with_multiple_type_params(e, expr)
{
if recover.no() {
return Err(e);
}
e.emit();
self.recover_stmt();
}
@@ -432,7 +450,7 @@ impl<'a> Parser<'a> {
Stmt { id: DUMMY_NODE_ID, kind, span, tokens: None }
}

fn mk_stmt_err(&self, span: Span) -> Stmt {
pub(super) fn mk_stmt_err(&self, span: Span) -> Stmt {
self.mk_stmt(span, StmtKind::Expr(self.mk_expr_err(span)))
}

15 changes: 15 additions & 0 deletions src/test/ui/parser/bare-struct-body.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
struct Foo {
val: (),
}

fn foo() -> Foo { //~ ERROR struct literal body without path
val: (),
}

fn main() {
let x = foo();
x.val == 42; //~ ERROR mismatched types
let x = { //~ ERROR struct literal body without path
val: (),
};
}
41 changes: 41 additions & 0 deletions src/test/ui/parser/bare-struct-body.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
error: struct literal body without path
--> $DIR/bare-struct-body.rs:5:17
|
LL | fn foo() -> Foo {
| _________________^
LL | | val: (),
LL | | }
| |_^
|
help: you might have forgotten to add the struct literal inside the block
|
LL | fn foo() -> Foo { SomeStruct {
LL | val: (),
LL | } }
|

error: struct literal body without path
--> $DIR/bare-struct-body.rs:12:13
|
LL | let x = {
| _____________^
LL | | val: (),
LL | | };
| |_____^
|
help: you might have forgotten to add the struct literal inside the block
|
LL | let x = { SomeStruct {
LL | val: (),
LL | } };
|

error[E0308]: mismatched types
--> $DIR/bare-struct-body.rs:11:14
|
LL | x.val == 42;
| ^^ expected `()`, found integer

error: aborting due to 3 previous errors

For more information about this error, try `rustc --explain E0308`.