Skip to content

Commit d9985fc

Browse files
committed
Auto merge of rust-lang#75470 - estebank:bare-type-expr, r=davidtwco
Detect blocks that could be struct expr bodies This approach lives exclusively in the parser, so struct expr bodies that are syntactically correct on their own but are otherwise incorrect will still emit confusing errors, like in the following case: ```rust fn foo() -> Foo { bar: Vec::new() } ``` ``` error[E0425]: cannot find value `bar` in this scope --> src/file.rs:5:5 | 5 | bar: Vec::new() | ^^^ expecting a type here because of type ascription error[E0214]: parenthesized type parameters may only be used with a `Fn` trait --> src/file.rs:5:15 | 5 | bar: Vec::new() | ^^^^^ only `Fn` traits may use parentheses error[E0107]: wrong number of type arguments: expected 1, found 0 --> src/file.rs:5:10 | 5 | bar: Vec::new() | ^^^^^^^^^^ expected 1 type argument ``` If that field had a trailing comma, that would be a parse error and it would trigger the new, more targetted, error: ``` error: struct literal body without path --> file.rs:4:17 | 4 | fn foo() -> Foo { | _________________^ 5 | | bar: Vec::new(), 6 | | } | |_^ | help: you might have forgotten to add the struct literal inside the block | 4 | fn foo() -> Foo { Path { 5 | bar: Vec::new(), 6 | } } | ``` Partially address last remaining part of rust-lang#34255.
2 parents e055f87 + e5f83bc commit d9985fc

File tree

7 files changed

+183
-17
lines changed

7 files changed

+183
-17
lines changed

compiler/rustc_expand/src/expand.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use rustc_data_structures::map_in_place::MapInPlace;
2020
use rustc_data_structures::stack::ensure_sufficient_stack;
2121
use rustc_errors::{struct_span_err, Applicability, PResult};
2222
use rustc_feature::Features;
23-
use rustc_parse::parser::Parser;
23+
use rustc_parse::parser::{AttemptLocalParseRecovery, Parser};
2424
use rustc_parse::validate_attr;
2525
use rustc_session::lint::builtin::UNUSED_DOC_COMMENTS;
2626
use rustc_session::lint::BuiltinLintDiagnostics;
@@ -921,7 +921,7 @@ pub fn parse_ast_fragment<'a>(
921921
let mut stmts = SmallVec::new();
922922
// Won't make progress on a `}`.
923923
while this.token != token::Eof && this.token != token::CloseDelim(token::Brace) {
924-
if let Some(stmt) = this.parse_full_stmt()? {
924+
if let Some(stmt) = this.parse_full_stmt(AttemptLocalParseRecovery::Yes)? {
925925
stmts.push(stmt);
926926
}
927927
}

compiler/rustc_parse/src/parser/diagnostics.rs

+85-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ use rustc_ast::ptr::P;
55
use rustc_ast::token::{self, Lit, LitKind, TokenKind};
66
use rustc_ast::util::parser::AssocOp;
77
use rustc_ast::{
8-
self as ast, AngleBracketedArgs, AttrVec, BinOpKind, BindingMode, BlockCheckMode, Expr,
9-
ExprKind, Item, ItemKind, Mutability, Param, Pat, PatKind, PathSegment, QSelf, Ty, TyKind,
8+
self as ast, AngleBracketedArgs, AttrVec, BinOpKind, BindingMode, Block, BlockCheckMode, Expr,
9+
ExprKind, Item, ItemKind, Mutability, Param, Pat, PatKind, Path, PathSegment, QSelf, Ty,
10+
TyKind,
1011
};
1112
use rustc_ast_pretty::pprust;
1213
use rustc_data_structures::fx::FxHashSet;
@@ -119,6 +120,28 @@ crate enum ConsumeClosingDelim {
119120
No,
120121
}
121122

123+
#[derive(Clone, Copy)]
124+
pub enum AttemptLocalParseRecovery {
125+
Yes,
126+
No,
127+
}
128+
129+
impl AttemptLocalParseRecovery {
130+
pub fn yes(&self) -> bool {
131+
match self {
132+
AttemptLocalParseRecovery::Yes => true,
133+
AttemptLocalParseRecovery::No => false,
134+
}
135+
}
136+
137+
pub fn no(&self) -> bool {
138+
match self {
139+
AttemptLocalParseRecovery::Yes => false,
140+
AttemptLocalParseRecovery::No => true,
141+
}
142+
}
143+
}
144+
122145
impl<'a> Parser<'a> {
123146
pub(super) fn span_fatal_err<S: Into<MultiSpan>>(
124147
&self,
@@ -321,6 +344,66 @@ impl<'a> Parser<'a> {
321344
}
322345
}
323346

347+
pub fn maybe_suggest_struct_literal(
348+
&mut self,
349+
lo: Span,
350+
s: BlockCheckMode,
351+
) -> Option<PResult<'a, P<Block>>> {
352+
if self.token.is_ident() && self.look_ahead(1, |t| t == &token::Colon) {
353+
// We might be having a struct literal where people forgot to include the path:
354+
// fn foo() -> Foo {
355+
// field: value,
356+
// }
357+
let mut snapshot = self.clone();
358+
let path =
359+
Path { segments: vec![], span: self.prev_token.span.shrink_to_lo(), tokens: None };
360+
let struct_expr = snapshot.parse_struct_expr(path, AttrVec::new(), false);
361+
let block_tail = self.parse_block_tail(lo, s, AttemptLocalParseRecovery::No);
362+
return Some(match (struct_expr, block_tail) {
363+
(Ok(expr), Err(mut err)) => {
364+
// We have encountered the following:
365+
// fn foo() -> Foo {
366+
// field: value,
367+
// }
368+
// Suggest:
369+
// fn foo() -> Foo { Path {
370+
// field: value,
371+
// } }
372+
err.delay_as_bug();
373+
self.struct_span_err(expr.span, "struct literal body without path")
374+
.multipart_suggestion(
375+
"you might have forgotten to add the struct literal inside the block",
376+
vec![
377+
(expr.span.shrink_to_lo(), "{ SomeStruct ".to_string()),
378+
(expr.span.shrink_to_hi(), " }".to_string()),
379+
],
380+
Applicability::MaybeIncorrect,
381+
)
382+
.emit();
383+
*self = snapshot;
384+
Ok(self.mk_block(
385+
vec![self.mk_stmt_err(expr.span)],
386+
s,
387+
lo.to(self.prev_token.span),
388+
))
389+
}
390+
(Err(mut err), Ok(tail)) => {
391+
// We have a block tail that contains a somehow valid type ascription expr.
392+
err.cancel();
393+
Ok(tail)
394+
}
395+
(Err(mut snapshot_err), Err(err)) => {
396+
// We don't know what went wrong, emit the normal error.
397+
snapshot_err.cancel();
398+
self.consume_block(token::Brace, ConsumeClosingDelim::Yes);
399+
Err(err)
400+
}
401+
(Ok(_), Ok(tail)) => Ok(tail),
402+
});
403+
}
404+
None
405+
}
406+
324407
pub fn maybe_annotate_with_ascription(
325408
&mut self,
326409
err: &mut DiagnosticBuilder<'_>,

compiler/rustc_parse/src/parser/expr.rs

+12-4
Original file line numberDiff line numberDiff line change
@@ -2015,9 +2015,12 @@ impl<'a> Parser<'a> {
20152015
) -> Option<PResult<'a, P<Expr>>> {
20162016
let struct_allowed = !self.restrictions.contains(Restrictions::NO_STRUCT_LITERAL);
20172017
if struct_allowed || self.is_certainly_not_a_block() {
2018-
// This is a struct literal, but we don't can't accept them here.
2019-
let expr = self.parse_struct_expr(path.clone(), attrs.clone());
2018+
if let Err(err) = self.expect(&token::OpenDelim(token::Brace)) {
2019+
return Some(Err(err));
2020+
}
2021+
let expr = self.parse_struct_expr(path.clone(), attrs.clone(), true);
20202022
if let (Ok(expr), false) = (&expr, struct_allowed) {
2023+
// This is a struct literal, but we don't can't accept them here.
20212024
self.error_struct_lit_not_allowed_here(path.span, expr.span);
20222025
}
20232026
return Some(expr);
@@ -2035,12 +2038,13 @@ impl<'a> Parser<'a> {
20352038
.emit();
20362039
}
20372040

2041+
/// Precondition: already parsed the '{'.
20382042
pub(super) fn parse_struct_expr(
20392043
&mut self,
20402044
pth: ast::Path,
20412045
mut attrs: AttrVec,
2046+
recover: bool,
20422047
) -> PResult<'a, P<Expr>> {
2043-
self.bump();
20442048
let mut fields = Vec::new();
20452049
let mut base = None;
20462050
let mut recover_async = false;
@@ -2059,10 +2063,11 @@ impl<'a> Parser<'a> {
20592063
let exp_span = self.prev_token.span;
20602064
match self.parse_expr() {
20612065
Ok(e) => base = Some(e),
2062-
Err(mut e) => {
2066+
Err(mut e) if recover => {
20632067
e.emit();
20642068
self.recover_stmt();
20652069
}
2070+
Err(e) => return Err(e),
20662071
}
20672072
self.recover_struct_comma_after_dotdot(exp_span);
20682073
break;
@@ -2114,6 +2119,9 @@ impl<'a> Parser<'a> {
21142119
);
21152120
}
21162121
}
2122+
if !recover {
2123+
return Err(e);
2124+
}
21172125
e.emit();
21182126
self.recover_stmt_(SemiColonMode::Comma, BlockMode::Ignore);
21192127
self.eat(&token::Comma);

compiler/rustc_parse/src/parser/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ mod stmt;
1010
mod ty;
1111

1212
use crate::lexer::UnmatchedBrace;
13+
pub use diagnostics::AttemptLocalParseRecovery;
1314
use diagnostics::Error;
1415
pub use path::PathStyle;
1516

compiler/rustc_parse/src/parser/stmt.rs

+27-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::attr::DEFAULT_INNER_ATTR_FORBIDDEN;
2-
use super::diagnostics::Error;
2+
use super::diagnostics::{AttemptLocalParseRecovery, Error};
33
use super::expr::LhsExpr;
44
use super::pat::GateOr;
55
use super::path::PathStyle;
@@ -79,8 +79,8 @@ impl<'a> Parser<'a> {
7979
return self.parse_stmt_mac(lo, attrs.into(), path);
8080
}
8181

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

324-
Ok((self.parse_inner_attributes()?, self.parse_block_tail(lo, blk_mode)?))
324+
let attrs = self.parse_inner_attributes()?;
325+
let tail = if let Some(tail) = self.maybe_suggest_struct_literal(lo, blk_mode) {
326+
tail?
327+
} else {
328+
self.parse_block_tail(lo, blk_mode, AttemptLocalParseRecovery::Yes)?
329+
};
330+
Ok((attrs, tail))
325331
}
326332

327333
/// Parses the rest of a block expression or function body.
328334
/// Precondition: already parsed the '{'.
329-
fn parse_block_tail(&mut self, lo: Span, s: BlockCheckMode) -> PResult<'a, P<Block>> {
335+
crate fn parse_block_tail(
336+
&mut self,
337+
lo: Span,
338+
s: BlockCheckMode,
339+
recover: AttemptLocalParseRecovery,
340+
) -> PResult<'a, P<Block>> {
330341
let mut stmts = vec![];
331342
while !self.eat(&token::CloseDelim(token::Brace)) {
332343
if self.token == token::Eof {
333344
break;
334345
}
335-
let stmt = match self.parse_full_stmt() {
336-
Err(mut err) => {
346+
let stmt = match self.parse_full_stmt(recover) {
347+
Err(mut err) if recover.yes() => {
337348
self.maybe_annotate_with_ascription(&mut err, false);
338349
err.emit();
339350
self.recover_stmt_(SemiColonMode::Ignore, BlockMode::Ignore);
340351
Some(self.mk_stmt_err(self.token.span))
341352
}
342353
Ok(stmt) => stmt,
354+
Err(err) => return Err(err),
343355
};
344356
if let Some(stmt) = stmt {
345357
stmts.push(stmt);
@@ -352,7 +364,10 @@ impl<'a> Parser<'a> {
352364
}
353365

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

@@ -391,6 +406,9 @@ impl<'a> Parser<'a> {
391406
if let Err(mut e) =
392407
self.check_mistyped_turbofish_with_multiple_type_params(e, expr)
393408
{
409+
if recover.no() {
410+
return Err(e);
411+
}
394412
e.emit();
395413
self.recover_stmt();
396414
}
@@ -432,7 +450,7 @@ impl<'a> Parser<'a> {
432450
Stmt { id: DUMMY_NODE_ID, kind, span, tokens: None }
433451
}
434452

435-
fn mk_stmt_err(&self, span: Span) -> Stmt {
453+
pub(super) fn mk_stmt_err(&self, span: Span) -> Stmt {
436454
self.mk_stmt(span, StmtKind::Expr(self.mk_expr_err(span)))
437455
}
438456

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
struct Foo {
2+
val: (),
3+
}
4+
5+
fn foo() -> Foo { //~ ERROR struct literal body without path
6+
val: (),
7+
}
8+
9+
fn main() {
10+
let x = foo();
11+
x.val == 42; //~ ERROR mismatched types
12+
let x = { //~ ERROR struct literal body without path
13+
val: (),
14+
};
15+
}
+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
error: struct literal body without path
2+
--> $DIR/bare-struct-body.rs:5:17
3+
|
4+
LL | fn foo() -> Foo {
5+
| _________________^
6+
LL | | val: (),
7+
LL | | }
8+
| |_^
9+
|
10+
help: you might have forgotten to add the struct literal inside the block
11+
|
12+
LL | fn foo() -> Foo { SomeStruct {
13+
LL | val: (),
14+
LL | } }
15+
|
16+
17+
error: struct literal body without path
18+
--> $DIR/bare-struct-body.rs:12:13
19+
|
20+
LL | let x = {
21+
| _____________^
22+
LL | | val: (),
23+
LL | | };
24+
| |_____^
25+
|
26+
help: you might have forgotten to add the struct literal inside the block
27+
|
28+
LL | let x = { SomeStruct {
29+
LL | val: (),
30+
LL | } };
31+
|
32+
33+
error[E0308]: mismatched types
34+
--> $DIR/bare-struct-body.rs:11:14
35+
|
36+
LL | x.val == 42;
37+
| ^^ expected `()`, found integer
38+
39+
error: aborting due to 3 previous errors
40+
41+
For more information about this error, try `rustc --explain E0308`.

0 commit comments

Comments
 (0)