From aea04541653f71da733bc0b6a8e5fb87bcaf1b5b Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 28 Oct 2023 13:24:05 -0400 Subject: [PATCH 1/3] Fix ``undefined-variable`` etc for Python 3.12 generic type syntax --- doc/whatsnew/fragments/9193.false_positive | 4 ++++ pylint/checkers/variables.py | 19 ++++++++++++++++--- .../u/undefined/undefined_variable_py312.py | 7 +++++++ .../u/undefined/undefined_variable_py312.rc | 2 ++ 4 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 doc/whatsnew/fragments/9193.false_positive create mode 100644 tests/functional/u/undefined/undefined_variable_py312.py create mode 100644 tests/functional/u/undefined/undefined_variable_py312.rc diff --git a/doc/whatsnew/fragments/9193.false_positive b/doc/whatsnew/fragments/9193.false_positive new file mode 100644 index 0000000000..39dc70b81d --- /dev/null +++ b/doc/whatsnew/fragments/9193.false_positive @@ -0,0 +1,4 @@ +Fix false positives for ``undefined-variable`` and ``unused-argument`` for +classes and functions using Python 3.12 generic type syntax. + +Closes #9193 diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 6cf3924665..34f5a54607 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -1738,6 +1738,10 @@ def _should_node_be_skipped( elif consumer.scope_type == "function" and self._defined_in_function_definition( node, consumer.node ): + for param in consumer.node.type_params: + if node.name == param.name.name: + return False + # If the name node is used as a function default argument's value or as # a decorator, then start from the parent frame of the function instead # of the function frame - and thus open an inner class scope @@ -1861,6 +1865,12 @@ def _check_consumer( or annotation_return or isinstance(defstmt, nodes.Delete) ): + if ( + defined_by_stmt + and isinstance(defstmt, nodes.ClassDef) + and node.parent in defstmt.type_params + ): + return (VariableVisitConsumerAction.RETURN, None) if not utils.node_ignores_exception(node, NameError): # Handle postponed evaluation of annotations if not ( @@ -2262,10 +2272,13 @@ def _is_variable_violation( isinstance(defframe, nodes.FunctionDef) and frame is defframe and defframe.parent_of(node) - and stmt is not defstmt + and ( + defnode in defframe.type_params + # Single statement function, with the statement on the + # same line as the function definition + or stmt is not defstmt + ) ): - # Single statement function, with the statement on the - # same line as the function definition maybe_before_assign = False elif ( isinstance(defstmt, NODES_WITH_VALUE_ATTR) diff --git a/tests/functional/u/undefined/undefined_variable_py312.py b/tests/functional/u/undefined/undefined_variable_py312.py new file mode 100644 index 0000000000..0ca2475eb9 --- /dev/null +++ b/tests/functional/u/undefined/undefined_variable_py312.py @@ -0,0 +1,7 @@ +# pylint: disable=missing-function-docstring,missing-module-docstring,missing-class-docstring,too-few-public-methods + +def f[T](a: T) -> T: + print(a) + +class ChildClass[T, *Ts, **P]: + ... diff --git a/tests/functional/u/undefined/undefined_variable_py312.rc b/tests/functional/u/undefined/undefined_variable_py312.rc new file mode 100644 index 0000000000..9c966d4bda --- /dev/null +++ b/tests/functional/u/undefined/undefined_variable_py312.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.12 From ea8d6cf1135e6536548694236beccffe9eb983e7 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 28 Oct 2023 13:30:53 -0400 Subject: [PATCH 2/3] fixup! Fix ``undefined-variable`` --- pylint/checkers/variables.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 34f5a54607..e00e33426a 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -1738,9 +1738,8 @@ def _should_node_be_skipped( elif consumer.scope_type == "function" and self._defined_in_function_definition( node, consumer.node ): - for param in consumer.node.type_params: - if node.name == param.name.name: - return False + if any(node.name == param.name.name for param in consumer.node.type_params): + return False # If the name node is used as a function default argument's value or as # a decorator, then start from the parent frame of the function instead From 23d6041736ffe787a47fee54f00fd042b6840ad3 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sun, 29 Oct 2023 10:41:55 -0400 Subject: [PATCH 3/3] Remove code not essential to pass test --- pylint/checkers/variables.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index e00e33426a..c06efc3d85 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -1864,12 +1864,6 @@ def _check_consumer( or annotation_return or isinstance(defstmt, nodes.Delete) ): - if ( - defined_by_stmt - and isinstance(defstmt, nodes.ClassDef) - and node.parent in defstmt.type_params - ): - return (VariableVisitConsumerAction.RETURN, None) if not utils.node_ignores_exception(node, NameError): # Handle postponed evaluation of annotations if not (