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

Commit 8acf805

Browse files
author
Hendrik van Antwerpen
committed
Unify cycle detector implementations
1 parent e4bdc08 commit 8acf805

File tree

5 files changed

+147
-174
lines changed

5 files changed

+147
-174
lines changed

stack-graphs/src/cycles.rs

Lines changed: 83 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ use crate::graph::StackGraph;
4242
use crate::partial::PartialPath;
4343
use crate::partial::PartialPaths;
4444
use crate::paths::Path;
45+
use crate::paths::PathResolutionError;
4546
use crate::stitching::Database;
4647
use crate::stitching::OwnedOrDatabasePath;
4748

@@ -136,43 +137,64 @@ where
136137
}
137138

138139
// ----------------------------------------------------------------------------
139-
// Cycle detector when appending edges
140+
// Cycle detector
141+
142+
pub trait Appendable {
143+
type Ctx;
144+
145+
fn append_to(
146+
&self,
147+
graph: &StackGraph,
148+
partials: &mut PartialPaths,
149+
ctx: &mut Self::Ctx,
150+
path: &mut PartialPath,
151+
) -> Result<(), PathResolutionError>;
152+
fn start_node(&self, ctx: &mut Self::Ctx) -> Handle<Node>;
153+
fn end_node(&self, ctx: &mut Self::Ctx) -> Handle<Node>;
154+
}
140155

141156
#[derive(Clone)]
142-
pub struct EdgeAppendingCycleDetector {
143-
edges: List<Edge>,
157+
pub struct AppendingCycleDetector<A> {
158+
appendages: List<A>,
144159
}
145160

146-
pub type AppendedEdges = ListArena<Edge>;
161+
pub type Appendages<A> = ListArena<A>;
147162

148-
impl EdgeAppendingCycleDetector {
163+
impl<A: Appendable + Clone> AppendingCycleDetector<A> {
149164
pub fn new() -> Self {
150165
Self {
151-
edges: List::empty(),
166+
appendages: List::empty(),
152167
}
153168
}
154169

155-
pub fn append_edge(
170+
pub fn from(appendages: &mut Appendages<A>, appendage: A) -> Self {
171+
let mut result = Self::new();
172+
result.appendages.push_front(appendages, appendage);
173+
result
174+
}
175+
176+
pub fn append(
156177
&mut self,
157178
graph: &StackGraph,
158179
partials: &mut PartialPaths,
159-
appended_edges: &mut AppendedEdges,
160-
edge: Edge,
161-
) -> Result<(), ()> {
162-
let end_node = edge.sink;
163-
self.edges.push_front(appended_edges, edge);
180+
ctx: &mut A::Ctx,
181+
appendages: &mut Appendages<A>,
182+
appendage: A,
183+
) -> Result<(), PathResolutionError> {
184+
let end_node = appendage.end_node(ctx);
185+
self.appendages.push_front(appendages, appendage);
164186

165187
let mut maybe_cyclic_path = None;
166-
let mut index = self.edges;
167-
let mut edges = self.edges;
188+
let mut index = self.appendages;
189+
let mut values = self.appendages;
168190
loop {
169191
// find loop point
170192
let mut count = 0usize;
171193
loop {
172-
match index.pop_front(appended_edges) {
173-
Some(edge) => {
194+
match index.pop_front(appendages) {
195+
Some(appendage) => {
174196
count += 1;
175-
if edge.source == end_node {
197+
if appendage.start_node(ctx) == end_node {
176198
break;
177199
}
178200
}
@@ -181,18 +203,21 @@ impl EdgeAppendingCycleDetector {
181203
}
182204

183205
// get prefix edges
184-
let mut prefix_edges = List::empty();
206+
let mut prefix_appendages = List::empty();
185207
for _ in 0..count {
186-
prefix_edges.push_front(appended_edges, *edges.pop_front(appended_edges).unwrap());
208+
let appendage = values.pop_front(appendages).unwrap();
209+
prefix_appendages.push_front(appendages, appendage.clone());
187210
}
188211

189-
// build prefix path
212+
// build prefix path -- prefix starts at end_node, because this is a cycle
190213
let mut prefix_path = PartialPath::from_node(graph, partials, end_node);
191-
while let Some(edge) = prefix_edges.pop_front(appended_edges) {
214+
while let Some(appendage) = prefix_appendages.pop_front(appendages) {
192215
prefix_path
193-
.resolve_to(graph, partials, edge.source)
216+
.resolve_to(graph, partials, appendage.start_node(ctx))
217+
.unwrap();
218+
appendage
219+
.append_to(graph, partials, ctx, &mut prefix_path)
194220
.unwrap();
195-
prefix_path.append(graph, partials, *edge).unwrap();
196221
}
197222

198223
// build cyclic path
@@ -206,99 +231,54 @@ impl EdgeAppendingCycleDetector {
206231
.concatenate(graph, partials, &cyclic_path)
207232
.unwrap();
208233
if !prefix_path.is_productive(graph, partials) {
209-
return Err(());
234+
return Err(PathResolutionError::DisallowedCycle);
210235
}
211236
maybe_cyclic_path = Some(prefix_path);
212237
}
213238
}
214239
}
215240

216-
// ----------------------------------------------------------------------------
217-
// Cycle detector when appending partial paths
241+
impl Appendable for Edge {
242+
type Ctx = ();
218243

219-
#[derive(Clone)]
220-
pub struct PartialPathAppendingCycleDetector {
221-
paths: List<OwnedOrDatabasePath>,
222-
}
244+
fn append_to(
245+
&self,
246+
graph: &StackGraph,
247+
partials: &mut PartialPaths,
248+
_: &mut (),
249+
path: &mut PartialPath,
250+
) -> Result<(), PathResolutionError> {
251+
path.append(graph, partials, *self)
252+
}
223253

224-
pub type AppendedPartialPaths = ListArena<OwnedOrDatabasePath>;
225-
226-
impl PartialPathAppendingCycleDetector {
227-
pub fn from_partial_path(
228-
_graph: &StackGraph,
229-
_partials: &mut PartialPaths,
230-
_db: &mut Database,
231-
appended_paths: &mut AppendedPartialPaths,
232-
path: OwnedOrDatabasePath,
233-
) -> Self {
234-
let mut paths = List::empty();
235-
paths.push_front(appended_paths, path);
236-
Self { paths }
254+
fn start_node(&self, _: &mut ()) -> Handle<Node> {
255+
self.source
237256
}
238257

239-
pub fn append_partial_path(
240-
&mut self,
241-
graph: &StackGraph,
242-
partials: &mut PartialPaths,
243-
db: &Database,
244-
appended_paths: &mut AppendedPartialPaths,
245-
path: OwnedOrDatabasePath,
246-
) -> Result<(), ()> {
247-
let end_node = path.get(db).end_node;
248-
self.paths.push_front(appended_paths, path);
258+
fn end_node(&self, _: &mut ()) -> Handle<Node> {
259+
self.sink
260+
}
261+
}
249262

250-
let mut maybe_cyclic_path = None;
251-
let mut index = self.paths;
252-
let mut paths = self.paths;
253-
loop {
254-
// find loop point
255-
let mut count = 0usize;
256-
loop {
257-
match index.pop_front(appended_paths) {
258-
Some(path) => {
259-
count += 1;
260-
if path.get(db).start_node == end_node {
261-
break;
262-
}
263-
}
264-
None => return Ok(()),
265-
}
266-
}
263+
impl Appendable for OwnedOrDatabasePath {
264+
type Ctx = Database;
267265

268-
// get prefix paths
269-
let mut prefix_paths = List::empty();
270-
for _ in 0..count {
271-
prefix_paths.push_front(
272-
appended_paths,
273-
paths.pop_front(appended_paths).unwrap().clone(),
274-
);
275-
}
266+
fn append_to(
267+
&self,
268+
graph: &StackGraph,
269+
partials: &mut PartialPaths,
270+
db: &mut Database,
271+
path: &mut PartialPath,
272+
) -> Result<(), PathResolutionError> {
273+
path.ensure_no_overlapping_variables(partials, self.get(db));
274+
path.concatenate(graph, partials, self.get(db))
275+
}
276276

277-
// build prefix path
278-
let mut prefix_path = PartialPath::from_node(graph, partials, end_node);
279-
while let Some(path) = prefix_paths.pop_front(appended_paths) {
280-
let path = path.get(db);
281-
prefix_path
282-
.resolve_to(graph, partials, path.start_node)
283-
.unwrap();
284-
prefix_path.ensure_no_overlapping_variables(partials, path);
285-
prefix_path.concatenate(graph, partials, path).unwrap();
286-
}
277+
fn start_node(&self, db: &mut Database) -> Handle<Node> {
278+
self.get(db).start_node
279+
}
287280

288-
// build cyclic path
289-
let cyclic_path = maybe_cyclic_path
290-
.unwrap_or_else(|| PartialPath::from_node(graph, partials, end_node));
291-
prefix_path
292-
.resolve_to(graph, partials, cyclic_path.start_node)
293-
.unwrap();
294-
prefix_path.ensure_no_overlapping_variables(partials, &cyclic_path);
295-
prefix_path
296-
.concatenate(graph, partials, &cyclic_path)
297-
.unwrap();
298-
if !prefix_path.is_productive(graph, partials) {
299-
return Err(());
300-
}
301-
maybe_cyclic_path = Some(prefix_path);
302-
}
281+
fn end_node(&self, db: &mut Database) -> Handle<Node> {
282+
self.get(db).end_node
303283
}
304284
}

stack-graphs/src/partial.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ use smallvec::SmallVec;
4545
use crate::arena::Deque;
4646
use crate::arena::DequeArena;
4747
use crate::arena::Handle;
48-
use crate::cycles::AppendedEdges;
49-
use crate::cycles::EdgeAppendingCycleDetector;
48+
use crate::cycles::Appendages;
49+
use crate::cycles::AppendingCycleDetector;
5050
use crate::cycles::SimilarPathDetector;
5151
use crate::graph::Edge;
5252
use crate::graph::File;
@@ -2378,13 +2378,13 @@ impl PartialPath {
23782378
/// as a parameter, instead of building it up ourselves, so that you have control over which
23792379
/// particular collection type to use, and so that you can reuse result collections across
23802380
/// multiple calls.
2381-
pub fn extend_from_file<R: Extend<(PartialPath, EdgeAppendingCycleDetector)>>(
2381+
pub fn extend_from_file<R: Extend<(PartialPath, AppendingCycleDetector<Edge>)>>(
23822382
&self,
23832383
graph: &StackGraph,
23842384
partials: &mut PartialPaths,
23852385
file: Handle<File>,
2386-
edges: &mut AppendedEdges,
2387-
path_cycle_detector: EdgeAppendingCycleDetector,
2386+
edges: &mut Appendages<Edge>,
2387+
path_cycle_detector: AppendingCycleDetector<Edge>,
23882388
result: &mut R,
23892389
) {
23902390
let extensions = graph.outgoing_edges(self.end_node);
@@ -2408,7 +2408,7 @@ impl PartialPath {
24082408
continue;
24092409
}
24102410
if new_cycle_detector
2411-
.append_edge(graph, partials, edges, extension)
2411+
.append(graph, partials, &mut (), edges, extension)
24122412
.is_err()
24132413
{
24142414
copious_debugging!(" * cycle");
@@ -2700,7 +2700,7 @@ impl PartialPaths {
27002700
copious_debugging!("Find all partial paths in {}", graph[file]);
27012701
let mut similar_path_detector = SimilarPathDetector::new();
27022702
let mut queue = VecDeque::new();
2703-
let mut edges = AppendedEdges::new();
2703+
let mut edges = Appendages::new();
27042704
queue.extend(
27052705
graph
27062706
.nodes_for_file(file)
@@ -2709,7 +2709,7 @@ impl PartialPaths {
27092709
.map(|node| {
27102710
(
27112711
PartialPath::from_node(graph, self, node),
2712-
EdgeAppendingCycleDetector::new(),
2712+
AppendingCycleDetector::new(),
27132713
)
27142714
}),
27152715
);

stack-graphs/src/paths.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -716,6 +716,8 @@ impl<'a> DisplayWithPaths for &'a Path {
716716
/// Errors that can occur during the path resolution process.
717717
#[derive(Debug)]
718718
pub enum PathResolutionError {
719+
/// The path is cyclic, and the cycle is disallowed.
720+
DisallowedCycle,
719721
/// The path contains a _jump to scope_ node, but there are no scopes on the scope stack to
720722
/// jump to.
721723
EmptyScopeStack,

0 commit comments

Comments
 (0)