From 585a40963ea59808e74803f8610659a505b145e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Wed, 4 Jun 2025 18:18:06 +0000 Subject: [PATCH] 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. --- .../rustc_hir_typeck/src/method/suggest.rs | 123 +++++++++++++++++- tests/ui/methods/missing-bound-on-tuple.rs | 39 ++++++ .../ui/methods/missing-bound-on-tuple.stderr | 58 +++++++++ 3 files changed, 218 insertions(+), 2 deletions(-) create mode 100644 tests/ui/methods/missing-bound-on-tuple.rs create mode 100644 tests/ui/methods/missing-bound-on-tuple.stderr diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index 7b71f5de7569f..54e0a42e79944 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>, + Option>, + )], + ) -> 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 0000000000000..25deabf59267b --- /dev/null +++ b/tests/ui/methods/missing-bound-on-tuple.rs @@ -0,0 +1,39 @@ +trait WorksOnDefault { + fn do_something() {} +} + +impl 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 WorksOnFoo for T {} +//~^ NOTE the following trait bounds were not satisfied +//~| NOTE unsatisfied trait bound introduced here + +impl 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 0000000000000..f3e0897e5e645 --- /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 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 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 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`.