-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix useless-super-delegation false positive when default keyword argument is a variable. #5157
Conversation
Pull Request Test Coverage Report for Build 1359894741
π - Coveralls |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the cleanup and adding the missing test case !
pylint/checkers/classes.py
Outdated
nodes.List: "elts", | ||
nodes.Dict: "items", | ||
astroid_type_comparators = { | ||
nodes.Const: lambda a, b: a.value == b.value, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this design, good job π
Thanks for the review @Pierre-Sassoulas! Did you see my note about the Fix in the PR description?
|
In this case, it might be more efficient to check if it's a call of self/cls/ClassName, is that what you meant ? I did not understand the second part of your question, could you provide an example ? |
As part of this check, we try to determine of the default arguments of the original and overridden function take the default value. We explicitly check a few "handled" types, like for dicts we compare the items. When an argument takes an unhandled type as the default value, we just assume that the original and overridden values are the same. If you refer to my example in the PR description you'll see that X and Y are assumed to be the same value even though they are quite obviously different. This PR goes with the approach of adding nodes.Name to the explicit list of handled types and saying that X and Y are the same only if their inferred values are the same. Another approach would have been to say "If we don't know how to compare the values, assume that they are different". This would change this to produce more false negatives than false positives, which is definitely preferable imo. |
Thank you for clarifying, I see it now. I think the approach with less false positives is better.
Definitely, we very rarely have open issues for false negative π |
Ok! So I'll make the change for unhandled types when I get a chance. Regarding the explicit |
Well it's true it might reduce speed, but being able to do that (treading performance for exactness) is also the differentiating factor of pylint, so when there is no clever way of doing something without inference, we should use inference. Let's keep infer, if the result is better with it :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure about this suggestion but this (pre-existing) implementation felt complicated.
pylint/checkers/classes.py
Outdated
original_type = _get_node_type( | ||
original_default, tuple(handled for handled in astroid_type_comparators) | ||
) | ||
if original_type: | ||
# We handle only astroid types that are inside the dict astroid_type_compared_attr | ||
if not isinstance(overridden_default, original_type): | ||
# Two args with same name but different types | ||
return True | ||
if not astroid_type_comparators[original_type]( | ||
original_default, overridden_default | ||
): | ||
# Two args with same type but different values | ||
return True |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
original_type = _get_node_type( | |
original_default, tuple(handled for handled in astroid_type_comparators) | |
) | |
if original_type: | |
# We handle only astroid types that are inside the dict astroid_type_compared_attr | |
if not isinstance(overridden_default, original_type): | |
# Two args with same name but different types | |
return True | |
if not astroid_type_comparators[original_type]( | |
original_default, overridden_default | |
): | |
# Two args with same type but different values | |
return True | |
if original_default in astroid_type_comparators: | |
# We handle only astroid types that are inside the dict astroid_type_compared_attr | |
if not isinstance(overridden_default, original_default): | |
# Two args with same name but different types | |
return True | |
if not astroid_type_comparators[original_default]( | |
original_default, overridden_default | |
): | |
# Two args with same type but different values | |
return True |
Maybe we can even remove _get_node_type
? I can't check usage right now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't equivalent logic - original_default will never be in the astroid_type_comparators since it is an instance of a node, not the type. I agree that _get_node_type
seems overly complicated though so I'll see what refactoring I can do
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ho right, we need to use type() or __class__
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I confirmed that _get_node_name
was only used here, so I changed this function to just use type()
and removed _get_node_name
. The change from isinstance()
to type()
shouldn't matter here since none of the handled nodes
types have any subtyes as far as I can tell.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π
Great first contribution ! Congratulation on becoming a pylint contributor :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great first PR @nickpesce!
Could you address these two comments in a followup?
astroid_type_comparators = { | ||
nodes.Const: lambda a, b: a.value == b.value, | ||
nodes.ClassDef: lambda a, b: a.name == b.name, | ||
nodes.Tuple: lambda a, b: a.elts == b.elts, | ||
nodes.List: lambda a, b: a.elts == b.elts, | ||
nodes.Dict: lambda a, b: a.items == b.items, | ||
nodes.Name: lambda a, b: set(a.infer()) == set(b.infer()), | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This dict doesn't change. It would be better to move that to the module scope instead of recreating it each time the function is called.
# Both args have a default value. Compare them | ||
astroid_type_comparators = { | ||
nodes.Const: lambda a, b: a.value == b.value, | ||
nodes.ClassDef: lambda a, b: a.name == b.name, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For ClassDef
, it would be better to use qname
instead.
Created ^ for the followups. Thanks @Pierre-Sassoulas and @cdce8p! Glad I could make this contribution! |
Type of Changes
Description
similar to #3773
Problem
Minimal example of this issue:
variables X an Y are incorrectly assumed to be the same
Fix
Currently, only a select few types of default values are checked and all others are assumed to be the same.
nodes.Name
was not one of those handled cases, soX
andY
above were assumed to be the same. I could think of 2 solutionsnodes.Name
I went with option 2 since it seemed like the least intrusive and less likely to have side effects unknown to me, but I could switch it to option 1 if preferred.
Two variable names are considered the same if the inference system outputs the same possible values. The simpler option would be to always call two variables different but that would give more false negatives. I don't know much about the inference system though, so I'm looking for some input on this approach.