Skip to content

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

Closed

Conversation

kreathon
Copy link
Contributor

Motivation

Detect unreachable except branches as proposed here #11514.

try:
    error()
except Exception:
    pass
except RuntimeError:  # This is unreachable.
    pass

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 the warn-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

Copy link
Member

@sobolevn sobolevn left a 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):
Copy link
Member

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 🤔

Copy link
Contributor Author

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)),
Copy link
Member

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
Copy link
Member

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.

Copy link
Contributor Author

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")

Copy link
Member

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], ...]
Copy link
Member

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
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
class ContinueOnError: pass
class ContinueOnError(Exception): pass

Is this what you meant? 🤔

Copy link
Contributor Author

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, ):
Copy link
Member

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.

Copy link
Contributor Author

@kreathon kreathon Jan 28, 2022

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".

Copy link
Member

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!

@kreathon
Copy link
Contributor Author

@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):
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@sobolevn any updates here? ;)

@kreathon kreathon requested a review from sobolevn March 10, 2022 16:57
@github-actions

This comment has been minimized.

@JelleZijlstra
Copy link
Member

You'll need to adjust to the error checker changes from #12631 and #12699.

@github-actions
Copy link
Contributor

github-actions bot commented May 1, 2022

According to mypy_primer, this change has no effect on the checked open source code. 🤖🎉

@kreathon kreathon closed this Dec 27, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Detect unreachable except branches
3 participants