@@ -93,15 +93,6 @@ def _repr_pretty_(self, p, cycle):
9393 "pretty" ,
9494]
9595
96- PRIMITIVE_TYPES_ALWAYS_USE_REPR = (
97- int ,
98- float ,
99- str ,
100- bytes ,
101- bool ,
102- type (None ),
103- )
104-
10596
10697def _safe_getattr (obj : object , attr : str , default : Any | None = None ) -> Any :
10798 """Safe version of getattr.
@@ -134,6 +125,98 @@ def __eq__(self, __o: object) -> bool:
134125 return isinstance (__o , type (self )) and id (self .value ) == id (__o .value )
135126
136127
128+ def _try_inline_lambda (
129+ func_name : str ,
130+ args : Sequence [object ],
131+ kwargs : dict [str , object ],
132+ printer : "RepresentationPrinter" ,
133+ ) -> bool :
134+ """Try to inline single-use lambda arguments into the body expression.
135+
136+ Given e.g. func_name="lambda b: hashlib.sha256(b).hexdigest()" with
137+ args=(b'',), returns the printer output for "hashlib.sha256(b'').hexdigest()"
138+ by substituting the argument repr into the AST.
139+
140+ Returns True if inlining succeeded (the printer has been written to),
141+ False if inlining is not possible (parse failure, multi-use params, etc).
142+ """
143+ try :
144+ tree = ast .parse (func_name , mode = "eval" )
145+ except Exception :
146+ return False
147+ lam = tree .body
148+ if not isinstance (lam , ast .Lambda ):
149+ return False
150+
151+ # Build param name -> argument repr mapping, matching Python call semantics
152+ params = lam .args
153+ if params .vararg or params .kwonlyargs or params .kw_defaults or params .kwarg :
154+ return False
155+
156+ param_names = [p .arg for p in params .args ]
157+ # params.defaults are right-aligned: if there are 3 params and 1 default,
158+ # params.defaults applies to the last param only.
159+ n_defaults = len (params .defaults )
160+ has_default = (
161+ set (param_names [len (param_names ) - n_defaults :]) if n_defaults else set ()
162+ )
163+
164+ # Bail if there are more positional args than parameters, or if any
165+ # kwarg doesn't match a parameter name — these can't be inlined.
166+ if len (args ) > len (param_names ):
167+ return False
168+ if any (k not in param_names for k in kwargs ):
169+ return False
170+
171+ arg_reprs : dict [str , str ] = {}
172+ for i , name in enumerate (param_names ):
173+ if i < len (args ):
174+ arg_reprs [name ] = pretty (args [i ])
175+ elif name in kwargs :
176+ arg_reprs [name ] = pretty (kwargs [name ])
177+ elif name in has_default :
178+ pass # not passed, will use its default — just skip
179+ else :
180+ return False
181+
182+ # Bail if any repr is not valid Python (e.g. "HypothesisRandom(generated data)")
183+ for repr_str in arg_reprs .values ():
184+ try :
185+ ast .parse (repr_str , mode = "eval" )
186+ except Exception :
187+ return False
188+
189+ use_counts = dict .fromkeys (param_names , 0 )
190+ for node in ast .walk (lam .body ):
191+ if isinstance (node , ast .Name ) and node .id in use_counts :
192+ use_counts [node .id ] += 1
193+
194+ # Bail if any parameter is used more than once (avoid duplicating expressions)
195+ if any (count > 1 for count in use_counts .values ()):
196+ return False
197+
198+ # Substitute argument reprs into the body AST
199+ class _Inliner (ast .NodeTransformer ):
200+ def visit_Name (self , node : ast .Name ) -> ast .AST :
201+ if node .id in arg_reprs :
202+ # Parse the repr as an expression and splice it in.
203+ # Wrap in parens to preserve precedence in all contexts.
204+ replacement = ast .parse (arg_reprs [node .id ], mode = "eval" ).body
205+ return ast .copy_location (replacement , node )
206+ return node
207+
208+ new_body = _Inliner ().visit (lam .body )
209+ ast .fix_missing_locations (new_body )
210+
211+ try :
212+ result = ast .unparse (new_body )
213+ except Exception :
214+ return False
215+
216+ printer .text (result )
217+ return True
218+
219+
137220class RepresentationPrinter :
138221 """Special pretty printer that has a `pretty` method that calls the pretty
139222 printer for a python object.
@@ -428,11 +511,12 @@ def maybe_repr_known_object_as_call(
428511 kwargs : dict [str , object ],
429512 arg_labels : ArgLabelsT | None = None ,
430513 ) -> None :
431- if isinstance (obj , PRIMITIVE_TYPES_ALWAYS_USE_REPR ):
432- return _repr_pprint (obj , self , cycle )
433-
434- # pprint this object as a call, _unless_ the call would be invalid syntax
435- # and the repr would be valid and there are not comments on arguments.
514+ # pprint this object as a call if it seems like a good idea to do so,
515+ # otherwise pprint as repr.
516+ # Rules:
517+ # 1. If there are comments, we *must* print as a call.
518+ # 2. Prefer valid syntax to invalid syntax.
519+ # 3. Prefer shorter expressions.
436520 if cycle :
437521 return self .text ("<...>" )
438522 # Look up comments from slice_comments if we have arg_labels
@@ -452,6 +536,8 @@ def maybe_repr_known_object_as_call(
452536 p .known_object_printers = self .known_object_printers
453537 p .repr_call (name , args , kwargs )
454538 # If the call is not valid syntax, use the repr
539+ if len (repr (obj )) < len (p .getvalue ()):
540+ return _repr_pprint (obj , self , cycle )
455541 try :
456542 ast .parse (p .getvalue ())
457543 except Exception :
@@ -479,6 +565,19 @@ def repr_call(
479565 """
480566 assert isinstance (func_name , str )
481567 if func_name .startswith (("lambda:" , "lambda " )):
568+ # Before wrapping the lambda in parens for a call, try to inline
569+ # arguments that are used exactly once in the body. If all args
570+ # get inlined, we can emit just the body expression with no call.
571+ # Skip inlining only when there are actual comments on arguments,
572+ # since comments need the call-style repr to attach to.
573+ has_comments = arg_slices and any (
574+ sr in self .slice_comments and sr not in self ._commented_slices
575+ for sr in arg_slices .values ()
576+ )
577+ if not has_comments :
578+ inlined = _try_inline_lambda (func_name , args , kwargs , self )
579+ if inlined :
580+ return
482581 func_name = f"({ func_name } )"
483582 self .text (func_name )
484583 # Build list of (label, value) pairs. Labels are "arg[i]" for positional
0 commit comments