From 015c43afd1c8acb6e4da5d0eed1058b98dd84fc5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 30 Dec 2023 20:24:01 +0100 Subject: [PATCH 1/7] Fixed false positive nested-min-max for nested lists (#9323) (#9337) Nesting is useful for finding the maximum in a matrix. Therefore, pylint allows nesting of the form: max(max([[1, 2, 3], [4, 5, 6]])) Closes #9307 Co-authored-by: Pierre Sassoulas (cherry picked from commit da13c74213ef89d7b54a2f973a85ce1ac179e966) Co-authored-by: Udi Fuchs --- doc/whatsnew/fragments/9307.false_positive | 3 +++ pylint/checkers/nested_min_max.py | 9 ++++++++- tests/functional/n/nested_min_max.py | 8 ++++++++ tests/functional/n/nested_min_max.txt | 2 ++ 4 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 doc/whatsnew/fragments/9307.false_positive diff --git a/doc/whatsnew/fragments/9307.false_positive b/doc/whatsnew/fragments/9307.false_positive new file mode 100644 index 0000000000..546959bf66 --- /dev/null +++ b/doc/whatsnew/fragments/9307.false_positive @@ -0,0 +1,3 @@ +Fixed false positive nested-min-max for nested lists. + +Closes #9307 diff --git a/pylint/checkers/nested_min_max.py b/pylint/checkers/nested_min_max.py index a935d62f53..c8231fe7d2 100644 --- a/pylint/checkers/nested_min_max.py +++ b/pylint/checkers/nested_min_max.py @@ -62,7 +62,14 @@ def get_redundant_calls(cls, node: nodes.Call) -> list[nodes.Call]: return [ arg for arg in node.args - if cls.is_min_max_call(arg) and arg.func.name == node.func.name + if ( + cls.is_min_max_call(arg) + and arg.func.name == node.func.name + # Nesting is useful for finding the maximum in a matrix. + # Allow: max(max([[1, 2, 3], [4, 5, 6]])) + # Meaning, redundant call only if parent max call has more than 1 arg. + and len(arg.parent.args) > 1 + ) ] @only_required_for_messages("nested-min-max") diff --git a/tests/functional/n/nested_min_max.py b/tests/functional/n/nested_min_max.py index 7bb11264e9..d4c31bd858 100644 --- a/tests/functional/n/nested_min_max.py +++ b/tests/functional/n/nested_min_max.py @@ -54,3 +54,11 @@ max(3, max(list(range(4)))) # [nested-min-max] max(3, *list(range(4))) + +# Nesting is useful for finding the maximum in a matrix +# No message if external call has exactly 1 argument +matrix = [[1, 2, 3], [4, 5, 6]] +max(max(matrix)) +max(max(max(matrix))) +max(3, max(max(matrix))) # [nested-min-max] +max(max(3, max(matrix))) # [nested-min-max] diff --git a/tests/functional/n/nested_min_max.txt b/tests/functional/n/nested_min_max.txt index 87b31daf65..80d6a24d32 100644 --- a/tests/functional/n/nested_min_max.txt +++ b/tests/functional/n/nested_min_max.txt @@ -16,3 +16,5 @@ nested-min-max:46:0:46:25::Do not use nested call of 'max'; it's possible to do nested-min-max:49:0:49:45::Do not use nested call of 'max'; it's possible to do 'max(3, *[5] + [i for i in range(4) if i])' instead:INFERENCE nested-min-max:52:0:52:33::Do not use nested call of 'max'; it's possible to do 'max(3, *[5] + list(range(4)))' instead:INFERENCE nested-min-max:55:0:55:27::Do not use nested call of 'max'; it's possible to do 'max(3, *list(range(4)))' instead:INFERENCE +nested-min-max:63:0:63:24::Do not use nested call of 'max'; it's possible to do 'max(3, max(matrix))' instead:INFERENCE +nested-min-max:64:4:64:23::Do not use nested call of 'max'; it's possible to do 'max(3, *matrix)' instead:INFERENCE From 0bbaddcfa28fb9a8a302925779edc3b206019eb2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 10 Jan 2024 23:07:17 +0100 Subject: [PATCH 2/7] Fix FP for `no-member`: function attributes from decorator (#9308) (#9355) (cherry picked from commit 4739f844b5fa6819fcc8b0229ef7640c0649321c) Co-authored-by: Jacob Walls --- doc/whatsnew/fragments/9246.false_positive | 4 ++++ pylint/checkers/typecheck.py | 8 +++++++- tests/functional/n/no/no_member_decorator.py | 16 ++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 doc/whatsnew/fragments/9246.false_positive create mode 100644 tests/functional/n/no/no_member_decorator.py diff --git a/doc/whatsnew/fragments/9246.false_positive b/doc/whatsnew/fragments/9246.false_positive new file mode 100644 index 0000000000..1659aeea62 --- /dev/null +++ b/doc/whatsnew/fragments/9246.false_positive @@ -0,0 +1,4 @@ +Avoid false positives for ``no-member`` involving function +attributes supplied by decorators. + +Closes #9246 diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index 72ebd74036..93a0492a05 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -1060,7 +1060,7 @@ def visit_assignattr(self, node: nodes.AssignAttr) -> None: def visit_delattr(self, node: nodes.DelAttr) -> None: self.visit_attribute(node) - # pylint: disable = too-many-branches + # pylint: disable = too-many-branches, too-many-statements @only_required_for_messages("no-member", "c-extension-no-member") def visit_attribute( self, node: nodes.Attribute | nodes.AssignAttr | nodes.DelAttr @@ -1128,6 +1128,12 @@ def visit_attribute( except astroid.DuplicateBasesError: continue except astroid.NotFoundError: + # Avoid false positive in case a decorator supplies member. + if ( + isinstance(owner, (astroid.FunctionDef, astroid.BoundMethod)) + and owner.decorators + ): + continue # This can't be moved before the actual .getattr call, # because there can be more values inferred and we are # stopping after the first one which has the attribute in question. diff --git a/tests/functional/n/no/no_member_decorator.py b/tests/functional/n/no/no_member_decorator.py new file mode 100644 index 0000000000..3c6d30593c --- /dev/null +++ b/tests/functional/n/no/no_member_decorator.py @@ -0,0 +1,16 @@ +"""Attributes supplied by a decorator.""" + +from functools import lru_cache + + +class SomeClass: # pylint: disable=too-few-public-methods + """https://github.com/pylint-dev/pylint/issues/9246""" + @classmethod + @lru_cache + def __cached_fun(cls, arg: int) -> str: + return str(arg) + + @classmethod + def cache_clear(cls): + """__cached_fun()'s @cache decorator supplies cache_clear().""" + cls.__cached_fun.cache_clear() From b2b54cd84d8af6e8243eedacfeb675470d7abab9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 18:33:23 +0000 Subject: [PATCH 3/7] [used-before-assignment] Fix FP for try/else/continue (#9374) (#9381) (cherry picked from commit 7d0c7acda0ee2642d2c247eaec214a2b878dbed9) Co-authored-by: Jacob Walls --- doc/whatsnew/fragments/6804.false_positive | 5 +++ pylint/checkers/variables.py | 10 +++++ .../used_before_assignment_else_continue.py | 42 +++++++++++++++++++ .../used_before_assignment_else_continue.txt | 1 + 4 files changed, 58 insertions(+) create mode 100644 doc/whatsnew/fragments/6804.false_positive create mode 100644 tests/functional/u/used/used_before_assignment_else_continue.py create mode 100644 tests/functional/u/used/used_before_assignment_else_continue.txt diff --git a/doc/whatsnew/fragments/6804.false_positive b/doc/whatsnew/fragments/6804.false_positive new file mode 100644 index 0000000000..9069ec35bd --- /dev/null +++ b/doc/whatsnew/fragments/6804.false_positive @@ -0,0 +1,5 @@ +``used-before-assignment`` is no longer emitted when using a name in a loop and +depending on an earlier name assignment in an ``except`` block paired with +``else: continue``. + +Closes #6804 diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 925088f60d..b13e25491a 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -883,6 +883,16 @@ def _uncertain_nodes_in_except_blocks( and utils.is_terminating_func(else_statement.value) for else_statement in closest_try_except.orelse ) + else_block_continues = any( + isinstance(else_statement, nodes.Continue) + for else_statement in closest_try_except.orelse + ) + if ( + else_block_continues + and isinstance(node_statement.parent, (nodes.For, nodes.While)) + and closest_try_except.parent.parent_of(node_statement) + ): + continue if try_block_returns or else_block_returns or else_block_exits: # Exception: if this node is in the final block of the other_node_statement, diff --git a/tests/functional/u/used/used_before_assignment_else_continue.py b/tests/functional/u/used/used_before_assignment_else_continue.py new file mode 100644 index 0000000000..e147c5eed8 --- /dev/null +++ b/tests/functional/u/used/used_before_assignment_else_continue.py @@ -0,0 +1,42 @@ +"""If the else block continues, it is generally safe to rely on assignments in the except, +inside the same for loop only.""" + + +def safe(): + """Name used safely inside the loop.""" + while True: + try: + pass + except ValueError: + error = True + else: + continue + + print(error) + + +def halfway_safe(): + """Name used safely inside the loop, unsafely outside it.""" + for _temp in range(0, 1): + try: + pass + except ValueError: + error = True + else: + continue + + print(error) + print(error) # https://github.com/pylint-dev/pylint/issues/9379 + + +def unsafe(): + """Name used unsafely outside the loop.""" + for _temp in range(0, 1): + try: + pass + except ValueError: + error = True + else: + continue + + print(error) # [used-before-assignment] diff --git a/tests/functional/u/used/used_before_assignment_else_continue.txt b/tests/functional/u/used/used_before_assignment_else_continue.txt new file mode 100644 index 0000000000..76e8c66a1d --- /dev/null +++ b/tests/functional/u/used/used_before_assignment_else_continue.txt @@ -0,0 +1 @@ +used-before-assignment:42:10:42:15:unsafe:Using variable 'error' before assignment:CONTROL_FLOW From ce9b7cbdaf19acd5ce72c09660caea51bff022ad Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 31 Jan 2024 15:20:19 +0000 Subject: [PATCH 4/7] Fix astroid base_nodes imports (#9394) (#9404) (cherry picked from commit 6062c1d8bb16de5d1a9f14f51b265653483ad6dd) Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com> --- pylint/checkers/typecheck.py | 3 ++- pylint/checkers/utils.py | 4 ++-- pylint/checkers/variables.py | 18 ++++++++++-------- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index 93a0492a05..11ad7f97fa 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -21,6 +21,7 @@ import astroid.exceptions import astroid.helpers from astroid import arguments, bases, nodes, util +from astroid.nodes import _base_nodes from astroid.typing import InferenceResult, SuccessfulInferenceResult from pylint.checkers import BaseChecker, utils @@ -660,7 +661,7 @@ def _determine_callable( def _has_parent_of_type( node: nodes.Call, node_type: nodes.Keyword | nodes.Starred, - statement: nodes.Statement, + statement: _base_nodes.Statement, ) -> bool: """Check if the given node has a parent of the given type.""" parent = node.parent diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py index eb9d52d4d5..3b6a26db35 100644 --- a/pylint/checkers/utils.py +++ b/pylint/checkers/utils.py @@ -22,7 +22,7 @@ from astroid import TooManyLevelsError, nodes, util from astroid.context import InferenceContext from astroid.exceptions import AstroidError -from astroid.nodes._base_nodes import ImportNode +from astroid.nodes._base_nodes import ImportNode, Statement from astroid.typing import InferenceResult, SuccessfulInferenceResult if TYPE_CHECKING: @@ -1986,7 +1986,7 @@ def is_typing_member(node: nodes.NodeNG, names_to_check: tuple[str, ...]) -> boo @lru_cache -def in_for_else_branch(parent: nodes.NodeNG, stmt: nodes.Statement) -> bool: +def in_for_else_branch(parent: nodes.NodeNG, stmt: Statement) -> bool: """Returns True if stmt is inside the else branch for a parent For stmt.""" return isinstance(parent, nodes.For) and any( else_stmt.parent_of(stmt) or else_stmt == stmt for else_stmt in parent.orelse diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index b13e25491a..24a2eeaf5d 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -850,7 +850,7 @@ def _node_guarded_by_same_test(node: nodes.NodeNG, other_if: nodes.If) -> bool: def _uncertain_nodes_in_except_blocks( found_nodes: list[nodes.NodeNG], node: nodes.NodeNG, - node_statement: nodes.Statement, + node_statement: _base_nodes.Statement, ) -> list[nodes.NodeNG]: """Return any nodes in ``found_nodes`` that should be treated as uncertain because they are in an except block. @@ -1071,7 +1071,7 @@ def _try_in_loop_body( @staticmethod def _recursive_search_for_continue_before_break( - stmt: nodes.Statement, break_stmt: nodes.Break + stmt: _base_nodes.Statement, break_stmt: nodes.Break ) -> bool: """Return True if any Continue node can be found in descendants of `stmt` before encountering `break_stmt`, ignoring any nested loops. @@ -1091,7 +1091,7 @@ def _recursive_search_for_continue_before_break( @staticmethod def _uncertain_nodes_in_try_blocks_when_evaluating_except_blocks( - found_nodes: list[nodes.NodeNG], node_statement: nodes.Statement + found_nodes: list[nodes.NodeNG], node_statement: _base_nodes.Statement ) -> list[nodes.NodeNG]: """Return any nodes in ``found_nodes`` that should be treated as uncertain. @@ -1139,7 +1139,7 @@ def _uncertain_nodes_in_try_blocks_when_evaluating_except_blocks( @staticmethod def _uncertain_nodes_in_try_blocks_when_evaluating_finally_blocks( - found_nodes: list[nodes.NodeNG], node_statement: nodes.Statement + found_nodes: list[nodes.NodeNG], node_statement: _base_nodes.Statement ) -> list[nodes.NodeNG]: uncertain_nodes: list[nodes.NodeNG] = [] ( @@ -2183,8 +2183,8 @@ def _in_lambda_or_comprehension_body( def _is_variable_violation( node: nodes.Name, defnode: nodes.NodeNG, - stmt: nodes.Statement, - defstmt: nodes.Statement, + stmt: _base_nodes.Statement, + defstmt: _base_nodes.Statement, frame: nodes.LocalsDictNodeNG, # scope of statement of node defframe: nodes.LocalsDictNodeNG, base_scope_type: str, @@ -2338,7 +2338,7 @@ def _is_variable_violation( return maybe_before_assign, annotation_return, use_outer_definition @staticmethod - def _maybe_used_and_assigned_at_once(defstmt: nodes.Statement) -> bool: + def _maybe_used_and_assigned_at_once(defstmt: _base_nodes.Statement) -> bool: """Check if `defstmt` has the potential to use and assign a name in the same statement. """ @@ -2380,7 +2380,9 @@ def _is_builtin(self, name: str) -> bool: return name in self.linter.config.additional_builtins or utils.is_builtin(name) @staticmethod - def _is_only_type_assignment(node: nodes.Name, defstmt: nodes.Statement) -> bool: + def _is_only_type_assignment( + node: nodes.Name, defstmt: _base_nodes.Statement + ) -> bool: """Check if variable only gets assigned a type and never a value.""" if not isinstance(defstmt, nodes.AnnAssign) or defstmt.value: return False From 4d162c3b5886b17870de6236b9537c4648ee9c39 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 22 Feb 2024 14:11:46 +0000 Subject: [PATCH 5/7] [used-before-assignment] Fix FP under finally block (#9452) (#9453) Closes #9451 (cherry picked from commit a83e6b9e32379220f0061574baca30098b025b44) Co-authored-by: Jacob Walls --- doc/whatsnew/fragments/9451.false_positive | 4 ++++ pylint/checkers/variables.py | 12 ++++++++++-- .../u/used/used_before_assignment_issue85.py | 10 ++++++++++ .../u/used/used_before_assignment_issue85.txt | 16 ++++++++-------- 4 files changed, 32 insertions(+), 10 deletions(-) create mode 100644 doc/whatsnew/fragments/9451.false_positive diff --git a/doc/whatsnew/fragments/9451.false_positive b/doc/whatsnew/fragments/9451.false_positive new file mode 100644 index 0000000000..ea2045f91d --- /dev/null +++ b/doc/whatsnew/fragments/9451.false_positive @@ -0,0 +1,4 @@ +Fix false positive for ``used-before-assignment`` in a ``finally`` block +when assignments took place in both the ``try`` block and each exception handler. + +Closes #9451 diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 24a2eeaf5d..4a75d4d56e 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -666,7 +666,7 @@ def get_next_to_consume(self, node: nodes.Name) -> list[nodes.NodeNG] | None: if found_nodes: uncertain_nodes = ( self._uncertain_nodes_in_try_blocks_when_evaluating_finally_blocks( - found_nodes, node_statement + found_nodes, node_statement, name ) ) self.consumed_uncertain[node.name] += uncertain_nodes @@ -1139,7 +1139,9 @@ def _uncertain_nodes_in_try_blocks_when_evaluating_except_blocks( @staticmethod def _uncertain_nodes_in_try_blocks_when_evaluating_finally_blocks( - found_nodes: list[nodes.NodeNG], node_statement: _base_nodes.Statement + found_nodes: list[nodes.NodeNG], + node_statement: _base_nodes.Statement, + name: str, ) -> list[nodes.NodeNG]: uncertain_nodes: list[nodes.NodeNG] = [] ( @@ -1186,6 +1188,12 @@ def _uncertain_nodes_in_try_blocks_when_evaluating_finally_blocks( ) ): continue + # Is the name defined in all exception clauses? + if other_node_try_finally_ancestor.handlers and all( + NamesConsumer._defines_name_raises_or_returns_recursive(name, handler) + for handler in other_node_try_finally_ancestor.handlers + ): + continue # Passed all tests for uncertain execution uncertain_nodes.append(other_node) return uncertain_nodes diff --git a/tests/functional/u/used/used_before_assignment_issue85.py b/tests/functional/u/used/used_before_assignment_issue85.py index 7ede436bb9..218dad9d00 100644 --- a/tests/functional/u/used/used_before_assignment_issue85.py +++ b/tests/functional/u/used/used_before_assignment_issue85.py @@ -34,6 +34,16 @@ def try_except_finally_assignment_in_final_block(): print(res) +def try_except_finally_assignment_in_both_try_and_except(): + """Assignment of the name in both try and except blocks is fine.""" + try: + res = 1 / 0 + except ZeroDivisionError: + res = 0 + finally: + print(res) + + def try_except_finally_nested_try_finally_in_try(): """Don't confuse assignments in different finally statements where one is nested inside a try. diff --git a/tests/functional/u/used/used_before_assignment_issue85.txt b/tests/functional/u/used/used_before_assignment_issue85.txt index 9f405c2c5c..d7d002d75e 100644 --- a/tests/functional/u/used/used_before_assignment_issue85.txt +++ b/tests/functional/u/used/used_before_assignment_issue85.txt @@ -1,10 +1,10 @@ used-before-assignment:8:14:8:17:main:Using variable 'res' before assignment:CONTROL_FLOW used-before-assignment:20:14:20:17:try_except_finally:Using variable 'res' before assignment:CONTROL_FLOW -used-before-assignment:45:18:45:21:try_except_finally_nested_try_finally_in_try:Using variable 'res' before assignment:HIGH -used-before-assignment:70:18:70:29:try_except_finally_nested_in_finally:Using variable 'outer_times' before assignment:CONTROL_FLOW -used-before-assignment:84:18:84:29:try_except_finally_nested_in_finally_2:Using variable 'inner_times' before assignment:CONTROL_FLOW -used-before-assignment:85:14:85:25:try_except_finally_nested_in_finally_2:Using variable 'outer_times' before assignment:CONTROL_FLOW -used-before-assignment:100:18:100:29:try_except_finally_nested_in_finally_3:Using variable 'inner_times' before assignment:CONTROL_FLOW -used-before-assignment:101:18:101:29:try_except_finally_nested_in_finally_3:Using variable 'outer_times' before assignment:CONTROL_FLOW -used-before-assignment:122:22:122:33:try_except_finally_nested_in_finally_4:Using variable 'inner_times' before assignment:CONTROL_FLOW -used-before-assignment:123:22:123:33:try_except_finally_nested_in_finally_4:Using variable 'outer_times' before assignment:CONTROL_FLOW +used-before-assignment:55:18:55:21:try_except_finally_nested_try_finally_in_try:Using variable 'res' before assignment:HIGH +used-before-assignment:80:18:80:29:try_except_finally_nested_in_finally:Using variable 'outer_times' before assignment:CONTROL_FLOW +used-before-assignment:94:18:94:29:try_except_finally_nested_in_finally_2:Using variable 'inner_times' before assignment:CONTROL_FLOW +used-before-assignment:95:14:95:25:try_except_finally_nested_in_finally_2:Using variable 'outer_times' before assignment:CONTROL_FLOW +used-before-assignment:110:18:110:29:try_except_finally_nested_in_finally_3:Using variable 'inner_times' before assignment:CONTROL_FLOW +used-before-assignment:111:18:111:29:try_except_finally_nested_in_finally_3:Using variable 'outer_times' before assignment:CONTROL_FLOW +used-before-assignment:132:22:132:33:try_except_finally_nested_in_finally_4:Using variable 'inner_times' before assignment:CONTROL_FLOW +used-before-assignment:133:22:133:33:try_except_finally_nested_in_finally_4:Using variable 'outer_times' before assignment:CONTROL_FLOW From 327785d7b964cc7dd688d7eb0d48784496a5bbeb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 22 Feb 2024 23:45:25 +0000 Subject: [PATCH 6/7] Catch ValueError - generator already executing (#9454) (#9455) (cherry picked from commit d4f0ef704119ce154b9d569ce4536b312b00880e) Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com> --- .pyenchant_pylint_custom_dict.txt | 1 + doc/whatsnew/fragments/9138.bugfix | 4 ++++ pylint/__init__.py | 21 +++++++++++++++++++++ 3 files changed, 26 insertions(+) create mode 100644 doc/whatsnew/fragments/9138.bugfix diff --git a/.pyenchant_pylint_custom_dict.txt b/.pyenchant_pylint_custom_dict.txt index 8a5f150739..712ab70cca 100644 --- a/.pyenchant_pylint_custom_dict.txt +++ b/.pyenchant_pylint_custom_dict.txt @@ -361,6 +361,7 @@ unicode Uninferable uninferable unittest +unraisablehook untriggered # prefix for string ur diff --git a/doc/whatsnew/fragments/9138.bugfix b/doc/whatsnew/fragments/9138.bugfix new file mode 100644 index 0000000000..cc48d11ab0 --- /dev/null +++ b/doc/whatsnew/fragments/9138.bugfix @@ -0,0 +1,4 @@ +Catch incorrect ValueError ``"generator already executing"`` for Python 3.12.0 - 3.12.2. +This is fixed upstream in Python 3.12.3. + +Closes #9138 diff --git a/pylint/__init__.py b/pylint/__init__.py index cc0f609aac..75cf2b6ee4 100644 --- a/pylint/__init__.py +++ b/pylint/__init__.py @@ -95,4 +95,25 @@ def modify_sys_path() -> None: sys.path.pop(1) +def _catch_valueerror(unraisable: sys.UnraisableHookArgs) -> None: # pragma: no cover + """Overwrite sys.unraisablehook to catch incorrect ValueError. + + Python 3.12 introduced changes that sometimes cause astroid to emit ValueErrors + with 'generator already executing'. Fixed in Python 3.12.3 and 3.13. + + https://github.com/pylint-dev/pylint/issues/9138 + """ + if ( + isinstance(unraisable.exc_value, ValueError) + and unraisable.exc_value.args[0] == "generator already executing" + ): + return + + sys.__unraisablehook__(unraisable) + + +if (3, 12, 0) <= sys.version_info[:3] < (3, 12, 3): + sys.unraisablehook = _catch_valueerror + + version = __version__ From 811538121f07173bab6a8a1e0366ed56b020954c Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Fri, 23 Feb 2024 21:45:24 +0100 Subject: [PATCH 7/7] Bump pylint to 3.0.4, update changelog (#9458) --- doc/whatsnew/3/3.0/index.rst | 40 ++++++++++++++++++++++ doc/whatsnew/fragments/6804.false_positive | 5 --- doc/whatsnew/fragments/9138.bugfix | 4 --- doc/whatsnew/fragments/9246.false_positive | 4 --- doc/whatsnew/fragments/9307.false_positive | 3 -- doc/whatsnew/fragments/9451.false_positive | 4 --- pylint/__pkginfo__.py | 2 +- tbump.toml | 2 +- towncrier.toml | 2 +- 9 files changed, 43 insertions(+), 23 deletions(-) delete mode 100644 doc/whatsnew/fragments/6804.false_positive delete mode 100644 doc/whatsnew/fragments/9138.bugfix delete mode 100644 doc/whatsnew/fragments/9246.false_positive delete mode 100644 doc/whatsnew/fragments/9307.false_positive delete mode 100644 doc/whatsnew/fragments/9451.false_positive diff --git a/doc/whatsnew/3/3.0/index.rst b/doc/whatsnew/3/3.0/index.rst index bb7c091107..aad65c984c 100644 --- a/doc/whatsnew/3/3.0/index.rst +++ b/doc/whatsnew/3/3.0/index.rst @@ -65,6 +65,46 @@ easier to parse and provides more info, here's a sample output. .. towncrier release notes start +What's new in Pylint 3.0.4? +--------------------------- +Release date: 2024-02-23 + + +False Positives Fixed +--------------------- + +- ``used-before-assignment`` is no longer emitted when using a name in a loop and + depending on an earlier name assignment in an ``except`` block paired with + ``else: continue``. + + Closes #6804 (`#6804 `_) + +- Avoid false positives for ``no-member`` involving function + attributes supplied by decorators. + + Closes #9246 (`#9246 `_) + +- Fixed false positive nested-min-max for nested lists. + + Closes #9307 (`#9307 `_) + +- Fix false positive for ``used-before-assignment`` in a ``finally`` block + when assignments took place in both the ``try`` block and each exception handler. + + Closes #9451 (`#9451 `_) + + + +Other Bug Fixes +--------------- + +- Catch incorrect ValueError ``"generator already executing"`` for Python 3.12.0 - 3.12.2. + This is fixed upstream in Python 3.12.3. + + Closes #9138 (`#9138 `_) + + + What's new in Pylint 3.0.3? --------------------------- Release date: 2023-12-11 diff --git a/doc/whatsnew/fragments/6804.false_positive b/doc/whatsnew/fragments/6804.false_positive deleted file mode 100644 index 9069ec35bd..0000000000 --- a/doc/whatsnew/fragments/6804.false_positive +++ /dev/null @@ -1,5 +0,0 @@ -``used-before-assignment`` is no longer emitted when using a name in a loop and -depending on an earlier name assignment in an ``except`` block paired with -``else: continue``. - -Closes #6804 diff --git a/doc/whatsnew/fragments/9138.bugfix b/doc/whatsnew/fragments/9138.bugfix deleted file mode 100644 index cc48d11ab0..0000000000 --- a/doc/whatsnew/fragments/9138.bugfix +++ /dev/null @@ -1,4 +0,0 @@ -Catch incorrect ValueError ``"generator already executing"`` for Python 3.12.0 - 3.12.2. -This is fixed upstream in Python 3.12.3. - -Closes #9138 diff --git a/doc/whatsnew/fragments/9246.false_positive b/doc/whatsnew/fragments/9246.false_positive deleted file mode 100644 index 1659aeea62..0000000000 --- a/doc/whatsnew/fragments/9246.false_positive +++ /dev/null @@ -1,4 +0,0 @@ -Avoid false positives for ``no-member`` involving function -attributes supplied by decorators. - -Closes #9246 diff --git a/doc/whatsnew/fragments/9307.false_positive b/doc/whatsnew/fragments/9307.false_positive deleted file mode 100644 index 546959bf66..0000000000 --- a/doc/whatsnew/fragments/9307.false_positive +++ /dev/null @@ -1,3 +0,0 @@ -Fixed false positive nested-min-max for nested lists. - -Closes #9307 diff --git a/doc/whatsnew/fragments/9451.false_positive b/doc/whatsnew/fragments/9451.false_positive deleted file mode 100644 index ea2045f91d..0000000000 --- a/doc/whatsnew/fragments/9451.false_positive +++ /dev/null @@ -1,4 +0,0 @@ -Fix false positive for ``used-before-assignment`` in a ``finally`` block -when assignments took place in both the ``try`` block and each exception handler. - -Closes #9451 diff --git a/pylint/__pkginfo__.py b/pylint/__pkginfo__.py index 521ffd04cd..f1cd07dc27 100644 --- a/pylint/__pkginfo__.py +++ b/pylint/__pkginfo__.py @@ -9,7 +9,7 @@ from __future__ import annotations -__version__ = "3.0.3" +__version__ = "3.0.4" def get_numversion_from_version(v: str) -> tuple[int, int, int]: diff --git a/tbump.toml b/tbump.toml index b7db1431f4..b4ae108ba3 100644 --- a/tbump.toml +++ b/tbump.toml @@ -1,7 +1,7 @@ github_url = "https://github.com/pylint-dev/pylint" [version] -current = "3.0.3" +current = "3.0.4" regex = ''' ^(?P0|[1-9]\d*) \. diff --git a/towncrier.toml b/towncrier.toml index 46ceaf08c2..761b86fbb0 100644 --- a/towncrier.toml +++ b/towncrier.toml @@ -1,5 +1,5 @@ [tool.towncrier] -version = "3.0.3" +version = "3.0.4" directory = "doc/whatsnew/fragments" filename = "doc/whatsnew/3/3.0/index.rst" template = "doc/whatsnew/fragments/_template.rst"