Skip to content

Commit 7efb436

Browse files
hippo91PCManticore
authored andcommitted
useless-super-delegation takes into account default values for the current implementation
A method can reimplement a super method in order to provide a different default value, in which case we shouldn't emit ``useless-super-delegation``.
1 parent d70cb16 commit 7efb436

File tree

6 files changed

+248
-12
lines changed

6 files changed

+248
-12
lines changed

CONTRIBUTORS.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,3 +139,5 @@ Order doesn't matter (not that much, at least ;)
139139
* Martin von Gagern (Google): Added 'raising-format-tuple' warning.
140140

141141
* Ahirnish Pareek, 'keyword-arg-before-var-arg' check
142+
143+
* Guillaume Peillex: contributor.

ChangeLog

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ What's New in Pylint 1.8?
104104
c-extensions.
105105
Close #1466
106106

107+
* Fix a false positive ``useless-super-delegation`` message when
108+
parameters default values are different from those used in the base class.
109+
Close #1085
110+
107111
* Disabling 'wrong-import-order', 'wrong-import-position', or
108112
'ungrouped-imports' for a single line now prevents that line from
109113
triggering violations on subsequent lines.

doc/whatsnew/1.8.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,3 +314,6 @@ Other Changes
314314
315315
* ``missing-param-doc`` and ``missing-type-doc`` are no longer emitted when
316316
``Args`` and ``Keyword Args`` are mixed in Google docstring.
317+
318+
* Fix of false positive ``useless-super-delegation`` message when
319+
parameters default values are different from those used in the base class.

pylint/checkers/classes.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,82 @@ def _positional_parameters(method):
134134
return positional
135135

136136

137+
def _get_node_type(node, potential_types):
138+
"""
139+
Return the type of the node if it exists in potential_types.
140+
141+
Args:
142+
node (astroid.node): node to get the type of.
143+
potential_types (tuple): potential types of the node.
144+
145+
Returns:
146+
type: type of the node or None.
147+
"""
148+
for potential_type in potential_types:
149+
if isinstance(node, potential_type):
150+
return potential_type
151+
return None
152+
153+
154+
def _check_arg_equality(node_a, node_b, attr_name):
155+
"""
156+
Check equality of nodes based on the comparison of their attributes named attr_name.
157+
158+
Args:
159+
node_a (astroid.node): first node to compare.
160+
node_b (astroid.node): second node to compare.
161+
attr_name (str): name of the nodes attribute to use for comparison.
162+
163+
Returns:
164+
bool: True if node_a.attr_name == node_b.attr_name, False otherwise.
165+
"""
166+
return getattr(node_a, attr_name) == getattr(node_b, attr_name)
167+
168+
169+
def _has_different_parameters_default_value(original, overridden):
170+
"""
171+
Check if original and overridden methods arguments have different default values
172+
173+
Return True if one of the overridden arguments has a default
174+
value different from the default value of the original argument
175+
If one of the method doesn't have argument (.args is None)
176+
return False
177+
"""
178+
if original.args is None or overridden.args is None:
179+
return False
180+
181+
original_param_names = [param.name for param in original.args]
182+
default_missing = object()
183+
for param_name in original_param_names:
184+
try:
185+
original_default = original.default_value(param_name)
186+
except astroid.exceptions.NoDefault:
187+
original_default = default_missing
188+
try:
189+
overridden_default = overridden.default_value(param_name)
190+
except astroid.exceptions.NoDefault:
191+
overridden_default = default_missing
192+
193+
default_list = [arg == default_missing for arg in (original_default, overridden_default)]
194+
if any(default_list) and not all(default_list):
195+
# Only one arg has no default value
196+
return True
197+
198+
astroid_type_compared_attr = {astroid.Const: "value", astroid.ClassDef: "name",
199+
astroid.Tuple: "elts", astroid.List: "elts"}
200+
handled_types = tuple(astroid_type for astroid_type in astroid_type_compared_attr)
201+
original_type = _get_node_type(original_default, handled_types)
202+
if original_type:
203+
# We handle only astroid types that are inside the dict astroid_type_compared_attr
204+
if not isinstance(overridden_default, original_type):
205+
# Two args with same name but different types
206+
return True
207+
if not _check_arg_equality(original_default, overridden_default,
208+
astroid_type_compared_attr[original_type]):
209+
# Two args with same type but different values
210+
return True
211+
return False
212+
137213
def _has_different_parameters(original, overridden, dummy_parameter_regex):
138214
zipped = six.moves.zip_longest(original, overridden)
139215
for original_param, overridden_param in zipped:
@@ -758,6 +834,26 @@ def _check_useless_super_delegation(self, function):
758834
if super_call.type.name != current_scope.name:
759835
return
760836

837+
# Check values of default args
838+
klass = function.parent.frame()
839+
for overridden in klass.local_attr_ancestors(function.name):
840+
# get astroid for the searched method
841+
try:
842+
meth_node = overridden[function.name]
843+
except KeyError:
844+
# we have found the method but it's not in the local
845+
# dictionary.
846+
# This may happen with astroid build from living objects
847+
continue
848+
if not isinstance(meth_node, astroid.FunctionDef):
849+
# If the method have an ancestor which is not a function
850+
# then it is legitimate to redefine it
851+
return
852+
if _has_different_parameters_default_value(meth_node.args, function.args):
853+
return
854+
else:
855+
break
856+
761857
# Detect if the parameters are the same as the call's arguments.
762858
params = _signature_from_arguments(function.args)
763859
args = _signature_from_call(call)

pylint/test/functional/useless_super_delegation.py

Lines changed: 126 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,59 @@
11
# pylint: disable=missing-docstring, no-member, no-self-use, bad-super-call
2-
# pylint: disable=too-few-public-methods, unused-argument,invalid-name,too-many-public-methods
2+
# pylint: disable=too-few-public-methods, unused-argument, invalid-name, too-many-public-methods
3+
# pylint: disable=line-too-long
34

4-
def not_a_method():
5-
return super(None, None).not_a_method()
65

6+
def not_a_method(param, param2):
7+
return super(None, None).not_a_method(param, param2)
78

8-
class Base(object):
9+
10+
class SuperBase(object):
11+
def with_default_arg(self, first, default_arg="only_in_super_base"):
12+
pass
13+
14+
def with_default_arg_bis(self, first, default_arg="only_in_super_base"):
15+
pass
16+
17+
def with_default_arg_ter(self, first, default_arg="will_be_changed"):
18+
pass
19+
20+
def with_default_arg_quad(self, first, default_arg="will_be_changed"):
21+
pass
22+
23+
24+
class Base(SuperBase):
25+
26+
fake_method = not_a_method
927

1028
def something(self):
1129
pass
1230

31+
def with_default_argument(self, first, default_arg="default"):
32+
pass
33+
34+
def with_default_argument_bis(self, first, default_arg="default"):
35+
pass
36+
37+
def without_default_argument(self, first, second):
38+
pass
39+
40+
def with_default_argument_none(self, first, default_arg=None):
41+
pass
42+
43+
def without_default_argument2(self, first, second):
44+
pass
45+
46+
def with_default_argument_int(self, first, default_arg=42):
47+
pass
48+
49+
def with_default_argument_tuple(self, first, default_arg=()):
50+
pass
51+
52+
def with_default_arg_ter(self, first, default_arg="has_been_changed"):
53+
super(Base, self).with_default_arg_ter(first, default_arg)
54+
55+
def with_default_arg_quad(self, first, default_arg="has_been_changed"):
56+
super(Base, self).with_default_arg_quad(first, default_arg)
1357

1458
class NotUselessSuper(Base):
1559

@@ -92,6 +136,55 @@ def extraneous_positional_args(self, **args):
92136
super(NotUselessSuper, self).extraneous_positional_args(
93137
1, 2, **args)
94138

139+
def with_default_argument(self, first, default_arg="other"):
140+
# Not useless because the default_arg is different from the one in the base class
141+
super(NotUselessSuper, self).with_default_argument(first, default_arg)
142+
143+
def without_default_argument(self, first, second=True):
144+
# Not useless because in the base class there is not default value for second argument
145+
super(NotUselessSuper, self).without_default_argument(first, second)
146+
147+
def with_default_argument_none(self, first, default_arg='NotNone'):
148+
# Not useless because the default_arg is different from the one in the base class
149+
super(NotUselessSuper, self).with_default_argument_none(first, default_arg)
150+
151+
def without_default_argument2(self, first, second=None):
152+
# Not useless because in the base class there is not default value for second argument
153+
super(NotUselessSuper, self).without_default_argument2(first, second)
154+
155+
def with_default_argument_int(self, first, default_arg="42"):
156+
# Not useless because the default_arg is a string whereas in the base class it's an int
157+
super(NotUselessSuper, self).with_default_argument_int(first, default_arg)
158+
159+
def with_default_argument_tuple(self, first, default_arg=("42", "a")):
160+
# Not useless because the default_arg is different from the one in the base class
161+
super(NotUselessSuper, self).with_default_argument_tuple(first, default_arg)
162+
163+
def with_default_argument_bis(self, first, default_arg="default"):
164+
# Although the default_arg is the same as in the base class, the call signature
165+
# differs. Thus it is not useless.
166+
super(NotUselessSuper, self).with_default_argument_bis(default_arg + "_argument")
167+
168+
def fake_method(self, param2="other"):
169+
super(NotUselessSuper, self).fake_method(param2)
170+
171+
def with_default_arg(self, first, default_arg="only_in_super_base"):
172+
# Not useless because the call of this method is different from the function signature
173+
super(NotUselessSuper, self).with_default_arg(first, default_arg + "_and_here")
174+
175+
def with_default_arg_bis(self, first, default_arg="default_changed"):
176+
# Not useless because the default value is different from the SuperBase one
177+
super(NotUselessSuper, self).with_default_arg_bis(first, default_arg)
178+
179+
def with_default_arg_ter(self, first, default_arg="has_been_changed_again"):
180+
# Not useless because the default value is different from the Base one
181+
super(NotUselessSuper, self).with_default_arg_ter(first, default_arg)
182+
183+
def with_default_arg_quad(self, first, default_arg="has_been_changed"):
184+
# Not useless because the default value is the same as in the base but the
185+
# call is different from the signature
186+
super(NotUselessSuper, self).with_default_arg_quad(first, default_arg + "_and_modified")
187+
95188

96189
class UselessSuper(Base):
97190

@@ -116,9 +209,38 @@ def equivalent_params_5(self, first, *args): # [useless-super-delegation]
116209
def equivalent_params_6(self, first, *args, **kwargs): # [useless-super-delegation]
117210
return super(UselessSuper, self).equivalent_params_6(first, *args, **kwargs)
118211

212+
def with_default_argument(self, first, default_arg="default"): # [useless-super-delegation]
213+
# useless because the default value here is the same as in the base class
214+
return super(UselessSuper, self).with_default_argument(first, default_arg)
215+
216+
def without_default_argument(self, first, second): # [useless-super-delegation]
217+
return super(UselessSuper, self).without_default_argument(first, second)
218+
219+
def with_default_argument_none(self, first, default_arg=None): # [useless-super-delegation]
220+
# useless because the default value here is the same as in the base class
221+
super(UselessSuper, self).with_default_argument_none(first, default_arg)
222+
223+
def with_default_argument_int(self, first, default_arg=42): # [useless-super-delegation]
224+
super(UselessSuper, self).with_default_argument_int(first, default_arg)
225+
226+
def with_default_argument_tuple(self, first, default_arg=()): # [useless-super-delegation]
227+
super(UselessSuper, self).with_default_argument_tuple(first, default_arg)
228+
119229
def __init__(self): # [useless-super-delegation]
120230
super(UselessSuper, self).__init__()
121231

232+
def with_default_arg(self, first, default_arg="only_in_super_base"): # [useless-super-delegation]
233+
super(UselessSuper, self).with_default_arg(first, default_arg)
234+
235+
def with_default_arg_bis(self, first, default_arg="only_in_super_base"): # [useless-super-delegation]
236+
super(UselessSuper, self).with_default_arg_bis(first, default_arg)
237+
238+
def with_default_arg_ter(self, first, default_arg="has_been_changed"): # [useless-super-delegation]
239+
super(UselessSuper, self).with_default_arg_ter(first, default_arg)
240+
241+
def with_default_arg_quad(self, first, default_arg="has_been_changed"): # [useless-super-delegation]
242+
super(UselessSuper, self).with_default_arg_quad(first, default_arg)
243+
122244

123245
def trigger_something(value_to_trigger):
124246
pass
Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
1-
useless-super-delegation:98:UselessSuper.equivalent_params:Useless super delegation in method 'equivalent_params'
2-
useless-super-delegation:101:UselessSuper.equivalent_params_1:Useless super delegation in method 'equivalent_params_1'
3-
useless-super-delegation:104:UselessSuper.equivalent_params_2:Useless super delegation in method 'equivalent_params_2'
4-
useless-super-delegation:107:UselessSuper.equivalent_params_3:Useless super delegation in method 'equivalent_params_3'
5-
useless-super-delegation:110:UselessSuper.equivalent_params_4:Useless super delegation in method 'equivalent_params_4'
6-
useless-super-delegation:113:UselessSuper.equivalent_params_5:Useless super delegation in method 'equivalent_params_5'
7-
useless-super-delegation:116:UselessSuper.equivalent_params_6:Useless super delegation in method 'equivalent_params_6'
8-
useless-super-delegation:119:UselessSuper.__init__:Useless super delegation in method '__init__'
1+
useless-super-delegation:191:UselessSuper.equivalent_params:Useless super delegation in method 'equivalent_params'
2+
useless-super-delegation:194:UselessSuper.equivalent_params_1:Useless super delegation in method 'equivalent_params_1'
3+
useless-super-delegation:197:UselessSuper.equivalent_params_2:Useless super delegation in method 'equivalent_params_2'
4+
useless-super-delegation:200:UselessSuper.equivalent_params_3:Useless super delegation in method 'equivalent_params_3'
5+
useless-super-delegation:203:UselessSuper.equivalent_params_4:Useless super delegation in method 'equivalent_params_4'
6+
useless-super-delegation:206:UselessSuper.equivalent_params_5:Useless super delegation in method 'equivalent_params_5'
7+
useless-super-delegation:209:UselessSuper.equivalent_params_6:Useless super delegation in method 'equivalent_params_6'
8+
useless-super-delegation:212:UselessSuper.with_default_argument:Useless super delegation in method 'with_default_argument'
9+
useless-super-delegation:216:UselessSuper.without_default_argument:Useless super delegation in method 'without_default_argument'
10+
useless-super-delegation:219:UselessSuper.with_default_argument_none:Useless super delegation in method 'with_default_argument_none'
11+
useless-super-delegation:223:UselessSuper.with_default_argument_int:Useless super delegation in method 'with_default_argument_int'
12+
useless-super-delegation:226:UselessSuper.with_default_argument_tuple:Useless super delegation in method 'with_default_argument_tuple'
13+
useless-super-delegation:229:UselessSuper.__init__:Useless super delegation in method '__init__'
14+
useless-super-delegation:232:UselessSuper.with_default_arg:Useless super delegation in method 'with_default_arg'
15+
useless-super-delegation:235:UselessSuper.with_default_arg_bis:Useless super delegation in method 'with_default_arg_bis'
16+
useless-super-delegation:238:UselessSuper.with_default_arg_ter:Useless super delegation in method 'with_default_arg_ter'
17+
useless-super-delegation:241:UselessSuper.with_default_arg_quad:Useless super delegation in method 'with_default_arg_quad'

0 commit comments

Comments
 (0)