about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEsteban Küber <esteban@kuber.com.ar>2025-06-04 18:18:06 +0000
committerEsteban Küber <esteban@kuber.com.ar>2025-06-04 18:18:06 +0000
commit585a40963ea59808e74803f8610659a505b145e0 (patch)
treef50eeab8d28fbcc5bc1d06c28ae3b3dba8cd93d0
parent642e49bfed2481e54e252732be20d3c24cbec9e8 (diff)
downloadrust-585a40963ea59808e74803f8610659a505b145e0.tar.gz
rust-585a40963ea59808e74803f8610659a505b145e0.zip
Detect method not being present that is present in other tuple types
When a method is not present because of a trait bound not being met, and that trait bound is on a tuple, we check if making the tuple have no borrowed types makes the method to be found and highlight it if it does. This is a common problem for Bevy in particular and ORMs in general.
-rw-r--r--compiler/rustc_hir_typeck/src/method/suggest.rs123
-rw-r--r--tests/ui/methods/missing-bound-on-tuple.rs39
-rw-r--r--tests/ui/methods/missing-bound-on-tuple.stderr58
3 files changed, 218 insertions, 2 deletions
diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs
index 7b71f5de756..54e0a42e799 100644
--- a/compiler/rustc_hir_typeck/src/method/suggest.rs
+++ b/compiler/rustc_hir_typeck/src/method/suggest.rs
@@ -14,7 +14,9 @@ use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
 use rustc_data_structures::sorted_map::SortedMap;
 use rustc_data_structures::unord::UnordSet;
 use rustc_errors::codes::*;
-use rustc_errors::{Applicability, Diag, MultiSpan, StashKey, pluralize, struct_span_code_err};
+use rustc_errors::{
+    Applicability, Diag, DiagStyledString, MultiSpan, StashKey, pluralize, struct_span_code_err,
+};
 use rustc_hir::def::{CtorKind, DefKind, Res};
 use rustc_hir::def_id::DefId;
 use rustc_hir::intravisit::{self, Visitor};
@@ -1569,7 +1571,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             );
         }
 
-        if rcvr_ty.is_numeric() && rcvr_ty.is_fresh() || restrict_type_params || suggested_derive {
+        if rcvr_ty.is_numeric() && rcvr_ty.is_fresh()
+            || restrict_type_params
+            || suggested_derive
+            || self.lookup_alternative_tuple_impls(&mut err, &unsatisfied_predicates)
+        {
         } else {
             self.suggest_traits_to_import(
                 &mut err,
@@ -1744,6 +1750,119 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         err.emit()
     }
 
+    /// If the predicate failure is caused by an unmet bound on a tuple, recheck if the bound would
+    /// succeed if all the types on the tuple had no borrows. This is a common problem for libraries
+    /// like Bevy and ORMs, which rely heavily on traits being implemented on tuples.
+    fn lookup_alternative_tuple_impls(
+        &self,
+        err: &mut Diag<'_>,
+        unsatisfied_predicates: &[(
+            ty::Predicate<'tcx>,
+            Option<ty::Predicate<'tcx>>,
+            Option<ObligationCause<'tcx>>,
+        )],
+    ) -> bool {
+        let mut found_tuple = false;
+        for (pred, root, _ob) in unsatisfied_predicates {
+            let mut preds = vec![pred];
+            if let Some(root) = root {
+                // We will look at both the current predicate and the root predicate that caused it
+                // to be needed. If calling something like `<(A, &B)>::default()`, then `pred` is
+                // `&B: Default` and `root` is `(A, &B): Default`, which is the one we are checking
+                // for further down, so we check both.
+                preds.push(root);
+            }
+            for pred in preds {
+                if let Some(clause) = pred.as_clause()
+                    && let Some(clause) = clause.as_trait_clause()
+                    && let ty = clause.self_ty().skip_binder()
+                    && let ty::Tuple(types) = ty.kind()
+                {
+                    let path = clause.skip_binder().trait_ref.print_only_trait_path();
+                    let def_id = clause.def_id();
+                    let ty = Ty::new_tup(
+                        self.tcx,
+                        self.tcx.mk_type_list_from_iter(types.iter().map(|ty| ty.peel_refs())),
+                    );
+                    let args = ty::GenericArgs::for_item(self.tcx, def_id, |param, _| {
+                        if param.index == 0 {
+                            ty.into()
+                        } else {
+                            self.infcx.var_for_def(DUMMY_SP, param)
+                        }
+                    });
+                    if self
+                        .infcx
+                        .type_implements_trait(def_id, args, self.param_env)
+                        .must_apply_modulo_regions()
+                    {
+                        // "`Trait` is implemented for `(A, B)` but not for `(A, &B)`"
+                        let mut msg = DiagStyledString::normal(format!("`{path}` "));
+                        msg.push_highlighted("is");
+                        msg.push_normal(" implemented for `(");
+                        let len = types.len();
+                        for (i, t) in types.iter().enumerate() {
+                            msg.push(
+                                format!("{}", with_forced_trimmed_paths!(t.peel_refs())),
+                                t.peel_refs() != t,
+                            );
+                            if i < len - 1 {
+                                msg.push_normal(", ");
+                            }
+                        }
+                        msg.push_normal(")` but ");
+                        msg.push_highlighted("not");
+                        msg.push_normal(" for `(");
+                        for (i, t) in types.iter().enumerate() {
+                            msg.push(
+                                format!("{}", with_forced_trimmed_paths!(t)),
+                                t.peel_refs() != t,
+                            );
+                            if i < len - 1 {
+                                msg.push_normal(", ");
+                            }
+                        }
+                        msg.push_normal(")`");
+
+                        // Find the span corresponding to the impl that was found to point at it.
+                        if let Some(impl_span) = self
+                            .tcx
+                            .all_impls(def_id)
+                            .filter(|&impl_def_id| {
+                                let header = self.tcx.impl_trait_header(impl_def_id).unwrap();
+                                let trait_ref = header.trait_ref.instantiate(
+                                    self.tcx,
+                                    self.infcx.fresh_args_for_item(DUMMY_SP, impl_def_id),
+                                );
+
+                                let value = ty::fold_regions(self.tcx, ty, |_, _| {
+                                    self.tcx.lifetimes.re_erased
+                                });
+                                // FIXME: Don't bother dealing with non-lifetime binders here...
+                                if value.has_escaping_bound_vars() {
+                                    return false;
+                                }
+                                self.infcx.can_eq(ty::ParamEnv::empty(), trait_ref.self_ty(), value)
+                                    && header.polarity == ty::ImplPolarity::Positive
+                            })
+                            .map(|impl_def_id| self.tcx.def_span(impl_def_id))
+                            .next()
+                        {
+                            err.highlighted_span_note(impl_span, msg.0);
+                        } else {
+                            err.highlighted_note(msg.0);
+                        }
+                        found_tuple = true;
+                    }
+                    // If `pred` was already on the tuple, we don't need to look at the root
+                    // obligation too.
+                    break;
+                }
+            }
+        }
+        found_tuple
+    }
+
     /// If an appropriate error source is not found, check method chain for possible candidates
     fn lookup_segments_chain_for_no_match_method(
         &self,
diff --git a/tests/ui/methods/missing-bound-on-tuple.rs b/tests/ui/methods/missing-bound-on-tuple.rs
new file mode 100644
index 00000000000..25deabf5926
--- /dev/null
+++ b/tests/ui/methods/missing-bound-on-tuple.rs
@@ -0,0 +1,39 @@
+trait WorksOnDefault {
+    fn do_something() {}
+}
+
+impl<T: Default> WorksOnDefault for T {}
+//~^ NOTE the following trait bounds were not satisfied
+//~| NOTE unsatisfied trait bound introduced here
+
+trait Foo {}
+
+trait WorksOnFoo {
+    fn do_be_do() {}
+}
+
+impl<T: Foo> WorksOnFoo for T {}
+//~^ NOTE the following trait bounds were not satisfied
+//~| NOTE unsatisfied trait bound introduced here
+
+impl<A: Foo, B: Foo, C: Foo> Foo for (A, B, C) {}
+//~^ NOTE `Foo` is implemented for `(i32, u32, String)`
+impl Foo for i32 {}
+impl Foo for &i32 {}
+impl Foo for u32 {}
+impl Foo for String {}
+
+fn main() {
+    let _success = <(i32, u32, String)>::do_something();
+    let _failure = <(i32, &u32, String)>::do_something(); //~ ERROR E0599
+    //~^ NOTE `Default` is implemented for `(i32, u32, String)`
+    //~| NOTE function or associated item cannot be called on
+    let _success = <(i32, u32, String)>::do_be_do();
+    let _failure = <(i32, &u32, String)>::do_be_do(); //~ ERROR E0599
+    //~^ NOTE function or associated item cannot be called on
+    let _success = <(i32, u32, String)>::default();
+    let _failure = <(i32, &u32, String)>::default(); //~ ERROR E0599
+    //~^ NOTE `Default` is implemented for `(i32, u32, String)`
+    //~| NOTE function or associated item cannot be called on
+    //~| NOTE the following trait bounds were not satisfied
+}
diff --git a/tests/ui/methods/missing-bound-on-tuple.stderr b/tests/ui/methods/missing-bound-on-tuple.stderr
new file mode 100644
index 00000000000..f3e0897e5e6
--- /dev/null
+++ b/tests/ui/methods/missing-bound-on-tuple.stderr
@@ -0,0 +1,58 @@
+error[E0599]: the function or associated item `do_something` exists for tuple `(i32, &u32, String)`, but its trait bounds were not satisfied
+  --> $DIR/missing-bound-on-tuple.rs:28:43
+   |
+LL |     let _failure = <(i32, &u32, String)>::do_something();
+   |                                           ^^^^^^^^^^^^ function or associated item cannot be called on `(i32, &u32, String)` due to unsatisfied trait bounds
+   |
+note: the following trait bounds were not satisfied:
+      `&(i32, &u32, String): Default`
+      `&mut (i32, &u32, String): Default`
+      `(i32, &u32, String): Default`
+  --> $DIR/missing-bound-on-tuple.rs:5:9
+   |
+LL | impl<T: Default> WorksOnDefault for T {}
+   |         ^^^^^^^  --------------     -
+   |         |
+   |         unsatisfied trait bound introduced here
+note: `Default` is implemented for `(i32, u32, String)` but not for `(i32, &u32, String)`
+  --> $SRC_DIR/core/src/tuple.rs:LL:COL
+   = note: this error originates in the macro `tuple_impls` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error[E0599]: the function or associated item `do_be_do` exists for tuple `(i32, &u32, String)`, but its trait bounds were not satisfied
+  --> $DIR/missing-bound-on-tuple.rs:32:43
+   |
+LL |     let _failure = <(i32, &u32, String)>::do_be_do();
+   |                                           ^^^^^^^^ function or associated item cannot be called on `(i32, &u32, String)` due to unsatisfied trait bounds
+   |
+note: the following trait bounds were not satisfied:
+      `&(i32, &u32, String): Foo`
+      `&mut (i32, &u32, String): Foo`
+      `(i32, &u32, String): Foo`
+  --> $DIR/missing-bound-on-tuple.rs:15:9
+   |
+LL | impl<T: Foo> WorksOnFoo for T {}
+   |         ^^^  ----------     -
+   |         |
+   |         unsatisfied trait bound introduced here
+note: `Foo` is implemented for `(i32, u32, String)` but not for `(i32, &u32, String)`
+  --> $DIR/missing-bound-on-tuple.rs:19:1
+   |
+LL | impl<A: Foo, B: Foo, C: Foo> Foo for (A, B, C) {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error[E0599]: the function or associated item `default` exists for tuple `(i32, &u32, String)`, but its trait bounds were not satisfied
+  --> $DIR/missing-bound-on-tuple.rs:35:43
+   |
+LL |     let _failure = <(i32, &u32, String)>::default();
+   |                                           ^^^^^^^ function or associated item cannot be called on `(i32, &u32, String)` due to unsatisfied trait bounds
+   |
+   = note: the following trait bounds were not satisfied:
+           `&u32: Default`
+           which is required by `(i32, &u32, String): Default`
+note: `Default` is implemented for `(i32, u32, String)` but not for `(i32, &u32, String)`
+  --> $SRC_DIR/core/src/tuple.rs:LL:COL
+   = note: this error originates in the macro `tuple_impls` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: aborting due to 3 previous errors
+
+For more information about this error, try `rustc --explain E0599`.