Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow redefinitions in except/else/finally #18515

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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]
sterliakov marked this conversation as resolved.
Show resolved Hide resolved
# 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:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please, also test 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
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This note no longer applies, necessary stubs were connected in #17384.

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
Loading