Skip to content

Commit 8fb2b3e

Browse files
authored
Merge pull request #19579 from ChayimFriedman2/cyclic-closure
fix: Prevent panics when there is a cyclic dependency between closures
2 parents 21351d9 + cdc5ba3 commit 8fb2b3e

File tree

3 files changed

+65
-7
lines changed

3 files changed

+65
-7
lines changed

crates/hir-ty/src/infer/closure.rs

+36-4
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use hir_def::{
2323
use hir_def::{Lookup, type_ref::TypeRefId};
2424
use hir_expand::name::Name;
2525
use intern::sym;
26-
use rustc_hash::FxHashMap;
26+
use rustc_hash::{FxHashMap, FxHashSet};
2727
use smallvec::{SmallVec, smallvec};
2828
use stdx::{format_to, never};
2929
use syntax::utils::is_raw_identifier;
@@ -107,9 +107,7 @@ impl InferenceContext<'_> {
107107
)
108108
.intern(Interner);
109109
self.deferred_closures.entry(closure_id).or_default();
110-
if let Some(c) = self.current_closure {
111-
self.closure_dependencies.entry(c).or_default().push(closure_id);
112-
}
110+
self.add_current_closure_dependency(closure_id);
113111
(Some(closure_id), closure_ty, None)
114112
}
115113
};
@@ -1748,8 +1746,42 @@ impl InferenceContext<'_> {
17481746
}
17491747
}
17501748
}
1749+
assert!(deferred_closures.is_empty(), "we should have analyzed all closures");
17511750
result
17521751
}
1752+
1753+
pub(super) fn add_current_closure_dependency(&mut self, dep: ClosureId) {
1754+
if let Some(c) = self.current_closure {
1755+
if !dep_creates_cycle(&self.closure_dependencies, &mut FxHashSet::default(), c, dep) {
1756+
self.closure_dependencies.entry(c).or_default().push(dep);
1757+
}
1758+
}
1759+
1760+
fn dep_creates_cycle(
1761+
closure_dependencies: &FxHashMap<ClosureId, Vec<ClosureId>>,
1762+
visited: &mut FxHashSet<ClosureId>,
1763+
from: ClosureId,
1764+
to: ClosureId,
1765+
) -> bool {
1766+
if !visited.insert(from) {
1767+
return false;
1768+
}
1769+
1770+
if from == to {
1771+
return true;
1772+
}
1773+
1774+
if let Some(deps) = closure_dependencies.get(&to) {
1775+
for dep in deps {
1776+
if dep_creates_cycle(closure_dependencies, visited, from, *dep) {
1777+
return true;
1778+
}
1779+
}
1780+
}
1781+
1782+
false
1783+
}
1784+
}
17531785
}
17541786

17551787
/// Call this only when the last span in the stack isn't a split.

crates/hir-ty/src/infer/expr.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -1705,9 +1705,7 @@ impl InferenceContext<'_> {
17051705
if let TyKind::Closure(c, _) =
17061706
self.table.resolve_completely(callee_ty.clone()).kind(Interner)
17071707
{
1708-
if let Some(par) = self.current_closure {
1709-
self.closure_dependencies.entry(par).or_default().push(*c);
1710-
}
1708+
self.add_current_closure_dependency(*c);
17111709
self.deferred_closures.entry(*c).or_default().push((
17121710
derefed_callee.clone(),
17131711
callee_ty.clone(),

crates/ide/src/inlay_hints.rs

+28
Original file line numberDiff line numberDiff line change
@@ -1004,4 +1004,32 @@ fn foo() {
10041004
"#,
10051005
);
10061006
}
1007+
1008+
#[test]
1009+
fn closure_dependency_cycle_no_panic() {
1010+
check(
1011+
r#"
1012+
fn foo() {
1013+
let closure;
1014+
// ^^^^^^^ impl Fn()
1015+
closure = || {
1016+
closure();
1017+
};
1018+
}
1019+
1020+
fn bar() {
1021+
let closure1;
1022+
// ^^^^^^^^ impl Fn()
1023+
let closure2;
1024+
// ^^^^^^^^ impl Fn()
1025+
closure1 = || {
1026+
closure2();
1027+
};
1028+
closure2 = || {
1029+
closure1();
1030+
};
1031+
}
1032+
"#,
1033+
);
1034+
}
10071035
}

0 commit comments

Comments
 (0)