diff --git a/compiler/rustc_parse/src/parser/generics.rs b/compiler/rustc_parse/src/parser/generics.rs index 419ea9cced0..62ed104aef3 100644 --- a/compiler/rustc_parse/src/parser/generics.rs +++ b/compiler/rustc_parse/src/parser/generics.rs @@ -4,7 +4,7 @@ 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 @@ pub(super) fn parse_where_clause(&mut self) -> PResult<'a, WhereClause> { 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 06849b31256..93f5d79c0db 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -1221,7 +1221,7 @@ fn parse_enum_variant(&mut self) -> PResult<'a, Option> { 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 @@ fn parse_item_struct(&mut self) -> PResult<'a, ItemInfo> { VariantData::Unit(DUMMY_NODE_ID) } else { // If we see: `struct Foo 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;` @@ -1283,7 +1284,8 @@ fn parse_item_struct(&mut self) -> PResult<'a, ItemInfo> { 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 @@ fn parse_item_union(&mut self) -> PResult<'a, ItemInfo> { 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 @@ fn parse_item_union(&mut self) -> PResult<'a, ItemInfo> { fn parse_record_struct_body( &mut self, adt_ty: &str, + parsed_where: bool, ) -> PResult<'a, (Vec, /* recovered */ bool)> { let mut fields = Vec::new(); let mut recovered = false; @@ -1353,9 +1358,19 @@ fn parse_record_struct_body( 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 00000000000..823880b1b42 --- /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 00000000000..bb79776dc84 --- /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 00000000000..9eb2ffb06f0 --- /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 00000000000..8250d4f1e05 --- /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 +