-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Report unreachable except blocks #12086
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
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Cool feature! I had some bugs with except
order in my life 👍
mypy/checker.py
Outdated
if expr and isinstance(expr, NameExpr) and isinstance(expr.node, TypeInfo): | ||
with self.expr_checker.msg.disable_errors(): | ||
typ = get_proper_type(self.check_except_handler_test(expr)) | ||
if isinstance(typ, AnyType): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be a Union
with Any
type as well 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you think of a case where this happens?
I would argue that his can only happen for variables and tuples (which are both filtered with the if expr and isinstance(expr, NameExpr) and isinstance(expr.node, TypeInfo):
)
For safety reasons: I can add Union to the check if isinstance(typ, AnyType) or isinstance(typ, UnionType):
mypy/messages.py
Outdated
@@ -1386,6 +1386,15 @@ def note_call(self, | |||
def unreachable_statement(self, context: Context) -> None: | |||
self.fail("Statement is unreachable", context, code=codes.UNREACHABLE) | |||
|
|||
def already_caught(self, typ: Type, context: Context) -> None: | |||
self.fail("Except is unreachable, {} has already been caught".format(format_type(typ)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add new simple errors to message_registry
@@ -841,6 +841,38 @@ def baz(x: int) -> int: | |||
return x | |||
[builtins fixtures/exception.pyi] | |||
|
|||
[case testUnreachableFlagExcepts] | |||
# flags: --warn-unreachable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need the same test (or simplified) without this flag to make sure it does not produce any errors in normal mode.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where should this test go? Still in check-unreachable-code.test
? I cannot find the equivalent for other features in this file. Into check-statements.test?
In check-statements.test
there is already a test covering the use-case.
[case testMultipleExceptHandlers]
import typing
try:
pass
except BaseException as e:
pass
except Err as f:
f = BaseException() # Fail
f = Err()
class Err(BaseException): pass
[builtins fixtures/exception.pyi]
[out]
main:7: error: Incompatible types in assignment (expression has type "BaseException", variable has type "Err")
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, check-unreachable-code.test
is fine I guess 🙂
def error() -> NoReturn: ... | ||
|
||
ignore: Type[Exception] | ||
exclude: Tuple[Type[Exception], ...] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's also test Union
type here.
|
||
ignore: Type[Exception] | ||
exclude: Tuple[Type[Exception], ...] | ||
class ContinueOnError: pass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
class ContinueOnError: pass | |
class ContinueOnError(Exception): pass |
Is this what you meant? 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to provoke self.check_except_handler_test(expr)
to produce and error and thus return Any
. In this case, we must ignore it, otherwise is_subtype
always evaluates to True
.
I will add a test to make this more clear.
pass | ||
except RuntimeError: # E: Except is unreachable, superclass "Exception" of "RuntimeError" has already been caught | ||
pass | ||
except (NotImplementedError, ): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that this should be reported as unreachable as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean I should support tuples in general or only this special case?
My feeling is that this already makes the code more complex, because the simple check expr and isinstance(expr, NameExpr) and isinstance(expr.node, TypeInfo)
to support everything that is not supported is no longer sufficient. If its fine, I would like to move it into a separate PR to keep this a bit "cleaner".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can defer this to the next PR I guess 🙂
But, please don't forget to open an issue for it!
@sobolevn Thank you for your feedback 🙂. I pushed some updates. Can you have a look again? |
if expr and isinstance(expr, NameExpr) and isinstance(expr.node, TypeInfo): | ||
with self.expr_checker.msg.disable_errors(): | ||
typ = get_proper_type(self.check_except_handler_test(expr)) | ||
if isinstance(typ, AnyType) or isinstance(typ, UnionType): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, this is not what I meant 🙂
Theoretically we can have a case when some var has type[SomeException] | Any
type. For example, from unresolved import or some runtime magic.
Is there anything we can do to handle this case here? Or shall we ignore it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I know what you mean. So, we do not have a variable, but a class whose type might be a Union.
I do not how how to construct such a case for testing, can you help me with that? I was also not able to find something in check-statements.test
The tests already cover:
Parent: Any
class MyError(Parent): ...
omit: Union[Type[RuntimeError], Type[MyError]]
Nevertheless, it think this scenario would not pass the new or isinstance(typ, UnionType)
filter, thus we would just ignore it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sobolevn any updates here? ;)
This comment has been minimized.
This comment has been minimized.
According to mypy_primer, this change has no effect on the checked open source code. 🤖🎉 |
Motivation
Detect unreachable except branches as proposed here #11514.
These checks are already implemented in PyCharm and were also picked up by pyright (microsoft/pyright#2761).
Implementation
I mainly followed the idea/plan given by @sobolevn in the linked issue. Thank you for that! :)
Rationale
I decided to not implement this directly in
visit_try_without_finally
where the except expressions are already evaluated, because the feature is only enabled via thewarn-unreachable
flag and it would make the loop even more complex.In order to avoid false positives, the check is skipped for variables in the except clause (they are usually typed as
Type[Exception]
).Limitations
Even though the checks could be extended to Tuples in the except clause, I decided to not implement it in this first draft.
Test Plan
[case testUnreachableFlagExcepts]
was added.It checks the error messages as well as that no false positives for variables are generated.
Closes #11514