Skip to content

Commit

Permalink
Allow redefinitions in except/else/finally (#18515)
Browse files Browse the repository at this point in the history
Fixes #18514.

Only `try` clause should be treated as fallible, this should not prevent
`--allow-redefinition` from working in other try clauses (except, else,
finally).
  • Loading branch information
sterliakov authored Jan 26, 2025
1 parent 1eb9d4c commit 0451880
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 3 deletions.
16 changes: 15 additions & 1 deletion mypy/renaming.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
44 changes: 44 additions & 0 deletions test-data/unit/check-python311.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]
64 changes: 62 additions & 2 deletions test-data/unit/check-redefine.test
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ def h(a: Iterable[int]) -> None:

[case testCannotRedefineLocalWithinTry]
# flags: --allow-redefinition
def g(): pass
def f() -> None:
try:
x = 0
Expand All @@ -102,7 +103,67 @@ 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 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
Expand Down Expand Up @@ -274,7 +335,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 \
Expand Down

0 comments on commit 0451880

Please sign in to comment.