Skip to content

Commit b479550

Browse files
committed
autoderef: completely resolve and deduplicate types
1 parent b7497fc commit b479550

File tree

3 files changed

+69
-5
lines changed

3 files changed

+69
-5
lines changed

crates/hir-ty/src/autoderef.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,37 @@ pub(crate) enum AutoderefKind {
2222
Overloaded,
2323
}
2424

25+
/// Returns types that `ty` transitively dereferences to. This function is only meant to be used
26+
/// outside `hir-ty`.
27+
///
28+
/// It is guaranteed that:
29+
/// - the yielded types don't contain inference variables (but may contain `TyKind::Error`).
30+
/// - a type won't be yielded more than once; in other words, the returned iterator will stop if it
31+
/// detects a cycle in the deref chain.
2532
pub fn autoderef(
2633
db: &dyn HirDatabase,
2734
env: Arc<TraitEnvironment>,
2835
ty: Canonical<Ty>,
29-
) -> impl Iterator<Item = Canonical<Ty>> + '_ {
36+
) -> impl Iterator<Item = Ty> {
3037
let mut table = InferenceTable::new(db, env);
3138
let ty = table.instantiate_canonical(ty);
3239
let mut autoderef = Autoderef::new(&mut table, ty);
3340
let mut v = Vec::new();
3441
while let Some((ty, _steps)) = autoderef.next() {
35-
v.push(autoderef.table.canonicalize(ty).value);
42+
// `ty` may contain unresolved inference variables. Since there's no chance they would be
43+
// resolved, just replace with fallback type.
44+
let resolved = autoderef.table.resolve_completely(ty);
45+
46+
// If the deref chain contains a cycle (e.g. `A` derefs to `B` and `B` derefs to `A`), we
47+
// would revisit some already visited types. Stop here to avoid duplication.
48+
//
49+
// XXX: The recursion limit for `Autoderef` is currently 10, so `Vec::contains()` shouldn't
50+
// be too expensive. Replace this duplicate check with `FxHashSet` if it proves to be more
51+
// performant.
52+
if v.contains(&resolved) {
53+
break;
54+
}
55+
v.push(resolved);
3656
}
3757
v.into_iter()
3858
}

crates/hir/src/lib.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3795,14 +3795,16 @@ impl Type {
37953795
}
37963796
}
37973797

3798-
pub fn autoderef<'a>(&'a self, db: &'a dyn HirDatabase) -> impl Iterator<Item = Type> + 'a {
3798+
/// Returns types that this type dereferences to (including this type itself). The returned
3799+
/// iterator won't yield the same type more than once even if the deref chain contains a cycle.
3800+
pub fn autoderef(&self, db: &dyn HirDatabase) -> impl Iterator<Item = Type> + '_ {
37993801
self.autoderef_(db).map(move |ty| self.derived(ty))
38003802
}
38013803

3802-
fn autoderef_<'a>(&'a self, db: &'a dyn HirDatabase) -> impl Iterator<Item = Ty> + 'a {
3804+
fn autoderef_(&self, db: &dyn HirDatabase) -> impl Iterator<Item = Ty> {
38033805
// There should be no inference vars in types passed here
38043806
let canonical = hir_ty::replace_errors_with_variables(&self.ty);
3805-
autoderef(db, self.env.clone(), canonical).map(|canonical| canonical.value)
3807+
autoderef(db, self.env.clone(), canonical)
38063808
}
38073809

38083810
// This would be nicer if it just returned an iterator, but that runs into

crates/ide-completion/src/completions/dot.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -979,4 +979,46 @@ fn test(thing: impl Encrypt) {
979979
"#]],
980980
)
981981
}
982+
983+
#[test]
984+
fn only_consider_same_type_once() {
985+
check(
986+
r#"
987+
//- minicore: deref
988+
struct A(u8);
989+
struct B(u16);
990+
impl core::ops::Deref for A {
991+
type Target = B;
992+
fn deref(&self) -> &Self::Target { loop {} }
993+
}
994+
impl core::ops::Deref for B {
995+
type Target = A;
996+
fn deref(&self) -> &Self::Target { loop {} }
997+
}
998+
fn test(a: A) {
999+
a.$0
1000+
}
1001+
"#,
1002+
expect![[r#"
1003+
fd 0 u16
1004+
fd 0 u8
1005+
me deref() (use core::ops::Deref) fn(&self) -> &<Self as Deref>::Target
1006+
"#]],
1007+
);
1008+
}
1009+
1010+
#[test]
1011+
fn no_inference_var_in_completion() {
1012+
check(
1013+
r#"
1014+
struct S<T>(T);
1015+
fn test(s: S<Unknown>) {
1016+
s.$0
1017+
}
1018+
"#,
1019+
expect![[r#"
1020+
fd 0 {unknown}
1021+
"#]],
1022+
);
1023+
}
9821024
}

0 commit comments

Comments
 (0)