Skip to content

Commit 97463da

Browse files
BGR360lcnr
authored andcommitted
Make find_similar_impl_candidates a little fuzzier.
1 parent df368ae commit 97463da

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

+113-31
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,
@@ -1084,18 +1099,18 @@ trait InferCtxtPrivExt<'hir, 'tcx> {
10841099
error: &MismatchedProjectionTypes<'tcx>,
10851100
);
10861101

1087-
fn fuzzy_match_tys(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> bool;
1102+
fn fuzzy_match_tys(&self, a: Ty<'tcx>, b: Ty<'tcx>, strip_references: StripReferences) -> bool;
10881103

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

10911106
fn find_similar_impl_candidates(
10921107
&self,
10931108
trait_ref: ty::PolyTraitRef<'tcx>,
1094-
) -> Vec<ty::TraitRef<'tcx>>;
1109+
) -> Vec<ImplCandidate<'tcx>>;
10951110

10961111
fn report_similar_impl_candidates(
10971112
&self,
1098-
impl_candidates: Vec<ty::TraitRef<'tcx>>,
1113+
impl_candidates: Vec<ImplCandidate<'tcx>>,
10991114
err: &mut DiagnosticBuilder<'_>,
11001115
);
11011116

@@ -1389,7 +1404,7 @@ impl<'a, 'tcx> InferCtxtPrivExt<'a, 'tcx> for InferCtxt<'a, 'tcx> {
13891404
});
13901405
}
13911406

1392-
fn fuzzy_match_tys(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> bool {
1407+
fn fuzzy_match_tys(&self, a: Ty<'tcx>, b: Ty<'tcx>, strip_references: StripReferences) -> bool {
13931408
/// returns the fuzzy category of a given type, or None
13941409
/// if the type can be equated to any type.
13951410
fn type_category(t: Ty<'_>) -> Option<u32> {
@@ -1421,6 +1436,23 @@ impl<'a, 'tcx> InferCtxtPrivExt<'a, 'tcx> for InferCtxt<'a, 'tcx> {
14211436
}
14221437
}
14231438

1439+
let strip_reference = |mut t: Ty<'tcx>| -> Ty<'tcx> {
1440+
loop {
1441+
match t.kind() {
1442+
ty::Ref(_, inner, _) | ty::RawPtr(ty::TypeAndMut { ty: inner, .. }) => {
1443+
t = inner
1444+
}
1445+
_ => break t,
1446+
}
1447+
}
1448+
};
1449+
1450+
let (a, b) = if strip_references == StripReferences::Yes {
1451+
(strip_reference(a), strip_reference(b))
1452+
} else {
1453+
(a, b)
1454+
};
1455+
14241456
match (type_category(a), type_category(b)) {
14251457
(Some(cat_a), Some(cat_b)) => match (a.kind(), b.kind()) {
14261458
(&ty::Adt(def_a, _), &ty::Adt(def_b, _)) => def_a == def_b,
@@ -1443,7 +1475,7 @@ impl<'a, 'tcx> InferCtxtPrivExt<'a, 'tcx> for InferCtxt<'a, 'tcx> {
14431475
fn find_similar_impl_candidates(
14441476
&self,
14451477
trait_ref: ty::PolyTraitRef<'tcx>,
1446-
) -> Vec<ty::TraitRef<'tcx>> {
1478+
) -> Vec<ImplCandidate<'tcx>> {
14471479
// We simplify params and strip references here.
14481480
//
14491481
// This both removes a lot of unhelpful suggestions, e.g.
@@ -1461,40 +1493,75 @@ impl<'a, 'tcx> InferCtxtPrivExt<'a, 'tcx> for InferCtxt<'a, 'tcx> {
14611493
let all_impls = self.tcx.all_impls(trait_ref.def_id());
14621494

14631495
match simp {
1464-
Some(simp) => all_impls
1465-
.filter_map(|def_id| {
1466-
let imp = self.tcx.impl_trait_ref(def_id).unwrap();
1467-
let imp_simp = fast_reject::simplify_type(
1468-
self.tcx,
1469-
imp.self_ty(),
1470-
SimplifyParams::Yes,
1471-
StripReferences::Yes,
1472-
);
1473-
if let Some(imp_simp) = imp_simp {
1474-
if simp != imp_simp {
1496+
Some(simp) => {
1497+
all_impls
1498+
.filter_map(|def_id| {
1499+
if self.tcx.impl_polarity(def_id) == ty::ImplPolarity::Negative {
14751500
return None;
14761501
}
1477-
}
1478-
if self.tcx.impl_polarity(def_id) == ty::ImplPolarity::Negative {
1479-
return None;
1480-
}
1481-
Some(imp)
1482-
})
1483-
.collect(),
1502+
1503+
let imp = self.tcx.impl_trait_ref(def_id).unwrap();
1504+
1505+
// Check for exact match.
1506+
if trait_ref.skip_binder().self_ty() == imp.self_ty() {
1507+
return Some(ImplCandidate {
1508+
trait_ref: imp,
1509+
similarity: CandidateSimilarity::Exact,
1510+
});
1511+
}
1512+
1513+
// Check for match between simplified types.
1514+
let imp_simp = fast_reject::simplify_type(
1515+
self.tcx,
1516+
imp.self_ty(),
1517+
SimplifyParams::Yes,
1518+
StripReferences::Yes,
1519+
);
1520+
if let Some(imp_simp) = imp_simp {
1521+
if simp == imp_simp {
1522+
return Some(ImplCandidate {
1523+
trait_ref: imp,
1524+
similarity: CandidateSimilarity::Simplified,
1525+
});
1526+
}
1527+
}
1528+
1529+
// Check for fuzzy match.
1530+
// Pass `StripReferences::Yes` because although we do want to
1531+
// be fuzzier than `simplify_type`, we don't want to be
1532+
// *too* fuzzy.
1533+
if self.fuzzy_match_tys(
1534+
trait_ref.skip_binder().self_ty(),
1535+
imp.self_ty(),
1536+
StripReferences::Yes,
1537+
) {
1538+
return Some(ImplCandidate {
1539+
trait_ref: imp,
1540+
similarity: CandidateSimilarity::Fuzzy,
1541+
});
1542+
}
1543+
1544+
None
1545+
})
1546+
.collect()
1547+
}
14841548
None => all_impls
14851549
.filter_map(|def_id| {
14861550
if self.tcx.impl_polarity(def_id) == ty::ImplPolarity::Negative {
14871551
return None;
14881552
}
1489-
self.tcx.impl_trait_ref(def_id)
1553+
self.tcx.impl_trait_ref(def_id).map(|trait_ref| ImplCandidate {
1554+
trait_ref,
1555+
similarity: CandidateSimilarity::Unknown,
1556+
})
14901557
})
14911558
.collect(),
14921559
}
14931560
}
14941561

14951562
fn report_similar_impl_candidates(
14961563
&self,
1497-
impl_candidates: Vec<ty::TraitRef<'tcx>>,
1564+
impl_candidates: Vec<ImplCandidate<'tcx>>,
14981565
err: &mut DiagnosticBuilder<'_>,
14991566
) {
15001567
if impl_candidates.is_empty() {
@@ -1518,13 +1585,24 @@ impl<'a, 'tcx> InferCtxtPrivExt<'a, 'tcx> for InferCtxt<'a, 'tcx> {
15181585
};
15191586

15201587
// Sort impl candidates so that ordering is consistent for UI tests.
1521-
let mut normalized_impl_candidates =
1522-
impl_candidates.iter().copied().map(normalize).collect::<Vec<String>>();
1523-
1524-
// Sort before taking the `..end` range,
15251588
// because the ordering of `impl_candidates` may not be deterministic:
15261589
// https://github.com/rust-lang/rust/pull/57475#issuecomment-455519507
1527-
normalized_impl_candidates.sort();
1590+
//
1591+
// Prefer more similar candidates first, then sort lexicographically
1592+
// by their normalized string representation.
1593+
let mut normalized_impl_candidates_and_similarities = impl_candidates
1594+
.into_iter()
1595+
.map(|ImplCandidate { trait_ref, similarity }| {
1596+
let normalized = normalize(trait_ref);
1597+
(similarity, normalized)
1598+
})
1599+
.collect::<Vec<_>>();
1600+
normalized_impl_candidates_and_similarities.sort();
1601+
1602+
let normalized_impl_candidates = normalized_impl_candidates_and_similarities
1603+
.into_iter()
1604+
.map(|(_, normalized)| normalized)
1605+
.collect::<Vec<_>>();
15281606

15291607
err.help(&format!(
15301608
"the following implementations were found:{}{}",
@@ -1688,7 +1766,11 @@ impl<'a, 'tcx> InferCtxtPrivExt<'a, 'tcx> for InferCtxt<'a, 'tcx> {
16881766
return;
16891767
}
16901768

1691-
let impl_candidates = self.find_similar_impl_candidates(trait_ref);
1769+
let impl_candidates = self
1770+
.find_similar_impl_candidates(trait_ref)
1771+
.into_iter()
1772+
.map(|candidate| candidate.trait_ref)
1773+
.collect();
16921774
let mut err = self.emit_inference_failure_err(
16931775
body_id,
16941776
span,

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

+2-1
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

+10
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

+2
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

+2
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

+2-1
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

+8
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

+3
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

+2
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

+3
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)