pylint icon indicating copy to clipboard operation
pylint copied to clipboard

typing_extensions assert_never's Never not supported

Open henryiii opened this issue 2 years ago • 6 comments

Bug description

Python 3.11 added Never as an alias of NoReturn. The signature of assert_never is assert_never(__x: Never) -> Never (personally, I wish had been assert_never(__x: Never) -> NoReturn, but that's what it is). This was backported to typing_extensions 4.1.0, as well.

I tried the following, but it seems typing_extensions.assert_never is uninferable.

diff --git a/pylint/checkers/refactoring/refactoring_checker.py b/pylint/checkers/refactoring/refactoring_checker.py
index ed1b7a938..21b9b2b07 100644
--- a/pylint/checkers/refactoring/refactoring_checker.py
+++ b/pylint/checkers/refactoring/refactoring_checker.py
@@ -1907,9 +1907,9 @@ class RefactoringChecker(checkers.BaseTokenChecker):
         if isinstance(node, nodes.FunctionDef) and node.returns:
             return (
                 isinstance(node.returns, nodes.Attribute)
-                and node.returns.attrname == "NoReturn"
+                and node.returns.attrname in {"NoReturn", "Never"}
                 or isinstance(node.returns, nodes.Name)
-                and node.returns.name == "NoReturn"
+                and node.returns.name in {"NoReturn", "Never"}
             )
         try:
             return node.qname() in self._never_returning_functions

If it's defined locally, however:

def assert_never(value: Never) -> Never:
    assert False, f"Unhandled value: {value} ({type(value).__name__})"

Then the above patch fixes it, so there's that, at least. Maybe that would at least fix it on Python 3.11? Maybe typing_extensions.assert_never could be added to the list of no return functions if it can't be inferred? However, when I stick an print statement in here, it seems the node is "Uninferrable", and adding "typing_extensions.assert_never" to refactoring.never-returning-functions does not work.

This is using cibuildwheel's source, see https://github.com/pypa/cibuildwheel/pull/1291.

It looks like this was partially addressed in https://github.com/PyCQA/pylint/issues/7311, but not for all places NoReturn shows up.

Configuration

No response

Command used

pylint src

Pylint output

cibuildwheel/architecture.py:117:4: R1710: Either all return statements in a function should return an expression, or none of them should. (inconsistent-return-statements)

Expected behavior

Your code has been rated at 10.00/10 (previous run: 10.00/10, +0.00)

Pylint version

pylint 2.15.3
astroid 2.12.10
Python 3.10.7 (main, Sep 15 2022, 01:51:29) [Clang 14.0.0 (clang-1400.0.29.102)]

OS / Environment

No response

Additional dependencies

typing_extensions==4.3.0

henryiii avatar Oct 03 '22 20:10 henryiii

I'd like to point out that this is still a bug in pylint, which is quite annoying when you use assert_never relatively often. The patch above fixes the problem, so it would be nice to have it in the code.

In addition, handling self._never_returning_function after the first if seems wrong: If one explicitly specifies that a function never returns, this should override any inference by pylint, so this test should come first. At the current state, this configuration option is quite useless, one can't e.g. specify that assert_never never returns.

svenpanne avatar Sep 01 '23 10:09 svenpanne

Do you want to open a PR with the patch @svenpanne ?

If one explicitly specifies that a function never returns, this should override any inference by pylint

This is not the case right now, see #4813. Changing that will be quite a journey

At the current state, this configuration option is quite useless, one can't e.g. specify that assert_never never returns.

What configuration options are we talking about ?

Pierre-Sassoulas avatar Sep 01 '23 12:09 Pierre-Sassoulas

Do you want to open a PR with the patch @svenpanne ?

It's basically the patch above, let's see when I find some time...

If one explicitly specifies that a function never returns, this should override any inference by pylint

This is not the case right now, see #4813. Changing that will be quite a journey

Actually pylint already figures out enough, verified by print()ing in the above method. It's just that pylint doesn't handle the Never type introduced in Python 3.11 last year, see e.g. https://typing.readthedocs.io/en/latest/source/unreachable.html. In addition to checking for NoReturn, it needs to check fo Never, too. This would already solve the problem for assert_never.

At the current state, this configuration option is quite useless, one can't e.g. specify that assert_never never returns.

What configuration options are we talking about ?

https://pylint.readthedocs.io/en/latest/user_guide/configuration/all-options.html#never-returning-functions My point is: If the user explicitly configures that some functions never return, this should override any logic in pylint, so the order of tests in the function above should be reversed. If this was the case, adding typing.assert_never to that configuration option would work even without the Never change above, but currently it doesn't.

So in effect, there are 2 bugs in the method.

svenpanne avatar Sep 01 '23 13:09 svenpanne

Repro for the second issue @svenpanne describes:

bug7565.py:

# pylint: disable=missing-function-docstring,missing-module-docstring
from __future__ import annotations

from typing_extensions import (
        assert_never,
        Union,
        )

def some_func(arg: Union[int, str]) ->  int:

    if isinstance(arg, int):
        return 1
    if isinstance(arg, str):
        return 2

    assert_never(arg)

pylint --rcfile=/dev/null --never-returning-functions=typing_extensions.assert_never bug7565.py:

************* Module bug7565
bug7565.py:9:0: R1710: Either all return statements in a function should return an expression, or none of them should. (inconsistent-return-statements)

-----------------------------------
Your code has been rated at 8.75/10

finite-state-machine avatar Jan 31 '24 17:01 finite-state-machine

Any update on this?

antoniogamizbadger avatar Apr 01 '24 14:04 antoniogamizbadger

OK, I've uploaded a PR for this, fixing both problems: Never was unknown and explicitly stating non-returning functions had the wrong priority compared to the incomplete inference of astroid/pylint.

With this change, things work out-of-the-box for Python 3.11 and 3.12 with both typing and typing_extensions. Older Python versions have to use the latter, which exposes its own implementation of assert_never then. Somehow astroid can't infer anything about that then, but this is a different problem. For a home-grown & visible version, things work, too, even for such old Pythons.

svenpanne avatar Apr 03 '24 16:04 svenpanne