Allow private accesses within special dunder methods (#4968)

This commit is contained in:
Charlie Marsh 2023-06-08 13:36:49 -04:00 committed by GitHub
parent 58d08219e8
commit 775d247731
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 134 additions and 75 deletions

View file

@ -53,6 +53,9 @@ class Foo(metaclass=BazMeta):
def __really_private_func(self, arg):
super().__really_private_func(arg)
def __eq__(self, other):
return self._private_thing == other._private_thing
foo = Foo()

View file

@ -75,6 +75,64 @@ pub(crate) fn private_member_access(checker: &mut Checker, expr: &Expr) {
return;
}
// Ignore accesses on instances within special methods (e.g., `__eq__`).
if let ScopeKind::Function(ast::StmtFunctionDef { name, .. }) =
checker.semantic_model().scope().kind
{
if matches!(
name.as_str(),
"__lt__"
| "__le__"
| "__eq__"
| "__ne__"
| "__gt__"
| "__ge__"
| "__add__"
| "__sub__"
| "__mul__"
| "__matmul__"
| "__truediv__"
| "__floordiv__"
| "__mod__"
| "__divmod__"
| "__pow__"
| "__lshift__"
| "__rshift__"
| "__and__"
| "__xor__"
| "__or__"
| "__radd__"
| "__rsub__"
| "__rmul__"
| "__rmatmul__"
| "__rtruediv__"
| "__rfloordiv__"
| "__rmod__"
| "__rdivmod__"
| "__rpow__"
| "__rlshift__"
| "__rrshift__"
| "__rand__"
| "__rxor__"
| "__ror__"
| "__iadd__"
| "__isub__"
| "__imul__"
| "__imatmul__"
| "__itruediv__"
| "__ifloordiv__"
| "__imod__"
| "__ipow__"
| "__ilshift__"
| "__irshift__"
| "__iand__"
| "__ixor__"
| "__ior__"
) {
return;
}
}
if let Expr::Call(ast::ExprCall { func, .. }) = value.as_ref() {
// Ignore `super()` calls.
if let Some(call_path) = collect_call_path(func) {
@ -82,43 +140,41 @@ pub(crate) fn private_member_access(checker: &mut Checker, expr: &Expr) {
return;
}
}
} else {
} else if let Some(call_path) = collect_call_path(value) {
// Ignore `self` and `cls` accesses.
if let Some(call_path) = collect_call_path(value) {
if call_path.as_slice() == ["self"]
|| call_path.as_slice() == ["cls"]
|| call_path.as_slice() == ["mcs"]
{
return;
}
if call_path.as_slice() == ["self"]
|| call_path.as_slice() == ["cls"]
|| call_path.as_slice() == ["mcs"]
{
return;
}
// Ignore accesses on class members from _within_ the class.
if checker
.semantic_model()
.scopes
.iter()
.rev()
.find_map(|scope| match &scope.kind {
ScopeKind::Class(ast::StmtClassDef { name, .. }) => Some(name),
_ => None,
})
.map_or(false, |name| {
if call_path.as_slice() == [name.as_str()] {
checker.semantic_model().find_binding(name).map_or(
false,
|binding| {
// TODO(charlie): Could the name ever be bound to a
// _different_ class here?
binding.kind.is_class_definition()
},
)
} else {
false
}
})
{
return;
}
// Ignore accesses on class members from _within_ the class.
if checker
.semantic_model()
.scopes
.iter()
.rev()
.find_map(|scope| match &scope.kind {
ScopeKind::Class(ast::StmtClassDef { name, .. }) => Some(name),
_ => None,
})
.map_or(false, |name| {
if call_path.as_slice() == [name.as_str()] {
checker
.semantic_model()
.find_binding(name)
.map_or(false, |binding| {
// TODO(charlie): Could the name ever be bound to a
// _different_ class here?
binding.kind.is_class_definition()
})
} else {
false
}
})
{
return;
}
}

View file

@ -40,72 +40,72 @@ SLF001.py:43:12: SLF001 Private member accessed: `_private_thing`
47 | return self.bar
|
SLF001.py:59:7: SLF001 Private member accessed: `_private_thing`
SLF001.py:62:7: SLF001 Private member accessed: `_private_thing`
|
59 | foo = Foo()
60 |
61 | print(foo._private_thing) # SLF001
62 | foo = Foo()
63 |
64 | print(foo._private_thing) # SLF001
| ^^^^^^^^^^^^^^^^^^ SLF001
62 | print(foo.__really_private_thing) # SLF001
63 | print(foo._private_func()) # SLF001
65 | print(foo.__really_private_thing) # SLF001
66 | print(foo._private_func()) # SLF001
|
SLF001.py:60:7: SLF001 Private member accessed: `__really_private_thing`
SLF001.py:63:7: SLF001 Private member accessed: `__really_private_thing`
|
60 | print(foo._private_thing) # SLF001
61 | print(foo.__really_private_thing) # SLF001
63 | print(foo._private_thing) # SLF001
64 | print(foo.__really_private_thing) # SLF001
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ SLF001
62 | print(foo._private_func()) # SLF001
63 | print(foo.__really_private_func(1)) # SLF001
65 | print(foo._private_func()) # SLF001
66 | print(foo.__really_private_func(1)) # SLF001
|
SLF001.py:61:7: SLF001 Private member accessed: `_private_func`
SLF001.py:64:7: SLF001 Private member accessed: `_private_func`
|
61 | print(foo._private_thing) # SLF001
62 | print(foo.__really_private_thing) # SLF001
63 | print(foo._private_func()) # SLF001
64 | print(foo._private_thing) # SLF001
65 | print(foo.__really_private_thing) # SLF001
66 | print(foo._private_func()) # SLF001
| ^^^^^^^^^^^^^^^^^ SLF001
64 | print(foo.__really_private_func(1)) # SLF001
65 | print(foo.bar._private) # SLF001
67 | print(foo.__really_private_func(1)) # SLF001
68 | print(foo.bar._private) # SLF001
|
SLF001.py:62:7: SLF001 Private member accessed: `__really_private_func`
SLF001.py:65:7: SLF001 Private member accessed: `__really_private_func`
|
62 | print(foo.__really_private_thing) # SLF001
63 | print(foo._private_func()) # SLF001
64 | print(foo.__really_private_func(1)) # SLF001
65 | print(foo.__really_private_thing) # SLF001
66 | print(foo._private_func()) # SLF001
67 | print(foo.__really_private_func(1)) # SLF001
| ^^^^^^^^^^^^^^^^^^^^^^^^^ SLF001
65 | print(foo.bar._private) # SLF001
66 | print(foo()._private_thing) # SLF001
68 | print(foo.bar._private) # SLF001
69 | print(foo()._private_thing) # SLF001
|
SLF001.py:63:7: SLF001 Private member accessed: `_private`
SLF001.py:66:7: SLF001 Private member accessed: `_private`
|
63 | print(foo._private_func()) # SLF001
64 | print(foo.__really_private_func(1)) # SLF001
65 | print(foo.bar._private) # SLF001
66 | print(foo._private_func()) # SLF001
67 | print(foo.__really_private_func(1)) # SLF001
68 | print(foo.bar._private) # SLF001
| ^^^^^^^^^^^^^^^^ SLF001
66 | print(foo()._private_thing) # SLF001
67 | print(foo()._private_thing__) # SLF001
69 | print(foo()._private_thing) # SLF001
70 | print(foo()._private_thing__) # SLF001
|
SLF001.py:64:7: SLF001 Private member accessed: `_private_thing`
SLF001.py:67:7: SLF001 Private member accessed: `_private_thing`
|
64 | print(foo.__really_private_func(1)) # SLF001
65 | print(foo.bar._private) # SLF001
66 | print(foo()._private_thing) # SLF001
67 | print(foo.__really_private_func(1)) # SLF001
68 | print(foo.bar._private) # SLF001
69 | print(foo()._private_thing) # SLF001
| ^^^^^^^^^^^^^^^^^^^^ SLF001
67 | print(foo()._private_thing__) # SLF001
70 | print(foo()._private_thing__) # SLF001
|
SLF001.py:65:7: SLF001 Private member accessed: `_private_thing__`
SLF001.py:68:7: SLF001 Private member accessed: `_private_thing__`
|
65 | print(foo.bar._private) # SLF001
66 | print(foo()._private_thing) # SLF001
67 | print(foo()._private_thing__) # SLF001
68 | print(foo.bar._private) # SLF001
69 | print(foo()._private_thing) # SLF001
70 | print(foo()._private_thing__) # SLF001
| ^^^^^^^^^^^^^^^^^^^^^^ SLF001
68 |
69 | print(foo.public_thing)
71 |
72 | print(foo.public_thing)
|