From 601ab30d2734e67e7a483109ec27f034e7cc3aae Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Mon, 14 Apr 2025 07:12:51 +0300 Subject: [PATCH] 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. --- .../crates/hir-ty/src/infer/closure.rs | 40 +++++++++++++++++-- .../crates/hir-ty/src/infer/expr.rs | 4 +- .../crates/ide/src/inlay_hints.rs | 28 +++++++++++++ 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 175f0c7bd750..e6cd1b9990ea 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>, + 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/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs index b5e6ccd6adf7..068d9a59da77 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 a260944d0553..a8a200a39998 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(); + }; +} + "#, + ); + } }