Unnamed repository; edit this file 'description' to name the repository.
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.
Chayim Refael Friedman 2025-04-14
parent 8365cf8 · commit cdc5ba3
-rw-r--r--crates/hir-ty/src/infer/closure.rs40
-rw-r--r--crates/hir-ty/src/infer/expr.rs4
-rw-r--r--crates/ide/src/inlay_hints.rs28
3 files changed, 65 insertions, 7 deletions
diff --git a/crates/hir-ty/src/infer/closure.rs b/crates/hir-ty/src/infer/closure.rs
index 175f0c7bd7..e6cd1b9990 100644
--- a/crates/hir-ty/src/infer/closure.rs
+++ b/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/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs
index b5e6ccd6ad..068d9a59da 100644
--- a/crates/hir-ty/src/infer/expr.rs
+++ b/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/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index a260944d05..a8a200a399 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/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();
+ };
+}
+ "#,
+ );
+ }
}