Skip to content

Commit c77191f

Browse files
authored
Rollup merge of #142034 - estebank:issue-141258, r=davidtwco
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. <img width="1166" alt="Screenshot 2025-06-04 at 10 38 24 AM" src="https://github.com/user-attachments/assets/d257c9ea-c2d7-42e7-8473-8b93aa54b8e0" /> Address #141258. I believe that more combination of cases in the tuple types should be handled (like adding borrows and checking when a specific type needs to not be a borrow while the rest stay the same), but for now this handles the most common case.
2 parents 8b6d55e + 585a409 commit c77191f

File tree

3 files changed

+218
-2
lines changed

3 files changed

+218
-2
lines changed

compiler/rustc_hir_typeck/src/method/suggest.rs

Lines changed: 121 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
1414
use rustc_data_structures::sorted_map::SortedMap;
1515
use rustc_data_structures::unord::UnordSet;
1616
use rustc_errors::codes::*;
17-
use rustc_errors::{Applicability, Diag, MultiSpan, StashKey, pluralize, struct_span_code_err};
17+
use rustc_errors::{
18+
Applicability, Diag, DiagStyledString, MultiSpan, StashKey, pluralize, struct_span_code_err,
19+
};
1820
use rustc_hir::def::{CtorKind, DefKind, Res};
1921
use rustc_hir::def_id::DefId;
2022
use rustc_hir::intravisit::{self, Visitor};
@@ -1569,7 +1571,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
15691571
);
15701572
}
15711573

1572-
if rcvr_ty.is_numeric() && rcvr_ty.is_fresh() || restrict_type_params || suggested_derive {
1574+
if rcvr_ty.is_numeric() && rcvr_ty.is_fresh()
1575+
|| restrict_type_params
1576+
|| suggested_derive
1577+
|| self.lookup_alternative_tuple_impls(&mut err, &unsatisfied_predicates)
1578+
{
15731579
} else {
15741580
self.suggest_traits_to_import(
15751581
&mut err,
@@ -1744,6 +1750,119 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
17441750
err.emit()
17451751
}
17461752

1753+
/// If the predicate failure is caused by an unmet bound on a tuple, recheck if the bound would
1754+
/// succeed if all the types on the tuple had no borrows. This is a common problem for libraries
1755+
/// like Bevy and ORMs, which rely heavily on traits being implemented on tuples.
1756+
fn lookup_alternative_tuple_impls(
1757+
&self,
1758+
err: &mut Diag<'_>,
1759+
unsatisfied_predicates: &[(
1760+
ty::Predicate<'tcx>,
1761+
Option<ty::Predicate<'tcx>>,
1762+
Option<ObligationCause<'tcx>>,
1763+
)],
1764+
) -> bool {
1765+
let mut found_tuple = false;
1766+
for (pred, root, _ob) in unsatisfied_predicates {
1767+
let mut preds = vec![pred];
1768+
if let Some(root) = root {
1769+
// We will look at both the current predicate and the root predicate that caused it
1770+
// to be needed. If calling something like `<(A, &B)>::default()`, then `pred` is
1771+
// `&B: Default` and `root` is `(A, &B): Default`, which is the one we are checking
1772+
// for further down, so we check both.
1773+
preds.push(root);
1774+
}
1775+
for pred in preds {
1776+
if let Some(clause) = pred.as_clause()
1777+
&& let Some(clause) = clause.as_trait_clause()
1778+
&& let ty = clause.self_ty().skip_binder()
1779+
&& let ty::Tuple(types) = ty.kind()
1780+
{
1781+
let path = clause.skip_binder().trait_ref.print_only_trait_path();
1782+
let def_id = clause.def_id();
1783+
let ty = Ty::new_tup(
1784+
self.tcx,
1785+
self.tcx.mk_type_list_from_iter(types.iter().map(|ty| ty.peel_refs())),
1786+
);
1787+
let args = ty::GenericArgs::for_item(self.tcx, def_id, |param, _| {
1788+
if param.index == 0 {
1789+
ty.into()
1790+
} else {
1791+
self.infcx.var_for_def(DUMMY_SP, param)
1792+
}
1793+
});
1794+
if self
1795+
.infcx
1796+
.type_implements_trait(def_id, args, self.param_env)
1797+
.must_apply_modulo_regions()
1798+
{
1799+
// "`Trait` is implemented for `(A, B)` but not for `(A, &B)`"
1800+
let mut msg = DiagStyledString::normal(format!("`{path}` "));
1801+
msg.push_highlighted("is");
1802+
msg.push_normal(" implemented for `(");
1803+
let len = types.len();
1804+
for (i, t) in types.iter().enumerate() {
1805+
msg.push(
1806+
format!("{}", with_forced_trimmed_paths!(t.peel_refs())),
1807+
t.peel_refs() != t,
1808+
);
1809+
if i < len - 1 {
1810+
msg.push_normal(", ");
1811+
}
1812+
}
1813+
msg.push_normal(")` but ");
1814+
msg.push_highlighted("not");
1815+
msg.push_normal(" for `(");
1816+
for (i, t) in types.iter().enumerate() {
1817+
msg.push(
1818+
format!("{}", with_forced_trimmed_paths!(t)),
1819+
t.peel_refs() != t,
1820+
);
1821+
if i < len - 1 {
1822+
msg.push_normal(", ");
1823+
}
1824+
}
1825+
msg.push_normal(")`");
1826+
1827+
// Find the span corresponding to the impl that was found to point at it.
1828+
if let Some(impl_span) = self
1829+
.tcx
1830+
.all_impls(def_id)
1831+
.filter(|&impl_def_id| {
1832+
let header = self.tcx.impl_trait_header(impl_def_id).unwrap();
1833+
let trait_ref = header.trait_ref.instantiate(
1834+
self.tcx,
1835+
self.infcx.fresh_args_for_item(DUMMY_SP, impl_def_id),
1836+
);
1837+
1838+
let value = ty::fold_regions(self.tcx, ty, |_, _| {
1839+
self.tcx.lifetimes.re_erased
1840+
});
1841+
// FIXME: Don't bother dealing with non-lifetime binders here...
1842+
if value.has_escaping_bound_vars() {
1843+
return false;
1844+
}
1845+
self.infcx.can_eq(ty::ParamEnv::empty(), trait_ref.self_ty(), value)
1846+
&& header.polarity == ty::ImplPolarity::Positive
1847+
})
1848+
.map(|impl_def_id| self.tcx.def_span(impl_def_id))
1849+
.next()
1850+
{
1851+
err.highlighted_span_note(impl_span, msg.0);
1852+
} else {
1853+
err.highlighted_note(msg.0);
1854+
}
1855+
found_tuple = true;
1856+
}
1857+
// If `pred` was already on the tuple, we don't need to look at the root
1858+
// obligation too.
1859+
break;
1860+
}
1861+
}
1862+
}
1863+
found_tuple
1864+
}
1865+
17471866
/// If an appropriate error source is not found, check method chain for possible candidates
17481867
fn lookup_segments_chain_for_no_match_method(
17491868
&self,
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
trait WorksOnDefault {
2+
fn do_something() {}
3+
}
4+
5+
impl<T: Default> WorksOnDefault for T {}
6+
//~^ NOTE the following trait bounds were not satisfied
7+
//~| NOTE unsatisfied trait bound introduced here
8+
9+
trait Foo {}
10+
11+
trait WorksOnFoo {
12+
fn do_be_do() {}
13+
}
14+
15+
impl<T: Foo> WorksOnFoo for T {}
16+
//~^ NOTE the following trait bounds were not satisfied
17+
//~| NOTE unsatisfied trait bound introduced here
18+
19+
impl<A: Foo, B: Foo, C: Foo> Foo for (A, B, C) {}
20+
//~^ NOTE `Foo` is implemented for `(i32, u32, String)`
21+
impl Foo for i32 {}
22+
impl Foo for &i32 {}
23+
impl Foo for u32 {}
24+
impl Foo for String {}
25+
26+
fn main() {
27+
let _success = <(i32, u32, String)>::do_something();
28+
let _failure = <(i32, &u32, String)>::do_something(); //~ ERROR E0599
29+
//~^ NOTE `Default` is implemented for `(i32, u32, String)`
30+
//~| NOTE function or associated item cannot be called on
31+
let _success = <(i32, u32, String)>::do_be_do();
32+
let _failure = <(i32, &u32, String)>::do_be_do(); //~ ERROR E0599
33+
//~^ NOTE function or associated item cannot be called on
34+
let _success = <(i32, u32, String)>::default();
35+
let _failure = <(i32, &u32, String)>::default(); //~ ERROR E0599
36+
//~^ NOTE `Default` is implemented for `(i32, u32, String)`
37+
//~| NOTE function or associated item cannot be called on
38+
//~| NOTE the following trait bounds were not satisfied
39+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
error[E0599]: the function or associated item `do_something` exists for tuple `(i32, &u32, String)`, but its trait bounds were not satisfied
2+
--> $DIR/missing-bound-on-tuple.rs:28:43
3+
|
4+
LL | let _failure = <(i32, &u32, String)>::do_something();
5+
| ^^^^^^^^^^^^ function or associated item cannot be called on `(i32, &u32, String)` due to unsatisfied trait bounds
6+
|
7+
note: the following trait bounds were not satisfied:
8+
`&(i32, &u32, String): Default`
9+
`&mut (i32, &u32, String): Default`
10+
`(i32, &u32, String): Default`
11+
--> $DIR/missing-bound-on-tuple.rs:5:9
12+
|
13+
LL | impl<T: Default> WorksOnDefault for T {}
14+
| ^^^^^^^ -------------- -
15+
| |
16+
| unsatisfied trait bound introduced here
17+
note: `Default` is implemented for `(i32, u32, String)` but not for `(i32, &u32, String)`
18+
--> $SRC_DIR/core/src/tuple.rs:LL:COL
19+
= note: this error originates in the macro `tuple_impls` (in Nightly builds, run with -Z macro-backtrace for more info)
20+
21+
error[E0599]: the function or associated item `do_be_do` exists for tuple `(i32, &u32, String)`, but its trait bounds were not satisfied
22+
--> $DIR/missing-bound-on-tuple.rs:32:43
23+
|
24+
LL | let _failure = <(i32, &u32, String)>::do_be_do();
25+
| ^^^^^^^^ function or associated item cannot be called on `(i32, &u32, String)` due to unsatisfied trait bounds
26+
|
27+
note: the following trait bounds were not satisfied:
28+
`&(i32, &u32, String): Foo`
29+
`&mut (i32, &u32, String): Foo`
30+
`(i32, &u32, String): Foo`
31+
--> $DIR/missing-bound-on-tuple.rs:15:9
32+
|
33+
LL | impl<T: Foo> WorksOnFoo for T {}
34+
| ^^^ ---------- -
35+
| |
36+
| unsatisfied trait bound introduced here
37+
note: `Foo` is implemented for `(i32, u32, String)` but not for `(i32, &u32, String)`
38+
--> $DIR/missing-bound-on-tuple.rs:19:1
39+
|
40+
LL | impl<A: Foo, B: Foo, C: Foo> Foo for (A, B, C) {}
41+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
42+
43+
error[E0599]: the function or associated item `default` exists for tuple `(i32, &u32, String)`, but its trait bounds were not satisfied
44+
--> $DIR/missing-bound-on-tuple.rs:35:43
45+
|
46+
LL | let _failure = <(i32, &u32, String)>::default();
47+
| ^^^^^^^ function or associated item cannot be called on `(i32, &u32, String)` due to unsatisfied trait bounds
48+
|
49+
= note: the following trait bounds were not satisfied:
50+
`&u32: Default`
51+
which is required by `(i32, &u32, String): Default`
52+
note: `Default` is implemented for `(i32, u32, String)` but not for `(i32, &u32, String)`
53+
--> $SRC_DIR/core/src/tuple.rs:LL:COL
54+
= note: this error originates in the macro `tuple_impls` (in Nightly builds, run with -Z macro-backtrace for more info)
55+
56+
error: aborting due to 3 previous errors
57+
58+
For more information about this error, try `rustc --explain E0599`.

0 commit comments

Comments
 (0)