Skip to content
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
25 changes: 25 additions & 0 deletions crates/ruff_linter/resources/test/fixtures/flake8_self/SLF001_1.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,28 @@ class M(type):
def f(mcs):
cls = mcs()
cls._value = 1


# https://github.com/astral-sh/ruff/issues/24140

from typing import Annotated, Self

class Sit:
def __init__(self, x: int) -> None:
self._x = x

def f(self) -> None:
this = self
print(this._x) # fine (assigned from self)

def g(self, other: Self) -> None:
print(other._x) # fine (annotated as Self)

def h(self, other: Annotated[Self, "meta"]) -> None:
print(other._x) # fine (Annotated[Self, ...])

@staticmethod
def s() -> None:
self = object()
alias = self
print(alias._x) # error (self is not an instance parameter)
Original file line number Diff line number Diff line change
Expand Up @@ -202,17 +202,34 @@ impl SameClassInstanceChecker {
}

impl TypeChecker for SameClassInstanceChecker {
/// `C`, `C[T]`, `Annotated[C, ...]`, `Annotated[C[T], ...]`
/// `C`, `C[T]`, `Annotated[C, ...]`, `Annotated[C[T], ...]`, `Self`, `Annotated[Self, ...]`
fn match_annotation(annotation: &Expr, semantic: &SemanticModel) -> bool {
let Some(class_name) = find_class_name(annotation, semantic) else {
let inner = unwrap_annotated(annotation, semantic);

if semantic.match_typing_expr(inner, "Self") {
return true;
}

let Expr::Name(class_name) = inner else {
return false;
};

Self::is_current_class_name(class_name, semantic)
}

/// `cls()`, `C()`, `C[T]()`, `super().__new__()`
/// `cls()`, `C()`, `C[T]()`, `super().__new__()`, `self`
fn match_initializer(initializer: &Expr, semantic: &SemanticModel) -> bool {
// `this = self` — a direct assignment from `self`, but only when
// `self` is actually a function parameter (not a local rebinding).
if let Expr::Name(name) = initializer
&& name.id == "self"
&& semantic
.resolve_name(name)
.is_some_and(|id| matches!(semantic.binding(id).kind, BindingKind::Argument))
{
return true;
}

let Expr::Call(call) = initializer else {
return false;
};
Expand Down Expand Up @@ -241,21 +258,21 @@ impl TypeChecker for SameClassInstanceChecker {
}
}

/// Convert `Annotated[C[T], ...]` to `C` (and similar) to `C` recursively.
fn find_class_name<'a>(expr: &'a Expr, semantic: &'a SemanticModel) -> Option<&'a ast::ExprName> {
/// Unwrap `Annotated[X, ...]` and `C[T]` to the innermost type expression.
fn unwrap_annotated<'a>(expr: &'a Expr, semantic: &'a SemanticModel) -> &'a Expr {
match expr {
Expr::Name(name) => Some(name),
Expr::Subscript(ast::ExprSubscript { value, slice, .. }) => {
if semantic.match_typing_expr(value, "Annotated")
&& let Some(tuple) = slice.as_tuple_expr()
&& let [inner, ..] = &tuple.elts[..]
{
return unwrap_annotated(inner, semantic);
}
if semantic.match_typing_expr(value, "Annotated") {
let [expr, ..] = &slice.as_tuple_expr()?.elts[..] else {
return None;
};

return find_class_name(expr, semantic);
return expr;
}

find_class_name(value, semantic)
unwrap_annotated(value, semantic)
}
_ => None,
_ => expr,
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
---
source: crates/ruff_linter/src/rules/flake8_self/mod.rs
---

SLF001 Private member accessed: `_x`
--> SLF001_1.py:70:15
|
68 | self = object()
69 | alias = self
70 | print(alias._x) # error (self is not an instance parameter)
| ^^^^^^^^
|
Loading