diff --git a/src/libsyntax/parse/parser.rs b/src/libsyntax/parse/parser.rs index c82ebd6aa5128c32329a575830b25aa07b6e0543..e0436cc7380abb6bf61b6e2faa75307021601bae 100644 --- a/src/libsyntax/parse/parser.rs +++ b/src/libsyntax/parse/parser.rs @@ -5172,8 +5172,12 @@ fn parse_trait_item_assoc_ty(&mut self) /// Parses (possibly empty) list of lifetime and type parameters, possibly including /// trailing comma and erroneous trailing attributes. crate fn parse_generic_params(&mut self) -> PResult<'a, Vec> { + let mut lifetimes = Vec::new(); let mut params = Vec::new(); - let mut seen_ty_param = false; + let mut seen_ty_param: Option = None; + let mut last_comma_span = None; + let mut bad_lifetime_pos = vec![]; + let mut suggestions = vec![]; loop { let attrs = self.parse_outer_attributes()?; if self.check_lifetime() { @@ -5184,25 +5188,42 @@ fn parse_trait_item_assoc_ty(&mut self) } else { Vec::new() }; - params.push(ast::GenericParam { + lifetimes.push(ast::GenericParam { ident: lifetime.ident, id: lifetime.id, attrs: attrs.into(), bounds, kind: ast::GenericParamKind::Lifetime, }); - if seen_ty_param { - self.span_err(self.prev_span, - "lifetime parameters must be declared prior to type parameters"); + if let Some(sp) = seen_ty_param { + let param_span = self.prev_span; + let ate_comma = self.eat(&token::Comma); + let remove_sp = if ate_comma { + param_span.until(self.span) + } else { + last_comma_span.unwrap_or(param_span).to(param_span) + }; + bad_lifetime_pos.push(param_span); + + if let Ok(snippet) = self.sess.source_map().span_to_snippet(param_span) { + suggestions.push((remove_sp, String::new())); + suggestions.push((sp.shrink_to_lo(), format!("{}, ", snippet))); + } + if ate_comma { + last_comma_span = Some(self.prev_span); + continue + } } } else if self.check_ident() { // Parse type parameter. params.push(self.parse_ty_param(attrs)?); - seen_ty_param = true; + if seen_ty_param.is_none() { + seen_ty_param = Some(self.prev_span); + } } else { // Check for trailing attributes and stop parsing. if !attrs.is_empty() { - let param_kind = if seen_ty_param { "type" } else { "lifetime" }; + let param_kind = if seen_ty_param.is_some() { "type" } else { "lifetime" }; self.span_err(attrs[0].span, &format!("trailing attribute after {} parameters", param_kind)); } @@ -5212,8 +5233,24 @@ fn parse_trait_item_assoc_ty(&mut self) if !self.eat(&token::Comma) { break } + last_comma_span = Some(self.prev_span); + } + if !bad_lifetime_pos.is_empty() { + let mut err = self.struct_span_err( + bad_lifetime_pos, + "lifetime parameters must be declared prior to type parameters", + ); + if !suggestions.is_empty() { + err.multipart_suggestion_with_applicability( + "move the lifetime parameter prior to the first type parameter", + suggestions, + Applicability::MachineApplicable, + ); + } + err.emit(); } - Ok(params) + lifetimes.extend(params); // ensure the correct order of lifetimes and type params + Ok(lifetimes) } /// Parse a set of optional generic type parameter declarations. Where diff --git a/src/test/ui/parser/issue-14303-enum.stderr b/src/test/ui/parser/issue-14303-enum.stderr index 7d546cb2180e3ab441f6943a8bf2608f3ac6aa73..622066a94f80a1562486882d570619102f8bfa14 100644 --- a/src/test/ui/parser/issue-14303-enum.stderr +++ b/src/test/ui/parser/issue-14303-enum.stderr @@ -3,6 +3,10 @@ error: lifetime parameters must be declared prior to type parameters | LL | enum X<'a, T, 'b> { | ^^ +help: move the lifetime parameter prior to the first type parameter + | +LL | enum X<'a, 'b, T> { + | ^^^ -- error: aborting due to previous error diff --git a/src/test/ui/parser/issue-14303-fn-def.stderr b/src/test/ui/parser/issue-14303-fn-def.stderr index c7b57f36376b54ee4b8c9b5a41dca0ab1e35b0d4..630c9cb40de37aa452dbef5c90a3bdf08e891e23 100644 --- a/src/test/ui/parser/issue-14303-fn-def.stderr +++ b/src/test/ui/parser/issue-14303-fn-def.stderr @@ -3,6 +3,10 @@ error: lifetime parameters must be declared prior to type parameters | LL | fn foo<'a, T, 'b>(x: &'a T) {} | ^^ +help: move the lifetime parameter prior to the first type parameter + | +LL | fn foo<'a, 'b, T>(x: &'a T) {} + | ^^^ -- error: aborting due to previous error diff --git a/src/test/ui/parser/issue-14303-impl.stderr b/src/test/ui/parser/issue-14303-impl.stderr index 0b7016eb7f71d1334a2c6568fcb0990a7903ddb8..2e3181de902758c3207b98e741716ace89776c76 100644 --- a/src/test/ui/parser/issue-14303-impl.stderr +++ b/src/test/ui/parser/issue-14303-impl.stderr @@ -3,6 +3,10 @@ error: lifetime parameters must be declared prior to type parameters | LL | impl<'a, T, 'b> X {} | ^^ +help: move the lifetime parameter prior to the first type parameter + | +LL | impl<'a, 'b, T> X {} + | ^^^ -- error: aborting due to previous error diff --git a/src/test/ui/parser/issue-14303-struct.stderr b/src/test/ui/parser/issue-14303-struct.stderr index 4a4b6789194952749d89ebd8710eb174a99c651d..c6b33120c18f046094b9501c4140c3871fd4c3cd 100644 --- a/src/test/ui/parser/issue-14303-struct.stderr +++ b/src/test/ui/parser/issue-14303-struct.stderr @@ -3,6 +3,10 @@ error: lifetime parameters must be declared prior to type parameters | LL | struct X<'a, T, 'b> { | ^^ +help: move the lifetime parameter prior to the first type parameter + | +LL | struct X<'a, 'b, T> { + | ^^^ -- error: aborting due to previous error diff --git a/src/test/ui/parser/issue-14303-trait.stderr b/src/test/ui/parser/issue-14303-trait.stderr index ab5cc5655bbe13f5b94aa0bb8a8221ccc471c88e..6d00f284bbbe741b75bac61292248964a7e4d2db 100644 --- a/src/test/ui/parser/issue-14303-trait.stderr +++ b/src/test/ui/parser/issue-14303-trait.stderr @@ -3,6 +3,10 @@ error: lifetime parameters must be declared prior to type parameters | LL | trait Foo<'a, T, 'b> {} | ^^ +help: move the lifetime parameter prior to the first type parameter + | +LL | trait Foo<'a, 'b, T> {} + | ^^^ -- error: aborting due to previous error diff --git a/src/test/ui/suggestions/suggest-move-lifetimes.rs b/src/test/ui/suggestions/suggest-move-lifetimes.rs new file mode 100644 index 0000000000000000000000000000000000000000..5051a406078aa74eb6961d01e65e84147674cacb --- /dev/null +++ b/src/test/ui/suggestions/suggest-move-lifetimes.rs @@ -0,0 +1,21 @@ +struct A { + t: &'a T, +} + +struct B { + t: &'a T, + u: U, +} + +struct C { + t: &'a T, + u: U, +} + +struct D { + t: &'a T, + u: &'b U, + v: &'c V, +} + +fn main() {} diff --git a/src/test/ui/suggestions/suggest-move-lifetimes.stderr b/src/test/ui/suggestions/suggest-move-lifetimes.stderr new file mode 100644 index 0000000000000000000000000000000000000000..f3d6469b5125501752128afcd05a02aed2f3c7d7 --- /dev/null +++ b/src/test/ui/suggestions/suggest-move-lifetimes.stderr @@ -0,0 +1,42 @@ +error: lifetime parameters must be declared prior to type parameters + --> $DIR/suggest-move-lifetimes.rs:1:13 + | +LL | struct A { + | ^^ +help: move the lifetime parameter prior to the first type parameter + | +LL | struct A<'a, T> { + | ^^^ -- + +error: lifetime parameters must be declared prior to type parameters + --> $DIR/suggest-move-lifetimes.rs:5:13 + | +LL | struct B { + | ^^ +help: move the lifetime parameter prior to the first type parameter + | +LL | struct B<'a, T, U> { + | ^^^ -- + +error: lifetime parameters must be declared prior to type parameters + --> $DIR/suggest-move-lifetimes.rs:10:16 + | +LL | struct C { + | ^^ +help: move the lifetime parameter prior to the first type parameter + | +LL | struct C<'a, T, U> { + | ^^^ -- + +error: lifetime parameters must be declared prior to type parameters + --> $DIR/suggest-move-lifetimes.rs:15:16 + | +LL | struct D { + | ^^ ^^ ^^ +help: move the lifetime parameter prior to the first type parameter + | +LL | struct D<'a, 'b, 'c, T, U, V> { + | ^^^ ^^^ ^^^ --- + +error: aborting due to 4 previous errors +