Skip to content

Commit 4bf3524

Browse files
authored
[false-negative] Fix for consider-using-min/max-builtin (#9127)
1 parent 8c24b1e commit 4bf3524

File tree

5 files changed

+69
-32
lines changed

5 files changed

+69
-32
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix a false-negative for unnecessary if blocks using a different than expected ordering of arguments.
2+
3+
Closes #8947.

pylint/checkers/refactoring/refactoring_checker.py

+31-26
Original file line numberDiff line numberDiff line change
@@ -871,16 +871,32 @@ def visit_if(self, node: nodes.If) -> None:
871871
self._check_consider_get(node)
872872
self._check_consider_using_min_max_builtin(node)
873873

874-
# pylint: disable = too-many-branches
875874
def _check_consider_using_min_max_builtin(self, node: nodes.If) -> None:
876875
"""Check if the given if node can be refactored as a min/max python builtin."""
876+
# This function is written expecting a test condition of form:
877+
# if a < b: # [consider-using-max-builtin]
878+
# a = b
879+
# if a > b: # [consider-using-min-builtin]
880+
# a = b
877881
if self._is_actual_elif(node) or node.orelse:
878882
# Not interested in if statements with multiple branches.
879883
return
880884

881885
if len(node.body) != 1:
882886
return
883887

888+
def get_node_name(node: nodes.NodeNG) -> str:
889+
"""Obtain simplest representation of a node as a string."""
890+
if isinstance(node, nodes.Name):
891+
return node.name # type: ignore[no-any-return]
892+
if isinstance(node, nodes.Attribute):
893+
return node.attrname # type: ignore[no-any-return]
894+
if isinstance(node, nodes.Const):
895+
return str(node.value)
896+
# this is a catch-all for nodes that are not of type Name or Attribute
897+
# extremely helpful for Call or BinOp
898+
return node.as_string() # type: ignore[no-any-return]
899+
884900
body = node.body[0]
885901
# Check if condition can be reduced.
886902
if not hasattr(body, "targets") or len(body.targets) != 1:
@@ -894,14 +910,9 @@ def _check_consider_using_min_max_builtin(self, node: nodes.If) -> None:
894910
and isinstance(body, nodes.Assign)
895911
):
896912
return
897-
898-
# Check that the assignation is on the same variable.
899-
if hasattr(node.test.left, "name"):
900-
left_operand = node.test.left.name
901-
elif hasattr(node.test.left, "attrname"):
902-
left_operand = node.test.left.attrname
903-
else:
904-
return
913+
# Assign body line has one requirement and that is the assign target
914+
# is of type name or attribute. Attribute referring to NamedTuple.x perse.
915+
# So we have to check that target is of these types
905916

906917
if hasattr(target, "name"):
907918
target_assignation = target.name
@@ -910,30 +921,24 @@ def _check_consider_using_min_max_builtin(self, node: nodes.If) -> None:
910921
else:
911922
return
912923

913-
if not (left_operand == target_assignation):
914-
return
915-
916924
if len(node.test.ops) > 1:
917925
return
918-
919-
if not isinstance(body.value, (nodes.Name, nodes.Const)):
920-
return
921-
922926
operator, right_statement = node.test.ops[0]
923-
if isinstance(body.value, nodes.Name):
924-
body_value = body.value.name
925-
else:
926-
body_value = body.value.value
927927

928-
if isinstance(right_statement, nodes.Name):
929-
right_statement_value = right_statement.name
930-
elif isinstance(right_statement, nodes.Const):
931-
right_statement_value = right_statement.value
928+
body_value = get_node_name(body.value)
929+
left_operand = get_node_name(node.test.left)
930+
right_statement_value = get_node_name(right_statement)
931+
932+
if left_operand == target_assignation:
933+
# statement is in expected form
934+
pass
935+
elif right_statement_value == target_assignation:
936+
# statement is in reverse form
937+
operator = utils.get_inverse_comparator(operator)
932938
else:
933939
return
934940

935-
# Verify the right part of the statement is the same.
936-
if right_statement_value != body_value:
941+
if body_value not in (right_statement_value, left_operand):
937942
return
938943

939944
if operator in {"<", "<="}:

tests/functional/a/access/access_attr_before_def_false_positive.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# pylint: disable=invalid-name,too-many-public-methods,attribute-defined-outside-init
2-
# pylint: disable=too-few-public-methods,deprecated-module
2+
# pylint: disable=too-few-public-methods,deprecated-module,consider-using-max-builtin
33
"""This module demonstrates a possible problem of pyLint with calling __init__ s
44
from inherited classes.
55
Initializations done there are not considered, which results in Error E0203 for

tests/functional/c/consider/consider_using_min_max_builtin.py

+25
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,18 @@
2323
if value > value2: # [consider-using-min-builtin]
2424
value = value2
2525

26+
if value2 > value3: # [consider-using-max-builtin]
27+
value3 = value2
28+
29+
if value < value2: # [consider-using-min-builtin]
30+
value2 = value
31+
32+
if value > float(value3): # [consider-using-min-builtin]
33+
value = float(value3)
34+
35+
offset = 1
36+
if offset + value < value2: # [consider-using-min-builtin]
37+
value2 = offset + value
2638

2739
class A:
2840
def __init__(self):
@@ -70,6 +82,12 @@ def __le__(self, b):
7082
if value > 10:
7183
value = 2
7284

85+
if 10 < value:
86+
value = 2
87+
88+
if 10 > value:
89+
value = 2
90+
7391
if value > 10:
7492
value = 2
7593
value2 = 3
@@ -96,6 +114,13 @@ def __le__(self, b):
96114
else:
97115
value = 3
98116

117+
if value > float(value3):
118+
value = float(value2)
119+
120+
offset = 1
121+
if offset + value < value2:
122+
value2 = offset
123+
99124

100125
# https://github.com/pylint-dev/pylint/issues/4379
101126
var = 1

tests/functional/c/consider/consider_using_min_max_builtin.txt

+9-5
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@ consider-using-max-builtin:14:0:15:14::Consider using 'value = max(value, 10)' i
44
consider-using-min-builtin:17:0:18:14::Consider using 'value = min(value, 10)' instead of unnecessary if block:UNDEFINED
55
consider-using-max-builtin:20:0:21:18::Consider using 'value = max(value, value2)' instead of unnecessary if block:UNDEFINED
66
consider-using-min-builtin:23:0:24:18::Consider using 'value = min(value, value2)' instead of unnecessary if block:UNDEFINED
7-
consider-using-min-builtin:33:0:34:17::Consider using 'value = min(value, 10)' instead of unnecessary if block:UNDEFINED
8-
consider-using-min-builtin:57:0:58:11::Consider using 'A1 = min(A1, A2)' instead of unnecessary if block:UNDEFINED
9-
consider-using-max-builtin:60:0:61:11::Consider using 'A2 = max(A2, A1)' instead of unnecessary if block:UNDEFINED
10-
consider-using-min-builtin:63:0:64:11::Consider using 'A1 = min(A1, A2)' instead of unnecessary if block:UNDEFINED
11-
consider-using-max-builtin:66:0:67:11::Consider using 'A2 = max(A2, A1)' instead of unnecessary if block:UNDEFINED
7+
consider-using-max-builtin:26:0:27:19::Consider using 'value3 = max(value3, value2)' instead of unnecessary if block:UNDEFINED
8+
consider-using-min-builtin:29:0:30:18::Consider using 'value2 = min(value2, value)' instead of unnecessary if block:UNDEFINED
9+
consider-using-min-builtin:32:0:33:25::Consider using 'value = min(value, float(value3))' instead of unnecessary if block:UNDEFINED
10+
consider-using-min-builtin:36:0:37:27::Consider using 'value2 = min(value2, offset + value)' instead of unnecessary if block:UNDEFINED
11+
consider-using-min-builtin:45:0:46:17::Consider using 'value = min(value, 10)' instead of unnecessary if block:UNDEFINED
12+
consider-using-min-builtin:69:0:70:11::Consider using 'A1 = min(A1, A2)' instead of unnecessary if block:UNDEFINED
13+
consider-using-max-builtin:72:0:73:11::Consider using 'A2 = max(A2, A1)' instead of unnecessary if block:UNDEFINED
14+
consider-using-min-builtin:75:0:76:11::Consider using 'A1 = min(A1, A2)' instead of unnecessary if block:UNDEFINED
15+
consider-using-max-builtin:78:0:79:11::Consider using 'A2 = max(A2, A1)' instead of unnecessary if block:UNDEFINED

0 commit comments

Comments
 (0)