]> git.lizzy.rs Git - rust.git/commitdiff
Suggest trait bound on type parameter when it is unconstrained
authorEsteban Küber <esteban@kuber.com.ar>
Wed, 17 Jul 2019 23:34:13 +0000 (16:34 -0700)
committerEsteban Küber <esteban@kuber.com.ar>
Thu, 18 Jul 2019 18:50:50 +0000 (11:50 -0700)
Given

```
mented on Jan 26, 2015 •
 trait Foo { fn method(&self) {} }

fn call_method<T>(x: &T) {
    x.method()
}
```

suggest constraining `T` with `Foo`.

src/librustc_typeck/check/method/suggest.rs
src/test/ui/issues/issue-39559.stderr
src/test/ui/span/issue-7575.stderr
src/test/ui/suggestions/issue-21673.rs [new file with mode: 0644]
src/test/ui/suggestions/issue-21673.stderr [new file with mode: 0644]

index 5febc694def0ca1e2a0c4bd963ab93d04f72342b..cb83630100a2d97aab65e30b4ec3e2d3c1284750 100644 (file)
@@ -643,13 +643,15 @@ fn suggest_valid_traits(&self,
         }
     }
 
-    fn suggest_traits_to_import<'b>(&self,
-                                    err: &mut DiagnosticBuilder<'_>,
-                                    span: Span,
-                                    rcvr_ty: Ty<'tcx>,
-                                    item_name: ast::Ident,
-                                    source: SelfSource<'b>,
-                                    valid_out_of_scope_traits: Vec<DefId>) {
+    fn suggest_traits_to_import<'b>(
+        &self,
+        err: &mut DiagnosticBuilder<'_>,
+        span: Span,
+        rcvr_ty: Ty<'tcx>,
+        item_name: ast::Ident,
+        source: SelfSource<'b>,
+        valid_out_of_scope_traits: Vec<DefId>,
+    ) {
         if self.suggest_valid_traits(err, valid_out_of_scope_traits) {
             return;
         }
@@ -683,30 +685,96 @@ fn suggest_traits_to_import<'b>(&self,
             candidates.sort_by(|a, b| a.cmp(b).reverse());
             candidates.dedup();
 
-            // FIXME #21673: this help message could be tuned to the case
-            // of a type parameter: suggest adding a trait bound rather
-            // than implementing.
-            err.help("items from traits can only be used if the trait is implemented and in scope");
-            let mut msg = format!("the following {traits_define} an item `{name}`, \
-                                   perhaps you need to implement {one_of_them}:",
-                                  traits_define = if candidates.len() == 1 {
-                                      "trait defines"
-                                  } else {
-                                      "traits define"
-                                  },
-                                  one_of_them = if candidates.len() == 1 {
-                                      "it"
-                                  } else {
-                                      "one of them"
-                                  },
-                                  name = item_name);
-
-            for (i, trait_info) in candidates.iter().enumerate() {
-                msg.push_str(&format!("\ncandidate #{}: `{}`",
-                                      i + 1,
-                                      self.tcx.def_path_str(trait_info.def_id)));
+            let param_type = match rcvr_ty.sty {
+                ty::Param(param) => Some(param),
+                ty::Ref(_, ty, _) => match ty.sty {
+                    ty::Param(param) => Some(param),
+                    _ => None,
+                }
+                _ => None,
+            };
+            err.help(if param_type.is_some() {
+                "items from traits can only be used if the type parameter is bounded by the trait"
+            } else {
+                "items from traits can only be used if the trait is implemented and in scope"
+            });
+            let mut msg = format!(
+                "the following {traits_define} an item `{name}`, perhaps you need to {action} \
+                 {one_of_them}:",
+                traits_define = if candidates.len() == 1 {
+                    "trait defines"
+                } else {
+                    "traits define"
+                },
+                action = if let Some(param) = param_type {
+                    format!("restrict type parameter `{}` with", param)
+                } else {
+                    "implement".to_string()
+                },
+                one_of_them = if candidates.len() == 1 {
+                    "it"
+                } else {
+                    "one of them"
+                },
+                name = item_name,
+            );
+            // Obtain the span for `param` and use it for a structured suggestion.
+            let mut suggested = false;
+            if let (Some(ref param), Some(ref table)) = (param_type, self.in_progress_tables) {
+                let table = table.borrow();
+                if let Some(did) = table.local_id_root {
+                    let generics = self.tcx.generics_of(did);
+                    let type_param = generics.type_param(param, self.tcx);
+                    let hir = &self.tcx.hir();
+                    if let Some(id) = hir.as_local_hir_id(type_param.def_id) {
+                        // Get the `hir::Param` to verify whether it already has any bounds.
+                        // We do this to avoid suggesting code that ends up as `T: FooBar`,
+                        // instead we suggest `T: Foo + Bar` in that case.
+                        let mut has_bounds = false;
+                        if let Node::GenericParam(ref param) = hir.get(id) {
+                            has_bounds = !param.bounds.is_empty();
+                        }
+                        let sp = hir.span(id);
+                        // `sp` only covers `T`, change it so that it covers
+                        // `T:` when appropriate
+                        let sp = if has_bounds {
+                            sp.to(self.tcx
+                                .sess
+                                .source_map()
+                                .next_point(self.tcx.sess.source_map().next_point(sp)))
+                        } else {
+                            sp
+                        };
+
+                        // FIXME: contrast `t.def_id` against `param.bounds` to not suggest traits
+                        // already there. That can happen when the cause is that we're in a const
+                        // scope or associated function used as a method.
+                        err.span_suggestions(
+                            sp,
+                            &msg[..],
+                            candidates.iter().map(|t| format!(
+                                "{}: {}{}",
+                                param,
+                                self.tcx.def_path_str(t.def_id),
+                                if has_bounds { " +"} else { "" },
+                            )),
+                            Applicability::MaybeIncorrect,
+                        );
+                        suggested = true;
+                    }
+                };
+            }
+
+            if !suggested {
+                for (i, trait_info) in candidates.iter().enumerate() {
+                    msg.push_str(&format!(
+                        "\ncandidate #{}: `{}`",
+                        i + 1,
+                        self.tcx.def_path_str(trait_info.def_id),
+                    ));
+                }
+                err.note(&msg[..]);
             }
-            err.note(&msg[..]);
         }
     }
 
index aded0c2de45e43b7a521aabf2b1a1523761e1fe2..b945b5e665459fd626e4d8fe6a9230a27c756a4e 100644 (file)
@@ -4,9 +4,11 @@ error[E0599]: no function or associated item named `dim` found for type `D` in t
 LL |     entries: [T; D::dim()],
    |                     ^^^ function or associated item not found in `D`
    |
-   = help: items from traits can only be used if the trait is implemented and in scope
-   = note: the following trait defines an item `dim`, perhaps you need to implement it:
-           candidate #1: `Dim`
+   = help: items from traits can only be used if the type parameter is bounded by the trait
+help: the following trait defines an item `dim`, perhaps you need to restrict type parameter `D` with it:
+   |
+LL | pub struct Vector<T, D: Dim + Dim> {
+   |                      ^^^^^^^^
 
 error: aborting due to previous error
 
index 614638752f166590a16fc5ac86ae991cfa268cb3..36db5bea8629489ffd456e22c947a2ef0a4e6a44 100644 (file)
@@ -61,9 +61,11 @@ note: the candidate is defined in the trait `ManyImplTrait`
 LL |     fn is_str() -> bool {
    |     ^^^^^^^^^^^^^^^^^^^
    = help: to disambiguate the method call, write `ManyImplTrait::is_str(t)` instead
-   = help: items from traits can only be used if the trait is implemented and in scope
-   = note: the following trait defines an item `is_str`, perhaps you need to implement it:
-           candidate #1: `ManyImplTrait`
+   = help: items from traits can only be used if the type parameter is bounded by the trait
+help: the following trait defines an item `is_str`, perhaps you need to restrict type parameter `T` with it:
+   |
+LL | fn param_bound<T: ManyImplTrait + ManyImplTrait>(t: T) -> bool {
+   |                ^^^^^^^^^^^^^^^^^^
 
 error: aborting due to 3 previous errors
 
diff --git a/src/test/ui/suggestions/issue-21673.rs b/src/test/ui/suggestions/issue-21673.rs
new file mode 100644 (file)
index 0000000..9d66cae
--- /dev/null
@@ -0,0 +1,13 @@
+trait Foo {
+    fn method(&self) {}
+}
+
+fn call_method<T: std::fmt::Debug>(x: &T) {
+    x.method() //~ ERROR E0599
+}
+
+fn call_method_2<T>(x: T) {
+    x.method() //~ ERROR E0599
+}
+
+fn main() {}
diff --git a/src/test/ui/suggestions/issue-21673.stderr b/src/test/ui/suggestions/issue-21673.stderr
new file mode 100644 (file)
index 0000000..6cf71c8
--- /dev/null
@@ -0,0 +1,27 @@
+error[E0599]: no method named `method` found for type `&T` in the current scope
+  --> $DIR/issue-21673.rs:6:7
+   |
+LL |     x.method()
+   |       ^^^^^^
+   |
+   = help: items from traits can only be used if the type parameter is bounded by the trait
+help: the following trait defines an item `method`, perhaps you need to restrict type parameter `T` with it:
+   |
+LL | fn call_method<T: Foo + std::fmt::Debug>(x: &T) {
+   |                ^^^^^^^^
+
+error[E0599]: no method named `method` found for type `T` in the current scope
+  --> $DIR/issue-21673.rs:10:7
+   |
+LL |     x.method()
+   |       ^^^^^^
+   |
+   = help: items from traits can only be used if the type parameter is bounded by the trait
+help: the following trait defines an item `method`, perhaps you need to restrict type parameter `T` with it:
+   |
+LL | fn call_method_2<T: Foo>(x: T) {
+   |                  ^^^^^^
+
+error: aborting due to 2 previous errors
+
+For more information about this error, try `rustc --explain E0599`.