Skip to content

Conversation

@hippo91
Copy link
Contributor

@hippo91 hippo91 commented Aug 11, 2017

Deletion of false-positive useless-super-delegation when parameters default values in child class are different from those in the base class.
Close #1085

I just created a method that check if the default value of astroid.Arguments.args are different in the base method and in the overridden one. I wonder if i also should check astroid.Arguments.kwarg and astroid.Arguments.kwonlyargs.

Moreover @AWhetter suggested this bug could be linked to #1553 but i haven't been able to solve it and #1085 together.

Copy link
Contributor

@rogalski rogalski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One minor warning from pylint - please fix that.

Overall looks good, I left some comments inlined.

orig_default = None
try:
over_default = overridden.default_value(param_name).value
if orig_default != over_default:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens when default value is None?
Is overridden.default_value(param_name).value equal to None or equal to nodes.Const(None)? Maybe we can test for that? (default value equal to None is very common).

Copy link
Contributor Author

@hippo91 hippo91 Aug 12, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice remark. My code fails if the default value is equal to None.
I'm adding a test and working on a fix.

# we have found the method but it's not in the local
# dictionary.
# This may happen with astroid build from living objects
continue
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this branch is not taken in tests - it would be cool to have this covered by some automated test, but I guess we do not have to be that restrictive.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, i just copied/pasted code of the visit_functiondef...

# This may happen with astroid build from living objects
continue
if not isinstance(meth_node, astroid.FunctionDef):
continue
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.

Copy link
Contributor

@rogalski rogalski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO it's really closed to being merged. See my comments and decide whether they seems reasonable to you.

Return True if one of the overridden arguments has a default
value different from the default value of the original argument
"""
orig_name = [param.name for param in original.args]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.as_string() is not kind of API you want to use in this case.

Maybe consider using standard sentinel notion. Something around the lines of:

default_missing = object()
for param_name in orig_name:
  try:
    orig_default = original.default_value(param_name)
  except astroid.exceptions.NoDefault:
    orig_default = default_missing
  try:
    over_default = overridden.default_value(param_name)
  except astroid.exceptions.NoDefault:
    over_default = default_missing
  if orig_default != over_default
    return True
return False

===========================

  - the check of args.args is None is done inside ``_has_different_parameters_default_value``
  - the call to ``_check_useless_super_delegation`` in  ``visit_functiondef`` method has been reordered in order
    to avoid the use of the ``_check_different_default_values_in_overridden_method`` method that had copy/pasted code
    which was also not tested.
  - deletion of ``_check_different_default_values_in_overridden_method`` which is now useless
  - change in the comment of ``_check_useless_super_delegation`` method to indicate that this method
    doesn't look into the value of default arguments to say delegation is useless

pylint/test/functional/useless_super_delegation.py
===================================================

  - add of test with fake method which doesn't really override base method
…e, then use as_string() method. Add of a functional test with default value that is not a literal but ca ClassDef (tuple)
@hippo91
Copy link
Contributor Author

hippo91 commented Aug 17, 2017

I finally removed the method _check_different_default_values_in_overridden_method which had redundancies with the method visit_functiondef. Moreover those redundancies correspond to uncovered code.
Instead i reordered the call to _check_useless_super_delegation in visit_functiondef. This way i find the code cleaner althought there are now 3 calls to _check_useless_super_delegation.
I also keep the use of as_string method inside _has_different_parameters_default_value method, but just in case the default value doesn't have a value attribute. This is the case when the default value is a astroid.ClassDef (happens when the default value is a tuple for example).

@hippo91
Copy link
Contributor Author

hippo91 commented Aug 27, 2017

I don't understand the coverage failure. I just changed the name of a local variable by removing a letter.

@AWhetter
Copy link
Contributor

AWhetter commented Sep 9, 2017

@PCManticore Please could we reauthorize coveralls with Github. I can't see why coveralls is failing at the moment.

@rogalski rogalski added Blocked 🚧 Blocked by a particular issue and removed reviewed-waiting-updates Work in progress labels Sep 20, 2017
@rogalski
Copy link
Contributor

Marked as blocked by coverage failure.

SOURCE NOT AVAILABLE
The owner of this repo needs to re-authorize with github; their OAuth credentials are no longer valid so the file cannot be pulled from the github API.

@hippo91
Copy link
Contributor Author

hippo91 commented Sep 21, 2017

Should i do something special in Github ?

@AWhetter
Copy link
Contributor

Unfortunately this can only be done by @PCManticore

@hippo91
Copy link
Contributor Author

hippo91 commented Sep 21, 2017

Ok thanks @AWhetter.

@PCManticore
Copy link
Contributor

This should work now, thanks for letting me know!

Copy link
Contributor

@PCManticore PCManticore left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a couple of minor things left to do, but the major one out of it is the actual place of the new _has_different_default_param_values function call, which is currently not where it should be.

continue
if not isinstance(meth_node, astroid.FunctionDef):
continue
# Don't care about functions with unknown argument (builtins).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the comment is stale

c-extensions.
Close #1466

* Fix a false positive ``useless-super-delegation`` message when
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will also need a What's new Entry, in the Other Changes section. Also please add yourself to Contributors file if you are not already there.

# if value attribute doesn't exist, it may be due to the fact
# that the default value is a ClassDef object and then the default
# value is taken to be the string representation
original_default = original.default_value(param_name).as_string()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say we should refactor this a bit to avoid some potential expensive calls:

  1. just get the default value, without accessing .value for both of the fields. thus when getting to line 165, we had no as_string call thus far
  2. there, check for common node kinds, by verifying their type. E.g, are these two Consts? get their value and check against that. Are these classes, get their name and check against that. We might throw the class type in there as well, so we can differentiate between List and Tuples for example. Calling as_string might be potentially slow which is why I prefer to avoid doing it unless necessary

if not isinstance(meth_node, astroid.FunctionDef):
continue
# Don't care about functions with unknown argument (builtins).
if not _has_different_parameters_default_value(meth_node.args, node.args):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not exactly sure this makes sense. Basically we can potentially call _check_useless_super_delegation for each overridden method, which can be many in certain rich hierarchy classes. This makes me think that the _has_different_parameters_default_value shouldn't actually be here, but rather in _check_useless_super_delegation itself

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, i put it here to avoid code duplicate but you are definitely right it make more sense to put it inside _check_useless_super_delegation itself.

@PCManticore PCManticore added reviewed-waiting-updates and removed Blocked 🚧 Blocked by a particular issue labels Sep 21, 2017
return
if not _has_different_parameters_default_value(meth_node.args, function.args):
self.add_message('useless-super-delegation', node=function, args=(function.name,))
# For each method that have an ancestor, the previous check is enough
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is correct. We still need to check that the call and the definition are equivalent, since this change only helps with default values. Here's an example where your code will emit a useless-super-delegation even though the super() call is not equivalent.

class A:
   def test(self, a=None):
      pass


class B(A):
   def test(self, a=None):
       super(B, self).test(a=a+1)

From what I see, if it these two functions have different default values, we should just skip the ancestor. If all ancestors behave the same, bail out and don't check for useless super delegation any longer. Otherwise, go to the signature check

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@PCManticore you are absolutely right. I will correct this ASAP. Thanks.

…e return early if the default values are different. A method redefinition is really useless if the default values are the same in the base class and in the child class but also if the call of the method is the same as the definition.
…ld method are the same but the call signature differs from the definition.
@PCManticore PCManticore merged commit 7efb436 into pylint-dev:master Sep 30, 2017
@PCManticore
Copy link
Contributor

Thank you so much @hippo91 for this contribution!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants