@@ -346,6 +346,16 @@ def exit_counts(self) -> dict[TLineNo, int]:
346346
347347 return exit_counts
348348
349+ def _finish_action_msg (self , action_msg : str | None , end : TLineNo ) -> str :
350+ """Apply some defaulting and formatting to an arc's description."""
351+ if action_msg is None :
352+ if end < 0 :
353+ action_msg = "jump to the function exit"
354+ else :
355+ action_msg = "jump to line {lineno}"
356+ action_msg = action_msg .format (lineno = end )
357+ return action_msg
358+
349359 def missing_arc_description (self , start : TLineNo , end : TLineNo ) -> str :
350360 """Provide an English sentence describing a missing arc."""
351361 if self ._missing_arc_fragments is None :
@@ -355,22 +365,26 @@ def missing_arc_description(self, start: TLineNo, end: TLineNo) -> str:
355365 fragment_pairs = self ._missing_arc_fragments .get ((start , end ), [(None , None )])
356366
357367 msgs = []
358- for smsg , emsg in fragment_pairs :
359- if emsg is None :
360- if end < 0 :
361- emsg = "didn't jump to the function exit"
362- else :
363- emsg = "didn't jump to line {lineno}"
364- emsg = emsg .format (lineno = end )
365-
366- msg = f"line { start } { emsg } "
367- if smsg is not None :
368- msg += f" because { smsg .format (lineno = start )} "
368+ for missing_cause_msg , action_msg in fragment_pairs :
369+ action_msg = self ._finish_action_msg (action_msg , end )
370+ msg = f"line { start } didn't { action_msg } "
371+ if missing_cause_msg is not None :
372+ msg += f" because { missing_cause_msg .format (lineno = start )} "
369373
370374 msgs .append (msg )
371375
372376 return " or " .join (msgs )
373377
378+ def arc_description (self , start : TLineNo , end : TLineNo ) -> str :
379+ """Provide an English description of an arc's effect."""
380+ if self ._missing_arc_fragments is None :
381+ self ._analyze_ast ()
382+ assert self ._missing_arc_fragments is not None
383+
384+ fragment_pairs = self ._missing_arc_fragments .get ((start , end ), [(None , None )])
385+ action_msg = self ._finish_action_msg (fragment_pairs [0 ][1 ], end )
386+ return action_msg
387+
374388
375389class ByteParser :
376390 """Parse bytecode to understand the structure of code."""
@@ -451,7 +465,7 @@ class ArcStart:
451465
452466 `lineno` is the line number the arc starts from.
453467
454- `cause` is an English text fragment used as the `startmsg ` for
468+ `cause` is an English text fragment used as the `missing_cause_msg ` for
455469 AstArcAnalyzer.missing_arc_fragments. It will be used to describe why an
456470 arc wasn't executed, so should fit well into a sentence of the form,
457471 "Line 17 didn't run because {cause}." The fragment can include "{lineno}"
@@ -485,10 +499,21 @@ def __call__(
485499 self ,
486500 start : TLineNo ,
487501 end : TLineNo ,
488- smsg : str | None = None ,
489- emsg : str | None = None ,
502+ missing_cause_msg : str | None = None ,
503+ action_msg : str | None = None ,
490504 ) -> None :
491- ...
505+ """
506+ Record an arc from `start` to `end`.
507+
508+ `missing_cause_msg` is a description of the reason the arc wasn't
509+ taken if it wasn't taken. For example, "the condition on line 10 was
510+ never true."
511+
512+ `action_msg` is a description of what the arc does, like "jump to line
513+ 10" or "exit from function 'fooey'."
514+
515+ """
516+
492517
493518TArcFragments = Dict [TArc , List [Tuple [Optional [str ], Optional [str ]]]]
494519
@@ -549,15 +574,15 @@ def process_raise_exits(self, exits: set[ArcStart], add_arc: TAddArcFn) -> bool:
549574 for xit in exits :
550575 add_arc (
551576 xit .lineno , - self .start , xit .cause ,
552- f"didn't except from function { self .name !r} " ,
577+ f"except from function { self .name !r} " ,
553578 )
554579 return True
555580
556581 def process_return_exits (self , exits : set [ArcStart ], add_arc : TAddArcFn ) -> bool :
557582 for xit in exits :
558583 add_arc (
559584 xit .lineno , - self .start , xit .cause ,
560- f"didn't return from function { self .name !r} " ,
585+ f"return from function { self .name !r} " ,
561586 )
562587 return True
563588
@@ -601,10 +626,10 @@ class AstArcAnalyzer:
601626 `missing_arc_fragments`: a dict mapping (from, to) arcs to lists of
602627 message fragments explaining why the arc is missing from execution::
603628
604- { (start, end): [(startmsg, endmsg ), ...], }
629+ { (start, end): [(missing_cause_msg, action_msg ), ...], }
605630
606631 For an arc starting from line 17, they should be usable to form complete
607- sentences like: "Line 17 {endmsg } because {startmsg }".
632+ sentences like: "Line 17 didn't {action_msg } because {missing_cause_msg }".
608633
609634 NOTE: Starting in July 2024, I've been whittling this down to only report
610635 arc that are part of true branches. It's not clear how far this work will
@@ -715,30 +740,27 @@ def _code_object__FunctionDef(self, node: ast.FunctionDef) -> None:
715740
716741 def _code_object__ClassDef (self , node : ast .ClassDef ) -> None :
717742 start = self .line_for_node (node )
718- exits = self .process_body (node .body )#, from_start=ArcStart(start))
743+ exits = self .process_body (node .body )
719744 for xit in exits :
720- self .add_arc (
721- xit .lineno , - start , xit .cause ,
722- f"didn't exit the body of class { node .name !r} " ,
723- )
745+ self .add_arc (xit .lineno , - start , xit .cause , f"exit class { node .name !r} " )
724746
725747 def add_arc (
726748 self ,
727749 start : TLineNo ,
728750 end : TLineNo ,
729- smsg : str | None = None ,
730- emsg : str | None = None ,
751+ missing_cause_msg : str | None = None ,
752+ action_msg : str | None = None ,
731753 ) -> None :
732754 """Add an arc, including message fragments to use if it is missing."""
733755 if self .debug : # pragma: debugging
734- print (f"Adding possible arc: ({ start } , { end } ): { smsg !r} , { emsg !r} " )
756+ print (f"Adding possible arc: ({ start } , { end } ): { missing_cause_msg !r} , { action_msg !r} " )
735757 print (short_stack (), end = "\n \n " )
736758 self .arcs .add ((start , end ))
737759 if start in self .current_with_starts :
738760 self .with_entries .add ((start , end ))
739761
740- if smsg is not None or emsg is not None :
741- self .missing_arc_fragments [(start , end )].append ((smsg , emsg ))
762+ if missing_cause_msg is not None or action_msg is not None :
763+ self .missing_arc_fragments [(start , end )].append ((missing_cause_msg , action_msg ))
742764
743765 def nearest_blocks (self ) -> Iterable [Block ]:
744766 """Yield the blocks in nearest-to-farthest order."""
0 commit comments