about summary refs log tree commit diff
diff options
context:
space:
mode:
authorHirochika Matsumoto <git@hkmatsumoto.com>2022-08-21 16:59:09 +0900
committerHirochika Matsumoto <git@hkmatsumoto.com>2022-08-21 17:00:18 +0900
commitbafa89bc467a2b5862c409fc6cae9c01fdf25c6e (patch)
treedeba6f9b635573d2a038632865920866e509a38b
parent908fc5b26d15fc96d630ab921e70b2db77a532c4 (diff)
downloadrust-bafa89bc467a2b5862c409fc6cae9c01fdf25c6e.tar.gz
rust-bafa89bc467a2b5862c409fc6cae9c01fdf25c6e.zip
Suggest moving redundant generic args of an assoc fn to its trait
-rw-r--r--compiler/rustc_typeck/src/structured_errors/wrong_number_of_generic_args.rs59
-rw-r--r--src/test/ui/suggestions/issue-89064.rs32
-rw-r--r--src/test/ui/suggestions/issue-89064.stderr69
3 files changed, 160 insertions, 0 deletions
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 99729391e02..cfa8191d953 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::<Option<_>>(42) // suggests considering `Into::<Option<_>>::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 "<trait>::<assoc fn>()" from "x.<assoc fn>()";
+            // 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 00000000000..c0fcad4236e
--- /dev/null
+++ b/src/test/ui/suggestions/issue-89064.rs
@@ -0,0 +1,32 @@
+use std::convert::TryInto;
+
+trait A<T> {
+    fn foo() {}
+}
+
+trait B<T, U> {
+    fn bar() {}
+}
+
+struct S;
+
+impl<T> A<T> for S {}
+impl<T, U> B<T, U> for S {}
+
+fn main() {
+    let _ = A::foo::<S>();
+    //~^ ERROR
+    //~| HELP remove these generics
+    //~| HELP consider moving this generic argument
+
+    let _ = B::bar::<S, S>();
+    //~^ ERROR
+    //~| HELP remove these generics
+    //~| HELP consider moving these generic arguments
+
+    // bad suggestion
+    let _ = A::<S>::foo::<S>();
+    //~^ 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 00000000000..11d652b4c60
--- /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::<S>();
+   |                ^^^ 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::<S>();
+LL +     let _ = A::foo();
+   |
+help: consider moving this generic argument to the `A` trait, which takes up to 1 argument
+   |
+LL -     let _ = A::foo::<S>();
+LL +     let _ = A::<S>::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::<S, S>();
+   |                ^^^ 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::<S, S>();
+LL +     let _ = B::bar();
+   |
+help: consider moving these generic arguments to the `B` trait, which takes up to 2 arguments
+   |
+LL -     let _ = B::bar::<S, S>();
+LL +     let _ = B::<S, S>::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::<S>::foo::<S>();
+   |                     ^^^ 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::<S>::foo::<S>();
+LL +     let _ = A::<S>::foo();
+   |
+help: consider moving this generic argument to the `A` trait, which takes up to 1 argument
+   |
+LL -     let _ = A::<S>::foo::<S>();
+LL +     let _ = A::<S>::<S>::foo();
+   |
+
+error: aborting due to 3 previous errors
+
+For more information about this error, try `rustc --explain E0107`.