@@ -742,40 +742,49 @@ impl<'a> Generator<'a> {
742742
743743 fn gen_expr ( & mut self , ty : & Ty , scope : & Scope , fuel : u32 ) -> String {
744744 if fuel == 0 {
745- return self . gen_atom ( ty, scope) ;
745+ return self . gen_atom ( ty, scope, 0 ) ;
746746 }
747747 // Occasionally satisfy an `Int` target with a monomorphized generic call.
748748 if * ty == Ty :: Int && !self . generic_fns . is_empty ( ) && fuel > 0 && self . seed . choice ( 4 ) == 0 {
749749 return self . gen_generic_call ( scope, fuel) ;
750750 }
751751 match self . seed . weighted ( & [ 3 , 2 , 4 ] ) {
752- 0 => self . gen_atom ( ty, scope) ,
752+ 0 => self . gen_atom ( ty, scope, fuel ) ,
753753 1 => self
754754 . gen_call ( ty, scope, fuel)
755- . unwrap_or_else ( || self . gen_atom ( ty, scope) ) ,
755+ . unwrap_or_else ( || self . gen_atom ( ty, scope, fuel ) ) ,
756756 _ => self . gen_compound ( ty, scope, fuel) ,
757757 }
758758 }
759759
760- /// A leaf: an in-scope variable, or a freshly constructed value.
761- fn gen_atom ( & mut self , ty : & Ty , scope : & Scope ) -> String {
760+ /// A leaf: an in-scope variable, or a freshly constructed value. `fuel` is the
761+ /// remaining recursion budget; construction of a compound payload consumes one
762+ /// unit, so at `fuel == 0` only nullary literals/atoms are produced — this is
763+ /// what guarantees termination even when a function returning `T` takes a
764+ /// compound-of-`T` parameter (the `T -> Option<T> -> T -> …` cycle).
765+ fn gen_atom ( & mut self , ty : & Ty , scope : & Scope , fuel : u32 ) -> String {
762766 let vars = scope. bindings_of ( ty) ;
763767 if !vars. is_empty ( ) && self . seed . bool ( ) {
764768 return vars[ self . seed . choice ( vars. len ( ) ) ] . name . clone ( ) ;
765769 }
766- self . gen_construct ( ty, scope)
770+ self . gen_construct ( ty, scope, fuel )
767771 }
768772
769773 /// Construct a value of `ty` (literal for scalars; constructor for compounds).
770- fn gen_construct ( & mut self , ty : & Ty , scope : & Scope ) -> String {
774+ /// Compound payloads are generated with strictly-decreasing `fuel` so that
775+ /// `Some`/`Ok` nesting cannot reset the recursion budget — at `fuel == 0` the
776+ /// payload is a plain `gen_construct` (a literal/atom that never re-enters
777+ /// `gen_expr`), which is what bounds generation.
778+ fn gen_construct ( & mut self , ty : & Ty , scope : & Scope , fuel : u32 ) -> String {
779+ let payload_fuel = fuel. saturating_sub ( 1 ) ;
771780 match ty {
772781 Ty :: Int | Ty :: Bool | Ty :: Float | Ty :: String => self . gen_literal ( ty) ,
773782 Ty :: Option ( inner) => {
774- let value = self . gen_expr ( inner, scope, 1 ) ;
783+ let value = self . gen_expr ( inner, scope, payload_fuel ) ;
775784 format ! ( "Some({value})" )
776785 }
777786 Ty :: Result ( ok, _) => {
778- let value = self . gen_expr ( ok, scope, 1 ) ;
787+ let value = self . gen_expr ( ok, scope, payload_fuel ) ;
779788 format ! ( "Ok({value})" )
780789 }
781790 Ty :: Struct ( name) => {
@@ -921,7 +930,7 @@ impl<'a> Generator<'a> {
921930 | Ty :: List ( _)
922931 | Ty :: Map ( _, _)
923932 | Ty :: Set ( _)
924- | Ty :: Deque ( _) => self . gen_construct ( ty, scope) ,
933+ | Ty :: Deque ( _) => self . gen_construct ( ty, scope, fuel ) ,
925934 }
926935 }
927936
0 commit comments