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

Allow private accesses within special dunder methods #4968

Merged
merged 1 commit into from
Jun 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions crates/ruff/resources/test/fixtures/flake8_self/SLF001.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ def _private_func(self):
def __really_private_func(self, arg):
super().__really_private_func(arg)

def __eq__(self, other):
return self._private_thing == other._private_thing


foo = Foo()

Expand Down
126 changes: 91 additions & 35 deletions crates/ruff/src/rules/flake8_self/rules/private_member_access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,50 +75,106 @@ 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) {
if call_path.as_slice() == ["super"] {
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;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
|