Skip to content

Commit

Permalink
Fix protected-access for attributes and methods of nested classes (
Browse files Browse the repository at this point in the history
…#5232)

* Fix access to private function in inner class on protected-access bug

* Add functional test for protected-access from inner class

* Add Ikraduya to CONTRIBUTORS file

* Add if statement to avoid potential bug

* Fix ``protected-access`` for attributes and methods of nested classes
This closes #3066

Co-authored-by: ikraduya <ikraduya@gmail.com>
Co-authored-by: Pierre Sassoulas <pierre.sassoulas@gmail.com>
  • Loading branch information
3 people committed Oct 31, 2021
1 parent 1d3a7ff commit db5d77c
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 16 deletions.
2 changes: 2 additions & 0 deletions CONTRIBUTORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -570,3 +570,5 @@ contributors:
* Takahide Nojima: contributor

* Tushar Sadhwani (tusharsadhwani): contributor

* Ikraduya Edian: contributor
4 changes: 4 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ Release date: TBA

Closes #3197

* Fixed ``protected-access`` for accessing of attributes and methods of inner classes

Closes #3066

* Added support for ``ModuleNotFoundError`` (``import-error`` and ``no-name-in-module``).
``ModuleNotFoundError`` inherits from ``ImportError`` and was added in Python ``3.6``

Expand Down
4 changes: 4 additions & 0 deletions doc/whatsnew/2.12.rst
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ Other Changes
``use-implicit-booleaness-not-len`` in order to be consistent with
``use-implicit-booleaness-not-comparison``.

* Fixed ``protected-access`` for accessing of attributes and methods of inner classes

Closes #3066

* Update ``literal-comparison``` checker to ignore tuple literals

Closes #3031
Expand Down
16 changes: 15 additions & 1 deletion pylint/checkers/classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
class_is_abstract,
decorated_with,
decorated_with_property,
get_outer_class,
has_known_bases,
is_attr_private,
is_attr_protected,
Expand Down Expand Up @@ -1606,9 +1607,22 @@ def _check_protected_attribute_access(self, node: nodes.Attribute):
if self._is_type_self_call(node.expr):
return

# Check if we are inside the scope of a class or nested inner class
inside_klass = True
outer_klass = klass
parents_callee = callee.split(".")
parents_callee.reverse()
for callee in parents_callee:
if callee != outer_klass.name:
inside_klass = False
break

# Move up one level within the nested classes
outer_klass = get_outer_class(outer_klass)

# We are in a class, one remaining valid cases, Klass._attr inside
# Klass
if not (callee == klass.name or callee in klass.basenames):
if not (inside_klass or callee in klass.basenames):
# Detect property assignments in the body of the class.
# This is acceptable:
#
Expand Down
7 changes: 7 additions & 0 deletions pylint/checkers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,13 @@ def node_frame_class(node: nodes.NodeNG) -> Optional[nodes.ClassDef]:
return klass


def get_outer_class(class_node: astroid.ClassDef) -> Optional[astroid.ClassDef]:
"""Return the class that is the outer class of given (nested) class_node"""
parent_klass = class_node.parent.frame()

return parent_klass if isinstance(parent_klass, astroid.ClassDef) else None


def is_attr_private(attrname: str) -> Optional[Match[str]]:
"""Check that attribute name is private (at least two leading underscores,
at most one trailing underscore)
Expand Down
64 changes: 64 additions & 0 deletions tests/functional/a/access/access_to_protected_members.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,3 +211,67 @@ def access_other_attr(cls):
instance = Issue1159OtherClass()
instance._bar = 3 # [protected-access]
_ = instance._foo # [protected-access]


class Issue3066:
"""Test for GitHub issue 3066
Accessing of attributes/methods of inner and outer classes
https://github.com/PyCQA/pylint/issues/3066"""

attr = 0
_attr = 1

@staticmethod
def _bar(i):
"""Docstring."""

@staticmethod
def foobar(i):
"""Test access from outer class"""
Issue3066._attr = 2
Issue3066.Aclass._attr = "y" # [protected-access]
Issue3066.Aclass.Bclass._attr = "b" # [protected-access]

Issue3066._bar(i)
Issue3066.Aclass._bar(i) # [protected-access]
Issue3066.Aclass.Bclass._bar(i) # [protected-access]

class Aclass:
"""Inner class for GitHub issue 3066"""

_attr = "x"

@staticmethod
def foobar(i):
"""Test access from inner class"""
Issue3066._attr = 2 # [protected-access]
Issue3066.Aclass._attr = "y"
Issue3066.Aclass.Bclass._attr = "b" # [protected-access]

Issue3066._bar(i) # [protected-access]
Issue3066.Aclass._bar(i)
Issue3066.Aclass.Bclass._bar(i) # [protected-access]

@staticmethod
def _bar(i):
"""Docstring."""

class Bclass:
"""Inner inner class for GitHub issue 3066"""

_attr = "a"

@staticmethod
def foobar(i):
"""Test access from inner inner class"""
Issue3066._attr = 2 # [protected-access]
Issue3066.Aclass._attr = "y" # [protected-access]
Issue3066.Aclass.Bclass._attr = "b"

Issue3066._bar(i) # [protected-access]
Issue3066.Aclass._bar(i) # [protected-access]
Issue3066.Aclass.Bclass._bar(i)

@staticmethod
def _bar(i):
"""Docstring."""
42 changes: 27 additions & 15 deletions tests/functional/a/access/access_to_protected_members.txt
Original file line number Diff line number Diff line change
@@ -1,16 +1,28 @@
protected-access:19:14:MyClass.test:Access to a protected member _haha of a client class
protected-access:41:0::Access to a protected member _protected of a client class
protected-access:42:6::Access to a protected member _protected of a client class
protected-access:43:0::Access to a protected member _cls_protected of a client class
protected-access:44:6::Access to a protected member _cls_protected of a client class
protected-access:58:19:Issue1031.incorrect_access:Access to a protected member _protected of a client class
protected-access:72:48:Issue1802.__eq__:Access to a protected member __private of a client class
protected-access:80:32:Issue1802.not_in_special:Access to a protected member _foo of a client class
protected-access:100:32:Issue1802.__fake_special__:Access to a protected member _foo of a client class
protected-access:162:8:Issue1159.access_other_attr:Access to a protected member _bar of a client class
protected-access:163:12:Issue1159.access_other_attr:Access to a protected member _foo of a client class
protected-access:19:14:MyClass.test:Access to a protected member _haha of a client class:HIGH
protected-access:41:0::Access to a protected member _protected of a client class:HIGH
protected-access:42:6::Access to a protected member _protected of a client class:HIGH
protected-access:43:0::Access to a protected member _cls_protected of a client class:HIGH
protected-access:44:6::Access to a protected member _cls_protected of a client class:HIGH
protected-access:58:19:Issue1031.incorrect_access:Access to a protected member _protected of a client class:HIGH
protected-access:72:48:Issue1802.__eq__:Access to a protected member __private of a client class:HIGH
protected-access:80:32:Issue1802.not_in_special:Access to a protected member _foo of a client class:HIGH
protected-access:100:32:Issue1802.__fake_special__:Access to a protected member _foo of a client class:HIGH
protected-access:162:8:Issue1159.access_other_attr:Access to a protected member _bar of a client class:HIGH
protected-access:163:12:Issue1159.access_other_attr:Access to a protected member _foo of a client class:HIGH
no-member:194:12:Issue1159Subclass.access_missing_member:Instance of 'Issue1159Subclass' has no '_baz' member; maybe '_bar'?:INFERENCE
protected-access:194:12:Issue1159Subclass.access_missing_member:Access to a protected member _baz of a client class
attribute-defined-outside-init:203:8:Issue1159Subclass.assign_missing_member:Attribute '_qux' defined outside __init__
protected-access:212:8:Issue1159Subclass.access_other_attr:Access to a protected member _bar of a client class
protected-access:213:12:Issue1159Subclass.access_other_attr:Access to a protected member _foo of a client class
protected-access:194:12:Issue1159Subclass.access_missing_member:Access to a protected member _baz of a client class:HIGH
attribute-defined-outside-init:203:8:Issue1159Subclass.assign_missing_member:Attribute '_qux' defined outside __init__:HIGH
protected-access:212:8:Issue1159Subclass.access_other_attr:Access to a protected member _bar of a client class:HIGH
protected-access:213:12:Issue1159Subclass.access_other_attr:Access to a protected member _foo of a client class:HIGH
protected-access:232:8:Issue3066.foobar:Access to a protected member _attr of a client class:HIGH
protected-access:233:8:Issue3066.foobar:Access to a protected member _attr of a client class:HIGH
protected-access:236:8:Issue3066.foobar:Access to a protected member _bar of a client class:HIGH
protected-access:237:8:Issue3066.foobar:Access to a protected member _bar of a client class:HIGH
protected-access:247:12:Issue3066.Aclass.foobar:Access to a protected member _attr of a client class:HIGH
protected-access:249:12:Issue3066.Aclass.foobar:Access to a protected member _attr of a client class:HIGH
protected-access:251:12:Issue3066.Aclass.foobar:Access to a protected member _bar of a client class:HIGH
protected-access:253:12:Issue3066.Aclass.foobar:Access to a protected member _bar of a client class:HIGH
protected-access:267:16:Issue3066.Aclass.Bclass.foobar:Access to a protected member _attr of a client class:HIGH
protected-access:268:16:Issue3066.Aclass.Bclass.foobar:Access to a protected member _attr of a client class:HIGH
protected-access:271:16:Issue3066.Aclass.Bclass.foobar:Access to a protected member _bar of a client class:HIGH
protected-access:272:16:Issue3066.Aclass.Bclass.foobar:Access to a protected member _bar of a client class:HIGH

0 comments on commit db5d77c

Please sign in to comment.