diff --git a/compiler/rustc_error_messages/locales/en-US/parse.ftl b/compiler/rustc_error_messages/locales/en-US/parse.ftl index 3401978caf5f0..ecb05162f95ed 100644 --- a/compiler/rustc_error_messages/locales/en-US/parse.ftl +++ b/compiler/rustc_error_messages/locales/en-US/parse.ftl @@ -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 diff --git a/compiler/rustc_parse/src/errors.rs b/compiler/rustc_parse/src/errors.rs index 574591529f331..3fdb2177ab0e0 100644 --- a/compiler/rustc_parse/src/errors.rs +++ b/compiler/rustc_parse/src/errors.rs @@ -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, +} + +#[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, +} diff --git a/compiler/rustc_parse/src/parser/generics.rs b/compiler/rustc_parse/src/parser/generics.rs index fa75670b2ed82..8ba811715d80d 100644 --- a/compiler/rustc_parse/src/parser/generics.rs +++ b/compiler/rustc_parse/src/parser/generics.rs @@ -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), +} impl<'a> Parser<'a> { /// Parses bounds of a lifetime parameter `BOUND + BOUND + BOUND`, possibly with trailing `+`. @@ -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 + '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>)> { + 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>)> { 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`. To avoid that being a breaking @@ -272,7 +297,8 @@ 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. @@ -280,13 +306,21 @@ impl<'a> Parser<'a> { 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; } @@ -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", @@ -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> { diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index a958c294930ef..5c6650acd8a14 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -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 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 where T: Copy;` style decl. VariantData::Unit(DUMMY_NODE_ID) } else { @@ -1575,7 +1583,7 @@ impl<'a> Parser<'a> { Ok((fields, recovered)) } - fn parse_tuple_struct_body(&mut self) -> PResult<'a, Vec> { + pub(super) fn parse_tuple_struct_body(&mut self) -> PResult<'a, Vec> { // This is the case where we find `struct Foo(T) where T: Copy;` // Unit like structs are handled in parse_item_struct function self.parse_paren_comma_seq(|p| { diff --git a/tests/ui/parser/issues/issue-17904.rs b/tests/ui/parser/issues/issue-17904.rs index 7d6a54f4be12e..020fb41c22738 100644 --- a/tests/ui/parser/issues/issue-17904.rs +++ b/tests/ui/parser/issues/issue-17904.rs @@ -1,6 +1,8 @@ +// compile-flags: -Zparse-only + struct Baz where U: Eq(U); //This is parsed as the new Fn* style parenthesis syntax. struct Baz where U: Eq(U) -> R; // Notice this parses as well. struct Baz(U) where U: Eq; // This rightfully signals no error as well. -struct Foo where T: Copy, (T); //~ ERROR expected one of `:`, `==`, or `=`, found `;` +struct Foo where T: Copy, (T); //~ ERROR where clauses are not allowed before tuple struct bodies fn main() {} diff --git a/tests/ui/parser/issues/issue-17904.stderr b/tests/ui/parser/issues/issue-17904.stderr index a3cac676189c0..aa343975dcac5 100644 --- a/tests/ui/parser/issues/issue-17904.stderr +++ b/tests/ui/parser/issues/issue-17904.stderr @@ -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 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 where T: Copy, (T); +LL + struct Foo(T) where T: Copy; + | error: aborting due to previous error diff --git a/tests/ui/parser/recover-where-clause-before-tuple-struct-body-0.fixed b/tests/ui/parser/recover-where-clause-before-tuple-struct-body-0.fixed new file mode 100644 index 0000000000000..227c40e97c0a3 --- /dev/null +++ b/tests/ui/parser/recover-where-clause-before-tuple-struct-body-0.fixed @@ -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() {} diff --git a/tests/ui/parser/recover-where-clause-before-tuple-struct-body-0.rs b/tests/ui/parser/recover-where-clause-before-tuple-struct-body-0.rs new file mode 100644 index 0000000000000..3699e6fe5723f --- /dev/null +++ b/tests/ui/parser/recover-where-clause-before-tuple-struct-body-0.rs @@ -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() {} diff --git a/tests/ui/parser/recover-where-clause-before-tuple-struct-body-0.stderr b/tests/ui/parser/recover-where-clause-before-tuple-struct-body-0.stderr new file mode 100644 index 0000000000000..18aa5fadb6bc7 --- /dev/null +++ b/tests/ui/parser/recover-where-clause-before-tuple-struct-body-0.stderr @@ -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 + diff --git a/tests/ui/parser/recover-where-clause-before-tuple-struct-body-1.rs b/tests/ui/parser/recover-where-clause-before-tuple-struct-body-1.rs new file mode 100644 index 0000000000000..f515ae81e5106 --- /dev/null +++ b/tests/ui/parser/recover-where-clause-before-tuple-struct-body-1.rs @@ -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 where T: MyTrait, (T) +//~^ ERROR where clauses are not allowed before tuple struct bodies +//~| ERROR expected `;`, found `` diff --git a/tests/ui/parser/recover-where-clause-before-tuple-struct-body-1.stderr b/tests/ui/parser/recover-where-clause-before-tuple-struct-body-1.stderr new file mode 100644 index 0000000000000..2219c2a731630 --- /dev/null +++ b/tests/ui/parser/recover-where-clause-before-tuple-struct-body-1.stderr @@ -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 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 where T: MyTrait, (T) +LL + struct Foo(T) where T: MyTrait + | + +error: expected `;`, found `` + --> $DIR/recover-where-clause-before-tuple-struct-body-1.rs:5:35 + | +LL | struct Foo where T: MyTrait, (T) + | ^ expected `;` + +error: aborting due to 2 previous errors +