Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--crates/hir-ty/src/autoderef.rs24
-rw-r--r--crates/hir/src/lib.rs8
-rw-r--r--crates/ide-completion/src/completions/dot.rs42
3 files changed, 69 insertions, 5 deletions
diff --git a/crates/hir-ty/src/autoderef.rs b/crates/hir-ty/src/autoderef.rs
index f5b3f176b1..3860bccec8 100644
--- a/crates/hir-ty/src/autoderef.rs
+++ b/crates/hir-ty/src/autoderef.rs
@@ -22,17 +22,37 @@ pub(crate) enum AutoderefKind {
Overloaded,
}
+/// Returns types that `ty` transitively dereferences to. This function is only meant to be used
+/// outside `hir-ty`.
+///
+/// It is guaranteed that:
+/// - the yielded types don't contain inference variables (but may contain `TyKind::Error`).
+/// - a type won't be yielded more than once; in other words, the returned iterator will stop if it
+/// detects a cycle in the deref chain.
pub fn autoderef(
db: &dyn HirDatabase,
env: Arc<TraitEnvironment>,
ty: Canonical<Ty>,
-) -> impl Iterator<Item = Canonical<Ty>> + '_ {
+) -> impl Iterator<Item = Ty> {
let mut table = InferenceTable::new(db, env);
let ty = table.instantiate_canonical(ty);
let mut autoderef = Autoderef::new(&mut table, ty);
let mut v = Vec::new();
while let Some((ty, _steps)) = autoderef.next() {
- v.push(autoderef.table.canonicalize(ty).value);
+ // `ty` may contain unresolved inference variables. Since there's no chance they would be
+ // resolved, just replace with fallback type.
+ let resolved = autoderef.table.resolve_completely(ty);
+
+ // If the deref chain contains a cycle (e.g. `A` derefs to `B` and `B` derefs to `A`), we
+ // would revisit some already visited types. Stop here to avoid duplication.
+ //
+ // XXX: The recursion limit for `Autoderef` is currently 10, so `Vec::contains()` shouldn't
+ // be too expensive. Replace this duplicate check with `FxHashSet` if it proves to be more
+ // performant.
+ if v.contains(&resolved) {
+ break;
+ }
+ v.push(resolved);
}
v.into_iter()
}
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 5527eb5938..c890c83bd4 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -3795,14 +3795,16 @@ impl Type {
}
}
- pub fn autoderef<'a>(&'a self, db: &'a dyn HirDatabase) -> impl Iterator<Item = Type> + 'a {
+ /// Returns types that this type dereferences to (including this type itself). The returned
+ /// iterator won't yield the same type more than once even if the deref chain contains a cycle.
+ pub fn autoderef(&self, db: &dyn HirDatabase) -> impl Iterator<Item = Type> + '_ {
self.autoderef_(db).map(move |ty| self.derived(ty))
}
- fn autoderef_<'a>(&'a self, db: &'a dyn HirDatabase) -> impl Iterator<Item = Ty> + 'a {
+ fn autoderef_(&self, db: &dyn HirDatabase) -> impl Iterator<Item = Ty> {
// There should be no inference vars in types passed here
let canonical = hir_ty::replace_errors_with_variables(&self.ty);
- autoderef(db, self.env.clone(), canonical).map(|canonical| canonical.value)
+ autoderef(db, self.env.clone(), canonical)
}
// This would be nicer if it just returned an iterator, but that runs into
diff --git a/crates/ide-completion/src/completions/dot.rs b/crates/ide-completion/src/completions/dot.rs
index 57a784c45b..78f6c034ba 100644
--- a/crates/ide-completion/src/completions/dot.rs
+++ b/crates/ide-completion/src/completions/dot.rs
@@ -979,4 +979,46 @@ fn test(thing: impl Encrypt) {
"#]],
)
}
+
+ #[test]
+ fn only_consider_same_type_once() {
+ check(
+ r#"
+//- minicore: deref
+struct A(u8);
+struct B(u16);
+impl core::ops::Deref for A {
+ type Target = B;
+ fn deref(&self) -> &Self::Target { loop {} }
+}
+impl core::ops::Deref for B {
+ type Target = A;
+ fn deref(&self) -> &Self::Target { loop {} }
+}
+fn test(a: A) {
+ a.$0
+}
+"#,
+ expect![[r#"
+ fd 0 u16
+ fd 0 u8
+ me deref() (use core::ops::Deref) fn(&self) -> &<Self as Deref>::Target
+ "#]],
+ );
+ }
+
+ #[test]
+ fn no_inference_var_in_completion() {
+ check(
+ r#"
+struct S<T>(T);
+fn test(s: S<Unknown>) {
+ s.$0
+}
+"#,
+ expect![[r#"
+ fd 0 {unknown}
+ "#]],
+ );
+ }
}