Skip to content

Commit 7167442

Browse files
authored
Customize arguments-differ error messages (#4422)
Changes the output messages of arguments-differ based on different error cases. It is part one of the issue #3536
1 parent ce55bce commit 7167442

File tree

8 files changed

+146
-52
lines changed

8 files changed

+146
-52
lines changed

ChangeLog

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ Release date: 2021-04-25
5151

5252
Closes #4399
5353

54+
* The warning for ``arguments-differ`` now signals explicitly the difference it detected
55+
by naming the argument or arguments that changed and the type of change that occured.
56+
5457

5558
What's New in Pylint 2.8.0?
5659
===========================

doc/whatsnew/2.9.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,6 @@ Other Changes
2020
* Fix false-positive ``too-many-ancestors`` when inheriting from builtin classes,
2121
especially from the ``collections.abc`` module
2222

23+
* The output messages for ``arguments-differ`` error message have been customized based on the different error cases.
24+
2325
* New option ``--fail-on=<msg ids>`` to return non-zero exit codes regardless of ``fail-under`` value.

pylint/checkers/classes.py

Lines changed: 101 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"""
4545
import collections
4646
from itertools import chain, zip_longest
47+
from typing import List, Pattern
4748

4849
import astroid
4950

@@ -266,22 +267,48 @@ def _has_different_parameters_default_value(original, overridden):
266267
return False
267268

268269

269-
def _has_different_parameters(original, overridden, dummy_parameter_regex):
270+
def _has_different_parameters(
271+
original: List[astroid.AssignName],
272+
overridden: List[astroid.AssignName],
273+
dummy_parameter_regex: Pattern,
274+
counter: int,
275+
) -> List[str]:
276+
result = []
270277
zipped = zip_longest(original, overridden)
271278
for original_param, overridden_param in zipped:
272279
params = (original_param, overridden_param)
273280
if not all(params):
274-
return True
275-
281+
return ["Number of parameters "]
282+
283+
# check for the arguments' type
284+
original_type = original_param.parent.annotations[counter]
285+
if original_type is not None:
286+
overridden_type = overridden_param.parent.annotations[counter]
287+
if overridden_type is not None:
288+
if original_type.name != overridden_type.name:
289+
result.append(
290+
f"Parameter '{original_param.name}' was of type '{original_type.name}' and is now"
291+
+ f" of type '{overridden_type.name}' in"
292+
)
293+
counter += 1
294+
295+
# check for the arguments' name
276296
names = [param.name for param in params]
277297
if any(dummy_parameter_regex.match(name) for name in names):
278298
continue
279299
if original_param.name != overridden_param.name:
280-
return True
281-
return False
300+
result.append(
301+
f"Parameter '{original_param.name}' has been renamed to '{overridden_param.name}' in"
302+
)
303+
304+
return result
282305

283306

284-
def _different_parameters(original, overridden, dummy_parameter_regex):
307+
def _different_parameters(
308+
original: astroid.FunctionDef,
309+
overridden: astroid.FunctionDef,
310+
dummy_parameter_regex: Pattern,
311+
) -> List[str]:
285312
"""Determine if the two methods have different parameters
286313
287314
They are considered to have different parameters if:
@@ -293,6 +320,7 @@ def _different_parameters(original, overridden, dummy_parameter_regex):
293320
* they have different keyword only parameters.
294321
295322
"""
323+
output_messages = []
296324
original_parameters = _positional_parameters(original)
297325
overridden_parameters = _positional_parameters(overridden)
298326

@@ -315,26 +343,48 @@ def _different_parameters(original, overridden, dummy_parameter_regex):
315343
v for v in original.args.kwonlyargs if v.name in overidden_names
316344
]
317345

346+
arguments = list(original.args.args)
347+
# variable 'count' helps to check if the type of an argument has changed
348+
# at the _has_different_parameters method
349+
if any(arg.name == "self" for arg in arguments) and len(arguments) > 1:
350+
count = 1
351+
else:
352+
count = 0
353+
318354
different_positional = _has_different_parameters(
319-
original_parameters, overridden_parameters, dummy_parameter_regex
355+
original_parameters, overridden_parameters, dummy_parameter_regex, count
320356
)
321357
different_kwonly = _has_different_parameters(
322-
original_kwonlyargs, overridden.args.kwonlyargs, dummy_parameter_regex
358+
original_kwonlyargs, overridden.args.kwonlyargs, dummy_parameter_regex, count
323359
)
360+
if different_kwonly and different_positional:
361+
if "Number " in different_positional[0] and "Number " in different_kwonly[0]:
362+
output_messages.append("Number of parameters ")
363+
output_messages += different_positional[1:]
364+
output_messages += different_kwonly[1:]
365+
else:
366+
if different_positional:
367+
output_messages += different_positional
368+
if different_kwonly:
369+
output_messages += different_kwonly
370+
324371
if original.name in PYMETHODS:
325372
# Ignore the difference for special methods. If the parameter
326373
# numbers are different, then that is going to be caught by
327374
# unexpected-special-method-signature.
328375
# If the names are different, it doesn't matter, since they can't
329376
# be used as keyword arguments anyway.
330-
different_positional = different_kwonly = False
377+
output_messages.clear()
331378

332379
# Arguments will only violate LSP if there are variadics in the original
333380
# that are then removed from the overridden
334381
kwarg_lost = original.args.kwarg and not overridden.args.kwarg
335382
vararg_lost = original.args.vararg and not overridden.args.vararg
336383

337-
return any((different_positional, kwarg_lost, vararg_lost, different_kwonly))
384+
if kwarg_lost or vararg_lost:
385+
output_messages += ["Variadics removed in"]
386+
387+
return output_messages
338388

339389

340390
def _is_invalid_base_class(cls):
@@ -549,7 +599,7 @@ def _has_same_layout_slots(slots, assigned_value):
549599
"be written as a function.",
550600
),
551601
"W0221": (
552-
"Parameters differ from %s %r method",
602+
"%s %s %r method",
553603
"arguments-differ",
554604
"Used when a method has a different number of arguments than in "
555605
"the implemented interface or in an overridden method.",
@@ -1760,12 +1810,47 @@ def _check_signature(self, method1, refmethod, class_type, cls):
17601810
if is_property_setter(method1):
17611811
return
17621812

1763-
if _different_parameters(
1813+
arg_differ_output = _different_parameters(
17641814
refmethod, method1, dummy_parameter_regex=self._dummy_rgx
1765-
):
1766-
self.add_message(
1767-
"arguments-differ", args=(class_type, method1.name), node=method1
1768-
)
1815+
)
1816+
if len(arg_differ_output) > 0:
1817+
for msg in arg_differ_output:
1818+
if "Number" in msg:
1819+
total_args_method1 = len(method1.args.args)
1820+
if method1.args.vararg:
1821+
total_args_method1 += 1
1822+
if method1.args.kwarg:
1823+
total_args_method1 += 1
1824+
if method1.args.kwonlyargs:
1825+
total_args_method1 += len(method1.args.kwonlyargs)
1826+
total_args_refmethod = len(refmethod.args.args)
1827+
if refmethod.args.vararg:
1828+
total_args_refmethod += 1
1829+
if refmethod.args.kwarg:
1830+
total_args_refmethod += 1
1831+
if refmethod.args.kwonlyargs:
1832+
total_args_refmethod += len(refmethod.args.kwonlyargs)
1833+
self.add_message(
1834+
"arguments-differ",
1835+
args=(
1836+
msg
1837+
+ f"was {total_args_refmethod} in '{refmethod.parent.name}.{refmethod.name}' and "
1838+
f"is now {total_args_method1} in",
1839+
class_type,
1840+
str(method1.parent.name) + "." + str(method1.name),
1841+
),
1842+
node=method1,
1843+
)
1844+
else:
1845+
self.add_message(
1846+
"arguments-differ",
1847+
args=(
1848+
msg,
1849+
class_type,
1850+
str(method1.parent.name) + "." + str(method1.name),
1851+
),
1852+
node=method1,
1853+
)
17691854
elif (
17701855
len(method1.args.defaults) < len(refmethod.args.defaults)
17711856
and not method1.args.vararg

tests/functional/a/arguments_differ.py

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ def test(self):
99

1010
class Child(Parent):
1111

12-
def test(self, arg): # [arguments-differ]
12+
def test(self, arg: int): #[arguments-differ]
1313
pass
1414

1515

@@ -27,7 +27,7 @@ def test(self, arg=None): # [arguments-differ]
2727
class Classmethod(object):
2828

2929
@classmethod
30-
def func(cls, data):
30+
def func(cls, data: str):
3131
return data
3232

3333
@classmethod
@@ -56,20 +56,20 @@ def fromkeys(cls, arg, arg1):
5656

5757
class Varargs(object):
5858

59-
def has_kwargs(self, arg, **kwargs):
59+
def has_kwargs(self, arg: bool, **kwargs):
6060
pass
6161

62-
def no_kwargs(self, args):
62+
def no_kwargs(self, args: bool):
6363
pass
6464

6565

6666
class VarargsChild(Varargs):
6767

68-
def has_kwargs(self, arg): # [arguments-differ]
69-
"Not okay to lose capabilities."
68+
def has_kwargs(self, arg: int): #[arguments-differ, arguments-differ]
69+
"Not okay to lose capabilities. Also, type has changed."
7070

71-
def no_kwargs(self, arg, **kwargs): # [arguments-differ]
72-
"Not okay to add extra capabilities."
71+
def no_kwargs(self, arg: bool, **kwargs): # [arguments-differ]
72+
"Addition of kwargs does not violate LSP, but first argument's name has changed."
7373

7474

7575
class Super(object):
@@ -111,14 +111,14 @@ def method(self, param='abc'):
111111
class Staticmethod(object):
112112

113113
@staticmethod
114-
def func(data):
114+
def func(data: int):
115115
return data
116116

117117

118118
class StaticmethodChild(Staticmethod):
119119

120120
@classmethod
121-
def func(cls, data):
121+
def func(cls, data: str):
122122
return data
123123

124124

@@ -169,7 +169,7 @@ def test(self, *args):
169169

170170
class SecondChangesArgs(FirstHasArgs):
171171

172-
def test(self, first, second, *args): # [arguments-differ]
172+
def test(self, first: int, second: int, *args): # [arguments-differ]
173173
pass
174174

175175

@@ -213,23 +213,26 @@ def mixed(self, first, *args, third, **kwargs):
213213

214214
class HasSpecialMethod(object):
215215

216-
def __getitem__(self, key):
216+
def __getitem__(self, key: int):
217217
return key
218218

219219

220220
class OverridesSpecialMethod(HasSpecialMethod):
221221

222-
def __getitem__(self, cheie):
222+
def __getitem__(self, cheie: int):
223+
# no error here, method overrides special method
223224
return cheie + 1
224225

225226

226227
class ParentClass(object):
227228

228-
def meth(self, arg, arg1):
229+
def meth(self, arg: str, arg1: str):
229230
raise NotImplementedError
230231

231232

232233
class ChildClass(ParentClass):
233234

234-
def meth(self, _arg, dummy):
235+
def meth(self, _arg: str, dummy: str):
236+
# no error here, "dummy" and "_" are being ignored if
237+
# spotted in a variable name (declared in dummy_parameter_regex)
235238
pass
Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
arguments-differ:12:4:Child.test:Parameters differ from overridden 'test' method
2-
arguments-differ:23:4:ChildDefaults.test:Parameters differ from overridden 'test' method
3-
arguments-differ:41:4:ClassmethodChild.func:Parameters differ from overridden 'func' method
4-
arguments-differ:68:4:VarargsChild.has_kwargs:Parameters differ from overridden 'has_kwargs' method
5-
arguments-differ:71:4:VarargsChild.no_kwargs:Parameters differ from overridden 'no_kwargs' method
6-
arguments-differ:172:4:SecondChangesArgs.test:Parameters differ from overridden 'test' method
1+
arguments-differ:12:4:Child.test:Number of parameters was 1 in 'Parent.test' and is now 2 in overridden 'Child.test' method
2+
arguments-differ:23:4:ChildDefaults.test:Number of parameters was 3 in 'ParentDefaults.test' and is now 2 in overridden 'ChildDefaults.test' method
3+
arguments-differ:41:4:ClassmethodChild.func:Number of parameters was 2 in 'Classmethod.func' and is now 0 in overridden 'ClassmethodChild.func' method
4+
arguments-differ:68:4:VarargsChild.has_kwargs:Parameter 'arg' was of type 'bool' and is now of type 'int' in overridden 'VarargsChild.has_kwargs' method
5+
arguments-differ:68:4:VarargsChild.has_kwargs:Variadics removed in overridden 'VarargsChild.has_kwargs' method
6+
arguments-differ:71:4:VarargsChild.no_kwargs:Parameter 'args' has been renamed to 'arg' in overridden 'VarargsChild.no_kwargs' method
7+
arguments-differ:172:4:SecondChangesArgs.test:Number of parameters was 2 in 'FirstHasArgs.test' and is now 4 in overridden 'SecondChangesArgs.test' method

tests/functional/a/arguments_differ_py3.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,31 @@
11
# pylint: disable=missing-docstring,too-few-public-methods
22
class AbstractFoo:
33

4-
def kwonly_1(self, first, *, second, third):
4+
def kwonly_1(self, first: int, *, second: int, third: int):
55
"Normal positional with two positional only params."
66

7-
def kwonly_2(self, *, first, second):
7+
def kwonly_2(self, *, first: str, second: str):
88
"Two positional only parameter."
99

10-
def kwonly_3(self, *, first, second):
10+
def kwonly_3(self, *, first: str, second: str):
1111
"Two positional only params."
1212

13-
def kwonly_4(self, *, first, second=None):
13+
def kwonly_4(self, *, first: str, second=None):
1414
"One positional only and another with a default."
1515

16-
def kwonly_5(self, *, first, **kwargs):
16+
def kwonly_5(self, *, first: bool, **kwargs):
1717
"Keyword only and keyword variadics."
1818

19-
def kwonly_6(self, first, second, *, third):
19+
def kwonly_6(self, first: float, second: float, *, third: int):
2020
"Two positional and one keyword"
2121

2222

2323
class Foo(AbstractFoo):
2424

25-
def kwonly_1(self, first, *, second): # [arguments-differ]
25+
def kwonly_1(self, first: int, *, second: int): # [arguments-differ]
2626
"One positional and only one positional only param."
2727

28-
def kwonly_2(self, first): # [arguments-differ]
28+
def kwonly_2(self, *, first: str): # [arguments-differ]
2929
"Only one positional parameter instead of two positional only parameters."
3030

3131
def kwonly_3(self, first, second): # [arguments-differ]
@@ -34,7 +34,7 @@ def kwonly_3(self, first, second): # [arguments-differ]
3434
def kwonly_4(self, first, second): # [arguments-differ]
3535
"Two positional params."
3636

37-
def kwonly_5(self, *, first): # [arguments-differ]
37+
def kwonly_5(self, *, first: bool): # [arguments-differ]
3838
"Keyword only, but no variadics."
3939

4040
def kwonly_6(self, *args, **kwargs): # valid override
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
arguments-differ:25:4:Foo.kwonly_1:Parameters differ from overridden 'kwonly_1' method
2-
arguments-differ:28:4:Foo.kwonly_2:Parameters differ from overridden 'kwonly_2' method
3-
arguments-differ:31:4:Foo.kwonly_3:Parameters differ from overridden 'kwonly_3' method
4-
arguments-differ:34:4:Foo.kwonly_4:Parameters differ from overridden 'kwonly_4' method
5-
arguments-differ:37:4:Foo.kwonly_5:Parameters differ from overridden 'kwonly_5' method
1+
arguments-differ:25:4:Foo.kwonly_1:Number of parameters was 4 in 'AbstractFoo.kwonly_1' and is now 3 in overridden 'Foo.kwonly_1' method
2+
arguments-differ:28:4:Foo.kwonly_2:Number of parameters was 3 in 'AbstractFoo.kwonly_2' and is now 2 in overridden 'Foo.kwonly_2' method
3+
arguments-differ:31:4:Foo.kwonly_3:Number of parameters was 3 in 'AbstractFoo.kwonly_3' and is now 3 in overridden 'Foo.kwonly_3' method
4+
arguments-differ:34:4:Foo.kwonly_4:Number of parameters was 3 in 'AbstractFoo.kwonly_4' and is now 3 in overridden 'Foo.kwonly_4' method
5+
arguments-differ:37:4:Foo.kwonly_5:Variadics removed in overridden 'Foo.kwonly_5' method

tests/functional/t/too/too_many_ancestors.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# pylint: disable=missing-docstring, too-few-public-methods, useless-object-inheritance
1+
# pylint: disable=missing-docstring, too-few-public-methods, useless-object-inheritance, arguments-differ
22
from collections.abc import MutableSequence
33

44
class Aaaa(object):

0 commit comments

Comments
 (0)