diff --git a/doc/whatsnew/fragments/8168.bugfix b/doc/whatsnew/fragments/8168.bugfix new file mode 100644 index 0000000000..dd846b7348 --- /dev/null +++ b/doc/whatsnew/fragments/8168.bugfix @@ -0,0 +1,3 @@ +Fix ``nested-min-max`` suggestion message to indicate it's possible to splat iterable objects. + +Closes #8168 diff --git a/pylint/checkers/nested_min_max.py b/pylint/checkers/nested_min_max.py index 39feb5f42f..e9aa409f04 100644 --- a/pylint/checkers/nested_min_max.py +++ b/pylint/checkers/nested_min_max.py @@ -9,7 +9,7 @@ import copy from typing import TYPE_CHECKING -from astroid import nodes +from astroid import nodes, objects from pylint.checkers import BaseChecker from pylint.checkers.utils import only_required_for_messages, safe_infer @@ -18,6 +18,13 @@ if TYPE_CHECKING: from pylint.lint import PyLinter +DICT_TYPES = ( + objects.DictValues, + objects.DictKeys, + objects.DictItems, + nodes.node_classes.Dict, +) + class NestedMinMaxChecker(BaseChecker): """Multiple nested min/max calls on the same line will raise multiple messages. @@ -83,6 +90,20 @@ def visit_call(self, node: nodes.Call) -> None: redundant_calls = self.get_redundant_calls(fixed_node) + for idx, arg in enumerate(fixed_node.args): + if not isinstance(arg, nodes.Const): + inferred = safe_infer(arg) + if isinstance( + inferred, (nodes.List, nodes.Tuple, nodes.Set, *DICT_TYPES) + ): + splat_node = nodes.Starred(lineno=inferred.lineno) + splat_node.value = arg + fixed_node.args = ( + fixed_node.args[:idx] + + [splat_node] + + fixed_node.args[idx + 1 : idx] + ) + self.add_message( "nested-min-max", node=node, diff --git a/tests/functional/n/nested_min_max.py b/tests/functional/n/nested_min_max.py index cef63dc2b3..151e035dd1 100644 --- a/tests/functional/n/nested_min_max.py +++ b/tests/functional/n/nested_min_max.py @@ -19,3 +19,26 @@ # This is too complicated (for now) as there is no clear better way to write it max(max(i for i in range(10)), 0) max(max(max(i for i in range(10)), 0), 1) + +# These examples can be improved by splicing +lst = [1, 2] +max(3, max(lst)) # [nested-min-max] +max(3, *lst) + +nums = (1, 2,) +max(3, max(nums)) # [nested-min-max] +max(3, *nums) + +nums = {1, 2} +max(3, max(nums)) # [nested-min-max] +max(3, *nums) + +nums = {1: 2, 7: 10} +max(3, max(nums)) # [nested-min-max] +max(3, *nums) + +max(3, max(nums.values())) # [nested-min-max] +max(3, *nums.values()) + +lst2 = [3, 7, 10] +max(3, max(nums), max(lst2)) # [nested-min-max] diff --git a/tests/functional/n/nested_min_max.txt b/tests/functional/n/nested_min_max.txt index 9bcb663e34..c03f1b500c 100644 --- a/tests/functional/n/nested_min_max.txt +++ b/tests/functional/n/nested_min_max.txt @@ -6,3 +6,9 @@ nested-min-max:8:0:8:25::Do not use nested call of 'min'; it's possible to do 'm nested-min-max:11:0:11:25::Do not use nested call of 'min'; it's possible to do 'min(1, 2, 3, 4)' instead:INFERENCE nested-min-max:12:0:12:40::Do not use nested call of 'min'; it's possible to do 'min(len([]), len([1]), len([1, 2]))' instead:INFERENCE nested-min-max:17:0:17:27::Do not use nested call of 'orig_min'; it's possible to do 'orig_min(1, 2, 3)' instead:INFERENCE +nested-min-max:25:0:25:16::Do not use nested call of 'max'; it's possible to do 'max(3, *lst)' instead:INFERENCE +nested-min-max:29:0:29:17::Do not use nested call of 'max'; it's possible to do 'max(3, *nums)' instead:INFERENCE +nested-min-max:33:0:33:17::Do not use nested call of 'max'; it's possible to do 'max(3, *nums)' instead:INFERENCE +nested-min-max:37:0:37:17::Do not use nested call of 'max'; it's possible to do 'max(3, *nums)' instead:INFERENCE +nested-min-max:40:0:40:26::Do not use nested call of 'max'; it's possible to do 'max(3, *nums.values())' instead:INFERENCE +nested-min-max:44:0:44:28::Do not use nested call of 'max'; it's possible to do 'max(3, *nums, *lst2)' instead:INFERENCE