Skip to content
This repository was archived by the owner on Sep 9, 2025. It is now read-only.

Commit 3fc760a

Browse files
author
Hendrik van Antwerpen
committed
Refine cycle detection logic
1 parent 3bc764c commit 3fc760a

File tree

4 files changed

+140
-112
lines changed

4 files changed

+140
-112
lines changed

stack-graphs/src/cycles.rs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ use crate::arena::ListArena;
3939
use crate::graph::Edge;
4040
use crate::graph::Node;
4141
use crate::graph::StackGraph;
42+
use crate::partial::Cyclicity;
4243
use crate::partial::PartialPath;
4344
use crate::partial::PartialPaths;
4445
use crate::paths::Path;
@@ -173,16 +174,23 @@ impl<A: Appendable + Clone> AppendingCycleDetector<A> {
173174
result
174175
}
175176

176-
pub fn append(
177-
&mut self,
177+
pub fn append(&mut self, appendables: &mut Appendables<A>, appendage: A) {
178+
self.appendages.push_front(appendables, appendage);
179+
}
180+
181+
pub fn is_cyclic(
182+
&self,
178183
graph: &StackGraph,
179184
partials: &mut PartialPaths,
180185
ctx: &mut A::Ctx,
181186
appendables: &mut Appendables<A>,
182-
appendage: A,
183-
) -> Result<(), PathResolutionError> {
184-
let end_node = appendage.end_node(ctx);
185-
self.appendages.push_front(appendables, appendage);
187+
) -> Vec<Cyclicity> {
188+
let mut cycles = Vec::new();
189+
190+
let end_node = match self.appendages.clone().pop_front(appendables) {
191+
Some(appendage) => appendage.end_node(ctx),
192+
None => return cycles,
193+
};
186194

187195
let mut maybe_cyclic_path = None;
188196
let mut appendages = self.appendages;
@@ -199,7 +207,7 @@ impl<A: Appendable + Clone> AppendingCycleDetector<A> {
199207
break;
200208
}
201209
}
202-
None => return Ok(()),
210+
None => return cycles,
203211
}
204212
}
205213

@@ -224,8 +232,8 @@ impl<A: Appendable + Clone> AppendingCycleDetector<A> {
224232
prefix_path
225233
.concatenate(graph, partials, &cyclic_path)
226234
.expect("concatenating cyclic path failed ");
227-
if !prefix_path.is_productive(graph, partials) {
228-
return Err(PathResolutionError::DisallowedCycle);
235+
if let Some(cyclicity) = prefix_path.is_cyclic(graph, partials) {
236+
cycles.push(cyclicity);
229237
}
230238
maybe_cyclic_path = Some(prefix_path);
231239
}

stack-graphs/src/partial.rs

Lines changed: 39 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2069,13 +2069,14 @@ impl PartialPath {
20692069
graph[self.end_node].is_jump_to()
20702070
}
20712071

2072-
/// Returns whether a partial path is "productive" --- that is, it is not cyclic, or its
2073-
/// pre- and postcondition are incompatible.
2074-
pub fn is_productive(&self, graph: &StackGraph, partials: &mut PartialPaths) -> bool {
2072+
/// Returns whether a partial path is cyclic---that is, it starts and ends at the same node,
2073+
/// and its postcondition is compatible with its precondition. If the path is cyclic, a
2074+
/// tuple is returned indicating whether cycle requires strengthening the pre- or postcondition.
2075+
pub fn is_cyclic(&self, graph: &StackGraph, partials: &mut PartialPaths) -> Option<Cyclicity> {
20752076
// StackGraph ensures that there are no nodes with duplicate IDs, so we can do a simple
20762077
// comparison of node handles here.
20772078
if self.start_node != self.end_node {
2078-
return true;
2079+
return None;
20792080
}
20802081

20812082
let lhs = self;
@@ -2084,30 +2085,36 @@ impl PartialPath {
20842085

20852086
let join = match Self::compute_join(graph, partials, lhs, &rhs) {
20862087
Ok(join) => join,
2087-
Err(_) => return true,
2088+
Err(_) => return None,
20882089
};
20892090

20902091
if lhs
20912092
.symbol_stack_precondition
20922093
.variable
20932094
.into_option()
20942095
.map_or(false, |v| join.symbol_bindings.get(v).iter().len() > 0)
2096+
|| lhs
2097+
.scope_stack_precondition
2098+
.variable
2099+
.into_option()
2100+
.map_or(false, |v| join.scope_bindings.get(v).iter().len() > 0)
20952101
{
2096-
// symbol stack precondition strengthened
2097-
return true;
2098-
}
2099-
2100-
if lhs
2101-
.scope_stack_precondition
2102+
Some(Cyclicity::StrengthensPrecondition)
2103+
} else if rhs
2104+
.symbol_stack_postcondition
21022105
.variable
21032106
.into_option()
2104-
.map_or(false, |v| join.scope_bindings.get(v).iter().len() > 0)
2107+
.map_or(false, |v| join.symbol_bindings.get(v).iter().len() > 0)
2108+
|| rhs
2109+
.scope_stack_postcondition
2110+
.variable
2111+
.into_option()
2112+
.map_or(false, |v| join.scope_bindings.get(v).iter().len() > 0)
21052113
{
2106-
// scope stack precondition strengthened
2107-
return true;
2114+
Some(Cyclicity::StrengthensPostcondition)
2115+
} else {
2116+
Some(Cyclicity::Free)
21082117
}
2109-
2110-
false
21112118
}
21122119

21132120
/// Ensures that the content of this partial path is available in both forwards and backwards
@@ -2222,6 +2229,16 @@ impl PartialPath {
22222229
}
22232230
}
22242231

2232+
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
2233+
pub enum Cyclicity {
2234+
/// The path can be freely concatenated to itself.
2235+
Free,
2236+
/// Concatenating the path to itself strengthens the precondition---symbols are eliminated from the stack.
2237+
StrengthensPrecondition,
2238+
/// Concatenating the path to itself strengthens the postcondition---symbols are introduced on the stack.
2239+
StrengthensPostcondition,
2240+
}
2241+
22252242
impl<'a> DisplayWithPartialPaths for &'a PartialPath {
22262243
fn prepare(&mut self, graph: &StackGraph, partials: &mut PartialPaths) {
22272244
self.symbol_stack_precondition
@@ -2407,13 +2424,7 @@ impl PartialPath {
24072424
copious_debugging!(" * invalid extension");
24082425
continue;
24092426
}
2410-
if new_cycle_detector
2411-
.append(graph, partials, &mut (), edges, extension)
2412-
.is_err()
2413-
{
2414-
copious_debugging!(" * cycle");
2415-
continue;
2416-
}
2427+
new_cycle_detector.append(edges, extension);
24172428
result.push((new_path, new_cycle_detector));
24182429
}
24192430
}
@@ -2676,10 +2687,6 @@ impl PartialPaths {
26762687
/// (a) is minimal, no path can be constructed by stitching other paths in the set, and
26772688
/// (b) covers all complete paths, from references to definitions, when used for path stitching
26782689
///
2679-
/// Callers are advised _not_ to filter this set in the visitor using functions like
2680-
/// [`PartialPath::is_productive`][] as that may interfere with implementation changes of this
2681-
/// function.
2682-
///
26832690
/// This function will not return until all reachable partial paths have been processed, so
26842691
/// `graph` must already contain a complete stack graph. If you have a very large stack graph
26852692
/// stored in some other storage system, and want more control over lazily loading only the
@@ -2734,6 +2741,11 @@ impl PartialPaths {
27342741
copious_debugging!(" * visit");
27352742
visit(graph, self, path);
27362743
}
2744+
} else if !path_cycle_detector
2745+
.is_cyclic(graph, self, &mut (), &mut edges)
2746+
.is_empty()
2747+
{
2748+
copious_debugging!(" * cycle");
27372749
} else {
27382750
copious_debugging!(" * extend");
27392751
path.extend_from_file(

stack-graphs/src/stitching.rs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ use crate::cycles::SimilarPathDetector;
5454
use crate::graph::Node;
5555
use crate::graph::StackGraph;
5656
use crate::graph::Symbol;
57+
use crate::partial::Cyclicity;
5758
use crate::partial::PartialPath;
5859
use crate::partial::PartialPaths;
5960
use crate::partial::PartialSymbolStack;
@@ -931,15 +932,12 @@ impl ForwardPartialPathStitcher {
931932
copious_debugging!(" is invalid: {:?}", err);
932933
continue;
933934
}
934-
if new_cycle_detector
935-
.append(
936-
graph,
937-
partials,
938-
db,
939-
&mut self.appended_paths,
940-
extension.into(),
941-
)
942-
.is_err()
935+
new_cycle_detector.append(&mut self.appended_paths, extension.into());
936+
let cycles =
937+
new_cycle_detector.is_cyclic(graph, partials, db, &mut self.appended_paths);
938+
if !cycles
939+
.into_iter()
940+
.all(|c| c == Cyclicity::StrengthensPrecondition)
943941
{
944942
copious_debugging!(" is invalid: cyclic");
945943
continue;

0 commit comments

Comments
 (0)