Skip to content
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

Recover from where clauses placed before tuple struct bodies #106537

Merged
Merged
Show file tree
Hide file tree
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
6 changes: 6 additions & 0 deletions compiler/rustc_error_messages/locales/en-US/parse.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -368,3 +368,9 @@ parse_maybe_fn_typo_with_impl = you might have meant to write `impl` instead of

parse_expected_fn_path_found_fn_keyword = expected identifier, found keyword `fn`
.suggestion = use `Fn` to refer to the trait

parse_where_clause_before_tuple_struct_body = where clauses are not allowed before tuple struct bodies
.label = unexpected where clause
.name_label = while parsing this tuple struct
.body_label = the struct body
.suggestion = move the body before the where clause
24 changes: 24 additions & 0 deletions compiler/rustc_parse/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1237,3 +1237,27 @@ pub(crate) struct ExpectedFnPathFoundFnKeyword {
#[suggestion(applicability = "machine-applicable", code = "Fn", style = "verbose")]
pub fn_token_span: Span,
}

#[derive(Diagnostic)]
#[diag(parse_where_clause_before_tuple_struct_body)]
pub(crate) struct WhereClauseBeforeTupleStructBody {
#[primary_span]
#[label]
pub span: Span,
#[label(name_label)]
pub name: Span,
#[label(body_label)]
pub body: Span,
#[subdiagnostic]
pub sugg: Option<WhereClauseBeforeTupleStructBodySugg>,
}

#[derive(Subdiagnostic)]
#[multipart_suggestion(suggestion, applicability = "machine-applicable")]
pub(crate) struct WhereClauseBeforeTupleStructBodySugg {
#[suggestion_part(code = "{snippet}")]
pub left: Span,
pub snippet: String,
#[suggestion_part(code = "")]
pub right: Span,
}
118 changes: 108 additions & 10 deletions compiler/rustc_parse/src/parser/generics.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
use crate::errors::{WhereClauseBeforeTupleStructBody, WhereClauseBeforeTupleStructBodySugg};

use super::{ForceCollect, Parser, TrailingToken};

use ast::token::Delimiter;
use rustc_ast::token;
use rustc_ast::{
self as ast, AttrVec, GenericBounds, GenericParam, GenericParamKind, TyKind, WhereClause,
};
use rustc_errors::{Applicability, PResult};
use rustc_span::symbol::kw;
use rustc_span::symbol::{kw, Ident};
use rustc_span::Span;

enum PredicateOrStructBody {
Predicate(ast::WherePredicate),
StructBody(Vec<ast::FieldDef>),
}

impl<'a> Parser<'a> {
/// Parses bounds of a lifetime parameter `BOUND + BOUND + BOUND`, possibly with trailing `+`.
Expand Down Expand Up @@ -240,23 +249,39 @@ impl<'a> Parser<'a> {
})
}

/// Parses an optional where-clause and places it in `generics`.
/// Parses an optional where-clause.
///
/// ```ignore (only-for-syntax-highlight)
/// where T : Trait<U, V> + 'b, 'a : 'b
/// ```
pub(super) fn parse_where_clause(&mut self) -> PResult<'a, WhereClause> {
self.parse_where_clause_common(None).map(|(clause, _)| clause)
}

pub(super) fn parse_struct_where_clause(
&mut self,
struct_name: Ident,
body_insertion_point: Span,
) -> PResult<'a, (WhereClause, Option<Vec<ast::FieldDef>>)> {
self.parse_where_clause_common(Some((struct_name, body_insertion_point)))
}

fn parse_where_clause_common(
&mut self,
struct_: Option<(Ident, Span)>,
) -> PResult<'a, (WhereClause, Option<Vec<ast::FieldDef>>)> {
let mut where_clause = WhereClause {
has_where_token: false,
predicates: Vec::new(),
span: self.prev_token.span.shrink_to_hi(),
};
let mut tuple_struct_body = None;

if !self.eat_keyword(kw::Where) {
return Ok(where_clause);
return Ok((where_clause, None));
}
where_clause.has_where_token = true;
let lo = self.prev_token.span;
let where_lo = self.prev_token.span;

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

loop {
let lo = self.token.span;
let where_sp = where_lo.to(self.prev_token.span);
let pred_lo = self.token.span;
if self.check_lifetime() && self.look_ahead(1, |t| !t.is_like_plus()) {
let lifetime = self.expect_lifetime();
// Bounds starting with a colon are mandatory, but possibly empty.
self.expect(&token::Colon)?;
let bounds = self.parse_lt_param_bounds();
where_clause.predicates.push(ast::WherePredicate::RegionPredicate(
ast::WhereRegionPredicate {
span: lo.to(self.prev_token.span),
span: pred_lo.to(self.prev_token.span),
lifetime,
bounds,
},
));
} else if self.check_type() {
where_clause.predicates.push(self.parse_ty_where_predicate()?);
match self.parse_ty_where_predicate_or_recover_tuple_struct_body(
struct_, pred_lo, where_sp,
)? {
PredicateOrStructBody::Predicate(pred) => where_clause.predicates.push(pred),
PredicateOrStructBody::StructBody(body) => {
tuple_struct_body = Some(body);
break;
}
}
} else {
break;
}
Expand All @@ -297,7 +331,7 @@ impl<'a> Parser<'a> {
if self.eat_keyword_noexpect(kw::Where) {
let msg = "cannot define duplicate `where` clauses on an item";
let mut err = self.struct_span_err(self.token.span, msg);
err.span_label(lo, "previous `where` clause starts here");
err.span_label(pred_lo, "previous `where` clause starts here");
err.span_suggestion_verbose(
prev_token.shrink_to_hi().to(self.prev_token.span),
"consider joining the two `where` clauses into one",
Expand All @@ -310,8 +344,72 @@ impl<'a> Parser<'a> {
}
}

where_clause.span = lo.to(self.prev_token.span);
Ok(where_clause)
where_clause.span = where_lo.to(self.prev_token.span);
Ok((where_clause, tuple_struct_body))
}

fn parse_ty_where_predicate_or_recover_tuple_struct_body(
&mut self,
struct_: Option<(Ident, Span)>,
pred_lo: Span,
where_sp: Span,
) -> PResult<'a, PredicateOrStructBody> {
let mut snapshot = None;

if let Some(struct_) = struct_
&& self.may_recover()
&& self.token.kind == token::OpenDelim(Delimiter::Parenthesis)
{
snapshot = Some((struct_, self.create_snapshot_for_diagnostic()));
};

match self.parse_ty_where_predicate() {
Ok(pred) => Ok(PredicateOrStructBody::Predicate(pred)),
Err(type_err) => {
let Some(((struct_name, body_insertion_point), mut snapshot)) = snapshot else {
return Err(type_err);
};

// Check if we might have encountered an out of place tuple struct body.
match snapshot.parse_tuple_struct_body() {
// Since we don't know the exact reason why we failed to parse the
// predicate (we might have stumbled upon something bogus like `(T): ?`),
// employ a simple heuristic to weed out some pathological cases:
// Look for a semicolon (strong indicator) or anything that might mark
// the end of the item (weak indicator) following the body.
Ok(body)
if matches!(snapshot.token.kind, token::Semi | token::Eof)
|| snapshot.token.can_begin_item() =>
{
type_err.cancel();

let body_sp = pred_lo.to(snapshot.prev_token.span);
let map = self.sess.source_map();

self.sess.emit_err(WhereClauseBeforeTupleStructBody {
span: where_sp,
name: struct_name.span,
body: body_sp,
sugg: map.span_to_snippet(body_sp).ok().map(|body| {
WhereClauseBeforeTupleStructBodySugg {
left: body_insertion_point.shrink_to_hi(),
snippet: body,
right: map.end_point(where_sp).to(body_sp),
}
}),
});

self.restore_snapshot(snapshot);
Ok(PredicateOrStructBody::StructBody(body))
}
Ok(_) => Err(type_err),
Err(body_err) => {
body_err.cancel();
Err(type_err)
}
}
}
}
}

fn parse_ty_where_predicate(&mut self) -> PResult<'a, ast::WherePredicate> {
Expand Down
14 changes: 11 additions & 3 deletions compiler/rustc_parse/src/parser/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1454,8 +1454,16 @@ impl<'a> Parser<'a> {
// struct.

let vdata = if self.token.is_keyword(kw::Where) {
generics.where_clause = self.parse_where_clause()?;
if self.eat(&token::Semi) {
let tuple_struct_body;
(generics.where_clause, tuple_struct_body) =
self.parse_struct_where_clause(class_name, generics.span)?;

if let Some(body) = tuple_struct_body {
// If we see a misplaced tuple struct body: `struct Foo<T> where T: Copy, (T);`
let body = VariantData::Tuple(body, DUMMY_NODE_ID);
self.expect_semi()?;
body
} else if self.eat(&token::Semi) {
// If we see a: `struct Foo<T> where T: Copy;` style decl.
VariantData::Unit(DUMMY_NODE_ID)
} else {
Expand Down Expand Up @@ -1575,7 +1583,7 @@ impl<'a> Parser<'a> {
Ok((fields, recovered))
}

fn parse_tuple_struct_body(&mut self) -> PResult<'a, Vec<FieldDef>> {
pub(super) fn parse_tuple_struct_body(&mut self) -> PResult<'a, Vec<FieldDef>> {
// This is the case where we find `struct Foo<T>(T) where T: Copy;`
// Unit like structs are handled in parse_item_struct function
self.parse_paren_comma_seq(|p| {
Expand Down
4 changes: 3 additions & 1 deletion tests/ui/parser/issues/issue-17904.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// compile-flags: -Zparse-only

struct Baz<U> where U: Eq(U); //This is parsed as the new Fn* style parenthesis syntax.
struct Baz<U> where U: Eq(U) -> R; // Notice this parses as well.
struct Baz<U>(U) where U: Eq; // This rightfully signals no error as well.
struct Foo<T> where T: Copy, (T); //~ ERROR expected one of `:`, `==`, or `=`, found `;`
struct Foo<T> where T: Copy, (T); //~ ERROR where clauses are not allowed before tuple struct bodies

fn main() {}
15 changes: 12 additions & 3 deletions tests/ui/parser/issues/issue-17904.stderr
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
error: expected one of `:`, `==`, or `=`, found `;`
--> $DIR/issue-17904.rs:4:33
error: where clauses are not allowed before tuple struct bodies
--> $DIR/issue-17904.rs:6:15
|
LL | struct Foo<T> where T: Copy, (T);
| ^ expected one of `:`, `==`, or `=`
| --- ^^^^^^^^^^^^^^ --- the struct body
| | |
| | unexpected where clause
| while parsing this tuple struct
|
help: move the body before the where clause
|
LL - struct Foo<T> where T: Copy, (T);
LL + struct Foo<T>(T) where T: Copy;
|

error: aborting due to previous error

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Regression test for issues #100790 and #106439.
// run-rustfix

pub struct Example(usize)
where
(): Sized;
//~^^^ ERROR where clauses are not allowed before tuple struct bodies

struct _Demo(pub usize, usize)
where
(): Sized,
String: Clone;
//~^^^^ ERROR where clauses are not allowed before tuple struct bodies

fn main() {}
17 changes: 17 additions & 0 deletions tests/ui/parser/recover-where-clause-before-tuple-struct-body-0.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Regression test for issues #100790 and #106439.
// run-rustfix

pub struct Example
where
(): Sized,
(usize);
//~^^^ ERROR where clauses are not allowed before tuple struct bodies

struct _Demo
where
(): Sized,
String: Clone,
(pub usize, usize);
//~^^^^ ERROR where clauses are not allowed before tuple struct bodies

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
error: where clauses are not allowed before tuple struct bodies
--> $DIR/recover-where-clause-before-tuple-struct-body-0.rs:5:1
|
LL | pub struct Example
| ------- while parsing this tuple struct
LL | / where
LL | | (): Sized,
| |______________^ unexpected where clause
LL | (usize);
| ------- the struct body
|
help: move the body before the where clause
|
LL ~ pub struct Example(usize)
LL | where
LL ~ (): Sized;
|

error: where clauses are not allowed before tuple struct bodies
--> $DIR/recover-where-clause-before-tuple-struct-body-0.rs:11:1
|
LL | struct _Demo
| ----- while parsing this tuple struct
LL | / where
LL | | (): Sized,
LL | | String: Clone,
| |__________________^ unexpected where clause
LL | (pub usize, usize);
| ------------------ the struct body
|
help: move the body before the where clause
|
LL ~ struct _Demo(pub usize, usize)
LL | where
LL | (): Sized,
LL ~ String: Clone;
|

error: aborting due to 2 previous errors

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Regression test for issues #100790 and #106439.

// Make sure that we still show a helpful error message even if the trailing semicolon is missing.

struct Foo<T> where T: MyTrait, (T)
//~^ ERROR where clauses are not allowed before tuple struct bodies
//~| ERROR expected `;`, found `<eof>`
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
error: where clauses are not allowed before tuple struct bodies
--> $DIR/recover-where-clause-before-tuple-struct-body-1.rs:5:15
|
LL | struct Foo<T> where T: MyTrait, (T)
| --- ^^^^^^^^^^^^^^^^^ --- the struct body
| | |
| | unexpected where clause
| while parsing this tuple struct
|
help: move the body before the where clause
|
LL - struct Foo<T> where T: MyTrait, (T)
LL + struct Foo<T>(T) where T: MyTrait
|

error: expected `;`, found `<eof>`
--> $DIR/recover-where-clause-before-tuple-struct-body-1.rs:5:35
|
LL | struct Foo<T> where T: MyTrait, (T)
| ^ expected `;`

error: aborting due to 2 previous errors