diff options
| author | Chayim Refael Friedman <chayimfr@gmail.com> | 2025-04-14 07:12:51 +0300 |
|---|---|---|
| committer | Chayim Refael Friedman <chayimfr@gmail.com> | 2025-04-14 07:12:51 +0300 |
| commit | 601ab30d2734e67e7a483109ec27f034e7cc3aae (patch) | |
| tree | 1d76adf641538b11f28ccf9a11ef164a4d928a74 | |
| parent | 121a12edc699b5f50a5ab4924f8db3ab1ce96b75 (diff) | |
| download | rust-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.
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(); + }; +} + "#, + ); + } } |
