Skip to content

Commit 7eaaab9

Browse files
committed
In stable, retain blanket exemption
1 parent 71fabe2 commit 7eaaab9

File tree

6 files changed

+141
-90
lines changed

6 files changed

+141
-90
lines changed
Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,43 @@
1-
# https://github.com/astral-sh/ruff/issues/24275
2-
31
class C:
4-
def false_positive(this):
5-
this._x = 0 # fine
2+
def non_self_named_method_receiver(this):
3+
this._x = 0 # stable: fine, preview: fine
64

75
@classmethod
8-
def false_positive_2(that):
9-
return that._x # fine
6+
def non_self_named_classmethod_receiver(that):
7+
return that._x # stable: fine, preview: fine
108

11-
def false_negative_1(this, self):
12-
self._x = 1 # error
9+
def non_receiver_named_self_parameter(this, self):
10+
self._x = 1 # stable: fine, preview: error
1311

1412
@classmethod
15-
def false_positive_3(self):
16-
return self._x # fine
13+
def classmethod_named_self(self):
14+
return self._x # stable: fine, preview: fine
1715

1816
@staticmethod
19-
def false_negative_3(self):
20-
return self._x # error
17+
def staticmethod_named_self(self):
18+
return self._x # stable: fine, preview: error
2119

2220

23-
def false_negative_4(self):
24-
return self._x # error
21+
def top_level_self_parameter(self):
22+
return self._x # stable: fine, preview: error
2523

2624

27-
def false_negative_5():
25+
def local_self_binding():
2826
self = C()
29-
return self._x # error
27+
return self._x # stable: fine, preview: error
3028

3129

3230
self = C()
3331

3432

35-
def false_negative_6():
36-
return self._x # error
33+
def global_self_binding():
34+
return self._x # stable: fine, preview: error
3735

3836

39-
def false_negative_7(cls):
40-
return cls._x # error
37+
def top_level_cls_parameter(cls):
38+
return cls._x # stable: fine, preview: error
4139

4240

43-
def false_negative_8():
41+
def local_mcs_binding():
4442
mcs = C
45-
return mcs._x # error
43+
return mcs._x # stable: fine, preview: error

crates/ruff_linter/src/preview.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,11 @@ pub(crate) const fn is_refined_submodule_import_match_enabled(settings: &LinterS
218218
settings.preview.is_enabled()
219219
}
220220

221+
// https://github.com/astral-sh/ruff/issues/24275
222+
pub(crate) const fn is_slf001_self_cls_mcs_enforcement_enabled(settings: &LinterSettings) -> bool {
223+
settings.preview.is_enabled()
224+
}
225+
221226
// https://github.com/astral-sh/ruff/pull/20660
222227
pub(crate) const fn is_type_var_default_enabled(settings: &LinterSettings) -> bool {
223228
settings.preview.is_enabled()

crates/ruff_linter/src/rules/flake8_self/mod.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ mod tests {
88

99
use crate::registry::Rule;
1010
use crate::rules::flake8_self;
11+
use crate::settings::types::PreviewMode;
1112
use crate::test::test_path;
1213
use crate::{assert_diagnostics, settings};
1314
use anyhow::Result;
@@ -27,6 +28,20 @@ mod tests {
2728
Ok(())
2829
}
2930

31+
#[test_case(Rule::PrivateMemberAccess, Path::new("SLF001_2.py"))]
32+
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
33+
let snapshot = format!("preview__{}_{}", rule_code.name(), path.to_string_lossy());
34+
let diagnostics = test_path(
35+
Path::new("flake8_self").join(path).as_path(),
36+
&settings::LinterSettings {
37+
preview: PreviewMode::Enabled,
38+
..settings::LinterSettings::for_rule(rule_code)
39+
},
40+
)?;
41+
assert_diagnostics!(snapshot, diagnostics);
42+
Ok(())
43+
}
44+
3045
#[test]
3146
fn ignore_names() -> Result<()> {
3247
let diagnostics = test_path(
@@ -47,6 +62,7 @@ mod tests {
4762
let diagnostics = test_path(
4863
Path::new("flake8_self/SLF001_custom_decorators.py"),
4964
&settings::LinterSettings {
65+
preview: PreviewMode::Enabled,
5066
pep8_naming: crate::rules::pep8_naming::settings::Settings {
5167
classmethod_decorators: vec!["custom_classmethod".to_string()],
5268
staticmethod_decorators: vec!["custom_staticmethod".to_string()],

crates/ruff_linter/src/rules/flake8_self/rules/private_member_access.rs

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use ruff_text_size::Ranged;
1010

1111
use crate::Violation;
1212
use crate::checkers::ast::Checker;
13+
use crate::preview::is_slf001_self_cls_mcs_enforcement_enabled;
1314
use crate::rules::pylint::helpers::is_dunder_operator_method;
1415

1516
/// ## What it does
@@ -77,6 +78,7 @@ pub(crate) fn private_member_access(checker: &Checker, expr: &Expr) {
7778

7879
let semantic = checker.semantic();
7980
let current_scope = semantic.current_scope();
81+
let enforce_self_cls_mcs = is_slf001_self_cls_mcs_enforcement_enabled(checker.settings());
8082

8183
if semantic.in_annotation() {
8284
return;
@@ -118,6 +120,13 @@ pub(crate) fn private_member_access(checker: &Checker, expr: &Expr) {
118120
}
119121
}
120122

123+
if !enforce_self_cls_mcs
124+
&& let Some(name) = UnqualifiedName::from_expr(value)
125+
&& matches!(name.segments(), ["self" | "cls" | "mcs"])
126+
{
127+
return;
128+
}
129+
121130
if let Expr::Name(name) = value.as_ref() {
122131
// Ignore accesses on class members from _within_ the class.
123132
if semantic
@@ -134,7 +143,12 @@ pub(crate) fn private_member_access(checker: &Checker, expr: &Expr) {
134143
return;
135144
}
136145

137-
if is_same_class_instance(checker, name) {
146+
if is_same_class_instance(
147+
name,
148+
semantic,
149+
&checker.settings().pep8_naming.classmethod_decorators,
150+
&checker.settings().pep8_naming.staticmethod_decorators,
151+
) {
138152
return;
139153
}
140154
}
@@ -171,12 +185,21 @@ pub(crate) fn private_member_access(checker: &Checker, expr: &Expr) {
171185
///
172186
/// This function is intentionally naive and does not handle more complex cases.
173187
/// It is expected to be expanded overtime, possibly when type-aware APIs are available.
174-
fn is_same_class_instance(checker: &Checker, name: &ast::ExprName) -> bool {
175-
if is_method_receiver(checker, name) {
188+
fn is_same_class_instance(
189+
name: &ast::ExprName,
190+
semantic: &SemanticModel,
191+
classmethod_decorators: &[String],
192+
staticmethod_decorators: &[String],
193+
) -> bool {
194+
if is_method_receiver(
195+
name,
196+
semantic,
197+
classmethod_decorators,
198+
staticmethod_decorators,
199+
) {
176200
return true;
177201
}
178202

179-
let semantic = checker.semantic();
180203
let Some(binding_id) = semantic.resolve_name(name) else {
181204
return false;
182205
};
@@ -185,9 +208,14 @@ fn is_same_class_instance(checker: &Checker, name: &ast::ExprName) -> bool {
185208
typing::check_type::<SameClassInstanceChecker>(binding, semantic)
186209
}
187210

188-
fn is_method_receiver(checker: &Checker, name: &ast::ExprName) -> bool {
189-
let semantic = checker.semantic();
190-
211+
/// Return `true` if `name` resolves to the first parameter of a syntactic
212+
/// method receiver, including class methods and `__new__`.
213+
fn is_method_receiver(
214+
name: &ast::ExprName,
215+
semantic: &SemanticModel,
216+
classmethod_decorators: &[String],
217+
staticmethod_decorators: &[String],
218+
) -> bool {
191219
let Some(binding_id) = semantic.resolve_name(name) else {
192220
return false;
193221
};
@@ -225,8 +253,8 @@ fn is_method_receiver(checker: &Checker, name: &ast::ExprName) -> bool {
225253
&function.decorator_list,
226254
parent_scope,
227255
semantic,
228-
&checker.settings().pep8_naming.classmethod_decorators,
229-
&checker.settings().pep8_naming.staticmethod_decorators,
256+
classmethod_decorators,
257+
staticmethod_decorators,
230258
),
231259
function_type::FunctionType::Method
232260
| function_type::FunctionType::ClassMethod
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
---
2+
source: crates/ruff_linter/src/rules/flake8_self/mod.rs
3+
---
4+
SLF001 Private member accessed: `_x`
5+
--> SLF001_2.py:10:9
6+
|
7+
9 | def non_receiver_named_self_parameter(this, self):
8+
10 | self._x = 1 # stable: fine, preview: error
9+
| ^^^^^^^
10+
11 |
11+
12 | @classmethod
12+
|
13+
14+
SLF001 Private member accessed: `_x`
15+
--> SLF001_2.py:18:16
16+
|
17+
16 | @staticmethod
18+
17 | def staticmethod_named_self(self):
19+
18 | return self._x # stable: fine, preview: error
20+
| ^^^^^^^
21+
|
22+
23+
SLF001 Private member accessed: `_x`
24+
--> SLF001_2.py:22:12
25+
|
26+
21 | def top_level_self_parameter(self):
27+
22 | return self._x # stable: fine, preview: error
28+
| ^^^^^^^
29+
|
30+
31+
SLF001 Private member accessed: `_x`
32+
--> SLF001_2.py:27:12
33+
|
34+
25 | def local_self_binding():
35+
26 | self = C()
36+
27 | return self._x # stable: fine, preview: error
37+
| ^^^^^^^
38+
|
39+
40+
SLF001 Private member accessed: `_x`
41+
--> SLF001_2.py:34:12
42+
|
43+
33 | def global_self_binding():
44+
34 | return self._x # stable: fine, preview: error
45+
| ^^^^^^^
46+
|
47+
48+
SLF001 Private member accessed: `_x`
49+
--> SLF001_2.py:38:12
50+
|
51+
37 | def top_level_cls_parameter(cls):
52+
38 | return cls._x # stable: fine, preview: error
53+
| ^^^^^^
54+
|
55+
56+
SLF001 Private member accessed: `_x`
57+
--> SLF001_2.py:43:12
58+
|
59+
41 | def local_mcs_binding():
60+
42 | mcs = C
61+
43 | return mcs._x # stable: fine, preview: error
62+
| ^^^^^^
63+
|
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,4 @@
11
---
22
source: crates/ruff_linter/src/rules/flake8_self/mod.rs
33
---
4-
SLF001 Private member accessed: `_x`
5-
--> SLF001_2.py:12:9
6-
|
7-
11 | def false_negative_1(this, self):
8-
12 | self._x = 1 # error
9-
| ^^^^^^^
10-
13 |
11-
14 | @classmethod
12-
|
134

14-
SLF001 Private member accessed: `_x`
15-
--> SLF001_2.py:20:16
16-
|
17-
18 | @staticmethod
18-
19 | def false_negative_3(self):
19-
20 | return self._x # error
20-
| ^^^^^^^
21-
|
22-
23-
SLF001 Private member accessed: `_x`
24-
--> SLF001_2.py:24:12
25-
|
26-
23 | def false_negative_4(self):
27-
24 | return self._x # error
28-
| ^^^^^^^
29-
|
30-
31-
SLF001 Private member accessed: `_x`
32-
--> SLF001_2.py:29:12
33-
|
34-
27 | def false_negative_5():
35-
28 | self = C()
36-
29 | return self._x # error
37-
| ^^^^^^^
38-
|
39-
40-
SLF001 Private member accessed: `_x`
41-
--> SLF001_2.py:36:12
42-
|
43-
35 | def false_negative_6():
44-
36 | return self._x # error
45-
| ^^^^^^^
46-
|
47-
48-
SLF001 Private member accessed: `_x`
49-
--> SLF001_2.py:40:12
50-
|
51-
39 | def false_negative_7(cls):
52-
40 | return cls._x # error
53-
| ^^^^^^
54-
|
55-
56-
SLF001 Private member accessed: `_x`
57-
--> SLF001_2.py:45:12
58-
|
59-
43 | def false_negative_8():
60-
44 | mcs = C
61-
45 | return mcs._x # error
62-
| ^^^^^^
63-
|

0 commit comments

Comments
 (0)