diff --git a/Cargo.lock b/Cargo.lock index 28dcd3026fc..908bfa35557 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3564,6 +3564,7 @@ dependencies = [ name = "rustc_ast_passes" version = "0.0.0" dependencies = [ + "itertools 0.8.0", "log", "rustc_ast", "rustc_ast_pretty", diff --git a/src/librustc_ast/ast.rs b/src/librustc_ast/ast.rs index 6586280d214..f91cbe51d85 100644 --- a/src/librustc_ast/ast.rs +++ b/src/librustc_ast/ast.rs @@ -300,8 +300,8 @@ pub enum GenericBound { impl GenericBound { pub fn span(&self) -> Span { match self { - &GenericBound::Trait(ref t, ..) => t.span, - &GenericBound::Outlives(ref l) => l.ident.span, + GenericBound::Trait(ref t, ..) => t.span, + GenericBound::Outlives(ref l) => l.ident.span, } } } diff --git a/src/librustc_ast_passes/Cargo.toml b/src/librustc_ast_passes/Cargo.toml index 5d096e4965d..e4d1d79abb2 100644 --- a/src/librustc_ast_passes/Cargo.toml +++ b/src/librustc_ast_passes/Cargo.toml @@ -9,6 +9,7 @@ name = "rustc_ast_passes" path = "lib.rs" [dependencies] +itertools = "0.8" log = "0.4" rustc_ast_pretty = { path = "../librustc_ast_pretty" } rustc_attr = { path = "../librustc_attr" } diff --git a/src/librustc_ast_passes/ast_validation.rs b/src/librustc_ast_passes/ast_validation.rs index de7ae10723f..9563325fe32 100644 --- a/src/librustc_ast_passes/ast_validation.rs +++ b/src/librustc_ast_passes/ast_validation.rs @@ -6,6 +6,7 @@ // This pass is supposed to perform only simple checks not requiring name resolution // or type checking or some other kind of complex analysis. +use itertools::{Either, Itertools}; use rustc_ast::ast::*; use rustc_ast::attr; use rustc_ast::expand::is_proc_macro_attr; @@ -14,7 +15,7 @@ use rustc_ast::visit::{self, AssocCtxt, FnCtxt, FnKind, Visitor}; use rustc_ast::walk_list; use rustc_ast_pretty::pprust; use rustc_data_structures::fx::FxHashMap; -use rustc_errors::{error_code, struct_span_err, Applicability}; +use rustc_errors::{error_code, pluralize, struct_span_err, Applicability}; use rustc_parse::validate_attr; use rustc_session::lint::builtin::PATTERNS_IN_FNS_WITHOUT_BODY; use rustc_session::lint::LintBuffer; @@ -640,6 +641,33 @@ impl<'a> AstValidator<'a> { } } + fn correct_generic_order_suggestion(&self, data: &AngleBracketedArgs) -> String { + // Lifetimes always come first. + let lt_sugg = data.args.iter().filter_map(|arg| match arg { + AngleBracketedArg::Arg(lt @ GenericArg::Lifetime(_)) => { + Some(pprust::to_string(|s| s.print_generic_arg(lt))) + } + _ => None, + }); + let args_sugg = data.args.iter().filter_map(|a| match a { + AngleBracketedArg::Arg(GenericArg::Lifetime(_)) | AngleBracketedArg::Constraint(_) => { + None + } + AngleBracketedArg::Arg(arg) => Some(pprust::to_string(|s| s.print_generic_arg(arg))), + }); + // Constraints always come last. + let constraint_sugg = data.args.iter().filter_map(|a| match a { + AngleBracketedArg::Arg(_) => None, + AngleBracketedArg::Constraint(c) => { + Some(pprust::to_string(|s| s.print_assoc_constraint(c))) + } + }); + format!( + "<{}>", + lt_sugg.chain(args_sugg).chain(constraint_sugg).collect::>().join(", ") + ) + } + /// Enforce generic args coming before constraints in `<...>` of a path segment. fn check_generic_args_before_constraints(&self, data: &AngleBracketedArgs) { // Early exit in case it's partitioned as it should be. @@ -647,24 +675,36 @@ impl<'a> AstValidator<'a> { return; } // Find all generic argument coming after the first constraint... - let mut misplaced_args = Vec::new(); - let mut first = None; - for arg in &data.args { - match (arg, first) { - (AngleBracketedArg::Arg(a), Some(_)) => misplaced_args.push(a.span()), - (AngleBracketedArg::Constraint(c), None) => first = Some(c.span), - (AngleBracketedArg::Arg(_), None) | (AngleBracketedArg::Constraint(_), Some(_)) => { - } - } - } + let (constraint_spans, arg_spans): (Vec, Vec) = + data.args.iter().partition_map(|arg| match arg { + AngleBracketedArg::Constraint(c) => Either::Left(c.span), + AngleBracketedArg::Arg(a) => Either::Right(a.span()), + }); + let args_len = arg_spans.len(); + let constraint_len = constraint_spans.len(); // ...and then error: self.err_handler() .struct_span_err( - misplaced_args.clone(), + arg_spans.clone(), "generic arguments must come before the first constraint", ) - .span_label(first.unwrap(), "the first constraint is provided here") - .span_labels(misplaced_args, "generic argument") + .span_label(constraint_spans[0], &format!("constraint{}", pluralize!(constraint_len))) + .span_label( + *arg_spans.iter().last().unwrap(), + &format!("generic argument{}", pluralize!(args_len)), + ) + .span_labels(constraint_spans, "") + .span_labels(arg_spans, "") + .span_suggestion_verbose( + data.span, + &format!( + "move the constraint{} after the generic argument{}", + pluralize!(constraint_len), + pluralize!(args_len) + ), + self.correct_generic_order_suggestion(&data), + Applicability::MachineApplicable, + ) .emit(); } } diff --git a/src/librustc_ast_pretty/pprust.rs b/src/librustc_ast_pretty/pprust.rs index 6541ac156b4..f68868633f1 100644 --- a/src/librustc_ast_pretty/pprust.rs +++ b/src/librustc_ast_pretty/pprust.rs @@ -869,7 +869,7 @@ impl<'a> State<'a> { } } - fn print_assoc_constraint(&mut self, constraint: &ast::AssocTyConstraint) { + pub fn print_assoc_constraint(&mut self, constraint: &ast::AssocTyConstraint) { self.print_ident(constraint.ident); self.s.space(); match &constraint.kind { @@ -883,7 +883,7 @@ impl<'a> State<'a> { } } - crate fn print_generic_arg(&mut self, generic_arg: &GenericArg) { + pub fn print_generic_arg(&mut self, generic_arg: &GenericArg) { match generic_arg { GenericArg::Lifetime(lt) => self.print_lifetime(*lt), GenericArg::Type(ty) => self.print_type(ty), diff --git a/src/librustc_errors/diagnostic_builder.rs b/src/librustc_errors/diagnostic_builder.rs index fffae0bfd24..2dbd9f4e52f 100644 --- a/src/librustc_errors/diagnostic_builder.rs +++ b/src/librustc_errors/diagnostic_builder.rs @@ -315,6 +315,20 @@ impl<'a> DiagnosticBuilder<'a> { self } + pub fn span_suggestion_verbose( + &mut self, + sp: Span, + msg: &str, + suggestion: String, + applicability: Applicability, + ) -> &mut Self { + if !self.0.allow_suggestions { + return self; + } + self.0.diagnostic.span_suggestion_verbose(sp, msg, suggestion, applicability); + self + } + pub fn span_suggestion_hidden( &mut self, sp: Span, diff --git a/src/test/ui/parser/issue-32214.stderr b/src/test/ui/parser/issue-32214.stderr index 742f4fdc38b..bc61b3b74e2 100644 --- a/src/test/ui/parser/issue-32214.stderr +++ b/src/test/ui/parser/issue-32214.stderr @@ -4,7 +4,12 @@ error: generic arguments must come before the first constraint LL | pub fn test >() {} | ------- ^ generic argument | | - | the first constraint is provided here + | constraint + | +help: move the constraint after the generic argument + | +LL | pub fn test >() {} + | ^^^^^^^^^^^^^^ error: aborting due to previous error diff --git a/src/test/ui/suggestions/suggest-move-types.stderr b/src/test/ui/suggestions/suggest-move-types.stderr index 4dd0613757a..3bb6fd6e4f4 100644 --- a/src/test/ui/suggestions/suggest-move-types.stderr +++ b/src/test/ui/suggestions/suggest-move-types.stderr @@ -4,79 +4,103 @@ error: generic arguments must come before the first constraint LL | struct A> { | ---- ^ generic argument | | - | the first constraint is provided here + | constraint + | +help: move the constraint after the generic argument + | +LL | struct A> { + | ^^^^^^^^^^^ error: generic arguments must come before the first constraint --> $DIR/suggest-move-types.rs:33:43 | LL | struct Al<'a, T, M: OneWithLifetime> { - | ---- ^ ^^ generic argument - | | | - | | generic argument - | the first constraint is provided here + | ---- ^ ^^ generic arguments + | | + | constraint + | +help: move the constraint after the generic arguments + | +LL | struct Al<'a, T, M: OneWithLifetime<'a, T, A = ()>> { + | ^^^^^^^^^^^^^^^ error: generic arguments must come before the first constraint --> $DIR/suggest-move-types.rs:40:46 | LL | struct B> { - | ---- ^ ^ ^ generic argument - | | | | - | | | generic argument - | | generic argument - | the first constraint is provided here + | ---- ---- ---- ^ ^ ^ generic arguments + | | + | constraints + | +help: move the constraints after the generic arguments + | +LL | struct B> { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: generic arguments must come before the first constraint --> $DIR/suggest-move-types.rs:48:71 | LL | struct Bl<'a, 'b, 'c, T, U, V, M: ThreeWithLifetime> { - | ---- ^ ^ ^ ^^ ^^ ^^ generic argument - | | | | | | | - | | | | | | generic argument - | | | | | generic argument - | | | | generic argument - | | | generic argument - | | generic argument - | the first constraint is provided here + | ---- ---- ---- ^ ^ ^ ^^ ^^ ^^ generic arguments + | | + | constraints + | +help: move the constraints after the generic arguments + | +LL | struct Bl<'a, 'b, 'c, T, U, V, M: ThreeWithLifetime<'a, 'b, 'c, T, U, V, A = (), B = (), C = ()>> { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: generic arguments must come before the first constraint - --> $DIR/suggest-move-types.rs:57:49 + --> $DIR/suggest-move-types.rs:57:28 | LL | struct C> { - | ---- ^ ^ generic argument - | | | - | | generic argument - | the first constraint is provided here + | ^ ---- ---- ---- ^ ^ generic arguments + | | + | constraints + | +help: move the constraints after the generic arguments + | +LL | struct C> { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: generic arguments must come before the first constraint - --> $DIR/suggest-move-types.rs:65:78 + --> $DIR/suggest-move-types.rs:65:53 | LL | struct Cl<'a, 'b, 'c, T, U, V, M: ThreeWithLifetime> { - | ---- ^ ^^ ^ ^^ generic argument - | | | | | - | | | | generic argument - | | | generic argument - | | generic argument - | the first constraint is provided here + | ^ ^^ ---- ---- ---- ^ ^^ ^ ^^ generic arguments + | | + | constraints + | +help: move the constraints after the generic arguments + | +LL | struct Cl<'a, 'b, 'c, T, U, V, M: ThreeWithLifetime<'a, 'b, 'c, T, U, V, A = (), B = (), C = ()>> { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: generic arguments must come before the first constraint - --> $DIR/suggest-move-types.rs:74:43 + --> $DIR/suggest-move-types.rs:74:28 | LL | struct D> { - | ---- ^ ^ generic argument - | | | - | | generic argument - | the first constraint is provided here + | ^ ---- ---- ^ ---- ^ generic arguments + | | + | constraints + | +help: move the constraints after the generic arguments + | +LL | struct D> { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: generic arguments must come before the first constraint - --> $DIR/suggest-move-types.rs:82:72 + --> $DIR/suggest-move-types.rs:82:53 | LL | struct Dl<'a, 'b, 'c, T, U, V, M: ThreeWithLifetime> { - | ---- ^ ^^ ^ ^^ generic argument - | | | | | - | | | | generic argument - | | | generic argument - | | generic argument - | the first constraint is provided here + | ^ ^^ ---- ---- ^ ^^ ---- ^ ^^ generic arguments + | | + | constraints + | +help: move the constraints after the generic arguments + | +LL | struct Dl<'a, 'b, 'c, T, U, V, M: ThreeWithLifetime<'a, 'b, 'c, T, U, V, A = (), B = (), C = ()>> { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error[E0747]: type provided when a lifetime was expected --> $DIR/suggest-move-types.rs:33:43