Skip to content

Commit a9d9dc3

Browse files
Fix suggestion for min-max with expressions (#9131) (#9141)
Closes #8524 (cherry picked from commit ce8d1a9) Co-authored-by: theirix <[email protected]>
1 parent f2cded4 commit a9d9dc3

File tree

7 files changed

+64
-5
lines changed

7 files changed

+64
-5
lines changed

doc/whatsnew/fragments/8524.bugfix

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fixes suggestion for ``nested-min-max`` for expressions with additive operators, list and dict comprehensions.
2+
3+
Closes #8524

pylint/checkers/nested_min_max.py

+36-5
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
from pylint.checkers import BaseChecker
1616
from pylint.checkers.utils import only_required_for_messages, safe_infer
17+
from pylint.constants import PY39_PLUS
1718
from pylint.interfaces import INFERENCE
1819

1920
if TYPE_CHECKING:
@@ -93,13 +94,10 @@ def visit_call(self, node: nodes.Call) -> None:
9394

9495
for idx, arg in enumerate(fixed_node.args):
9596
if not isinstance(arg, nodes.Const):
96-
inferred = safe_infer(arg)
97-
if isinstance(
98-
inferred, (nodes.List, nodes.Tuple, nodes.Set, *DICT_TYPES)
99-
):
97+
if self._is_splattable_expression(arg):
10098
splat_node = nodes.Starred(
10199
ctx=Context.Load,
102-
lineno=inferred.lineno,
100+
lineno=arg.lineno,
103101
col_offset=0,
104102
parent=nodes.NodeNG(
105103
lineno=None,
@@ -125,6 +123,39 @@ def visit_call(self, node: nodes.Call) -> None:
125123
confidence=INFERENCE,
126124
)
127125

126+
def _is_splattable_expression(self, arg: nodes.NodeNG) -> bool:
127+
"""Returns true if expression under min/max could be converted to splat
128+
expression.
129+
"""
130+
# Support sequence addition (operator __add__)
131+
if isinstance(arg, nodes.BinOp) and arg.op == "+":
132+
return self._is_splattable_expression(
133+
arg.left
134+
) and self._is_splattable_expression(arg.right)
135+
# Support dict merge (operator __or__ in Python 3.9)
136+
if isinstance(arg, nodes.BinOp) and arg.op == "|" and PY39_PLUS:
137+
return self._is_splattable_expression(
138+
arg.left
139+
) and self._is_splattable_expression(arg.right)
140+
141+
inferred = safe_infer(arg)
142+
if inferred and inferred.pytype() in {"builtins.list", "builtins.tuple"}:
143+
return True
144+
if isinstance(
145+
inferred or arg,
146+
(
147+
nodes.List,
148+
nodes.Tuple,
149+
nodes.Set,
150+
nodes.ListComp,
151+
nodes.DictComp,
152+
*DICT_TYPES,
153+
),
154+
):
155+
return True
156+
157+
return False
158+
128159

129160
def register(linter: PyLinter) -> None:
130161
linter.register_checker(NestedMinMaxChecker(linter))

tests/functional/n/nested_min_max.py

+12
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,15 @@
4242

4343
lst2 = [3, 7, 10]
4444
max(3, max(nums), max(lst2)) # [nested-min-max]
45+
46+
max(3, max([5] + [6, 7])) # [nested-min-max]
47+
max(3, *[5] + [6, 7])
48+
49+
max(3, max([5] + [i for i in range(4) if i])) # [nested-min-max]
50+
max(3, *[5] + [i for i in range(4) if i])
51+
52+
max(3, max([5] + list(range(4)))) # [nested-min-max]
53+
max(3, *[5] + list(range(4)))
54+
55+
max(3, max(list(range(4)))) # [nested-min-max]
56+
max(3, *list(range(4)))

tests/functional/n/nested_min_max.txt

+4
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,7 @@ nested-min-max:33:0:33:17::Do not use nested call of 'max'; it's possible to do
1212
nested-min-max:37:0:37:17::Do not use nested call of 'max'; it's possible to do 'max(3, *nums)' instead:INFERENCE
1313
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
1414
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
15+
nested-min-max:46:0:46:25::Do not use nested call of 'max'; it's possible to do 'max(3, *[5] + [6, 7])' instead:INFERENCE
16+
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
17+
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
18+
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
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
"""Test detection of redundant nested calls to min/max functions"""
2+
3+
# pylint: disable=redefined-builtin,unnecessary-lambda-assignment
4+
5+
max(3, max({1: 2} | {i: i for i in range(4) if i})) # [nested-min-max]
6+
max(3, *{1: 2} | {i: i for i in range(4) if i})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[testoptions]
2+
min_pyver=3.9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
nested-min-max:5:0:5:51::"Do not use nested call of 'max'; it's possible to do 'max(3, *{1: 2} | {i: i for i in range(4) if i})' instead":INFERENCE

0 commit comments

Comments
 (0)