diff --git a/doc/whatsnew/fragments/9643.false_positive b/doc/whatsnew/fragments/9643.false_positive new file mode 100644 index 0000000000..471807d3b2 --- /dev/null +++ b/doc/whatsnew/fragments/9643.false_positive @@ -0,0 +1,4 @@ +Fix a false positive for `possibly-used-before-assignment` when using +`typing.assert_never()` (3.11+) to indicate exhaustiveness. + +Closes #9643 diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 39c9051ed0..6c33a05556 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -32,7 +32,7 @@ is_sys_guard, overridden_method, ) -from pylint.constants import PY39_PLUS, TYPING_NEVER, TYPING_NORETURN +from pylint.constants import PY39_PLUS, PY311_PLUS, TYPING_NEVER, TYPING_NORETURN from pylint.interfaces import CONTROL_FLOW, HIGH, INFERENCE, INFERENCE_FAILURE from pylint.typing import MessageDefinitionTuple @@ -940,12 +940,15 @@ def _uncertain_nodes_in_except_blocks( def _defines_name_raises_or_returns(name: str, node: nodes.NodeNG) -> bool: if isinstance(node, (nodes.Raise, nodes.Assert, nodes.Return, nodes.Continue)): return True - if ( - isinstance(node, nodes.Expr) - and isinstance(node.value, nodes.Call) - and utils.is_terminating_func(node.value) - ): - return True + if isinstance(node, nodes.Expr) and isinstance(node.value, nodes.Call): + if utils.is_terminating_func(node.value): + return True + if ( + PY311_PLUS + and isinstance(node.value.func, nodes.Name) + and node.value.func.name == "assert_never" + ): + return True if ( isinstance(node, nodes.AnnAssign) and node.value diff --git a/pylint/constants.py b/pylint/constants.py index e51022e654..f147e5189a 100644 --- a/pylint/constants.py +++ b/pylint/constants.py @@ -17,6 +17,7 @@ PY38_PLUS = sys.version_info[:2] >= (3, 8) PY39_PLUS = sys.version_info[:2] >= (3, 9) PY310_PLUS = sys.version_info[:2] >= (3, 10) +PY311_PLUS = sys.version_info[:2] >= (3, 11) PY312_PLUS = sys.version_info[:2] >= (3, 12) IS_PYPY = platform.python_implementation() == "PyPy" diff --git a/tests/functional/u/used/used_before_assignment_py311.py b/tests/functional/u/used/used_before_assignment_py311.py new file mode 100644 index 0000000000..2e46ff5fd6 --- /dev/null +++ b/tests/functional/u/used/used_before_assignment_py311.py @@ -0,0 +1,21 @@ +"""assert_never() introduced in 3.11""" +from enum import Enum +from typing import assert_never + + +class MyEnum(Enum): + """A lovely enum.""" + VAL1 = 1 + VAL2 = 2 + + +def do_thing(val: MyEnum) -> None: + """Do a thing.""" + if val is MyEnum.VAL1: + note = 'got 1' + elif val is MyEnum.VAL2: + note = 'got 2' + else: + assert_never(val) + + print('Note:', note) diff --git a/tests/functional/u/used/used_before_assignment_py311.rc b/tests/functional/u/used/used_before_assignment_py311.rc new file mode 100644 index 0000000000..56e6770585 --- /dev/null +++ b/tests/functional/u/used/used_before_assignment_py311.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.11