From f35d43cdf01f8405009ee6167ac2afe4e02ba51c Mon Sep 17 00:00:00 2001
From: Michael Goulet <michael@errs.io>
Date: Tue, 1 Feb 2022 21:44:44 -0800
Subject: [PATCH] better suggestion for duplicated `where`

---
 compiler/rustc_parse/src/parser/generics.rs   | 18 ++++-
 compiler/rustc_parse/src/parser/item.rs       | 29 +++++--
 .../ui/parser/bad-struct-following-where.rs   |  2 +
 .../parser/bad-struct-following-where.stderr  |  8 ++
 src/test/ui/parser/duplicate-where-clauses.rs | 19 +++++
 .../ui/parser/duplicate-where-clauses.stderr  | 80 +++++++++++++++++++
 6 files changed, 147 insertions(+), 9 deletions(-)
 create mode 100644 src/test/ui/parser/bad-struct-following-where.rs
 create mode 100644 src/test/ui/parser/bad-struct-following-where.stderr
 create mode 100644 src/test/ui/parser/duplicate-where-clauses.rs
 create mode 100644 src/test/ui/parser/duplicate-where-clauses.stderr

diff --git a/compiler/rustc_parse/src/parser/generics.rs b/compiler/rustc_parse/src/parser/generics.rs
index 419ea9cced0d7..62ed104aef37c 100644
--- a/compiler/rustc_parse/src/parser/generics.rs
+++ b/compiler/rustc_parse/src/parser/generics.rs
@@ -4,7 +4,7 @@ use rustc_ast::token;
 use rustc_ast::{
     self as ast, Attribute, GenericBounds, GenericParam, GenericParamKind, WhereClause,
 };
-use rustc_errors::PResult;
+use rustc_errors::{Applicability, PResult};
 use rustc_span::symbol::kw;
 
 impl<'a> Parser<'a> {
@@ -256,7 +256,21 @@ impl<'a> Parser<'a> {
                 break;
             }
 
-            if !self.eat(&token::Comma) {
+            let prev_token = self.prev_token.span;
+            let ate_comma = self.eat(&token::Comma);
+
+            if self.eat_keyword_noexpect(kw::Where) {
+                let msg = &format!("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_suggestion_verbose(
+                    prev_token.shrink_to_hi().to(self.prev_token.span),
+                    "consider joining the two `where` clauses into one",
+                    ",".to_owned(),
+                    Applicability::MaybeIncorrect,
+                );
+                err.emit();
+            } else if !ate_comma {
                 break;
             }
         }
diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs
index 06849b3125683..93f5d79c0db13 100644
--- a/compiler/rustc_parse/src/parser/item.rs
+++ b/compiler/rustc_parse/src/parser/item.rs
@@ -1221,7 +1221,7 @@ impl<'a> Parser<'a> {
 
                 let struct_def = if this.check(&token::OpenDelim(token::Brace)) {
                     // Parse a struct variant.
-                    let (fields, recovered) = this.parse_record_struct_body("struct")?;
+                    let (fields, recovered) = this.parse_record_struct_body("struct", false)?;
                     VariantData::Struct(fields, recovered)
                 } else if this.check(&token::OpenDelim(token::Paren)) {
                     VariantData::Tuple(this.parse_tuple_struct_body()?, DUMMY_NODE_ID)
@@ -1275,7 +1275,8 @@ impl<'a> Parser<'a> {
                 VariantData::Unit(DUMMY_NODE_ID)
             } else {
                 // If we see: `struct Foo<T> where T: Copy { ... }`
-                let (fields, recovered) = self.parse_record_struct_body("struct")?;
+                let (fields, recovered) =
+                    self.parse_record_struct_body("struct", generics.where_clause.has_where_token)?;
                 VariantData::Struct(fields, recovered)
             }
         // No `where` so: `struct Foo<T>;`
@@ -1283,7 +1284,8 @@ impl<'a> Parser<'a> {
             VariantData::Unit(DUMMY_NODE_ID)
         // Record-style struct definition
         } else if self.token == token::OpenDelim(token::Brace) {
-            let (fields, recovered) = self.parse_record_struct_body("struct")?;
+            let (fields, recovered) =
+                self.parse_record_struct_body("struct", generics.where_clause.has_where_token)?;
             VariantData::Struct(fields, recovered)
         // Tuple-style struct definition with optional where-clause.
         } else if self.token == token::OpenDelim(token::Paren) {
@@ -1313,10 +1315,12 @@ impl<'a> Parser<'a> {
 
         let vdata = if self.token.is_keyword(kw::Where) {
             generics.where_clause = self.parse_where_clause()?;
-            let (fields, recovered) = self.parse_record_struct_body("union")?;
+            let (fields, recovered) =
+                self.parse_record_struct_body("union", generics.where_clause.has_where_token)?;
             VariantData::Struct(fields, recovered)
         } else if self.token == token::OpenDelim(token::Brace) {
-            let (fields, recovered) = self.parse_record_struct_body("union")?;
+            let (fields, recovered) =
+                self.parse_record_struct_body("union", generics.where_clause.has_where_token)?;
             VariantData::Struct(fields, recovered)
         } else {
             let token_str = super::token_descr(&self.token);
@@ -1332,6 +1336,7 @@ impl<'a> Parser<'a> {
     fn parse_record_struct_body(
         &mut self,
         adt_ty: &str,
+        parsed_where: bool,
     ) -> PResult<'a, (Vec<FieldDef>, /* recovered */ bool)> {
         let mut fields = Vec::new();
         let mut recovered = false;
@@ -1353,9 +1358,19 @@ impl<'a> Parser<'a> {
             self.eat(&token::CloseDelim(token::Brace));
         } else {
             let token_str = super::token_descr(&self.token);
-            let msg = &format!("expected `where`, or `{{` after struct name, found {}", token_str);
+            let msg = &format!(
+                "expected {}`{{` after struct name, found {}",
+                if parsed_where { "" } else { "`where`, or " },
+                token_str
+            );
             let mut err = self.struct_span_err(self.token.span, msg);
-            err.span_label(self.token.span, "expected `where`, or `{` after struct name");
+            err.span_label(
+                self.token.span,
+                format!(
+                    "expected {}`{{` after struct name",
+                    if parsed_where { "" } else { "`where`, or " }
+                ),
+            );
             return Err(err);
         }
 
diff --git a/src/test/ui/parser/bad-struct-following-where.rs b/src/test/ui/parser/bad-struct-following-where.rs
new file mode 100644
index 0000000000000..823880b1b42c6
--- /dev/null
+++ b/src/test/ui/parser/bad-struct-following-where.rs
@@ -0,0 +1,2 @@
+struct A where T: Sized !
+//~^ ERROR expected `{` after struct name, found
diff --git a/src/test/ui/parser/bad-struct-following-where.stderr b/src/test/ui/parser/bad-struct-following-where.stderr
new file mode 100644
index 0000000000000..bb79776dc8459
--- /dev/null
+++ b/src/test/ui/parser/bad-struct-following-where.stderr
@@ -0,0 +1,8 @@
+error: expected `{` after struct name, found `!`
+  --> $DIR/bad-struct-following-where.rs:1:25
+   |
+LL | struct A where T: Sized !
+   |                         ^ expected `{` after struct name
+
+error: aborting due to previous error
+
diff --git a/src/test/ui/parser/duplicate-where-clauses.rs b/src/test/ui/parser/duplicate-where-clauses.rs
new file mode 100644
index 0000000000000..9eb2ffb06f02d
--- /dev/null
+++ b/src/test/ui/parser/duplicate-where-clauses.rs
@@ -0,0 +1,19 @@
+struct A where (): Sized where (): Sized {}
+//~^ ERROR cannot define duplicate `where` clauses on an item
+
+fn b() where (): Sized where (): Sized {}
+//~^ ERROR cannot define duplicate `where` clauses on an item
+
+enum C where (): Sized where (): Sized {}
+//~^ ERROR cannot define duplicate `where` clauses on an item
+
+struct D where (): Sized, where (): Sized {}
+//~^ ERROR cannot define duplicate `where` clauses on an item
+
+fn e() where (): Sized, where (): Sized {}
+//~^ ERROR cannot define duplicate `where` clauses on an item
+
+enum F where (): Sized, where (): Sized {}
+//~^ ERROR cannot define duplicate `where` clauses on an item
+
+fn main() {}
diff --git a/src/test/ui/parser/duplicate-where-clauses.stderr b/src/test/ui/parser/duplicate-where-clauses.stderr
new file mode 100644
index 0000000000000..8250d4f1e0568
--- /dev/null
+++ b/src/test/ui/parser/duplicate-where-clauses.stderr
@@ -0,0 +1,80 @@
+error: cannot define duplicate `where` clauses on an item
+  --> $DIR/duplicate-where-clauses.rs:1:32
+   |
+LL | struct A where (): Sized where (): Sized {}
+   |                -               ^
+   |                |
+   |                previous `where` clause starts here
+   |
+help: consider joining the two `where` clauses into one
+   |
+LL | struct A where (): Sized, (): Sized {}
+   |                         ~
+
+error: cannot define duplicate `where` clauses on an item
+  --> $DIR/duplicate-where-clauses.rs:4:30
+   |
+LL | fn b() where (): Sized where (): Sized {}
+   |              -               ^
+   |              |
+   |              previous `where` clause starts here
+   |
+help: consider joining the two `where` clauses into one
+   |
+LL | fn b() where (): Sized, (): Sized {}
+   |                       ~
+
+error: cannot define duplicate `where` clauses on an item
+  --> $DIR/duplicate-where-clauses.rs:7:30
+   |
+LL | enum C where (): Sized where (): Sized {}
+   |              -               ^
+   |              |
+   |              previous `where` clause starts here
+   |
+help: consider joining the two `where` clauses into one
+   |
+LL | enum C where (): Sized, (): Sized {}
+   |                       ~
+
+error: cannot define duplicate `where` clauses on an item
+  --> $DIR/duplicate-where-clauses.rs:10:33
+   |
+LL | struct D where (): Sized, where (): Sized {}
+   |                -                ^
+   |                |
+   |                previous `where` clause starts here
+   |
+help: consider joining the two `where` clauses into one
+   |
+LL | struct D where (): Sized, (): Sized {}
+   |                         ~
+
+error: cannot define duplicate `where` clauses on an item
+  --> $DIR/duplicate-where-clauses.rs:13:31
+   |
+LL | fn e() where (): Sized, where (): Sized {}
+   |              -                ^
+   |              |
+   |              previous `where` clause starts here
+   |
+help: consider joining the two `where` clauses into one
+   |
+LL | fn e() where (): Sized, (): Sized {}
+   |                       ~
+
+error: cannot define duplicate `where` clauses on an item
+  --> $DIR/duplicate-where-clauses.rs:16:31
+   |
+LL | enum F where (): Sized, where (): Sized {}
+   |              -                ^
+   |              |
+   |              previous `where` clause starts here
+   |
+help: consider joining the two `where` clauses into one
+   |
+LL | enum F where (): Sized, (): Sized {}
+   |                       ~
+
+error: aborting due to 6 previous errors
+