diff --git a/compiler/rustc_typeck/src/structured_errors/wrong_number_of_generic_args.rs b/compiler/rustc_typeck/src/structured_errors/wrong_number_of_generic_args.rs index 6aa1c915542e2..d4b5e5e2fe44d 100644 --- a/compiler/rustc_typeck/src/structured_errors/wrong_number_of_generic_args.rs +++ b/compiler/rustc_typeck/src/structured_errors/wrong_number_of_generic_args.rs @@ -523,6 +523,7 @@ impl<'a, 'tcx> WrongNumberOfGenericArgs<'a, 'tcx> { if self.not_enough_args_provided() { self.suggest_adding_args(err); } else if self.too_many_args_provided() { + self.suggest_moving_args_from_assoc_fn_to_trait(err); self.suggest_removing_args_or_generics(err); } else { unreachable!(); @@ -653,6 +654,123 @@ impl<'a, 'tcx> WrongNumberOfGenericArgs<'a, 'tcx> { } } + /// Suggests moving redundant argument(s) of an associate function to the + /// trait it belongs to. + /// + /// ```compile_fail + /// Into::into::>(42) // suggests considering `Into::>::into(42)` + /// ``` + fn suggest_moving_args_from_assoc_fn_to_trait(&self, err: &mut Diagnostic) { + let trait_ = match self.tcx.trait_of_item(self.def_id) { + Some(def_id) => def_id, + None => return, + }; + + // Skip suggestion when the associated function is itself generic, it is unclear + // how to split the provided parameters between those to suggest to the trait and + // those to remain on the associated type. + let num_assoc_fn_expected_args = + self.num_expected_type_or_const_args() + self.num_expected_lifetime_args(); + if num_assoc_fn_expected_args > 0 { + return; + } + + let num_assoc_fn_excess_args = + self.num_excess_type_or_const_args() + self.num_excess_lifetime_args(); + + let trait_generics = self.tcx.generics_of(trait_); + let num_trait_generics_except_self = + trait_generics.count() - if trait_generics.has_self { 1 } else { 0 }; + + let msg = format!( + "consider moving {these} generic argument{s} to the `{name}` trait, which takes up to {num} argument{s}", + these = pluralize!("this", num_assoc_fn_excess_args), + s = pluralize!(num_assoc_fn_excess_args), + name = self.tcx.item_name(trait_), + num = num_trait_generics_except_self, + ); + + if let Some(hir_id) = self.path_segment.hir_id + && let Some(parent_node) = self.tcx.hir().find_parent_node(hir_id) + && let Some(parent_node) = self.tcx.hir().find(parent_node) + && let hir::Node::Expr(expr) = parent_node { + match expr.kind { + hir::ExprKind::Path(ref qpath) => { + self.suggest_moving_args_from_assoc_fn_to_trait_for_qualified_path( + err, + qpath, + msg, + num_assoc_fn_excess_args, + num_trait_generics_except_self + ) + }, + hir::ExprKind::MethodCall(..) => { + self.suggest_moving_args_from_assoc_fn_to_trait_for_method_call( + err, + trait_, + expr, + msg, + num_assoc_fn_excess_args, + num_trait_generics_except_self + ) + }, + _ => return, + } + } + } + + fn suggest_moving_args_from_assoc_fn_to_trait_for_qualified_path( + &self, + err: &mut Diagnostic, + qpath: &'tcx hir::QPath<'tcx>, + msg: String, + num_assoc_fn_excess_args: usize, + num_trait_generics_except_self: usize, + ) { + if let hir::QPath::Resolved(_, path) = qpath + && let Some(trait_path_segment) = path.segments.get(0) { + let num_generic_args_supplied_to_trait = trait_path_segment.args().num_generic_params(); + + if num_assoc_fn_excess_args == num_trait_generics_except_self - num_generic_args_supplied_to_trait { + if let Some(span) = self.gen_args.span_ext() + && let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) { + let sugg = vec![ + (self.path_segment.ident.span, format!("{}::{}", snippet, self.path_segment.ident)), + (span.with_lo(self.path_segment.ident.span.hi()), "".to_owned()) + ]; + + err.multipart_suggestion( + msg, + sugg, + Applicability::MaybeIncorrect + ); + } + } + } + } + + fn suggest_moving_args_from_assoc_fn_to_trait_for_method_call( + &self, + err: &mut Diagnostic, + trait_: DefId, + expr: &'tcx hir::Expr<'tcx>, + msg: String, + num_assoc_fn_excess_args: usize, + num_trait_generics_except_self: usize, + ) { + if let hir::ExprKind::MethodCall(_, args, _) = expr.kind { + assert_eq!(args.len(), 1); + if num_assoc_fn_excess_args == num_trait_generics_except_self { + if let Some(gen_args) = self.gen_args.span_ext() + && let Ok(gen_args) = self.tcx.sess.source_map().span_to_snippet(gen_args) + && let Ok(args) = self.tcx.sess.source_map().span_to_snippet(args[0].span) { + let sugg = format!("{}::{}::{}({})", self.tcx.item_name(trait_), gen_args, self.tcx.item_name(self.def_id), args); + err.span_suggestion(expr.span, msg, sugg, Applicability::MaybeIncorrect); + } + } + } + } + /// Suggests to remove redundant argument(s): /// /// ```text diff --git a/src/test/ui/const-generics/invalid-const-arg-for-type-param.stderr b/src/test/ui/const-generics/invalid-const-arg-for-type-param.stderr index b1b359619dcfa..d955b4f9651da 100644 --- a/src/test/ui/const-generics/invalid-const-arg-for-type-param.stderr +++ b/src/test/ui/const-generics/invalid-const-arg-for-type-param.stderr @@ -2,15 +2,22 @@ error[E0107]: this associated function takes 0 generic arguments but 1 generic a --> $DIR/invalid-const-arg-for-type-param.rs:6:23 | LL | let _: u32 = 5i32.try_into::<32>().unwrap(); - | ^^^^^^^^------ help: remove these generics - | | - | expected 0 generic arguments + | ^^^^^^^^ expected 0 generic arguments | note: associated function defined here, with 0 generic parameters --> $SRC_DIR/core/src/convert/mod.rs:LL:COL | LL | fn try_into(self) -> Result; | ^^^^^^^^ +help: consider moving this generic argument to the `TryInto` trait, which takes up to 1 argument + | +LL | let _: u32 = TryInto::<32>::try_into(5i32).unwrap(); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +help: remove these generics + | +LL - let _: u32 = 5i32.try_into::<32>().unwrap(); +LL + let _: u32 = 5i32.try_into().unwrap(); + | error[E0599]: no method named `f` found for struct `S` in the current scope --> $DIR/invalid-const-arg-for-type-param.rs:9:7 diff --git a/src/test/ui/suggestions/issue-89064.rs b/src/test/ui/suggestions/issue-89064.rs new file mode 100644 index 0000000000000..fa5fc899dc083 --- /dev/null +++ b/src/test/ui/suggestions/issue-89064.rs @@ -0,0 +1,35 @@ +use std::convert::TryInto; + +trait A { + fn foo() {} +} + +trait B { + fn bar() {} +} + +struct S; + +impl A for S {} +impl B for S {} + +fn main() { + let _ = A::foo::(); + //~^ ERROR + //~| HELP remove these generics + //~| HELP consider moving this generic argument + + let _ = B::bar::(); + //~^ ERROR + //~| HELP remove these generics + //~| HELP consider moving these generic arguments + + let _ = A::::foo::(); + //~^ ERROR + //~| HELP remove these generics + + let _ = 42.into::>(); + //~^ ERROR + //~| HELP remove these generics + //~| HELP consider moving this generic argument +} diff --git a/src/test/ui/suggestions/issue-89064.stderr b/src/test/ui/suggestions/issue-89064.stderr new file mode 100644 index 0000000000000..8b2a388162806 --- /dev/null +++ b/src/test/ui/suggestions/issue-89064.stderr @@ -0,0 +1,82 @@ +error[E0107]: this associated function takes 0 generic arguments but 1 generic argument was supplied + --> $DIR/issue-89064.rs:17:16 + | +LL | let _ = A::foo::(); + | ^^^ expected 0 generic arguments + | +note: associated function defined here, with 0 generic parameters + --> $DIR/issue-89064.rs:4:8 + | +LL | fn foo() {} + | ^^^ +help: consider moving this generic argument to the `A` trait, which takes up to 1 argument + | +LL - let _ = A::foo::(); +LL + let _ = A::::foo(); + | +help: remove these generics + | +LL - let _ = A::foo::(); +LL + let _ = A::foo(); + | + +error[E0107]: this associated function takes 0 generic arguments but 2 generic arguments were supplied + --> $DIR/issue-89064.rs:22:16 + | +LL | let _ = B::bar::(); + | ^^^ expected 0 generic arguments + | +note: associated function defined here, with 0 generic parameters + --> $DIR/issue-89064.rs:8:8 + | +LL | fn bar() {} + | ^^^ +help: consider moving these generic arguments to the `B` trait, which takes up to 2 arguments + | +LL - let _ = B::bar::(); +LL + let _ = B::::bar(); + | +help: remove these generics + | +LL - let _ = B::bar::(); +LL + let _ = B::bar(); + | + +error[E0107]: this associated function takes 0 generic arguments but 1 generic argument was supplied + --> $DIR/issue-89064.rs:27:21 + | +LL | let _ = A::::foo::(); + | ^^^----- help: remove these generics + | | + | expected 0 generic arguments + | +note: associated function defined here, with 0 generic parameters + --> $DIR/issue-89064.rs:4:8 + | +LL | fn foo() {} + | ^^^ + +error[E0107]: this associated function takes 0 generic arguments but 1 generic argument was supplied + --> $DIR/issue-89064.rs:31:16 + | +LL | let _ = 42.into::>(); + | ^^^^ expected 0 generic arguments + | +note: associated function defined here, with 0 generic parameters + --> $SRC_DIR/core/src/convert/mod.rs:LL:COL + | +LL | fn into(self) -> T; + | ^^^^ +help: consider moving this generic argument to the `Into` trait, which takes up to 1 argument + | +LL | let _ = Into::>::into(42); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +help: remove these generics + | +LL - let _ = 42.into::>(); +LL + let _ = 42.into(); + | + +error: aborting due to 4 previous errors + +For more information about this error, try `rustc --explain E0107`.