@@ -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+
22252242impl < ' 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 (
0 commit comments