Skip to content

Commit 002456a

Browse files
BGR360lcnr
authored andcommitted
Make find_similar_impl_candidates a little fuzzier.
1 parent 1e12aef commit 002456a

File tree

10 files changed

+147
-33
lines changed

10 files changed

+147
-33
lines changed

compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs

Lines changed: 113 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,21 @@ use suggestions::InferCtxtExt as _;
4040

4141
pub use rustc_infer::traits::error_reporting::*;
4242

43+
// When outputting impl candidates, prefer showing those that are more similar.
44+
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
45+
pub enum CandidateSimilarity {
46+
Exact,
47+
Simplified,
48+
Fuzzy,
49+
Unknown,
50+
}
51+
52+
#[derive(Debug, Clone, Copy)]
53+
pub struct ImplCandidate<'tcx> {
54+
pub trait_ref: ty::TraitRef<'tcx>,
55+
pub similarity: CandidateSimilarity,
56+
}
57+
4358
pub trait InferCtxtExt<'tcx> {
4459
fn report_fulfillment_errors(
4560
&self,
@@ -1143,18 +1158,18 @@ trait InferCtxtPrivExt<'hir, 'tcx> {
11431158
error: &MismatchedProjectionTypes<'tcx>,
11441159
);
11451160

1146-
fn fuzzy_match_tys(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> bool;
1161+
fn fuzzy_match_tys(&self, a: Ty<'tcx>, b: Ty<'tcx>, strip_references: StripReferences) -> bool;
11471162

11481163
fn describe_generator(&self, body_id: hir::BodyId) -> Option<&'static str>;
11491164

11501165
fn find_similar_impl_candidates(
11511166
&self,
11521167
trait_ref: ty::PolyTraitRef<'tcx>,
1153-
) -> Vec<ty::TraitRef<'tcx>>;
1168+
) -> Vec<ImplCandidate<'tcx>>;
11541169

11551170
fn report_similar_impl_candidates(
11561171
&self,
1157-
impl_candidates: Vec<ty::TraitRef<'tcx>>,
1172+
impl_candidates: Vec<ImplCandidate<'tcx>>,
11581173
err: &mut DiagnosticBuilder<'_>,
11591174
);
11601175

@@ -1446,7 +1461,7 @@ impl<'a, 'tcx> InferCtxtPrivExt<'a, 'tcx> for InferCtxt<'a, 'tcx> {
14461461
});
14471462
}
14481463

1449-
fn fuzzy_match_tys(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> bool {
1464+
fn fuzzy_match_tys(&self, a: Ty<'tcx>, b: Ty<'tcx>, strip_references: StripReferences) -> bool {
14501465
/// returns the fuzzy category of a given type, or None
14511466
/// if the type can be equated to any type.
14521467
fn type_category(t: Ty<'_>) -> Option<u32> {
@@ -1478,6 +1493,23 @@ impl<'a, 'tcx> InferCtxtPrivExt<'a, 'tcx> for InferCtxt<'a, 'tcx> {
14781493
}
14791494
}
14801495

1496+
let strip_reference = |mut t: Ty<'tcx>| -> Ty<'tcx> {
1497+
loop {
1498+
match t.kind() {
1499+
ty::Ref(_, inner, _) | ty::RawPtr(ty::TypeAndMut { ty: inner, .. }) => {
1500+
t = inner
1501+
}
1502+
_ => break t,
1503+
}
1504+
}
1505+
};
1506+
1507+
let (a, b) = if strip_references == StripReferences::Yes {
1508+
(strip_reference(a), strip_reference(b))
1509+
} else {
1510+
(a, b)
1511+
};
1512+
14811513
match (type_category(a), type_category(b)) {
14821514
(Some(cat_a), Some(cat_b)) => match (a.kind(), b.kind()) {
14831515
(&ty::Adt(def_a, _), &ty::Adt(def_b, _)) => def_a == def_b,
@@ -1500,7 +1532,7 @@ impl<'a, 'tcx> InferCtxtPrivExt<'a, 'tcx> for InferCtxt<'a, 'tcx> {
15001532
fn find_similar_impl_candidates(
15011533
&self,
15021534
trait_ref: ty::PolyTraitRef<'tcx>,
1503-
) -> Vec<ty::TraitRef<'tcx>> {
1535+
) -> Vec<ImplCandidate<'tcx>> {
15041536
// We simplify params and strip references here.
15051537
//
15061538
// This both removes a lot of unhelpful suggestions, e.g.
@@ -1518,40 +1550,75 @@ impl<'a, 'tcx> InferCtxtPrivExt<'a, 'tcx> for InferCtxt<'a, 'tcx> {
15181550
let all_impls = self.tcx.all_impls(trait_ref.def_id());
15191551

15201552
match simp {
1521-
Some(simp) => all_impls
1522-
.filter_map(|def_id| {
1523-
let imp = self.tcx.impl_trait_ref(def_id).unwrap();
1524-
let imp_simp = fast_reject::simplify_type(
1525-
self.tcx,
1526-
imp.self_ty(),
1527-
SimplifyParams::Yes,
1528-
StripReferences::Yes,
1529-
);
1530-
if let Some(imp_simp) = imp_simp {
1531-
if simp != imp_simp {
1553+
Some(simp) => {
1554+
all_impls
1555+
.filter_map(|def_id| {
1556+
if self.tcx.impl_polarity(def_id) == ty::ImplPolarity::Negative {
15321557
return None;
15331558
}
1534-
}
1535-
if self.tcx.impl_polarity(def_id) == ty::ImplPolarity::Negative {
1536-
return None;
1537-
}
1538-
Some(imp)
1539-
})
1540-
.collect(),
1559+
1560+
let imp = self.tcx.impl_trait_ref(def_id).unwrap();
1561+
1562+
// Check for exact match.
1563+
if trait_ref.skip_binder().self_ty() == imp.self_ty() {
1564+
return Some(ImplCandidate {
1565+
trait_ref: imp,
1566+
similarity: CandidateSimilarity::Exact,
1567+
});
1568+
}
1569+
1570+
// Check for match between simplified types.
1571+
let imp_simp = fast_reject::simplify_type(
1572+
self.tcx,
1573+
imp.self_ty(),
1574+
SimplifyParams::Yes,
1575+
StripReferences::Yes,
1576+
);
1577+
if let Some(imp_simp) = imp_simp {
1578+
if simp == imp_simp {
1579+
return Some(ImplCandidate {
1580+
trait_ref: imp,
1581+
similarity: CandidateSimilarity::Simplified,
1582+
});
1583+
}
1584+
}
1585+
1586+
// Check for fuzzy match.
1587+
// Pass `StripReferences::Yes` because although we do want to
1588+
// be fuzzier than `simplify_type`, we don't want to be
1589+
// *too* fuzzy.
1590+
if self.fuzzy_match_tys(
1591+
trait_ref.skip_binder().self_ty(),
1592+
imp.self_ty(),
1593+
StripReferences::Yes,
1594+
) {
1595+
return Some(ImplCandidate {
1596+
trait_ref: imp,
1597+
similarity: CandidateSimilarity::Fuzzy,
1598+
});
1599+
}
1600+
1601+
None
1602+
})
1603+
.collect()
1604+
}
15411605
None => all_impls
15421606
.filter_map(|def_id| {
15431607
if self.tcx.impl_polarity(def_id) == ty::ImplPolarity::Negative {
15441608
return None;
15451609
}
1546-
self.tcx.impl_trait_ref(def_id)
1610+
self.tcx.impl_trait_ref(def_id).map(|trait_ref| ImplCandidate {
1611+
trait_ref,
1612+
similarity: CandidateSimilarity::Unknown,
1613+
})
15471614
})
15481615
.collect(),
15491616
}
15501617
}
15511618

15521619
fn report_similar_impl_candidates(
15531620
&self,
1554-
impl_candidates: Vec<ty::TraitRef<'tcx>>,
1621+
impl_candidates: Vec<ImplCandidate<'tcx>>,
15551622
err: &mut DiagnosticBuilder<'_>,
15561623
) {
15571624
if impl_candidates.is_empty() {
@@ -1575,13 +1642,24 @@ impl<'a, 'tcx> InferCtxtPrivExt<'a, 'tcx> for InferCtxt<'a, 'tcx> {
15751642
};
15761643

15771644
// Sort impl candidates so that ordering is consistent for UI tests.
1578-
let mut normalized_impl_candidates =
1579-
impl_candidates.iter().copied().map(normalize).collect::<Vec<String>>();
1580-
1581-
// Sort before taking the `..end` range,
15821645
// because the ordering of `impl_candidates` may not be deterministic:
15831646
// https://github.com/rust-lang/rust/pull/57475#issuecomment-455519507
1584-
normalized_impl_candidates.sort();
1647+
//
1648+
// Prefer more similar candidates first, then sort lexicographically
1649+
// by their normalized string representation.
1650+
let mut normalized_impl_candidates_and_similarities = impl_candidates
1651+
.into_iter()
1652+
.map(|ImplCandidate { trait_ref, similarity }| {
1653+
let normalized = normalize(trait_ref);
1654+
(similarity, normalized)
1655+
})
1656+
.collect::<Vec<_>>();
1657+
normalized_impl_candidates_and_similarities.sort();
1658+
1659+
let normalized_impl_candidates = normalized_impl_candidates_and_similarities
1660+
.into_iter()
1661+
.map(|(_, normalized)| normalized)
1662+
.collect::<Vec<_>>();
15851663

15861664
err.help(&format!(
15871665
"the following implementations were found:{}{}",
@@ -1744,7 +1822,11 @@ impl<'a, 'tcx> InferCtxtPrivExt<'a, 'tcx> for InferCtxt<'a, 'tcx> {
17441822
return;
17451823
}
17461824

1747-
let impl_candidates = self.find_similar_impl_candidates(trait_ref);
1825+
let impl_candidates = self
1826+
.find_similar_impl_candidates(trait_ref)
1827+
.into_iter()
1828+
.map(|candidate| candidate.trait_ref)
1829+
.collect();
17481830
let mut err = self.emit_inference_failure_err(
17491831
body_id,
17501832
span,

compiler/rustc_trait_selection/src/traits/error_reporting/on_unimplemented.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use super::{
44
use crate::infer::InferCtxt;
55
use rustc_hir as hir;
66
use rustc_hir::def_id::DefId;
7+
use rustc_middle::ty::fast_reject::StripReferences;
78
use rustc_middle::ty::subst::Subst;
89
use rustc_middle::ty::{self, GenericParamDefKind};
910
use rustc_span::symbol::sym;
@@ -56,7 +57,7 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
5657
trait_ref.substs.types().skip(1),
5758
impl_trait_ref.substs.types().skip(1),
5859
)
59-
.all(|(u, v)| self.fuzzy_match_tys(u, v))
60+
.all(|(u, v)| self.fuzzy_match_tys(u, v, StripReferences::No))
6061
{
6162
fuzzy_match_impls.push(def_id);
6263
}

src/test/ui/associated-types/associated-types-path-2.stderr

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ error[E0277]: the trait bound `u32: Foo` is not satisfied
1515
LL | f1(2u32, 4u32);
1616
| ^^ the trait `Foo` is not implemented for `u32`
1717
|
18+
= help: the following implementations were found:
19+
<i32 as Foo>
1820
note: required by a bound in `f1`
1921
--> $DIR/associated-types-path-2.rs:13:14
2022
|
@@ -26,6 +28,9 @@ error[E0277]: the trait bound `u32: Foo` is not satisfied
2628
|
2729
LL | f1(2u32, 4u32);
2830
| ^^^^ the trait `Foo` is not implemented for `u32`
31+
|
32+
= help: the following implementations were found:
33+
<i32 as Foo>
2934

3035
error[E0277]: the trait bound `u32: Foo` is not satisfied
3136
--> $DIR/associated-types-path-2.rs:35:8
@@ -35,6 +40,8 @@ LL | f1(2u32, 4i32);
3540
| |
3641
| required by a bound introduced by this call
3742
|
43+
= help: the following implementations were found:
44+
<i32 as Foo>
3845
note: required by a bound in `f1`
3946
--> $DIR/associated-types-path-2.rs:13:14
4047
|
@@ -46,6 +53,9 @@ error[E0277]: the trait bound `u32: Foo` is not satisfied
4653
|
4754
LL | f1(2u32, 4i32);
4855
| ^^^^ the trait `Foo` is not implemented for `u32`
56+
|
57+
= help: the following implementations were found:
58+
<i32 as Foo>
4959

5060
error[E0308]: mismatched types
5161
--> $DIR/associated-types-path-2.rs:41:18

src/test/ui/auto-traits/typeck-default-trait-impl-precedence.stderr

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ error[E0277]: the trait bound `u32: Signed` is not satisfied
44
LL | is_defaulted::<&'static u32>();
55
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Signed` is not implemented for `u32`
66
|
7+
= help: the following implementations were found:
8+
<i32 as Signed>
79
note: required because of the requirements on the impl of `Defaulted` for `&'static u32`
810
--> $DIR/typeck-default-trait-impl-precedence.rs:10:19
911
|

src/test/ui/const-generics/defaults/rp_impl_trait_fail.stderr

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ LL | fn uwu<const N: u8>() -> impl Traitor<N> {
1515
|
1616
= help: the following implementations were found:
1717
<u32 as Traitor<N, 2_u8>>
18+
<u64 as Traitor<1_u8, 2_u8>>
1819

1920
error[E0277]: the trait bound `u64: Traitor<1_u8, 1_u8>` is not satisfied
2021
--> $DIR/rp_impl_trait_fail.rs:22:13
@@ -24,6 +25,7 @@ LL | fn owo() -> impl Traitor {
2425
|
2526
= help: the following implementations were found:
2627
<u64 as Traitor<1_u8, 2_u8>>
28+
<u32 as Traitor<N, 2_u8>>
2729

2830
error: aborting due to 3 previous errors
2931

src/test/ui/did_you_mean/issue-39802-show-5-trait-impls.stderr

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ LL | Foo::<i32>::bar(&1i8);
1111
<i8 as Foo<u16>>
1212
<i8 as Foo<u32>>
1313
<i8 as Foo<u64>>
14-
<i8 as Foo<u8>>
14+
and 5 others
1515

1616
error[E0277]: the trait bound `u8: Foo<i32>` is not satisfied
1717
--> $DIR/issue-39802-show-5-trait-impls.rs:25:21
@@ -26,6 +26,7 @@ LL | Foo::<i32>::bar(&1u8);
2626
<u8 as Foo<u16>>
2727
<u8 as Foo<u32>>
2828
<u8 as Foo<u64>>
29+
and 5 others
2930

3031
error[E0277]: the trait bound `bool: Foo<i32>` is not satisfied
3132
--> $DIR/issue-39802-show-5-trait-impls.rs:26:21

src/test/ui/kindck/kindck-copy.stderr

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ LL | assert_copy::<&'static mut isize>();
66
|
77
= help: the following implementations were found:
88
<isize as Copy>
9+
<i128 as Copy>
10+
<i16 as Copy>
11+
<i32 as Copy>
12+
and 8 others
913
note: required by a bound in `assert_copy`
1014
--> $DIR/kindck-copy.rs:5:18
1115
|
@@ -20,6 +24,10 @@ LL | assert_copy::<&'a mut isize>();
2024
|
2125
= help: the following implementations were found:
2226
<isize as Copy>
27+
<i128 as Copy>
28+
<i16 as Copy>
29+
<i32 as Copy>
30+
and 8 others
2331
note: required by a bound in `assert_copy`
2432
--> $DIR/kindck-copy.rs:5:18
2533
|

src/test/ui/suggestions/issue-71394-no-from-impl.stderr

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ error[E0277]: the trait bound `&[i8]: From<&[u8]>` is not satisfied
44
LL | let _: &[i8] = data.into();
55
| ^^^^ the trait `From<&[u8]>` is not implemented for `&[i8]`
66
|
7+
= help: the following implementations were found:
8+
<[T; LANES] as From<Simd<T, LANES>>>
9+
<[bool; LANES] as From<Mask<T, LANES>>>
710
= note: required because of the requirements on the impl of `Into<&[i8]>` for `&[u8]`
811

912
error: aborting due to previous error

src/test/ui/traits/issue-79458.stderr

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ LL | bar: &'a mut T
99
|
1010
= help: the following implementations were found:
1111
<&T as Clone>
12+
<*const T as Clone>
13+
<*mut T as Clone>
1214
= note: `Clone` is implemented for `&T`, but not for `&mut T`
1315
= note: this error originates in the derive macro `Clone` (in Nightly builds, run with -Z macro-backtrace for more info)
1416

src/test/ui/try-trait/bad-interconversion.stderr

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ LL | Ok(Err(123_i32)?)
1010
= help: the following implementations were found:
1111
<u8 as From<NonZeroU8>>
1212
<u8 as From<bool>>
13+
<i128 as From<NonZeroI128>>
14+
<i128 as From<bool>>
15+
and 60 others
1316
= note: required because of the requirements on the impl of `FromResidual<Result<Infallible, i32>>` for `Result<u64, u8>`
1417

1518
error[E0277]: the `?` operator can only be used on `Result`s, not `Option`s, in a function that returns `Result`

0 commit comments

Comments
 (0)