about summary refs log tree commit diff
diff options
context:
space:
mode:
authorLeón Orell Valerian Liehr <liehr.exchange@gmx.net>2023-01-06 17:47:21 +0100
committerLeón Orell Valerian Liehr <liehr.exchange@gmx.net>2023-01-11 17:54:48 +0100
commit70ddde76dfbb03909c565b329f86e49de1f767cc (patch)
tree70f3b7a49c33c052749413d30547e52dd913a7b2
parentb22c152958eade17a71d899b29a2d39bcc77aa48 (diff)
downloadrust-70ddde76dfbb03909c565b329f86e49de1f767cc.tar.gz
rust-70ddde76dfbb03909c565b329f86e49de1f767cc.zip
parser: recover from where clauses placed before tuple struct bodies
-rw-r--r--compiler/rustc_error_messages/locales/en-US/parse.ftl6
-rw-r--r--compiler/rustc_parse/src/errors.rs24
-rw-r--r--compiler/rustc_parse/src/parser/generics.rs118
-rw-r--r--compiler/rustc_parse/src/parser/item.rs14
-rw-r--r--tests/ui/parser/issues/issue-17904.rs4
-rw-r--r--tests/ui/parser/issues/issue-17904.stderr15
-rw-r--r--tests/ui/parser/recover-where-clause-before-tuple-struct-body-0.fixed15
-rw-r--r--tests/ui/parser/recover-where-clause-before-tuple-struct-body-0.rs17
-rw-r--r--tests/ui/parser/recover-where-clause-before-tuple-struct-body-0.stderr40
-rw-r--r--tests/ui/parser/recover-where-clause-before-tuple-struct-body-1.rs7
-rw-r--r--tests/ui/parser/recover-where-clause-before-tuple-struct-body-1.stderr23
11 files changed, 266 insertions, 17 deletions
diff --git a/compiler/rustc_error_messages/locales/en-US/parse.ftl b/compiler/rustc_error_messages/locales/en-US/parse.ftl
index 3401978caf5..ecb05162f95 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 574591529f3..3fdb2177ab0 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<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,
+}
diff --git a/compiler/rustc_parse/src/parser/generics.rs b/compiler/rustc_parse/src/parser/generics.rs
index fa75670b2ed..8ba811715d8 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<ast::FieldDef>),
+}
 
 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<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
@@ -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 a958c294930..5c6650acd8a 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<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 {
@@ -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| {
diff --git a/tests/ui/parser/issues/issue-17904.rs b/tests/ui/parser/issues/issue-17904.rs
index 7d6a54f4be1..020fb41c227 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<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() {}
diff --git a/tests/ui/parser/issues/issue-17904.stderr b/tests/ui/parser/issues/issue-17904.stderr
index a3cac676189..aa343975dca 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<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
 
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 00000000000..227c40e97c0
--- /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 00000000000..3699e6fe572
--- /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 00000000000..18aa5fadb6b
--- /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 00000000000..f515ae81e51
--- /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<T> where T: MyTrait, (T)
+//~^ ERROR where clauses are not allowed before tuple struct bodies
+//~| ERROR expected `;`, found `<eof>`
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 00000000000..2219c2a7316
--- /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<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
+