Note that finding undefined vars in `finally` requires different handling from the rest of the code. In particular, we want to disallow skipping branches due to jump statements in except/else clauses for finally but not for other cases. Imagine a case like: def f() -
(self, o: TryStmt)
| 530 | super().visit_expression_stmt(o) |
| 531 | |
| 532 | def visit_try_stmt(self, o: TryStmt) -> None: |
| 533 | """ |
| 534 | Note that finding undefined vars in `finally` requires different handling from |
| 535 | the rest of the code. In particular, we want to disallow skipping branches due to jump |
| 536 | statements in except/else clauses for finally but not for other cases. Imagine a case like: |
| 537 | def f() -> int: |
| 538 | try: |
| 539 | x = 1 |
| 540 | except: |
| 541 | # This jump statement needs to be handled differently depending on whether or |
| 542 | # not we're trying to process `finally` or not. |
| 543 | return 0 |
| 544 | finally: |
| 545 | # `x` may be undefined here. |
| 546 | pass |
| 547 | # `x` is always defined here. |
| 548 | return x |
| 549 | """ |
| 550 | self.try_depth += 1 |
| 551 | if o.finally_body is not None: |
| 552 | # In order to find undefined vars in `finally`, we need to |
| 553 | # process try/except with branch skipping disabled. However, for the rest of the code |
| 554 | # after finally, we need to process try/except with branch skipping enabled. |
| 555 | # Therefore, we need to process try/finally twice. |
| 556 | # Because processing is not idempotent, we should make a copy of the tracker. |
| 557 | old_tracker = self.tracker.copy() |
| 558 | self.tracker.disable_branch_skip = True |
| 559 | self.process_try_stmt(o) |
| 560 | self.tracker = old_tracker |
| 561 | self.process_try_stmt(o) |
| 562 | self.try_depth -= 1 |
| 563 | |
| 564 | def process_try_stmt(self, o: TryStmt) -> None: |
| 565 | """ |
nothing calls this directly
no test coverage detected