diff --git a/src/libsyntax/parse/parser.rs b/src/libsyntax/parse/parser.rs index c7e33a1656428..955dce47721e2 100644 --- a/src/libsyntax/parse/parser.rs +++ b/src/libsyntax/parse/parser.rs @@ -5530,22 +5530,31 @@ impl<'a> Parser<'a> { fn parse_generic_args(&mut self) -> PResult<'a, (Vec, Vec)> { let mut args = Vec::new(); let mut bindings = Vec::new(); + let mut seen_type = false; let mut seen_binding = false; + + let mut last_comma_span = None; let mut first_type_or_binding_span: Option = None; + let mut first_binding_span: Option = None; + let mut bad_lifetime_pos = vec![]; - let mut last_comma_span = None; - let mut suggestions = vec![]; + let mut bad_type_pos = vec![]; + + let mut lifetime_suggestions = vec![]; + let mut type_suggestions = vec![]; loop { if self.check_lifetime() && self.look_ahead(1, |t| !t.is_like_plus()) { // Parse lifetime argument. args.push(GenericArg::Lifetime(self.expect_lifetime())); + if seen_type || seen_binding { let remove_sp = last_comma_span.unwrap_or(self.prev_span).to(self.prev_span); bad_lifetime_pos.push(self.prev_span); + if let Ok(snippet) = self.sess.source_map().span_to_snippet(self.prev_span) { - suggestions.push((remove_sp, String::new())); - suggestions.push(( + lifetime_suggestions.push((remove_sp, String::new())); + lifetime_suggestions.push(( first_type_or_binding_span.unwrap().shrink_to_lo(), format!("{}, ", snippet))); } @@ -5563,24 +5572,29 @@ impl<'a> Parser<'a> { ty, span, }); + seen_binding = true; if first_type_or_binding_span.is_none() { first_type_or_binding_span = Some(span); } + if first_binding_span.is_none() { + first_binding_span = Some(span); + } } else if self.check_type() { // Parse type argument. let ty_param = self.parse_ty()?; if seen_binding { - self.struct_span_err( - ty_param.span, - "type parameters must be declared prior to associated type bindings" - ) - .span_label( - ty_param.span, - "must be declared prior to associated type bindings", - ) - .emit(); + let remove_sp = last_comma_span.unwrap_or(self.prev_span).to(self.prev_span); + bad_type_pos.push(self.prev_span); + + if let Ok(snippet) = self.sess.source_map().span_to_snippet(self.prev_span) { + type_suggestions.push((remove_sp, String::new())); + type_suggestions.push(( + first_binding_span.unwrap().shrink_to_lo(), + format!("{}, ", snippet))); + } } + if first_type_or_binding_span.is_none() { first_type_or_binding_span = Some(ty_param.span); } @@ -5596,27 +5610,93 @@ impl<'a> Parser<'a> { last_comma_span = Some(self.prev_span); } } - if !bad_lifetime_pos.is_empty() { - let mut err = self.struct_span_err( + + self.maybe_report_incorrect_generic_argument_order( + bad_lifetime_pos, bad_type_pos, lifetime_suggestions, type_suggestions + ); + + Ok((args, bindings)) + } + + /// Maybe report an error about incorrect generic argument order - "lifetime parameters + /// must be declared before type parameters", "type parameters must be declared before + /// associated type bindings" or both. + fn maybe_report_incorrect_generic_argument_order( + &self, + bad_lifetime_pos: Vec, + bad_type_pos: Vec, + lifetime_suggestions: Vec<(Span, String)>, + type_suggestions: Vec<(Span, String)>, + ) { + let mut err = if !bad_lifetime_pos.is_empty() && !bad_type_pos.is_empty() { + let mut positions = bad_lifetime_pos.clone(); + positions.extend_from_slice(&bad_type_pos); + + self.struct_span_err( + positions, + "generic arguments must declare lifetimes, types and associated type bindings in \ + that order", + ) + } else if !bad_lifetime_pos.is_empty() { + self.struct_span_err( bad_lifetime_pos.clone(), "lifetime parameters must be declared prior to type parameters" - ); + ) + } else if !bad_type_pos.is_empty() { + self.struct_span_err( + bad_type_pos.clone(), + "type parameters must be declared prior to associated type bindings" + ) + } else { + return; + }; + + if !bad_lifetime_pos.is_empty() { for sp in &bad_lifetime_pos { err.span_label(*sp, "must be declared prior to type parameters"); } - if !suggestions.is_empty() { - err.multipart_suggestion_with_applicability( - &format!( - "move the lifetime parameter{} prior to the first type parameter", - if bad_lifetime_pos.len() > 1 { "s" } else { "" }, - ), - suggestions, - Applicability::MachineApplicable, - ); + } + + if !bad_type_pos.is_empty() { + for sp in &bad_type_pos { + err.span_label(*sp, "must be declared prior to associated type bindings"); } - err.emit(); } - Ok((args, bindings)) + + if !lifetime_suggestions.is_empty() && !type_suggestions.is_empty() { + let mut suggestions = lifetime_suggestions; + suggestions.extend_from_slice(&type_suggestions); + + let plural = bad_lifetime_pos.len() + bad_type_pos.len() > 1; + err.multipart_suggestion_with_applicability( + &format!( + "move the parameter{}", + if plural { "s" } else { "" }, + ), + suggestions, + Applicability::MachineApplicable, + ); + } else if !lifetime_suggestions.is_empty() { + err.multipart_suggestion_with_applicability( + &format!( + "move the lifetime parameter{} prior to the first type parameter", + if bad_lifetime_pos.len() > 1 { "s" } else { "" }, + ), + lifetime_suggestions, + Applicability::MachineApplicable, + ); + } else if !type_suggestions.is_empty() { + err.multipart_suggestion_with_applicability( + &format!( + "move the type parameter{} prior to the first associated type binding", + if bad_type_pos.len() > 1 { "s" } else { "" }, + ), + type_suggestions, + Applicability::MachineApplicable, + ); + } + + err.emit(); } /// Parses an optional `where` clause and places it in `generics`. diff --git a/src/test/ui/parser/issue-32214.stderr b/src/test/ui/parser/issue-32214.stderr index a889513eaee52..660e517c85a13 100644 --- a/src/test/ui/parser/issue-32214.stderr +++ b/src/test/ui/parser/issue-32214.stderr @@ -3,6 +3,10 @@ error: type parameters must be declared prior to associated type bindings | LL | pub fn test >() {} | ^ must be declared prior to associated type bindings +help: move the type parameter prior to the first associated type binding + | +LL | pub fn test >() {} + | ^^ -- error: aborting due to previous error diff --git a/src/test/ui/suggestions/suggest-move-types.rs b/src/test/ui/suggestions/suggest-move-types.rs new file mode 100644 index 0000000000000..fd10ba4350c4a --- /dev/null +++ b/src/test/ui/suggestions/suggest-move-types.rs @@ -0,0 +1,85 @@ +// ignore-tidy-linelength + +#![allow(warnings)] + +// This test verifies that the suggestion to move types before associated type bindings +// is correct. + +trait One { + type A; +} + +trait OneWithLifetime<'a, T> { + type A; +} + +trait Three { + type A; + type B; + type C; +} + +trait ThreeWithLifetime<'a, 'b, 'c, T, U, V> { + type A; + type B; + type C; +} + +struct A> { //~ ERROR type parameters must be declared + m: M, + t: T, +} + + +struct Al<'a, T, M: OneWithLifetime> { +//~^ ERROR generic arguments must declare lifetimes, types and associated type bindings in that order + m: M, + t: &'a T, +} + +struct B> { //~ ERROR type parameters must be declared + m: M, + t: T, + u: U, + v: V, +} + +struct Bl<'a, 'b, 'c, T, U, V, M: ThreeWithLifetime> { +//~^ ERROR generic arguments must declare lifetimes, types and associated type bindings in that order + m: M, + t: &'a T, + u: &'b U, + v: &'c V, +} + +struct C> { //~ ERROR type parameters must be declared + m: M, + t: T, + u: U, + v: V, +} + +struct Cl<'a, 'b, 'c, T, U, V, M: ThreeWithLifetime> { +//~^ ERROR generic arguments must declare lifetimes, types and associated type bindings in that order + m: M, + t: &'a T, + u: &'b U, + v: &'c V, +} + +struct D> { //~ ERROR type parameters must be declared + m: M, + t: T, + u: U, + v: V, +} + +struct Dl<'a, 'b, 'c, T, U, V, M: ThreeWithLifetime> { +//~^ ERROR generic arguments must declare lifetimes, types and associated type bindings in that order + m: M, + t: &'a T, + u: &'b U, + v: &'c V, +} + +fn main() {} diff --git a/src/test/ui/suggestions/suggest-move-types.stderr b/src/test/ui/suggestions/suggest-move-types.stderr new file mode 100644 index 0000000000000..3643d9a912455 --- /dev/null +++ b/src/test/ui/suggestions/suggest-move-types.stderr @@ -0,0 +1,107 @@ +error: type parameters must be declared prior to associated type bindings + --> $DIR/suggest-move-types.rs:28:26 + | +LL | struct A> { //~ ERROR type parameters must be declared + | ^ must be declared prior to associated type bindings +help: move the type parameter prior to the first associated type binding + | +LL | struct A> { //~ ERROR type parameters must be declared + | ^^ -- + +error: generic arguments must declare lifetimes, types and associated type bindings in that order + --> $DIR/suggest-move-types.rs:34:46 + | +LL | struct Al<'a, T, M: OneWithLifetime> { + | ^ ^^ must be declared prior to type parameters + | | + | must be declared prior to associated type bindings +help: move the parameters + | +LL | struct Al<'a, T, M: OneWithLifetime<'a, T, A=()>> { + | ^^^ ^^ -- + +error: type parameters must be declared prior to associated type bindings + --> $DIR/suggest-move-types.rs:40:46 + | +LL | struct B> { //~ ERROR type parameters must be declared + | ^ ^ ^ must be declared prior to associated type bindings + | | | + | | must be declared prior to associated type bindings + | must be declared prior to associated type bindings +help: move the type parameters prior to the first associated type binding + | +LL | struct B> { //~ ERROR type parameters must be declared + | ^^ ^^ ^^ -- + +error: generic arguments must declare lifetimes, types and associated type bindings in that order + --> $DIR/suggest-move-types.rs:47:80 + | +LL | struct Bl<'a, 'b, 'c, T, U, V, M: ThreeWithLifetime> { + | ^ ^ ^ ^^ ^^ ^^ must be declared prior to type parameters + | | | | | | + | | | | | must be declared prior to type parameters + | | | | must be declared prior to type parameters + | | | must be declared prior to associated type bindings + | | must be declared prior to associated type bindings + | must be declared prior to associated type bindings +help: move the parameters + | +LL | struct Bl<'a, 'b, 'c, T, U, V, M: ThreeWithLifetime<'a, 'b, 'c, T, U, V, A=(), B=(), C=()>> { + | ^^^ ^^^ ^^^ ^^ ^^ ^^ -- + +error: type parameters must be declared prior to associated type bindings + --> $DIR/suggest-move-types.rs:55:49 + | +LL | struct C> { //~ ERROR type parameters must be declared + | ^ ^ must be declared prior to associated type bindings + | | + | must be declared prior to associated type bindings +help: move the type parameters prior to the first associated type binding + | +LL | struct C> { //~ ERROR type parameters must be declared + | ^^ ^^ -- + +error: generic arguments must declare lifetimes, types and associated type bindings in that order + --> $DIR/suggest-move-types.rs:62:56 + | +LL | struct Cl<'a, 'b, 'c, T, U, V, M: ThreeWithLifetime> { + | ^^ ^ ^^ ^ ^^ must be declared prior to type parameters + | | | | | + | | | | must be declared prior to associated type bindings + | | | must be declared prior to type parameters + | | must be declared prior to associated type bindings + | must be declared prior to type parameters +help: move the parameters + | +LL | struct Cl<'a, 'b, 'c, T, U, V, M: ThreeWithLifetime<'a, 'b, 'c, T, U, V, A=(), B=(), C=()>> { + | ^^^ ^^^ ^^^ -- ^^ ^^ -- + +error: type parameters must be declared prior to associated type bindings + --> $DIR/suggest-move-types.rs:70:43 + | +LL | struct D> { //~ ERROR type parameters must be declared + | ^ ^ must be declared prior to associated type bindings + | | + | must be declared prior to associated type bindings +help: move the type parameters prior to the first associated type binding + | +LL | struct D> { //~ ERROR type parameters must be declared + | ^^ ^^ -- -- + +error: generic arguments must declare lifetimes, types and associated type bindings in that order + --> $DIR/suggest-move-types.rs:77:56 + | +LL | struct Dl<'a, 'b, 'c, T, U, V, M: ThreeWithLifetime> { + | ^^ ^ ^^ ^ ^^ must be declared prior to type parameters + | | | | | + | | | | must be declared prior to associated type bindings + | | | must be declared prior to type parameters + | | must be declared prior to associated type bindings + | must be declared prior to type parameters +help: move the parameters + | +LL | struct Dl<'a, 'b, 'c, T, U, V, M: ThreeWithLifetime<'a, 'b, 'c, T, U, V, A=(), B=(), C=()>> { + | ^^^ ^^^ ^^^ -- ^^ ^^ -- -- + +error: aborting due to 8 previous errors +