about summary refs log tree commit diff
diff options
context:
space:
mode:
authorChayim Refael Friedman <chayimfr@gmail.com>2025-04-14 07:12:51 +0300
committerChayim Refael Friedman <chayimfr@gmail.com>2025-04-14 07:12:51 +0300
commit601ab30d2734e67e7a483109ec27f034e7cc3aae (patch)
tree1d76adf641538b11f28ccf9a11ef164a4d928a74
parent121a12edc699b5f50a5ab4924f8db3ab1ce96b75 (diff)
downloadrust-601ab30d2734e67e7a483109ec27f034e7cc3aae.tar.gz
rust-601ab30d2734e67e7a483109ec27f034e7cc3aae.zip
Prevent panics when there is a cyclic dependency between closures
We didn't include them in the sorted closures list, therefore didn't analyze them, then failed to find them.
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs40
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs4
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs28
3 files changed, 65 insertions, 7 deletions
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs
index 175f0c7bd75..e6cd1b9990e 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs
@@ -23,7 +23,7 @@ use hir_def::{
 use hir_def::{Lookup, type_ref::TypeRefId};
 use hir_expand::name::Name;
 use intern::sym;
-use rustc_hash::FxHashMap;
+use rustc_hash::{FxHashMap, FxHashSet};
 use smallvec::{SmallVec, smallvec};
 use stdx::{format_to, never};
 use syntax::utils::is_raw_identifier;
@@ -107,9 +107,7 @@ impl InferenceContext<'_> {
                 )
                 .intern(Interner);
                 self.deferred_closures.entry(closure_id).or_default();
-                if let Some(c) = self.current_closure {
-                    self.closure_dependencies.entry(c).or_default().push(closure_id);
-                }
+                self.add_current_closure_dependency(closure_id);
                 (Some(closure_id), closure_ty, None)
             }
         };
@@ -1748,8 +1746,42 @@ impl InferenceContext<'_> {
                 }
             }
         }
+        assert!(deferred_closures.is_empty(), "we should have analyzed all closures");
         result
     }
+
+    pub(super) fn add_current_closure_dependency(&mut self, dep: ClosureId) {
+        if let Some(c) = self.current_closure {
+            if !dep_creates_cycle(&self.closure_dependencies, &mut FxHashSet::default(), c, dep) {
+                self.closure_dependencies.entry(c).or_default().push(dep);
+            }
+        }
+
+        fn dep_creates_cycle(
+            closure_dependencies: &FxHashMap<ClosureId, Vec<ClosureId>>,
+            visited: &mut FxHashSet<ClosureId>,
+            from: ClosureId,
+            to: ClosureId,
+        ) -> bool {
+            if !visited.insert(from) {
+                return false;
+            }
+
+            if from == to {
+                return true;
+            }
+
+            if let Some(deps) = closure_dependencies.get(&to) {
+                for dep in deps {
+                    if dep_creates_cycle(closure_dependencies, visited, from, *dep) {
+                        return true;
+                    }
+                }
+            }
+
+            false
+        }
+    }
 }
 
 /// Call this only when the last span in the stack isn't a split.
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs
index b5e6ccd6adf..068d9a59da7 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs
@@ -1705,9 +1705,7 @@ impl InferenceContext<'_> {
                 if let TyKind::Closure(c, _) =
                     self.table.resolve_completely(callee_ty.clone()).kind(Interner)
                 {
-                    if let Some(par) = self.current_closure {
-                        self.closure_dependencies.entry(par).or_default().push(*c);
-                    }
+                    self.add_current_closure_dependency(*c);
                     self.deferred_closures.entry(*c).or_default().push((
                         derefed_callee.clone(),
                         callee_ty.clone(),
diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs
index a260944d055..a8a200a3999 100644
--- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs
@@ -1004,4 +1004,32 @@ fn foo() {
 "#,
         );
     }
+
+    #[test]
+    fn closure_dependency_cycle_no_panic() {
+        check(
+            r#"
+fn foo() {
+    let closure;
+     // ^^^^^^^ impl Fn()
+    closure = || {
+        closure();
+    };
+}
+
+fn bar() {
+    let closure1;
+     // ^^^^^^^^ impl Fn()
+    let closure2;
+     // ^^^^^^^^ impl Fn()
+    closure1 = || {
+        closure2();
+    };
+    closure2 = || {
+        closure1();
+    };
+}
+        "#,
+        );
+    }
 }