From 6fafcdeb884e7c8103b3bac24b34f18f5c8242e9 Mon Sep 17 00:00:00 2001 From: STerliakov Date: Thu, 23 Jan 2025 16:13:37 +0100 Subject: [PATCH 1/3] Allow renaming in except/else/finally clauses --- mypy/renaming.py | 16 +++++++++- test-data/unit/check-redefine.test | 49 ++++++++++++++++++++++++++++-- 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/mypy/renaming.py b/mypy/renaming.py index 7cc96566235a8..dff76b157acc0 100644 --- a/mypy/renaming.py +++ b/mypy/renaming.py @@ -152,7 +152,21 @@ def visit_try_stmt(self, stmt: TryStmt) -> None: # type checker which allows them to be always redefined, so no need to # do renaming here. with self.enter_try(): - super().visit_try_stmt(stmt) + stmt.body.accept(self) + + for var, tp, handler in zip(stmt.vars, stmt.types, stmt.handlers): + with self.enter_block(): + # Handle except variable together with its body + if tp is not None: + tp.accept(self) + if var is not None: + self.handle_def(var) + for s in handler.body: + s.accept(self) + if stmt.else_body is not None: + stmt.else_body.accept(self) + if stmt.finally_body is not None: + stmt.finally_body.accept(self) def visit_with_stmt(self, stmt: WithStmt) -> None: for expr in stmt.expr: diff --git a/test-data/unit/check-redefine.test b/test-data/unit/check-redefine.test index b7642d30efc8d..e72fc209204f3 100644 --- a/test-data/unit/check-redefine.test +++ b/test-data/unit/check-redefine.test @@ -88,6 +88,7 @@ def h(a: Iterable[int]) -> None: [case testCannotRedefineLocalWithinTry] # flags: --allow-redefinition +def g(): pass def f() -> None: try: x = 0 @@ -102,7 +103,52 @@ def f() -> None: y y = '' -def g(): pass +[case testRedefineLocalWithinTryClauses] +# flags: --allow-redefinition +def fn_str(_: str) -> int: ... +def fn_int(_: int) -> None: ... + +def in_block() -> None: + try: + a = "" + a = fn_str(a) # E: Incompatible types in assignment (expression has type "int", variable has type "str") + fn_int(a) # E: Argument 1 to "fn_int" has incompatible type "str"; expected "int" + except: + b = "" + b = fn_str(b) + fn_int(b) + else: + c = "" + c = fn_str(c) + fn_int(c) + finally: + d = "" + d = fn_str(d) + fn_int(d) + reveal_type(a) # N: Revealed type is "builtins.str" + reveal_type(b) # N: Revealed type is "builtins.int" + reveal_type(c) # N: Revealed type is "builtins.int" + reveal_type(d) # N: Revealed type is "builtins.int" + +def across_blocks() -> None: + try: + a = "" + except: + pass + else: + a = fn_str(a) # E: Incompatible types in assignment (expression has type "int", variable has type "str") + reveal_type(a) # N: Revealed type is "builtins.str" + +[case testRedefineLocalExceptVar] +# flags: --allow-redefinition +def fn_exc(_: Exception) -> str: ... + +def exc_name() -> None: + try: + pass + except RuntimeError as e: + e = fn_exc(e) +[builtins fixtures/exception.pyi] [case testRedefineLocalWithinWith] # flags: --allow-redefinition @@ -274,7 +320,6 @@ def f() -> None: # E: Incompatible types in assignment (expression has type "int", variable has type "TypeVar") reveal_type(x) # N: Revealed type is "typing.TypeVar" y = 1 - # NOTE: '"int" not callable' is due to test stubs y = TypeVar('y') # E: Cannot redefine "y" as a type variable \ # E: Incompatible types in assignment (expression has type "TypeVar", variable has type "int") def h(a: y) -> y: return a # E: Variable "y" is not valid as a type \ From a0ad1b9acfd09b0b8b8e51b0302a4142760a8401 Mon Sep 17 00:00:00 2001 From: STerliakov Date: Thu, 23 Jan 2025 18:52:59 +0100 Subject: [PATCH 2/3] Add a testcase with nested `try` --- test-data/unit/check-redefine.test | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test-data/unit/check-redefine.test b/test-data/unit/check-redefine.test index e72fc209204f3..1aacffe1fc938 100644 --- a/test-data/unit/check-redefine.test +++ b/test-data/unit/check-redefine.test @@ -150,6 +150,21 @@ def exc_name() -> None: e = fn_exc(e) [builtins fixtures/exception.pyi] +[case testRedefineNestedInTry] +# flags: --allow-redefinition + +def fn_int(_: int) -> None: ... + +try: + try: + ... + finally: + a = "" + a = 5 # E: Incompatible types in assignment (expression has type "int", variable has type "str") + fn_int(a) # E: Argument 1 to "fn_int" has incompatible type "str"; expected "int" +except: + pass + [case testRedefineLocalWithinWith] # flags: --allow-redefinition def f() -> None: From 00e226101589e47d368edc8b1e78741975baa749 Mon Sep 17 00:00:00 2001 From: STerliakov Date: Fri, 24 Jan 2025 18:26:24 +0100 Subject: [PATCH 3/3] Add similar test with except* --- test-data/unit/check-python311.test | 44 +++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/test-data/unit/check-python311.test b/test-data/unit/check-python311.test index 6f4c540572b00..dfbb3d45e56fc 100644 --- a/test-data/unit/check-python311.test +++ b/test-data/unit/check-python311.test @@ -259,3 +259,47 @@ def foo(): continue # E: "continue" not allowed in except* block return # E: "return" not allowed in except* block [builtins fixtures/exception.pyi] + +[case testRedefineLocalWithinExceptStarTryClauses] +# flags: --allow-redefinition +def fn_str(_: str) -> int: ... +def fn_int(_: int) -> None: ... +def fn_exc(_: Exception) -> str: ... + +def in_block() -> None: + try: + a = "" + a = fn_str(a) # E: Incompatible types in assignment (expression has type "int", variable has type "str") + fn_int(a) # E: Argument 1 to "fn_int" has incompatible type "str"; expected "int" + except* Exception: + b = "" + b = fn_str(b) + fn_int(b) + else: + c = "" + c = fn_str(c) + fn_int(c) + finally: + d = "" + d = fn_str(d) + fn_int(d) + reveal_type(a) # N: Revealed type is "builtins.str" + reveal_type(b) # N: Revealed type is "builtins.int" + reveal_type(c) # N: Revealed type is "builtins.int" + reveal_type(d) # N: Revealed type is "builtins.int" + +def across_blocks() -> None: + try: + a = "" + except* Exception: + a = fn_str(a) # E: Incompatible types in assignment (expression has type "int", variable has type "str") + else: + a = fn_str(a) # E: Incompatible types in assignment (expression has type "int", variable has type "str") + reveal_type(a) # N: Revealed type is "builtins.str" + +def exc_name() -> None: + try: + pass + except* RuntimeError as e: + e = fn_exc(e) +[builtins fixtures/exception.pyi]