-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Add a new declare-non-slot
error code
#9564
Add a new declare-non-slot
error code
#9564
Conversation
- Add test case which should report the error code. - Add test case where the error is suppressed due to __dict__ in __slots__. - Add test to detect annotation not in any base class' __slots__ - Add test against false-positive when base doesn't have __slots__ (since there is still then an instance __dict__ thanks to base) - Add test against false-positive when base has __slots__ with __dict__ entry Ignore declare-non-slots for regression_5479.py - This regression test is for "assigning-non-slot" so ignore "declare-non-slots" which also matches this example. Implement check for "declare-non-slot" and 2 new helper methods: - Add method `_check_declare_non_slot` to report "declare-non-slot" error. This method checks `node` and all bases for a valid `__slots__`, gathering the names in all `__slots__` found. If `node` has an annotation not in any `__slots__`, then "declare-non-slot" is reported. - Add helper method `_has_valid_slots` which returns True if a valid `__slots__` is found on a ClassDef (re-use logic from `_check_slots`). - Refactor `_check_redefined_slots` to split out logic for getting `__slots__` names into `_get_classdef_slots_names` helper.
for more information, see https://pre-commit.ci
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #9564 +/- ##
==========================================
+ Coverage 95.83% 95.84% +0.01%
==========================================
Files 174 174
Lines 18812 18862 +50
==========================================
+ Hits 18028 18078 +50
Misses 784 784
|
This comment has been minimized.
This comment has been minimized.
Based on these messages, the error should probably exclude any annotation that looks like a descriptor. I'm assuming that's how such members are implemented but I'll need to look into it. EDIT: upon further reading, an empty |
declare-non-slot
error code
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.
Looks great already, thank you. I don't have the time to really review the change at the moment but regarding the CI fail: You can create an example in the doc (see for example bad-chained-comparison : https://github.com/pylint-dev/pylint/tree/main/doc/data/messages/b/bad-chained-comparison) and a changelog entry for towncrier (Some doc here, but you can copy paste an existing fragment of course: https://pylint.readthedocs.io/en/stable/development_guide/contributor_guide/contribute.html#creating-a-pull-request)
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.
Left some comments. Code looks really good, thanks for opening the PR. I intended to have a look at your fork, but this is much easier as I can make in-line comments. Nice job on your first PR!
Thanks very much for the feedback! I'll incorporate suggestions when I have a little more time, hopefully in the next week or so. |
Co-authored-by: Daniël van Noord <[email protected]>
Co-authored-by: Pierre Sassoulas <[email protected]>
Avoid unnecessary inference if any ancestor has __dict__ in __slots__ Co-authored-by: Pierre Sassoulas <[email protected]>
Thanks so much for the review comments! I've reflected on my implementation and I think it needs a bit more work 🤔 - just to share a few thoughts:
|
This comment has been minimized.
This comment has been minimized.
- An empty __slots__ is required if a class takes part in multiple inheritance, so assume this is the case if the base class has an empty __slots__ and abort the check. - Abort early if __dict__ found in any __slots__. - No need to call self._has_valid_slots in self._get_classdef_slots_names since we already call it at the start of the check. - Move definition to below E0244 for consistency, remove "'" around %r as this adds extra quotes when formatted.
for more information, see https://pre-commit.ci
This comment has been minimized.
This comment has been minimized.
Re. reporting an error for an empty |
This comment has been minimized.
This comment has been minimized.
Just letting you know that I do want to review this but haven't found the time. I hope to get to this in the next 7 days. |
@@ -1485,6 +1536,24 @@ def _check_functools_or_not(self, decorator: nodes.Attribute) -> bool: | |||
|
|||
return "functools" in dict(import_node.names) | |||
|
|||
def _has_valid_slots(self, node: nodes.ClassDef) -> bool: |
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'm on mobile but this seems very similar to _check_slots
. I'm a bit worried that we're duplicating checks and run the risk of code drift.
Have you considered refactoring the other method to serve both purposes?
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 agree that _check_slots
and _has_valid_slots
are similar and that it would be best not to duplicate these checks. Refactoring is complicated by the fact that _check_slots
is doing a few jobs at the same time:
- checking whether
__slots__
is valid - if not, add 1 of 2 possible messages based on the reason why it isn't valid
- If
__slots__
looks valid, get the slot items and apply 2 further checks.
The difference between _check_slots
and _has_valid_slots
are that _check_slots
cares about the reason why __slots__
isn't valid, whereas _has_valid_slots
only detects whether __slots__
is valid. It might be wise to introduce a method that serves both purposes e.g. _validate_slots
that returns an Enum
that either reports a valid __slots__
or says why it is invalid. This would allow you to separate the multiple concerns of the _check_slots
method a little more clearly.
The question is whether refactoring _check_slots
is within the scope of this MR. To me that feels like quite a big refactor that should be its own MR. I would propose that if the present MR is accepted an issue should then be opened to highlight the need to refactor these slot checks to reduce the duplication. I'd be happy to work on that issue as a separate MR.
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.
Thanks, I had another good look and think this is good to go.
@Pierre-Sassoulas do you want to do a second review? Or shall we just merge this? :)
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.
Just a nit if it's not too much work to fully cover the new code. This looks amazing, a great new check, thank you @adamtuft
for child in node.body: | ||
if isinstance(child, nodes.AnnAssign): | ||
if child.value is not None: | ||
continue |
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.
We have self.a = 42
line 183 in the functional test, maybe getting coverage for this is as simple as adding a line with self.b : str = "AnnAssign.value is not None"
too ?
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.
Thanks, I've added this, hopefully this will fix the code coverage complaint. Waiting for the CI checks to run 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.
There might be specific condition to enter this condition, (a "b" value in slot maybe ?)
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.
The codecov test result seems to have changed now, it's claiming that a line isn't covered which (surely?) must be covered by the tests which generate the new message. I don't understand why the codecov test is failing, are you able to shed any light on this please?
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 tried something with b: str
being declared. I assume the diff between main and this branch got too big. Also fixed the doc generation.
This comment has been minimized.
This comment has been minimized.
Thank you both for the reviews again! I don't understand why the codecov check is still failing... surely the highlighted line is covered by tests since it has to be executed for the tests which generate the new error? |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
||
# Not in any base __slots__ | ||
d: int # [declare-non-slot] | ||
e: str= "AnnAssign.value is not None" |
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.
Added this line to cover everything. Do we agree it shouldn't raised @adamtuft ?
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.
Thanks, I see now that I added that annotation to a test which doesn't report the message, so it wasn't covering the required line.
Yes, looks good, that doesn't raise the error 👍 thank you for your help!
🤖 According to the primer, this change has no effect on the checked open source code. 🤖🎉 This comment was generated for commit 05e0e14 |
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 @adamtuft, congratulation on becoming a pylint contributor ! This will be released in 3.3.0 :)
Thank you very much @Pierre-Sassoulas and @DanielNoord for your help and feedback on this contribution! Very happy to have a contribution accepted 🥳 |
Type of Changes
Description
Refs #9499
Implement
declare-non-slot
which reports an error when a class has a__slots__
member and a type hint on the class is not present in__slots__
.Details
Tests:
Add test case which should report the error code.
Add test case where the error is suppressed due to
__dict__
in__slots__
.Add test to detect annotation not in any base class's
__slots__
.Add test against false-positive when base doesn't have
__slots__
(since there is still then an instance__dict__
thanks to base)Add test against false-positive when base has
__slots__
with__dict__
entry.Ignore
declare-non-slots
forregression_5479.py
which is forassigning-non-slot
.Implementation:
Implement check for
declare-non-slot
inclass_checker.py
and 2 new helper methods:Add method
_check_declare_non_slot
to reportdeclare-non-slot
error. This method checksnode
and all bases for a valid__slots__
, gathering the names in all__slots__
found. Ifnode
has an annotation not in any__slots__
, thendeclare-non-slot
is reported.Add helper method
_has_valid_slots
which returnsTrue
if a valid__slots__
is found on aClassDef
(re-use logic from_check_slots
).Refactor
_check_redefined_slots
to split out logic for getting__slots__
names into_get_classdef_slots_names
helper, since the same logic can be used by both functions.Some thoughts:
declare-non-slot
overdefine-non-slot
as annotating a class feels more like a declaration that such a member exists, rather than a definition of that member._get_classdef_slots_names
as this seemed the cleanest way to re-use existing logic. I hope this is ok.python3 -m pytest -m primer_stdlib --primer-stdlib
.To Do:
missing-slot-annotation
from Check "__slots__" and "@dataclass" attributes #9499Add documentation (good/bad code examples)Add changelog/towncrier entry