Perform some limited variable renaming in with statements. This allows reusing a variable in multiple with statements with different types. For example, the two instances of 'x' can have incompatible types: with C() as x: f(x) with D() as x: g(x)
| 425 | |
| 426 | |
| 427 | class LimitedVariableRenameVisitor(TraverserVisitor): |
| 428 | """Perform some limited variable renaming in with statements. |
| 429 | |
| 430 | This allows reusing a variable in multiple with statements with |
| 431 | different types. For example, the two instances of 'x' can have |
| 432 | incompatible types: |
| 433 | |
| 434 | with C() as x: |
| 435 | f(x) |
| 436 | with D() as x: |
| 437 | g(x) |
| 438 | |
| 439 | The above code gets renamed conceptually into this (not valid Python!): |
| 440 | |
| 441 | with C() as x': |
| 442 | f(x') |
| 443 | with D() as x: |
| 444 | g(x) |
| 445 | |
| 446 | If there's a reference to a variable defined in 'with' outside the |
| 447 | statement, or if there's any trickiness around variable visibility |
| 448 | (e.g. function definitions), we give up and won't perform renaming. |
| 449 | |
| 450 | The main use case is to allow binding both readable and writable |
| 451 | binary files into the same variable. These have different types: |
| 452 | |
| 453 | with open(fnam, 'rb') as f: ... |
| 454 | with open(fnam, 'wb') as f: ... |
| 455 | """ |
| 456 | |
| 457 | def __init__(self) -> None: |
| 458 | # Short names of variables bound in with statements using "as" |
| 459 | # in a surrounding scope |
| 460 | self.bound_vars: list[str] = [] |
| 461 | # Stack of names that can't be safely renamed, per scope ('*' means that |
| 462 | # no names can be renamed) |
| 463 | self.skipped: list[set[str]] = [] |
| 464 | # References to variables that we may need to rename. Stack of |
| 465 | # scopes; each scope is a mapping from name to list of collections |
| 466 | # of names that refer to the same logical variable. |
| 467 | self.refs: list[dict[str, list[list[NameExpr]]]] = [] |
| 468 | |
| 469 | def visit_mypy_file(self, file_node: MypyFile) -> None: |
| 470 | """Rename variables within a file. |
| 471 | |
| 472 | This is the main entry point to this class. |
| 473 | """ |
| 474 | with self.enter_scope(): |
| 475 | for d in file_node.defs: |
| 476 | d.accept(self) |
| 477 | |
| 478 | def visit_func_def(self, fdef: FuncDef) -> None: |
| 479 | self.reject_redefinition_of_vars_in_scope() |
| 480 | with self.enter_scope(): |
| 481 | for arg in fdef.arguments: |
| 482 | self.record_skipped(arg.variable.name) |
| 483 | super().visit_func_def(fdef) |
| 484 |
no outgoing calls
no test coverage detected
searching dependent graphs…