Skip to content
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 slots to base classes, @add_slots takes bases into account #605

Merged
merged 7 commits into from
Jan 16, 2022

Conversation

ariebovenberg
Copy link
Contributor

@ariebovenberg ariebovenberg commented Jan 14, 2022

Summary

See #574: many slots were missing in base classes, meaning we didn't get the full memory savings or the ability to block arbitrary attribute setting.

Additionally, I found some slots overlapping where add_slots dataclasses inherited from each other. I think this isn't even done right in the stdlib...

Some changes were needed to the tests now that arbitrary attributes could not be set. Here I was less sure of the desired outcome.

Test Plan

Future violations can be prevented with slotscheck (which I developed).
See here a recent PR where I added it to the returns library.

I'm of course happy to include in dev/CI plan as you indidated

@facebook-github-bot facebook-github-bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jan 14, 2022
mock.visited_a()
object.__setattr__(node, "a_attr", True)
def visit_Del(self, node: cst.Del) -> None:
object.__setattr__(node, "target", mock.visited_a())
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I had to change to using Del, because Pass didn't have two fields available to reproduce the original test.

due to the slots, arbitrary fields can no longer be set. Existing fields need to be (ab)used.

def visit_Pass(self, node: cst.Pass) -> None:
mock.visit_Pass()
object.__setattr__(node, "visit_Pass", True)
def visit_If(self, node: cst.If) -> None:
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I had to change to using If, because Pass didn't have four fields available to reproduce the original test.

due to the slots, arbitrary fields can no longer be set. Existing fields need to be (ab)used.

@ariebovenberg
Copy link
Contributor Author

Wow, I did a rough benchmark:

from pympler.asizeof import asizeof
from libcst import parse_module
from pathlib import Path

parsed = parse_module(Path('libcst/_typed_visitor.py').read_text())
print(asizeof(parsed))

without patched slots: 15_240_816
with patched slots: 5_660_976

This cuts memory usage by a factor 3 :)

Copy link
Member

@zsol zsol left a comment

Choose a reason for hiding this comment

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

Thanks for the high quality PR! Much appreciated. All of this looks good to me. There's just one change I'd like to request before merging: can you please add a unit test that exercises the new logic in add_slots?

Comment on lines 113 to 116
# pyre-ignore[4]: Attribute `__slots__` of class `CSTNode`
# has type `typing.Tuple[]` but no type is specified.
# But we know it's str
__slots__ = ()
Copy link
Member

Choose a reason for hiding this comment

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

I think you can remove this pyre-ignore by doing

Suggested change
# pyre-ignore[4]: Attribute `__slots__` of class `CSTNode`
# has type `typing.Tuple[]` but no type is specified.
# But we know it's str
__slots__ = ()
__slots__: Sequence[str] = ()

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I needed an additional ClassVar to prevent it from being picked up as a dataclass field

@codecov-commenter
Copy link

codecov-commenter commented Jan 15, 2022

Codecov Report

Merging #605 (dcb6447) into main (5f22b6c) will increase coverage by 0.04%.
The diff coverage is 100.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #605      +/-   ##
==========================================
+ Coverage   94.72%   94.76%   +0.04%     
==========================================
  Files         244      245       +1     
  Lines       24916    25038     +122     
==========================================
+ Hits        23602    23728     +126     
+ Misses       1314     1310       -4     
Impacted Files Coverage Δ
libcst/_add_slots.py 96.66% <100.00%> (+10.95%) ⬆️
libcst/_nodes/base.py 95.48% <100.00%> (+0.07%) ⬆️
libcst/_nodes/expression.py 96.78% <100.00%> (+0.03%) ⬆️
libcst/_nodes/op.py 95.73% <100.00%> (+0.07%) ⬆️
libcst/_nodes/statement.py 95.15% <100.00%> (+0.01%) ⬆️
libcst/_nodes/whitespace.py 98.94% <100.00%> (+0.01%) ⬆️
libcst/tests/test_add_slots.py 100.00% <100.00%> (ø)
libcst/tests/test_batched_visitor.py 100.00% <100.00%> (ø)
...emod/visitors/tests/test_apply_type_annotations.py 100.00% <0.00%> (ø)
libcst/codemod/visitors/_apply_type_annotations.py 95.18% <0.00%> (+1.00%) ⬆️
... and 1 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 5f22b6c...dcb6447. Read the comment docs.

Copy link
Member

@zsol zsol left a comment

Choose a reason for hiding this comment

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

Let's go🚀! Thanks for the contribution

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants