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>, + visited: &mut FxHashSet, + 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(); + }; +} + "#, + ); + } }