Skip to content

Commit d4203ed

Browse files
committed
Auto merge of rust-lang#106537 - fmease:recover-where-clause-before-tuple-struct-body, r=estebank
Recover from where clauses placed before tuple struct bodies Open to any suggestions regarding the phrasing of the diagnostic. Fixes rust-lang#100790. `@rustbot` label A-diagnostics r? diagnostics
2 parents 56ee65a + 70ddde7 commit d4203ed

11 files changed

+266
-17
lines changed

compiler/rustc_error_messages/locales/en-US/parse.ftl

+6
Original file line numberDiff line numberDiff line change
@@ -372,3 +372,9 @@ parse_maybe_fn_typo_with_impl = you might have meant to write `impl` instead of
372372
373373
parse_expected_fn_path_found_fn_keyword = expected identifier, found keyword `fn`
374374
.suggestion = use `Fn` to refer to the trait
375+
376+
parse_where_clause_before_tuple_struct_body = where clauses are not allowed before tuple struct bodies
377+
.label = unexpected where clause
378+
.name_label = while parsing this tuple struct
379+
.body_label = the struct body
380+
.suggestion = move the body before the where clause

compiler/rustc_parse/src/errors.rs

+24
Original file line numberDiff line numberDiff line change
@@ -1255,3 +1255,27 @@ pub(crate) struct ExpectedFnPathFoundFnKeyword {
12551255
#[suggestion(applicability = "machine-applicable", code = "Fn", style = "verbose")]
12561256
pub fn_token_span: Span,
12571257
}
1258+
1259+
#[derive(Diagnostic)]
1260+
#[diag(parse_where_clause_before_tuple_struct_body)]
1261+
pub(crate) struct WhereClauseBeforeTupleStructBody {
1262+
#[primary_span]
1263+
#[label]
1264+
pub span: Span,
1265+
#[label(name_label)]
1266+
pub name: Span,
1267+
#[label(body_label)]
1268+
pub body: Span,
1269+
#[subdiagnostic]
1270+
pub sugg: Option<WhereClauseBeforeTupleStructBodySugg>,
1271+
}
1272+
1273+
#[derive(Subdiagnostic)]
1274+
#[multipart_suggestion(suggestion, applicability = "machine-applicable")]
1275+
pub(crate) struct WhereClauseBeforeTupleStructBodySugg {
1276+
#[suggestion_part(code = "{snippet}")]
1277+
pub left: Span,
1278+
pub snippet: String,
1279+
#[suggestion_part(code = "")]
1280+
pub right: Span,
1281+
}

compiler/rustc_parse/src/parser/generics.rs

+108-10
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
1+
use crate::errors::{WhereClauseBeforeTupleStructBody, WhereClauseBeforeTupleStructBodySugg};
2+
13
use super::{ForceCollect, Parser, TrailingToken};
24

5+
use ast::token::Delimiter;
36
use rustc_ast::token;
47
use rustc_ast::{
58
self as ast, AttrVec, GenericBounds, GenericParam, GenericParamKind, TyKind, WhereClause,
69
};
710
use rustc_errors::{Applicability, PResult};
8-
use rustc_span::symbol::kw;
11+
use rustc_span::symbol::{kw, Ident};
12+
use rustc_span::Span;
13+
14+
enum PredicateOrStructBody {
15+
Predicate(ast::WherePredicate),
16+
StructBody(Vec<ast::FieldDef>),
17+
}
918

1019
impl<'a> Parser<'a> {
1120
/// Parses bounds of a lifetime parameter `BOUND + BOUND + BOUND`, possibly with trailing `+`.
@@ -240,23 +249,39 @@ impl<'a> Parser<'a> {
240249
})
241250
}
242251

243-
/// Parses an optional where-clause and places it in `generics`.
252+
/// Parses an optional where-clause.
244253
///
245254
/// ```ignore (only-for-syntax-highlight)
246255
/// where T : Trait<U, V> + 'b, 'a : 'b
247256
/// ```
248257
pub(super) fn parse_where_clause(&mut self) -> PResult<'a, WhereClause> {
258+
self.parse_where_clause_common(None).map(|(clause, _)| clause)
259+
}
260+
261+
pub(super) fn parse_struct_where_clause(
262+
&mut self,
263+
struct_name: Ident,
264+
body_insertion_point: Span,
265+
) -> PResult<'a, (WhereClause, Option<Vec<ast::FieldDef>>)> {
266+
self.parse_where_clause_common(Some((struct_name, body_insertion_point)))
267+
}
268+
269+
fn parse_where_clause_common(
270+
&mut self,
271+
struct_: Option<(Ident, Span)>,
272+
) -> PResult<'a, (WhereClause, Option<Vec<ast::FieldDef>>)> {
249273
let mut where_clause = WhereClause {
250274
has_where_token: false,
251275
predicates: Vec::new(),
252276
span: self.prev_token.span.shrink_to_hi(),
253277
};
278+
let mut tuple_struct_body = None;
254279

255280
if !self.eat_keyword(kw::Where) {
256-
return Ok(where_clause);
281+
return Ok((where_clause, None));
257282
}
258283
where_clause.has_where_token = true;
259-
let lo = self.prev_token.span;
284+
let where_lo = self.prev_token.span;
260285

261286
// We are considering adding generics to the `where` keyword as an alternative higher-rank
262287
// parameter syntax (as in `where<'a>` or `where<T>`. To avoid that being a breaking
@@ -272,21 +297,30 @@ impl<'a> Parser<'a> {
272297
}
273298

274299
loop {
275-
let lo = self.token.span;
300+
let where_sp = where_lo.to(self.prev_token.span);
301+
let pred_lo = self.token.span;
276302
if self.check_lifetime() && self.look_ahead(1, |t| !t.is_like_plus()) {
277303
let lifetime = self.expect_lifetime();
278304
// Bounds starting with a colon are mandatory, but possibly empty.
279305
self.expect(&token::Colon)?;
280306
let bounds = self.parse_lt_param_bounds();
281307
where_clause.predicates.push(ast::WherePredicate::RegionPredicate(
282308
ast::WhereRegionPredicate {
283-
span: lo.to(self.prev_token.span),
309+
span: pred_lo.to(self.prev_token.span),
284310
lifetime,
285311
bounds,
286312
},
287313
));
288314
} else if self.check_type() {
289-
where_clause.predicates.push(self.parse_ty_where_predicate()?);
315+
match self.parse_ty_where_predicate_or_recover_tuple_struct_body(
316+
struct_, pred_lo, where_sp,
317+
)? {
318+
PredicateOrStructBody::Predicate(pred) => where_clause.predicates.push(pred),
319+
PredicateOrStructBody::StructBody(body) => {
320+
tuple_struct_body = Some(body);
321+
break;
322+
}
323+
}
290324
} else {
291325
break;
292326
}
@@ -297,7 +331,7 @@ impl<'a> Parser<'a> {
297331
if self.eat_keyword_noexpect(kw::Where) {
298332
let msg = "cannot define duplicate `where` clauses on an item";
299333
let mut err = self.struct_span_err(self.token.span, msg);
300-
err.span_label(lo, "previous `where` clause starts here");
334+
err.span_label(pred_lo, "previous `where` clause starts here");
301335
err.span_suggestion_verbose(
302336
prev_token.shrink_to_hi().to(self.prev_token.span),
303337
"consider joining the two `where` clauses into one",
@@ -310,8 +344,72 @@ impl<'a> Parser<'a> {
310344
}
311345
}
312346

313-
where_clause.span = lo.to(self.prev_token.span);
314-
Ok(where_clause)
347+
where_clause.span = where_lo.to(self.prev_token.span);
348+
Ok((where_clause, tuple_struct_body))
349+
}
350+
351+
fn parse_ty_where_predicate_or_recover_tuple_struct_body(
352+
&mut self,
353+
struct_: Option<(Ident, Span)>,
354+
pred_lo: Span,
355+
where_sp: Span,
356+
) -> PResult<'a, PredicateOrStructBody> {
357+
let mut snapshot = None;
358+
359+
if let Some(struct_) = struct_
360+
&& self.may_recover()
361+
&& self.token.kind == token::OpenDelim(Delimiter::Parenthesis)
362+
{
363+
snapshot = Some((struct_, self.create_snapshot_for_diagnostic()));
364+
};
365+
366+
match self.parse_ty_where_predicate() {
367+
Ok(pred) => Ok(PredicateOrStructBody::Predicate(pred)),
368+
Err(type_err) => {
369+
let Some(((struct_name, body_insertion_point), mut snapshot)) = snapshot else {
370+
return Err(type_err);
371+
};
372+
373+
// Check if we might have encountered an out of place tuple struct body.
374+
match snapshot.parse_tuple_struct_body() {
375+
// Since we don't know the exact reason why we failed to parse the
376+
// predicate (we might have stumbled upon something bogus like `(T): ?`),
377+
// employ a simple heuristic to weed out some pathological cases:
378+
// Look for a semicolon (strong indicator) or anything that might mark
379+
// the end of the item (weak indicator) following the body.
380+
Ok(body)
381+
if matches!(snapshot.token.kind, token::Semi | token::Eof)
382+
|| snapshot.token.can_begin_item() =>
383+
{
384+
type_err.cancel();
385+
386+
let body_sp = pred_lo.to(snapshot.prev_token.span);
387+
let map = self.sess.source_map();
388+
389+
self.sess.emit_err(WhereClauseBeforeTupleStructBody {
390+
span: where_sp,
391+
name: struct_name.span,
392+
body: body_sp,
393+
sugg: map.span_to_snippet(body_sp).ok().map(|body| {
394+
WhereClauseBeforeTupleStructBodySugg {
395+
left: body_insertion_point.shrink_to_hi(),
396+
snippet: body,
397+
right: map.end_point(where_sp).to(body_sp),
398+
}
399+
}),
400+
});
401+
402+
self.restore_snapshot(snapshot);
403+
Ok(PredicateOrStructBody::StructBody(body))
404+
}
405+
Ok(_) => Err(type_err),
406+
Err(body_err) => {
407+
body_err.cancel();
408+
Err(type_err)
409+
}
410+
}
411+
}
412+
}
315413
}
316414

317415
fn parse_ty_where_predicate(&mut self) -> PResult<'a, ast::WherePredicate> {

compiler/rustc_parse/src/parser/item.rs

+11-3
Original file line numberDiff line numberDiff line change
@@ -1454,8 +1454,16 @@ impl<'a> Parser<'a> {
14541454
// struct.
14551455

14561456
let vdata = if self.token.is_keyword(kw::Where) {
1457-
generics.where_clause = self.parse_where_clause()?;
1458-
if self.eat(&token::Semi) {
1457+
let tuple_struct_body;
1458+
(generics.where_clause, tuple_struct_body) =
1459+
self.parse_struct_where_clause(class_name, generics.span)?;
1460+
1461+
if let Some(body) = tuple_struct_body {
1462+
// If we see a misplaced tuple struct body: `struct Foo<T> where T: Copy, (T);`
1463+
let body = VariantData::Tuple(body, DUMMY_NODE_ID);
1464+
self.expect_semi()?;
1465+
body
1466+
} else if self.eat(&token::Semi) {
14591467
// If we see a: `struct Foo<T> where T: Copy;` style decl.
14601468
VariantData::Unit(DUMMY_NODE_ID)
14611469
} else {
@@ -1575,7 +1583,7 @@ impl<'a> Parser<'a> {
15751583
Ok((fields, recovered))
15761584
}
15771585

1578-
fn parse_tuple_struct_body(&mut self) -> PResult<'a, Vec<FieldDef>> {
1586+
pub(super) fn parse_tuple_struct_body(&mut self) -> PResult<'a, Vec<FieldDef>> {
15791587
// This is the case where we find `struct Foo<T>(T) where T: Copy;`
15801588
// Unit like structs are handled in parse_item_struct function
15811589
self.parse_paren_comma_seq(|p| {

tests/ui/parser/issues/issue-17904.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
// compile-flags: -Zparse-only
2+
13
struct Baz<U> where U: Eq(U); //This is parsed as the new Fn* style parenthesis syntax.
24
struct Baz<U> where U: Eq(U) -> R; // Notice this parses as well.
35
struct Baz<U>(U) where U: Eq; // This rightfully signals no error as well.
4-
struct Foo<T> where T: Copy, (T); //~ ERROR expected one of `:`, `==`, or `=`, found `;`
6+
struct Foo<T> where T: Copy, (T); //~ ERROR where clauses are not allowed before tuple struct bodies
57

68
fn main() {}
+12-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
1-
error: expected one of `:`, `==`, or `=`, found `;`
2-
--> $DIR/issue-17904.rs:4:33
1+
error: where clauses are not allowed before tuple struct bodies
2+
--> $DIR/issue-17904.rs:6:15
33
|
44
LL | struct Foo<T> where T: Copy, (T);
5-
| ^ expected one of `:`, `==`, or `=`
5+
| --- ^^^^^^^^^^^^^^ --- the struct body
6+
| | |
7+
| | unexpected where clause
8+
| while parsing this tuple struct
9+
|
10+
help: move the body before the where clause
11+
|
12+
LL - struct Foo<T> where T: Copy, (T);
13+
LL + struct Foo<T>(T) where T: Copy;
14+
|
615

716
error: aborting due to previous error
817

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Regression test for issues #100790 and #106439.
2+
// run-rustfix
3+
4+
pub struct Example(usize)
5+
where
6+
(): Sized;
7+
//~^^^ ERROR where clauses are not allowed before tuple struct bodies
8+
9+
struct _Demo(pub usize, usize)
10+
where
11+
(): Sized,
12+
String: Clone;
13+
//~^^^^ ERROR where clauses are not allowed before tuple struct bodies
14+
15+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Regression test for issues #100790 and #106439.
2+
// run-rustfix
3+
4+
pub struct Example
5+
where
6+
(): Sized,
7+
(usize);
8+
//~^^^ ERROR where clauses are not allowed before tuple struct bodies
9+
10+
struct _Demo
11+
where
12+
(): Sized,
13+
String: Clone,
14+
(pub usize, usize);
15+
//~^^^^ ERROR where clauses are not allowed before tuple struct bodies
16+
17+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
error: where clauses are not allowed before tuple struct bodies
2+
--> $DIR/recover-where-clause-before-tuple-struct-body-0.rs:5:1
3+
|
4+
LL | pub struct Example
5+
| ------- while parsing this tuple struct
6+
LL | / where
7+
LL | | (): Sized,
8+
| |______________^ unexpected where clause
9+
LL | (usize);
10+
| ------- the struct body
11+
|
12+
help: move the body before the where clause
13+
|
14+
LL ~ pub struct Example(usize)
15+
LL | where
16+
LL ~ (): Sized;
17+
|
18+
19+
error: where clauses are not allowed before tuple struct bodies
20+
--> $DIR/recover-where-clause-before-tuple-struct-body-0.rs:11:1
21+
|
22+
LL | struct _Demo
23+
| ----- while parsing this tuple struct
24+
LL | / where
25+
LL | | (): Sized,
26+
LL | | String: Clone,
27+
| |__________________^ unexpected where clause
28+
LL | (pub usize, usize);
29+
| ------------------ the struct body
30+
|
31+
help: move the body before the where clause
32+
|
33+
LL ~ struct _Demo(pub usize, usize)
34+
LL | where
35+
LL | (): Sized,
36+
LL ~ String: Clone;
37+
|
38+
39+
error: aborting due to 2 previous errors
40+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Regression test for issues #100790 and #106439.
2+
3+
// Make sure that we still show a helpful error message even if the trailing semicolon is missing.
4+
5+
struct Foo<T> where T: MyTrait, (T)
6+
//~^ ERROR where clauses are not allowed before tuple struct bodies
7+
//~| ERROR expected `;`, found `<eof>`
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
error: where clauses are not allowed before tuple struct bodies
2+
--> $DIR/recover-where-clause-before-tuple-struct-body-1.rs:5:15
3+
|
4+
LL | struct Foo<T> where T: MyTrait, (T)
5+
| --- ^^^^^^^^^^^^^^^^^ --- the struct body
6+
| | |
7+
| | unexpected where clause
8+
| while parsing this tuple struct
9+
|
10+
help: move the body before the where clause
11+
|
12+
LL - struct Foo<T> where T: MyTrait, (T)
13+
LL + struct Foo<T>(T) where T: MyTrait
14+
|
15+
16+
error: expected `;`, found `<eof>`
17+
--> $DIR/recover-where-clause-before-tuple-struct-body-1.rs:5:35
18+
|
19+
LL | struct Foo<T> where T: MyTrait, (T)
20+
| ^ expected `;`
21+
22+
error: aborting due to 2 previous errors
23+

0 commit comments

Comments
 (0)