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 99729391e02b..cfa8191d953d 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 @@ -525,6 +525,7 @@ impl<'a, 'tcx> WrongNumberOfGenericArgs<'a, 'tcx> { self.suggest_adding_args(err); } else if self.too_many_args_provided() { self.suggest_removing_args_or_generics(err); + self.suggest_moving_args(err); } else { unreachable!(); } @@ -654,6 +655,64 @@ 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(&self, err: &mut Diagnostic) { + if let Some(trait_) = self.tcx.trait_of_item(self.def_id) { + // HACK(hkmatsumoto): Ugly way to tell "::()" from "x.()"; + // we don't care the latter (for now). + if self.path_segment.res == Some(hir::def::Res::Err) { + return; + } + + // Say, if the assoc fn takes `A`, `B` and `C` as generic arguments while expecting 1 + // argument, and its trait expects 2 arguments. It is hard to "split" them right as + // there are too many cases to handle: `A` `B` | `C`, `A` `B` | `C`, `A` `C` | `B`, ... + 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 }; + + // FIXME(hkmatsumoto): RHS of this condition ideally should be + // `num_trait_generics_except_self` - "# of generic args already provided to trait" + // but unable to get that information with `self.def_id`. + if num_assoc_fn_excess_args == num_trait_generics_except_self { + if let Some(span) = self.gen_args.span_ext() + && let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) { + 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, + ); + 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 + ); + } + } + } + } + /// Suggests to remove redundant argument(s): /// /// ```text diff --git a/src/test/ui/suggestions/issue-89064.rs b/src/test/ui/suggestions/issue-89064.rs new file mode 100644 index 000000000000..c0fcad4236e8 --- /dev/null +++ b/src/test/ui/suggestions/issue-89064.rs @@ -0,0 +1,32 @@ +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 + + // bad suggestion + let _ = A::::foo::(); + //~^ 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 000000000000..11d652b4c603 --- /dev/null +++ b/src/test/ui/suggestions/issue-89064.stderr @@ -0,0 +1,69 @@ +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: remove these generics + | +LL - let _ = A::foo::(); +LL + let _ = A::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(); + | + +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: remove these generics + | +LL - let _ = B::bar::(); +LL + let _ = B::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(); + | + +error[E0107]: this associated function takes 0 generic arguments but 1 generic argument was supplied + --> $DIR/issue-89064.rs:28:21 + | +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: remove these generics + | +LL - let _ = A::::foo::(); +LL + let _ = A::::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(); + | + +error: aborting due to 3 previous errors + +For more information about this error, try `rustc --explain E0107`.